Remote nodes
zeus-node makes a VPS (or another workstation) a first-party Zeus execution
host. Use it when SSH is not enough: you want per-machine Claude/Codex
logins, merged usage in the inspector, and a move/fork of a live thread
between laptop and VPS.
Provider credentials stay on the machine where the CLI runs. Zeus gets a versioned management interface for accounts, usage, provider sessions, and handoff.
SSH is still configured. It is the install/recovery path and the compatibility terminal path. It is no longer the source of truth for identity, usage, or movement.
Most people should start with Remote hosts. Come here when you are ready to treat a VPS as a first-class Zeus machine.
Install on the VPS
The VPS needs Claude Code and/or Codex on PATH, a private Tailscale address,
and systemd user services.
cargo build --release -p zeus-node
./scripts/install-node.sh
systemctl --user edit zeus-node
Set the listener to the VPS's Tailscale address in the override:
[Service]
Environment=ZEUS_NODE_LISTEN=100.64.0.2:7337
Then start it and print the one-time enrollment values:
systemctl --user enable --now zeus-node
zeus-node init
The node config is owner-only and contains its app-layer capability token.
Tailscale encrypts the transport; the token authenticates Zeus at the
application layer. Do not bind the listener to a public interface and do not
copy node.json wholesale.
On the local Mac, put only the printed token in an owner-only file:
mkdir -p ~/.config/zeus
chmod 700 ~/.config/zeus
printf '%s\n' 'PASTE_THE_TOKEN' > ~/.config/zeus/forge.token
chmod 600 ~/.config/zeus/forge.token
In Zeus, open Settings → Remote and add both the SSH fallback and
first-party node fields. The resulting hosts.json entry is:
{
"id": "forge",
"name": "Forge",
"ssh": "you@forge",
"defaultCwd": "~/code",
"node": {
"endpoint": "tcp://100.64.0.2:7337",
"tokenFile": "~/.config/zeus/forge.token",
"nodeId": "node-a1b2c3d4"
}
}
nodeId pins the stable identity. It may be omitted for first enrollment, but
should be saved after the first verified hello.
Individual accounts on each machine
Profiles are labels and identity metadata. An installation is that profile's login on one node. Provider secrets never appear in the profile registry or over the Zeus protocol.
zeus-node account add --provider codex --id personal --label Personal
zeus-node account add --provider codex --id work --label Work
zeus-node account add --provider claude --id personal --label Personal
zeus-node account login --id personal
zeus-node account status --id personal
zeus-node account default --provider codex --id work
zeus-node account list
You can run those commands directly from the local Mac against the VPS. No
interactive SSH shell is required. Add the enrolled connection flags to any
status or account command:
zeus-node account login --id personal \
--endpoint tcp://100.64.0.2:7337 \
--token-file ~/.config/zeus/forge.token \
--node-id node-a1b2c3d4
Codex uses the official app-server device-code/browser flow. The URL and
one-time code are printed where the command runs, so a VPS login completes in
the local browser without copying auth.json. Claude's supported interactive
auth command is streamed through the node; open the emitted URL locally. On
Linux, each Claude installation uses its own CLAUDE_CONFIG_DIR. Claude Code's
macOS Keychain credential is host-wide, so multiple simultaneous Claude
subscription identities on one Mac are intentionally not claimed as isolated.
Codex installations use a separate CODEX_HOME. Sessions bind to an explicit
profile; changing a node default affects new sessions, not a running session.
This is identity selection for legitimate personal/work contexts, not
automatic rate-limit failover.
Instant move and fork
The client handoff coordinator performs:
- target identity/account/capability preflight;
- source checkpoint at a turn boundary;
- content-addressed transfer of only missing blobs;
- target restore into quarantine;
- provider-native resume/fork (
thread/resumeorthread/forkfor Codex; background-agent resume/fork for Claude); - the same location lease committed on target and source.
The live workspace is never overwritten during staging. .git, provider homes,
SSH material, .env*, known credential files, dependency/build directories,
symlinks, oversized files, and special files are excluded and recorded in the
manifest. A failure before commit aborts both sides; a committed move is
reversed with a new move rather than destructive rollback.
The existing session.migrate SSH implementation remains available for old
hosts and recovery.
The same coordinator is available from the management CLI for a node-managed
session. This example moves a Codex thread from the local node to Forge; use
--mode fork to leave the source lease running:
zeus-node handoff \
--provider codex --profile work \
--session zeus-session-id --provider-session codex-thread-id \
--workspace "$PWD" --mode move \
--target-endpoint tcp://100.64.0.2:7337 \
--target-token-file ~/.config/zeus/forge.token \
--target-node-id node-a1b2c3d4
Add --endpoint, --token-file, and optional --node-id when the source is
another enrolled node rather than the local node service. The source and target
may use different provider logins under the same profile label, which makes a
local work installation and a VPS work installation independently tunable.
Fleet usage
Each node keeps usage.sqlite3 in WAL mode. Events are idempotent and
dimensioned by node, profile, session, provider, model, and source. The schema
distinguishes:
- subscription/rate-limit quota;
- estimated API-equivalent cost;
- authoritative billed spend.
Codex account/rate-limit/usage snapshots come from app-server. Node-managed
transcripts are scanned every 30 seconds as the durable fallback. Claude
OpenTelemetry collectors can normalize request events into usage.record;
transcript import remains available when telemetry is not enabled. The desktop
queries every enrolled node and merges today/month provider totals with its
local usage projection. An unreachable node never blocks local numbers and is
retried on reconciliation.
Operations and security
- Run one node per Unix user. Never run it as root.
- Bind only loopback or a Tailscale address. Public TCP is unsupported.
- Keep
node.json,accounts.json, provider homes, and enrollment-token files mode0600/ directories0700. - Provider credentials are node-local. Checkpoints and usage rows contain no auth material.
- Rotate enrollment by stopping the service, replacing
authTokeninnode.json, and updating enrolled token files. - Back up
usage.sqlite3,accounts.json, and provider homes according to provider policy. Checkpoint blobs are disposable caches. - Audit by checkpoint/move JSON records and the SQLite ledger; raw provider auth responses are not logged.
Protocol changes are additive within NODE_PROTOCOL_VERSION = 1. Clients
authenticate with node.hello, verify the pinned identity, and negotiate
explicit capability strings before using a feature.