archy/image-recipe/configs/archipelago.service
archipelago 80765c5755 feat(systemd): delegate cgroup controllers to archipelago.service
Adds Delegate=memory pids cpu io to the archipelago.service unit.

Context: the service runs as User=archipelago under system.slice with
rootless podman. When podman creates transient libpod-*.scope units for
containers under user.slice, systemd needs the caller to hold
CAP_SYS_ADMIN on the target cgroup subtree \u2014 which happens iff
Delegate= lists the controllers we want to set. Without Delegate, any
future code path that goes through the podman CLI (runtime.rs) instead
of the libpod HTTP API (podman_client.rs) would hit MemoryMax
rejections that have exactly the same symptom as the bug I just fixed
in parse_memory_limit but with a completely different root cause.

Belt-and-braces: current production path uses PodmanClient and was
fixed in the preceding commit. But the DockerRuntime CLI path in
runtime.rs:262-268 (cmd.arg("--memory")) is still reachable via
AutoRuntime fallback on hosts without podman, and future rust
orchestrator code may legitimately need cgroup delegation. This
directive is no-op harmful on hosts that already delegate upstream
(systemd gracefully handles duplicate/nested delegation).
2026-04-23 03:44:36 -04:00

65 lines
2.7 KiB
Desktop File

[Unit]
Description=Archipelago Backend
After=network-online.target archipelago-setup-tor.service
Wants=network-online.target
[Service]
Type=notify
User=archipelago
Environment="ARCHIPELAGO_BIND=127.0.0.1:5678"
# DEV_MODE disabled in production — enabled via override.conf on dev servers
Environment="XDG_RUNTIME_DIR=/run/user/1000"
# + prefix runs these as root (needed for chown/mkdir outside ReadWritePaths)
ExecStartPre=+/bin/bash -c 'mkdir -p /run/user/1000 && chown archipelago:archipelago /run/user/1000 && chmod 700 /run/user/1000'
ExecStartPre=+/bin/bash -c 'mkdir -p /var/lib/archipelago && chown archipelago:archipelago /var/lib/archipelago && echo "ARCHIPELAGO_HOST_IP=$(hostname -I 2>/dev/null | awk "{print $$1}")" > /var/lib/archipelago/host-ip.env && chown archipelago:archipelago /var/lib/archipelago/host-ip.env'
ExecStart=/usr/local/bin/archipelago
Restart=on-failure
RestartSec=5
WatchdogSec=300
TimeoutStartSec=300
# Backend shuts down in <1s; 15s is generous for any cleanup
TimeoutStopSec=15
# Filesystem protection
ProtectSystem=strict
# ProtectHome=no: rootless podman needs writable ~/.local/share/containers
ProtectHome=no
# PrivateTmp disabled: rootless podman runtime lives in /tmp/podman-run-UID/
# and must be shared between the service and SSH-created containers
ReadWritePaths=/var/lib/archipelago /etc/containers /var/lib/containers /run/containers /run/user /tmp /home/archipelago/.local/share/containers /home/archipelago/.config/containers /etc
# Privilege restriction — NoNewPrivileges=no required for sudo archipelago-wg
# (WireGuard peer management). Scoped via sudoers to only archipelago-wg.
NoNewPrivileges=no
PrivateDevices=no
SupplementaryGroups=dialout debian-tor
# Syscall and network restrictions — safe on Debian 13 (systemd 256+)
# which respects NoNewPrivileges=no as an explicit override for seccomp filters
SystemCallArchitectures=native
RestrictAddressFamilies=AF_UNIX AF_INET AF_INET6 AF_NETLINK
RestrictRealtime=yes
# MemoryDenyWriteExecute removed: ring (rustls) and secp256k1 (bitcoin/nostr)
# use assembly code that requires executable memory mappings on some platforms
# Resource limits
MemoryMax=4G
LimitNOFILE=65535
TasksMax=2048
# Delegate cgroup controllers so rootless podman (run from this system service
# as user=archipelago, not user@1000.service) can create transient libpod-*.scope
# units with --memory / --cpus / --pids-limit. Without this, podman create fails
# at start time with: "MemoryMax is out of range" because systemd rejects resource
# limits on undelegated cgroup subtrees. Required for the ProdContainerOrchestrator
# code path (see core/archipelago/src/container/prod_orchestrator.rs).
Delegate=memory pids cpu io
# Logging
StandardOutput=journal
StandardError=journal
[Install]
WantedBy=multi-user.target