Day 2026-10-19: agent 0.160.0 (released, not vouched), controller 0.332.0 floor (one sign-in R-1100, escrow confirm R-1151), hub 0.159.0 deployed (R-1150, R-1154), R-1153 not a defect, old-restic check passed; register 168 -> 166 (closed 10, opened R-1155..R-1162)
gates / gates (push) Successful in 7m20s

This commit is contained in:
2026-10-19 05:38:38 +02:00
parent 200ab6530b
commit db2371c6f5
35 changed files with 525 additions and 39 deletions
@@ -539,8 +539,28 @@ state (both demo boxes) the panel says „Ezt a jelszót te állítottad be a F
the press. The box uses FileBrowser's own CLI (`set -u admin,<pw>`) with the server STOPPED (the CLI opens the
database with no lock timeout), verifies new=200 and admin/admin refused, stores the value encrypted and records
`generated`; a failed verify leaves `operator`. Not the `FILEBROWSER_ADMIN_PASSWORD` env: it re-applies on every
start. Spike: `audits/logins-folders-2026-10-16/P1b`. FileBrowser still asks for its own login the first time; one
sign-in for both is R-1100.
start. Spike: `audits/logins-folders-2026-10-16/P1b`. Since v0.332.0 the folder links open FileBrowser without its own
login (the next paragraph); `admin`'s password is the fallback and the operator's way in.
**[DESIGN] One sign-in for the file manager (v0.332.0, R-1100, operator decision D16 = yes 2026-10-19; spike
`audits/proxyauth-spike-2026-10-18/DESIGN-NOTE.md`).** Every dashboard link to a FileBrowser folder goes to
`/__files/open?to=<path>` on the dashboard host, which — for a signed-in, not-cross-site request — mints a pass (60 s,
one use, bound to `files.<domain>` and to the session) and sends the browser to `files.<domain>/_felhom/sso`. traefik
asks the controller before every FileBrowser request (forwardAuth `felhom-files-sso` → `/__felhom_gate/files`, the setup
gate's handshake): the pass becomes the host-only `felhom_files` cookie (a value derived from the session's fingerprint
for that host — the R-976 shape); a valid cookie → 200 with the box's **secret header** naming FileBrowser user
`felhom-household`; no cookie → 200 without it (FileBrowser's own password); a stale cookie (sign-out, password change)
→ both cookies cleared, back to the dashboard. **Why a secret header name:** FileBrowser trusts the header blindly and
every app container reaches `filebrowser:80` directly — measured: a forged fixed-name header from an app logged in. The
name (`X-Felhom-User-<32 hex>`, per box, 0600 in the data dir) is written only into FileBrowser's config and traefik's
route (`authResponseHeaders`, which strips a client's copy); never a log, a page or a report. **Why not a parent-domain
cookie:** every app subdomain is third-party code and would receive the dashboard session. **What stays:** `admin` is a
password user (R-513's reveal and reset, the R-1126 gate unchanged); the header user is a separate FileBrowser user whose
rights come from the box's userDefaults (change, add, delete, share, download — not admin, not API); FileBrowser's logout
goes to the dashboard. Residual: FileBrowser's own 2-hour token for the header user is cleared with the files cookie,
but a browser that lost only the files cookie keeps it until it expires. Measured live on all three test boxes
(`audits/day-2026-10-19/P2/`, `P4/`): opened at the folder with no login; forged headers refused through traefik and
from an app container; a sign-out asks again.
**[DESIGN] A stored FileBrowser password is checked before it is offered (v0.321.0, R-1101).** In `generated`, the
page asks FileBrowser whether the stored value still logs in; only FileBrowser's own 401/403 counts as refused (a
@@ -562,8 +582,9 @@ role, else the first declared folder, else (a media app) the first present drive
`movies`, the players both, a download client its `media_library.folder`) with NO backup sentence — the bind's class
there means „not this app's data", which is not the class-driven „temporary, not backed up". **A folder whose drive
is not bound into FileBrowser now is text, never a link** (`drivePresent` = the same gates the FileBrowser sync
applies). The button never promises one click: FileBrowser's router brings a cold visitor back to the folder after its
login (measured in the headless browser, `audits/logins-folders-2026-10-16/browser/`).
applies). Since v0.332.0 the button opens the folder signed in (one sign-in, above); on a box without it, FileBrowser's
router brings a cold visitor back to the folder after its login (measured in the headless browser,
`audits/logins-folders-2026-10-16/browser/`).
**[DESIGN] „Cím és kulcsok" is rows, not the install form.** The settings tab does not re-render the
install wizard's fields: the address is the „Cím" row, the storage is a „Tárhely: <név>" row, every
+10 -3
View File
@@ -544,11 +544,18 @@ nicety.
**loses Felhom's backup guarantees** — no PBS verify / monitoring on storage we can't reach. An
honest signup-time tradeoff, not a hidden one.
**When the new package reaches the hub (R-978, agent `main` 2026-10-18, unreleased):** a ceremony started again no
**When the new package reaches the hub (R-978; agent 0.160.0, 2026-10-19):** a ceremony started again no
longer replaces the hub's package when it starts. The package travels with the pending ceremony and is uploaded when its
code is claimed, before the code is handed out (an upload failure is a 502 and the code stays claimable). An abandoned
ceremony — TTL, a new start, a restart — leaves the first code opening the hub's package. Residual: the typed-back check
is the browser's, so a code opened and not written down still replaces the package (R-1151).
ceremony — TTL, a new start, a restart — leaves the first code opening the hub's package. **[DESIGN] Only after the
household typed it back (R-1151; agent 0.160.0 + controller 0.332.0, 2026-10-19):** a controller ≥ 0.332.0 claims with
`{"defer_upload": true}`; the agent hands out the code with two word positions it drew, keeps the package (and only those
two words, never the code) for 30 minutes, and uploads it only when `POST /escrow/ceremony/confirm` brings the two words
back — so a code opened and not written down never replaces the package. Wrong words 422 (still waiting), window gone 410,
nothing waiting 404, hub failure 502 (still waiting). A controller that has not updated claims with `{}` and gets the
R-978 order. Proven on demo-hp 2026-10-19: ceremony 1 typed back → code 1 opens the hub's package, code 0 is a kept
earlier one (422); ceremony 2 shown and abandoned → code 1 still opens it, code 2 opens nothing
(`audits/day-2026-10-19/P1/escrow-*.txt`).
#### Custody across generations — what the hub retains when a blob is superseded (hub v0.60.0; **contract corrected hub v0.93.0, R-198**)
@@ -383,7 +383,7 @@ never moved by an update or an undo, and the unit restore accepts it.
| **Tier-1** recovery unit | `<nsRoot>/backups/primary/<app>/` on the app's own drive | compose + `.felhom.yml` + **secret-stripped** `app.yaml`, `db-dumps/*.sql`, `volume-dumps/*.tar`, `manifest.json` | nightly at W, plus a checksum-gated refresh on the periodic status pass | one point per app (`restore_points.go:14-18`) | no |
| **Tier-2** cross-drive | `<target nsRoot>/backups/secondary/<app>/` | **always** a full mirror of the unit (`tier2.go:368-369`) **plus** `mandatory + optional` file legs, v2 layout `hdd/<rel>` + `userdata/<rel>` | nightly at W+60m | mirror (rsync) | no |
| **Tier-3** restic offsite | Hetzner Storage Box over SFTP, one multi-path snapshot per app per run | the unit **plus** `mandatory` legs only; a separate `_shares` snapshot | nightly at W+105m | `--keep-daily 7 --keep-weekly 4 --keep-monthly 6` | **yes** (restic) |
| **Plane-2** whole-guest, local | `local:` → `/var/lib/vz/dump` on the host | rootfs + `mp0 /var/lib/felhom` (its `docker/` and `sys_drive/` folders, bound in the guest to `/var/lib/docker` and `/mnt/sys_drive` — §7.5.1; `mp1` no longer exists, corrected 2026-10-15) | **24 h** (`backup_cadence_seconds: 0`) | `local_backup_retention: 3` | **no** |
| **Plane-2** whole-guest, local | `local:` → `/var/lib/vz/dump` on the host | rootfs + `mp0 /var/lib/felhom` (its `docker/` and `sys_drive/` folders, bound in the guest to `/var/lib/docker` and `/mnt/sys_drive` — §7.5.1; `mp1` no longer exists, corrected 2026-10-15) | **24 h** (`backup_cadence_seconds: 0`) | `local_backup_retention: 3` → `--prune-backups keep-daily=3`: one copy per day, 3 days (keep-last until agent 0.160.0, R-1149) | **no** |
| **Plane-2** whole-guest, offsite | `felhom-pbs:` → ep0 datastore `felhom-offsite`, per-customer namespace, over WireGuard | same contents | **7 days** (`604800`) | server-side prune on ep0, `keep-last 2` at `03:30` — **and the box asks for none** (R-191) | **yes** (per-customer key) |
> **[FACT] Tier-3 has a fifth state the table above does not show: PAUSED (R-543, controller
@@ -479,15 +479,20 @@ upload library is a mandatory class) or uploaded again by the household — its
`pct config 9201`, both hosts). So the whole-guest tiers carry the guest and **none of the customer's
data drives** — 916 GB on demo-felhom, 938 GB on demo-hp.
> **The whole-guest tiers are capped per guest (R-973, agent `main` 2026-10-18, unreleased) — *decided by CC unattended,
> operator may reverse*.** Because the tiers prune by COUNT, a compromised controller that runs `POST /backup` again and
> **The whole-guest tiers are capped per guest (R-973; agent 0.160.0, released 2026-10-19 on the operator's yes — the
> numbers were *decided by CC unattended, operator may reverse*, and the operator said yes to the release).** Because the tiers prune by COUNT, a compromised controller that runs `POST /backup` again and
> again pushes out older restore points. The agent now refuses (429, logged, kept across a restart, a CRITICAL degraded
> status for 24 h) a run beyond: the daily tier — **3 successful runs per rolling 24 h** (the night run, one „Mentés
> most", the next night: a press never costs the night, R-899); the weekly off-site tier — **one per 6 days**
> (max(cadence − 24 h, cadence / 2)). The question: how many runs may a guest make before the cap refuses? Options: no cap
> (today, floodable); a per-tier count per rolling window (chosen: no ordinary day is refused); a time-bucketed prune
> instead (the durable shape, but a retention change — R-1149 for the local tier, R-973's design line for ep0). Limits:
> keep-last 3 can still be replaced within one day (R-1149); ep0's keep-last 2 is a count.
> ep0's keep-last 2 is a count. **[DESIGN] The local tier keeps one copy per day (R-1149, operator decision D17,
> 2026-10-19; agent 0.160.0):** `keep-daily=3` instead of keep-last 3 — the same at most three archives, but extra runs on
> one day replace only that day's copy, so with the cap a day of flooding leaves the two earlier days' copies
> (`TestBackupCap_R1149_LocalFloodKeepsOlderDays`). Measured at the first new-rule prune on demo-hp (retention 1: the
> same single newest copy either way) and read before on Tester 1 and demo-felhom (one copy per day each: the same copy
> goes at their next night under either rule) — `audits/day-2026-10-19/P1/`.
### 6.1.1 A box that is not always on — the catch-up, the banner, the alarms (R-871..R-874)
@@ -87,7 +87,7 @@ Title: **"Where a household's data is copied"**
| **Tier 1 — recovery unit** | each app: its settings, database dump, volumes | data drive 1, on the box | nightly at **02:30** (W) | 1 point per app | not encrypted (it never leaves the home) |
| **Tier 2 — cross-drive** | a mirror of Tier 1, plus the app's files | data drive 2, on the box (counted only on a different physical disk; in a VM made, not counted) | nightly **03:30** | mirror | not encrypted |
| **Tier 3 — off-site, per app** | Tier 1 + the app's must-keep files | **Hetzner Storage Box** (Germany), one folder per household | nightly **04:15** | 7 daily, 4 weekly, 6 monthly | the household's own restic password |
| **Whole guest — local** | the guest's system (not the data drives) | the box's own system disk | daily, in the window **04:30–08:30** | up to 3 copies (fewer on a small disk) | not encrypted |
| **Whole guest — local** | the guest's system (not the data drives) | the box's own system disk | daily, in the window **04:30–08:30** | one copy per day, up to 3 days (fewer on a small disk) | not encrypted |
| **Whole guest — off-site** | the same | **ep0** (Hetzner, Germany), one namespace per household | weekly, same window | last 2 | the household's own key, escrowed with a recovery code |
Footnote: "W = the household's backup window start, 02:30 by default. Tiers 1–3 follow at fixed offsets so they cannot run out of order. A missed night is caught up 15 minutes after the box starts."
File diff suppressed because one or more lines are too long