fix: daemon /run spawn hardening, auth guard before first paint, WS refresh cap, interfaces runtime state
systemd: pre-create volatile /run paths so vacuum-walld's ProtectSystem=strict namespace setup cannot fail with 226/NAMESPACE — RuntimeDirectory=vacuum-wall nginx plus a tmpfiles.d spec (installed to /etc/tmpfiles.d/) covering /run/firewalld and /run/nginx.pid. Drop /run/sudo from ReadWritePaths: NOPASSWD children never need it, and its absence crash-looped restarts after sudo removed /run/sudo.
webui: run the auth session check before mounting the shell so logged-out visitors never flash the sidebar or a protected page; router guard and sidebar now react to auth state, and the login page renders full-bleed.
ws: cap refresh->reconnect episodes at 2 consecutive failures; if the WS path stays dead after a token refresh, abandon reconnection instead of looping refreshAuth forever (UI keeps working via REST until reload).
api: GET /api/network/interfaces now includes loopback and returns per-interface {config, runtime}; dashboard reads runtime.state (carrier counts as up) and the interfaces page filters lo client-side.
daemon: re-collect nginx state after lazy config migration (cached list went stale when the on-disk format changed under it), skip system_import.nginx when config.json already exists (re-parsing vacuum-wall's own generated sites is lossy), and poll nginx (60s) / acme (300s) state so file drift self-heals.
This commit is contained in:
@@ -11,6 +11,12 @@
|
||||
* never on the refresh promise. Terminal (no-token) transitions are
|
||||
* handled by the auth model's onSuccess (clears storage, redirects,
|
||||
* dispatches auth:logout).
|
||||
*
|
||||
* The refresh path is capped at 2 consecutive failing episodes (3 closed
|
||||
* connections each): if refresh + reconnect still cannot establish a
|
||||
* socket, the WS path itself is dead, and retrying would loop token
|
||||
* rotation forever. Reconnection is then abandoned until the page is
|
||||
* reloaded; the UI keeps working via the REST API.
|
||||
*/
|
||||
|
||||
import { refreshByTopic } from './model.js';
|
||||
@@ -19,6 +25,11 @@ import { refreshAuth, getAuthToken } from './auth_model.js';
|
||||
let _wsConn = null;
|
||||
let _wsReconnectMs = 0;
|
||||
let _wsFailCount = 0;
|
||||
// Consecutive refresh→reconnect episodes that still failed. Capped so a
|
||||
// dead WS path cannot loop `refreshAuth()` forever (each 200 refresh rotates
|
||||
// the user's token pair, so an unbounded loop storms the refresh endpoint).
|
||||
let _wsRefreshStreak = 0;
|
||||
let _wsGivingUp = false;
|
||||
let _wsClosingHandled = false;
|
||||
|
||||
/** Direct onMessage handlers — { topics, handler, unsubscribed }[] */
|
||||
@@ -55,6 +66,8 @@ function _wsConnect() {
|
||||
_wsConn.onopen = () => {
|
||||
_wsReconnectMs = 0;
|
||||
_wsFailCount = 0;
|
||||
_wsRefreshStreak = 0;
|
||||
_wsGivingUp = false;
|
||||
_wsClosingHandled = false;
|
||||
};
|
||||
|
||||
@@ -62,11 +75,25 @@ function _wsConnect() {
|
||||
if (_wsClosingHandled) return;
|
||||
_wsClosingHandled = true;
|
||||
if (!getAuthToken()) return;
|
||||
if (_wsGivingUp) return;
|
||||
_wsFailCount++;
|
||||
|
||||
if (_wsFailCount >= 3) {
|
||||
const oldConn = _wsConn;
|
||||
_wsFailCount = 0;
|
||||
_wsRefreshStreak++;
|
||||
if (_wsRefreshStreak >= 2) {
|
||||
// Refresh + reconnect has failed twice in a row — the WS path
|
||||
// is dead (not just the token). Stop retrying: the page keeps
|
||||
// working API-only, and a fresh page load (or the next
|
||||
// successful socket) restarts the cycle.
|
||||
_wsGivingUp = true;
|
||||
console.error(
|
||||
'[WS] giving up after repeated refresh+reconnect failures; ' +
|
||||
'live updates paused until the page is reloaded',
|
||||
);
|
||||
return;
|
||||
}
|
||||
await refreshAuth(); // never rejects; failure path handled by model onSuccess
|
||||
if (getAuthToken()) {
|
||||
_wsReconnectMs = 0;
|
||||
|
||||
Reference in New Issue
Block a user