# The one systemd unit.
#
#     sudo cp deploy/tera-api.service /etc/systemd/system/
#     sudo systemctl enable --now tera-api
#
# Note the `-` on EnvironmentFile: the file is optional, so this unit starts and
# serves /api/v1/health on a box where /etc/tera/tera.env was never created. That
# is the same promise the compose file and the tests make, expressed in the one
# place an operator is most likely to discover it the hard way. CONTRACT.md §5.1.
#
# Assumes the repo is checked out at /srv/tera with `npm ci --omit=dev` already
# run at the root. There is no build step — Node runs the TypeScript sources
# directly — so a deploy is a git pull and a restart.

[Unit]
Description=Tera API — city data for the Tera map view
Documentation=https://github.com/lumbridge-public/tera
After=network-online.target
Wants=network-online.target

[Service]
Type=simple
User=tera
Group=tera
WorkingDirectory=/srv/tera
EnvironmentFile=-/etc/tera/tera.env
ExecStart=/usr/bin/node server/src/index.ts
Restart=on-failure
RestartSec=2s

# Requires Node >= 22.18, where type stripping runs without a flag.
Environment=NODE_ENV=production

# Hardening. This process reads two directories, opens outbound HTTPS to at most
# one weather feed, and listens on loopback. It has no business doing anything
# else, and saying so here is cheaper than trusting that it never will.
NoNewPrivileges=yes
PrivateTmp=yes
PrivateDevices=yes
ProtectSystem=strict
ProtectHome=yes
ProtectKernelTunables=yes
ProtectKernelModules=yes
ProtectControlGroups=yes
RestrictAddressFamilies=AF_INET AF_INET6
RestrictNamespaces=yes
LockPersonality=yes
MemoryDenyWriteExecute=no
SystemCallFilter=@system-service
SystemCallErrorNumber=EPERM

# Nothing is written at runtime. The marker snapshot is written by the sync
# oneshot, which is a different unit with a different user and the only holder of
# a Workie credential — add its directory here only if you run both as `tera`.
ReadOnlyPaths=/srv/tera

MemoryMax=512M
TasksMax=64

[Install]
WantedBy=multi-user.target
