feat(setup-host): auto-pick a free subnet — nested box-in-box with zero flags (#80) #91
No reviewers
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#91
Loading…
Reference in a new issue
No description provided.
Delete branch "feat/subnet-autopick"
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?
Completes #80's fix #1. #86 shipped the refuse half — "refuse … or automatically select a non-colliding subnet" — and left the nested case (drills and rehearsals inside a box, the flagship footgun #80 documented) stopping on a refusal that even named the workaround it could have applied itself. A bare
box setup-hostnow applies it: it decides the subnet before any mutation, and everything downstream (BOX_GW, the bridge, the ACL's gateway carve-out, the firewall, the doctor's expectations) derives from that decision.The four cases (deliberate)
BOX_SUBNET)boxnetbridge10.88.0.0/2410.88.0.0/24, as always.10.89.0.0/24…10.127.0.0/24in order, take the first free/24(per-candidatesubnet_claimant), announce the pick, the claimant, and theBOX_SUBNETpin for scripts; give up with the old refusal only when every candidate is claimed.Two precedence points worth calling out:
10.88), and converging would rebuild the blackouts. That refusal names the bridge move (incus network set boxnet ipv4.address …) — pre-autopick, this state also refused (via the claimant gate), so no regression, just a sharper message.Why explicit pins still refuse
A pin is the operator talking — a script that says
BOX_SUBNET=10.90.0.0/24must get 10.90 or a loud stop, never a silent substitute: every derived artifact (ACL carve-out, firewall, doctor expectations) would otherwise drift from what the script believes it built. Auto-pick is strictly the unpinned behavior.Drills and rehearsals
The nested run —
install.sh/drill/drill.shinside a box whose own uplink owns10.88.0.0/24— now sails through on an auto-picked subnet with zero flags, announcing e.g.:Neither the drill nor the doctor assumed the refusal; the doctor's one wording that did ("setup-host now refuses this") is updated. No overlap with #89's
box-firewall.sh/doctor.shUFW diff —box-firewallalready reads the gateway off the live bridge, so it follows the pick unchanged.Verification
bash test/cli.sh: 370 passed, 0 failed (339 on main — +31:choose_subnetextracted with its helpers and driven case by case under the shimip, plus the driven whole-script fixtures: pinned refusals unchanged, bridge-present converge with no scan, default-free → default, default-claimed → 10.89, default+10.89 claimed → 10.90, all 40 candidates claimed → the old refusal naming10.127.0.0/24andBOX_SUBNET, the announcement text, and the bridge + ACL carve-out following the pick end to end).shellcheck -x bin/* **/*.sh(CI's exact invocation): clean.10.88.0.0/24, bridge previously remapped to10.89.0.1/24— #80's workaround host), exercising the extracted decision function read-only:boxnet already lives on 10.89.0.0/24 — converging to it.→ prints10.89.0.0/24, rc 0 — no refusal, no scan;10.88.0.0/24→ the #86 refusal, verbatim claimant:this machine's own DEFAULT GATEWAY (10.88.0.1 via enp5s0), rc 1;10.89.0.0/24(agrees with the bridge) → honored, rc 0.🤖 Generated with Claude Code
Rebased onto current main (now containing #89) before going ready — no conflicts; full suite on the rebased head: 402/0 (main 339 + #89's 32 + this PR's 31), shellcheck clean. Live decision-path evidence on a real nested host (uplink 10.88, bridge at 10.89): bare → converge to
10.89.0.0/24rc 0;BOX_SUBNET=10.88.0.0/24pin → refusal rc 1.Verdict: Approve — I agree with this as-is.
Completes #80 fix #1 on top of #86's refuse half. The four-case
choose_subnetdecision is the right shape and runs before any mutation:10.88.0.0/2410.89…10.127, first free wins, loud announce +BOX_SUBNETpin hint; all-claimed keeps the old refusalExtracted pure helpers + shim-
ipcase matrix (pin refusals, bridge converge/no-scan, auto-pick skip, all-claimed) plus whole-script refuse-before-mutation fixtures match the contract. Doctor wording updated for auto-pick. CI green. No blockers.Verdict: I agree with everything and have no additional feedback.
The subnet decision is made before mutation, preserves explicit-pin semantics, converges an existing bridge without re-addressing it, refuses the poisoned bridge-plus-foreign-claim state, and deterministically selects a free candidate for an unpinned nested host. Downstream gateway and ACL values follow the chosen subnet, the full decision matrix is tested, and all current checks pass.
✅ Reviewed — I agree with all of this, no concerns.
First pass on this PR. Read
choose_subnetand its four cases end to end againsttest/cli.sh:subnet_claimantexcludes the bridge itself (dev != "boxnet"on the route probe,$2 != "boxnet"on the addr scan), so a bare re-run against an existingboxnetconverges rather than flagging the bridge as its own claimant — only a foreign claimant on the bridge's subnet refuses. That's the subtle bit and it's right.10.88claimant in$hitfor the announcement (the loop'ssubnet_claimantcalls declarelocal hit, so the outer one survives) — the "why" line names the machine's own gateway, as the test asserts.10.127.0.0/24andBOX_SUBNET.valid_subnet's dot-terminated prefix match holds (10.8.0.0/24≠10.88.x), and there's an explicit test for it.README,
bin/boxhelp, CHANGELOG and thedoctor.shmessage are all updated consistently with the new behavior. Nothing to add.— automated review by
claude-bot-andresmgsl· heavy-duty-review-botRound 1: unanimous (codex ✅ / grok ✅ / claude ✅-agree, first pass, nothing to add). CI green. Requesting @danmt for final review — this is the zero-flag nested-box ergonomics you asked for: bare
box setup-hostinside a box now auto-picks the first free /24 and says so; explicit pins still refuse on collision; existing bridges converge.