rig made every box except one: the Incus host that runs the claudeboxes was
hand-built, so "every box is rig-made, reproducibly" had a hole exactly where
an agent runs. Add `rig bootstrap dev`, same shape as the other roles —
idempotent, convergent, a second run is a no-op.
dev reuses all the shared machinery (00-rig.conf sshd drop-in + sshd -T
effective assert, hostname convergence, tailscale join) and adds:
- Tag policy: defaults TS_TAG to tag:local and REFUSES tag:server (exit 2,
mirroring the runner refusal). tag:server's ACL grants :22, so a mis-tagged
dev host would hand the control plane free SSH — the exact bug that made both
M900s retag-by-hand jobs. Correct-tag-only is enforced, not documented.
- Incus install + init, dev-only: apt-get install incus, then `incus admin
init --auto` ONCE. --auto is not idempotent, so a prior init is detected by
its artefacts (a storage pool AND a root disk on the default profile) and
re-init skipped, keeping a second run a true no-op. Effective state is
asserted after (default profile root disk, incusbr0) rather than trusting
init's exit code.
- A comment recording that the guest claudeboxes deliberately do NOT join the
tailnet — the host joins, guests are reached via ProxyJump through it; an
"enrol the guests" convenience would be the bug.
Unit tests cover arg parsing and the tag:server refusal; incus init and the
effective tag:local assertion need a real host and belong in the rehearsal.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>