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.
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
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:
- Node 24.19.0.
- Appium 3.6.0.
- Inspector plugin 2026.7.1.
- UiAutomator2 8.2.2.
- Android command-line tools 15859902, platform-tools, Emulator, API 36,
build-tools 36.0.0 with
aapt2, and a host-compatible Google APIs image. - 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:
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:
| Option | Release default | Constraint |
|---|---|---|
--api-level | 36 | Must match the release manifest. |
--device-profile | pixel_8 | Must match the release manifest. |
--image-tag | google_apis | Must match the release manifest. |
--abi | host | Resolves to x86_64 or arm64-v8a for the current host. |
--avd-name | shaft_pixel_8_api_36 | Use a safe Android identifier. |
--ram-mb | 4096 | Choose 2048 through 32768 MB. |
--cores | 2 | Choose 1 through 16 cores. |
--port | 4723 | Choose 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:
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:
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
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.
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
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:
| Option | Default | Effect |
|---|---|---|
--offline | false | Require verified cached artifacts and disable network access. |
--auto-start | false | Bind a startup request for providers that own a service. |
--prefer-system-tools=true|false | true | Bind whether a provider may prefer a compatible host tool. |
--reuse-owned-processes=true|false | true | Bind whether a provider may reuse compatible SHAFT-owned processes. |
--startup-timeout <duration> | PT2M | Bind a positive ISO-8601 startup timeout for providers with lifecycle support. |
--shutdown-timeout <duration> | PT30S | Bind 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.
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:
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:
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 code | Meaning |
|---|---|
0 | Ready or successful. |
2 | Invalid input, policy, or approval. |
3 | Missing or degraded readiness. |
4 | No provider supports the requested operation. |
5 | Execution 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.