From de037c879563083715be4c9dd99ea7c67f3321c3 Mon Sep 17 00:00:00 2001 From: Jon Vanvik Date: Tue, 23 Jun 2026 14:30:06 +0200 Subject: [PATCH] web/deploy: add one-shot install.sh MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Adds web/deploy/install.sh — full LXC setup in one command. It derives the repo path from its own location (clone anywhere; /opt/ponfw recommended), installs git/node/npm if missing (checks Node >= 18), creates the ponfw service user, runs npm install --omit=dev, writes /etc/pon-fleet-web.env on first run, and installs+enables the systemd unit with WorkingDirectory set to the checkout. Idempotent; warns if cloned under /home (ProtectHome blocks it). README + CLAUDE.md §16 updated to lead with install.sh and clarify the clone location. Co-Authored-By: Claude Opus 4.8 --- CLAUDE.md | 11 +++-- web/README.md | 42 ++++++++++--------- web/deploy/install.sh | 96 +++++++++++++++++++++++++++++++++++++++++++ 3 files changed, 126 insertions(+), 23 deletions(-) create mode 100755 web/deploy/install.sh diff --git a/CLAUDE.md b/CLAUDE.md index 2f72356..f148ef1 100644 --- a/CLAUDE.md +++ b/CLAUDE.md @@ -808,10 +808,13 @@ Consequences for editing: age badge from it (`renderCacheBadge`), and the Dashboard **Refresh** button sends `fresh:true` to bypass the cache. The Electron app sends no cache field, so the badge shows "live". -- **Deploy** lives in `web/deploy/` (systemd unit + env template + - `update.sh`); Debian-13-LXC clone-to-run steps and the Nginx Proxy Manager - setup are in `web/README.md`. Note streams send `X-Accel-Buffering: no` so - nginx/NPM don't buffer the NDJSON progress. +- **Deploy** lives in `web/deploy/`: `install.sh` (one-shot, idempotent — + derives the repo path from its own location, so clone anywhere; points the + systemd unit at that checkout), `update.sh` (git pull + npm install + + restart), the unit, and the env template. Debian-13-LXC steps + Nginx Proxy + Manager setup are in `web/README.md`. Clone to `/opt/ponfw` — the hardened + unit's `ProtectHome=true` blocks a checkout under `/home`. Streams send + `X-Accel-Buffering: no` so nginx/NPM don't buffer the NDJSON progress. The web app must stay a subdirectory of this repo — it `require`s `../src/*` and borrows `../node_modules` (`tough-cookie`); no separate install. Deploy diff --git a/web/README.md b/web/README.md index b4e205b..eaefc49 100644 --- a/web/README.md +++ b/web/README.md @@ -78,34 +78,38 @@ caller's MCMS-host snapshots. ## Deploy on a Debian 13 LXC (clone-to-run) -Files in `web/deploy/`: a systemd unit, an env template, and `update.sh`. +Files in `web/deploy/`: `install.sh` (one-shot setup), `update.sh`, a systemd +unit, and an env template. + +**Where to clone:** anywhere — `install.sh` points the service at wherever it +finds itself. `/opt/ponfw` is recommended: it's the FHS spot for this kind of +software, and the hardened unit sets `ProtectHome=true`, which would block the +service from reading a checkout under `/home`. ```bash # --- as root on the LXC, once --- -apt update && apt install -y nodejs npm git # Node 18+; or use NodeSource -adduser --system --group --no-create-home ponfw - git clone ssh://forgejo@int.git.vanvikinternet.no/Svorka/ponfw.git /opt/ponfw -cd /opt/ponfw -npm install --omit=dev --no-audit --no-fund # installs tough-cookie, skips Electron - -install -m 0644 web/deploy/pon-fleet-web.service /etc/systemd/system/ -install -m 0640 web/deploy/pon-fleet-web.env.example /etc/pon-fleet-web.env -$EDITOR /etc/pon-fleet-web.env # set HOST / PFW_SECURE_COOKIE etc. - -systemctl daemon-reload -systemctl enable --now pon-fleet-web -systemctl status pon-fleet-web -journalctl -u pon-fleet-web -f # logs +sudo /opt/ponfw/web/deploy/install.sh +$EDITOR /etc/pon-fleet-web.env # optional: HOST / PFW_SECURE_COOKIE / TTL +sudo systemctl restart pon-fleet-web # if you edited the env ``` -The repo lives at `/opt/ponfw` (root-owned, read-only to the service); the -service runs as the unprivileged `ponfw` user and writes nothing to disk. +`install.sh` is idempotent and: -**To update:** just re-pull and restart — +- installs `git` / `nodejs` / `npm` if missing (and checks Node ≥ 18), +- creates the unprivileged `ponfw` system user, +- runs `npm install --omit=dev` (tough-cookie only; skips Electron), +- writes `/etc/pon-fleet-web.env` from the template on first run, +- installs the systemd unit with `WorkingDirectory` set to this checkout, + then enables + starts it, +- prints the LXC IP and the NPM forward target. + +The checkout is root-owned and read-only to the service; the service runs as +`ponfw` and writes nothing to disk. ```bash -sudo /opt/ponfw/web/deploy/update.sh # git pull + npm install + restart +journalctl -u pon-fleet-web -f # logs +sudo /opt/ponfw/web/deploy/update.sh # update: git pull + npm install + restart ``` ## Behind Nginx Proxy Manager diff --git a/web/deploy/install.sh b/web/deploy/install.sh new file mode 100755 index 0000000..7ae2c73 --- /dev/null +++ b/web/deploy/install.sh @@ -0,0 +1,96 @@ +#!/usr/bin/env bash +# One-shot setup for the PON Fleet web server on a Debian 13 LXC. +# +# Clone the repo first (anywhere — /opt/ponfw recommended), then run this +# from inside the checkout as root: +# +# git clone ssh://forgejo@int.git.vanvikinternet.no/Svorka/ponfw.git /opt/ponfw +# sudo /opt/ponfw/web/deploy/install.sh +# +# It installs runtime deps (tough-cookie only; skips Electron), creates an +# unprivileged service user, writes /etc/pon-fleet-web.env on first run, and +# installs + enables a systemd unit pointed at THIS checkout. Safe to re-run. +set -euo pipefail + +SERVICE="pon-fleet-web" +SERVICE_USER="ponfw" +ENV_FILE="/etc/pon-fleet-web.env" + +# Repo root is two levels up from this script (web/deploy/install.sh). +SCRIPT_DIR="$(cd "$(dirname "$(readlink -f "$0")")" && pwd)" +REPO="$(cd "$SCRIPT_DIR/../.." && pwd)" +DEPLOY="$REPO/web/deploy" + +if [[ $EUID -ne 0 ]]; then + echo "Run as root: sudo $0" >&2 + exit 1 +fi + +echo "==> Repo checkout: $REPO" +case "$REPO" in + /home/*) + echo "!! WARNING: the repo is under /home. The hardened systemd unit sets" >&2 + echo "!! ProtectHome=true, which will stop the service from reading it." >&2 + echo "!! Clone to /opt/ponfw instead, or remove ProtectHome from the unit." >&2 + ;; +esac + +# --- dependencies (git, node, npm) --- +need=() +command -v git >/dev/null 2>&1 || need+=(git) +command -v node >/dev/null 2>&1 || need+=(nodejs) +command -v npm >/dev/null 2>&1 || need+=(npm) +if (( ${#need[@]} )); then + echo "==> apt-get install ${need[*]}" + apt-get update -y + apt-get install -y "${need[@]}" +fi + +NODE_MAJOR="$(node -p 'process.versions.node.split(".")[0]' 2>/dev/null || echo 0)" +if (( NODE_MAJOR < 18 )); then + echo "!! Node $(node -v 2>/dev/null || echo '?') is too old (need >= 18)." >&2 + echo "!! Install a newer Node (e.g. via NodeSource) and re-run." >&2 + exit 1 +fi +echo "==> Using $(node -v)" + +# --- service user --- +if ! id -u "$SERVICE_USER" >/dev/null 2>&1; then + echo "==> Creating system user $SERVICE_USER" + adduser --system --group --no-create-home "$SERVICE_USER" +fi + +# --- runtime deps (npm install, not ci: package-lock.json is gitignored) --- +echo "==> Installing runtime dependencies" +( cd "$REPO" && npm install --omit=dev --no-audit --no-fund ) + +# --- env file (first run only; never clobber operator edits) --- +if [[ -f "$ENV_FILE" ]]; then + echo "==> $ENV_FILE already exists — leaving it untouched" +else + echo "==> Writing $ENV_FILE from the example (review it)" + install -m 0640 "$DEPLOY/pon-fleet-web.env.example" "$ENV_FILE" +fi + +# --- systemd unit, with WorkingDirectory pointed at THIS checkout --- +echo "==> Installing systemd unit" +sed "s|^WorkingDirectory=.*|WorkingDirectory=$REPO/web|" \ + "$DEPLOY/$SERVICE.service" > "/etc/systemd/system/$SERVICE.service" +systemctl daemon-reload +systemctl enable --now "$SERVICE" +sleep 1 +systemctl --no-pager --lines=10 status "$SERVICE" || true + +IP="$(hostname -I 2>/dev/null | awk '{print $1}')" +cat < Done — $SERVICE is enabled and running. + Config: $ENV_FILE Logs: journalctl -u $SERVICE -f + Update: sudo $DEPLOY/update.sh + +Point Nginx Proxy Manager at this LXC: + Forward Hostname/IP: ${IP:-} Forward Port: 8080 Scheme: http + Add your Access List, request an SSL cert, enable Block Common Exploits. + (If NPM runs on a different host, set HOST=0.0.0.0 in $ENV_FILE and + restart: systemctl restart $SERVICE) +MSG