Commit graph

5 commits

Author SHA1 Message Date
dan-claude-bot
86dbb0449e refactor(templates): the tenant seeds carry rig's -box family suffix
rig is growing a second family of roles, and once a 'staging' role can mean
either a fleet machine or a box tenant, the bare name stops naming anything.
rig's answer is a suffix on the role (heavy-duty/rig#76): '-server' for fleet
machines, '-box' for box tenants. box's answer is that a template keeps being
named for the role it converges, so the tenant templates move with it:

  claude  -> claude-box    codex   -> codex-box
  grok    -> grok-box      staging -> staging-box

Templates are the only surface that spells a rig role out loud
(BOX_BOOTSTRAP_ROLE, auto-run at mint since #81), so a directory that says one
thing and a role key that says another is a trap with a 15-minute fuse: it
mints clean and dies at convergence. Renamed with 'git mv' so the history of
each seed follows it.

'blank' keeps its name. It seeds no tenant role and sets no
BOX_BOOTSTRAP_ROLE, so it has nothing to agree with — renaming it would only
churn the default template's name for symmetry's sake.

Two namespaces move apart here and only one of them moved: the template name
and the role are now claude-box, while the seed USER stays 'claude' — that is
the user rig's role converges and the one 'box shell' lands in. test/cli.sh
pins the pair per tenant rather than each half alone, because a later rename
that moves one and forgets the other mints a box whose role dies looking for a
user nobody created. drill.sh keeps its bare box NAMES ('codex', 'grok' — what
the pre-flight banner announces and what teardown deletes) and only moves the
--template it passes.

The mint-time hints in cmd_new match both spellings of user.box.template, and
that is not an alias for the role: 'rig bootstrap claude' is gone and nothing
here softens the cut. The stamp is a fact about an INSTANCE, written at its own
mint time and carried forward by every clone; refusing the old spelling would
cut nothing over and only drop the login hint on boxes that predate today —
the same reason user.claudebox is honored everywhere else. migrate-host.sh
stamps re-homed legacy boxes claude-box, the name the template has today, so a
re-homed box looks like a fresh mint rather than a fossil.

Ordered AFTER rig's rename, and that is not a preference. The seeds install rig
from RIG_REPO/RIG_REF, defaulting to heavy-duty/rig@main and unpinned until
rig#32's releases, so these templates ask whatever main happens to be for
'rig bootstrap claude-box'. Against a pre-rename rig that role does not exist
and cmd_new refuses to call the box ready. Merged in the other order the window
closes instead of opening: rig's cut is hard, with no aliases, so the day it
lands every unmerged box seed naming a bare role is the broken one.

Closes #123

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-20 00:01:29 +00:00
dan-claude-bot
b429f5b65c feat(setup-host): refuse a claimed subnet, and BOX_SUBNET to move off one (#80)
Run inside a box, setup-host built a nested boxnet claiming the exact
subnet and gateway of the guest's own uplink: the guest then held
10.88.0.1 as a LOCAL address while its default route pointed at 10.88.0.1
as its GATEWAY, carried two connected routes for the subnet, and suffered
intermittent, self-recovering egress blackouts (~24-36 s, roughly hourly)
that no host-side check could attribute. The flagship use case funnels
agents toward exactly this — working on box, in a box — so the guard, not
the operator, has to catch it.

setup-host now scans the target subnet BEFORE any mutation: the default
route's gateway inside it (the smoking gun — that is this machine's own
uplink), or any non-boxnet interface holding an address in it, refuses
with the way out named (BOX_SUBNET). A prior boxnet owning the subnet is
the one claimant that is NOT a collision — the legitimate re-run — and an
existing bridge on a DIFFERENT subnet refuses too: setup-host converges a
bridge, it never re-addresses one under live leases.

BOX_SUBNET=<a.b.c.0/24> (validated at the gate, alongside BOX_DNS) moves
the whole stack from one place:
  · the bridge address derives (ipv4.address=<gw>/24)
  · the ACL's gateway carve-out derives — and the ruleset is now converged
    via 'network acl edit', not created once: the #80 escape hatch (moving
    the bridge) left the old /32 behind, stranding box DNS inside the
    10.0.0.0/8 drop, which presents as a dead resolver, never as a stale
    ACL
  · box-firewall reads the gateway off the live bridge ('|| true' because
    under pipefail an absent bridge would kill the script instead of
    answering "no bridge yet")
  · the drill, multiuser rehearsal and migrate-host probes derive the
    prefix from 'incus network get boxnet ipv4.address' instead of
    hardcoding 10.88 — a BOX_SUBNET host must not fail its own rehearsals

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-18 19:46:58 +00:00
dan-claude-bot
437a3a8e35 test+ci: add CI workflow and a dependency-free test suite
box had no CI and no unit tests — only the live-host drill. Mirror rig's CI:
one `check` job = globstar `shellcheck -x` over bin/* and **/*.sh, then
`bash test/cli.sh`. The suite is dependency-free and runs non-root with no
Incus: the full CLI contract; install.sh's DEST/BINDIR branch driven
functionally against a shim `id` (both tiers + the BOX_HOME/BOX_BIN overrides);
the root-only a+rX and #66's confirm/no-op flow grep-guarded; tmux asserted in
every template. Pre-existing repo shellcheck findings (bin/box SC2034/SC2015/
SC2020, and file-level SC2015 idioms in doctor.sh/wipe.sh/migrate-host.sh) were
resolved — real fixes where behaviour allows, reasoned disables otherwise — so
the new CI is green over the whole repo.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-07-18 00:01:15 +00:00
claude-hdb
6f7c3bfd60 fix: run 17's four real findings — migrate retire, expose proxy, wait_box, grok PATH
The first drill run where every failure was the RELEASE CODE, not the
environment. 71 passed, 5 failed; all five traced to four bugs:

1. migrate-host --retire-legacy could NEVER succeed. Re-homing ADDS
   user.box=1 but never removed user.claudebox=1, and legacy_boxes()
   counted the old tag — so retire saw its own freshly-migrated box as
   un-migrated and refused forever ('legacy boxes still exist:
   legacybox'), leaving claudenet + claude-dev behind. Now: a verified
   re-home drops the legacy tag LAST (after the move is proven, so a
   failure anywhere above still leaves the box valid under one tag or
   the other), and legacy_boxes() ignores boxes already carrying
   user.box=1.

2. box expose died with a bare 'could not add the proxy device' — it
   swallowed incus's reason, exactly the sin this repo keeps punishing.
   Now it prints incus's error. And the mechanism is corrected: a VM's
   proxy needs NAT mode, which requires a static NIC address, so expose
   pins the box's current lease first (which also fixes the restart
   caveat — the exposure no longer points at a lease the box may lose).

3. wait_box's 2-minute window was too short: the legacy box was declared
   dead and then every migration check against it passed. 4 minutes.

4. The grok template hunted for a regular file named exactly
   'grok-build' under /home/grok and found nothing — an installer's drop
   may be a SYMLINK, and its binary name is upstream's to choose. Now it
   tries the plausible names and paths, falls back to any executable
   grok*, links both names, and SAYS what it found — or dumps what the
   installer actually left when it finds nothing. The drill likewise
   dumps the on-disk evidence and the cloud-init log on a --version
   failure instead of discarding the box.
2026-07-14 19:56:17 +00:00
claude-hdb
c9712834f2 feat: host/migrate-host.sh — re-home legacy boxes onto the new stack, then retire it
The 0.4.0 transition is zero-ceremony (install + setup-host = a
dual-stack host). This script is the two things that path does not do:

- --box <name> / --all-boxes: re-home a pre-rename box onto the new
  stack, PRESERVING its authed state (no re-login). Order is
  load-bearing — tag first (additive, reversible), profile-assign last
  (the network move), then verify the box actually resolves + reaches
  the internet on its 10.88 leg before declaring it migrated. A box
  never ends up tagless or profileless.
- --retire-legacy: remove claudenet/claude-dev/claude-isolate and the
  old firewall unit + nft tables, but REFUSE while any legacy box still
  references them; assert their absence rather than trust exit codes.

Drill phase M builds a faithful legacy stack (claudenet on 10.87, a
claude-dev profile pinned to it, a box on the old tag), then proves:
retire refuses with a legacy box present, re-home flips the tag +
reassigns box-net + lands a 10.88 address + resolves, and retire then
succeeds and leaves nothing. The transition is measured, not asserted.

Closes #53
2026-07-14 16:02:27 +00:00