Compile the API instead of running its TypeScript in production.
The unit's ExecStart named src/index.ts, so every start depended on the host having Node 22.18 or newer for native type stripping. A deploy onto a host with Node 20 met ERR_UNKNOWN_FILE_EXTENSION, exited in under a second, and was restarted 464 times over fifteen hours with nothing anywhere going red. api/tsconfig.json now emits to api/dist. The source keeps its explicit .ts import specifiers, which is what makes `node --watch src/index.ts` work in development; rewriteRelativeImportExtensions turns them into .js on the way out, so what runs in production is ordinary ESM that any Node from 20.18 up will start. `pnpm build` builds shared, then api, then web. `pnpm dev` is unchanged. deploy/ is tracked rather than ignored: the unit files are the thing an operator copies to /etc/systemd/system, and the alert unit added next has to live somewhere a deploy can find it. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This commit is contained in:
co-authored by
Claude Opus 5
parent
06ba3d35e7
commit
65307ba278
@@ -0,0 +1,61 @@
|
||||
# /etc/systemd/system/cashumints-site.service
|
||||
#
|
||||
# Serves the built site on loopback. nginx proxies to it and never opens a file itself,
|
||||
# which is the point: when nginx held a `root` inside /home/cashumints, every directory
|
||||
# down to dist had to be traversable by www-data, and the one that was not took the
|
||||
# whole site down as a blanket 404 with nothing in the error log naming the cause.
|
||||
#
|
||||
# This is a long-running daemon, unlike cashumints-web.service next to it — that one is
|
||||
# the oneshot that produces what this one serves.
|
||||
|
||||
[Unit]
|
||||
Description=cashumints.space static site server
|
||||
Wants=network-online.target
|
||||
After=network-online.target
|
||||
# Give up after five failures in a minute instead of restarting forever. A process that
|
||||
# cannot start will not start on the 4000th attempt either, and `failed` in
|
||||
# `systemctl status` is a far louder signal than a journal scrolling past. These two are
|
||||
# [Unit] keys; systemd ignores them under [Service] with only a warning.
|
||||
StartLimitIntervalSec=60
|
||||
StartLimitBurst=5
|
||||
# Not Requires=cashumints.service: the pages are prerendered, so the site keeps serving
|
||||
# a correct-as-of-last-build copy while the API is down. Only the islands go quiet.
|
||||
|
||||
[Service]
|
||||
Type=simple
|
||||
User=cashumints
|
||||
Group=cashumints
|
||||
WorkingDirectory=/home/cashumints/CashuMints.space/web
|
||||
|
||||
# The tree comes from cashumints-web.service, which rsyncs it here after a build.
|
||||
# Serving web/dist directly would mean a rebuild empties the site for the length of it.
|
||||
StateDirectory=cashumints
|
||||
Environment=NODE_ENV=production
|
||||
Environment=SITE_PORT=8789
|
||||
Environment=SITE_HOST=127.0.0.1
|
||||
Environment=WEB_ROOT=/var/lib/cashumints/web
|
||||
ExecStart=/usr/bin/node server.mjs
|
||||
|
||||
Restart=on-failure
|
||||
RestartSec=5s
|
||||
KillSignal=SIGTERM
|
||||
# In-flight responses finish; idle keep-alive connections are closed at once.
|
||||
TimeoutStopSec=15s
|
||||
UMask=0027
|
||||
|
||||
NoNewPrivileges=true
|
||||
PrivateTmp=true
|
||||
PrivateDevices=true
|
||||
ProtectSystem=strict
|
||||
# Read-only rather than absent: server.mjs itself lives under /home/cashumints.
|
||||
ProtectHome=read-only
|
||||
ReadWritePaths=/var/lib/cashumints
|
||||
ProtectKernelTunables=true
|
||||
ProtectKernelModules=true
|
||||
ProtectControlGroups=true
|
||||
RestrictAddressFamilies=AF_UNIX AF_INET AF_INET6
|
||||
RestrictSUIDSGID=true
|
||||
LockPersonality=true
|
||||
|
||||
[Install]
|
||||
WantedBy=multi-user.target
|
||||
Reference in New Issue
Block a user