#!/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 # The same hazard, the other forge (#109): leaving the tailnet under a live # Forgejo runner strands a registration this box can no longer serve, and # Forgejo has no deregistration endpoint — so the ghost it leaves is one # somebody has to delete BY HAND in the instance's admin UI. That makes the # refusal more load-bearing here than for GitHub, not less. # # RIG_FORGEJO_RUNNER_DIR mirrors RIG_RUNNER_DIR above so tests can point this # at a fixture. The glob covers the tenant default (`ci`) and the dedicated # account alike, because both are reachable defaults of `install --user`. forgejo_runner_installed=0 if [ -n "${RIG_FORGEJO_RUNNER_DIR:-}" ]; then [ -e "$RIG_FORGEJO_RUNNER_DIR/.runner" ] && forgejo_runner_installed=1 else for runner_config in /home/*/forgejo-runner/.runner /root/forgejo-runner/.runner; do [ -e "$runner_config" ] && forgejo_runner_installed=1 done [ -e /etc/systemd/system/forgejo-runner.service ] && forgejo_runner_installed=1 fi if [ "$forgejo_runner_installed" -eq 1 ]; then die "a Forgejo runner is installed — run 'rig forgejo-runner remove' first so undo does not leave a ghost runner in the instance" 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"