bootstrap: add a dev role — the Incus claudebox host #19
No reviewers
Labels
No labels
attention
blocked
blocker:ci-red
blocker:conflict
blocker:drill-pending
blocker:unrequested
bug
claimed
documentation
enhancement
epic
merge-next
needs-ruling
needs-triage
offsite
post-merge
ready
release
scope:bootstrap
scope:coolify
scope:db
scope:docs
scope:drill
scope:installer
scope:labels
scope:platform
scope:runner
scope:users
stale
state:addressing
state:bots-reviewing
state:building
state:needs-human
No milestone
No project
No assignees
1 participant
Notifications
Due date
No due date set.
Dependencies
No dependencies set.
Reference: heavy-duty/rig#19
Loading…
Reference in a new issue
No description provided.
Delete branch "feat/bootstrap-dev-role"
Deleting a branch is permanent. Although the deleted branch may continue to exist for a short time before it actually gets removed, it CANNOT be undone in most cases. Continue?
Plan
rig bootstrapshipped three roles: control-plane, workload, runner. The one machine class NOT rig-made was the Incus host that runs the claudeboxes (dev-server). It was hand-built, so "every box is rig-made, reproducibly" had a hole exactly where an agent runs. This closes it:rig bootstrap dev --hostname dev-server, same shape as the other roles — idempotent, convergent, a second run is a no-op.What the role does. It reuses ALL the shared machinery for free — the
00-rig.confsshd drop-in plus thesshd -Teffective-config assert, system hostname convergence, and the tailscale join. On top of that it installs and initialises Incus.The three hard constraints, each enforced rather than documented:
tag:local, nevertag:server. The ACL grantstag:server → :22, so a mis-tagged dev host hands the control plane free SSH. This already happened — both M900s came uptag:serverand were retagged by hand. SodevdefaultsTS_TAGtotag:localand refusestag:server(exit 2), mirroring the runner refusal. The correct tag is the only reachable outcome, not a flag the operator has to remember.Incus init idempotency.
incus admin init --autois not idempotent (a second run errors "storage pool already exists"), which would break convergence. So a prior init is detected by the artefacts--autoleaves behind — a storage pool AND a root disk on thedefaultprofile — and re-init is skipped when both exist. State is then asserted effectively (incus profile device show defaulthas a root disk;incus network listshowsincusbr0) rather than trustinginit's exit code — the repo's "assert what resolved, not the action" rule, the same discipline that caught the sshd first-wins bug.What changed
commands/bootstrap.sh—devadded to the role case + die messages;devdefaultsTS_TAG=tag:local; tag:server refusal extended todev; dev-only Incus install + idempotent init + effective-state assert block (guarded on[ "$ROLE" = dev ]); guests-never-join comment;next:hint; usage() listsdev.bin/rig—devadded to the bootstrap line in the usage() heredoc.test/cli.sh—bootstrap dev --ts-tag tag:serverrefused (exit 2);bootstrap devparses and refuses non-root (exit 1); mirrors the runner tests.README.md—### rig bootstrap devsubsection (what it does, the three constraints + reasoning, rehearsal-asserts-effective-state); role list and examples updated.Testing
bash test/cli.sh→ 68 passed, 0 failed (includes the new dev arg/refusal tests).shellcheck -xoverbin/* **/*.sh→ clean.Unit tests can only reach arg parsing and the tag:server refusal (exit 2) and the non-root guard (exit 1) — the incus install/init and the effective
tag:localassertion need a real host. Those belong in the rehearsal, which must assert effective state, not files rig wrote: the existing Incus rehearsal runs in a pristine Debian container with no cloud-init drop-in and is structurally blind to the sshd first-wins bug. A dev-role rehearsal assertssshd -T,incus info, andtailscale status --jsonshowingtag:local, then a secondbootstrap devproving a clean no-op.Interactions with other PRs
setup-host.shneeds a sudo-capable admin user. Merge this after #17 / rebase on it.commands/bootstrap.sh(the role case and the tailscale block especially). Expect to rebase.--ts-tagand makes the tag come from the pre-auth key, with post-join verification of.Self.Tags. This PR still uses--advertise-tags=tag:local(main's model). If #16 merges first, dev's tag policy must move from--advertise-tags=tag:localonto asserting the effective tag istag:local(via.Self.Tags) after join — same intent (tag:local is the only reachable outcome, tag:server refused), different mechanism. Flagging clearly for the maintainer.Closes #12
Parking this as a draft pending the user-modeling decision in #17.
Two things need rework before this is right, and both hinge on #17:
Delegate to
box setup-host, don't reinvent incus. This PR does a bareincus admin init --auto(dir backend, default profile). The real target is heavy-duty/box'ssetup-host, which builds the actual claudebox host stack: a btrfs pool (CoW clones —--auto/dirmakes every box clone a full multi-GB copy), theboxnetisolated NAT bridge, thebox-isolateegress ACL (drops all RFC1918/CGNAT/link-local), DNS pinning, box-to-box firewall isolation, and thebox-netprofile. Sobootstrap devshould install box (curl .../install.sh | bash) and runbox setup-host, leaving the host ready tobox new.Who runs box?
box setup-hostis written for a non-root sudo user — itsudos, adds$USERtoincus-admin, andexit 0s telling you to re-login. Run as root out ofrig bootstrap, it returns success having done half the job (exactly the 'claimed success without checking effective state' failure the sshd first-wins bug taught this repo). That means the dev role needs a real user model — which is #17, deliberately parked for proper design. Per this issue's own text, #17 is a prerequisite for #12, so we're revisiting both together rather than guessing the user model here and redoing it.The base of this PR (hardening + hostname +
tag:localenforcement + refusingtag:server) is sound and stays; only the incus step and the user model change.Closing as superseded by #29, per its description: the traits model (#27) and host-class box install (#28) landed the bulk of #12 on main, and #29 delivers the remnants. This branch predates the traits redesign and is CONFLICTING against main. See the merge-order plan on #29/#30.
Pull request closed