Files
felhom.eu/documentation/audits/day-2026-10-08/design-R-35.md
T

3.9 KiB

R-35 — a config apply ends the household's dashboard session: a one-page design (2026-10-08)

Status: design only. Read in source today (controller main a0370b4ed8ef). Architecture documents: architecture/02-controller-module-map.md (config refresh), 07-backup-architecture.md §5 (the whole-guest archive is plaintext on the household's premises), 09-update-architecture.md §3 decisions 63-64 (the family gate).

The problem, as measured

2026-07-21: the hub raised config_version 10→11 at 16:54:58; the controller self-restarted (StartedAt 16:54:59Z, up 16:55:02); the household was logged out mid-flow (row evidence controller-log-full.txt). Still true in source:

  • internal/report/config_refresh.go:33-71 — any config_version change: re-pull controller.yaml, record, then Restart = api.GracefulSelfRestart (cmd/controller/main.go:1100-1109). No per-setting choice.
  • internal/web/auth.go:15-18, 250-270 — dashboard sessions live only in s.sessions map[string]*session (token, expiry, CSRF token). Any process exit ends every session. This is not only a config-apply problem: every controller update (the floor, the household's own update press, a crash restart) logs the household out too.

New since the row's last note (2026-10-05): the family gate (decisions 63-64, controller v0.287.0) already persists its sessions to family.json (0600, tmp+fsync+rename) in the data dir „so a restart keeps every session" (internal/family/family.go:11-12, 45-50, 267-290). It stores the session ID itself. So „login tokens on disk" is already the product's state for family sign-ins; the dashboard is the odd one out.

Options

What Costs Risk
A — hot-apply Adopt changed controller.yaml fields in the running process; restart only for fields that need it. A per-field ruling over the whole config (hub, offbox, cloudflare, paths…), a reload path per module. ~2-3 sessions. A field applied half-way; fixes config apply only, not updates.
B — persist sessions, token on disk Write the session map to sessions.json (0600) like family.json. ~½ session. A copy of the archive (plaintext by design, 07 §5) holds live 7-day dashboard tokens.
C — persist sessions, fingerprint on disk As B, but the file holds sha256(token) → {expiry, CSRF token}; lookup hashes the cookie. Cleared by invalidateAllSessions (password change) and logout as today; expired rows dropped at load and at the 15-min cleanup. ~½ session + tests. A stolen archive gives a fingerprint that cannot be turned back into a cookie. A box restored from an archive keeps that day's sessions valid until they expire (≤7 days) — the same as family sessions today.

Pick: C. It ends the logout for every restart, not just config apply, at the smallest cost, and it removes the one cost the row named (tokens in the archive). A stays a later refinement if restarts themselves become a problem.

First slice, with its red test: internal/web/session_store.go (load at NewServer, save under sessionsMu on create/delete/invalidate, atomic write as in family.saveLocked). Tests: (1) create a session, build a NEW Server on the same data dir, the cookie is still valid and returns the same CSRF token — red today (map is fresh); (2) the file contains no token bytes (grep the file for the token: must be absent); (3) after invalidateAllSessions, a new Server rejects the old cookie; (4) an expired row is not loaded. Live check on scratch 9202: sign in, trigger a controller restart, the next page load needs no login.

One question for the operator

May the box remember a dashboard sign-in across its own restarts, keeping on its disk only a fingerprint that cannot be used to sign in? If you do nothing: the household is logged out at every settings push and every controller update, as today.