feat(coolify): warn when the role marker names a non-control-plane box — advisory, never a gate (#25)

Issue #25 named this consumer when it introduced /etc/rig/role: 'rig
<cmd> sanity warnings later (e.g. coolify install on a non-control-plane
box)'. Both coolify verbs now read the marker through the lib's
read_role_marker (RIG_ROLE_MARKER overrides the path for fixtures, repo
precedent) and warn when it names any role but control-plane — the
likeliest story is the wrong SSH session about to put a control plane on
a workload box.

The marker stays advisory: it may be absent (pre-marker boxes,
hand-built boxes) and absence stays silent — warning there would nag
every legitimate run — and a present-but-different marker warns and
proceeds, because an advisory file must never outrank the operator
(contrast close-root, where the marker IS the gate: shutting the root
door blind is irreversible in a way an extra Coolify is not). The check
sits after arg validation and before the root check, so exit codes are
untouched (usage stays 2, the root refusal stays 1) and the harness
proves it non-root.

Tests drive the live matrix through fixture markers (warns on workload,
silent on control-plane and on absence, still exits 1 at the root
check) and pin the warning's presence in both shipped scripts for
root-run environments.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
This commit is contained in:
dan-claude-bot 2026-07-18 14:50:46 +00:00
parent 34f1986da0
commit 900697bdc2
3 changed files with 89 additions and 2 deletions

View file

@ -7,6 +7,10 @@
# already-filled bindings file is left untouched.
set -euo pipefail
HERE="$(cd "$(dirname "$(readlink -f "${BASH_SOURCE[0]}")")" && pwd)"
# shellcheck source=SCRIPTDIR/lib/users-config.sh
. "$HERE/lib/users-config.sh" # read_role_marker — the traits line bootstrap wrote
log() { printf 'rig-coolify-backup: %s\n' "$*"; }
warn() { printf 'rig-coolify-backup: WARNING: %s\n' "$*" >&2; }
die() { printf 'rig-coolify-backup: ERROR: %s\n' "$1" >&2; exit "${2:-1}"; }
@ -67,6 +71,18 @@ done
[ -n "$PG_USER" ] || die "--pg-user must not be empty" 2
[ -n "$PG_DB" ] || die "--pg-db must not be empty" 2
# --- role-marker sanity (issue #25) ------------------------------------------
# Same advisory check as `rig coolify install`, same reasoning: this command
# dumps the CONTROL PLANE's database, so a marker naming any other role almost
# certainly means the wrong SSH session — but the marker is advisory and may be
# absent, so WARN, never die, and warn before the root check so the harness can
# prove it non-root (RIG_ROLE_MARKER points it at fixtures, repo precedent).
MARKER_LINE="$(read_role_marker "${RIG_ROLE_MARKER:-/etc/rig/role}")"
case "$MARKER_LINE" in
""|"role=control-plane "*) ;;
*) warn "this box's role marker says '${MARKER_LINE}' — not a control-plane box. The nightly dump targets Coolify's own database, which lives on role control-plane; if this is the wrong box, stop here and re-check your SSH session." ;;
esac
# --- guards ----------------------------------------------------------------
[ "$(id -u)" -eq 0 ] || die "must run as root"
if [ -r /etc/os-release ]; then

View file

@ -4,8 +4,13 @@
# explicit act.
set -euo pipefail
log() { printf 'rig-coolify: %s\n' "$*"; }
die() { printf 'rig-coolify: ERROR: %s\n' "$1" >&2; exit "${2:-1}"; }
HERE="$(cd "$(dirname "$(readlink -f "${BASH_SOURCE[0]}")")" && pwd)"
# shellcheck source=SCRIPTDIR/lib/users-config.sh
. "$HERE/lib/users-config.sh" # read_role_marker — the traits line bootstrap wrote
log() { printf 'rig-coolify: %s\n' "$*"; }
warn() { printf 'rig-coolify: WARNING: %s\n' "$*" >&2; }
die() { printf 'rig-coolify: ERROR: %s\n' "$1" >&2; exit "${2:-1}"; }
usage() {
cat <<'EOF'
@ -32,6 +37,26 @@ if [ -z "$VERSION" ]; then
die "--version <pin> is required" 2
fi
# --- role-marker sanity (issue #25) ------------------------------------------
# Coolify belongs on the control-plane box and nowhere else — but the marker is
# ADVISORY, never a gate. It may legitimately be absent (a box bootstrapped
# before rig wrote markers, or a hand-built one), and rig refuses to guess from
# silence. When the marker EXISTS and names another role, the likeliest story
# is an operator in the wrong SSH session about to put a control plane on a
# workload box — so say it loudly. But WARN, never die: the operator may also
# be deliberately repurposing the box, and an advisory file must never outrank
# the human running the command (contrast close-root, where the marker IS the
# gate — shutting the root door blind is irreversible in a way an extra
# Coolify is not). Placed BEFORE the root check for the same reason arg errors
# are: the harness proves it non-root, and reading a 0644 file needs no
# privilege. RIG_ROLE_MARKER overrides the path so tests point it at fixtures
# (repo precedent: users-apply, users-close-root).
MARKER_LINE="$(read_role_marker "${RIG_ROLE_MARKER:-/etc/rig/role}")"
case "$MARKER_LINE" in
""|"role=control-plane "*) ;;
*) warn "this box's role marker says '${MARKER_LINE}' — not a control-plane box. Coolify belongs on role control-plane; if this is the wrong box, stop here and re-check your SSH session. Repurposing it on purpose? Re-run 'rig bootstrap control-plane' first so the marker tells the truth." ;;
esac
[ "$(id -u)" -eq 0 ] || die "must run as root"
export AUTOUPDATE=false

View file

@ -200,6 +200,52 @@ else
echo "skip: coolify backup non-root refusal (running as root)"
fi
# --- role-marker sanity: coolify verbs off the control plane (#25) -----------
# Both coolify commands read /etc/rig/role and WARN — never die — when the
# marker names a non-control-plane role: the likeliest story is the wrong SSH
# session, but the marker is advisory and must not outrank the operator. The
# warning fires BEFORE the root check (same testability rule as arg errors),
# so a non-root run prints it and then hits the root refusal — provable here
# with RIG_ROLE_MARKER pointed at fixtures (repo precedent: the close-root
# marker gate). Counting fires proves silence too: a control-plane marker, an
# absent marker, and a marker-less box must all stay quiet, because warning on
# absence would nag every pre-marker box on every legitimate run.
marker_warns() { # marker_warns <marker_path> <cmd...> — how many warnings fired
local marker="$1"; shift
env RIG_ROLE_MARKER="$marker" "$@" 2>&1 | grep -c "not a control-plane box" || true
}
MARKER_FIX="$(mktemp -d)"
printf 'role=workload class=server host=no join=authkey\n' > "$MARKER_FIX/workload"
printf 'role=control-plane class=server host=no join=authkey\n' > "$MARKER_FIX/control-plane"
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
check "coolify: control-plane marker stays silent" 0 "0" \
marker_warns "$MARKER_FIX/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 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" \
env RIG_ROLE_MARKER="$MARKER_FIX/workload" "$ROOT/commands/coolify-install.sh" --version 4.1.2
check "coolify backup: warns on a non-control-plane marker" 0 "1" \
marker_warns "$MARKER_FIX/workload" "$ROOT/commands/coolify-backup-install.sh"
check "coolify backup: control-plane marker stays silent" 0 "0" \
marker_warns "$MARKER_FIX/control-plane" "$ROOT/commands/coolify-backup-install.sh"
check "coolify backup: the marker warns but never refuses" 1 "must run as root" \
env RIG_ROLE_MARKER="$MARKER_FIX/workload" "$ROOT/commands/coolify-backup-install.sh"
else
echo "skip: coolify role-marker warning checks (running as root)"
fi
rm -rf "$MARKER_FIX"
# Root runs skip the live checks above, so also pin the warning's presence in
# both shipped scripts — a deleted advisory cannot ship green (repo precedent:
# the staging/runner tag greps).
check "coolify: marker warning present in the shipped script" 0 "" \
grep -q "not a control-plane box" "$ROOT/commands/coolify-install.sh"
check "coolify backup: marker warning present in the shipped script" 0 "" \
grep -q "not a control-plane box" "$ROOT/commands/coolify-backup-install.sh"
# --- rig db (ad-hoc dump/restore) -------------------------------------------
check "bare db shows usage, exit 2" 2 "usage:" "$ROOT/bin/rig" db
check "db --help exits 0" 0 "usage:" "$ROOT/bin/rig" db --help