Reframe claudebox as box: the Claude box is one template among several #17
Labels
No labels
blocked
blocker:ci-red
blocker:conflict
blocker:drill-pending
blocker:unrequested
bug
claimed
documentation
enhancement
epic
merge-next
needs-triage
ready
release
scope:cli
scope:drill
scope:host
scope:installer
scope:templates
scope:tiers
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/box#17
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?
Split from #12 (part 3 of 3: #15 audit → #16 isolation hardening → box + templates). Blocked by #16 — its
tests/isolation.shstands guard over this rename.claudeboxmints exactly one kind of box: Debian 13 + Claude Code, one cloud-init file, one profile, one user namedclaude. That is a template, but it is welded to the tool. The tool underneath is already generic — a thin, honest wrapper over Incus. This issue renames it toboxand makes the Claude box one template among several (blank,claude, and whatever comes later).What is actually Claude-specific
The CLI is 587 lines; the whole repo ~1,250 (refs @
0982a2d):bin/claudebox:423-426--profile claude-dev, the imageimages:debian/13/cloud, the singlecloud-init/user-data.yaml, theuser.claudebox=1tagbin/claudebox:422profiles/claude-dev.yaml:4-5pins 4 CPU / 8GiBbin/claudebox:522-523sudo -u claude -iinshell/exec— the only structural blockerbin/claudebox:349-355,441,485resolve_box()gates every verb onuser.claudebox=1;listfilters on ithost/setup-host.sh,host/claudebox-firewall.sh,host/claudebox-firewall.service,host/teardown-host.shclaudenet,claude-isolate,claude-dev, nft tableinet claudebox,/usr/local/sbin/claudebox-firewall, the unitinstall.shCLAUDEBOX_*env vars,~/.local/share/claudebox, the symlink, the tarball check (:41,53)cloud-init/user-data.yaml:8~/.claude/CLAUDE.mdAll but one are find-and-replace. The
sudo -u claudeone is the design problem:shell/execmust know which user to become, and today they simply know. Ablankbox has no such user.The mechanic: stamp template metadata onto the instance at launch
shell/execread it back off the instance. Two subtleties:incus config getprints an empty string and exits 0 for an unset key (audit B4) — so the fallback is${u:-…}, never|| echo root.user.claudebox=1and nouser.box.user, and must keep landing inclaude, notroot.incus copypreserves config keys (audit B2), sonew --fromkeeps working unchanged and a cloned box still knows what it is — the whole reuse story.Tag matching is the other half of compat, and it is non-negotiable.
resolve_box()(bin/claudebox:349-355) refuses any instance not taggeduser.claudebox=1— under a renameduser.box=1, an old box doesn't just land in the wrong user, it stops being a box at all: invisible tolist, unreachable byshell, unusable as a--fromsource. Match both tags inresolve_box()and thelistfilters (:441,485), whatever the CLI-name compat answer — snapshots of old boxes outlive the release that minted them. Thanks to the command-table refactor (#13) that is one function, not per-verb edits.One deliberate consequence:
execalways becomes the template's user (every template sets one), so therootfallback is effectively unreachable — anything needing root goes through theincusescape hatch (#13). A sentence in the help, not a flag.Templates
KEY="value", deliberately not YAML (zero-dependency bash tool;yqfor two keys would be the largest cost in the change). But notsourced — sourcing hands every template arbitrary bash execution on the host at mint time. A strict allowlist parser (~15 lines of grep/case, still zero-dep) accepts exactly theBOX_*keys above and hard-errors on anything else. The parser is the enforcement of the rule below: there is simply no key for a network.BOX_USERand the user created inuser-data.yamlmust agree by hand — the cloud-init file stays verbatim and unparsed, so the duplication is accepted; agrepwarning at mint time is optional polish.The rule that keeps this honest
A template must not be able to weaken isolation. The shared
box-netprofile is the placement contract: the NIC (with the hardening issue's security flags) and the root-disk device (with--profile, Incus does not apply the default profile, so the disk lives here) — nothing template-controlled. Templates set image, user, resources, cloud-init, and nothing else:BOX_CPU/BOX_MEMORYland as--config limits.*at launch,BOX_DISKas the VM's--device root,size=override. Then no template can ship a box onto the wrong network, andblankis "a box with nobody home", not "a box with the safety off".Migration detail: existing boxes reference
claude-dev, and Incus refuses to delete an in-use profile —claude-devstays until its last box is gone (or setup-host reassigns live boxes withincus profile assign); pick one and say so in the PR.CLI surface
Template discovery post-install is free:
bin/claudebox:8resolves$rootthrough the install symlink, sotemplates/ships exactly likecloud-init/does today.Blast radius outside this repo
.claudebox/repo-runbook convention stays in v1 — consuming repos (e.g.heavy-duty/incubator) reference it; the tool's name and the runbook convention do not have to move together.curlURL pinsheavy-duty/claudebox; renaming the repo breaks it (GitHub redirects clones, not muscle memory), keeping it means a repo namedclaudeboxshipping a binary namedbox— andboxis a generic name on PATH. Open question below.~/.claude/CLAUDE.mdtext — cosmetic, they keep working.Open questions (maintainer)
claudeboxshim →box new --template claudefor a release; (b) clean cut at the next minor (current: 0.3.0), re-mint. Leaning (b); dual-tag matching and the legacy-user fallback ship under either answer.claude(muscle memory survives) or force--template? Leaningclaude.heavy-duty/claudeboxshippingbox? Isboxdistinctive enough on PATH?Acceptance
box new --name x --template blank→ no Claude, noclaudeuser, same isolation as aclaudebox.box new --name y --template claudebehaviorally identical to today'sclaudebox new --name y.box new --name z --from y/authedclones;box shell zlands in the right user without consulting the template.user.claudebox=1, no new metadata): appears inbox list, shells intoclaude, works as a--fromsource.box.envwith an unknown key (e.g.BOX_NETWORK=lan) is rejected, naming the key.box templateslists what is installable.tests/isolation.sh(renamed with everything else) green after the rename.About a day: ~150 lines in the CLI (template resolve + parser, metadata stamp, user lookup, dual-tag match), two template dirs, the profile split, the host renames, installer, docs.