catcrafts.net/deploy/catcrafts-server.service
Jorijn van der Graaf df91762271
All checks were successful
Deploy / build-deploy (push) Successful in 3m47s
Replaced mollie
2026-08-20 20:15:47 +02:00

95 lines
4.2 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:
# TRANSFER_IBAN / TRANSFER_BENEFICIARY / TRANSFER_BIC — the BANK rail
# EURC_CHAINS=/etc/catcrafts/eurc-chains.json — the CRYPTO rail (self-hosted
# EURC; no key). Omit and checkout offers only bank.
# The address pool defaults to
# /var/lib/catcrafts/orders.jsonl.eurc-addresses.
# 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
# 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