Files
OpenFUT/scripts/systemd/openfut-netns-bind.sh
T
funman300 8ae432223a ops: systemd supervision for Core and the FIFA17 host (staging-proven)
Replaces the detached `setsid nohup … nsenter …` launch, which had no restart
policy, no boot persistence and no supervisor-visible logs. Staging units are
installed and proven; production units are TEMPLATES and are not installed.

Three decisions, each measured rather than assumed:

* `Wants=`, not `Requires=`, from host to Core. With `Requires`, stopping Core
  stopped the host AND a later Core start did not bring it back -- a routine
  Core restart would leave the client with no server. With `Wants` the host
  survives a Core outage, answers 503 core_unavailable, never falls back to
  Python, and resumes the moment Core returns with no intervention. Both halves
  tested.
* Readiness is a bounded ExecStartPre TCP gate, because ordering proves nothing
  about readiness and Type=exec only proves the binary exec'd. Core binds its
  listener after migrations and content load, so "port open" is a real signal.
  The gate FAILS rather than blocking: a host that waits forever looks healthy
  while serving nobody.
* The netns is resolved by container NAME every start. The container is
  restart=unless-stopped and its netns inode CHANGES on restart (measured:
  4026539938 -> 4026540033), so a hardcoded pid is wrong by construction and
  anything left in the old namespace serves nobody. Proven equivalent to today's
  nsenter against a scratch container, never production's namespace.

`systemd-analyze verify` caught two real defects before deployment:
StartLimitIntervalSec/StartLimitBurst sat in [Service], where systemd 252
silently ignores them, so the crash-loop ceiling was not taking effect; and a
Documentation URL containing %20 parsed as a specifier. Both fixed and the
effective properties re-confirmed from the running units.

Staging evidence: Core-first ordering, host refused when Core is absent or
merely not listening, outage survival, automatic recovery, restart, graceful
stop with no strays, boot simulated via multi-user.target, 3x SIGKILL contained
at ~5s spacing, journald logs, and economy state byte-identical throughout
(integrity ok, fk 0).
2026-08-22 20:46:15 +00:00

58 lines
2.4 KiB
Bash
Executable File

#!/bin/sh
# Publish a Docker container's network namespace into /run/netns so systemd
# units can enter it declaratively with NetworkNamespacePath=.
#
# WHY THIS EXISTS. Production Core and host must run inside the
# `openfut-fut-backend` container's netns: that is where the published client
# ports live and where the Python oracle answers on 127.0.0.1:8199. Today they
# get there with `nsenter --net=/proc/<pid>/ns/net`, where <pid> is typed by
# hand into a runbook.
#
# THE HAZARD THIS FIXES, MEASURED NOT ASSUMED. The container runs with
# `restart=unless-stopped`. On a container restart its netns inode CHANGES
# (observed 2026-08-22: net:[4026539938] -> net:[4026540033]). A hardcoded pid
# is then simply wrong, and — worse — any process already inside the old
# namespace keeps running in a namespace with no interfaces, silently serving
# nobody. Resolving by container NAME at every start removes the hardcoded pid;
# the companion watcher unit handles the already-running case by restarting the
# stack when the container restarts.
#
# Idempotent: a stale bind mount is unmounted and re-made, so re-running after a
# container restart is the fix, not a second problem.
#
# usage: openfut-netns-bind.sh <container-name> <netns-name>
set -eu
CONTAINER="${1:?container name}"
NSNAME="${2:?netns name}"
TARGET="/run/netns/${NSNAME}"
CPID="$(docker inspect -f '{{.State.Pid}}' "$CONTAINER" 2>/dev/null || true)"
if [ -z "$CPID" ] || [ "$CPID" = "0" ]; then
echo "openfut-netns-bind: container '$CONTAINER' is not running (pid='$CPID')" >&2
exit 1
fi
if [ ! -e "/proc/$CPID/ns/net" ]; then
echo "openfut-netns-bind: /proc/$CPID/ns/net does not exist" >&2
exit 1
fi
WANT="$(readlink "/proc/$CPID/ns/net")"
mkdir -p /run/netns
# Already published and already CURRENT? Then do nothing — re-mounting under a
# live service would be gratuitous churn.
if mountpoint -q "$TARGET" 2>/dev/null; then
HAVE="ns:[$(stat -c %i "$TARGET" 2>/dev/null || echo 0)]"
if [ "net:[$(stat -c %i "$TARGET" 2>/dev/null)]" = "$WANT" ]; then
echo "openfut-netns-bind: $TARGET already current ($WANT)"
exit 0
fi
echo "openfut-netns-bind: $TARGET is STALE ($HAVE, want $WANT) — refreshing"
umount "$TARGET" || true
fi
[ -e "$TARGET" ] || touch "$TARGET"
mount --bind "/proc/$CPID/ns/net" "$TARGET"
echo "openfut-netns-bind: $TARGET -> $CONTAINER pid $CPID $WANT"