From 8b4a55a7120807c0bd7ba744785356001592cdef Mon Sep 17 00:00:00 2001 From: codex-bot-andresmgsl <244098813+codex-bot-andresmgsl@users.noreply.github.com> Date: Fri, 24 Jul 2026 01:29:58 +0000 Subject: [PATCH] feat: add guarded bootstrap undo --- CHANGELOG.md | 1 + bin/rig | 4 ++++ commands/bootstrap-undo.sh | 43 ++++++++++++++++++++++++++++++++++++++ commands/bootstrap.sh | 15 +++++++++++-- 4 files changed, 61 insertions(+), 2 deletions(-) create mode 100755 commands/bootstrap-undo.sh diff --git a/CHANGELOG.md b/CHANGELOG.md index 9710954..dc9cb27 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -13,6 +13,7 @@ on the way to cutting its first release, and this file starts there. - `rig platform` prints a stable machine `ID`, derived from `/etc/machine-id`, never the raw value (#95) - Platform, drill, docs and labels changes receive dedicated scope labels (#119) - CI drills the install lifecycle against a real tree — install from the checkout, converge to an empty diff, uninstall to proven absence (#106) +- `rig bootstrap --undo` removes only a tailnet join rig can prove it made (#63) - `kimi-box` joins the box tenant roles — the Kimi CLI agent guest (#109) - The `changelog-armed` guard returns, version-keyed (#112, ceremony#13) - The `.ceremony/` doctrine mirror, verified by `docs-sync` on every PR (#112, ceremony#19) diff --git a/bin/rig b/bin/rig index 8367638..690432e 100755 --- a/bin/rig +++ b/bin/rig @@ -33,6 +33,10 @@ commands: tenant roles); custom and workstation take none. join=login (workstation) needs no key: interactive login, node must come up untagged. Run as root. + bootstrap --undo + Leave the tailnet only when /etc/rig/role proves rig performed the + join, then remove that marker. Refuses for pre-existing or old unknown + joins, and while a GitHub runner is installed. Run as root. bootstrap [--user ] The box TENANT roles: converge a box-minted guest. The '-box' suffix names the family (a guest, vs the '-server' machine roles above). diff --git a/commands/bootstrap-undo.sh b/commands/bootstrap-undo.sh new file mode 100755 index 0000000..3f28cb9 --- /dev/null +++ b/commands/bootstrap-undo.sh @@ -0,0 +1,43 @@ +#!/usr/bin/env bash +# rig bootstrap --undo — remove only off-box state rig can prove it created. +set -euo pipefail + +log() { printf 'rig-bootstrap: %s\n' "$*"; } +die() { printf 'rig-bootstrap: ERROR: %s\n' "$*" >&2; exit 1; } + +MARKER="${RIG_ROLE_MARKER:-/etc/rig/role}" +RUNNER_DIR="${RIG_RUNNER_DIR:-/home/github-runner/actions-runner}" + +[ "$(id -u)" -eq 0 ] || die "must run as root" +[ -e "$MARKER" ] || die "no /etc/rig/role marker — refusing to touch the tailnet" + +if [ -e "$RUNNER_DIR/.runner" ]; then + die "a GitHub runner is installed — run 'rig runner remove' first so undo does not leave a ghost runner in the repository" +fi + +join_by="" +while IFS= read -r field; do + case "$field" in + join-by=*) join_by="${field#join-by=}" ;; + esac +done < <(tr '[:space:]' '\n' < "$MARKER") + +case "$join_by" in + rig) ;; + preexisting) + die "the tailnet join predates this bootstrap run (join-by=preexisting), so rig will not remove state it did not create; run 'tailscale logout' by hand if that is intended" ;; + "") + die "the role marker predates join-by provenance, so rig cannot prove it made this tailnet join and will not remove it; re-run bootstrap to write a current marker, or run 'tailscale logout' by hand" ;; + *) + die "the role marker has unknown join-by=$join_by, so rig cannot prove it made this tailnet join and will not remove it; run 'tailscale logout' by hand if that is intended" ;; +esac + +# The same back-out/keep law as first-join verification: logout is earned only +# when the marker proves rig performed the join. Preserve the marker on failure +# so the operation remains retryable and never reports a half-undone machine. +if ! tailscale logout; then + die "tailscale logout failed; role marker kept so 'rig bootstrap --undo' can be retried" +fi + +rm -f -- "$MARKER" +log "tailnet join removed; role marker removed" diff --git a/commands/bootstrap.sh b/commands/bootstrap.sh index e310b2f..81ace2c 100755 --- a/commands/bootstrap.sh +++ b/commands/bootstrap.sh @@ -32,6 +32,9 @@ usage: rig bootstrap [--user ] (the box TENANT roles — see their own --help; they take no --users, see below) + rig bootstrap --undo + leave the tailnet only when the role marker proves rig + performed the join, then remove the role marker --users the users file this box's operators come from — REQUIRED. It is applied as bootstrap's last phase, exactly as `rig users apply @@ -114,6 +117,10 @@ EOF # --- args (validated before the root check, so errors are testable) --------- ROLE="${1:-}" case "$ROLE" in + --undo) + shift + [ $# -eq 0 ] || die "bootstrap --undo takes no arguments" 2 + exec "$HERE/bootstrap-undo.sh" ;; control-plane-server|workload-server|runner-server|staging-server|dev-server|workstation|custom) shift ;; claude-box|codex-box|grok-box|kimi-box|staging-box) # The box TENANT roles (#31) are a different family — guests a box mints, @@ -534,6 +541,7 @@ if ! command -v tailscale >/dev/null 2>&1; then log "installing tailscale" curl -fsSL https://tailscale.com/install.sh | sh fi +JOIN_BY=preexisting if tailscale status >/dev/null 2>&1; then log "tailnet already joined; skipping tailscale up (no pre-auth key needed)" # ...but skipping `tailscale up` also skipped --hostname, so the TAILNET name @@ -574,6 +582,7 @@ elif [ "$JOIN" = "login" ]; then log "joining tailnet as ${TS_HOSTNAME} (interactive login; follow the URL tailscale prints)" tailscale up --hostname="$TS_HOSTNAME" verify_user_owned back-out + JOIN_BY=rig else # env override, else prompt; never touches disk. The prompt only fires on a # tty: with no terminal, a bare `read` exits non-zero and `set -e` would end @@ -594,6 +603,7 @@ else log "joining tailnet as ${TS_HOSTNAME} (tag comes from the pre-auth key)" tailscale up --authkey="$TS_AUTHKEY" --hostname="$TS_HOSTNAME" verify_effective_tag back-out + JOIN_BY=rig fi # --- role marker -------------------------------------------------------------- @@ -611,11 +621,12 @@ fi # those exist in the field by the thousand and nothing will rewrite them. MARKER=/etc/rig/role MARKER_TMP="$(mktemp)" -printf 'role=%s root-door=%s host=%s join=%s\n' "$ROLE" "$ROOT_DOOR" "$HOST" "$JOIN" > "$MARKER_TMP" +printf 'role=%s root-door=%s host=%s join=%s join-by=%s\n' \ + "$ROLE" "$ROOT_DOOR" "$HOST" "$JOIN" "$JOIN_BY" > "$MARKER_TMP" if ! cmp -s "$MARKER_TMP" "$MARKER" 2>/dev/null; then mkdir -p /etc/rig install -m 0644 "$MARKER_TMP" "$MARKER" - log "role marker written: role=$ROLE root-door=$ROOT_DOOR host=$HOST join=$JOIN" + log "role marker written: role=$ROLE root-door=$ROOT_DOOR host=$HOST join=$JOIN join-by=$JOIN_BY" else log "role marker already current" fi