The prototype was served by a bare `python3 -m http.server` in a foreground
shell. It has no restart policy, so it died silently and the public URL
returned a 502 from Caddy with valid DNS and a valid certificate already in
place. This unit makes the failure mode visible and recoverable.
umbratrans-web-prototype.service:
- Restart=on-failure with a 5s delay and a 120s/5-attempt start limit, so a
crash loop stops retrying but a clean stop always starts again.
- Binds 0.0.0.0:8442 serving web/; Caddy keeps terminating TLS upstream.
- Sandboxed: runs unprivileged with an empty capability bounding set, no new
privileges, read-only home and system, private tmp and devices, system call
filtering, and no access to kernel or control-group interfaces.
- Journal output to the user journal for inspection.
PrivateUsers=yes is documented as deliberately omitted. It hides /etc/group
from the manager, so systemd cannot resolve supplementary groups and the unit
fails with status=216/GROUP. A probe unit confirmed the remaining sandbox
directives are all valid in user scope.
Verified: all three routes and the icon return 200 locally and the public
HTTPS URL returns 200. SIGKILL of the main PID recovered automatically within
the restart delay. `systemd-analyze verify` is clean.
Two follow-ups requiring root are recorded in web/README.md: the rootless
install must drop User= because a --user manager cannot setuid, and
`loginctl enable-linger umbra` is needed for the service to survive logout and
start at boot.