JarvisOS HomeLab status
No signal

Node bootstrap and worker onboarding

2 target(s) · 15 steps · kit adopted

The bootstrap kit is the shortest path from 'a machine exists' to 'the fleet is using it'. It is declared here and executed by scripts/bootstrap-node.sh; the console renders this file so the plan an operator reads is the plan that runs. Every step states how re-running it is safe, how to prove it worked, and how to undo it — a bootstrap step you cannot verify or reverse is a one-way door onto somebody's home network.

Sign the Claude CLI in as the jarvis service account on home1

Needs a human · home1 · nothing else in the onboarding path can do this

Claude Code is the engineering worker. Its sign-in is an interactive OAuth device flow: it prints a URL, waits for a browser, and writes the credential itself. Nothing headless can complete it, and until it is done home1 can be dispatched planning work but no code.

Blocks: All engineering dispatch to home1. The node will onboard, heartbeat and advertise capacity without it — and every coding task will still land on VIN's single slot.

Run this

ssh root@uk-james-home1.tail99ba7.ts.net
sudo -u jarvis -H npm install -g @anthropic-ai/claude-code   # only if `sudo -u jarvis -H command -v claude` is empty
sudo -u jarvis -H claude
# then, at the prompt: /login  — it prints a URL, open it, approve, come back

Values you will be asked for

Account
jkkec23@gmail.com The same account the CLIs on home1 already use under the 'james' user. Using a different one is fine, but write down which.
Verify
sudo -u jarvis -H test -f /home/jarvis/.claude.json && echo signed-in
Then
sudo /opt/jarvisos/bin/jarvisos-bootstrap.sh advertise Re-advertises immediately; this card clears on the next heartbeat.

Open https://claude.ai/login Onboarding detail → Clears itself when home1 advertises worker-claude.

Nodes reporting 0/2 the rest are declared, not connected
Worker slots live 0 4 more once onboarding completes
Human blockers 2 one is surfaced at a time
Reversible steps 10/10 every step that changes a host declares its undo

Fleet capacity, as the governor should see it

Derived from measurements, never declared

The dispatch supervisor derives worker fan-out from available memory alone. That is right on a 1.9 GiB edge node and wrong on a 34 GiB home node that is already 15 GiB into swap and stalling for half of every five minutes. These numbers add the two constraints memory cannot see — core count and pressure — and are served at /api/v1/bootstrap so admission control can read them rather than re-derive them.

Node Slots Bound by Memory-only would give Source
home1 3 cpu + pressure 8 evidence 2026-08-16
vin 1 memory 1 evidence 2026-08-16

Knobs: 300 MiB held back, 350 MiB per worker, 2 cores per worker, halved above 25% CPU pressure or 1% memory pressure.

Open gaps

What is between here and a fully onboarded fleet

Targets

home1 not onboarded

home1 · primary worker and always-on compute
3 worker slots

Bound by cpu: 12 core(s) at 2 per worker. Halved for pressure — CPU stalled 38.8% of the last five minutes (threshold 25%); memory fully stalled 6.55% of the last ten seconds (threshold 1%).

Memory allows 46 · cores allow 6 · declared ceiling 6 · halved for pressure · from a measurement on 2026-08-16

Reached at
uk-james-home1.tail99ba7.ts.net
Admin path
tailscale-ssh as root
Service account
jarvis
Last heartbeat
never
Ceiling
6 — 12 cores at two cores per worker. Deliberately below what memory alone would allow (46), because this node already runs 83 containers and a legacy service estate that were here first.
Advertising nothing yet
Missing worker-claudeworker-codex

Governor: memory-only admission would dispatch 8 here. This node is already stalling; the derived answer is 3.

vin not onboarded

vin · public edge and dispatch node
1 worker slot

Bound by memory: 773 MiB available, 300 MiB held back, 350 MiB per worker.

Memory allows 1 · cores allow 1 · declared ceiling 2 · from a measurement on 2026-08-16

Reached at
vin.tail99ba7.ts.net
Admin path
local as jarvis
Service account
jarvis
Last heartbeat
never
Ceiling
2 — 1 vCPU and 1.9 GiB of RAM. Memory is the binding constraint here and the existing governor already models it correctly.
Advertising nothing yet
Missing worker-claudeworker-codexworker-gemini

Human-only steps

One is surfaced on the overview at a time, by priority

Everything not on this list is the installer's job. These three are interactive OAuth flows and a network enrolment: no automation can complete them, and pretending otherwise would mean an installer that can add machines to a private network and sign in as its owner.

  1. Sign the Claude CLI in as the jarvis service account on home1 surfaced now
    Claude Code is the engineering worker. Its sign-in is an interactive OAuth device flow: it prints a URL, waits for a browser, and writes the credential itself. Nothing headless can complete it, and until it is done home1 can be dispatched planning work but no code.
  2. Sign the Codex CLI in as the jarvis service account on home1 queued
    Codex is the reviewer in the dispatch model, so without it home1's work is written and merged by the same worker. Its sign-in is the same interactive OAuth flow and equally cannot be automated.
  3. Enrol the node on the tailnet done — observed, not advertised
    Joining a private network is an act of enrolment. It needs an auth key or an interactive login from the admin console, and this kit deliberately has neither: an installer that can add machines to your private network is an installer that can add anyone's.

The installer, step by step

scripts/bootstrap-node.sh · plan · install · verify · advertise · rollback

Every step states what re-running it does, how to prove it worked and how to undo it. A step that changes a host and cannot name its undo fails npm run check rather than reaching a machine.

Preflight

Prove the machine is the kind of machine this kit can bootstrap, and that we are already on the private plane, before anything is written.

Step Re-running it Proven by Undone by
preflight-host safe
Linux with systemd, and we are root
read-only uname -s is Linux, /run/systemd/system exists, effective uid is 0 (or sudo -n works) Reads only; there is nothing to undo.
preflight-mesh safe
Tailscale is installed, logged in and carrying this node
read-only BackendState is Running and Self.DNSName is non-empty Reads only. The kit never runs `tailscale up`, never edits the tailnet ACL and never touches a firewall: joining a private network is an act of enrolment that belongs to a human with the admin console open.
preflight-console safe
The status console answers from this node
read-only HTTP 200 with console:"ok" Reads only.

Service account

Give fleet work an identity of its own, so what it can reach is a decision rather than an accident of whose shell it borrowed.

Step Re-running it Proven by Undone by
service-account disruptive
Create the jarvis system account
exact — an existing jarvis account is left completely untouched, including its shell, home and groups getent passwd jarvis returns a line and the home directory exists userdel jarvis (only with --purge, and only if this run created it; the account is otherwise left alone)

Roots

Create the four roots everything else assumes: code, state, logs and transcripts. Nothing here is on the NAS — a worker root that lives on the thing we may be recovering cannot be used to recover it.

Step Re-running it Proven by Undone by
roots-code safe
Code root /opt/jarvisos and its bin/
exact — mkdir -p and an explicit chmod; existing content is not touched /opt/jarvisos/bin is a directory, group jarvis, mode 0755 rmdir /opt/jarvisos/bin /opt/jarvisos if empty; never recursive
roots-state safe
State, workspace, report and transcript roots under /var/lib/jarvisos
exact — mkdir -p; existing task state, workspaces and transcripts are never rewritten all five directories exist, owned by jarvis, mode 0750 Left in place. These hold task history and worktrees; removing them on rollback would delete work the rollback is trying to protect. --purge removes them only after printing what it is about to lose.
roots-log safe
Log root /var/log/jarvisos
exact /var/log/jarvisos exists, owned by jarvis, mode 0750 Left in place; logs are evidence about the run that is being rolled back.

Command line tools

Prove the tools the runner and the workers need are present. The kit reports what is missing; it installs only from the distribution's own package manager, and only when told to.

Step Re-running it Proven by Undone by
tooling-base disruptive
Base CLIs: git, curl, node, python3, rsync, jq
exact when everything is present (the common case); otherwise apt-get install of the missing set only, and only with --allow-apt every one of the six resolves on PATH Packages installed by this step are listed in the run log and can be removed with apt-get remove. Nothing is removed automatically: another workload may have started depending on them in the meantime.
tooling-workers safe
Worker CLIs and their sign-in state for the service account
read-only each binary resolves for the jarvis account and its credential file exists — presence only; the kit never reads the contents Reads only. Credentials are never written, copied or moved by this kit: a token copied between accounts is a token whose custody nobody can describe afterwards.

Secret references

Put the node's configuration file in place with the right ownership and the right keys, and leave the values to whoever owns them.

Step Re-running it Proven by Undone by
config-env disruptive
/etc/jarvisos/config.env, root:jarvis 0640
additive — an existing file is never rewritten; only missing keys are appended, and an existing key keeps its value even when empty the file exists, is mode 0640 root:jarvis, and contains every key in paths.configKeys A backup is written to /etc/jarvisos/config.env.bak-bootstrap-<stamp> before any append; rollback restores it. A file this run created from nothing is removed.
ingest-token disruptive
Install or enroll this node's scoped ingest identity
exact — an identical credential is a no-op; a different one is refused unless --force-token POST /api/v1/heartbeat from this node returns 202, not 401 The pre-change credential backup is restored. Revocation in the console registry is a separate operator action.

Node agent

Install the heartbeat and capability agent, so the node stops being something a human has to remember and starts being something the fleet can see.

Step Re-running it Proven by Undone by
agent-install safe
Copy the kit to /opt/jarvisos/bin/jarvisos-bootstrap.sh
exact — byte-identical copy is skipped; a differing copy is replaced and the old one kept as .prev /opt/jarvisos/bin/jarvisos-bootstrap.sh --version matches the kit version Remove the file, restoring .prev if this run replaced one.
agent-unit disruptive
systemd unit and timer jarvisos-node-agent
exact — the unit text is generated deterministically and written only when it differs; the timer is enabled only if not already enabled systemctl is-enabled jarvisos-node-agent.timer is 'enabled' and one manual `systemctl start jarvisos-node-agent.service` exits 0 systemctl disable --now jarvisos-node-agent.timer, then remove both unit files and daemon-reload.

Host-substrate deployment runner

Provide a break-glass deployment path only for workloads explicitly classified as host substrate. Ordinary applications use the Coolify templates in config/deploy-templates.json.

Step Re-running it Proven by Undone by
deploy-runner disruptive
Exception-gated template unit jarvisos-deploy@.service
exact — deterministic unit text, written only when it differs systemctl cat jarvisos-deploy@.service resolves and a dry-run instance exits 0 Remove the template unit and daemon-reload. Instances are transient and hold no state.

Advertise capacity

Tell the fleet what this node can actually take, measured rather than declared, within seconds of it being connected.

Step Re-running it Proven by Undone by
advertise-capacity safe
Measure and POST the capability heartbeat
exact — a heartbeat is an observation; sending it twice reports the same node twice the console returns 202 and the node appears with a fresh heartbeat and a non-empty capability list Nothing is written on either side beyond an in-memory observation that expires on its own. Stopping the timer stops the advertisement.

Paths and secret references

Names and locations only — this console never holds a value
PathPurposeOwnerModeBackup
/opt/jarvisos code root root:jarvis 0755 t0-crown-jewels
/opt/jarvisos/bin/jarvisos-bootstrap.sh installer and node agent, one file root:jarvis 0755 t0-crown-jewels
/var/lib/jarvisos/state task store and locks jarvis:jarvis 0750 t0-crown-jewels
/var/lib/jarvisos/workspaces repository and worktree root, one directory per project jarvis:jarvis 0750 t1-reproducible
/var/lib/jarvisos/reports worker run reports jarvis:jarvis 0750 t1-reproducible
/var/lib/jarvisos/transcripts worker transcripts; may contain quoted source and must never leave the node unredacted jarvis:jarvis 0750 t1-reproducible
/var/log/jarvisos agent and deployment logs jarvis:jarvis 0750 none
/etc/jarvisos/config.env node configuration and non-secret settings root:jarvis 0640 t0-crown-jewels
/etc/jarvisos/node-credential this node's scoped ingest credential root:jarvis 0640 t0-crown-jewels

Current-state evidence

What was actually observed, and how

Notes

How to read this page

Source: config/bootstrap.json · installer scripts/bootstrap-node.sh · machine-readable at /api/v1/bootstrap · narrative in docs/NODE-BOOTSTRAP.md.