catcrafts.net/deploy/catcrafts-server.service
Jorijn van der Graaf e68d2c245c
All checks were successful
Deploy / build-deploy (push) Successful in 2m20s
finacial page
2026-08-14 02:50:58 +02:00

101 lines
4.6 KiB
Desktop File

# catcrafts-server — the server-rendering backend.
#
# Install to /etc/systemd/system/catcrafts-server.service, then:
# systemctl daemon-reload && systemctl enable --now catcrafts-server
#
# Layout this expects on the host:
# /srv/catcrafts.net/ the wasm bundle + static assets (Caddy's root,
# and the rsync --delete target from CI)
# /srv/catcrafts-app/ the server binary and content/, deployed by CI
# catcrafts-server
# content/{projects,posts}.json
# /var/lib/catcrafts/ runtime state — the SQLite database and keys
# once the shop exists. NEVER in the webroot:
# that directory is both publicly served and
# wiped by `rsync --delete` on every deploy.
[Unit]
Description=catcrafts.net server-rendering backend
Documentation=https://forgejo.catcrafts.net/Catcrafts/catcrafts.net
After=network-online.target
Wants=network-online.target
# Caddy proxies to this; if it is down Caddy falls back to the static shell, so
# there is no hard ordering requirement between them.
[Service]
Type=simple
User=catcrafts
Group=catcrafts
WorkingDirectory=/srv/catcrafts-app
# --webroot points at Caddy's root so the boot <script> tags (with their
# per-build ?v= cache buster) are read from the deployed index.html rather than
# hardcoded. Bind to loopback only: Caddy terminates TLS and this speaks
# plaintext HTTP/1.1.
ExecStart=/srv/catcrafts-app/catcrafts-server --serve 8081 \
--content=/srv/catcrafts-app/content \
--webroot=/srv/catcrafts.net \
--orders=/var/lib/catcrafts/orders.jsonl
Restart=always
RestartSec=2s
# ── hardening ────────────────────────────────────────────────────────────
# This process will later hold bank and payment credentials, so it gets locked
# down now rather than after there is something worth stealing.
NoNewPrivileges=true
ProtectSystem=strict
ProtectHome=true
PrivateTmp=true
PrivateDevices=true
ProtectKernelTunables=true
ProtectKernelModules=true
ProtectControlGroups=true
ProtectClock=true
ProtectHostname=true
RestrictNamespaces=true
RestrictRealtime=true
RestrictSUIDSGID=true
LockPersonality=true
MemoryDenyWriteExecute=true
# Only IP sockets — no unix, no netlink, no packet sockets.
RestrictAddressFamilies=AF_INET AF_INET6
SystemCallArchitectures=native
SystemCallFilter=@system-service
SystemCallErrorNumber=EPERM
# ProtectSystem=strict makes everything read-only; grant just the state
# directory. StateDirectory creates /var/lib/catcrafts with the right owner.
StateDirectory=catcrafts
StateDirectoryMode=0700
# The content and webroot are read-only to this process by design: content is
# generated at build time and the webroot belongs to the deploy step.
ReadOnlyPaths=/srv/catcrafts-app /srv/catcrafts.net
# Secrets arrive from OUTSIDE the deployed tree — the web root is public and
# rsync-wiped, and /srv/catcrafts-app is CI-writable; neither may ever hold a
# credential. /etc/catcrafts/payments.env (root:root 0600) carries:
# MOLLIE_API_KEY=live_... (or test_... while verifying) — the BANK rail
# COINGATE_API_KEY=... the CRYPTO rail; omit and checkout offers only
# COINGATE_SANDBOX=1 bank. Sandbox tokens are not live tokens.
# SENDCLOUD_PUBLIC_KEY / SENDCLOUD_SECRET_KEY / SENDCLOUD_METHOD — REQUIRED
# to sell: no rate table means checkout refuses
# INVOICE_GPG_KEY=... invoice signing (see deploy/README.md)
# MAIL_COMMAND=msmtp -t order confirmation email (see deploy/README.md,
# MAIL_FROM=... "Order email"); unset = no email is sent
# BUNQ_CALLBACK_SECRET=... the last segment of the bunq mutation callback
# URL, and what brings that endpoint into being:
# unset, /api/bunq/* is a plain 404. NOT an API
# key — no bunq key belongs on this box, because
# one can initiate payments (deploy/README.md,
# "The bunq mutation callback")
# BUNQ_CALLBACK_PUBKEY=... path to bunq's server public key in PEM; set it
# to REQUIRE a valid RSA-SHA256 body signature
# The '-' prefix makes the file optional: without it the server starts with
# payments off and the shop renders but refuses checkout — degraded, not down.
EnvironmentFile=-/etc/catcrafts/payments.env
# Invoice signing keyring (see deploy/README.md, "Invoice signing").
Environment=GNUPGHOME=/var/lib/catcrafts/gnupg
[Install]
WantedBy=multi-user.target