# 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