# PON Fleet — web server A multi-user, browser-based front end for the same tooling as the Electron **PON Fleet Upgrader** (Dashboard / ONU firmware upgrades / OLT PON flooding mode). Built to sit behind a TLS-terminating reverse proxy so several operators can use it at once, **each with their own MCMS login**. ## How it relates to the Electron app It does **not** duplicate the app — it reuses it: - **Core logic** comes straight from `../src/` (`mcms-api.js`, `bank-strategy.js`, `dashboard.js`). One source of truth for every MCMS quirk. - **The UI is the same files.** The server serves `../renderer/app.js` and `../renderer/app.css` as-is and rewrites `../renderer/index.html` on the fly (adds a mobile viewport, swaps in the browser API shim + a small responsive overlay). So the Electron app and the web app never drift. - The only web-specific code is `server.js`, `public/api-web.js` (a `window.api` shim: Electron IPC → `fetch` + NDJSON streaming, Electron file dialogs → browser file pickers, CSV-to-Downloads → Blob download), and `public/web.css` (responsive overlay). Because of the `../` references this folder **must** live inside `pon-fleet-upgrader/` (it borrows the already-installed `tough-cookie` from `../node_modules`). No separate `npm install` is needed. ## Run ```bash cd pon-fleet-upgrader/web npm start # node server.js — listens on 127.0.0.1:8080 npm run check # node --check on server.js + api-web.js ``` Then browse to . Each browser logs in with its own MCMS host + credentials; the server keeps a separate `McmsClient` (separate cookie jar) per session. ### Configuration (env) | Var | Default | Meaning | |---|---|---| | `PORT` | `8080` | listen port | | `HOST` | `127.0.0.1` | bind address — `0.0.0.0` if the proxy is on another host | | `PFW_CACHE_TTL_SECONDS` | `60` | shared upstream cache TTL; `0` disables (see Caching) | | `PFW_IDLE_MINUTES` | `30` | idle-session expiry (logs the MCMS session out) | | `PFW_SECURE_COOKIE` | off | force the `Secure` cookie flag (else inferred from `X-Forwarded-Proto: https`) | | `PFW_VERBOSE` | off | verbose MCMS request logging (very noisy with many users) | ## Caching (fewer MCMS requests) The server keeps a small in-memory cache so multiple operators don't each hammer MCMS. The heavy bulk **reads** — ONU configs, ONU states, OLT states, OLT configs, controller configs, firmware inventory — are cached with a TTL (`PFW_CACHE_TTL_SECONDS`, default 60s) and **keyed by MCMS host**, so: - Everyone pointed at the same MCMS shares one snapshot. Ten operators loading the fleet within a minute trigger **one** upstream fetch, not ten. - A **single-flight** guard means even simultaneous cold loads collapse into one upstream request (the rest await it). - Different MCMS hosts never share, and the dashboard's light path reuses the same OLT-STATE snapshot the fleet view already fetched. What is **not** cached (always live, by design): - Per-ONU `getOnuConfig` used right before a firmware PUT (read-modify-write must see the current bank state). - The FEC pre-flight and verify per-ONU `getOnuState` (operators want the reading at decision time). **Writes invalidate** the affected datasets for that MCMS host, so a delete / firmware push / flood-mode change is reflected on the next load instead of waiting out the TTL. Tune the TTL up to cut MCMS load further, or set `PFW_CACHE_TTL_SECONDS=0` to disable caching entirely. `POST /api/_cacheStats` (authenticated) shows hits/misses/coalesced; `POST /api/_cacheClear` drops the caller's MCMS-host snapshots. ## Deploy on a Debian 13 LXC (clone-to-run) 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 --- git clone ssh://forgejo@int.git.vanvikinternet.no/Svorka/ponfw.git /opt/ponfw 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 ``` `install.sh` is idempotent and: - 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 journalctl -u pon-fleet-web -f # logs sudo /opt/ponfw/web/deploy/update.sh # update: git pull + npm install + restart ``` ## Behind Nginx Proxy Manager Point an NPM **Proxy Host** at the server: - Forward Hostname/IP: the LXC's address · Forward Port: `8080` - Scheme: `http` (NPM terminates TLS; request an SSL cert in NPM) - Enable **Block Common Exploits**; add your **Access List** for auth/IP allow-listing (this is your access control — the app itself has no user directory beyond MCMS login). - **Websockets Support** on is harmless. The progress streams (bulk delete, FEC pre-flight, flood change) are plain NDJSON over HTTP and already send `X-Accel-Buffering: no`, which nginx/NPM honour, so they stream live with no extra config. If you ever see progress arrive only at the end, add to the host's **Advanced** tab: ```nginx proxy_buffering off; proxy_read_timeout 300s; ``` If NPM runs on a different host than this LXC, set `HOST=0.0.0.0` in `/etc/pon-fleet-web.env` and restrict reachability to the NPM host via the LXC firewall + the NPM access list. ## Security notes - The server holds each logged-in user's **live MCMS session cookie in memory**. Run it behind HTTPS, keep `HOST=127.0.0.1`, and rely on the idle timeout. The session cookie is `HttpOnly`, `SameSite=Lax`, and `Secure` behind TLS. - "Accept self-signed TLS certificate" disables verification **to MCMS** for that session only (same as the desktop app). - There's no app-level user directory — anyone who can reach the page and has valid MCMS credentials can log in. Restrict network access at the proxy (mTLS / SSO / IP allow-list) if you need more than MCMS auth. - Several operators running bulk operations at once multiply load on MCMS; the page-size-100 / timeout mitigations from the main `CLAUDE.md` §5 still apply per session. ## What differs from the desktop app - **CSV files download through the browser** instead of auto-saving to `~/Downloads` (a server can't write to each user's machine). Same filenames, same BOM/CRLF/quoted format. - Firmware `.bin` and plan-CSV selection use the browser file picker. - Everything else — views, filters, FEC logic, flood-mode rules, the dashboard and its drill-downs — is the shared renderer code.