refactor(templates): the tenant seeds carry rig's -box family suffix #125
16 changed files with 194 additions and 95 deletions
54
CHANGELOG.md
54
CHANGELOG.md
|
|
@ -5,6 +5,60 @@ which records not just what changed but what each drill run proved.
|
|||
|
||||
## Unreleased
|
||||
|
||||
### Changed
|
||||
|
||||
- **The tenant templates carry rig's family suffix: `claude` → `claude-box`,
|
||||
`codex` → `codex-box`, `grok` → `grok-box`, `staging` → `staging-box`**
|
||||
(#123, following heavy-duty/rig#76) — 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 itself — `-server` for fleet machines, `-box` for box tenants —
|
||||
and box's answer is that a template keeps being named for the role it
|
||||
converges. Templates are the only surface that spells a rig role out loud
|
||||
(`BOX_BOOTSTRAP_ROLE`, auto-run at mint since #81), so a template whose
|
||||
directory says one thing and whose role key says another is a trap with a
|
||||
15-minute fuse: it mints clean and dies at convergence. `blank` keeps its
|
||||
name — it seeds no tenant role, sets no `BOX_BOOTSTRAP_ROLE`, and
|
||||
therefore has nothing to agree with. 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`, because that is the user
|
||||
the rig role converges and the one `box shell` lands in. `test/cli.sh`
|
||||
pins the pair per tenant rather than each half alone — a future rename
|
||||
that moves one and forgets the other mints a box whose role dies looking
|
||||
for a user nobody created.
|
||||
|
||||
**This lands after rig's rename, not before, and the ordering is not a
|
||||
preference.** The seeds install rig from `RIG_REPO`/`RIG_REF`, which
|
||||
default to `heavy-duty/rig@main` and are unpinned until rig#32's releases
|
||||
— so a box minted from these templates asks whatever `main` happens to be
|
||||
for `rig bootstrap claude-box`. Against a pre-rename rig that role does
|
||||
not exist, `cmd_new` refuses to call the box ready, and the operator is
|
||||
handed a failed mint for a change neither repo has finished making. Merged
|
||||
in the other order the window closes instead of opening: rig's roles are a
|
||||
hard cut with no aliases, so the day rig's rename lands, every unmerged
|
||||
box seed naming a bare role is the broken one.
|
||||
|
||||
One deliberate asymmetry: the mint-time hints in `cmd_new` match both the
|
||||
new and the old spelling of `user.box.template`. That is not an alias for
|
||||
the role — nothing here softens the cut, and `rig bootstrap claude` is
|
||||
gone. It reads a stamp left on an *instance* at its own mint time, which
|
||||
every box minted before today carries forever and every clone carries
|
||||
forward; refusing the old spelling would cut nothing over and only drop
|
||||
the login hint on boxes that predate the rename, 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.
|
||||
|
||||
The **machine**-role half of rig's rename reaches box too, in one place:
|
||||
the tailnet workload join box prints as a next step for a `staging-box`
|
||||
guest is now `sudo rig bootstrap workload-server`. box never runs it — it
|
||||
holds a pre-auth key, and that it stays operator-run is the absence
|
||||
keeping box creds-free end to end — but box does *print* it, in three
|
||||
places that all had to move together (`cmd_new`'s hint, the
|
||||
`staging-box` seed's own comment, and the README). A next step an
|
||||
operator copy-pastes is as wrong as a role box executes, and it fails
|
||||
later and further from the cause.
|
||||
|
||||
## 0.8.0 — 2026-07-19
|
||||
|
||||
### Added
|
||||
|
|
|
|||
46
README.md
46
README.md
|
|
@ -2,8 +2,8 @@
|
|||
|
||||
**Headless, trust-less, throwaway dev VMs.** One command mints a fresh,
|
||||
network-isolated Incus box from a **template**; the coding-agent templates
|
||||
hand you a CLI agent on Debian 13 — `claude` (Claude Code), `codex` (OpenAI
|
||||
Codex), `grok` (xAI Grok) — **box mints, [rig](https://github.com/heavy-duty/rig)
|
||||
hand you a CLI agent on Debian 13 — `claude-box` (Claude Code), `codex-box`
|
||||
(OpenAI Codex), `grok-box` (xAI Grok) — **box mints, [rig](https://github.com/heavy-duty/rig)
|
||||
converges**: the template is a thin seed, and the agent tooling lands via a
|
||||
creds-free `rig bootstrap` role auto-run at mint
|
||||
([#81](https://github.com/heavy-duty/box/issues/81)). The box is the product
|
||||
|
|
@ -217,13 +217,14 @@ VM boundary itself is proven on real hardware, like the rest of the drill).
|
|||
## Quick start
|
||||
|
||||
```sh
|
||||
box new --name work --template claude # a creds-free coding-agent box (~10 min cold)
|
||||
box shell work # enter as the template's user
|
||||
box new --name work --template claude-box # a creds-free coding-agent box (~10 min cold)
|
||||
box shell work # enter as the template's user
|
||||
```
|
||||
|
||||
Pick whichever coding-agent template you like — `claude`, `codex`, `grok` — or
|
||||
`blank` for none. Inside the box, authenticate as needed. The `claude` template
|
||||
looks like this; the others follow the same shape with their own login step:
|
||||
Pick whichever coding-agent template you like — `claude-box`, `codex-box`,
|
||||
`grok-box` — or `blank` for none. Inside the box, authenticate as needed. The
|
||||
`claude-box` template looks like this; the others follow the same shape with
|
||||
their own login step:
|
||||
|
||||
```sh
|
||||
claude # then run /login — copy the URL (press c), open it
|
||||
|
|
@ -238,13 +239,13 @@ claude # if the repo has .box/, the agent reads it and
|
|||
No coding agent is special — each is one template among several, and adding
|
||||
another is just another directory. What ships today:
|
||||
|
||||
| Template | What it becomes |
|
||||
| --------- | ---------------------------------------------------------------------- |
|
||||
| `blank` | Bare Debian 13 — same isolation, no tooling. The default. |
|
||||
| `claude` | Claude Code, creds-free — where this project started |
|
||||
| `codex` | OpenAI Codex CLI, creds-free |
|
||||
| `grok` | xAI Grok CLI, creds-free |
|
||||
| `staging` | Server-class: docker + sshd hardening via rig; VM-only, autostarts |
|
||||
| Template | What it becomes |
|
||||
| ------------- | ------------------------------------------------------------------ |
|
||||
| `blank` | Bare Debian 13 — same isolation, no tooling. The default. |
|
||||
| `claude-box` | Claude Code, creds-free — where this project started |
|
||||
| `codex-box` | OpenAI Codex CLI, creds-free |
|
||||
| `grok-box` | xAI Grok CLI, creds-free |
|
||||
| `staging-box` | Server-class: docker + sshd hardening via rig; VM-only, autostarts |
|
||||
|
||||
**Templates are thin seeds; rig does the becoming**
|
||||
([#81](https://github.com/heavy-duty/box/issues/81)). A template is a
|
||||
|
|
@ -255,8 +256,11 @@ pin tokens below). The seed is deliberately small — the tenant user, tmux,
|
|||
and [rig](https://github.com/heavy-duty/rig) preinstalled, nothing that
|
||||
joins a tailnet or admits credentials — and after cloud-init settles, box
|
||||
auto-runs the template's **creds-free** tenant role inside the guest
|
||||
(`rig bootstrap claude` / `codex` / `grok` / `staging`,
|
||||
[rig#31](https://github.com/heavy-duty/rig/issues/31)). The agent CLI,
|
||||
(`rig bootstrap claude-box` / `codex-box` / `grok-box` / `staging-box`,
|
||||
[rig#31](https://github.com/heavy-duty/rig/issues/31); the roles carry a
|
||||
family suffix — `-box` for box tenants, `-server` for fleet machines — and a
|
||||
template is named for the role it converges,
|
||||
[rig#76](https://github.com/heavy-duty/rig/issues/76)). The agent CLI,
|
||||
docker, the server posture and the agent-context file all come from that
|
||||
role — convergent and idempotent, so the same command re-run later converges
|
||||
an *existing* box to a newer spec (`box shell <box>` →
|
||||
|
|
@ -265,9 +269,9 @@ an *existing* box to a newer spec (`box shell <box>` →
|
|||
`box setup-host`, `box teardown-host` or the drill *inside* a box — once,
|
||||
from rig's roles, instead of copy-pasted per template.
|
||||
|
||||
**Anything that joins or admits stays operator-run.** The `staging` box's
|
||||
tailnet workload join holds a pre-auth key, so box only prints it as the
|
||||
next step — `box shell <name>`, then `sudo rig bootstrap workload` — and
|
||||
**Anything that joins or admits stays operator-run.** The `staging-box`
|
||||
tenant's tailnet workload join holds a pre-auth key, so box only prints it as the
|
||||
next step — `box shell <name>`, then `sudo rig bootstrap workload-server` — and
|
||||
never sees the key ([#69](https://github.com/heavy-duty/box/issues/69)'s
|
||||
split, kept).
|
||||
|
||||
|
|
@ -279,9 +283,9 @@ carries `@RIG_REPO@`/`@RIG_REF@` tokens that box resolves at mint from the
|
|||
environment:
|
||||
|
||||
```sh
|
||||
box new --name work --template claude # heavy-duty/rig @ main
|
||||
box new --name work --template claude-box # heavy-duty/rig @ main
|
||||
RIG_REPO=you/rig RIG_REF=my-branch \
|
||||
box new --name trial --template claude # a rig branch under review
|
||||
box new --name trial --template claude-box # a rig branch under review
|
||||
```
|
||||
|
||||
Both directions of that edge track `main` unpinned today — said honestly,
|
||||
|
|
|
|||
35
bin/box
35
bin/box
|
|
@ -185,7 +185,7 @@ OPTIONS
|
|||
|
||||
EXAMPLES
|
||||
# mint a claude box and log in inside it — the tool never handles your token
|
||||
box new --name work --template claude
|
||||
box new --name work --template claude-box
|
||||
box shell work # then: run 'claude', then /login
|
||||
|
||||
# log in once, reuse forever: checkpoint the authed box, clone from it
|
||||
|
|
@ -242,13 +242,13 @@ help_cmd() {
|
|||
case "$1" in
|
||||
new) cat <<'EOF'
|
||||
Mint a box. Without --from, launches a fresh box from a template (default:
|
||||
blank — bare Debian 13, nobody home; --template claude gets Claude Code
|
||||
blank — bare Debian 13, nobody home; --template claude-box gets Claude Code
|
||||
installed, creds-free, ~10 min cold). With --from, clones an existing box or
|
||||
one of its snapshots — login state, git creds and clones carry over,
|
||||
isolation is preserved, and the clone knows its template's user without
|
||||
being told.
|
||||
|
||||
A tenant template (claude, codex, grok, staging) is a THIN seed — the user,
|
||||
A tenant template (claude-box, codex-box, grok-box, staging-box) is a THIN seed — the user,
|
||||
tmux, rig (#81) — and after cloud-init box auto-runs the creds-free tenant
|
||||
role inside it ('rig bootstrap <role>', rig#31): that role installs the
|
||||
agent CLI / server posture and the agent-context file. rig is preinstalled
|
||||
|
|
@ -286,8 +286,8 @@ BOX_LAUNCH_TIMEOUT=<seconds> (default 600) bounds the 'incus launch' call —
|
|||
a launch that overruns it fails loudly instead of hanging forever (#93).
|
||||
|
||||
box new --name scratch # blank, the default
|
||||
box new --name work --template claude
|
||||
box new --name lean --template claude --cpu 2 --memory 3GiB
|
||||
box new --name work --template claude-box
|
||||
box new --name lean --template claude-box --cpu 2 --memory 3GiB
|
||||
box new --name feature --from work/authed
|
||||
EOF
|
||||
;;
|
||||
|
|
@ -473,7 +473,7 @@ The in-box server must listen on 0.0.0.0:<port>, not only its own loopback —
|
|||
a VM's forwarder connects to the box over the network. Inside an isolated box
|
||||
that is safe: nothing but this proxy can reach the port.
|
||||
|
||||
box new --name web --template claude
|
||||
box new --name web --template claude-box
|
||||
box shell web # inside: run a dev server on 0.0.0.0:3000
|
||||
box expose web 3000 # then open http://127.0.0.1:3000 in your browser
|
||||
EOF
|
||||
|
|
@ -1183,16 +1183,25 @@ cmd_new() {
|
|||
fi
|
||||
fi
|
||||
fi
|
||||
# The login hint belongs to the claude template — read the EFFECTIVE
|
||||
# template off the instance, so a clone of a claude box gets it too and a
|
||||
# blank box is not told to run a binary it doesn't have.
|
||||
# The login hint belongs to the claude-box template — read the EFFECTIVE
|
||||
# template off the instance, so a clone of a claude-box box gets it too and
|
||||
# a blank box is not told to run a binary it doesn't have.
|
||||
#
|
||||
# Both spellings match, and that is not an alias for the ROLE. The role
|
||||
# names are a hard cut (rig#76) — 'rig bootstrap claude' is gone, so the
|
||||
# seeds ask for 'claude-box' and nothing here softens that. What these arms
|
||||
# read is user.box.template, a stamp left on an INSTANCE at its own mint
|
||||
# time: every box minted before the rename carries the bare name forever,
|
||||
# and a clone carries it forward. Refusing the old spelling here would not
|
||||
# cut anything over, it would only drop the hint on boxes that predate the
|
||||
# rename — the same reason user.claudebox is honored everywhere else.
|
||||
local eff; eff="$(incus config get "$instance" user.box.template 2>/dev/null || true)"
|
||||
[ -z "$eff" ] && [ "$(incus config get "$instance" user.claudebox 2>/dev/null || true)" = 1 ] && eff=claude
|
||||
if [ "$eff" = claude ]; then
|
||||
[ -z "$eff" ] && [ "$(incus config get "$instance" user.claudebox 2>/dev/null || true)" = 1 ] && eff=claude-box
|
||||
if [ "$eff" = claude-box ] || [ "$eff" = claude ]; then
|
||||
echo "box: ready — 'box shell $name'. Log into Claude inside: run 'claude' then /login."
|
||||
elif [ "$eff" = staging ]; then
|
||||
elif [ "$eff" = staging-box ] || [ "$eff" = staging ]; then
|
||||
echo "box: ready — 'box shell $name'. The tailnet join stays operator-run (it holds a key box must never see):"
|
||||
echo " box shell $name # then: sudo rig bootstrap workload --hostname $name"
|
||||
echo " box shell $name # then: sudo rig bootstrap workload-server --hostname $name"
|
||||
else
|
||||
echo "box: ready — 'box shell $name'."
|
||||
fi
|
||||
|
|
|
|||
|
|
@ -1,8 +1,8 @@
|
|||
# box design
|
||||
|
||||
`box` is a CLI that mints and manages **trust-less, network-isolated VMs
|
||||
with a coding agent installed** (`claude`, `codex`, `grok`, or `blank` for
|
||||
none). It is infrastructure, not a project provisioner.
|
||||
with a coding agent installed** (`claude-box`, `codex-box`, `grok-box`, or
|
||||
`blank` for none). It is infrastructure, not a project provisioner.
|
||||
|
||||
See issue #3 for the full reframe and rationale. This doc captures the durable
|
||||
design decisions.
|
||||
|
|
@ -60,15 +60,17 @@ instead of pretending to sanitize it.
|
|||
A template is a **thin, creds-free seed** — base image, the tenant user,
|
||||
tmux, and [rig](https://github.com/heavy-duty/rig) preinstalled — and what
|
||||
the box *becomes* lives in rig's bootstrap roles (rig#31): box auto-runs the
|
||||
template's creds-free tenant role after cloud-init (`rig bootstrap claude` /
|
||||
`codex` / `grok` / `staging`), which installs the agent CLI or server
|
||||
posture. The split is deliberate: cloud-init is a first-boot one-shot —
|
||||
template's creds-free tenant role after cloud-init (`rig bootstrap claude-box`
|
||||
/ `codex-box` / `grok-box` / `staging-box` — the roles carry a family suffix,
|
||||
`-box` for box tenants and `-server` for fleet machines, and the templates are
|
||||
named for the roles they converge, rig#76), which installs the agent CLI or
|
||||
server posture. The split is deliberate: cloud-init is a first-boot one-shot —
|
||||
not convergent, not re-runnable, only parse-and-grep testable — while a rig
|
||||
role is an idempotent script with effective-state asserts that can also
|
||||
converge an *existing* box to a newer spec. Anything that joins a tailnet or
|
||||
holds a key (staging's workload join) stays operator-run through
|
||||
`box shell`; box prints it as a next step and never sees the key. The seed's
|
||||
rig install is pinned by `RIG_REPO`/`RIG_REF` at mint (default
|
||||
holds a key (the staging-box tenant's workload join) stays operator-run
|
||||
through `box shell`; box prints it as a next step and never sees the key. The
|
||||
seed's rig install is pinned by `RIG_REPO`/`RIG_REF` at mint (default
|
||||
`heavy-duty/rig@main`, unpinned — the honest edge until rig#32's releases),
|
||||
and box's template suite holds the line with fail-closed absence greps: no
|
||||
agent CLI, no docker, no tailscale, no context-file heredocs in any
|
||||
|
|
|
|||
|
|
@ -2,9 +2,9 @@
|
|||
|
||||
`box` mints trust-less, creds-free, isolated VMs with a coding agent already
|
||||
installed (`box new/shell/snapshot/restore/exec/down/start/rm/status`) — the
|
||||
`claude`, `codex`, and `grok` templates each ship a CLI agent. The tool knows
|
||||
**nothing** about your project. There is no `install` step and no host-run
|
||||
setup script.
|
||||
`claude-box`, `codex-box`, and `grok-box` templates each ship a CLI agent. The
|
||||
tool knows **nothing** about your project. There is no `install` step and no
|
||||
host-run setup script.
|
||||
|
||||
A project makes itself easy to stand up inside a box by shipping an optional
|
||||
`.box/` folder. This folder is **agent-facing documentation** — read and
|
||||
|
|
|
|||
|
|
@ -378,7 +378,7 @@ if ! timeout -k 10 300 ~/.local/share/box/current/host/setup-host.sh; then
|
|||
fi
|
||||
inf "host setup complete"
|
||||
|
||||
# A real server has room for the claude template's resources (8GiB/4cpu), and
|
||||
# A real server has room for the claude-box template's resources (8GiB/4cpu), and
|
||||
# drilling the real numbers is worth more than drilling shrunken ones. Only
|
||||
# shrink if we must. Since 0.4.0 resources are per-box, stamped from the
|
||||
# template at mint — a profile edit no longer reaches them; the supported
|
||||
|
|
@ -386,9 +386,9 @@ inf "host setup complete"
|
|||
ram="$(awk '/MemTotal/{print int($2/1024/1024)}' /proc/meminfo)"
|
||||
if [ "$ram" -lt 20 ]; then
|
||||
export BOX_MEMORY=3GiB BOX_CPU=2
|
||||
note "host has ${ram}GiB RAM — minting at 3GiB/2cpu via BOX_MEMORY/BOX_CPU (the claude template's 8GiB/4cpu is what was NOT drilled)"
|
||||
note "host has ${ram}GiB RAM — minting at 3GiB/2cpu via BOX_MEMORY/BOX_CPU (the claude-box template's 8GiB/4cpu is what was NOT drilled)"
|
||||
else
|
||||
inf "host has ${ram}GiB RAM — drilling the claude template's resources (8GiB/4cpu) unchanged"
|
||||
inf "host has ${ram}GiB RAM — drilling the claude-box template's resources (8GiB/4cpu) unchanged"
|
||||
fi
|
||||
|
||||
KVM=0; [ -e /dev/kvm ] && KVM=1
|
||||
|
|
@ -492,10 +492,10 @@ fi
|
|||
|
||||
# --- templates: the mint surface is itself a surface to test ----------------
|
||||
tpl_missing=""
|
||||
for t in blank claude codex grok; do
|
||||
for t in blank claude-box codex-box grok-box; do
|
||||
box templates 2>/dev/null | grep -q "^ $t" || tpl_missing="$tpl_missing $t"
|
||||
done
|
||||
[ -z "$tpl_missing" ] && ok "templates: lists blank, claude, codex, grok" \
|
||||
[ -z "$tpl_missing" ] && ok "templates: lists blank, claude-box, codex-box, grok-box" \
|
||||
|| no "templates listing is missing:$tpl_missing"
|
||||
box new --name tpl --template nosuch 2>&1 | grep -q 'no such template' \
|
||||
&& ok "unknown template refused, points at 'box templates'" || no "an unknown template was not refused"
|
||||
|
|
@ -551,19 +551,22 @@ else
|
|||
fi
|
||||
|
||||
# The generic mechanic (metadata, placement, user, isolation parity) is proven
|
||||
# once by blank+claude and needs no per-template repeat. What a NEW template
|
||||
# once by blank+claude-box and needs no per-template repeat. What a NEW template
|
||||
# still has to prove is its own payload: the CLI installs, lands on the
|
||||
# non-interactive exec PATH, and answers --version. One mint each.
|
||||
# The box NAME stays the bare agent name — it is what the pre-flight banner
|
||||
# announces and what teardown deletes — while the TEMPLATE carries rig#76's
|
||||
# family suffix. They are two different namespaces and only one of them moved.
|
||||
for t in codex grok; do
|
||||
case "$t" in codex) bin=codex; user=codex ;; grok) bin=grok; user=grok ;; esac
|
||||
printf '\n minting a %s box (cold — validates the template install)…\n' "$t"
|
||||
if mint_box "/tmp/mint-$t.log" --name "$t" --template "$t"; then
|
||||
if mint_box "/tmp/mint-$t.log" --name "$t" --template "$t-box"; then
|
||||
[ "$(incus config get "$t" user.box.user 2>/dev/null)" = "$user" ] \
|
||||
&& ok "$t: template user stamped ($user)" || no "$t: user.box.user not $user"
|
||||
if timeout -k 5 30 box exec "$t" -- "$bin" --version </dev/null >/dev/null 2>&1; then
|
||||
ok "$t: '$bin --version' answers via box exec — installed and on the non-interactive PATH"
|
||||
else
|
||||
no "$t: '$bin --version' FAILED via exec — not installed, or not on exec's PATH (the claude template's #15 bug)"
|
||||
no "$t: '$bin --version' FAILED via exec — not installed, or not on exec's PATH (the claude-box template's #15 bug)"
|
||||
inf "PATH as exec sees it: $(timeout -k 5 20 box exec "$t" -- printenv PATH </dev/null 2>/dev/null)"
|
||||
# Do not throw the evidence away — say WHAT the installer actually left.
|
||||
# Do NOT throw the evidence away — say what the installer actually left
|
||||
|
|
@ -583,10 +586,10 @@ for t in codex grok; do
|
|||
fi
|
||||
done
|
||||
|
||||
printf '\n minting a claude box (cold, ~10 min)…\n'
|
||||
printf '\n minting a claude-box box (cold, ~10 min)…\n'
|
||||
t0=$SECONDS
|
||||
if mint_box /tmp/mint-drill.log --name drill --template claude; then
|
||||
ok "box new --name drill --template claude ($((SECONDS - t0))s)"
|
||||
if mint_box /tmp/mint-drill.log --name drill --template claude-box; then
|
||||
ok "box new --name drill --template claude-box ($((SECONDS - t0))s)"
|
||||
else
|
||||
no "box new FAILED — tail: $(tail -3 /tmp/mint-drill.log | tr '\n' ' ')"
|
||||
timeout -k 5 60 incus delete -f drill >/dev/null 2>&1
|
||||
|
|
@ -799,7 +802,7 @@ fi
|
|||
# ===========================================================================
|
||||
phase "E. box expose — a deliberate loopback door (#55)"
|
||||
# ===========================================================================
|
||||
# archive is a running claude box (node is installed). Start a DETACHED
|
||||
# archive is a running claude-box box (node is installed). Start a DETACHED
|
||||
# listener on 0.0.0.0 inside it, expose the port, and prove the door works
|
||||
# from the HOST's loopback. Then prove removing it closes the door, and that a
|
||||
# NON-exposed port still obeys the ingress drop — the feature must not
|
||||
|
|
|
|||
|
|
@ -64,7 +64,11 @@ legacy_boxes() {
|
|||
}
|
||||
|
||||
# Re-home one box. Legacy boxes are all claude boxes (the only template the old
|
||||
# tool minted), so the new metadata is the claude template's.
|
||||
# tool minted), so the new metadata is the claude-box template's. The stamp
|
||||
# names the template as it is called TODAY, not as it was called when the box
|
||||
# was minted: it is what 'box shell' and the mint hints read, so a re-homed
|
||||
# box should look like a fresh claude-box mint, not like a fossil (rig#76's
|
||||
# family suffix — the template is named for the role it converges).
|
||||
rehome_one() {
|
||||
local b="$1" st
|
||||
incus config get "$b" user.claudebox >/dev/null 2>&1 || { warn "$b is not a legacy box (no user.claudebox tag) — skipping"; return 1; }
|
||||
|
|
@ -75,7 +79,7 @@ rehome_one() {
|
|||
|
||||
# 1. TAG FIRST — additive and reversible. A box that stops here is still a
|
||||
# valid legacy box (the old tag is untouched) AND now a new one.
|
||||
incus config set "$b" user.box=1 user.box.template=claude user.box.user=claude \
|
||||
incus config set "$b" user.box=1 user.box.template=claude-box user.box.user=claude \
|
||||
|| { warn "$b: could not set new metadata — left untouched"; return 1; }
|
||||
|
||||
# 2. Stop, reassign the profile (this is the network move), restart. Incus
|
||||
|
|
|
|||
|
|
@ -1,7 +1,11 @@
|
|||
# The claude template — a thin, creds-free seed (#81): Debian 13, the
|
||||
# The claude-box template — a thin, creds-free seed (#81): Debian 13, the
|
||||
# 'claude' user, tmux and rig. What the box BECOMES — the Claude Code CLI,
|
||||
# docker, node, the agent-context file with its #80 guard — is rig's job:
|
||||
# box auto-runs 'rig bootstrap claude' after mint (heavy-duty/rig#31).
|
||||
# box auto-runs 'rig bootstrap claude-box' after mint (heavy-duty/rig#31).
|
||||
# The template is named for the role it converges, suffix and all
|
||||
# (heavy-duty/rig#76): rig's roles carry a family suffix — '-server' for
|
||||
# fleet machines, '-box' for box tenants — and a seed that named the bare
|
||||
# 'claude' would ask a post-rename rig for a role that no longer exists.
|
||||
# KEY="value" only. Parsed against an allowlist, never sourced; there is no
|
||||
# key for a network or a security flag, on purpose — the shared box-net
|
||||
# profile is the placement contract and no template can weaken it.
|
||||
|
|
@ -14,4 +18,4 @@ BOX_USER="claude"
|
|||
BOX_CPU="4"
|
||||
BOX_MEMORY="8GiB"
|
||||
BOX_DISK="60GiB"
|
||||
BOX_BOOTSTRAP_ROLE="claude"
|
||||
BOX_BOOTSTRAP_ROLE="claude-box"
|
||||
|
|
@ -2,7 +2,7 @@
|
|||
# A thin, creds-free seed (#81): the tenant user, tmux (#65), and rig —
|
||||
# nothing that joins a tailnet or admits credentials, no agent CLI, no
|
||||
# docker, no context-file heredoc. Everything this box becomes comes from
|
||||
# 'rig bootstrap claude' (heavy-duty/rig#31), which box auto-runs after
|
||||
# 'rig bootstrap claude-box' (heavy-duty/rig#31), which box auto-runs after
|
||||
# mint; the agent-context file — including the #80 guard (never run
|
||||
# 'box setup-host' or the drill inside a box) — is rendered by that role,
|
||||
# once, instead of being copy-pasted per template.
|
||||
|
|
@ -21,7 +21,7 @@ packages:
|
|||
- ca-certificates
|
||||
runcmd:
|
||||
# Preinstall rig so the box can converge — and re-converge — via
|
||||
# 'rig bootstrap claude'. @RIG_REPO@/@RIG_REF@ are the pin point (#81):
|
||||
# 'rig bootstrap claude-box'. @RIG_REPO@/@RIG_REF@ are the pin point (#81):
|
||||
# box substitutes them at mint from the RIG_REPO/RIG_REF environment
|
||||
# (default heavy-duty/rig @ main — unpinned, tracking main, the same
|
||||
# honest edge as rig's own unpinned box install, until rig#32 ships a
|
||||
|
|
@ -1,7 +1,11 @@
|
|||
# The codex template — a thin, creds-free seed (#81): Debian 13, the
|
||||
# The codex-box template — a thin, creds-free seed (#81): Debian 13, the
|
||||
# 'codex' user, tmux and rig. What the box BECOMES — the OpenAI Codex CLI,
|
||||
# docker, node, the agent-context file with its #80 guard — is rig's job:
|
||||
# box auto-runs 'rig bootstrap codex' after mint (heavy-duty/rig#31).
|
||||
# box auto-runs 'rig bootstrap codex-box' after mint (heavy-duty/rig#31).
|
||||
# The template is named for the role it converges, suffix and all
|
||||
# (heavy-duty/rig#76): rig's roles carry a family suffix — '-server' for
|
||||
# fleet machines, '-box' for box tenants — and a seed that named the bare
|
||||
# 'codex' would ask a post-rename rig for a role that no longer exists.
|
||||
# KEY="value" only. Parsed against an allowlist, never sourced; there is no
|
||||
# key for a network or a security flag, on purpose — the shared box-net
|
||||
# profile is the placement contract and no template can weaken it.
|
||||
|
|
@ -14,4 +18,4 @@ BOX_USER="codex"
|
|||
BOX_CPU="4"
|
||||
BOX_MEMORY="8GiB"
|
||||
BOX_DISK="60GiB"
|
||||
BOX_BOOTSTRAP_ROLE="codex"
|
||||
BOX_BOOTSTRAP_ROLE="codex-box"
|
||||
|
|
@ -2,7 +2,7 @@
|
|||
# A thin, creds-free seed (#81): the tenant user, tmux (#65), and rig —
|
||||
# nothing that joins a tailnet or admits credentials, no agent CLI, no
|
||||
# docker, no context-file heredoc. Everything this box becomes comes from
|
||||
# 'rig bootstrap codex' (heavy-duty/rig#31), which box auto-runs after
|
||||
# 'rig bootstrap codex-box' (heavy-duty/rig#31), which box auto-runs after
|
||||
# mint; the agent-context file — including the #80 guard (never run
|
||||
# 'box setup-host' or the drill inside a box) — is rendered by that role,
|
||||
# once, instead of being copy-pasted per template.
|
||||
|
|
@ -21,7 +21,7 @@ packages:
|
|||
- ca-certificates
|
||||
runcmd:
|
||||
# Preinstall rig so the box can converge — and re-converge — via
|
||||
# 'rig bootstrap codex'. @RIG_REPO@/@RIG_REF@ are the pin point (#81):
|
||||
# 'rig bootstrap codex-box'. @RIG_REPO@/@RIG_REF@ are the pin point (#81):
|
||||
# box substitutes them at mint from the RIG_REPO/RIG_REF environment
|
||||
# (default heavy-duty/rig @ main — unpinned, tracking main, the same
|
||||
# honest edge as rig's own unpinned box install, until rig#32 ships a
|
||||
|
|
@ -1,7 +1,11 @@
|
|||
# The grok template — a thin, creds-free seed (#81): Debian 13, the
|
||||
# The grok-box template — a thin, creds-free seed (#81): Debian 13, the
|
||||
# 'grok' user, tmux and rig. What the box BECOMES — the xAI Grok CLI,
|
||||
# docker, the agent-context file with its #80 guard — is rig's job:
|
||||
# box auto-runs 'rig bootstrap grok' after mint (heavy-duty/rig#31).
|
||||
# box auto-runs 'rig bootstrap grok-box' after mint (heavy-duty/rig#31).
|
||||
# The template is named for the role it converges, suffix and all
|
||||
# (heavy-duty/rig#76): rig's roles carry a family suffix — '-server' for
|
||||
# fleet machines, '-box' for box tenants — and a seed that named the bare
|
||||
# 'grok' would ask a post-rename rig for a role that no longer exists.
|
||||
# KEY="value" only. Parsed against an allowlist, never sourced; there is no
|
||||
# key for a network or a security flag, on purpose — the shared box-net
|
||||
# profile is the placement contract and no template can weaken it.
|
||||
|
|
@ -14,4 +18,4 @@ BOX_USER="grok"
|
|||
BOX_CPU="4"
|
||||
BOX_MEMORY="8GiB"
|
||||
BOX_DISK="60GiB"
|
||||
BOX_BOOTSTRAP_ROLE="grok"
|
||||
BOX_BOOTSTRAP_ROLE="grok-box"
|
||||
|
|
@ -2,7 +2,7 @@
|
|||
# A thin, creds-free seed (#81): the tenant user, tmux (#65), and rig —
|
||||
# nothing that joins a tailnet or admits credentials, no agent CLI, no
|
||||
# docker, no context-file heredoc. Everything this box becomes comes from
|
||||
# 'rig bootstrap grok' (heavy-duty/rig#31), which box auto-runs after
|
||||
# 'rig bootstrap grok-box' (heavy-duty/rig#31), which box auto-runs after
|
||||
# mint; the agent-context file — including the #80 guard (never run
|
||||
# 'box setup-host' or the drill inside a box) — is rendered by that role,
|
||||
# once, instead of being copy-pasted per template.
|
||||
|
|
@ -21,7 +21,7 @@ packages:
|
|||
- ca-certificates
|
||||
runcmd:
|
||||
# Preinstall rig so the box can converge — and re-converge — via
|
||||
# 'rig bootstrap grok'. @RIG_REPO@/@RIG_REF@ are the pin point (#81):
|
||||
# 'rig bootstrap grok-box'. @RIG_REPO@/@RIG_REF@ are the pin point (#81):
|
||||
# box substitutes them at mint from the RIG_REPO/RIG_REF environment
|
||||
# (default heavy-duty/rig @ main — unpinned, tracking main, the same
|
||||
# honest edge as rig's own unpinned box install, until rig#32 ships a
|
||||
|
|
@ -1,9 +1,14 @@
|
|||
# The staging template — a thin, creds-free, server-class seed (#81, the
|
||||
# The staging-box template — a thin, creds-free, server-class seed (#81, the
|
||||
# re-cut of #69's layering): Debian 13, the 'ops' user, tmux and rig. The
|
||||
# server posture — docker, sshd hardening — is rig's job: box auto-runs
|
||||
# 'rig bootstrap staging' after mint (heavy-duty/rig#31). The tailnet
|
||||
# 'rig bootstrap staging-box' after mint (heavy-duty/rig#31). The template is
|
||||
# named for the role it converges, suffix and all (heavy-duty/rig#76): rig's
|
||||
# roles carry a family suffix — '-server' for fleet machines, '-box' for box
|
||||
# tenants — and a seed that named the bare 'staging' would ask a post-rename
|
||||
# rig for a role that no longer exists. Server-CLASS, not a fleet machine:
|
||||
# this is still a box tenant, so it takes '-box', not '-server'. The tailnet
|
||||
# workload join holds a key and therefore STAYS operator-run:
|
||||
# box shell <name> # then: sudo rig bootstrap workload --hostname <name>
|
||||
# box shell <name> # then: sudo rig bootstrap workload-server --hostname <name>
|
||||
# KEY="value" only. Parsed against an allowlist, never sourced; there is no
|
||||
# key for a network or a security flag, on purpose — the shared box-net
|
||||
# profile is the placement contract and no template can weaken it.
|
||||
|
|
@ -21,4 +26,4 @@ BOX_MEMORY="8GiB"
|
|||
BOX_DISK="60GiB"
|
||||
BOX_REQUIRE_VM="1"
|
||||
BOX_AUTOSTART="1"
|
||||
BOX_BOOTSTRAP_ROLE="staging"
|
||||
BOX_BOOTSTRAP_ROLE="staging-box"
|
||||
|
|
@ -2,9 +2,9 @@
|
|||
# A thin, creds-free, server-class seed (#81): the 'ops' user, tmux (#65),
|
||||
# and rig — nothing that joins a tailnet or admits credentials, no docker,
|
||||
# no sshd config, no keys. The server posture comes from
|
||||
# 'rig bootstrap staging' (heavy-duty/rig#31), which box auto-runs after
|
||||
# 'rig bootstrap staging-box' (heavy-duty/rig#31), which box auto-runs after
|
||||
# mint; the tailnet workload join holds a pre-auth key and stays
|
||||
# operator-run ('box shell' → 'sudo rig bootstrap workload'), exactly as
|
||||
# operator-run ('box shell' → 'sudo rig bootstrap workload-server'), exactly as
|
||||
# #69 designed it — box never sees the key.
|
||||
users:
|
||||
- name: ops
|
||||
|
|
@ -22,7 +22,7 @@ packages:
|
|||
- ca-certificates
|
||||
runcmd:
|
||||
# Preinstall rig so the box can converge — and re-converge — via
|
||||
# 'rig bootstrap staging'. @RIG_REPO@/@RIG_REF@ are the pin point (#81):
|
||||
# 'rig bootstrap staging-box'. @RIG_REPO@/@RIG_REF@ are the pin point (#81):
|
||||
# box substitutes them at mint from the RIG_REPO/RIG_REF environment
|
||||
# (default heavy-duty/rig @ main — unpinned, tracking main, the same
|
||||
# honest edge as rig's own unpinned box install, until rig#32 ships a
|
||||
34
test/cli.sh
34
test/cli.sh
|
|
@ -310,16 +310,22 @@ done
|
|||
# is its trust boundary (its guest runs docker, via rig) and a server returns
|
||||
# from a host reboot without an operator. Pinned to the FILE so neither can
|
||||
# quietly vanish in a rewrite.
|
||||
check "staging: demands VM mode (BOX_REQUIRE_VM=1)" 0 "" \
|
||||
grep -qx 'BOX_REQUIRE_VM="1"' "$ROOT/templates/staging/box.env"
|
||||
check "staging: demands autostart (BOX_AUTOSTART=1)" 0 "" \
|
||||
grep -qx 'BOX_AUTOSTART="1"' "$ROOT/templates/staging/box.env"
|
||||
check "staging: the tenant role is 'staging'" 0 "ROLE=staging" tpl "$ROOT" staging
|
||||
check "staging: the seed user is rig's default for the role ('ops')" 0 "USER=ops" tpl "$ROOT" staging
|
||||
# The agent tenants: role = user = template name, rig's default mapping.
|
||||
for t in claude codex grok; do
|
||||
check "$t: role and user are '$t' (rig's default tenant mapping)" 0 "USER=$t REQUIRE_VM= AUTOSTART= ROLE=$t" \
|
||||
tpl "$ROOT" "$t"
|
||||
check "staging-box: demands VM mode (BOX_REQUIRE_VM=1)" 0 "" \
|
||||
grep -qx 'BOX_REQUIRE_VM="1"' "$ROOT/templates/staging-box/box.env"
|
||||
check "staging-box: demands autostart (BOX_AUTOSTART=1)" 0 "" \
|
||||
grep -qx 'BOX_AUTOSTART="1"' "$ROOT/templates/staging-box/box.env"
|
||||
check "staging-box: the tenant role is 'staging-box'" 0 "ROLE=staging-box" tpl "$ROOT" staging-box
|
||||
check "staging-box: the seed user is rig's default for the role ('ops')" 0 "USER=ops" tpl "$ROOT" staging-box
|
||||
# The agent tenants. Two names, not one: the TEMPLATE is named for the rig role
|
||||
# it converges — suffix and all, since rig's roles carry a family suffix
|
||||
# ('-box' for box tenants, '-server' for fleet machines, rig#76) — while the
|
||||
# seed USER stays the bare agent name, because that is the user rig's role
|
||||
# converges and the one 'box shell' lands in. The pairing is the whole point of
|
||||
# pinning it here: a rename that moves one and forgets the other mints a box
|
||||
# whose role dies looking for a user that was never created.
|
||||
for u in claude codex grok; do
|
||||
check "$u-box: role is '$u-box', seed user is '$u' (rig's tenant mapping)" \
|
||||
0 "USER=$u REQUIRE_VM= AUTOSTART= ROLE=$u-box" tpl "$ROOT" "$u-box"
|
||||
done
|
||||
# blank stays a box with NOBODY home: no rig, no role — same isolation, no
|
||||
# tooling, and nothing auto-runs in it.
|
||||
|
|
@ -426,11 +432,11 @@ check "new: the overran-but-registered branch says so (not the wedge story)" 0 "
|
|||
# shellcheck disable=SC2016 # the $-strings are literals in the target file
|
||||
check "new: BOX_LAUNCH_TIMEOUT is documented in box help new" 0 "" bash -c '
|
||||
"'"$ROOT"'/bin/box" help new | grep "BOX_LAUNCH_TIMEOUT" | grep -q 600'
|
||||
# staging's creds-holding join stays OPERATOR-run: cmd_new may print it as a
|
||||
# next step, but no template and no code path auto-runs "rig bootstrap
|
||||
# workload" — the one absence that keeps box creds-free end to end.
|
||||
# staging-box's creds-holding join stays OPERATOR-run: cmd_new may print it as
|
||||
# a next step, but no template and no code path auto-runs "rig bootstrap
|
||||
# workload-server" — the one absence that keeps box creds-free end to end.
|
||||
check "new: the workload join is printed, never exec'd" 1 "" bash -c '
|
||||
grep "rig bootstrap workload" "'"$ROOT"'/bin/box" | grep -q "incus exec"'
|
||||
grep "rig bootstrap workload-server" "'"$ROOT"'/bin/box" | grep -q "incus exec"'
|
||||
check "templates: no template names a creds-holding role" 1 "" bash -c '
|
||||
grep -h "^BOX_BOOTSTRAP_ROLE=" "'"$ROOT"'"/templates/*/box.env | grep -qE "workload|host|custom"'
|
||||
|
||||
|
|
|
|||
Loading…
Reference in a new issue