From 00f6351e2815da596c9a51fcb49efb65c108d863 Mon Sep 17 00:00:00 2001 From: dan-claude-bot Date: Mon, 20 Jul 2026 00:21:35 +0000 Subject: [PATCH] fix(bootstrap): stop telling operators to run bare roles; pin the migration Two review findings from the bot round on this stack. BLOCKING (codex-bot, claude-bot -- both, independently). bootstrap-tenant.sh emits the staging guest's tailnet-join next step at the end of a converge ("box shell -> sudo rig bootstrap workload"), repeats it in usage, and two of its refusals recite the old machine-role list. Fixed here rather than on the stacked tenant PR because THIS is the branch that removes the `workload` role -- shipping it alone would print a next step naming a role that no longer exists. None of those four sites is code that ACCEPTS a role, which is why the rename missed them, and is also what makes them the worse failure. A stale flag dies immediately with a usage error. A stale next-step is copy-pasted by a human onto a DIFFERENT box, minutes after the run that printed it reported success, and dies there with no thread back to the cause. So test/cli.sh sweeps every shipped script under bin/ and commands/ for `rig bootstrap ` rather than pinning the four known sites: the next instance of this class will be somewhere else. Proven non-vacuous -- reintroducing the bare `workload` next-step turns the suite red (412/1), restoring it turns it green (413/0). NON-BLOCKING (claude-bot). The migration story was documented and untested: every marker fixture was renamed alongside the code, so nothing asserted what a real pre-rename box does. A `role=control-plane` fixture now pins both halves of the promise -- such a box WARNS on the coolify verbs (its marker no longer names a role that exists) and is never REFUSED. Both halves matter: a rename that turned this into a refusal would break the exact boxes the CHANGELOG promises keep working, on the command that installs the control plane. Co-Authored-By: Claude Opus 4.8 --- CHANGELOG.md | 9 +++++++++ commands/bootstrap-tenant.sh | 10 +++++----- test/cli.sh | 30 ++++++++++++++++++++++++++++++ 3 files changed, 44 insertions(+), 5 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 80b243c..f7c1ad1 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -49,6 +49,15 @@ on the way to cutting its first release, and this file starts there. branch until it is re-bootstrapped. That check has always been advisory and never a gate, so the run still proceeds and the warning names the repair. + The rename also reaches every string that *tells an operator to run a role*, + not just the code that accepts one — `bootstrap-tenant.sh` emits the staging + guest's tailnet-join next step (`sudo rig bootstrap workload-server`), and + two of its refusals recite the machine-role list. A stale next-step is worse + than a stale flag: it fails when someone copy-pastes it, on a different box, + minutes after the run that printed it reported success. `test/cli.sh` sweeps + every shipped script for pre-rename role names rather than pinning the known + sites, because the next instance of this will be somewhere else. + `dev-server` is `class=human`, which reads like a contradiction and is not: the suffix names the family, the class names the root-SSH door policy, and operators enter a dev box as themselves so `close-root` shuts its door. The diff --git a/commands/bootstrap-tenant.sh b/commands/bootstrap-tenant.sh index a09f23b..f5ea140 100755 --- a/commands/bootstrap-tenant.sh +++ b/commands/bootstrap-tenant.sh @@ -10,7 +10,7 @@ # Creds-free BY CONTRACT: box auto-runs these at mint ('box exec … rig # bootstrap claude'), so every path here is non-interactive and nothing joins # or admits — no tailnet, no keys, no prompts. staging's tailnet join stays -# operator-run ('rig bootstrap workload' through 'box shell'), exactly the +# operator-run ('rig bootstrap workload-server' through 'box shell'), exactly the # creds split box#69 designed. # Convergent: safe to re-run; a second run changes nothing. set -euo pipefail @@ -43,7 +43,7 @@ tenant on top, and re-runs converge an existing box to a new spec. hardening. The tailnet workload join is deliberately NOT here — it holds a credential, so it stays operator-run: `box shell` → `sudo rig bootstrap - workload` with a tagged pre-auth key. + workload-server` with a tagged pre-auth key. --user the tenant user the box seed created (default: the role's name; staging defaults to `ops`) @@ -77,10 +77,10 @@ while [ $# -gt 0 ]; do # from the box seed, and the one trait-shaped thing a staging guest # eventually does (join the tailnet as a workload) is deliberately not # here: it holds a credential, so it stays operator-run. - die "tenant roles have no traits: $1 belongs to the machine roles (control-plane|workload|runner|dev|workstation|custom). A tenant box's shape comes from its seed; staging's tailnet join is operator-run via 'rig bootstrap workload'." 2 ;; + die "tenant roles have no traits: $1 belongs to the machine roles (control-plane-server|workload-server|runner-server|staging-server|dev-server|workstation|custom). A tenant box's shape comes from its seed; staging's tailnet join is operator-run via 'rig bootstrap workload-server'. The METAL that hosts these guests is 'rig bootstrap staging-server'." 2 ;; --ts-tag) [ $# -ge 2 ] && shift - die "--ts-tag is gone and tenant roles never join the tailnet anyway. staging's join is operator-run via 'rig bootstrap workload', where the tag comes from the pre-auth key." 2 ;; + die "--ts-tag is gone and tenant roles never join the tailnet anyway. staging's join is operator-run via 'rig bootstrap workload-server', where the tag comes from the pre-auth key." 2 ;; *) die "unknown flag: $1" 2 ;; esac done @@ -384,7 +384,7 @@ fi log "done — tenant ${ROLE}, user ${TENANT_USER}" if [ "$ROLE" = "staging" ]; then - log "next (operator-run, holds a credential): box shell → sudo rig bootstrap workload --hostname with a tagged pre-auth key" + log "next (operator-run, holds a credential): box shell → sudo rig bootstrap workload-server --hostname with a tagged pre-auth key" else log "next: creds stay with the operator — ${CLI} authenticates through its own interactive login when a human decides" fi diff --git a/test/cli.sh b/test/cli.sh index 5ce8b10..1ff95ac 100644 --- a/test/cli.sh +++ b/test/cli.sh @@ -93,6 +93,19 @@ check "bootstrap: workstation keeps its bare name" 2 "unset TS_AUTHKEY" \ env TS_AUTHKEY=x "$ROOT/commands/bootstrap.sh" workstation check "bootstrap: custom keeps its bare name" 2 "--hostname" \ "$ROOT/commands/bootstrap.sh" custom --class server --host no --join authkey +# NOTHING may still TELL an operator to run a pre-#76 role. The rename is a +# hard cut, so a next-step string, a usage line or a refusal that still recites +# a bare role name is a command that fails when someone copy-pastes it — and it +# fails later and further from the cause than a broken flag would, because it +# fails on a different box, minutes after this run reported success. The tenant +# script is the one that emits the staging guest's workload-join next step, so +# it is where this bites first (caught in review on #80, fixed here where the +# rename actually happens). Swept across every shipped script rather than +# asserted at the one known site: the next instance of this will be somewhere +# else, and a site-specific check would not see it. +check "roles: no shipped script tells an operator to run a pre-#76 role name" 1 "" \ + grep -rnE "rig bootstrap (control-plane|workload|runner|dev)( |'|\"|$)" \ + "$ROOT/bin/rig" "$ROOT/commands/" # --- traits: roles are presets, every trait individually settable (#26) ----- check "bootstrap: unknown role still exits 2" 2 "unknown role" "$ROOT/commands/bootstrap.sh" potato check "bootstrap: bad --class value exits 2" 2 "human|server" "$ROOT/commands/bootstrap.sh" workload-server --class potato @@ -624,6 +637,12 @@ MARKER_FIX="$(mktemp -d)" printf 'role=workload-server class=server host=no join=authkey\n' > "$MARKER_FIX/workload" printf 'role=control-plane-server class=server host=no join=authkey\n' > "$MARKER_FIX/control-plane" printf 'role=control-plane-server\n' > "$MARKER_FIX/bare-control-plane" +# A PRE-#76 marker, verbatim as a real box bootstrapped before the rename +# carries it. This is the one fixture that must keep its old spelling: the +# CHANGELOG promises such a box takes the warning branch and keeps working, +# and until this existed nothing asserted it — every other fixture here was +# renamed with the code, so the migration story was documented and untested. +printf 'role=control-plane class=server host=no join=authkey\n' > "$MARKER_FIX/pre-rename-cp" if [ "$(id -u)" -ne 0 ]; then check "coolify: warns on a non-control-plane marker" 0 "1" \ marker_warns "$MARKER_FIX/workload" "$ROOT/commands/coolify-install.sh" --version 4.1.2 @@ -635,6 +654,17 @@ if [ "$(id -u)" -ne 0 ]; then marker_warns "$MARKER_FIX/bare-control-plane" "$ROOT/commands/coolify-install.sh" --version 4.1.2 check "coolify: absent marker stays silent (advisory, not a gate)" 0 "0" \ marker_warns "$MARKER_FIX/absent" "$ROOT/commands/coolify-install.sh" --version 4.1.2 + # The migration story, pinned in both halves: a pre-#76 control plane WARNS + # (its marker no longer names a role that exists) but is never refused. Both + # halves matter — a rename that turned this into a refusal would break the + # exact boxes the CHANGELOG promises keep working, and it would do it on the + # command that installs the control plane. + check "coolify: a PRE-#76 'role=control-plane' marker warns (migration)" 0 "1" \ + marker_warns "$MARKER_FIX/pre-rename-cp" "$ROOT/commands/coolify-install.sh" --version 4.1.2 + check "coolify: ...and is still never refused" 1 "must run as root" \ + env RIG_ROLE_MARKER="$MARKER_FIX/pre-rename-cp" "$ROOT/commands/coolify-install.sh" --version 4.1.2 + check "coolify backup: a PRE-#76 'role=control-plane' marker warns (migration)" 0 "1" \ + marker_warns "$MARKER_FIX/pre-rename-cp" "$ROOT/commands/coolify-backup-install.sh" # The warning must stay a warning: the run proceeds past it and stops at the # root check (exit 1), never turned into a marker refusal. check "coolify: the marker warns but never refuses" 1 "must run as root" \