Files
GoblinPay/deploy/gp-server.service
T
2ro 3fdf4a230c M11: reproducible deploy pipeline
Multi-stage non-root Dockerfile (builds -p gp-server against the nip44/nym siblings; excludes the goblin-tree dev crate), a full docker-compose (server + bundled nostr-rs-relay + auto-HTTPS Caddy), a hardened systemd unit (DynamicUser, ProtectSystem=strict, NoNewPrivileges, seed via LoadCredential), an install.sh bare-metal bootstrap, .env.example, and an fmt+clippy+test CI workflow for Gitea and GitHub.
2026-07-03 03:22:43 -04:00

81 lines
3.1 KiB
Desktop File

# Hardened systemd unit for the GoblinPay server on bare metal.
#
# Install (or just run deploy/install.sh):
# sudo install -m0755 target/release/gp-server /usr/local/bin/
# sudo install -m0640 deploy/.env.example /etc/goblinpay.env # then EDIT it
# sudo install -m0644 deploy/gp-server.service /etc/systemd/system/
# sudo mkdir -p /etc/goblinpay/secrets # 0400 secret files
# sudo systemctl daemon-reload && sudo systemctl enable --now gp-server
#
# Unlike goblin-nip05d, this service holds MONEY secrets (the Grin seed and the
# wallet password) and a wallet data directory. The seed and password are passed
# as systemd credentials (read by PID1 as root, exposed read-only to the dynamic
# service user) rather than left world-readable, and the config supports the
# `*_FILE` mounted-secret variants for exactly this.
[Unit]
Description=GoblinPay — self-hostable, receive-only Grin payment server
After=network-online.target
Wants=network-online.target
[Service]
Type=exec
# Throwaway unprivileged user allocated at runtime. For a stable data owner,
# comment this out and set `User=goblinpay` (create the user first).
DynamicUser=yes
# Non-secret config (domain, node, tokens, webhook, relay URL). Read by systemd
# as root, so a 0640 root:root file is fine even under DynamicUser.
EnvironmentFile=/etc/goblinpay.env
# Money/identity secrets as credentials: the source files stay root-owned 0400;
# systemd exposes copies under $CREDENTIALS_DIRECTORY (%d), readable by the
# dynamic service user. Point the wallet at them via the *_FILE variants.
LoadCredential=gp_mnemonic:/etc/goblinpay/secrets/mnemonic
LoadCredential=gp_wallet_password:/etc/goblinpay/secrets/wallet_password
Environment=GP_MNEMONIC_FILE=%d/gp_mnemonic
Environment=GP_WALLET_PASSWORD_FILE=%d/gp_wallet_password
# Optional: a NIP-49 encrypted Nostr identity (else a random one is generated
# and persisted under the data dir on first start). Uncomment with its file:
#LoadCredential=gp_ncryptsec:/etc/goblinpay/secrets/ncryptsec
#Environment=GP_NCRYPTSEC_FILE=%d/gp_ncryptsec
# Managed state at /var/lib/goblinpay: the SQLite db, the wallet files, and the
# encrypted seed at rest. 0700 — only the service user may read it.
StateDirectory=goblinpay
StateDirectoryMode=0700
Environment=GP_DB_PATH=/var/lib/goblinpay/goblinpay.db
Environment=GP_DATA_DIR=/var/lib/goblinpay/gp-data
ExecStart=/usr/local/bin/gp-server
Restart=on-failure
RestartSec=2
# --- hardening ---
NoNewPrivileges=yes
ProtectSystem=strict
ProtectHome=yes
PrivateTmp=yes
PrivateDevices=yes
ProtectKernelTunables=yes
ProtectKernelModules=yes
ProtectControlGroups=yes
ProtectClock=yes
ProtectHostname=yes
RestrictNamespaces=yes
RestrictRealtime=yes
RestrictSUIDSGID=yes
LockPersonality=yes
# If the Nym mixnet stack ever fails to start with a W^X error, comment this out.
MemoryDenyWriteExecute=yes
SystemCallArchitectures=native
SystemCallFilter=@system-service
SystemCallFilter=~@privileged @resources
# Only the state directory is writable.
ReadWritePaths=/var/lib/goblinpay
# No raw sockets; only IP + unix.
RestrictAddressFamilies=AF_INET AF_INET6 AF_UNIX
[Install]
WantedBy=multi-user.target