SSH Compute Driver
Use the SSH Compute Driver to run embedded OpenClaw on operator-managed
Linux hosts. It prepares each Namespace over SSH and runs one systemd gateway
unit per Agent. OCC still owns Agent resources, authorization, revisions, and
activation. Trusted Installation YAML can select compute-ssh (implementation
occ/ssh) in development or production.
Select harnessAuth: { "method": "runtime" } and supply model credentials on
the host. OCC does not deliver or validate those credentials; confirm access
with a real model response. SSH does not support dedicated Codex, an OCC
Secret-backed model API key, or a managed ChatGPT account. Use
Kubernetes Compute if you need those options.
Requirements and configuration
Provision Linux with systemd, util-linux flock, getent, the shadow account
management tools (useradd, userdel, groupadd, groupdel), a root SSH
account, Node.js 24, and a readable OpenClaw entrypoint. The controller processes
need the system ssh executable and protected identity and known-hosts files.
The Compose node:24-bookworm base includes OpenSSH; provide the client
yourself if you choose another controller image base. The Driver never installs
or upgrades host software and has no sudo fallback.
API and worker production preflight both verify configured hosts, so both
processes need these SSH inputs. Hosts receive no controller credentials,
database access, or Installation YAML.
In the existing trusted Installation document at OCC_CONFIG_PATH, select:
drivers: compute: id: compute-ssh configuration: ssh: identityFile: /etc/openclaw/ssh/id_ed25519 knownHostsFile: /etc/openclaw/ssh/known_hosts connectTimeoutSeconds: 10 hosts: stable: address: 203.0.113.10 port: 22 user: root nodePath: /usr/bin/node openclawPath: /opt/openclaw/current/dist/index.js runtime: nodePath: /usr/bin/node openclawPath: /opt/openclaw/current/dist/index.js user: openclaw root: /var/lib/openclaw-enterprise systemdUnitDirectory: /etc/systemd/system network: gatewayPortRange: start: 18800 end: 18899This is the Compute selection only. Keep the required occ, Configuration,
IAM, and Secret selections from the
Installation contract. SSH does not consume the Secret Driver;
OCC Secret bindings are unsupported. Select Configuration storage appropriate to
the control plane; SSH does not provision Kubernetes namespaces for bundled
ConfigMap or Secret storage. SSH selection does not add a raw-host control-plane
installer or alter the existing production API security requirements.
SSH Compute rejects AgentRevisions with a nonempty requested plugin map or an Agent default plugin approver policy, including an explicit empty approver list, before applying host effects. Agent create/update can still save these policies; clear both before deploying through SSH. Use Kubernetes Compute for bundled PluginDriver proof paths.
| Setting | Contract |
|---|---|
ssh.identityFile, ssh.knownHostsFile |
Required absolute worker-local file paths. |
ssh.connectTimeoutSeconds |
Positive safe integer, default 10. |
hosts |
Nonempty map keyed by exact platform Namespace name, not resource ID. |
hosts.<name>.address, .user |
Required hostname/IP and SSH user root. |
hosts.<name>.port |
Integer 1–65535, default 22. |
hosts.<name>.nodePath, .openclawPath |
Optional absolute host-specific overrides. |
runtime.nodePath, .openclawPath |
Required absolute shared host executable/entrypoint paths. |
runtime.user |
Required non-root account-name prefix for Driver-managed per-Agent users and private groups. |
runtime.root |
Required absolute state root; /var/lib/openclaw-enterprise is recommended. Never under /tmp or /var/tmp: PrivateTmp hides those from the unit. |
runtime.systemdUnitDirectory |
Absolute path, default /etc/systemd/system. |
network.gatewayPortRange |
Inclusive integers 1024 <= start <= end <= 65535. |
Unknown keys fail startup. Paths cannot contain whitespace, quotes, control
characters, shell metacharacters, or systemd expansion syntax. Host ports and
paths are trusted operator settings, never caller-selected placement. The
worker uses BatchMode=yes, StrictHostKeyChecking=yes, IdentitiesOnly=yes,
the explicit known-hosts file and identity, the configured connect timeout, and
LogLevel=ERROR. Each invocation sends the controller-owned CommonJS helper on
stdin with one base64 JSON operation argument. Operations are bounded to 180
seconds and terminated on compute cancellation; readiness polling is bounded
to 120 seconds. Closing the SSH session does not signal the remote helper, so
the helper writes a heartbeat line every second and stops mutating as soon as
a write to the closed session pipe fails; it also enforces its own deadline
below the transport timeout. A step already handed to systemctl completes on
the host.
Host layout and ownership
nsHash, agentHash, and revHash are the first 12 hexadecimal characters of
SHA-256 of the exact Namespace, Agent, and AgentRevision IDs.
<root>/accounts/<runtimeUser>.json<root>/namespaces/<nsHash>/namespace.json<root>/namespaces/<nsHash>/agents/<agentHash>/agent.json<root>/namespaces/<nsHash>/agents/<agentHash>/home/<root>/namespaces/<nsHash>/agents/<agentHash>/state/<root>/namespaces/<nsHash>/agents/<agentHash>/gateway-password.env<root>/namespaces/<nsHash>/agents/<agentHash>/env<root>/namespaces/<nsHash>/agents/<agentHash>/revisions/<revHash>/openclaw.json<root>/namespaces/<nsHash>/agents/<agentHash>/revisions/<revHash>/revision.json<root>/namespaces/<nsHash>/agents/<agentHash>/current -> revisions/<revHash><root>/namespaces/<nsHash>/agents/<agentHash>/served.json<systemdUnitDirectory>/openclaw-enterprise-gateway-<agentHash>.serviceEach Agent uses a distinct system user and private group. The account name is
the first 19 characters of runtime.user, a hyphen, and the first 12 hex
characters of SHA-256 of <namespaceId>:<agentId>. The Driver creates accounts
with a non-login shell and records their exact ownership and UID/GID under
accounts/. An existing account or group without its matching ownership marker
is refused. Accounts persist across revision retirement and are removed with
the Namespace.
Markers record exact Driver, Namespace, Agent, and ServicePrincipal ownership.
Revision markers also record revision ID/number, configuration hash, and
Harness. Foreign markers, changed snapshots, unexpected symlinks, and units
without the exact Namespace/Agent ownership header are refused rather than
adopted. JSON markers and gateway-password.env are 0600; home/ and state/ are
0700 and owned by the Agent's private user and group. The native configuration
snapshot stays owned by the SSH account with mode 0640 and the Agent's
private group, so its gateway can read but never rewrite the admitted document
and sibling Agents cannot read it. served.json records
the revision whose restart last reached readiness; a pointer flip alone never
counts as served. Writes and the current symlink are replaced atomically.
Port allocation chooses the lowest unused port across all Agent markers under
the shared root on that host, including other Namespaces. A kernel flock on
<root>/.compute-lock, held by a helper child whose stdin is the helper itself,
serializes allocation and lifecycle effects; acquisition waits up to 30 seconds.
Whatever ends the helper closes that pipe and the kernel releases the lock, so
there is no stale-lock state to reclaim. All Drivers addressing the same host
must use the same root and unit directory for that inventory to be shared.
Reserve the configured range for these gateways; the Driver does not claim ports
belonging to unrelated host processes.
Namespace and revision lifecycle
Preflight verifies both local SSH files, then probes every host for SSH
reachability, systemctl --version, flock and account-management commands on
PATH, executable Node, and readable OpenClaw. Failure names the configured host and stops
production startup.
ensureNamespace creates or verifies the Namespace marker and invokes selected
afterNamespacePrepared hooks. An unmapped name fails permanently. Before
revision operations, the worker calls bindAgent with server-owned Namespace,
Agent, and ServicePrincipal identities; an unbound revision fails closed.
prepareRevision accepts only openclaw/embedded with runtime authentication and without OCC Secret bindings.
It verifies ownership, creates or verifies the Agent's Unix identity, allocates
or reuses the Agent port, and writes the immutable snapshot. A superseded
candidate returns not-ready. Preparation does not replace current, write the
systemd unit, or restart the running gateway. An OCC publication failure leaves
the previous gateway serving its existing revision.
After OCC commits the active revision, activateRevision runs selected
Configuration and IAM beforeWorkloadStart hooks, renders the systemd unit,
switches current, restarts the unit, and waits for loopback /readyz. The
bounded opaque environment placeholders returned by those hooks are projected
into the gateway unit. Hook failure prevents launch; activation failure invokes
bounded beforeWorkloadStop compensation for prepared bindings. Only successful
readiness writes served.json; retries must establish both the exact revision
and readiness. Activation is a bounded restart with interrupted Agent service.
The unit runs <nodePath> <openclawPath> gateway --port <port> as the Agent's private Unix account,
with HOME, OPENCLAW_STATE_DIR, OPENCLAW_CONFIG_PATH, and
OPENCLAW_GATEWAY_PORT. It uses Restart=always, RestartSec=2, SIGTERM,
TimeoutStopSec=30, NoNewPrivileges=true, and PrivateTmp=true. The admitted
document owns logging; the Driver sets no OPENCLAW_LOG_LEVEL. Logs go to journald.
deactivateRevision verifies
ownership and returns; the worker only needs deactivation for the dedicated
topology, which SSH preparation rejects. stopRevision invokes selected
beforeWorkloadStop hooks, stops and disables the unit, and removes current
and served.json while retaining the immutable revision snapshot, home, state,
and operator credentials. A later deployment can activate its newly admitted
snapshot. retireRevision invokes selected
beforeWorkloadStop hooks and removes only that snapshot. If it is still
current, retirement stops/disables the unit and removes the pointer first.
Home, state, operator credentials, and other revisions remain.
deleteNamespace invokes beforeNamespaceDelete, verifies all owned Agents,
stops/disables their units, removes the unit files, reloads systemd, and removes
the Namespace tree including state and its owned runtime accounts. If the Namespace tree is already gone, deletion retries cleanup of any remaining
owned account markers.
Foreign ownership or configuration failures are permanent; transport, timeouts,
and unexpected helper failures are retryable.
Credentials and supported boundaries
When native Configuration omits gateway.auth.mode, the Driver renders
password mode with a managed environment SecretRef using
OPENCLAW_GATEWAY_PASSWORD. Explicit password and trusted-proxy modes are
supported. An explicit password must use that variable's default-provider
environment SecretRef; trusted proxy can opt into the same loopback password.
The Driver admits only supported authentication fields and modes before host
changes.
The helper creates a private per-Agent gateway-password.env when the managed
password is needed. Later revisions reuse that file; the Driver does not rotate
it during prepare, activation, or stop. The systemd unit loads it for managed
password access and omits it for trusted proxy without a password.
The optional EnvironmentFile=-<agentDir>/env is operator-owned and never read
or written by the Driver. Provision model/channel credential lines there and
reference them through native environment SecretRefs. Systemd reads this file
as root; keep it root:root 0600. Protect the state root and SSH identity and
never put plaintext credentials in native Configuration or Installation YAML.
The revision records only { "method": "runtime" }; there is no Secret source,
account identity, key value, or configurable environment-variable field in this
binding. OCC still authorizes deployment and checks gateway /readyz, but makes
no model request and does not inspect the operator file. A gateway can be ready
while its model credentials are invalid. Verify model access separately.
Redeployment, stop, and revision retirement preserve the operator file. Changing it can affect an existing revision without redeployment; restart the process to load changed environment values. Namespace deletion removes the owned Namespace tree, including this file.
Embedded Agents may use any channel provider supported by the host's OpenClaw build. Separate Unix users and private groups protect sibling state and native configuration snapshots. Agents still share host networking; SSH does not provide Kubernetes NetworkPolicy isolation or the channel isolation of dedicated execution. Operators own host/network trust and credential access.
Dedicated Codex, SandboxDriver composition, OCC Secret delivery, workspace-file
API endpoint resolution, existingNamespace adoption, active-runtime
maintenance, macOS launchd, non-root SSH, and zero-downtime rollout are
unsupported. Host runtime upgrades are operator changes followed by explicit
redeployment of each Agent.
Troubleshooting
For host failures, inspect the exact unit with systemctl status and
journalctl -u openclaw-enterprise-gateway-<agentHash>.service. Verify executable
paths, the Agent runtime account and ownership marker, snapshot readability,
and operator environment inputs.
An SSH preflight failure usually indicates a missing key/known-host entry,
wrong host path/account, or unavailable systemd. Never disable host-key checking
to bypass it. Ownership errors require operator inspection of the exact markers,
snapshot and unit; the Driver will not repair them. <root>/.compute-lock is an
empty file whose kernel lock is released when its holder exits; a killed helper
never leaves it held, so it needs no manual cleanup.
