Skip to main content

Set up local infrastructure

Use SHAFT's setup surface to inspect external prerequisites or install supported tools into SHAFT-owned user directories. The safe default is EXTERNAL: SHAFT diagnoses the host without downloading, installing, or starting anything.

The setup catalog includes web, mobile, Grid, reporting, OCR, agent-tool, and local-AI profiles. Provider-backed managed installation includes REPORTING and MOBILE_ANDROID. Reporting uses SHAFT's pinned, SHA-256-verified portable Node and adds Allure 3. Android uses the same portable Node owner and adds a reviewed Android SDK, Appium project, emulator, and SHAFT-owned virtual device.

Inspect the catalog and host

Install shaft-cli through the shaft-cli installation flow, then list the setup profiles:

shaft-cli setup catalog
shaft-cli setup doctor --profile REPORTING
shaft-cli setup status --profile REPORTING

Add --json to catalog, doctor, status, or verify when a script needs a versioned machine-readable result. Readiness commands exit with 0 when ready and 3 when the profile is missing or degraded.

Review and approve an installation

Create an exact plan before allowing any mutation. Use an absolute path for the plan file:

shaft-cli setup plan \
--profile REPORTING \
--mode MANAGED \
--output /absolute/path/reporting-plan.json

Review the JSON plan and copy the printed sha256: digest. Apply that exact plan with the same policy options used to create it:

shaft-cli setup install \
--plan /absolute/path/reporting-plan.json \
--approve sha256:<digest>

shaft-cli setup verify --profile REPORTING

apply and update are aliases for install. SHAFT rejects a changed or stale plan, a mismatched policy, a missing license acceptance, or an artifact whose checksum does not match before publishing it as installed.

warning

Treat the plan digest as a one-plan approval, not a general consent switch. Changing a version, source, checksum, destination, timeout, or policy option changes the digest and requires a new review.

Install managed Android and Appium

Not in a published release yet

The MOBILE_ANDROID provider is available on SHAFT_ENGINE main after engine PR #4913 and its readiness follow-up #4917. Use these commands with a source build until a containing SHAFT release is published.

Use this profile when you want SHAFT to own one local Android emulator and its Appium server. The release plan contains exactly six ordered actions:

  1. Node 24.19.0.
  2. Appium 3.6.0.
  3. Inspector plugin 2026.7.1.
  4. UiAutomator2 8.2.2.
  5. Android command-line tools 15859902, platform-tools, Emulator, API 36, build-tools 36.0.0 with aapt2, and a host-compatible Google APIs image.
  6. A SHAFT-owned Pixel 8 AVD.

SHAFT verifies the official command-line-tools archive hash and the packaged Appium dependency lock before publication. It runs npm inside a versioned SHAFT project, not as a global install, and never enables Appium relaxed security for the owned server.

Check platform prerequisites

Install a compatible JDK and enable hardware virtualization for your host before planning a runtime. On Linux, grant the current user access to KVM. On Windows, enable a supported Android Emulator hypervisor path. On macOS, run on a host that can use Apple's virtualization support. SHAFT diagnoses these conditions with emulator -accel-check; it does not elevate, enable firmware features, change group membership, install host drivers, or edit shell profiles.

Keep loopback ports 5554 and 5555 free for the owned emulator. The default Appium port is 4723; choose another free port with --port when creating the plan. The selected system-image ABI must match the host architecture.

Review the Android plan and license

Run the shared command sequence below. Stop after plan, inspect all six actions and the printed digest, then continue with that exact digest:

Prepare and run managed Android
shaft-cli setup status --profile MOBILE_ANDROID --mode MANAGED
shaft-cli setup plan \
--profile MOBILE_ANDROID \
--mode MANAGED \
--output /absolute/path/android-plan.json
shaft-cli setup install \
--plan /absolute/path/android-plan.json \
--approve sha256:<reviewed-digest> \
--accept-license android-sdk-license
shaft-cli setup verify --profile MOBILE_ANDROID --mode MANAGED
shaft-cli setup start \
--plan /absolute/path/android-plan.json \
--approve sha256:<reviewed-digest> \
--accept-license android-sdk-license
shaft-cli setup logs --profile MOBILE_ANDROID
shaft-cli setup stop --profile MOBILE_ANDROID

android-sdk-license is an explicit approval identifier. Omitting it or supplying a different identifier fails before SHAFT creates setup roots, starts a process, or contacts the network. SHAFT supplies consent only to the reviewed package installation; it does not run a blanket sdkmanager --licenses acceptance.

Use these selectors on doctor, status, plan, verify, start, logs, or stop. You may repeat them on install, but every repeated value must match the plan:

OptionRelease defaultConstraint
--api-level36Must match the release manifest.
--device-profilepixel_8Must match the release manifest.
--image-taggoogle_apisMust match the release manifest.
--abihostResolves to x86_64 or arm64-v8a for the current host.
--avd-nameshaft_pixel_8_api_36Use a safe Android identifier.
--ram-mb4096Choose 2048 through 32768 MB.
--cores2Choose 1 through 16 cores.
--port4723Choose 1024 through 65535, excluding 5554 and 5555.

The persisted plan remains authoritative. Install reconstructs the typed request from the plan and rejects changed, missing, duplicate, or unknown selection metadata.

Use the cache and offline mode

SHAFT stores verified downloads and npm cache data under its cache root. It stores versioned Node, Appium, Android SDK, AVD, receipts, leases, and logs under its data root. Set both --cache-root and --data-root to absolute, dedicated user paths if you override the defaults.

Pass --offline to both plan and install when network access is prohibited. The Android provider currently accepts offline installation only when every required managed component is already present and verifies exactly. A cold, partial, corrupt, wrong-version, or linked state fails before npm, sdkmanager, or another setup process starts. Run one approved online install to populate the managed state before relying on offline reuse.

Start, inspect, and recover the owned runtime

start requires the reviewed plan, matching approval, accepted license, and a compatible final receipt. It starts the emulator first and waits for the exact serial, Android boot completion, package manager, selected AVD, and system image. It then starts Appium on 127.0.0.1 and accepts it only when /status reports Appium 3.6.0.

Compatible callers share a durable lease and increment its reference count. The final release or setup stop shuts down Appium, then the emulator, after validating each PID, start instant, command, root, and endpoint. SHAFT never adopts or kills an unknown process. setup logs reads only the two owned log paths and rejects a file larger than 2 MiB.

If startup fails after the emulator launches, SHAFT stops only the processes started by that call and retains their logs. If a lease is partially alive or its identity differs from the live process, preserve the lease and logs and inspect them before manual recovery. If both processes are gone, the next start removes the stale lease. Resolve occupied ports, acceleration failures, or wrong SDK/AVD revisions, then retry the same reviewed plan.

Use the typed Java API

Use AndroidSetupRequest with the additive SHAFT.Infrastructure overloads. The first phase writes a plan for review:

PlanManagedAndroid.java
import com.shaft.driver.SHAFT;
import com.shaft.infrastructure.AndroidSetupRequest;
import com.shaft.infrastructure.SetupMode;
import com.shaft.infrastructure.SetupOptions;
import com.shaft.infrastructure.SetupPlan;
import com.shaft.infrastructure.SetupPlanStore;
import com.shaft.infrastructure.SetupProfile;
import com.shaft.infrastructure.ShaftCachePaths;

import java.nio.file.Path;

public final class PlanManagedAndroid {
public static void main(String[] args) throws Exception {
SetupOptions options = SetupOptions
.defaults(SetupProfile.MOBILE_ANDROID, ShaftCachePaths.current())
.withMode(SetupMode.MANAGED);
AndroidSetupRequest request = AndroidSetupRequest.defaults();
SetupPlan plan = SHAFT.Infrastructure.plan(options, request);
SetupPlanStore.write(Path.of(args[0]).toAbsolutePath(), plan);
System.out.println(plan.digest());
}
}

Run installation and startup only after a separate review supplies the digest:

RunReviewedAndroid.java
import com.shaft.driver.SHAFT;
import com.shaft.infrastructure.AndroidSetupRequest;
import com.shaft.infrastructure.ManagedEnvironment;
import com.shaft.infrastructure.SetupApproval;
import com.shaft.infrastructure.SetupMode;
import com.shaft.infrastructure.SetupOptions;
import com.shaft.infrastructure.SetupPlan;
import com.shaft.infrastructure.SetupPlanStore;
import com.shaft.infrastructure.SetupProfile;
import com.shaft.infrastructure.ShaftCachePaths;

import java.nio.file.Path;
import java.time.Instant;
import java.util.Set;

public final class RunReviewedAndroid {
public static void main(String[] args) throws Exception {
SetupPlan plan = SetupPlanStore.read(Path.of(args[0]).toAbsolutePath());
AndroidSetupRequest request = AndroidSetupRequest.fromPlan(plan);
SetupOptions options = SetupOptions
.defaults(SetupProfile.MOBILE_ANDROID, ShaftCachePaths.current())
.withMode(SetupMode.MANAGED);
SetupApproval approval = new SetupApproval(
System.getenv("SHAFT_APPROVED_SETUP_DIGEST"),
Instant.now(),
Set.of("android-sdk-license"));

SHAFT.Infrastructure.install(plan, approval, options, request);
try (ManagedEnvironment runtime =
SHAFT.Infrastructure.start(plan, approval, options, request)) {
System.out.println(runtime.endpoint().orElseThrow());
}
}
}

Closing ManagedEnvironment releases this caller's lease. Another compatible caller can keep the same runtime alive until its own release.

Install managed Lighthouse

Not released

This workflow depends on SHAFT Engine issue #4884. It is not yet available on SHAFT_ENGINE main or in a published SHAFT release. Keep using the current Lighthouse flow until a release that contains the managed LIGHTHOUSE provider is available.

Install the LIGHTHOUSE profile before a test calls generateLightHouseReport(). SHAFT manages its own Node 24.19.0 and Lighthouse 13.4.1 under the configured tool root. It does not use a global Node or npm installation.

Prepare managed Lighthouse
shaft-cli setup status --profile LIGHTHOUSE
shaft-cli setup plan \
--profile LIGHTHOUSE \
--mode MANAGED \
--output /absolute/path/lighthouse-plan.json
shaft-cli setup install \
--plan /absolute/path/lighthouse-plan.json \
--approve sha256:<reviewed-digest>
shaft-cli setup verify --profile LIGHTHOUSE

Stop after plan and review the JSON plus its printed digest before running install. The plan binds the exact Node artifact, Lighthouse package archive, bundled dependency lock, destinations, and policy. Report generation only uses an already verified installation; it never installs a missing toolchain.

For a cold offline install, pass --offline to both plan and install. The SHAFT cache must already contain the verified Node and Lighthouse artifacts and the complete transitive npm cache required by the bundled lock. An already verified managed installation can be reused without those cached downloads. A missing or corrupt entry needed by a cold install fails without network access or a partially published Lighthouse installation.

Use the default SHAFT roots for this preview. The CLI can bind separate custom cache and data roots, but the Browser Actions runtime currently exposes only infrastructure.cacheDirectory and cannot reproduce every such layout.

Preview: managed OCR setup

Not released

This workflow is not yet available on SHAFT_ENGINE main or in a published SHAFT release. Keep using the current OCR first-use model flow until a release that contains the complete setup stack is available.

The preview defaults to the eng and ara model bundle. Omit --language to use that bundle, or repeat exact Tesseract codes on plan and selected status/verify commands. Install recovers the selected languages from the reviewed plan:

shaft-cli setup plan --profile OCR --mode MANAGED \
--language fra --language deu \
--output /absolute/path/ocr-plan.json
shaft-cli setup install --plan /absolute/path/ocr-plan.json \
--approve sha256:<reviewed-digest>
shaft-cli setup verify --profile OCR --language fra --language deu

The reviewed actions bind the normalized component set. Repeating --language during install is optional and must match when supplied. The planned OCR provider enforces --offline: it can accept a verified installed model, legacy model, or artifact-cache entry without network access. It has no start, stop, or logs lifecycle.

Keep policy options identical

Plan and install accept the same execution policy:

OptionDefaultEffect
--offlinefalseRequire verified cached artifacts and disable network access.
--auto-startfalseBind a startup request for providers that own a service.
--prefer-system-tools=true|falsetrueBind whether a provider may prefer a compatible host tool.
--reuse-owned-processes=true|falsetrueBind whether a provider may reuse compatible SHAFT-owned processes.
--startup-timeout <duration>PT2MBind a positive ISO-8601 startup timeout for providers with lifecycle support.
--shutdown-timeout <duration>PT30SBind a positive ISO-8601 shutdown timeout for providers with lifecycle support.

Pass any non-default option to both commands. You may also pass an absolute --cache-root and --data-root pair to both commands; SHAFT rejects a single root or a relative path.

The current REPORTING provider enforces --offline. It has no owned service, so auto-start, process reuse, and lifecycle timeouts are policy-bound for provider parity but do not change a reporting install. Reporting installs SHAFT-owned portable tools rather than adopting system Node or Allure. The unreleased LIGHTHOUSE provider follows the same lifecycle shape.

warning

Custom roots become mutable SHAFT-owned storage. Use dedicated, user-scoped directories. Do not point them at a repository, shared or system directory, or a path reached through a symlink alias.

Use the Java API

Configure the same policy through SHAFT.Properties.infrastructure, then plan and explicitly approve the immutable result:

ReportingInfrastructure.java
import com.shaft.driver.SHAFT;
import com.shaft.infrastructure.SetupMode;
import com.shaft.infrastructure.SetupPlan;
import com.shaft.infrastructure.SetupPlanStore;
import com.shaft.infrastructure.SetupProfile;

import java.nio.file.Path;

public final class ReportingInfrastructure {
public static void main(String[] args) throws Exception {
SHAFT.Properties.infrastructure.set()
.profile(SetupProfile.REPORTING)
.mode(SetupMode.MANAGED)
.offline(false)
.autoStart(false);

SetupPlan plan = SHAFT.Infrastructure.plan();
SetupPlanStore.write(Path.of(args[0]).toAbsolutePath(), plan);

// Stop this phase and review the written JSON plus this digest.
System.out.println(plan.digest());
}
}

Run the mutation in a separate phase. Supply the digest you reviewed instead of deriving it from a newly generated plan:

InstallReviewedReportingPlan.java
import com.shaft.driver.SHAFT;
import com.shaft.infrastructure.SetupApproval;
import com.shaft.infrastructure.SetupMode;
import com.shaft.infrastructure.SetupPlan;
import com.shaft.infrastructure.SetupPlanStore;
import com.shaft.infrastructure.SetupProfile;
import com.shaft.infrastructure.SetupReceipt;

import java.nio.file.Path;
import java.time.Instant;
import java.util.Set;

public final class InstallReviewedReportingPlan {
public static void main(String[] args) throws Exception {
SetupPlan plan = SetupPlanStore.read(Path.of(args[0]).toAbsolutePath());
String reviewedDigest = System.getenv("SHAFT_APPROVED_SETUP_DIGEST");

// Recreate every policy value used by the planning phase.
SHAFT.Properties.infrastructure.set()
.profile(SetupProfile.REPORTING)
.mode(SetupMode.MANAGED)
.offline(false)
.autoStart(false);

if (!plan.executionPolicyDigest().equals(
SHAFT.Infrastructure.options().policyDigest())) {
throw new IllegalStateException(
"Current setup policy differs from the reviewed plan");
}

SetupApproval approval = new SetupApproval(
reviewedDigest, Instant.now(), Set.of());
SetupReceipt receipt = SHAFT.Infrastructure.install(plan, approval);
System.out.println(receipt.planDigest());
}
}

Use SHAFT.Infrastructure.catalog(), doctor(), status(), and verify() for read-only inspection. install(...) and start(...) require both the exact plan and its approval; there is no unapproved mutation overload.

Use an absolute plan path in both Java phases. Reproduce every property and path from the planning phase before installation; schema 3 rejects even a single policy or destination difference.

The configuration defaults are:

infrastructure.mode=EXTERNAL
infrastructure.profile=REPORTING
infrastructure.cacheDirectory=
infrastructure.offline=false
infrastructure.autoStart=false
infrastructure.preferSystemTools=true
infrastructure.reuseOwnedProcesses=true
infrastructure.startupTimeout=PT2M
infrastructure.shutdownTimeout=PT30S

Set infrastructure.cacheDirectory only to an absolute path. An empty value uses the platform-specific SHAFT cache and application-data locations.

Understand remote precedence

An explicit remote execution address keeps endpoint-backed profiles external, even when infrastructure.mode=MANAGED. This applies to web, Selenium Grid, mobile, and Healenium profiles, so a remote test configuration cannot unexpectedly provision local infrastructure. It does not change unrelated profiles such as REPORTING or LIGHTHOUSE.

Interpret CLI failures

Exit codeMeaning
0Ready or successful.
2Invalid input, policy, or approval.
3Missing or degraded readiness.
4No provider supports the requested operation.
5Execution or integrity failure.

An install is atomic per action, not across the entire plan. If a later action fails, an earlier verified action can remain installed while the final profile receipt is absent. Fix the failure and retry the same approved plan; SHAFT re-verifies compatible completed state before continuing.

If setup reports that both an artifact destination and its .quarantine recovery file exist, preserve both files and stop. Verify each against the reviewed action checksum, then deliberately retain the valid copy before retrying. Never blindly delete the quarantine: it may be the only known-good pre-replacement artifact.

start and stop return unsupported for profiles without an owned service. SHAFT does not adopt or stop an unknown process. Use shaft-cli setup logs --profile REPORTING to read an existing provider log; it returns 3 when no owned log exists.