SysDeck/klanker-gate/arch/klanker-gate.service

69 lines
2.7 KiB
SYSTEMD

[Unit]
Description=Frosty Deno LLM gateway (klanker-gate)
Documentation=file:/usr/share/klanker-gate/docs/index.md
Documentation=file:/usr/share/klanker-gate/permissions.md
# The production bootstrap path fails closed without PostgreSQL, so the
# service wants the local unit up when the state store is local. A remote
# FROSTY_PG_URL simply ignores it; Restart=on-failure covers the race where
# postgres is still finishing recovery when the gateway first probes it.
Wants=network-online.target
Wants=postgresql.service
After=network-online.target postgresql.service
[Service]
Type=exec
User=klanker
Group=klanker
EnvironmentFile=/etc/klanker-gate/env
# WorkingDirectory is the gateway's write sandbox: the Deno permission
# contract uses --allow-write=data (relative), so "data" resolves to
# /var/lib/klanker-gate/data and nowhere else.
WorkingDirectory=/var/lib/klanker-gate
StateDirectory=klanker-gate
RuntimeDirectory=klanker-gate
# ── v0.3.0 security packaging guard (SysDeck arch/ layer) ────────────
# Upstream binds 0.0.0.0 by default and, when FROSTY_ADMIN_TOKEN is
# unset, serves the ENTIRE admin API (provider CRUD, key management,
# /api/config/export?include_secrets=true) unauthenticated in
# "explicit local-admin mode". That is fine on a loopback-only dev
# box, but this package binds all interfaces — so the packaging layer
# fails closed instead: no token, no start. See arch/SECURITY-UPSTREAM.md
# (finding U-1/U-2). To run the upstream no-token mode anyway (only on
# a firewall-isolated host), override with a drop-in:
# # /etc/systemd/system/klanker-gate.service.d/override.conf
# [Service]
# ExecStartPre=
ExecStartPre=/bin/sh -c 'test -n "$FROSTY_ADMIN_TOKEN" || { echo "FROSTY_ADMIN_TOKEN is not set in /etc/klanker-gate/env — refusing to start an unauthenticated admin API on 0.0.0.0 (see /usr/share/klanker-gate/SECURITY-UPSTREAM.md)"; exit 1; }'
ExecStart=/usr/bin/klanker-gate
Restart=on-failure
RestartSec=2
TimeoutStopSec=15
KillSignal=SIGTERM
# ── hardening (kept compatible with Deno/V8: no MemoryDenyWriteExecute,
# because the JIT needs W^X pages; no SystemCallFilter until validated
# against the Code Mode worker) ─────────────────────────────────────
NoNewPrivileges=true
PrivateTmp=true
ProtectSystem=full
ProtectHome=true
ProtectKernelTunables=true
ProtectKernelModules=true
ProtectControlGroups=true
ProtectClock=true
ProtectHostname=true
ProtectProc=invisible
RestrictSUIDSGID=true
RestrictRealtime=true
RestrictNamespaces=true
DevicePolicy=closed
LockPersonality=true
CapabilityBoundingSet=
AmbientCapabilities=
UMask=0027
[Install]
WantedBy=multi-user.target