docs: BOX_SUBNET, the setup-host refusal, and the doctor signature (#80)

README's setup-host section documents the subnet input and the refusal;
'box help setup-host' names BOX_SUBNET and the nested-box trap; 'box help
doctor' names the #80 signature it now judges; the changelog carries the
full story, including that suggested fix 4 (the agent-context guard note
for the templates) lands in heavy-duty/rig#31's bootstrap roles per the
thin-templates split (#81).

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
This commit is contained in:
dan-claude-bot 2026-07-18 19:47:20 +00:00
parent 7d48822cc0
commit 53dc2b9047
3 changed files with 57 additions and 2 deletions

View file

@ -7,6 +7,34 @@ which records not just what changed but what each drill run proved.
### Added ### Added
- **`setup-host` refuses a claimed subnet, and `BOX_SUBNET` picks another**
(#80) — run inside a box, `setup-host` used to build a nested `boxnet` on
the exact subnet and gateway of the guest's own uplink: the guest then held
its gateway's address as a *local* address, carried duplicate connected
routes for its uplink subnet, and suffered intermittent, self-recovering
egress blackouts that looked like flaky internet (measured live: ~2436 s
outages, roughly hourly, with the host clean throughout). `setup-host` now
scans the target subnet **before any mutation** — the default route's
gateway inside it, or any non-`boxnet` interface holding an address in it —
and refuses, naming the way out. A prior `boxnet` owning the subnet is the
legitimate converge path and does not trip it. `BOX_SUBNET=<a.b.c.0/24>`
(validated, alongside the existing `BOX_DNS`) moves the whole stack: the
bridge address, the ACL's gateway carve-out (now converged via
`network acl edit`, so a bridge moved off a colliding subnet no longer
strands box DNS behind a stale `/32`), the firewall (`box-firewall` reads
the gateway off the live bridge), and every drill/migrate probe that used
to hardcode `10.88`.
- **`box doctor` knows the #80 signature** — a default gateway held as a
LOCAL address, and duplicate connected routes for the uplink subnet, judged
from `ip route`/`ip addr` on the machine doctor runs on (both tiers, before
any daemon check — the nested daemon answering could be the impostor) and
probed *inside* every box it examines. The existing "egress broken but DNS
fine" split now names itself as #80's fingerprint (the impostor dnsmasq on
a captured gateway keeps resolving while IP egress dies), and the admin ACL
section verifies the gateway carve-out matches `boxnet`'s actual gateway.
The agent-context guard for the templates (suggested fix 4) lands in
heavy-duty/rig#31's bootstrap roles per the thin-templates split (#81).
- **Server-posture template keys** (#81, carved from #69) — two optional - **Server-posture template keys** (#81, carved from #69) — two optional
`box.env` allowlist keys. `BOX_REQUIRE_VM=1` refuses both the silent `box.env` allowlist keys. `BOX_REQUIRE_VM=1` refuses both the silent
container fallback (no `/dev/kvm`, exit 1) and an explicit `--container` container fallback (no `/dev/kvm`, exit 1) and an explicit `--container`

View file

@ -126,6 +126,20 @@ re-apply at boot via `box-firewall.service` — no post-reboot ritual. If
the host lacks `dnsmasq-base` (Debian cloud images skip Recommends): the host lacks `dnsmasq-base` (Debian cloud images skip Recommends):
`sudo apt-get install -y dnsmasq-base`. `sudo apt-get install -y dnsmasq-base`.
The stack's subnet is `10.88.0.0/24` by default; `BOX_SUBNET` picks another
`/24` (`BOX_SUBNET=10.89.0.0/24 box setup-host` — the bridge address, the
ACL's gateway carve-out and the firewall all derive from it). setup-host
**refuses to build on a subnet something already claims** — most tellingly
when this machine's own default gateway sits inside it, which means it is
being run *inside a box*: a nested `boxnet` on the guest's own uplink subnet
captures its gateway address and blackholes the guest's egress in
intermittent, maddening-to-attribute blackouts
([#80](https://github.com/heavy-duty/box/issues/80)). `BOX_SUBNET` is the
sanctioned way out for a nested or otherwise-conflicted install, and
`box doctor` recognizes the poisoned state (a gateway held as a local
address, duplicate uplink routes) on the machine it runs on and inside every
box it probes.
A host still carrying the pre-0.4.0 stack: `box migrate-host --all-boxes` A host still carrying the pre-0.4.0 stack: `box migrate-host --all-boxes`
re-homes each legacy box onto `boxnet` (authed state preserved), and re-homes each legacy box onto `boxnet` (authed state preserved), and
`box migrate-host --retire-legacy` removes the old bridge and profile once no `box migrate-host --retire-legacy` removes the old bridge and profile once no

17
bin/box
View file

@ -459,8 +459,12 @@ EOF
Answer "is this host fit to mint boxes?" from ground truth, not config claims: Answer "is this host fit to mint boxes?" from ground truth, not config claims:
is the Incus daemon answering, is a dnsmasq actually serving boxnet, does is the Incus daemon answering, is a dnsmasq actually serving boxnet, does
the kernel's bridge port say 'isolated on', is the resolver pinned or is a the kernel's bridge port say 'isolated on', is the resolver pinned or is a
host VPN's DNS leaking into boxes, can a box actually resolve names. Every host VPN's DNS leaking into boxes, can a box actually resolve names — and
check exists because its fault has happened — most kill a cold mint with a the #80 nested-stack signature: a default gateway held as a LOCAL address,
or duplicate connected routes for the uplink subnet, judged on this machine
AND inside every box it probes (a box stack installed inside a box squats on
the guest's gateway and blackholes its egress, intermittently). Every check
exists because its fault has happened — most kill a cold mint with a
cloud-init error that names none of them. cloud-init error that names none of them.
--fix also revert what a drill run may have left behind --fix also revert what a drill run may have left behind
@ -484,7 +488,16 @@ install.sh runs it for you, so this is for re-applying by hand.
One run is enough. If it has to add you to the incus-admin group it re-runs One run is enough. If it has to add you to the incus-admin group it re-runs
itself under that group — no re-login, no second invocation. itself under that group — no re-login, no second invocation.
The stack's subnet is 10.88.0.0/24 by default; BOX_SUBNET picks another /24
(the bridge, the gateway carve-out and the firewall all derive from it). It
REFUSES, before touching anything, where the target subnet is already claimed
— most tellingly when this machine's own default gateway sits inside it,
i.e. when you are running setup-host INSIDE a box: a nested stack on the
guest's own uplink subnet captures its gateway address and blackholes its
egress, intermittently (issue #80). The sanctioned way to nest:
box setup-host box setup-host
BOX_SUBNET=10.89.0.0/24 box setup-host # nested, or a conflicted host
Multi-user hosts: setup-host builds the stack once, for everyone. An admin Multi-user hosts: setup-host builds the stack once, for everyone. An admin
then hands individual users the restricted tier with 'box grant <user>' — then hands individual users the restricted tier with 'box grant <user>' —