bootstrap: add a dev role — the Incus claudebox host is the one box rig doesn't make
#12
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#12
Loading…
Reference in a new issue
No description provided.
Delete branch "%!s()"
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?
rig bootstrapships three roles today —control-plane,workload,runner. The one machine class that is not rig-made is the Incus host that runs the claudeboxes (dev-server). It was hand-built, so the fleet's "every box is rig-made, reproducibly" property has a hole in exactly the place an agent runs.Proposed
Same shape as the other roles — idempotent, convergent, a second run is a no-op.
What it does:
00-rig.confsshd drop-in plus thesshd -Teffective-config assert (the first-wins fix — must not regress)tag:localincusinstall + init: storage pool, default profile, networkHard constraints
tag:local, nevertag:server. The ACL grantstag:server → :22, so a mis-tagged dev host hands the control plane free SSH. This has already happened once on this tailnet — both M900s were carryingtag:serverand had to be retagged. The role should make the correct tag the only reachable outcome, not a flag the operator remembers to pass.ProxyJump dev-server, or an Incus proxy device) — never via a tailnet node of its own. Ifbootstrap devever grows a "join the guests too" convenience, that is the bug.Context
Two bare-metal machines, one role each:
dev-server(Incus claudeboxes, never Coolify-registered) andstaging-server(hosts the Coolify-managedstaging-boxVM). staging-server's VM is already covered by the existingworkloadrole.dev-serverhas no role at all — this issue is that gap.Rehearsal
The existing Incus rehearsal is structurally blind: it runs in a pristine Debian container, which has no cloud-init drop-in, which is precisely why the
99-rig.confvs50-cloud-init.conffirst-wins sshd bug survived it and shipped to every real box. Adevrole rehearsal must assert effective state —sshd -T,incus info,tailscale status --jsonshowingtag:local— and not the mere presence of files rig wrote.Revisiting this against the current state of
boxand rig. The role is still right; four things in the spec above are now stale, and one of them would have rig re-implement — and quietly degrade — workboxalready does.1. It's
boxnow, notclaudeboxThe repo renamed to
heavy-duty/box(v0.5.0, no longer "claudebox"). rig's README still linksheavy-duty/claudeboxin the philosophy line; GitHub redirects, so it works and will keep working, but it should be corrected while we're here.box's own installer has already been bitten by the rename once — its comments record the archive glob dying on every host when it kept looking forclaudebox-*.2. rig should NOT do the Incus init —
boxalready does, betterThe bullet above reads "
incusinstall + init: storage pool, default profile, network". Butbox/host/setup-host.shalready does exactly that, and encodes decisions rig would lose by reimplementing:incus admin init --minimal— because minimal picks thedirbackend, which has no copy-on-write, making every snapshot and clone a full multi-GB copy. That guts the entire "log in once, snapshot, clone forever" workflow the dev-server exists for.dirstays as a fallback, and it says so out loud when it lands there.boxneton 10.88, not 10.87 — because a pre-rename host may still haveclaudeneton 10.87 with legacy boxes attached, and two bridges must not claim one subnet.box-isolateACL — egress allow to the gateway first (so instance DNS via dnsmasq survives), then explicit drops to 10/8, 172.16/12, and the rest of private space.A rig-side
incus admin initwould, by default, do the naive thing and silently destroy the clone-forever property this box exists for. The dev role should installboxand delegate host setup tobox's own script. rig owns the OS plumbing;boxowns what a box host is. That split is also just this repo's stated philosophy applied one level up.3.
setup-host.shcannot be run as root — it will report success having done half the jobThis is the part that needs design, not just a call:
It is written for a sudo-capable non-root user: it
sudos, and on first run it adds the group and exits 0 asking for a re-login. Invoked as root fromrig bootstrap dev, that returns success with no storage pool, no network, no ACL — and rig, believing it, would printdone — role dev.That is the sshd first-wins bug's exact shape: rig trusting an exit code instead of checking effective state. Whatever the design,
bootstrap devmust assertincus info/ the pool / the network afterwards and refuse to claim success on the strength ofexit 0.Same root-vs-user problem in
box's installer: it targets$HOME/.local/share/boxand$HOME/.local/bin(overridable viaBOX_HOME/BOX_BIN). Run as root,boxlands in/root/.local/bin— installed for nobody who will use it.Both point the same way: the dev role needs the admin user from #17, and needs
boxinstalled and set up as that user. #17 is a prerequisite for this one, not a neighbour.4.
tag:local— the flag this issue complains about is being deletedThe hard constraint above says "the role should make the correct tag the only reachable outcome, not a flag the operator remembers to pass" — and notes both M900s carried
tag:serverand had to be retagged.#16 delivers exactly that:
--ts-taggoes away, the pre-auth key's tags apply on their own, and rig verifies.Self.Tagsfromtailscale status --jsonon every run, first join or not. The dev role then needs no tag flag at all — just a policy entry (devmust carrytag:local, nevertag:server) and a key minted withtag:local. The re-run verification is also what would have caught the M900s without anyone noticing by hand.Worth stating plainly in this issue's spec, since "tailscale join as
tag:local" above currently implies rig requests it.5. Version pinning: there is nothing to pin to
boxhas no tags and no releases, and its installer buildshttps://github.com/$REPO/archive/refs/heads/$REF.tar.gz—refs/headsis branches only, soBOX_REF=v0.5.0would 404 even if the tag existed.bootstrap devcan therefore only install a movingmain.By rig's own logic that is the wrong default:
coolify installdemands a pin because Coolify never self-updates, so its version is a contract.boxdoesn't self-update either — it has the same shape as Coolify, and the opposite ofactions/runner(which is latest-by-default only because GitHub refuses jobs from stale runners). There's no--ephemeral-style forcing function here.Not a blocker for this issue, but it needs a decision: either
boxcuts tags and teaches its installerrefs/tags, orbootstrap devinstallsmainand the README says out loud that this one is unpinned and why. Silently trackingmainon the box that runs the agents is the option not to pick.Still correct, unchanged
The three hard constraints above all stand —
tag:localnevertag:server, the boxes must not join the tailnet (the host joins; guests reach the operator viaProxyJump dev-server), and no credentials on the host. The last one lines up withbox's own design (creds-free; the operator runs/logininteractively), so rig has nothing to do there but not break it.The rehearsal note stands too, and gets easier:
incus infoandtailscale status --jsonshowingtag:localare exactly the effective-state assertions #16 is building the reader for.