v0.287.0: CHANGELOG, README, REUSE (the family gate)
gates / gates (push) Successful in 29s

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_0159rPz1ZhFKsS53msqPYxtS
This commit is contained in:
2026-10-02 07:43:49 +02:00
parent 977665d8c0
commit c8d5ee204d
3 changed files with 37 additions and 0 deletions
+29
View File
@@ -1,3 +1,32 @@
## v0.287.0 — the family gate: family members with their own logins in front of chosen apps (decisions 63/64, R-780) (2026-10-02)
**MinAgent: 0.131.0** (unchanged). Needs hub v0.123.0 (unchanged). New strings: `family_gate.*`, `err.stacks.family_gate_failed`,
`err.stacks.needs_newer_controller` (both languages). **Catalog companion:** `family_gate:`, `family_gate_except:` and
`min_controller:` in `.felhom.yml` (a template with `family_gate` must say `min_controller: "0.287.0"`).
- **The family list** (`internal/family`): the household's dashboard admin adds, resets and removes family members on the
security page („Család" card). Each has their OWN name and a generated password (4×4 letters/digits, shown once, in the
answer to the press — never in a page, never logged). Sessions last 30 days and survive a restart (`family.json`,
0600, atomic). A reset (the member's generation moves on), a removal or a logout ends access at the next request.
- **The door** (`internal/stacks/family_gate.go`): an app whose template says `family_gate: true` gets a traefik
forwardAuth file BEFORE its first start (also when a removed app is restored); a life record (`family_gate:` in
app.yaml) keeps it while the app is installed — a catalog change never gates or un-gates an installed app. Priority
below the install hold, the setup gate and the sign-up block. `family_gate_except:` lists literal path prefixes
(e-reader and phone apps) routed WITHOUT the door — **anchored** `^/prefix(/|$)` (the spike's finding F1: an unanchored
`PathPrefix(/api/v1/opds)` let `/api/v1/opdsx` through); a matcher or regex in the template refuses the install.
- **The answerer** (`internal/web/family_gate.go`): `/__felhom_gate/family` (forwardAuth; the app cookie
`felhom_famgate` is host-only and names a store session); `/__family/start|login|logout` on the dashboard host,
outside the dashboard's auth (the family session cookie `felhom_family` is scoped to `Path=/__family`). The sign-in is
counted per VISITOR (`clientIP`, R-753: 5 per minute) and per NAME (10 per 10 minutes) — a stranger locks only himself;
a name under a spread attack waits minutes. The household's dashboard session vouches (decision 46's rule) as a
household session in the family store. **`RequireAuth` never reads a family cookie; no family page ever sets the
dashboard cookie.** While the controller is down a gated app answers an error (traefik's forwardAuth), never the app.
- **`min_controller:`** — a template that needs a newer box is refused before anything is written.
- Tests: `TestFamily_*` (store), `TestFamilyGate_*` (web: stranger, member-not-dashboard, reset/remove/logout, locks,
household, token, setup gate untouched, messages, card), `TestFamilyExceptRegexp_Anchored`, `TestFamilyGate_*`
(stacks: before the first start, bad exception refuses, restore + loop), `TestMinController`. Red-proofs RP-F1..RP-F7,
each seen failing (`felhom.eu/documentation/audits/family-gate-2026-10-02/A/`).
## v0.286.1 — R-772 found live: a stopped probe container is seen on the next tick (2026-10-01)
**MinAgent: 0.131.0** (unchanged). Needs hub v0.123.0 (unchanged). No new strings. **v0.286.0 was never floored**
+1
View File
@@ -28,6 +28,7 @@
| `stacks.RunAfterInstall` / `expandAfterInstall` / `web.defaultLoginInEffect` (v0.279.0, decision 45) | controller/internal/stacks/after_install.go · controller/internal/web/known_login.go | `(name, wait)` / `(cmd, allowed, env)` / `(meta, cfg, installed)` | A fresh install replaces a known default login; the page says when a default is still in effect | **Only from the deploy-done hook** — never after a restore/kept load (R-694). A `success:` marker is required (exit 0 lies). Never log the expanded command |
| `stacks.OpenSetupGate` / `SetupGateTick` / `SetupGateHost` · `web.ServeGateAuth` / `ServeGateStart` (v0.280.0, decision 46) | controller/internal/stacks/setup_gate.go · controller/internal/web/setup_gate.go | `(name, by)` / `()` / `(host)` · handlers | The setup gate: a `setup_gate: true` install is closed to everyone but the household until its probe or the household's press opens it | **Write the gate BEFORE the first start** (spike F2). Open = record first, then remove the file. Never widen the dashboard cookie — the handshake mints a host-bound one-use token |
| `stacks.OpenSignupWindow` / `SignupBlocked` / `SetupGateProbe` · `web.ServeSignupClosed` (v0.281.0, decision 47) | controller/internal/stacks/signup_block.go · controller/internal/web/setup_gate.go | `(name)` | Sign-up closed at the app's own address once the gate opens; the household's 15-minute window; the press asks the probe | **The block goes up BEFORE the gate comes down** (a failed write keeps the gate closed). Never on an app this box did not gate |
| `family.Store` · `stacks.FamilyGateHost` / `familyGateTick` / `FamilyExceptRegexp` · `web.ServeFamilyGateAuth` / `ServeFamilyStart` / `ServeFamilyLogin` / `ServeFamilyLogout` (v0.287.0, decisions 63/64) | controller/internal/family/family.go · controller/internal/stacks/family_gate.go · controller/internal/web/family_gate.go | store `Add/Reset/Remove/Verify/NewSession/Valid/EndSession` · `(host)` / `()` / `(prefix)` · handlers | The PERMANENT family gate: family members with their own logins in front of a `family_gate: true` app | **A family cookie never opens the dashboard** (RequireAuth reads only `felhom_session`); the app cookie names a STORE session, so the store is asked on every request (reset/remove/logout end access at once); **every exception goes through `FamilyExceptRegexp`** (anchored — never a hand-written PathPrefix); door written before the first start, like the setup gate. `familyStoreOverride` is the test seam. Fifth atomic-write helper (family.json, fsync) — see §6 |
| `stacks.OpenInstallHold` / `installHoldTick` (v0.284.0, R-741) | controller/internal/stacks/install_hold.go | `(name, by)` / `()` | An `after_install` app held behind the setup gate's door until its known login is replaced | **Written before the first start**, like the gate; opens on `after_install` success or the household's "I changed it"; the door (`SetupGateHost`) reads holds first |
| `stacks.RetainImagesAfterUpdate` / `RetainImagesAfterRemove` / `RunImageRetentionOnce` · seam `imageDocker` (v0.284.0, decision 53) | controller/internal/stacks/image_retention.go | `(name, previous)` / `(name, repos)` / `()` | Deletes an app's images older than its running + previous one | **The keep set is box-wide and read at delete time** (containers, installed composes, installed/previous records); exact id, never forced or pruned; skipped while any update runs; tests use the `imageDocker` seam, never Docker |
| `stacks.RetainControllerImages(ControllerImageRecord)` · `selfupdate.UpdateState.RecordedPrevious` (v0.285.0, decision 56) | controller/internal/stacks/controller_image_retention.go | `({Repo, Running, Previous})` | Deletes controller images older than the running + previous one | The previous comes from the SWAP RECORD (success onto the running version), version order only as the fallback; versions above the running one and non-version tags are kept; skipped while the controller swaps itself; same `imageDocker` seam |
+7
View File
@@ -1972,6 +1972,13 @@ that folder is never a dead end, and an install never runs into it silently (R-6
into app.yaml + one `compose up -d`, or a command) set when the gate opens, after the block. The window lifts and the
loop re-applies it. `POST /apps/<slug>/close-signup` for an app installed before the rule: lock record
(`opened_by: close-signup`), block, switch; never a gate. Code: `internal/stacks/after_setup.go`.
- **The family gate (v0.287.0, decisions 63/64)** — `.felhom.yml` `family_gate: true` puts a PERMANENT door in front of
the app: only the household's family members (each with their own name and password, „Család" card on the security
page) and the household itself get through; `family_gate_except:` lists literal path prefixes left to the app's own
login (anchored `^/prefix(/|$)`). Door file `family-gate-<app>.yml`, written before the first start; answerer
`/__felhom_gate/family`; sign-in pages `/__family/start|login|logout` on the dashboard host; sessions 30 days in
`family.json`. A family cookie never opens the dashboard. `min_controller:` refuses a template too new for the box.
Code: `internal/family/`, `internal/stacks/family_gate.go`, `internal/web/family_gate.go`.
- **A removed app restored from its backup gets its lock back (v0.286.0, R-773)** — with no app.yaml left, the restore
(`PersistUnitRedeployConfig`) writes the lock record (`opened_by: restore`) and the block BEFORE anything starts; the
loop sets the app's own switch. An installed app the household never closed keeps what it had (decision 49).