web: shared MCMS cache + systemd/clone-to-run deploy + NPM docs

Caching (reduce MCMS API load with multiple operators):
- web/cache.js: in-memory TTL cache with single-flight, keyed by MCMS host.
  Concurrent identical bulk reads collapse into ONE upstream fetch; everyone
  on the same MCMS shares the snapshot.
- Wired into the bulk reads only (ONU/OLT config+state lists, controllers,
  firmware). Per-ONU read-modify-write and the FEC pre-flight stay live.
- Writes (delete / per-ONU + bulk upgrade / flood change / firmware upload)
  invalidate the affected datasets for that host, so changes show on the
  next load instead of waiting out the TTL.
- PFW_CACHE_TTL_SECONDS (default 60, 0 disables). Authenticated
  _cacheStats / _cacheClear endpoints for ops.

Deploy (Debian 13 LXC, clone-to-run) in web/deploy/:
- pon-fleet-web.service (runs as unprivileged ponfw, hardened, repo
  read-only, no disk writes), pon-fleet-web.env.example, update.sh
  (git pull + npm install --omit=dev + restart; uses install not ci since
  package-lock.json is gitignored).
- README: full LXC setup, Nginx Proxy Manager proxy-host + access-list
  notes. Streams already send X-Accel-Buffering: no so NPM/nginx don't
  buffer the NDJSON progress.

Verified: node --check; cache unit-tested (single-flight, TTL hit/expiry,
fresh, invalidate, key isolation, disable — 9/9); server boots and reports
the cache TTL; _cacheStats gated to logged-in sessions.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
Jon Vanvik 2026-06-23 14:21:04 +02:00
parent 96b0264179
commit 6ee1d37a79
7 changed files with 309 additions and 35 deletions

View file

@ -0,0 +1,30 @@
# PON Fleet web server config.
# Copy to /etc/pon-fleet-web.env and edit. All values are optional; the
# defaults shown are what the server uses if a line is omitted.
# Listen port.
#PORT=8080
# Bind address. Keep 127.0.0.1 if Nginx Proxy Manager runs on THIS host.
# If NPM is a separate box/container, set 0.0.0.0 and restrict access at the
# proxy (access lists) and/or the LXC firewall.
#HOST=127.0.0.1
# Shared upstream cache TTL (seconds). Repeated bulk reads (fleet load,
# dashboard, OLT list) within this window are served from one cached MCMS
# fetch, shared across all operators on the same MCMS host. 0 disables it.
# Raise to cut MCMS load further; lower for fresher data.
#PFW_CACHE_TTL_SECONDS=60
# Idle session timeout (minutes). After this with no requests the server
# logs that operator's MCMS session out and drops their client.
#PFW_IDLE_MINUTES=30
# Force the Secure flag on the session cookie. Recommended when always
# behind HTTPS (NPM terminates TLS). If unset, Secure is inferred from the
# X-Forwarded-Proto: https header NPM/nginx sends.
#PFW_SECURE_COOKIE=1
# Verbose MCMS request logging to the journal. Off by default — very noisy
# with multiple users and dumps request metadata.
#PFW_VERBOSE=0

View file

@ -0,0 +1,35 @@
[Unit]
Description=PON Fleet web server (MCMS tools, multi-user)
Documentation=https://git.vanvikinternet.no/Svorka/ponfw
After=network-online.target
Wants=network-online.target
[Service]
Type=simple
User=ponfw
Group=ponfw
# The repo is cloned to /opt/ponfw; the server lives in its web/ subdir.
WorkingDirectory=/opt/ponfw/web
ExecStart=/usr/bin/env node server.js
# Defaults; override anything in /etc/pon-fleet-web.env (optional).
Environment=NODE_ENV=production
EnvironmentFile=-/etc/pon-fleet-web.env
Restart=on-failure
RestartSec=3
# --- Hardening (the app reads its repo, writes nothing to disk) ---
NoNewPrivileges=true
PrivateTmp=true
ProtectSystem=strict
ProtectHome=true
ProtectKernelTunables=true
ProtectKernelModules=true
ProtectControlGroups=true
RestrictSUIDSGID=true
RestrictNamespaces=true
LockPersonality=true
# V8 needs writable+executable memory for its JIT, so do NOT deny W^X.
MemoryDenyWriteExecute=false
[Install]
WantedBy=multi-user.target

22
web/deploy/update.sh Executable file
View file

@ -0,0 +1,22 @@
#!/usr/bin/env bash
# Update the PON Fleet web server in place: pull the repo, reinstall runtime
# deps (tough-cookie only — Electron is a devDep and is skipped), restart.
# Run as root (or via sudo) on the LXC. Override the path with PONFW_DIR.
set -euo pipefail
REPO="${PONFW_DIR:-/opt/ponfw}"
SERVICE="pon-fleet-web"
echo "==> Updating $REPO"
cd "$REPO"
git pull --ff-only
echo "==> Installing runtime dependencies (omit dev / Electron)"
# `npm install` (not `npm ci`) because this repo gitignores package-lock.json.
npm install --omit=dev --no-audit --no-fund
echo "==> Restarting $SERVICE"
systemctl restart "$SERVICE"
sleep 1
systemctl --no-pager --lines=8 status "$SERVICE" || true
echo "==> Done."