88 lines
4.2 KiB
Desktop File
88 lines
4.2 KiB
Desktop File
[Unit]
|
|
# Morphit relay — broadcasts user-signed chain ops with the relay active key,
|
|
# plus invite/push/challenge REST endpoints. Matches the morphit-ops
|
|
# deployment: monorepo at /opt/morphit, root-owned config, Docker Postgres.
|
|
#
|
|
# Unattended unlock of the encrypted active key (ADR-0010 §4):
|
|
# The relay active key is an encrypted envelope. Its passphrase is delivered
|
|
# as a systemd ENCRYPTED CREDENTIAL — never as plaintext on disk. systemd
|
|
# decrypts it at every start (host key / TPM); no prompt, fully unattended
|
|
# across reboots. This is REQUIRED — the unit refuses to start without it.
|
|
# Create the credential once and migrate, per docs/OPERATIONS.md.
|
|
#
|
|
# Install (one-time, as root) — the encrypted credential MUST exist first:
|
|
# echo -n 'your-relay-passphrase' | systemd-creds encrypt --name=relay_passphrase - /etc/morphit/relay_passphrase.cred
|
|
# cp ops/systemd/morphit-relay.service /etc/systemd/system/
|
|
# systemctl daemon-reload
|
|
# systemctl enable --now morphit-relay
|
|
# journalctl -u morphit-relay -f
|
|
Description=Morphit relay (broadcasts user-signed chain ops; REST API)
|
|
Documentation=https://git.agorise.net/agorise/morphit/src/branch/main/docs/OPERATIONS.md
|
|
After=network-online.target docker.service
|
|
Wants=network-online.target
|
|
# Reboot recovery (cp597): disable the start-rate-limit so a power-cut restart
|
|
# never latches this unit into a permanent 'failed' state while Postgres (a
|
|
# Docker container here) is still coming up — it keeps retrying every RestartSec
|
|
# until the DB is reachable, with no ceiling on attempts.
|
|
StartLimitIntervalSec=0
|
|
|
|
[Service]
|
|
Type=simple
|
|
# Matches the current deployment (root-owned tree + config). De-privilege by
|
|
# chowning config + data to a service user and setting User=/Group= here.
|
|
User=root
|
|
WorkingDirectory=/opt/morphit/apps/relay
|
|
# ENFORCED encrypted-at-rest passphrase via systemd-creds. Create it ONCE:
|
|
# echo -n 'your-relay-passphrase' | systemd-creds encrypt --name=relay_passphrase - /etc/morphit/relay_passphrase.cred
|
|
# systemd decrypts it at every start (host key / TPM) into a memory-backed,
|
|
# service-only file; the relay reads the passphrase from that FILE
|
|
# (MORPHIT_RELAY_ACTIVE_KEY_PASSPHRASE_FILE) so the secret never enters the
|
|
# process environment, and never touches persistent disk in plaintext. If the
|
|
# credential is missing this unit FAILS to start — intentionally; do not fall
|
|
# back to a plaintext passphrase in morphit.env. See docs/OPERATIONS.md.
|
|
LoadCredentialEncrypted=relay_passphrase:/etc/morphit/relay_passphrase.cred
|
|
# Source BOTH env layouts, each guarded with `[ -f ]`: the ops-cli layout
|
|
# (/opt/morphit/morphit.env + morphit.config.env) AND the Ansible layout
|
|
# (/etc/morphit/relay.env), plus the generate-once VAPID file. Whichever a
|
|
# given install produced is sourced; the other is simply absent. This is why
|
|
# the relay works identically whether it was set up by `morphit-ops init` or
|
|
# by the Ansible playbook (which writes /etc/morphit/relay.env).
|
|
ExecStart=/usr/bin/env bash -c 'set -a; for f in /opt/morphit/morphit.env /opt/morphit/morphit.config.env /etc/morphit/relay.env /etc/morphit/relay-vapid.env; do [ -f "$f" ] && . "$f"; done; export MORPHIT_RELAY_ACTIVE_KEY_PASSPHRASE_FILE="${CREDENTIALS_DIRECTORY}/relay_passphrase"; set +a; exec /opt/morphit/node_modules/.bin/tsx src/main.ts'
|
|
# With the env-var passphrase path, restart-on-crash actually succeeds now
|
|
# (no interactive prompt to hang on).
|
|
Restart=on-failure
|
|
RestartSec=5
|
|
KillSignal=SIGTERM
|
|
TimeoutStopSec=25
|
|
StandardOutput=journal
|
|
StandardError=journal
|
|
SyslogIdentifier=morphit-relay
|
|
|
|
# --- Hardening (safe with User=root) -------------------------------
|
|
NoNewPrivileges=yes
|
|
ProtectHome=yes
|
|
PrivateTmp=yes
|
|
ProtectKernelTunables=yes
|
|
ProtectKernelModules=yes
|
|
ProtectKernelLogs=yes
|
|
ProtectControlGroups=yes
|
|
ProtectClock=yes
|
|
ProtectProc=invisible
|
|
ProcSubset=pid
|
|
RestrictRealtime=yes
|
|
RestrictSUIDSGID=yes
|
|
LockPersonality=yes
|
|
RestrictNamespaces=yes
|
|
SystemCallArchitectures=native
|
|
RestrictAddressFamilies=AF_INET AF_INET6 AF_UNIX
|
|
CapabilityBoundingSet=
|
|
AmbientCapabilities=
|
|
# A core dump would contain the decrypted WIF in memory.
|
|
LimitCORE=0
|
|
# Node's V8 JIT writes+executes in memory, so W^X must stay off.
|
|
MemoryDenyWriteExecute=no
|
|
MemoryMax=512M
|
|
TasksMax=256
|
|
|
|
[Install]
|
|
WantedBy=multi-user.target
|