forked from heavy-duty/rig
Merge pull request #127 from codex-bot-andresmgsl/build/63-bootstrap-undo
feat: add guarded bootstrap undo
This commit is contained in:
commit
20438f09e8
6 changed files with 158 additions and 7 deletions
|
|
@ -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)
|
||||
|
|
|
|||
32
README.md
32
README.md
|
|
@ -237,11 +237,33 @@ the only shapes it manages — every other role refuses an effective
|
|||
`tag:server` after join, one rule instead of per-role exceptions.
|
||||
|
||||
After the tag verification passes, bootstrap writes `/etc/rig/role` — one
|
||||
line, `role=… root-door=… host=… join=…` — recording the **effective** traits,
|
||||
overrides and all, so an overridden role never lies to the commands that read
|
||||
the marker later (`rig users` keys root policy off `root-door=`). Written
|
||||
post-join and cmp-guarded, so a marker never describes a box that failed to
|
||||
become what it claims.
|
||||
line, `role=… root-door=… host=… join=… join-by=…` — recording the
|
||||
**effective** traits, overrides and all, plus whether this run performed the
|
||||
tailnet join. `join-by=rig` means bootstrap called `tailscale up`;
|
||||
`join-by=preexisting` means it found the node already joined. Old markers name
|
||||
neither and are treated as unknown, never as permission to remove a join.
|
||||
Written post-join and cmp-guarded, so a marker never describes a box that
|
||||
failed to become what it claims.
|
||||
|
||||
### `rig bootstrap --undo`
|
||||
|
||||
```sh
|
||||
sudo rig bootstrap --undo
|
||||
```
|
||||
|
||||
Leaves the tailnet and then removes `/etc/rig/role`, but only when the marker
|
||||
says `join-by=rig`. A pre-existing join, an old marker with no provenance, or
|
||||
no marker at all is refused without calling `tailscale logout`; the refusal
|
||||
names the manual repair. Re-running bootstrap writes the current marker shape.
|
||||
|
||||
Undo also refuses while a GitHub runner is installed and points at
|
||||
`rig runner remove`, because restoring the local machine while leaving an
|
||||
off-box runner registration would create a ghost in the repository. If
|
||||
`tailscale logout` fails, the marker stays in place so the command is retryable.
|
||||
|
||||
This is intentionally not a general rollback. It does not uninstall packages,
|
||||
reverse sshd hardening, remove Docker, Node, agent CLIs, or users. Those changes
|
||||
are convergent rather than transactional and cannot be safely inferred away.
|
||||
|
||||
Immediately after it, bootstrap stamps `/etc/rig/manifest` — **provenance**:
|
||||
which rig converged this box and when (see [`rig
|
||||
|
|
|
|||
4
bin/rig
4
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 <claude-box|codex-box|grok-box|kimi-box|staging-box> [--user <name>]
|
||||
The box TENANT roles: converge a box-minted guest. The '-box' suffix
|
||||
names the family (a guest, vs the '-server' machine roles above).
|
||||
|
|
|
|||
52
commands/bootstrap-undo.sh
Executable file
52
commands/bootstrap-undo.sh
Executable file
|
|
@ -0,0 +1,52 @@
|
|||
#!/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}"
|
||||
|
||||
[ "$(id -u)" -eq 0 ] || die "must run as root"
|
||||
[ -e "$MARKER" ] || die "no /etc/rig/role marker — refusing to touch the tailnet"
|
||||
|
||||
runner_installed=0
|
||||
if [ -n "${RIG_RUNNER_DIR:-}" ]; then
|
||||
[ -e "$RIG_RUNNER_DIR/.runner" ] && runner_installed=1
|
||||
else
|
||||
for runner_config in /home/*/actions-runner/.runner /root/actions-runner/.runner; do
|
||||
[ -e "$runner_config" ] && runner_installed=1
|
||||
done
|
||||
compgen -G '/etc/systemd/system/actions.runner.*.service' >/dev/null \
|
||||
&& runner_installed=1
|
||||
fi
|
||||
if [ "$runner_installed" -eq 1 ]; 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"
|
||||
|
|
@ -32,6 +32,9 @@ usage: rig bootstrap <control-plane-server|workload-server|runner-server|
|
|||
rig bootstrap <claude-box|codex-box|grok-box|kimi-box|staging-box> [--user <name>]
|
||||
(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
|
||||
|
|
|
|||
61
test/cli.sh
61
test/cli.sh
|
|
@ -143,6 +143,67 @@ check "bootstrap: login verify fails closed on a stalled backend" 0 "" \
|
|||
# The marker is the traits' ground truth for rig users; assert the write exists.
|
||||
check "bootstrap: role marker write is present" 0 "" \
|
||||
grep -q "/etc/rig/role" "$ROOT/commands/bootstrap.sh"
|
||||
check "bootstrap: role marker records join provenance" 0 "join-by=%s" \
|
||||
grep -F "join-by=%s" "$ROOT/commands/bootstrap.sh"
|
||||
check "bootstrap: both first-join paths record join-by=rig" 0 "2" \
|
||||
grep -c "^[[:space:]]*JOIN_BY=rig$" "$ROOT/commands/bootstrap.sh"
|
||||
check "bootstrap: already-joined path defaults to join-by=preexisting" 0 "JOIN_BY=preexisting" \
|
||||
grep -F "JOIN_BY=preexisting" "$ROOT/commands/bootstrap.sh"
|
||||
|
||||
# Drive the narrow inverse end to end. Every refusal also asserts the tailscale
|
||||
# shim was NOT called: exit status alone would miss the destructive regression.
|
||||
UNDO_FIX="$(mktemp -d)"
|
||||
UNDO_BIN="$UNDO_FIX/bin"
|
||||
UNDO_MARKER="$UNDO_FIX/role"
|
||||
UNDO_RUNNER="$UNDO_FIX/runner"
|
||||
UNDO_CALLS="$UNDO_FIX/tailscale.calls"
|
||||
mkdir -p "$UNDO_BIN" "$UNDO_RUNNER"
|
||||
cat > "$UNDO_BIN/tailscale" <<'SH'
|
||||
#!/usr/bin/env bash
|
||||
printf '%s\n' "$*" >> "$UNDO_CALLS"
|
||||
if [ "${TAILSCALE_LOGOUT_FAIL:-0}" = 1 ]; then exit 1; fi
|
||||
SH
|
||||
cat > "$UNDO_BIN/id" <<'SH'
|
||||
#!/usr/bin/env bash
|
||||
if [ "${1:-}" = -u ]; then printf '0\n'; else exec /usr/bin/id "$@"; fi
|
||||
SH
|
||||
chmod +x "$UNDO_BIN/tailscale" "$UNDO_BIN/id"
|
||||
undo() {
|
||||
env PATH="$UNDO_BIN:$PATH" UNDO_CALLS="$UNDO_CALLS" \
|
||||
RIG_ROLE_MARKER="$UNDO_MARKER" RIG_RUNNER_DIR="$UNDO_RUNNER" \
|
||||
"$ROOT/bin/rig" bootstrap --undo
|
||||
}
|
||||
undo_untouched() {
|
||||
: > "$UNDO_CALLS"
|
||||
if undo >"$UNDO_FIX/undo.out" 2>&1; then return 1; fi
|
||||
[ ! -s "$UNDO_CALLS" ]
|
||||
}
|
||||
rm -f "$UNDO_MARKER"
|
||||
check "bootstrap --undo: no marker refuses without touching tailnet" 0 "" undo_untouched
|
||||
printf '%s\n' 'role=workload-server root-door=open host=no join=authkey' > "$UNDO_MARKER"
|
||||
check "bootstrap --undo: old marker names missing provenance" \
|
||||
1 "marker predates join-by provenance" undo
|
||||
check "bootstrap --undo: old marker leaves tailnet untouched" 0 "" undo_untouched
|
||||
printf '%s\n' 'role=workload-server root-door=open host=no join=authkey join-by=preexisting' > "$UNDO_MARKER"
|
||||
check "bootstrap --undo: pre-existing join refuses by name" 1 "join-by=preexisting" undo
|
||||
check "bootstrap --undo: pre-existing join leaves tailnet untouched" 0 "" undo_untouched
|
||||
printf '%s\n' 'role=runner-server root-door=open host=no join=authkey join-by=rig' > "$UNDO_MARKER"
|
||||
printf '%s\n' '{}' > "$UNDO_RUNNER/.runner"
|
||||
check "bootstrap --undo: installed runner points at its removal verb" \
|
||||
1 "rig runner remove" undo
|
||||
check "bootstrap --undo: installed runner leaves tailnet untouched" 0 "" undo_untouched
|
||||
rm -f "$UNDO_RUNNER/.runner"
|
||||
check "bootstrap --undo: failed logout is loud" \
|
||||
1 "role marker kept" env TAILSCALE_LOGOUT_FAIL=1 PATH="$UNDO_BIN:$PATH" \
|
||||
UNDO_CALLS="$UNDO_CALLS" RIG_ROLE_MARKER="$UNDO_MARKER" \
|
||||
RIG_RUNNER_DIR="$UNDO_RUNNER" "$ROOT/bin/rig" bootstrap --undo
|
||||
check "bootstrap --undo: failed logout preserves the marker" 0 "" test -e "$UNDO_MARKER"
|
||||
: > "$UNDO_CALLS"
|
||||
check "bootstrap --undo: proven rig join succeeds" 0 "tailnet join removed" undo
|
||||
check "bootstrap --undo: successful logout was called" 0 "logout" cat "$UNDO_CALLS"
|
||||
check "bootstrap --undo: success removes the marker" 1 "" test -e "$UNDO_MARKER"
|
||||
check "bootstrap --undo: second run refuses cleanly" 1 "no /etc/rig/role marker" undo
|
||||
rm -rf "$UNDO_FIX"
|
||||
# ...and that it is written in the CURRENT vocabulary (#77). New markers say
|
||||
# root-door=; the retired class= spelling is something rig READS forever and
|
||||
# WRITES never, so a marker line that reintroduces it must not ship green.
|
||||
|
|
|
|||
Loading…
Reference in a new issue