[Unit] Description=Vacuum Wall Daemon (privileged backend) Documentation=https://github.com/wall/vacuum-wall After=network.target firewalld.service Wants=firewalld.service [Service] Type=simple User={{ USER_DAEMON_NAME }} Group={{ USER_GROUP }} WorkingDirectory={{ PROJECT_DIR }} ExecStart={{ PROJECT_DIR }}/.venv/bin/python -m daemon Restart=on-failure RestartSec=5 TimeoutStopSec=15 Environment=PATH=/usr/local/bin:/usr/bin Environment=PYTHONUNBUFFERED=1 Environment=ACME_HOME={{ PROJECT_DIR }}/data/acme Environment=HOME={{ PROJECT_DIR }} # Runtime directories created before namespace setup. ProtectSystem=strict # makes the whole hierarchy read-only, and namespace setup fails # (exit 226/NAMESPACE) if any ReadWritePaths= entry is missing at spawn. # /run is a fresh tmpfs at every boot, so volatile /run paths must be # created up front (RuntimeDirectory= here; /run/firewalld via # system/tmpfiles.d/vacuum-wall.conf and by firewalld itself) rather than # at first use. # vacuum-wall : secure temp files used during config apply # nginx : /run/nginx (listed in ReadWritePaths) RuntimeDirectory=vacuum-wall nginx RuntimeDirectoryMode=0750 LogsDirectory=vacuum-wall # Security hardening ProtectSystem=strict # NOTE: every ReadWritePaths= entry must exist when the unit spawns or namespace # setup fails (226/NAMESPACE). Volatile /run entries are pre-created: # /run/vacuum-wall, /run/nginx → RuntimeDirectory= (above) # /run/firewalld → system/tmpfiles.d/vacuum-wall.conf (and is # present while firewalld runs, which starts # before this unit) # /run/sudo is intentionally NOT listed: the daemon's sudo children use the # NOPASSWD whitelist and never need sudo's session directory (verified with # the directory absent). Listing it made the unit crash-loop whenever it # restarted after the last sudo session had ended and sudo removed /run/sudo. # /run/nginx.pid IS listed: nginx -t opens the pid file for *writing* in # addition to -s/acme reading it, so a read-only mount makes every daemon-side # `nginx -t` (and therefore /nginx/apply) fail with EROFS. The file is # pre-created by system/tmpfiles.d/vacuum-wall.conf so the ReadWritePaths= # entry always exists at spawn (nginx rewrites it on start; nginx -t does # not modify its contents). ReadWritePaths={{ PROJECT_DIR }} /tmp /etc/systemd/network /etc/nginx /etc/dnsmasq.d /etc/wireguard /run/vacuum-wall /run/firewalld /run/nginx /run/nginx.pid /var/log/nginx /var/log/vacuum-wall PrivateTmp=yes ProtectKernelTunables=yes ProtectKernelModules=yes ProtectControlGroups=yes ProtectHostname=yes RestrictSUIDSGID=yes MemoryDenyWriteExecute=yes RestrictRealtime=yes RestrictNamespaces=yes LockPersonality=yes SystemCallFilter=@system-service PrivateDevices=yes RestrictAddressFamilies=AF_UNIX AF_INET AF_INET6 AF_NETLINK IPAddressDeny=any IPAddressAllow=localhost NoNewPrivileges=yes [Install] WantedBy=multi-user.target