#!/bin/sh # Install or remove the OpenFUT systemd supervision set for one environment. # # Environments are symmetric on purpose: staging and production differ only in # unit prefix, anchor container and EnvironmentFile location, so what staging # proved is what production gets. # # install copy units, daemon-reload, enable (does NOT start) # start start in dependency order and report the namespace agreement # status one-screen health: units, anchor, netns agreement, mounts # uninstall disable + stop + remove units (leaves binaries, DB and env alone) # # The install step deliberately does NOT start anything: on production the # changeover has to be sequenced against retiring the existing detached # processes, which is an operator decision, not a script's. # # usage: openfut-supervision-install.sh set -eu ACTION="${1:?install|start|status|uninstall}" ENVNAME="${2:?staging|production}" HERE="$(cd "$(dirname "$0")" && pwd)" case "$ENVNAME" in staging) PREFIX="openfut-staging" ANCHOR="openfut-staging-anchor" NSNAME="openfut-staging" UNITS="openfut-staging-netns.service openfut-staging-core.service openfut-staging-host.service openfut-staging-netns-reconcile.service openfut-staging-netns-reconcile.timer" ENABLE="openfut-staging-netns.service openfut-staging-core.service openfut-staging-host.service openfut-staging-netns-reconcile.timer" ;; production) PREFIX="openfut" ANCHOR="openfut-fut-backend" NSNAME="openfut" UNITS="openfut-netns.service openfut-core.service openfut-host.service openfut-netns-reconcile.service openfut-netns-reconcile.timer" ENABLE="openfut-netns.service openfut-core.service openfut-host.service openfut-netns-reconcile.timer" ;; *) echo "unknown environment '$ENVNAME'" >&2; exit 2 ;; esac case "$ACTION" in install) for u in $UNITS; do [ -f "$HERE/$u" ] || { echo "missing unit $HERE/$u" >&2; exit 1; } install -m 0644 "$HERE/$u" "/etc/systemd/system/$u" echo "installed /etc/systemd/system/$u" done systemctl daemon-reload for u in $UNITS; do systemd-analyze verify "/etc/systemd/system/$u" || true; done # shellcheck disable=SC2086 systemctl enable $ENABLE echo "enabled (NOT started — start explicitly once the old processes are retired)" ;; start) systemctl start "${PREFIX}-netns.service" systemctl start "${PREFIX}-core.service" systemctl start "${PREFIX}-host.service" systemctl start "${PREFIX}-netns-reconcile.timer" sleep 3 "$0" status "$ENVNAME" ;; status) printf '%-42s %s\n' "unit" "state" for u in $UNITS; do printf ' %-40s %s\n' "$u" "$(systemctl is-active "$u" 2>/dev/null || true)"; done cpid="$(docker inspect -f '{{.State.Pid}}' "$ANCHOR" 2>/dev/null || echo 0)" if [ "$cpid" != "0" ] && [ -e "/proc/$cpid/ns/net" ]; then want="$(readlink "/proc/$cpid/ns/net")" else want="(anchor absent)" fi echo " anchor $ANCHOR pid=$cpid ns=$want" for u in "${PREFIX}-core.service" "${PREFIX}-host.service"; do mp="$(systemctl show -p MainPID --value "$u" 2>/dev/null || echo 0)" ns="-"; [ "$mp" != "0" ] && [ -e "/proc/$mp/ns/net" ] && ns="$(readlink "/proc/$mp/ns/net")" 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)" ;; uninstall) # shellcheck disable=SC2086 systemctl disable --now $ENABLE 2>/dev/null || true systemctl stop "${PREFIX}-netns-reconcile.service" 2>/dev/null || true for u in $UNITS; do rm -f "/etc/systemd/system/$u"; echo "removed /etc/systemd/system/$u"; done systemctl daemon-reload systemctl reset-failed 2>/dev/null || true # The bind mount is intentionally left: the namespace belongs to the # container, and tearing it down is not part of removing supervision. echo "uninstalled. Binaries, EnvironmentFiles, /run/netns and the database are untouched." ;; *) echo "unknown action '$ACTION'" >&2; exit 2 ;; esac