ops(systemd): exact mount match in status, add detached rollback script
Two things surfaced by the production promotion. `status` counted namespace mounts with an unanchored grep, so on production "run/netns/openfut" also matched "openfut-staging" and reported a phantom "2 = leaked stack" against a perfectly healthy host. A status command that invents a fault is the same class of bug as a unit that reports active while serving nobody, so it is fixed with an exact mount-point match. The bind and reconcile logic is untouched; it always umounted an exact path. openfut-rollback-detached.sh makes the documented rollback executable rather than a paragraph in a runbook: it removes supervision, resolves the anchor's CURRENT pid from Docker, and relaunches the incumbent detached pair with the environment replayed from the captured env.json. --dry-run prints the exact commands and touches nothing, which is how it was validated while production was still being served by the processes it would restore.
This commit is contained in:
Executable
+58
@@ -0,0 +1,58 @@
|
||||
#!/bin/sh
|
||||
# Roll the production supervision promotion back to the incumbent detached pair.
|
||||
#
|
||||
# This restores the process-management arrangement that ran production before
|
||||
# the supervision cutover: two nohup'd binaries entered into the anchor
|
||||
# container's network namespace via nsenter, parented to init.
|
||||
#
|
||||
# It performs NO database or schema change, because the promotion it reverses
|
||||
# performs none either. The economy is never at risk here.
|
||||
#
|
||||
# Run with --dry-run first; that prints the exact commands and touches nothing.
|
||||
#
|
||||
# usage: openfut-rollback-detached.sh [--dry-run]
|
||||
set -eu
|
||||
|
||||
DRY=0
|
||||
[ "${1:-}" = "--dry-run" ] && DRY=1
|
||||
|
||||
ANCHOR=openfut-fut-backend
|
||||
ART=/home/alex/openfut-migration/promote-contract-20260822-184409/artifacts
|
||||
ENVJSON=/home/alex/openfut-migration/promote-contract-20260822-184409/env.json
|
||||
LOGDIR=/home/alex/openfut-promotion/logs
|
||||
|
||||
run() {
|
||||
if [ "$DRY" -eq 1 ]; then printf ' DRY %s\n' "$*"; else printf ' RUN %s\n' "$*"; sh -c "$*"; fi
|
||||
}
|
||||
|
||||
echo "== 1. stop and remove supervision (units only; binaries, env and DB untouched) =="
|
||||
run "systemctl disable --now openfut-host.service openfut-core.service openfut-netns.service openfut-netns-reconcile.timer || true"
|
||||
run "/home/alex/OpenFUT/scripts/systemd/openfut-supervision-install.sh uninstall production"
|
||||
|
||||
echo "== 2. resolve the anchor's CURRENT namespace (never a remembered pid) =="
|
||||
if [ "$DRY" -eq 1 ]; then
|
||||
APID="$(docker inspect -f '{{.State.Pid}}' "$ANCHOR" 2>/dev/null || echo '<anchor pid>')"
|
||||
echo " DRY anchor $ANCHOR pid=$APID ns=$(readlink "/proc/$APID/ns/net" 2>/dev/null || echo '<ns>')"
|
||||
else
|
||||
APID="$(docker inspect -f '{{.State.Pid}}' "$ANCHOR")"
|
||||
[ -n "$APID" ] && [ "$APID" != "0" ] || { echo "anchor $ANCHOR is not running — cannot roll back into its namespace" >&2; exit 1; }
|
||||
echo " anchor $ANCHOR pid=$APID ns=$(readlink "/proc/$APID/ns/net")"
|
||||
fi
|
||||
|
||||
# The environment is replayed from the captured live process environment, never
|
||||
# retyped. jq renders it as KEY=VALUE pairs for env(1).
|
||||
CORE_ENV="$(jq -r '.core | to_entries[] | "\(.key)=\(.value)"' "$ENVJSON" | tr '\n' ' ')"
|
||||
HOST_ENV="$(jq -r '.host | to_entries[] | "\(.key)=\(.value)"' "$ENVJSON" | tr '\n' ' ')"
|
||||
|
||||
echo "== 3. relaunch incumbent Core (the writer first) =="
|
||||
run "mkdir -p $LOGDIR"
|
||||
run "nsenter --net=/proc/$APID/ns/net env $CORE_ENV nohup $ART/openfut-core >> $LOGDIR/prod-core.log 2>&1 &"
|
||||
run "/home/alex/OpenFUT/scripts/systemd/openfut-wait-tcp.sh 127.0.0.1 18080 30"
|
||||
|
||||
echo "== 4. relaunch incumbent host =="
|
||||
run "nsenter --net=/proc/$APID/ns/net env $HOST_ENV nohup $ART/openfut-utas-host >> $LOGDIR/prod-host.log 2>&1 &"
|
||||
run "/home/alex/OpenFUT/scripts/systemd/openfut-wait-tcp.sh 127.0.0.1 8099 30"
|
||||
|
||||
echo "== 5. verify =="
|
||||
run "pgrep -af 'openfut-core|openfut-utas-host'"
|
||||
echo "Rollback complete. No database or schema change was made."
|
||||
@@ -78,7 +78,11 @@ case "$ACTION" in
|
||||
match="MISMATCH"; [ "$ns" = "$want" ] && match="ok"
|
||||
printf ' %-40s pid=%-8s ns=%-18s %s\n' "$u" "$mp" "$ns" "$match"
|
||||
done
|
||||
echo " netns mounts on /run/netns/$NSNAME: $(grep -c "run/netns/$NSNAME" /proc/mounts || true) (1 = healthy, >1 = leaked stack)"
|
||||
# Exact mount-point match. A substring grep for "openfut" also counts
|
||||
# "openfut-staging" and reports a phantom leaked stack on production —
|
||||
# a monitoring lie of exactly the kind this tooling exists to remove.
|
||||
mounts="$(awk -v t="/run/netns/$NSNAME" '$2==t {n++} END {print n+0}' /proc/mounts)"
|
||||
echo " netns mounts on /run/netns/$NSNAME: $mounts (1 = healthy, >1 = leaked stack)"
|
||||
;;
|
||||
|
||||
uninstall)
|
||||
|
||||
Reference in New Issue
Block a user