1e8d46b258
Recreating the Docker anchor left the supervised Core and host stranded in the dead namespace while systemd still reported them active — serving nobody, invisible to any monitoring that trusts unit state. Reproduced on staging: netns 4026539938 -> 4026540033, both pids unchanged in the old one, both units "active", traffic ConnectionResetError. There is no systemd-native edge signal to bind to. Containers do appear as units, but the scope name embeds the container ID (docker-<id>.scope), which changes on every recreate, so BindsTo= has no stable target; NetworkNamespacePath= resolves once at start; a .path unit on /run/netns would watch the file this tooling maintains. So: a level-triggered reconcile on a 10s timer, comparing the namespace the services are ACTUALLY in against the anchor's CURRENT one, acting only on a real difference. That cannot miss an event while the watcher restarts or dockerd is down, and needs no debounce — a burst of three recreations produced exactly one rebind. The trigger stays separable: a docker-events unit could invoke the same script. Anchor absent stops the dependants rather than falling back to host networking; docker unavailable logs once and retries on the next tick. Two defects found while testing and fixed here: - mount --bind STACKS when the old mount is busy, silently leaking nsfs entries; the bind helper now drains stale mounts in a loop. - reconcile must stop -> rebind -> start, not rebind -> restart: a running service holds the old namespace open and makes the umount fail busy. Staging also gained a faithful anchor container so the reproduction is structural rather than mocked. Economy state was byte-identical across every lifecycle test. Production units are templates only and remain uninstalled.
62 lines
2.3 KiB
Desktop File
62 lines
2.3 KiB
Desktop File
[Unit]
|
|
Description=OpenFUT FIFA17 UTAS host (STAGING) — client-facing, Core-dependent
|
|
Documentation=file:///home/alex/OpenFUT/scripts/systemd/README.md
|
|
|
|
# ORDERING AND ADMISSION. `Wants` + `After` order the host after Core and pull
|
|
# Core in when the host is started; neither gives READINESS, because Type=exec
|
|
# only proves the binary exec'd. The ExecStartPre below is what actually admits
|
|
# traffic: the host cannot reach "active" while Core is not listening.
|
|
Wants=openfut-staging-core.service
|
|
After=openfut-staging-core.service
|
|
# Requires on the NAMESPACE (hard) but only Wants on Core (soft) — the host
|
|
# must never bind the host network, yet must survive a Core blip.
|
|
Requires=openfut-staging-netns.service
|
|
After=openfut-staging-netns.service
|
|
|
|
# `Wants`, deliberately NOT `Requires`/`BindsTo`/`PartOf`. Those propagate a
|
|
# Core stop into a host stop, and — measured, not assumed — a later Core start
|
|
# does NOT bring the host back, so a routine Core restart would silently leave
|
|
# the client with no server at all.
|
|
#
|
|
# With `Wants` the host survives a Core outage and answers 503
|
|
# `core_unavailable`, never falling back to Python. That is the behaviour
|
|
# production already exhibited on 2026-08-22 when Core was SIGHUP'd out from
|
|
# under a live host. Because the host holds no Core state between requests, it
|
|
# resumes serving the moment Core returns, with no supervisor intervention.
|
|
|
|
# StartLimit* MUST live in [Unit]: systemd 252 silently IGNORES them in
|
|
# [Service] (`systemd-analyze verify` flags it), which would have left the
|
|
# crash-loop ceiling at the 10s/5 default instead of the intended 60s window.
|
|
StartLimitIntervalSec=60
|
|
StartLimitBurst=5
|
|
|
|
[Service]
|
|
Type=exec
|
|
User=alex
|
|
Group=alex
|
|
WorkingDirectory=/home/alex/openfut-sold-staging
|
|
|
|
NetworkNamespacePath=/run/netns/openfut-staging
|
|
|
|
EnvironmentFile=/home/alex/openfut-sold-staging/systemd/host.env
|
|
|
|
# Readiness gate. Bounded, and FAILS rather than blocking forever: a host that
|
|
# waits indefinitely looks healthy to the supervisor while serving nothing.
|
|
ExecStartPre=/home/alex/OpenFUT/scripts/systemd/openfut-wait-tcp.sh 127.0.0.1 18081 30
|
|
|
|
ExecStart=/home/alex/openfut-sold-staging/bin/openfut-utas-host
|
|
|
|
KillSignal=SIGTERM
|
|
KillMode=mixed
|
|
TimeoutStopSec=20
|
|
|
|
Restart=on-failure
|
|
RestartSec=5s
|
|
|
|
StandardOutput=journal
|
|
StandardError=journal
|
|
SyslogIdentifier=openfut-staging-host
|
|
|
|
[Install]
|
|
WantedBy=multi-user.target
|