Files
GoblinPay/deploy/gp-server.service
T
2ro e404aca7b4
ci / fmt / clippy / test (push) Has been cancelled
setup: operator-chosen wallet password + seed acknowledgement + restart-mode choice
Make the gp-server setup wizard grin-wallet-faithful about the two secrets
that belong to the operator, and let them decide how the till restarts.

- Wallet password: the wizard now PROMPTS for it (entered twice, confirmed to
  match, hidden echo on a TTY) instead of auto-generating it. Only the service
  secrets (API token, admin token, webhook secret) are still generated.
- Seed: a fresh 24-word seed is shown once and gated behind an explicit
  "wrote it down" acknowledgement before proceeding, like grin-wallet init; the
  paste-existing-recovery-phrase path is kept and gated the same way.
- Restart mode: the wizard offers both and defaults to unattended. Unattended
  seals the chosen password to the host as a 0400 systemd credential so the
  service auto-restarts (honest trade-off disclosed). Manual persists nothing
  and writes a gp-server.service.d/manual.conf drop-in that repoints the
  credential to a tmpfs path the operator populates via systemd-ask-password at
  each start.
- Pure helpers (validate_password, passwords_match, RestartMode/parse, parse_ack,
  render_manual_dropin, mode-aware render_env) unit-tested in gp-core; wizard
  flow tested in gp-server (fresh/paste, unattended/manual, mismatch reprompt).
- Reconfigure keeps an existing wallet's seed, password, and restart mode
  untouched (never re-prompts), so a running till is undisturbed.
- Updated deploy/gp-server.service, deploy/.env.example, README, and the
  WooCommerce/Medusa quickstarts to drop all auto-generated-password wording.
2026-07-05 14:41:00 -04:00

105 lines
4.7 KiB
Desktop File

# Hardened systemd unit for the GoblinPay server on bare metal.
#
# The recommended path is the setup wizard, which writes everything this unit
# reads (the env file and the 0400 wallet-password credential) and creates the
# encrypted wallet for you:
# sudo install -m0644 deploy/gp-server.service /etc/systemd/system/
# sudo gp-server setup # writes /etc/goblinpay.env + the secret file
# sudo systemctl daemon-reload && sudo systemctl enable --now gp-server
#
# Unlike goblin-nip05d, this service holds a MONEY secret (the wallet password,
# which decrypts the Grin seed at rest) and a wallet data directory. The
# operator CHOOSES that password at setup (grin-wallet-faithful; it is never
# auto-generated). It is passed as a systemd credential (read by PID1 as root,
# exposed read-only to the dynamic service user) rather than left world-readable.
#
# Restart mode (the wizard asks; default UNATTENDED):
# UNATTENDED — the chosen password is sealed to THIS host as the 0400 file
# below, so the service auto-restarts with no human. Honest trade-off: a
# full-machine compromise means wallet compromise; keep the till a small hot
# float and sweep to your own wallet regularly.
# MANUAL — nothing is stored on disk; the operator re-enters the password after
# every restart. The wizard drops in gp-server.service.d/manual.conf, which
# repoints LoadCredential to a tmpfs path (/run/goblinpay/wallet_password)
# the operator populates at each start via systemd-ask-password. Maximum
# protection against disk/machine theft, at the cost of hands-on restarts.
#
# The Grin SEED is deliberately NOT a service credential: the wizard consumes it
# once at setup to create the encrypted wallet, after which the wallet opens
# with the password alone (init-once). The seed then lives only encrypted at
# rest in the data dir and in the operator's written backup, never in this
# service's environment. (To bootstrap a wallet WITHOUT the wizard, set
# GP_MNEMONIC_FILE for the first boot only, then remove it.)
[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
# The wallet password as a credential: the source file stays root-owned 0400
# (in UNATTENDED mode the wizard writes the operator's CHOSEN password there);
# systemd exposes a copy under $CREDENTIALS_DIRECTORY (%d), readable by the
# dynamic service user. The wallet is pointed at it via the *_FILE variant. This
# overrides any GP_WALLET_PASSWORD_FILE in the env file above, so the service
# always reads the credential copy. In MANUAL mode the manual.conf drop-in resets
# this line and reads /run/goblinpay/wallet_password (tmpfs) instead.
LoadCredential=gp_wallet_password:/etc/goblinpay/secrets/wallet_password
Environment=GP_WALLET_PASSWORD_FILE=%d/gp_wallet_password
# First-boot-only seed bootstrap WITHOUT the wizard (remove after the wallet
# exists). The wizard makes this unnecessary.
#LoadCredential=gp_mnemonic:/etc/goblinpay/secrets/mnemonic
#Environment=GP_MNEMONIC_FILE=%d/gp_mnemonic
# 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