# systemd unit for the FastAPI process.
#
# Install:
#   sudo cp deploy/systemd/rfc-app.service /etc/systemd/system/
#   sudo systemctl daemon-reload
#   sudo systemctl enable --now rfc-app
#   sudo systemctl status rfc-app
#
# Logs:
#   sudo journalctl -u rfc-app -f
#
# Per §4.2 the app is intentionally single-process — one uvicorn
# worker, colocated SQLite. If the deployment ever needs more than one
# worker, the spec calls for a planned migration to Postgres first;
# raising `--workers` here would break the WAL-mode SQLite invariant.

[Unit]
Description=Wiggleverse RFC app
After=network.target
Documentation=https://git.wiggleverse.org/ben.stull/rfc-app

[Service]
Type=simple
User=rfc-app
Group=rfc-app
WorkingDirectory=/opt/rfc-app/backend
EnvironmentFile=/opt/rfc-app/backend/.env
ExecStart=/opt/rfc-app/backend/.venv/bin/uvicorn app.main:app \
    --host 127.0.0.1 \
    --port 8000 \
    --proxy-headers \
    --forwarded-allow-ips 127.0.0.1
Restart=on-failure
RestartSec=5s

# Hardening — modest defaults; tighten further if the host runs other
# services. The bot wrapper writes only to its own data dir; everything
# else is read-only.
NoNewPrivileges=true
ProtectSystem=strict
ProtectHome=true
PrivateTmp=true
ReadWritePaths=/opt/rfc-app/backend/data

# v0.25.0 security hardening (audit 0026 L4) — defense-in-depth.
# The service binds 127.0.0.1:8000 and runs plain CPython
# (FastAPI/uvicorn + sqlite + bcrypt + httpx), so it needs no
# capabilities and no exotic syscalls.
CapabilityBoundingSet=
AmbientCapabilities=
PrivateDevices=true
ProtectKernelTunables=true
ProtectKernelModules=true
ProtectKernelLogs=true
ProtectControlGroups=true
RestrictAddressFamilies=AF_INET AF_INET6 AF_UNIX
RestrictNamespaces=true
LockPersonality=true
# MemoryDenyWriteExecute=true blocks W^X memory — safe for stock
# CPython (no JIT) and the pure-Python/C-extension stack here, but
# would break a JIT or a C-ext that mmaps W+X. Watch the first
# restart's journal for a crash; if uvicorn fails to come up,
# comment this one line out and reload.
MemoryDenyWriteExecute=true
RestrictRealtime=true
RestrictSUIDSGID=true
SystemCallFilter=@system-service
SystemCallErrorNumber=EPERM
SystemCallArchitectures=native
UMask=0077

[Install]
WantedBy=multi-user.target
