feat(bootstrap): host-class machines install box + run setup-host (#12, #25) #28

Merged
dan-claude-bot merged 1 commit from feat/host-installs-box into main 2026-07-18 00:25:20 +00:00
dan-claude-bot commented 2026-07-17 23:04:34 +00:00 (Migrated from github.com)

What

When rig bootstrap converges a VM-host machine (trait host=yes — roles dev, staging, workstation, and custom --host yes), it now installs the box CLI globally and runs box's own setup-host automatically, instead of printing next: install the box CLI and run 'box setup-host'. The documented to-do becomes a real, convergent step.

Completes #12 (the dev role — the Incus claudebox host) and #25 (machine classes: the host class installs box + rig users).

How — rig delegates to box, never touches Incus

This upholds the design law rig users apply already enforces: "rig NEVER installs Incus: box's setup-host owns the daemon and its group." rig does not apt-get install incus, does not configure the daemon, does not create the incus group. After the role marker is written, on host=yes it runs box's own global installer as root:

curl -fsSL https://raw.githubusercontent.com/${BOX_REPO:-heavy-duty/box}/${BOX_REF:-main}/install.sh | BOX_YES=1 bash

BOX_YES=1 makes box non-interactive and keeps setup-host (so the Incus stack is built, not just the CLI dropped on PATH). box installs Incus; rig delegates. Two tools converging one daemon is drift by construction.

  • Convergent — box's installer is a no-op once box is installed, so re-running bootstrap changes nothing.
  • Opt-outRIG_SKIP_BOX_INSTALL=1 skips; also skips gracefully with a manual-command pointer when curl or the network is missing. box is the host extra, so a failed box install never aborts a bootstrap whose OS + tailnet core already succeeded.
  • PinnableBOX_REPO / BOX_REF (default heavy-duty/box@main).
  • Ordered — runs only after the role-marker write, so a box that failed to become what it claims (tag refused, join backed out) never installs box on a half-built host.

rig#12's hard constraints hold: the host joins the tailnet (guests never do — box doesn't join, fine); no credentials on the host (box is creds-free, fine).

⚠️ Depends on box #71 (delivered by heavy-duty/box#73)

The world-readable global install path — box under /opt/box with a /usr/local/bin shim readable by every non-root user — is delivered by heavy-duty/box#73 (issue box#71). Until that merges, box's root install lands in /root and non-root users (the dev box's human; any box-role rig user) can't reach it. This rig step is correct as written and convergent today; the effective multi-user outcome is gated on box#73, and no rig code changes when it lands. Called out inline in bootstrap.sh and in the plan doc.

Testing

  • bash test/cli.sh154 passed, 0 failed. 8 new checks: guard on host=yes, BOX_YES=1 install, pin default, RIG_SKIP_BOX_INSTALL opt-out, a negative-grep that rig never apt-get install … incus, box-after-marker ordering, and a manual-pointer-on-skip.
  • shellcheck -x bin/* **/*.sh → clean.
  • A real-host rehearsal is still required and cannot run in CI (needs root, a network, and a real Incus daemon). See docs/plans/2026-07-17-host-installs-box.md for the rehearsal that asserts effective state — incus info, and box templates / incus list as a fresh incus-group (non-root) user, which is box#73's acceptance line.

🤖 Generated with Claude Code

## What When `rig bootstrap` converges a VM-host machine (trait `host=yes` — roles `dev`, `staging`, `workstation`, and `custom --host yes`), it now **installs the `box` CLI globally and runs box's own `setup-host`** automatically, instead of printing `next: install the box CLI and run 'box setup-host'`. The documented to-do becomes a real, convergent step. Completes **#12** (the `dev` role — the Incus claudebox host) and **#25** (machine classes: the host class installs box + rig users). ## How — rig delegates to box, never touches Incus This upholds the design law `rig users apply` already enforces: *"rig NEVER installs Incus: box's setup-host owns the daemon and its group."* rig does not `apt-get install incus`, does not configure the daemon, does not create the `incus` group. After the role marker is written, on `host=yes` it runs **box's own global installer** as root: curl -fsSL https://raw.githubusercontent.com/${BOX_REPO:-heavy-duty/box}/${BOX_REF:-main}/install.sh | BOX_YES=1 bash `BOX_YES=1` makes box non-interactive **and** keeps `setup-host` (so the Incus stack is built, not just the CLI dropped on PATH). box installs Incus; rig delegates. Two tools converging one daemon is drift by construction. - **Convergent** — box's installer is a no-op once box is installed, so re-running bootstrap changes nothing. - **Opt-out** — `RIG_SKIP_BOX_INSTALL=1` skips; also skips gracefully with a manual-command pointer when curl or the network is missing. box is the host *extra*, so a failed box install **never aborts** a bootstrap whose OS + tailnet core already succeeded. - **Pinnable** — `BOX_REPO` / `BOX_REF` (default `heavy-duty/box@main`). - **Ordered** — runs only *after* the role-marker write, so a box that failed to become what it claims (tag refused, join backed out) never installs box on a half-built host. rig#12's hard constraints hold: the host joins the tailnet (guests never do — box doesn't join, fine); no credentials on the host (box is creds-free, fine). ## ⚠️ Depends on box #71 (delivered by heavy-duty/box#73) The **world-readable global install path** — box under `/opt/box` with a `/usr/local/bin` shim readable by every non-root user — is delivered by **heavy-duty/box#73** (issue box#71). Until that merges, box's root install lands in `/root` and non-root users (the `dev` box's human; any `box`-role rig user) can't reach it. This rig step is correct as written and convergent today; the *effective multi-user* outcome is gated on box#73, and **no rig code changes when it lands**. Called out inline in `bootstrap.sh` and in the plan doc. ## Testing - `bash test/cli.sh` → **154 passed, 0 failed**. 8 new checks: guard on `host=yes`, `BOX_YES=1` install, pin default, `RIG_SKIP_BOX_INSTALL` opt-out, a negative-grep that rig **never** `apt-get install … incus`, box-after-marker ordering, and a manual-pointer-on-skip. - `shellcheck -x bin/* **/*.sh` → clean. - **A real-host rehearsal is still required** and cannot run in CI (needs root, a network, and a real Incus daemon). See `docs/plans/2026-07-17-host-installs-box.md` for the rehearsal that asserts *effective* state — `incus info`, and `box templates` / `incus list` as a fresh incus-group (non-root) user, which is box#73's acceptance line. 🤖 Generated with [Claude Code](https://claude.com/claude-code)
Sign in to join this conversation.
No reviewers
No milestone
No project
No assignees
1 participant
Notifications
Due date
The due date is invalid or out of range. Please use the format "yyyy-mm-dd".

No due date set.

Dependencies

No dependencies set.

Reference: heavy-duty/rig#28
No description provided.