Adds /api/system/stream — a Server-Sent Events feed driven by a single
per-process ticker that reads /proc directly and splices in the latest
host-side metrics.json each second. Subscribers share the connection so
N open tabs cost one ticker, and the ticker pauses entirely when nobody
is listening.
Frontend gets a singleton LiveSystem EventSource manager with auto-
reconnect, Page-Visibility integration (closes on tab hide), and last-
sample replay for late subscribers. Admin -> System gauges and the
dashboard memory + disk tile now tick at 1 Hz; trend charts and the
per-app table keep their 30 s poll because the underlying files only
regenerate once a minute.
Also adds /api/system/history as a thin range-query wrapper over the
existing 24 h JSON ring buffer — the binary ring backend will slot in
behind it in the next phase without changing the response shape.
Signed-off-by: librelad <librelad@digitalangels.vip>
The 1h max-age set in Phase A caused a cache-vs-deploy mismatch when
Phase B refactored config-manager.js to lazy-load admin-overview.js et
al. The new index.html no longer eager-loads those scripts, but
browsers with the cached (pre-Phase-B) config-manager.js didn't do the
lazy-load either — so AdminOverview / AdminSystem / etc. were
undefined and the admin tools rendered 'failed to load' errors.
60s is the right balance: rapid in-session clicks skip the network
round-trip, but a deploy is visible within a minute. ETag-based 304s
still keep the per-request cost tiny when nothing changed.
Signed-off-by: librelad <librelad@digitalangels.vip>
Three WebUI cold-load wins:
1. DELETED containers/libreportal/frontend/js/components/config/config-manager-old.js
66 KB / 68189 bytes. Zero references anywhere in source or deployed
tree (confirmed via grep across containers/libreportal/). Pure dead
code from a previous refactor — removed.
2. ADDED `compression` middleware (defensive require)
Gzip-compresses JS/CSS/HTML/JSON responses. Typical ~70 % wire-size
reduction → the 1.7 MB cold-load drops to ~500 KB. New package.json
dependency; container's node_modules is baked into the image so the
require is wrapped in try/catch to degrade silently until the image
is next rebuilt (libreportal app install libreportal, or a full
deploy). Once active: free wire-size win on every response.
3. ADDED static cache headers via staticOptions on express.static
- JS/CSS/icons: Cache-Control: max-age=3600 + ETag
(1h browser cache, cheap 304 revalidation after)
- HTML files: Cache-Control: no-cache + ETag
(always revalidates so SPA shell updates land
immediately after a deploy; 304 if unchanged)
Repeat navigation in the same browser session skips ~25 script-tag
round-trips entirely.
Net effect once compression deploys:
- Cold load: 1.7 MB → ~500 KB on the wire (~70 % shrink)
- Warm load: 25 conditional requests → 0 (served from cache for 1h)
- Deploy lands: HTML revalidates immediately, JS/CSS picks up after 1h
or hard refresh
Phase B (defer non-critical scripts via SPA loadScript) and Phase C
(rebuild image / split the bind-mount story for node_modules) come
next; this commit is the safe Phase A foundation.
Signed-off-by: librelad <librelad@digitalangels.vip>
Audit follow-up — after a full-repo sweep, the only remaining functional /docker
refs are intentional (the legacy compat shim + the env-overridden legacy-safe
backend default). Fix the last user-visible/stale ones:
- config-options.js: backup PATH_MODE 'auto' label no longer hardcodes
/docker/backups (the path is relocatable) — describes the behaviour instead.
- config.js / setup-detector.js / webui_install_image.sh: refresh comments that
named /docker to the relocatable system/containers roots.
No behaviour change. Active container app scripts already use $containers_dir;
the remaining /docker hits across the tree are docker-compose.yml filenames,
/var/lib/docker, the docker binary, relative array paths, docs/site, and the
unused/ graveyard.
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
Signed-off-by: librelad <librelad@digitalangels.vip>
Introduce scripts/source/paths.sh as the canonical path resolver for three
independently-relocatable roots:
LP_SYSTEM_DIR manager-owned control plane (configs/logs/install/db/ssl/ssh/migrate)
LP_CONTAINERS_DIR container-user-owned live app data
LP_BACKUPS_DIR container-user-owned backup repos (own mount-able)
Roots come from the environment when set (install bakes them; CLI/app inherit
from init.sh), else default to /libreportal-*. A transitional compat default
keeps EXISTING installs (legacy single /docker tree, by config marker) on /docker
until a deliberate reinstall, so deploying this never strands a running box.
- init.sh derives the same vars inline (self-contained for the bare /root/init.sh
reinstall case); paths.sh mirrors it for the standalone task/check processors,
which now self-locate their scripts dir and source it.
- Replace functional /docker literals with the derived vars across runtime,
install, backup, crontab, crowdsec/restic, headscale, and reinstall paths;
clean the inert '== /docker/containers/*' guard fallbacks to the variable form.
- backend: CONTAINERS_DIR now from LP_CONTAINERS_DIR (compose env, filled at
generation via a new CONTAINERS_DIR_TAG), legacy-safe default for un-recreated
containers.
- backup default path falls back to the backups root; exclude paths.sh from the
sourced-file arrays (bootstrap file, sourced explicitly).
The CLI-wrapper heredoc + root helpers still reference /docker; those get baked
in phase 3. No layout/ownership change yet (phase 2).
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
Signed-off-by: librelad <librelad@digitalangels.vip>
A free, open, self-hosted app platform (GNU AGPLv3): one-click app deploys,
Traefik reverse proxy with automatic SSL, rootless Docker support, gluetun
VPN routing, and a web dashboard to manage it all.
Free & open forever to self-host; optional paid hosted services fund it.
See PROMISE.md.
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
Signed-off-by: librelad <librelad@digitalangels.vip>