systemd refuses to start a unit whose ReadWritePaths names a missing path, and none of Vili's three roots exist on Yggdrasil yet. Idunn's startup must not depend on a managed target, so the Vili line takes the "-" prefix. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
114 lines
6.2 KiB
Desktop File
114 lines
6.2 KiB
Desktop File
[Unit]
|
|
Description=Idunn deployment admission and daemon-survival authority
|
|
After=network-online.target docker.service
|
|
Wants=network-online.target
|
|
# Deployment runners use Docker, but continuity actuation does not. Warm,
|
|
# fence, promote, drain and restart all run on systemd; only the build step
|
|
# is containerized, and worktree-tree recipes skip even that. A hard
|
|
# Requires= would let a Docker outage stop Idunn itself, killing daemon
|
|
# survival for every target. Want it, order after it, and let individual
|
|
# container-backed deploys fail on their own if it is absent.
|
|
Wants=docker.service
|
|
|
|
[Service]
|
|
Type=simple
|
|
|
|
# Idunn runs privileged, and this is deliberate rather than laziness. It is the
|
|
# actuation authority: it starts transient units with systemd-run, writes unit
|
|
# files and route configuration, reloads nginx, and drives Docker. It invokes
|
|
# those programs directly -- there is no sudo anywhere in the source -- so a
|
|
# confined User= cannot actuate anything.
|
|
#
|
|
# The privilege separation is *internal*, not at the process boundary. Source
|
|
# freezing runs as the unprivileged idunn user via --source-uid/--source-gid
|
|
# (both validated as non-zero); the privileged driver never opens Git. What
|
|
# stops a compromised target repository from using that privilege is that Idunn
|
|
# accepts no imperative input: a recipe declares capability, the operator
|
|
# binding grants affordances, and cli_exposes_only_declarative_commands asserts
|
|
# there is no way to hand the daemon a command string.
|
|
#
|
|
# The previous generation ran as User=idunn and reached root through a narrow
|
|
# sudoers grant onto a shell actuator. That inverted the safety story: the
|
|
# process was confined but the thing it invoked was an unaudited root shell.
|
|
User=root
|
|
Group=root
|
|
|
|
# Idunn discovers no authority implicitly. Every store is named here, and every
|
|
# one of them is Idunn's own -- no path below belongs to a managed target.
|
|
ExecStart=/usr/local/bin/idunn serve \
|
|
--state-store /var/lib/gamecult/idunn/control.cc \
|
|
--bindings-dir /etc/gamecult/idunn/bindings \
|
|
--source-root /var/lib/gamecult/idunn/sources \
|
|
--staging-root /var/lib/gamecult/idunn/staging \
|
|
--topology-store /var/lib/gamecult/idunn-projection/topology.cc \
|
|
--incident-store /var/lib/gamecult/idunn-projection/incidents.cc \
|
|
--idunn-identity-store /var/lib/gamecult/idunn/idunn-service-identity.cc \
|
|
--odin-correlation-store /var/lib/gamecult/odin/topology.cc \
|
|
--odin-trust-anchor /etc/gamecult/idunn/odin-topology-anchor.cc \
|
|
--deployment-brake-operator-anchor /etc/gamecult/idunn/deployment-brake-operator-anchor.cc \
|
|
--host-actuator-bind 10.77.0.1:17890 \
|
|
--source-uid 986 \
|
|
--source-gid 978
|
|
Restart=always
|
|
RestartSec=5
|
|
TimeoutStopSec=20
|
|
KillSignal=SIGINT
|
|
UMask=027
|
|
|
|
# /run/idunn, for the route driver's preflight staging. systemd creates and
|
|
# removes it with the unit, so it never survives as stale state across a
|
|
# restart.
|
|
RuntimeDirectory=idunn
|
|
RuntimeDirectoryMode=0750
|
|
|
|
PrivateTmp=yes
|
|
ProtectHome=yes
|
|
# ProtectSystem=full keeps /usr and /etc read-only, then the paths Idunn
|
|
# actually actuates are re-opened one at a time. This list is the blast radius,
|
|
# and it is the reason running as root is tolerable: root plus an enumerated
|
|
# write set is narrower than root everywhere.
|
|
ProtectSystem=full
|
|
ReadWritePaths=/etc/gamecult/idunn
|
|
ReadWritePaths=/etc/nginx/idunn-stream-routes
|
|
# ufw keeps its rule files under /etc/ufw; the route driver owns one allow per
|
|
# routed stable endpoint and writes it through ufw.
|
|
ReadWritePaths=/etc/systemd/system
|
|
ReadWritePaths=/etc/ufw
|
|
# idunn-projection is deliberately outside Idunn's own 0750 directory: the
|
|
# topology store is Idunn's *published* surface, and every managed target must
|
|
# read it to verify its own Expected incarnation against the Idunn anchor. It
|
|
# was inside the private root, where no workload could traverse to it. The
|
|
# published topology store is read lock-free (Odin) and incidents.cc is read by
|
|
# Bifrost's reader; managed targets get Expected from their runtime bundle. Only
|
|
# the stores are readable: each .lock beside them is Idunn's alone, 0600.
|
|
ReadWritePaths=/var/lib/gamecult/idunn /var/lib/gamecult/idunn-authority /var/lib/gamecult/idunn-projection
|
|
ReadWritePaths=/srv/build
|
|
# Each managed target gets only its bound roots. A target Idunn cannot write is
|
|
# a target it cannot deploy; keep this list aligned with operator bindings.
|
|
ReadWritePaths=/etc/gamecult/heimdall /var/lib/gamecult/heimdall /srv/heimdall/app
|
|
# Odin is a managed target like any other now, so its three roots are writable
|
|
# on the same terms. This used to be ReadOnlyPaths, on the reasoning that Idunn
|
|
# only *reads* Odin's correlation store to gate promotion. That remains true of
|
|
# the correlation itself -- continuity never consults it, see
|
|
# stale_odin_provider_receipt_cannot_gate_continuity_restart, and an absent
|
|
# store yields None rather than an error, so Odin being down blocks promotions
|
|
# and leaves daemon survival untouched. But a target Idunn cannot write is a
|
|
# target it cannot deploy, and Odin has to be deployable: it is the bootstrap
|
|
# target every other target's warming presence is observed through.
|
|
ReadWritePaths=/etc/gamecult/odin /var/lib/gamecult/odin /srv/odin
|
|
# Ghostlight Dungeon: its runtime root only, not /etc/gamecult/ghostlight,
|
|
# which also holds the credential originals Idunn reads but never writes.
|
|
# The release root sits beside the legacy v1 actuator's /srv/ghostlight.
|
|
ReadWritePaths=/etc/gamecult/ghostlight/runtime /var/lib/gamecult/ghostlight-world-v2 /srv/ghostlight-idunn
|
|
# StreamPixels service: runtime bundle, rebuildable state boundary, and releases.
|
|
ReadWritePaths=/etc/gamecult/streampixels-service/runtime /var/lib/gamecult/streampixels-service /srv/streampixels-idunn/releases/service
|
|
# StreamPixels web is stateless; it needs only its runtime bundle and releases.
|
|
ReadWritePaths=/etc/gamecult/streampixels-web/runtime /srv/streampixels-idunn/releases/web
|
|
# Vili (motion worker): runtime bundle, state, and releases. Prefixed "-" so a
|
|
# target whose roots are not created yet cannot stop Idunn itself from starting
|
|
# (Idunn startup never depends on a managed target). A root created after Idunn
|
|
# starts is writable only from Idunn's next restart.
|
|
ReadWritePaths=-/etc/gamecult/vili/runtime -/var/lib/gamecult/vili -/srv/vili-idunn/releases
|
|
|
|
[Install]
|
|
WantedBy=multi-user.target
|