Provider provisioning ships in
@peaqos/peaq-os-sdk 0.5.0+ (JS/TS) and peaq-os-sdk 0.5.0+ (Python). It is machine-local: the runner executes entirely on the machine, against no external API.apiVersion: node-provider.peaq.network/v1alpha1) into an ordered, auditable install that provisions a machine as a node. All provider-specific complexity lives in the manifest; the runner only understands the schema’s generic vocabulary: phases, steps, prompts, captures, secrets, and verification probes. It never hardcodes any provider’s setup.
Design invariants
- Verification, not exit codes, defines success. A machine is a live provider only when the manifest’s success probes pass.
- Secrets never leak. Secret inputs, secret captures, and per-step redaction are applied before anything is streamed, logged, or persisted.
- Sudo is explicit and scoped. Auto mode elevates only the commands the manifest marks, and only within the operator’s granted allowlist.
- Handoffs are real stops. Owner-action steps block until the owner confirms; a confirmation is never synthesised.
- Payout context defaults to the machine wallet, never the operator’s. The runner exposes the address as generic context; the manifest decides how it maps into commission fields.
The lifecycle
The namespace isclient.provisioning in JS/TS and the peaq_os_sdk.provisioning module in Python; every method is also importable standalone.
Fetch a manifest
Manifests are hosted on a public repo and pulled on demand. The repo base URL is configuration, never hardcoded. The JS SDK is YAML-agnostic (pass a parse function from a YAML library you already depend on); the Python SDK parses YAML itself.- Version: pass an explicit
versionor resolve"latest"; the concrete version is read back from the document and recorded for reproducibility. - Integrity: pass an expected digest (
expectedDigestin JS,checksumin Python) to verify the fetched document; a mismatch fails closed and the manifest is never returned or used. One Python caveat: the fetched bytes are written tocache_dirbefore the digest check, so clear the cache after a mismatch (or keepchecksumon every read, includingoffline=Trueones); a later cache read without it would return the rejected document. - Pinning: request the raw source bytes (
includeSource: truein JS) and compute a deterministicsha256:<hex>pin withmanifestPinDigest, so a later verify can prove it ran against the exact document that provisioned the node. The CLI’s state file uses this pin. - Cache and offline: caching is opt-in. Configure it (
cachein JS,cache_dirin Python) and a network fetch stores the raw document for offline mode to reuse; configure nothing and nothing is stored, so offline mode has nothing to read. The document is re-validated against the schema on every read.
Manifest YAML keys are camelCase (
apiVersion, onFailure, allowedCommands). The JS SDK keeps them camelCase; the Python SDK maps them onto snake_case dataclass fields (validation.min/max become min_value/max_value).Resolve inputs
resolveInputs / resolve_inputs merges caller values with the manifest’s declared inputs: it enforces required, applies defaults, type-checks every declared type (string, int, bool, enum, url, hostname, bytesize, array, object), and applies validation rules (values, pattern, min, max). Inputs marked secret: true are flagged and never echoed in errors.
Pre-flight is a gate
runPreflight / run_preflight runs each check and reports passed, blocked, and per-check results. A check with onFailure: block that fails blocks provisioning; onFailure: warn surfaces without blocking. A privileged check that cannot be auto-run is surfaced for the owner, and a surfaced blocking check still blocks the gate (fail closed). provision refuses to start while the gate is blocked.
Provision: manual vs auto mode
provision runs in manual or auto mode, and every step also declares its own mode (manual, auto, or both). The manifest decides; the runner honours it.
Sudo is explicit per command (
never / optional / required). The runner never elevates a command the manifest does not mark. In auto mode, a sudo: required command runs only under a sudo grant matched by prefix against the manifest’s allowedCommands; the effective grant is the intersection of the two (the narrower side wins), so an operator can scope down but the grant can never broaden beyond the manifest. A privileged command outside the grant fails closed: it is not run unprivileged.
Step kinds
Prompts, captures, and the machine wallet
Interactive prompts fill frominput (a resolved input), capture (an earlier step’s value), or owner (answered interactively, never auto-filled). Captured values thread by key into later steps, verification, and post-provision. The runner exposes the machine wallet address as run context (context.machineWalletAddress in JS, where context.machineWallet is only a legacy alias slated for removal; {{ context.machine_wallet_address }} in Python, supplied via context=); the manifest declares how that maps into any commission or payout field. The runner itself holds no commission semantics.
Owner-action handoffs
Wallet funding, DNS changes, hardware-wallet signing, and SSH access confirmations areowner-action steps: real pause points. The runner emits the interpolated (redacted) instructions and blocks until the owner returns the declared confirmation. A missing handler or a mismatched answer fails the step.
Idempotency and resumability
Each step may declareidempotency: a check command runs first and skips the step when the effect is already present, and rerun policy is safe, unsafe, or requires-confirmation. The confirmation handler differs per SDK: JS prompts via onRerunConfirm and raises StepExecutionError when the handler is missing, while Python calls on_confirm_rerun and, when it is omitted, silently skips a requires-confirmation step whose check passes. Pass the handler in Python if the re-run decision matters.
An interrupted run always leaves a resumable state: provision returns a resume state, attaches one to the failure error, and (optionally) emits it at every step boundary via onStateChange / on_state_change, so a caller can checkpoint durably as the run progresses. The state is JSON-round-trippable and carries a schema version, the completed step ids (keyed by phase/step), a cursor, and the resolved non-secret inputs and captures. A resumed run skips completed steps and restores the non-secret captures.
The boundary callback also fires at the pre-confirmation owner-action pause, with the owner-action excluded: a run killed at a funding or DNS handoff resumes by re-entering that handoff, never by skipping it.
rerun policy is safe, takes a caller-re-supplied value via resumeSecrets / resume_secrets otherwise, and fails with ResumeNotPossibleError (naming the step and the missing keys) when neither applies. A step never interpolates a missing or empty secret.
Verification closes the loop
verifyProvisioning / verify_provisioning runs the manifest’s probes. Success requires every probe in verification.success.allOf to pass; exit-zero installs alone never count. A privileged probe that cannot be elevated is not run unprivileged: it is reported as failed. ensureVerified in JS/TS, and the result’s own raise_if_failed() in Python, raises VerificationFailedError (listing the failed probes) unless verification succeeded; report the node live only when it does not raise.
Post-provision operations
runPostProvision / run_post_provision runs one opt-in operator utility by id (tail logs, check balance, and similar), honouring its mode, sudo policy, and actor. These are helpers only, never part of the success gate.
Secrets and redaction
A run-scoped redaction registry holds every secret input value plus the declared extraction pattern of every secret capture, registered before its step runs. Every streamed chunk, event, captured output, rendered-file echo, and error message passes through redaction before it leaves the SDK. A secret stdout/stderr capture with no pattern suppresses raw stream emission entirely. Typed errors carry only structural metadata (ids, paths, exit codes), never secret material.Errors
Related
- Monetize function: where provisioning fits in the flow
- Heartbeat SDK reference: report presence once provisioned
- Monetization opt-in API

