diff --git a/README.md b/README.md index 5cedc41..4029716 100644 --- a/README.md +++ b/README.md @@ -81,9 +81,46 @@ ADMIN_PASSWORD=... DB_PATH=/var/lib/wutzcalc/wutz.db node server/dist/index.js Single Node process serves the API, both client entries (`/` tablet, `/admin` backoffice), and writes to one SQLite file. +## Run as a systemd service + +Template files live in [`deploy/`](deploy/): a unit ([`wutzcalc.service`](deploy/wutzcalc.service)) +and an environment file ([`wutzcalc.env.example`](deploy/wutzcalc.env.example)). +They assume the built app lives in `/opt/wutzcalc` and the database in +`/var/lib/wutzcalc` — adjust paths in the unit if yours differ. + +```sh +# 1. Dedicated system user (no login, no home) +sudo useradd --system --no-create-home --shell /usr/sbin/nologin wutzcalc + +# 2. Install the built app (run `pnpm install && pnpm build` first) +sudo mkdir -p /opt/wutzcalc +sudo cp -a . /opt/wutzcalc # or rsync/checkout into place +sudo chown -R root:root /opt/wutzcalc # app dir stays read-only to the service + +# 3. Config + secrets (chmod 600 — holds ADMIN_PASSWORD) +sudo mkdir -p /etc/wutzcalc +sudo install -m 600 deploy/wutzcalc.env.example /etc/wutzcalc/wutzcalc.env +sudoedit /etc/wutzcalc/wutzcalc.env # set ADMIN_PASSWORD + +# 4. Install and start the unit +sudo cp deploy/wutzcalc.service /etc/systemd/system/ +sudo systemctl daemon-reload +sudo systemctl enable --now wutzcalc + +# Logs / status +systemctl status wutzcalc +journalctl -u wutzcalc -f +``` + +Confirm `ExecStart` matches your Node path (`command -v node`) — it defaults to +`/usr/bin/node`. The unit creates `/var/lib/wutzcalc` via `StateDirectory`, so +the service user owns the database directory automatically. + ## Env vars - `PORT` (default `3000`) - `HOST` (default `0.0.0.0`) - `DB_PATH` (default `./wutz.db`) - `ADMIN_PASSWORD` (**required** for backoffice login) +- `WUTZ_TZ` (default `Europe/Berlin`) — timezone for stats display and grouping +- `WUTZ_DAY_CUTOFF_HOUR` (default `5`) — sales before this local hour count toward the previous business day diff --git a/deploy/wutzcalc.env.example b/deploy/wutzcalc.env.example new file mode 100644 index 0000000..6eed85d --- /dev/null +++ b/deploy/wutzcalc.env.example @@ -0,0 +1,18 @@ +# Copy to /etc/wutzcalc/wutzcalc.env and edit. +# Keep it readable only by root / the service user — it holds the admin password: +# sudo install -m 600 -o root -g root deploy/wutzcalc.env.example /etc/wutzcalc/wutzcalc.env + +# Required: backoffice login password. +ADMIN_PASSWORD=changeme + +# SQLite database file. With StateDirectory=wutzcalc this dir is created for you. +DB_PATH=/var/lib/wutzcalc/wutz.db + +# Network bind (defaults: 0.0.0.0:3000). +PORT=3000 +HOST=0.0.0.0 + +# Sales-day handling (defaults shown). Hours before the cutoff count toward the +# previous business day, so a 03:00 sale lands on the night before. +#WUTZ_TZ=Europe/Berlin +#WUTZ_DAY_CUTOFF_HOUR=5 diff --git a/deploy/wutzcalc.service b/deploy/wutzcalc.service new file mode 100644 index 0000000..75bde31 --- /dev/null +++ b/deploy/wutzcalc.service @@ -0,0 +1,38 @@ +[Unit] +Description=wutzcalc — festival drink-sale tracker +Documentation=https://git.berlin.ccc.de/vinzenz/wutzcalc +After=network-online.target +Wants=network-online.target + +[Service] +Type=simple +User=wutzcalc +Group=wutzcalc + +# Where `pnpm build` was run — adjust to your install location. +WorkingDirectory=/opt/wutzcalc +# `which node` may differ (e.g. /usr/local/bin/node or an nvm path). +ExecStart=/usr/bin/node server/dist/index.js + +# Secrets and config live here, not in the unit. See wutzcalc.env.example. +EnvironmentFile=/etc/wutzcalc/wutzcalc.env + +Restart=on-failure +RestartSec=5 + +# Creates/owns /var/lib/wutzcalc — point DB_PATH there. +StateDirectory=wutzcalc + +# Hardening +NoNewPrivileges=true +ProtectSystem=strict +ProtectHome=true +PrivateTmp=true +PrivateDevices=true +ProtectKernelTunables=true +ProtectControlGroups=true +RestrictAddressFamilies=AF_INET AF_INET6 AF_UNIX +ReadWritePaths=/var/lib/wutzcalc + +[Install] +WantedBy=multi-user.target