diff --git a/CONTEXT.md b/CONTEXT.md index 8425f9fd..38b2c5f1 100644 --- a/CONTEXT.md +++ b/CONTEXT.md @@ -16,6 +16,14 @@ > and holds nothing of its own; this file does hold its own content, namely the standing rulings below. +> **2026-10-02 — the family gate BUILT (R-780 CLOSED), controller v0.287.0 floored, golden 0.287.0 vouched; Grimmory and +> MeTube published behind it (R-767 CLOSED, R-775 narrowed).** `internal/family` + `stacks/family_gate.go` + +> `web/family_gate.go`; template fields `family_gate` / `family_gate_except` (literal, anchored `^/p(/|$)`) / +> `min_controller`; catalog gate `check-family-gate.py` (refuses a family app while the newest baked golden < 0.287.0 — +> why this golden was baked off-cadence). `01` §5 carries the design paragraph. Licence read of all 58 templates +> (R-787 CLOSED): `documentation/audits/licences-2026-10-02/TABLE.md`; operator rows R-789..R-795; SparkyFitness request +> drafted, not sent (R-784, decision 65). Register 422 → 435. Report: `REPORT-family-gate-2026-10-02.md`. + > **Rulings 2026-10-02 (morning, operator) — `09` §3 decisions 64, 65, recorded before the work.** **64 (R-780):** the > family gate — GO, both sessions. **65 (R-784 B):** SparkyFitness stays offered; the operator asks the author; no written > permission before the first paying customer → `lifecycle: hidden`. The licence read (R-787) runs this session. diff --git a/documentation/architecture/00-capability-map.md b/documentation/architecture/00-capability-map.md index a5064291..4fd6dc5f 100644 --- a/documentation/architecture/00-capability-map.md +++ b/documentation/architecture/00-capability-map.md @@ -192,7 +192,7 @@ likewise silent. Evidence: `audits/DRILL-r361-2026-08-22/evidence/06-part3-decis | Forgot dashboard password → instant reset code | controller v0.123, hub | **PROVEN-LIVE** | `DRILL-day0-take2-2026-07-12` F-15 (live re-run of the exact failure path: hash applied 1s after request, code accepted first try) | | | **The dashboard in English** — the household picks its language; Hungarian unchanged | controller v0.247.0 (spike) → v0.250.0 (slice 1, R-556 CLOSED) | **PARTIAL — PROVEN-LIVE for every dashboard template** (all 36 templates carry their copy in the bundles; the switch is on every dashboard page) | `audits/i18n-slice1-2026-09-17/C/live/` (and `A/`, `B/`, `audits/i18n-2026-09-17/live/`) — on demo-hp 0.250.0 each Hungarian page equals its 0.249.0 fetch once the new switch form is removed (live numbers aside; login, claim, catch-all byte-identical); `POST /settings/language` made every page English and back; the hub stored `hu`, `en`, `hu` (16:45:10Z, 16:46:06Z, 16:46:43Z). Unit: `TestI18nParity` (106 states vs fixtures from unconverted templates), `TestI18nParityCoversEveryMarker`, `TestI18nEnglishPages`, `TestDirectRenderHandlersFollowLanguage`, all red-proofed. Design `architecture/10-localisation.md` | Go-side messages and three app-name page titles (R-557 after R-553, R-566), hub e-mails (R-558), console/download page (R-559), catalog copy (R-560), guide + English stranger drill (R-561); ASCII-only Hungarian invisible to the English page test (R-565). **Hungarian households see the switch only once the fleet floor reaches 0.250.0** | | **The box tells visitors apart — a stranger's wrong passwords lock only the stranger** (dashboard, setup gate, apps that read the visitor from the right) | controller v0.286.1 (`felhom-tunnel`, traefik trust of cloudflared only, `clientaddr.go`), catalog router resets | **PARTIAL** | `audits/visitors-2026-10-01/A/L2-demo-hp-real-tunnel.txt` (real tunnel: the dashboard counter keyed on the visitor `37.191.56.193`, locked after 5 while forging a new X-Forwarded-For each try); `A/L1-9202-live.txt` (simulated tunnel: the household from another address in at once); `A/bookstack-3.6.txt` | The second OUTSIDE address on the real tunnel was refused by Cloudflare before the box → R-779; right-walking app settings → R-776; Emby/Jellyfin LAN rights → R-777 | -| A permanent household gate with family accounts in front of an app (Grimmory, MeTube) | spike only | **MISSING** (spiked, passed) | `audits/permanent-gate-2026-10-01/VERDICT.md` | → R-780 (operator go/no-go) | +| **The family gate — a permanent door with each family member's own login in front of an app** (Grimmory with its e-reader exceptions, MeTube with none) | controller v0.287.0 (`internal/family`, `stacks/family_gate.go`, the Család card), catalog `family_gate:` + `check-family-gate.py`, golden 0.287.0 | **PROVEN** on 9202 through the product: a stranger reached nothing (36 requests, LAN + simulated tunnel), members with their own logins, reset/remove/logout end access at the next request, a stranger's guesses lock only the stranger, each exception keeps the app's own login and a look-alike stays gated, the controller down → error never the app; survives an update and a remove + restore. Demo boxes on 0.287.0. Not yet: a household's phone through the real internet. | `audits/family-gate-2026-10-02/A/items.txt`, `B/box/` | R-780 closed; R-775 (narrowed), R-796, R-797 | | Multiple household users / per-person accounts | — | **MISSING** | — | Single dashboard password; acceptable for alpha → R-15 | | WireGuard base infra always-on; OOB operator access (felhom-sshd, /32 peer) | agent v0.72, hub v0.35 | **IMPLEMENTED** | `SPIKE-oob-wg-operator-peer-2026-07-05`, `SPIKE-felhom-sshd-2026-07-05` | Mutual-repair desired-state arc not built → R-13. **CHECKED 2026-08-08 (R-260) and this row was NOT claiming something untrue** — it claims the capability is implemented, never that it is monitored, so no correction was owed. What WAS untrue is narrower and sat one layer down: **the hub's own OOB health check could not see whether the operator's key was installed.** `HostOOBRow` mirrored five of the agent's eight OOB fields, so `operator_key_configured` — emitted every heartbeat since agent v0.72.0, i.e. from this row's own vintage — was discarded by `encoding/json` on arrival, and `oobDegraded` returned `ok` for a box with felhom-sshd active, reachable, a valid config, a configured peer and **no operator key at all**. `operator_peer_configured`, which it did read, only says the peer IP is in desired-state — that OOB is MEANT to work, not that entry is possible. Fixed hub v0.99.0; the missing key now degrades and the alert NAMES it; a stanza too old to carry the field is reported distinctly and is never a silent ok. Pinned end-to-end from raw report JSON by `TestHostOOB_MissingOperatorKey_EndToEnd` and `TestHostOOB_NoKeyField_IsNotSilentlyOK_EndToEnd` | | The operator can see WHERE a managed host is — its LAN address and its WireGuard address, on the host page | agent **v0.119.0**, hub **v0.85.0** | **PROVEN-LIVE** (2026-07-31) | `audits/host-addresses-visible-2026-07-31.md` | Before this the LAN IP was **not reportable at all** — `HostMetrics` carried no address of any kind — and the WG IP existed only in `/offsite`'s peer table keyed by pubkey (peer→host, never host→peer). New wire field `addresses[]`, one row per (interface, address); `IsGlobalUnicast()` is the whole filter, chosen by MEASURING both demo boxes, and it needs no veth/fwbr denylist because that plumbing carries no IP. Rendered live on both 0.119.0 hosts matching their `ip addr` ground truth exactly. **Two honesty properties carry the risk and are both red-proofed:** WireGuard shows the hub ALLOCATION and whether the box CONFIRMS holding it (allocation alone cannot tell a live tunnel from a peer never applied), and an agent below 0.119.0 renders **UNKNOWN, never "no addresses"** — proven live on `drill-r50-0a4f9a` (0.113.0). **Not covered:** a two-LAN-bridge box and a real WG drift, neither of which exists to observe | diff --git a/documentation/architecture/01-topology-and-trust.md b/documentation/architecture/01-topology-and-trust.md index f72b6589..26ad9b20 100644 --- a/documentation/architecture/01-topology-and-trust.md +++ b/documentation/architecture/01-topology-and-trust.md @@ -136,8 +136,8 @@ the visitor from the RIGHT.** The controller (`clientaddr.go`) believes the chai traefik saw, and for the tunnel's hop reads `CF-Connecting-IP` (the edge refuses a client-sent one). Catalog apps that read the LEFTMOST entry have the chain removed on their router. Design and measurements: `audits/visitors-2026-10-01/A/DESIGN.md`. A permanent household gate with family accounts in front of an app (Grimmory, -MeTube) was SPIKED on this base and passed (`audits/permanent-gate-2026-10-01/VERDICT.md`); it is not built — the -operator decides. +MeTube) was SPIKED on this base and passed (`audits/permanent-gate-2026-10-01/VERDICT.md`) and is BUILT since +controller 0.287.0 (`09` §3 decision 64) — the family gate, at the end of this section. **Who may reach an app, and through what (recorded 2026-09-29 — no document said it before; spike finding F1).** Every app is reached only through the box's traefik (no catalog app publishes a host port except crafty-controller's @@ -159,6 +159,22 @@ Since controller 0.282.0 there are two locks where the app has its own switch: t decision 47 gets both only when the household presses "Close sign-up now" (decision 49); wanderer, which is not gated, the same way. +**The family gate (`09` §3 decisions 63/64, controller ≥ 0.287.0).** An app whose template says `family_gate: true` is +PERMANENTLY behind a forwardAuth door, the setup gate's mechanism with three differences: it never opens; its priority is +below the install hold, the setup gate and the sign-up block; and the cookie it accepts is a FAMILY cookie. Each family +member has their own name and password (the dashboard's Biztonság → Család card adds, resets and removes them; the +password is shown once, stored as a bcrypt hash in the controller's data). A member signs in on the dashboard host's +`/__family/login`; that sets a family session cookie on `Path=/__family` only — **never accepted by the dashboard's own +RequireAuth** — and each app host gets a host-only cookie through a 60-second one-use token, as the setup gate does. The +session store is asked on EVERY request, so a reset, a removal or a logout ends access at the next request. The family +sign-in locks per real visitor (5 a minute) and per name (10 in 10 minutes) — never "everyone" (it reads the visitor +from the right, as above). `family_gate_except:` lists LITERAL path prefixes a phone or e-reader app calls (Grimmory's +OPDS, Kobo, KOReader, Komga API); each becomes a router without the door, anchored at a segment boundary +(`^/prefix(/|$)`), so the app's OWN login decides there and a look-alike (`/api/v1/opdsx`) stays behind the gate. The +gate is recorded at install (and at a removed app's restore), so a catalog change never gates or un-gates an installed +app. Controller down → the gated app answers an error, never the app. Measured on 9202: +`audits/family-gate-2026-10-02/A/items.txt`. + --- ## 6. Enrollment & identity diff --git a/documentation/architecture/09-update-architecture.md b/documentation/architecture/09-update-architecture.md index 89f46c50..05ed38fd 100644 --- a/documentation/architecture/09-update-architecture.md +++ b/documentation/architecture/09-update-architecture.md @@ -693,7 +693,9 @@ its length, and both fixes cost something the household would notice — operato sessions (controller, then catalog: Grimmory and MeTube published behind it) — *operator ruling 2026-10-02 (R-780).* The family login never opens the dashboard; every exception is anchored (finding F1) and still needs the app's own login. **Why:** the spike passed every exit item; one gate the box already runs answers Grimmory's lock (R-775) and - MeTube's "no login at all" (R-767) at once. + MeTube's "no login at all" (R-767) at once. **Built 2026-10-02:** controller v0.287.0 (floored, golden 0.287.0), + catalog fields `family_gate` / `family_gate_except` / `min_controller` and gate `check-family-gate.py`; Grimmory and + MeTube published — `audits/family-gate-2026-10-02/`. 65. **SparkyFitness stays offered (R-784, option B); the operator asks its author for written permission now; if there is no written permission before the first paying customer, it is hidden** (`lifecycle: hidden`) — *operator ruling 2026-10-02.* **Why, recorded:** the operator's model is that Felhom sells installation and care, not software; that diff --git a/documentation/audits/family-gate-2026-10-02/A/RP-F-family-gate-mutants.txt b/documentation/audits/family-gate-2026-10-02/A/RP-F-family-gate-mutants.txt new file mode 100644 index 00000000..49bdd0a3 --- /dev/null +++ b/documentation/audits/family-gate-2026-10-02/A/RP-F-family-gate-mutants.txt @@ -0,0 +1,58 @@ +## RP-F1 RequireAuth accepts a valid FAMILY session (2026-10-02T05:32:27Z) — TestFamilyGate_MemberPassesButNeverTheDashboard +=== RUN TestFamilyGate_MemberPassesButNeverTheDashboard + family_gate_test.go:188: a family cookie opened the dashboard at /launcher +--- FAIL: TestFamilyGate_MemberPassesButNeverTheDashboard (0.11s) +FAIL +FAIL gitea.dooplex.hu/admin/felhom-controller/internal/web 0.125s +FAIL + +## RP-F2 exceptions not anchored (plain prefix) (2026-10-02T05:32:30Z) — TestFamilyExceptRegexp_Anchored +=== RUN TestFamilyExceptRegexp_Anchored + family_gate_test.go:33: "/api/v1/opdsx": match=true want false (regexp ^/api/v1/opds) + family_gate_test.go:33: "/api/v1/opds-evil": match=true want false (regexp ^/api/v1/opds) + family_gate_test.go:33: "/api/v1/opds.json": match=true want false (regexp ^/api/v1/opds) + family_gate_test.go:37: a trailing slash is the same prefix: ^/api/kobo +--- FAIL: TestFamilyExceptRegexp_Anchored (0.00s) +FAIL +FAIL gitea.dooplex.hu/admin/felhom-controller/internal/stacks 0.008s + +## RP-F3 the app cookie is trusted on its MAC alone (the store is not asked) (2026-10-02T05:32:35Z) — TestFamilyGate_ResetRemoveLogoutEndAccessAtOnce +=== RUN TestFamilyGate_ResetRemoveLogoutEndAccessAtOnce + family_gate_test.go:212: a reset password must end the member's app access at once +--- FAIL: TestFamilyGate_ResetRemoveLogoutEndAccessAtOnce (0.12s) +FAIL +FAIL gitea.dooplex.hu/admin/felhom-controller/internal/web 0.129s +FAIL + +## RP-F4 the sign-in counted by NAME only (no visitor counter) (2026-10-02T05:32:40Z) — TestFamilyGate_LockPerVisitorAndPerName +=== RUN TestFamilyGate_LockPerVisitorAndPerName + family_gate_test.go:242: the stranger's own address must be locked even with the right password: 200 +--- FAIL: TestFamilyGate_LockPerVisitorAndPerName (0.12s) +FAIL +FAIL gitea.dooplex.hu/admin/felhom-controller/internal/web 0.130s +FAIL + +## RP-F5 one global counter for every visitor (the pre-R-753 'everyone' shape) (2026-10-02T05:32:45Z) — TestFamilyGate_LockPerVisitorAndPerName +=== RUN TestFamilyGate_LockPerVisitorAndPerName + family_gate_test.go:245: anna from her own address must get in at once: 429 +--- FAIL: TestFamilyGate_LockPerVisitorAndPerName (0.07s) +FAIL +FAIL gitea.dooplex.hu/admin/felhom-controller/internal/web 0.079s +FAIL + +## RP-F6 the install does not write the family door (2026-10-02T05:32:48Z) — TestFamilyGate_WrittenBeforeTheFirstStart +=== RUN TestFamilyGate_WrittenBeforeTheFirstStart + family_gate_test.go:72: the family-gate file did not exist when the app was first started — it was published open +--- FAIL: TestFamilyGate_WrittenBeforeTheFirstStart (0.01s) +FAIL +FAIL gitea.dooplex.hu/admin/felhom-controller/internal/stacks 0.018s +FAIL + +## RP-F7 a removed family app's restore gets no door (2026-10-02T05:32:52Z) — TestFamilyGate_RestoreOfARemovedAppAndTheLoop +=== RUN TestFamilyGate_RestoreOfARemovedAppAndTheLoop + family_gate_test.go:135: the restore of a removed family app must record its door +--- FAIL: TestFamilyGate_RestoreOfARemovedAppAndTheLoop (0.00s) +FAIL +FAIL gitea.dooplex.hu/admin/felhom-controller/internal/stacks 0.010s +FAIL + diff --git a/documentation/audits/family-gate-2026-10-02/A/items.txt b/documentation/audits/family-gate-2026-10-02/A/items.txt new file mode 100644 index 00000000..447be223 --- /dev/null +++ b/documentation/audits/family-gate-2026-10-02/A/items.txt @@ -0,0 +1,71 @@ +##### exit items through the product — 2026-10-02T05:53:44Z +## item 1 — a STRANGER (no cookie), LAN and simulated tunnel + library GET / LAN 302 GATE | tunnel 302 GATE + library GET / LAN 401 GATE | tunnel 401 GATE + library GET /api/v1/books LAN 401 GATE | tunnel 401 GATE + library GET /api/v1/healthcheck LAN 401 GATE | tunnel 401 GATE + library POST /api/v1/setup LAN 401 GATE | tunnel 401 GATE + library POST /api/v1/auth/login LAN 401 GATE | tunnel 401 GATE + library GET /ws/websocket LAN 401 GATE | tunnel 401 GATE + library GET /api/v1/opdsx LAN 401 GATE | tunnel 401 GATE + library GET /api/v1/opds-evil LAN 401 GATE | tunnel 401 GATE + library GET /api/v1/opds/../../api/v1/books LAN 401 GATE | tunnel 401 GATE + library GET /no-such-page LAN 302 GATE | tunnel 302 GATE + video GET / LAN 302 GATE | tunnel 302 GATE + video GET / LAN 401 GATE | tunnel 401 GATE + video GET /history LAN 401 GATE | tunnel 401 GATE + video POST /add LAN 401 GATE | tunnel 401 GATE + video GET /socket.io/?EIO=4&transport=polling LAN 401 GATE | tunnel 401 GATE + video GET /socket.io/?EIO=4&transport=websocket LAN 401 GATE | tunnel 401 GATE + video GET /download/x.mp4 LAN 401 GATE | tunnel 401 GATE +item 1: 0 app answers of 36 -> PASS +## item 2 — family members, their OWN logins, through the product's sign-in page + anna signs in on felhom.enkisfelhom.hu/__family/login: 200; felhom_family set; Max-Age 30 d; Path=/__family; dashboard cookie set: False + member GET library/ -> 200 APP + member GET library/api/v1/healthcheck -> 200 APP + member GET video/ -> 200 APP + member GET video/socket.io/?EIO=4&transport=polling -> 200 APP + member GET video/history -> 200 APP + bela MeTube websocket upgrade -> 101 APP ['Upgrade: websocket'] + bela's MeTube cookie at Grimmory -> 302 GATE + after the CONTROLLER restarted, anna's Grimmory cookie -> 200 APP (survived) + anna logs out (200) -> her Grimmory cookie 302 GATE (refused) +## item 3 — a stranger's guesses at the family sign-in lock only the stranger (simulated tunnel) + stranger 198.51.100.66, 7 wrong for anna: ['401', '401', '401', '401', '401', '429', '429'] + stranger with anna's RIGHT password while locked: 429 + anna herself from 203.0.113.10 (tunnel), at once: 200, session set + bela from the LAN, at once: 200, session set + controller log: 2026/10/02 05:54:55 family_gate.go:367: [WARN] [web] family sign-in failed — visitor 198.51.100.66 +2026/10/02 05:54:56 family_gate.go:360: [WARN] [web] family sign-in: too many wrong tries — visitor 198.51.100.66, name "anna" +2026/10/02 05:54:58 family_gate.go:360: [WARN] [web] family sign-in: too many wrong tries — visitor 198.51.100.66, name "anna" +2026/10/02 05:54:59 family_gate.go:360: [WARN] [web] family sign-in: too many wrong tries — visitor 198.51.100.66, name "anna" +2026/10/02 05:55:01 family_gate.go:380: [INFO] [web] family sign-in: anna — visitor 203.0.113.10 +2026/10/02 05:55:01 family_gate.go:380: [INFO] [web] family sign-in: bela — visitor 192.168.0.180 + +## item 4 — Grimmory's e-reader exceptions keep Grimmory's OWN login (no family cookie) + (setup as the household through the gate: OPDS on + an OPDS user, a Kobo token, a KOReader user — values not printed) + OPDS, the OPDS user's own login LAN -> 200 app '\n\n 401 app '{"timestamp":"2026-10-02T05:55:12.892Z","status":401,"error"' + KOReader sign-in, its own user + md5 key LAN -> 200 app '{"username":"korolvaso"}' + KOReader, a stranger's wrong key tunnel -> 401 app '{"timestamp":"2026-10-02T05:55:14.437Z","status":401,"error"' + Komga API, a stranger with nothing tunnel -> 401 app 'HTTP Status 401 - Full authentication is required to access' + look-alike /api/v1/opdsx (F1) tunnel -> 401 GATE '{"error":"sign in with your family login"}' + look-alike /api/koreaderx tunnel -> 401 GATE '{"error":"sign in with your family login"}' + ../ out of an exception tunnel -> 401 GATE '{"error":"sign in with your family login"}' +## R-775 re-measured — a stranger cannot reach Grimmory's web sign-in at all, so its 15-minute lock cannot be aimed from outside + stranger x6 at /api/v1/auth/login (tunnel): ['401', '401', '401', '401', '401', '401'] -> the household then signs in: 200 +## item 5 — the family login and the box dashboard + family cookies at /launcher -> 302 /login?next=%2Flauncher | at /api/stacks -> 401 | anna's family password at the dashboard login -> 200, session none +## reset and removal end access at the next request (every gated app) + card: new password for bela -> ok=True; bela's Grimmory cookie -> 302, MeTube cookie -> 302 (302 = sent to sign in) + card: remove bela -> ok=True members=['anna']; bela's MeTube cookie -> 401; bela signs in again -> 401 +## cost — the same request through the gated name, and straight at the container (60 pairs) + gated (LAN→traefik→forwardAuth→app) median 9.4 ms, p90 10.5 ms; the app straight (guest→container) median 1.0 ms — the honest number is the gate's own: see the controller's forwardAuth timing below +## the controller DOWN — a gated app answers an error, never the app; an exception still reaches the app's own login + controller stopped: MeTube with a member cookie -> 500 ''; Grimmory API -> 500; Grimmory OPDS exception (wrong password) -> 401 (the app's own refusal) + controller back: MeTube with bela's earlier cookie (bela was removed) -> 401 GATE +## cost, the gate's own answer: 100 forwardAuth calls from inside the guest straight to the controller with a member cookie — codes {'200'}, median 0.49 ms, p90 0.55 ms, max 0.64 ms (2026-10-02T05:56:36Z) diff --git a/documentation/audits/family-gate-2026-10-02/A/repoint-drill.txt b/documentation/audits/family-gate-2026-10-02/A/repoint-drill.txt new file mode 100644 index 00000000..2cf6c819 --- /dev/null +++ b/documentation/audits/family-gate-2026-10-02/A/repoint-drill.txt @@ -0,0 +1,12 @@ +git: + branch: main + repo_url: https://gitea.dooplex.hu/admin/app-catalog-drill.git + sync_interval: 15m + token: + username: "admin" +hub: + +CONTROL 1 — the box's clone: c3f433f DRILL: grimmory + metube behind the family gate (0.287.0 proof on 9202) +https://gitea.dooplex.hu/admin/app-catalog-drill.git +CONTROL 2 — live main: ['de0a9bd29fb0e2e42e3631f13ad84268eeccbab5'] +CONTROL 3 — expected: drill diff --git a/documentation/audits/family-gate-2026-10-02/A/setup.txt b/documentation/audits/family-gate-2026-10-02/A/setup.txt new file mode 100644 index 00000000..7c125972 --- /dev/null +++ b/documentation/audits/family-gate-2026-10-02/A/setup.txt @@ -0,0 +1,10 @@ +##### family gate live setup on 9202 — controller gitea.dooplex.hu/admin/felhom-controller:0.287.0 | 2026-10-02T05:50:41Z +Család card: add anna -> ok=True members=['anna'] password returned ONCE (len 19, not printed) +Család card: add bela -> ok=True members=['anna', 'bela'] password returned ONCE (len 19, not printed) +the security page carries the card: True | and no member password: True +deploy metube -> True + metube family-gate file: family-gate-metube.yml +deploy grimmory -> True + grimmory family-gate file: family-gate-grimmory.yml setup-gate-grimmory.yml +grimmory: the household makes the first admin THROUGH THE SETUP GATE -> 200 +grimmory: setup gate opened by its probe after ~10 s; files now: family-gate-grimmory.yml diff --git a/documentation/audits/family-gate-2026-10-02/B/RP-R801-routed-ports.txt b/documentation/audits/family-gate-2026-10-02/B/RP-R801-routed-ports.txt new file mode 100644 index 00000000..90278c15 --- /dev/null +++ b/documentation/audits/family-gate-2026-10-02/B/RP-R801-routed-ports.txt @@ -0,0 +1,16 @@ +## RP-R801 mutant: the old rule (dict labels read by VALUE only) — 2026-10-02T07:22:42Z + +Second list contains 1 additional elements. +First extra element 0: +8081 + +- [] ++ [8081] + +---------------------------------------------------------------------- +Ran 3 tests in 0.001s + +FAILED (failures=1) +## restored: + +OK diff --git a/documentation/audits/family-gate-2026-10-02/B/assets.txt b/documentation/audits/family-gate-2026-10-02/B/assets.txt new file mode 100644 index 00000000..fb14b71e --- /dev/null +++ b/documentation/audits/family-gate-2026-10-02/B/assets.txt @@ -0,0 +1,12 @@ +# B — assets for grimmory and metube, 2026-10-02T06:51:34Z +https://felhom.eu/assets/grimmory-logo.svg -> 200 1827B +https://felhom.eu/assets/grimmory-screenshot-1.webp -> 200 23696B +https://felhom.eu/assets/grimmory-screenshot-2.webp -> 200 20458B +https://felhom.eu/assets/grimmory-screenshot-3.webp -> 200 25850B +https://felhom.eu/assets/metube-logo.svg -> 200 1147B +https://felhom.eu/assets/metube-screenshot-1.webp -> 200 27634B +https://felhom.eu/assets/metube-screenshot-2.webp -> 200 32112B +https://felhom.eu/assets/metube-screenshot-3.webp -> 200 25488B +https://felhom.eu/assets/nosuchapp-logo.svg -> 404 153B (negative control) +logos: grimmory = static/assets/favicon.svg inside its image's app.jar (AGPL-3.0); metube = /app/ui/dist/metube/browser/assets/icons/safari-pinned-tab.svg inside ghcr.io/alexta69/metube (AGPL-3.0). screenshots 1-2: each app's own UI on 9202 (headless Chrome, tools/shots/); 3: the family sign-in page a visitor meets at the app's address (tools/shots/familylogin.js). felhom.eu commit 344081f; boxes get them with the next hub release (R-766). +8.5: the website's claim — Több mint 45 alkalmazásból Több mint 45 alkalmazást — templates after this publish: 58 directories, of which offered (lifecycle available): 56; the claim holds diff --git a/documentation/audits/family-gate-2026-10-02/B/bench-gates-create.txt b/documentation/audits/family-gate-2026-10-02/B/bench-gates-create.txt new file mode 100644 index 00000000..d157238e --- /dev/null +++ b/documentation/audits/family-gate-2026-10-02/B/bench-gates-create.txt @@ -0,0 +1,59 @@ ++ date -u +Fri Oct 2 07:01:33 UTC 2026 ++ pct list +VMID Status Lock Name +9201 running demo-hp +9202 running demo-hp-scratch ++ free -g ++ head -2 + total used free shared buff/cache available +Mem: 29 5 9 0 15 23 ++ pvesm status +Name Type Status Total (KiB) Used (KiB) Available (KiB) % +felhom-pbs pbs active 0 0 0 0.00% +local dir active 40453376 19527780 18838480 48.27% +local-lvm lvmthin active 56487936 34943437 21544498 61.86% +nvme-scratch dir active 983379700 77369600 855983488 7.87% ++ pct config 9401 ++ head -1 +Configuration file 'nodes/demo-hp/lxc/9401.conf' does not exist ++ pveam list local ++ grep debian-13 +local:vztmpl/debian-13-standard_13.6-1_amd64.tar.zst 123.93MB ++ pct create 9401 local:vztmpl/debian-13-standard_13.6-1_amd64.tar.zst --hostname upgrade-harness --cores 6 --memory 10240 --swap 0 --rootfs nvme-scratch:60 --net0 name=eth0,bridge=vmbr0,ip=dhcp --unprivileged 1 --features nesting=1,keyctl=1 --onboot 0 ++ tail -1 +done: SHA256:PyXdDIPUlAxyiDpveARaXo3kIwoxgwUdOjbhnfVXMdY root@upgrade-harness ++ pct config 9401 +arch: amd64 +cores: 6 +features: nesting=1,keyctl=1 +hostname: upgrade-harness +memory: 10240 +net0: name=eth0,bridge=vmbr0,hwaddr=BC:24:11:50:88:EF,ip=dhcp,type=veth +onboot: 0 +ostype: debian +rootfs: nvme-scratch:9401/vm-9401-disk-0.raw,size=60G +swap: 0 +unprivileged: 1 ++ pct start 9401 ++ sleep 15 ++ pct exec 9401 -- bash -c 'dpkg --print-architecture; hostname; ip -4 -o addr show eth0 | awk "{print \$4}"; apt-get update -qq >/dev/null 2>&1; DEBIAN_FRONTEND=noninteractive apt-get install -y -qq docker.io docker-compose python3 python3-yaml curl postgresql-client sqlite3 git >/dev/null 2>&1; docker --version; docker compose version; python3 --version; git --version; docker network create traefik-public >/dev/null && echo traefik-public-net; swapon --show; free -m | head -3; df -h /' +amd64 +upgrade-harness +192.168.0.147/24 +Docker version 26.1.5+dfsg1, build a72d7cd +Docker Compose version 2.26.1-4 +Python 3.13.5 +git version 2.47.3 +traefik-public-net + total used free shared buff/cache available +Mem: 10240 60 8502 0 1677 10179 +Swap: 0 0 0 +Filesystem Size Used Avail Use% Mounted on +/dev/loop2 59G 1.3G 55G 3% / ++ pvesm status +Name Type Status Total (KiB) Used (KiB) Available (KiB) % +felhom-pbs pbs active 0 0 0 0.00% +local dir active 40453376 19527876 18838384 48.27% +local-lvm lvmthin active 56487936 34943437 21544498 61.86% +nvme-scratch dir active 983379700 78992412 854360676 8.03% diff --git a/documentation/audits/family-gate-2026-10-02/B/bench-gates-teardown.txt b/documentation/audits/family-gate-2026-10-02/B/bench-gates-teardown.txt new file mode 100644 index 00000000..35de9d83 --- /dev/null +++ b/documentation/audits/family-gate-2026-10-02/B/bench-gates-teardown.txt @@ -0,0 +1,41 @@ ++ date -u +Fri Oct 2 07:11:46 UTC 2026 ++ pct list +VMID Status Lock Name +9201 running demo-hp +9202 running demo-hp-scratch +9401 running upgrade-harness ++ pvesm status +Name Type Status Total (KiB) Used (KiB) Available (KiB) % +felhom-pbs pbs active 0 0 0 0.00% +local dir active 40453376 19527920 18838340 48.27% +local-lvm lvmthin active 56487936 34943437 21544498 61.86% +nvme-scratch dir active 983379700 81073172 852279916 8.24% ++ pct exec 9401 -- bash -c 'docker ps -a --format {{.Names}} | wc -l; docker volume ls -q | wc -l' +0 +0 ++ pct stop 9401 ++ pct destroy 9401 --purge +purging CT 9401 from related configurations.. ++ pct list +VMID Status Lock Name +9201 running demo-hp +9202 running demo-hp-scratch ++ pct config 9401 ++ head -1 +Configuration file 'nodes/demo-hp/lxc/9401.conf' does not exist ++ ls /mnt/hdd_1/images/ +9202 ++ pvesm status +Name Type Status Total (KiB) Used (KiB) Available (KiB) % +felhom-pbs pbs active 0 0 0 0.00% +local dir active 40453376 19527852 18838408 48.27% +local-lvm lvmthin active 56487936 34943437 21544498 61.86% +nvme-scratch dir active 983379700 77369600 855983488 7.87% ++ rm -rf /tmp/b9401 ++ ls /tmp ++ grep -c b9401 +1 +teardown-script-removed +0 +# note: the in-script "1" is the teardown script itself (/tmp/teardown-b9401.sh matches b9401); after it was removed the count is 0. nvme-scratch Used back to 77369600 KiB = the pre-create baseline; local-lvm unchanged (34943437) throughout. Nothing committed or pushed. diff --git a/documentation/audits/family-gate-2026-10-02/B/bench-gates2-create.txt b/documentation/audits/family-gate-2026-10-02/B/bench-gates2-create.txt new file mode 100644 index 00000000..9f6a06bf --- /dev/null +++ b/documentation/audits/family-gate-2026-10-02/B/bench-gates2-create.txt @@ -0,0 +1,65 @@ ++ date -u +Fri Oct 2 07:13:17 UTC 2026 ++ pct list +VMID Status Lock Name +9201 running demo-hp +9202 running demo-hp-scratch ++ head -2 ++ free -g + total used free shared buff/cache available +Mem: 29 5 10 0 14 23 ++ pvesm status +Name Type Status Total (KiB) Used (KiB) Available (KiB) % +felhom-pbs pbs active 0 0 0 0.00% +local dir active 40453376 19527860 18838400 48.27% +local-lvm lvmthin active 56487936 34943437 21544498 61.86% +nvme-scratch dir active 983379700 77369600 855983488 7.87% ++ pct config 9401 ++ head -1 +Configuration file 'nodes/demo-hp/lxc/9401.conf' does not exist ++ pveam list local ++ grep debian-13 +local:vztmpl/debian-13-standard_13.6-1_amd64.tar.zst 123.93MB ++ pct create 9401 local:vztmpl/debian-13-standard_13.6-1_amd64.tar.zst --hostname upgrade-harness --cores 6 --memory 10240 --swap 0 --rootfs nvme-scratch:60 --net0 name=eth0,bridge=vmbr0,ip=dhcp --unprivileged 1 --features nesting=1,keyctl=1 --onboot 0 ++ tail -1 +done: SHA256:Eby8n+SmD/BjZN7bKvhPpaTpq5y5Ws7L+WO6Da40zOk root@upgrade-harness ++ pct config 9401 +arch: amd64 +cores: 6 +features: nesting=1,keyctl=1 +hostname: upgrade-harness +memory: 10240 +net0: name=eth0,bridge=vmbr0,hwaddr=BC:24:11:E7:95:BA,ip=dhcp,type=veth +onboot: 0 +ostype: debian +rootfs: nvme-scratch:9401/vm-9401-disk-0.raw,size=60G +swap: 0 +unprivileged: 1 ++ pct start 9401 ++ sleep 15 ++ pct exec 9401 -- bash -c 'dpkg --print-architecture; hostname; ip -4 -o addr show eth0 | awk "{print \$4}"; apt-get update -qq >/dev/null 2>&1; DEBIAN_FRONTEND=noninteractive apt-get install -y -qq docker.io docker-compose python3 python3-yaml curl postgresql-client sqlite3 git >/dev/null 2>&1; docker --version; docker compose version; python3 --version; git --version; docker network create traefik-public >/dev/null && echo traefik-public-net; swapon --show; free -m | head -3; df -h /' +amd64 +upgrade-harness +192.168.0.116/24 +Docker version 26.1.5+dfsg1, build a72d7cd +Docker Compose version 2.26.1-4 +Python 3.13.5 +git version 2.47.3 +traefik-public-net + total used free shared buff/cache available +Mem: 10240 58 8531 0 1649 10181 +Swap: 0 0 0 +Filesystem Size Used Avail Use% Mounted on +/dev/loop2 59G 1.3G 55G 3% / ++ pvesm status +Name Type Status Total (KiB) Used (KiB) Available (KiB) % +felhom-pbs pbs active 0 0 0 0.00% +local dir active 40453376 19527956 18838304 48.27% +local-lvm lvmthin active 56487936 34943437 21544498 61.86% +nvme-scratch dir active 983379700 78965928 854387160 8.03% +felhom.eu/documentation/audits/family-gate-2026-10-02/B/catalog-gates-grimmory.txt +felhom.eu/documentation/audits/family-gate-2026-10-02/B/catalog-gates-metube-run1.txt +felhom.eu/documentation/audits/family-gate-2026-10-02/B/catalog-gates-metube.txt +de0a9bd SparkyFitness onboarding record (Part C: bench + 9202 measured, six rows open — R-786; licence non-commercial — R-784); CHANGELOG/CONTEXT/REPORT for the R-753 catalog work +16 +golden-0.287.0-2026-10-02 diff --git a/documentation/audits/family-gate-2026-10-02/B/bench-gates2-teardown.txt b/documentation/audits/family-gate-2026-10-02/B/bench-gates2-teardown.txt new file mode 100644 index 00000000..b5cdab3b --- /dev/null +++ b/documentation/audits/family-gate-2026-10-02/B/bench-gates2-teardown.txt @@ -0,0 +1,37 @@ ++ date -u +Fri Oct 2 07:21:59 UTC 2026 ++ pct list +VMID Status Lock Name +9201 running demo-hp +9202 running demo-hp-scratch +9401 running upgrade-harness ++ pvesm status +Name Type Status Total (KiB) Used (KiB) Available (KiB) % +felhom-pbs pbs active 0 0 0 0.00% +local dir active 40453376 19527996 18838264 48.27% +local-lvm lvmthin active 56487936 34943437 21544498 61.86% +nvme-scratch dir active 983379700 80106504 853246584 8.15% ++ pct exec 9401 -- bash -c 'docker ps -a --format {{.Names}} | wc -l; docker volume ls -q | wc -l' +0 +0 ++ pct stop 9401 ++ pct destroy 9401 --purge +purging CT 9401 from related configurations.. ++ pct list +VMID Status Lock Name +9201 running demo-hp +9202 running demo-hp-scratch ++ pct config 9401 ++ head -1 +Configuration file 'nodes/demo-hp/lxc/9401.conf' does not exist ++ ls /mnt/hdd_1/images/ +9202 ++ pvesm status +Name Type Status Total (KiB) Used (KiB) Available (KiB) % +felhom-pbs pbs active 0 0 0 0.00% +local dir active 40453376 19527924 18838336 48.27% +local-lvm lvmthin active 56487936 34943437 21544498 61.86% +nvme-scratch dir active 983379700 77369600 855983488 7.87% ++ rm -rf /tmp/b9401 +tmp-leftovers=0 +# nothing committed or pushed; evidence copied to DooPlex before the destroy diff --git a/documentation/audits/family-gate-2026-10-02/B/bench-gates3-create.txt b/documentation/audits/family-gate-2026-10-02/B/bench-gates3-create.txt new file mode 100644 index 00000000..1a621830 --- /dev/null +++ b/documentation/audits/family-gate-2026-10-02/B/bench-gates3-create.txt @@ -0,0 +1,67 @@ ++ date -u +Fri Oct 2 07:23:12 UTC 2026 ++ pct list +VMID Status Lock Name +9201 running demo-hp +9202 running demo-hp-scratch ++ free -g ++ head -2 + total used free shared buff/cache available +Mem: 29 5 10 0 14 23 ++ pvesm status +Name Type Status Total (KiB) Used (KiB) Available (KiB) % +felhom-pbs pbs active 0 0 0 0.00% +local dir active 40453376 19527928 18838332 48.27% +local-lvm lvmthin active 56487936 34943437 21544498 61.86% +nvme-scratch dir active 983379700 77369600 855983488 7.87% ++ pct config 9401 ++ head -1 +Configuration file 'nodes/demo-hp/lxc/9401.conf' does not exist ++ pveam list local ++ grep debian-13 +local:vztmpl/debian-13-standard_13.6-1_amd64.tar.zst 123.93MB ++ pct create 9401 local:vztmpl/debian-13-standard_13.6-1_amd64.tar.zst --hostname upgrade-harness --cores 6 --memory 10240 --swap 0 --rootfs nvme-scratch:60 --net0 name=eth0,bridge=vmbr0,ip=dhcp --unprivileged 1 --features nesting=1,keyctl=1 --onboot 0 ++ tail -1 +done: SHA256:sD6AEj3m9KyUfvaqKMuaAkJCHSV8RV+g6DFQJDvvPnY root@upgrade-harness ++ pct config 9401 +arch: amd64 +cores: 6 +features: nesting=1,keyctl=1 +hostname: upgrade-harness +memory: 10240 +net0: name=eth0,bridge=vmbr0,hwaddr=BC:24:11:AF:A0:95,ip=dhcp,type=veth +onboot: 0 +ostype: debian +rootfs: nvme-scratch:9401/vm-9401-disk-0.raw,size=60G +swap: 0 +unprivileged: 1 ++ pct start 9401 ++ sleep 15 ++ pct exec 9401 -- bash -c 'dpkg --print-architecture; hostname; ip -4 -o addr show eth0 | awk "{print \$4}"; apt-get update -qq >/dev/null 2>&1; DEBIAN_FRONTEND=noninteractive apt-get install -y -qq docker.io docker-compose python3 python3-yaml curl postgresql-client sqlite3 git >/dev/null 2>&1; docker --version; docker compose version; python3 --version; git --version; docker network create traefik-public >/dev/null && echo traefik-public-net; swapon --show; free -m | head -3; df -h /' +amd64 +upgrade-harness +192.168.0.145/24 +Docker version 26.1.5+dfsg1, build a72d7cd +Docker Compose version 2.26.1-4 +Python 3.13.5 +git version 2.47.3 +traefik-public-net + total used free shared buff/cache available +Mem: 10240 60 8536 0 1643 10179 +Swap: 0 0 0 +Filesystem Size Used Avail Use% Mounted on +/dev/loop2 59G 1.3G 55G 3% / ++ pvesm status +Name Type Status Total (KiB) Used (KiB) Available (KiB) % +felhom-pbs pbs active 0 0 0 0.00% +local dir active 40453376 19528020 18838240 48.27% +local-lvm lvmthin active 56487936 34943437 21544498 61.86% +nvme-scratch dir active 983379700 78960252 854392836 8.03% +felhom.eu/documentation/audits/family-gate-2026-10-02/B/catalog-gates-grimmory-run1.txt +felhom.eu/documentation/audits/family-gate-2026-10-02/B/catalog-gates-grimmory.txt +felhom.eu/documentation/audits/family-gate-2026-10-02/B/catalog-gates-metube-run1.txt +felhom.eu/documentation/audits/family-gate-2026-10-02/B/catalog-gates-metube-run2.txt +felhom.eu/documentation/audits/family-gate-2026-10-02/B/catalog-gates-metube.txt +de0a9bd SparkyFitness onboarding record (Part C: bench + 9202 measured, six rows open — R-786; licence non-commercial — R-784); CHANGELOG/CONTEXT/REPORT for the R-753 catalog work +17 +golden-0.287.0-2026-10-02 diff --git a/documentation/audits/family-gate-2026-10-02/B/bench-gates3-teardown.txt b/documentation/audits/family-gate-2026-10-02/B/bench-gates3-teardown.txt new file mode 100644 index 00000000..90462421 --- /dev/null +++ b/documentation/audits/family-gate-2026-10-02/B/bench-gates3-teardown.txt @@ -0,0 +1,37 @@ ++ date -u +Fri Oct 2 07:35:01 UTC 2026 ++ pct list +VMID Status Lock Name +9201 running demo-hp +9202 running demo-hp-scratch +9401 running upgrade-harness ++ pvesm status +Name Type Status Total (KiB) Used (KiB) Available (KiB) % +felhom-pbs pbs active 0 0 0 0.00% +local dir active 40453376 19536264 18829996 48.29% +local-lvm lvmthin active 56487936 34943437 21544498 61.86% +nvme-scratch dir active 983379700 81070096 852282992 8.24% ++ pct exec 9401 -- bash -c 'docker ps -a --format {{.Names}} | wc -l; docker volume ls -q | wc -l' +0 +0 ++ pct stop 9401 ++ pct destroy 9401 --purge +purging CT 9401 from related configurations.. ++ pct list +VMID Status Lock Name +9201 running demo-hp +9202 running demo-hp-scratch ++ pct config 9401 ++ head -1 +Configuration file 'nodes/demo-hp/lxc/9401.conf' does not exist ++ ls /mnt/hdd_1/images/ +9202 ++ pvesm status +Name Type Status Total (KiB) Used (KiB) Available (KiB) % +felhom-pbs pbs active 0 0 0 0.00% +local dir active 40453376 19536192 18830068 48.29% +local-lvm lvmthin active 56487936 34943437 21544498 61.86% +nvme-scratch dir active 983379700 77369600 855983488 7.87% ++ rm -rf /tmp/b9401 +tmp-leftovers=0 +# nothing committed or pushed; evidence copied to DooPlex before the destroy diff --git a/documentation/audits/family-gate-2026-10-02/B/box/box-verdict-metube.json b/documentation/audits/family-gate-2026-10-02/B/box/box-verdict-metube.json new file mode 100644 index 00000000..e11d9364 --- /dev/null +++ b/documentation/audits/family-gate-2026-10-02/B/box/box-verdict-metube.json @@ -0,0 +1,18 @@ +{ + "app": "metube", + "venue": "box 9202 (drill catalog, controller 0.287.0), the product's guarded Update", + "from": { + "metube": "ghcr.io/alexta69/metube:2026.09.28" + }, + "to": { + "metube": "ghcr.io/alexta69/metube:2026.09.29" + }, + "verdict": "proven", + "seed_read_before": true, + "seed_read_after": true, + "healthy_after": true, + "duration_s": 34.9, + "final_phase": "done", + "measured_at": "2026-10-02T06:29:47Z", + "evidence": "felhom.eu/documentation/audits/family-gate-2026-10-02/B/box/metube-step.txt" +} \ No newline at end of file diff --git a/documentation/audits/family-gate-2026-10-02/B/box/final.txt b/documentation/audits/family-gate-2026-10-02/B/box/final.txt new file mode 100644 index 00000000..81b5f51c --- /dev/null +++ b/documentation/audits/family-gate-2026-10-02/B/box/final.txt @@ -0,0 +1,14 @@ +##### final on 9202 — controller gitea.dooplex.hu/admin/felhom-controller:0.287.0 | catalog e42bf78 DRILL grimmory: v3.5.0 (box step behind the family g | 2026-10-02T06:53:42Z +remove grimmory (WITH data and backups) -> 409 {'ok': False, 'error': 'A(z) /mnt/felhom-drives/scratch_hdd/userdata/grimmory tárhely jelenleg nem elérhető — az alkalmazás nem távolítható el, amíg a meghajtó vissza nem csatlakozik.'} + refused (R-442, no agent on 9202) — remove grimmory KEEPING drive data -> 200 {'ok': True, 'data': {'removed': 'grimmory', 'volumes_removed': ['grimmory_grimmory_db'], 'hdd_paths_removed': [], 'hdd_paths_preserved': ['/mnt/felhom-drives/scratch_hdd/userdata/grimmory/appdata/gri + the harness removed its own test folder /mnt/felhom-drives/scratch_hdd/userdata/grimmory by hand + left after: /opt/docker/stacks/grimmory +remove metube (WITH data and backups) -> 409 {'ok': False, 'error': 'A(z) /mnt/felhom-drives/scratch_hdd/userdata/metube tárhely jelenleg nem elérhető — az alkalmazás nem távolítható el, amíg a meghajtó vissza nem csatlakozik.'} + refused (R-442, no agent on 9202) — remove metube KEEPING drive data -> 200 {'ok': True, 'data': {'removed': 'metube', 'volumes_removed': ['metube_metube_state'], 'hdd_paths_removed': [], 'hdd_paths_preserved': [], 'backup_paths_removed': ['/mnt/felhom-drives/scratch_hdd/user + the harness removed its own test folder /mnt/felhom-drives/scratch_hdd/userdata/metube by hand + left after: /opt/docker/stacks/metube +gate files: none +5.1 (box) metube [24954133462d] 48 samples: peak anon 83.0 MiB = 10.8 % of the limit; oom_kill 0 +5.1 (box) metube [94ba5b8a2311] 13 samples: peak anon 58.8 MiB = 7.7 % of the limit; oom_kill 0 +5.1 (box) metube [fbbc26704571] 530 samples: peak anon 61.6 MiB = 8.0 % of the limit; oom_kill 0 +5.1 (box) metube [874b43d7ca42] 36 samples: peak anon 58.8 MiB = 7.7 % of the limit; oom_kill 0 diff --git a/documentation/audits/family-gate-2026-10-02/B/box/grim-mem-burst.csv b/documentation/audits/family-gate-2026-10-02/B/box/grim-mem-burst.csv new file mode 100644 index 00000000..b50b2ba7 --- /dev/null +++ b/documentation/audits/family-gate-2026-10-02/B/box/grim-mem-burst.csv @@ -0,0 +1,132 @@ +epoch,anon,oom_kill,limit,restarts +1790922772,384458752,0,1073741824,0 +1790922773,384458752,0,1073741824,0 +1790922774,384946176,0,1073741824,0 +1790922775,384946176,0,1073741824,0 +1790922776,384954368,0,1073741824,0 +1790922777,384954368,0,1073741824,0 +1790922778,390242304,0,1073741824,0 +1790922779,404082688,0,1073741824,0 +1790922780,422772736,0,1073741824,0 +1790922781,396791808,0,1073741824,0 +1790922782,396800000,0,1073741824,0 +1790922783,405811200,0,1073741824,0 +1790922784,446570496,0,1073741824,0 +1790922785,481116160,0,1073741824,0 +1790922786,487124992,0,1073741824,0 +1790922787,467677184,0,1073741824,0 +1790922788,467677184,0,1073741824,0 +1790922789,467677184,0,1073741824,0 +1790922790,440795136,0,1073741824,0 +1790922791,440795136,0,1073741824,0 +1790922792,440799232,0,1073741824,0 +1790922793,440799232,0,1073741824,0 +1790922794,440799232,0,1073741824,0 +1790922795,440799232,0,1073741824,0 +1790922796,440799232,0,1073741824,0 +1790922798,440799232,0,1073741824,0 +1790922799,440799232,0,1073741824,0 +1790922800,440799232,0,1073741824,0 +1790922801,440803328,0,1073741824,0 +1790922802,440803328,0,1073741824,0 +1790922803,440811520,0,1073741824,0 +1790922804,440811520,0,1073741824,0 +1790922805,440811520,0,1073741824,0 +1790922806,440811520,0,1073741824,0 +1790922807,440811520,0,1073741824,0 +1790922808,440811520,0,1073741824,0 +1790922809,440811520,0,1073741824,0 +1790922810,440811520,0,1073741824,0 +1790922811,440868864,0,1073741824,0 +1790922812,440868864,0,1073741824,0 +1790922813,440872960,0,1073741824,0 +1790922814,440872960,0,1073741824,0 +1790922815,441974784,0,1073741824,0 +1790922816,441974784,0,1073741824,0 +1790922817,441991168,0,1073741824,0 +1790922818,441991168,0,1073741824,0 +1790922819,442015744,0,1073741824,0 +1790922820,408018944,0,1073741824,0 +1790922821,393568256,0,1073741824,0 +1790922822,393568256,0,1073741824,0 +1790922823,393568256,0,1073741824,0 +1790922825,393568256,0,1073741824,0 +1790922826,393568256,0,1073741824,0 +1790922827,393568256,0,1073741824,0 +1790922828,393568256,0,1073741824,0 +1790922829,393568256,0,1073741824,0 +1790922830,393584640,0,1073741824,0 +1790922831,393584640,0,1073741824,0 +1790922832,393584640,0,1073741824,0 +1790922833,393584640,0,1073741824,0 +1790922834,393674752,0,1073741824,0 +1790922835,393674752,0,1073741824,0 +1790922836,393674752,0,1073741824,0 +1790922837,393674752,0,1073741824,0 +1790922838,393674752,0,1073741824,0 +1790922839,393674752,0,1073741824,0 +1790922840,393674752,0,1073741824,0 +1790922841,393940992,0,1073741824,0 +1790922842,393940992,0,1073741824,0 +1790922843,393940992,0,1073741824,0 +1790922844,393940992,0,1073741824,0 +1790922845,394649600,0,1073741824,0 +1790922846,394649600,0,1073741824,0 +1790922847,394723328,0,1073741824,0 +1790922848,394723328,0,1073741824,0 +1790922849,394723328,0,1073741824,0 +1790922851,393641984,0,1073741824,0 +1790922852,393641984,0,1073741824,0 +1790922853,393641984,0,1073741824,0 +1790922854,393641984,0,1073741824,0 +1790922855,393641984,0,1073741824,0 +1790922856,393641984,0,1073741824,0 +1790922857,393641984,0,1073741824,0 +1790922858,393646080,0,1073741824,0 +1790922859,393646080,0,1073741824,0 +1790922860,393650176,0,1073741824,0 +1790922861,393650176,0,1073741824,0 +1790922862,393728000,0,1073741824,0 +1790922863,393728000,0,1073741824,0 +1790922864,393728000,0,1073741824,0 +1790922865,393728000,0,1073741824,0 +1790922866,393728000,0,1073741824,0 +1790922867,393728000,0,1073741824,0 +1790922868,393728000,0,1073741824,0 +1790922869,393728000,0,1073741824,0 +1790922870,393826304,0,1073741824,0 +1790922871,394088448,0,1073741824,0 +1790922872,394088448,0,1073741824,0 +1790922873,394088448,0,1073741824,0 +1790922874,394088448,0,1073741824,0 +1790922875,394063872,0,1073741824,0 +1790922876,395186176,0,1073741824,0 +1790922878,395186176,0,1073741824,0 +1790922879,395198464,0,1073741824,0 +1790922880,395202560,0,1073741824,0 +1790922881,395202560,0,1073741824,0 +1790922882,393576448,0,1073741824,0 +1790922883,393576448,0,1073741824,0 +1790922884,393576448,0,1073741824,0 +1790922885,393576448,0,1073741824,0 +1790922886,393576448,0,1073741824,0 +1790922887,393576448,0,1073741824,0 +1790922888,393576448,0,1073741824,0 +1790922889,393584640,0,1073741824,0 +1790922890,393584640,0,1073741824,0 +1790922891,393674752,0,1073741824,0 +1790922892,393674752,0,1073741824,0 +1790922893,393674752,0,1073741824,0 +1790922894,393674752,0,1073741824,0 +1790922895,393674752,0,1073741824,0 +1790922896,393674752,0,1073741824,0 +1790922897,393674752,0,1073741824,0 +1790922898,393674752,0,1073741824,0 +1790922899,393678848,0,1073741824,0 +1790922900,393678848,0,1073741824,0 +1790922901,393940992,0,1073741824,0 +1790922902,393940992,0,1073741824,0 +1790922903,393940992,0,1073741824,0 +1790922904,393940992,0,1073741824,0 +1790922906,393940992,0,1073741824,0 +1790922907,394600448,0,1073741824,0 diff --git a/documentation/audits/family-gate-2026-10-02/B/box/grim-mem.txt b/documentation/audits/family-gate-2026-10-02/B/box/grim-mem.txt new file mode 100644 index 00000000..3776839a --- /dev/null +++ b/documentation/audits/family-gate-2026-10-02/B/box/grim-mem.txt @@ -0,0 +1,11 @@ +##### grimmory memory on 9202 — 2026-10-02T06:32:47Z | limit 1073741824 +5.2 burst: 40 EPUB uploads through the app's own API in 2.5 s: codes {'204': 40} + the library lists 41 books after the burst (8 s from its start); sampling 120 s more +5.2 grimmory, 131 samples (1/s) across the burst: peak anon 464.6 MiB = 45.4 % of 1024M; oom_kill 0; restarts 0 +5.4 ghcr.io/grimmory-tools/grimmory:v3.4.1 at a 768m container limit (the image's own JAVA_TOOL_OPTIONS): size_t MaxHeapSize = 483393536 {product} {ergonomic} | double MaxRAMPercentage = 60.000000 {product} {environment} +5.4 ghcr.io/grimmory-tools/grimmory:v3.4.1 at a 1024m container limit (the image's own JAVA_TOOL_OPTIONS): size_t MaxHeapSize = 644349952 {product} {ergonomic} | double MaxRAMPercentage = 60.000000 {product} {environment} +5.4 ghcr.io/grimmory-tools/grimmory:v3.4.1 at a 1536m container limit (the image's own JAVA_TOOL_OPTIONS): size_t MaxHeapSize = 966524928 {product} {ergonomic} | double MaxRAMPercentage = 60.000000 {product} {environment} +5.4 (re-read on the published tag, 2026-10-02T06:48:11Z) ghcr.io/grimmory-tools/grimmory:v3.5.0 at a 768m container limit: size_t MaxHeapSize = 483393536 {product} {ergonomic}| double MaxRAMPercentage = 60.000000 {product} {environment}| +5.4 (re-read on the published tag, 2026-10-02T06:48:12Z) ghcr.io/grimmory-tools/grimmory:v3.5.0 at a 1024m container limit: size_t MaxHeapSize = 644349952 {product} {ergonomic}| double MaxRAMPercentage = 60.000000 {product} {environment}| +5.4 (re-read on the published tag, 2026-10-02T06:48:14Z) ghcr.io/grimmory-tools/grimmory:v3.5.0 at a 1536m container limit: size_t MaxHeapSize = 966524928 {product} {ergonomic}| double MaxRAMPercentage = 60.000000 {product} {environment}| +NOTE: the burst above ran on v3.4.1 (9202's Grimmory was stepped to v3.5.0 afterwards, grim-step.txt); v3.5.0's JVM options are the same (the three lines just above). diff --git a/documentation/audits/family-gate-2026-10-02/B/box/grim-seed.txt b/documentation/audits/family-gate-2026-10-02/B/box/grim-seed.txt new file mode 100644 index 00000000..a9cec3fd --- /dev/null +++ b/documentation/audits/family-gate-2026-10-02/B/box/grim-seed.txt @@ -0,0 +1,6 @@ +##### grim-seed on 9202 — controller gitea.dooplex.hu/admin/felhom-controller:0.287.0 | catalog c3f433f DRILL: grimmory + metube behind the family gate (0.2 | 2026-10-02T06:12:03Z +grimmory: library 1, the household uploads an EPUB through its own API (behind the family gate) -> 204 + grimmory: readback http=200 found=True (controls: no token 401, wrong 401, second setup 403) +grimmory: the seed reads back: True +2.2/2.8 the book on the household's drive: /mnt/felhom-drives/scratch_hdd/userdata/grimmory/userdata/media/grimmory/Felhom/Felhom Csalad 4bd80aee/Felhom Csalad 4bd80aee - Felhom.epub +2.7 sizes: grimmory_grimmory_db=169M 76K /mnt/felhom-drives/scratch_hdd/userdata/grimmory diff --git a/documentation/audits/family-gate-2026-10-02/B/box/grim-step-attempt1.txt b/documentation/audits/family-gate-2026-10-02/B/box/grim-step-attempt1.txt new file mode 100644 index 00000000..6a9aa74d --- /dev/null +++ b/documentation/audits/family-gate-2026-10-02/B/box/grim-step-attempt1.txt @@ -0,0 +1,10 @@ +##### grim-step on 9202 — controller gitea.dooplex.hu/admin/felhom-controller:0.287.0 | catalog 3d8b76d DRILL metube: ghcr.io/alexta69/metube:2026.09.29 (bo | 2026-10-02T06:35:58Z +before: pinned={'grimmory': 'ghcr.io/grimmory-tools/grimmory:v3.4.1', 'grimmory-db': 'mariadb:11.4'} +drill: 051749b DRILL grimmory: v3.5.0 (box step behind the family gate, 2026-10-02) + phase +0.0s backing-up | err=None + phase +10.3s safety-dump | err=None + phase +11.3s failed | err=A frissítés nem indult el: az új verzió leírása nem olvasható be. Az alkalmazás változatlanul fut tovább. + grimmory: /api/v1/healthcheck never answered + gate files after the step: family-gate-grimmory.yml family-gate-metube.yml | a stranger at / -> LAN 401 | tunnel 401 | OPDS exception, a stranger -> LAN 401 | tunnel 401 +RESULT final_phase=failed after={'grimmory': 'ghcr.io/grimmory-tools/grimmory:v3.4.1', 'grimmory-db': 'mariadb:11.4'} seed_after=False (11.3 s) +NOTE (2026-10-02T06:47Z): attempt 1 is a HARNESS failure, not the product's. The drill edit opened the compose for writing before reading it, so the drill template went out EMPTY; the box refused it ("backup block rejected … matches no compose bind"), took its backup, and answered "the update did not start — the app runs on unchanged" (11.3 s). Grimmory kept running at v3.4.1, healthy. The member 401s after it: box_walk cached "not gated" from a GET / asked while Grimmory restarted after the pre-update backup (no router for a moment) — fixed in box_walk.gate_cookie (a 404/5xx without a gate redirect is no longer cached). Drill restored by commit; attempt 2: grim-step.txt. diff --git a/documentation/audits/family-gate-2026-10-02/B/box/grim-step.txt b/documentation/audits/family-gate-2026-10-02/B/box/grim-step.txt new file mode 100644 index 00000000..e505e757 --- /dev/null +++ b/documentation/audits/family-gate-2026-10-02/B/box/grim-step.txt @@ -0,0 +1,12 @@ +##### grim-step on 9202 — controller gitea.dooplex.hu/admin/felhom-controller:0.287.0 | catalog 051749b DRILL grimmory: v3.5.0 (box step behind the family g | 2026-10-02T06:46:39Z +before: pinned={'grimmory': 'ghcr.io/grimmory-tools/grimmory:v3.4.1', 'grimmory-db': 'mariadb:11.4'} +drill: e42bf78 DRILL grimmory: v3.5.0 (box step behind the family gate, 2026-10-02) + phase +0.0s safety-dump | err=None + phase +1.1s pulling | err=None + phase +14.4s copying | err=None + phase +17.5s starting | err=None + phase +23.6s verifying | err=None + phase +58.5s done | err=None + grimmory: readback http=200 found=True (controls: no token 401, wrong 401, second setup 403) + gate files after the step: family-gate-grimmory.yml family-gate-metube.yml | a stranger at / -> LAN 401 | tunnel 401 | OPDS exception, a stranger -> LAN 401 | tunnel 401 +RESULT final_phase=done after={'grimmory': 'ghcr.io/grimmory-tools/grimmory:v3.5.0', 'grimmory-db': 'mariadb:11.4'} seed_after=True (58.5 s) diff --git a/documentation/audits/family-gate-2026-10-02/B/box/life.txt b/documentation/audits/family-gate-2026-10-02/B/box/life.txt new file mode 100644 index 00000000..b99beeb4 --- /dev/null +++ b/documentation/audits/family-gate-2026-10-02/B/box/life.txt @@ -0,0 +1,20 @@ +##### life on 9202 — controller gitea.dooplex.hu/admin/felhom-controller:0.287.0 | catalog e42bf78 DRILL grimmory: v3.5.0 (box step behind the family g | 2026-10-02T06:48:28Z +before: gate files: family-gate-grimmory.yml family-gate-metube.yml +2.5 the night chain (debug action) -> 202 + 2026/10/02 06:49:04 night_chain.go:93: [INFO] [night-chain] finished in 33s + grimmory restore points: [(None, '2026-10-02T06:48:32Z')] + metube restore points: [(None, '2026-10-02T06:48:41Z')] +remove grimmory (KEEPING data and backups) -> 200 {'ok': True, 'data': {'removed': 'grimmory', 'volumes_removed': ['grimmory_grimmory_db'], 'hdd_paths_removed': [], 'hdd_paths_preserved': ['/mnt/felhom-drives/scratch_hdd/userdata/grimmory/appdata/grimmory/data (4.0K)'], 'verified': True}, 'message': 'Stack gr + left after: /opt/docker/stacks/grimmory | /mnt/felhom-drives/scratch_hdd/userdata/grimmory +remove metube (KEEPING data and backups) -> 200 {'ok': True, 'data': {'removed': 'metube', 'volumes_removed': ['metube_metube_state'], 'hdd_paths_removed': [], 'hdd_paths_preserved': [], 'verified': True}, 'message': 'Stack metube removed'} + left after: /opt/docker/stacks/metube | /mnt/felhom-drives/scratch_hdd/userdata/metube +after both removes: gate files: none | a stranger at library/ -> LAN 404 | tunnel 404 | video/ -> LAN 404 | tunnel 404 +2.5 restore grimmory: {'ok': True, 'snapshot_id': 'helyi', 'seconds': 64.8, 'state_after': 'running', 'hold_after': None} +2.5 restore metube: {'ok': True, 'snapshot_id': 'helyi', 'seconds': 38.5, 'state_after': 'running', 'hold_after': None} +after both restores: gate files: family-gate-grimmory.yml family-gate-metube.yml + family_gate record: family_gate: since: "2026-10-02T06:50:54Z" hosts: - library.enkisfelhom.hu | family_gate: since: "2026-10-02T06:52:01Z" hosts: - video.enkisfelhom.hu + a STRANGER (no cookie): library/ -> LAN 401 | tunnel 401 | library/api/v1/books -> LAN 401 | tunnel 401 | video/ -> LAN 401 | tunnel 401 | video/history -> LAN 401 | tunnel 401 + grimmory: readback http=200 found=True (controls: no token 401, wrong 401, second setup 403) +2.5 grimmory: the seed reads back after remove + restore (a member, then the app's own login): True + metube: history has it=True; GET /download/ http=200 +2.5 metube: the seed reads back after remove + restore (a member, then the app's own login): True diff --git a/documentation/audits/family-gate-2026-10-02/B/box/mem-metube.csv b/documentation/audits/family-gate-2026-10-02/B/box/mem-metube.csv new file mode 100644 index 00000000..294951d8 --- /dev/null +++ b/documentation/audits/family-gate-2026-10-02/B/box/mem-metube.csv @@ -0,0 +1,628 @@ +epoch,name,id,anon,current,swap,oom_kill,limit,restarts +1790922425,metube,24954133462d,38817792,45064192,0,0,805306368,0 +1790922427,metube,24954133462d,61632512,68804608,0,0,805306368,0 +1790922429,metube,24954133462d,61665280,69287936,0,0,805306368,0 +1790922431,metube,24954133462d,61665280,68931584,0,0,805306368,0 +1790922433,metube,24954133462d,61665280,68931584,0,0,805306368,0 +1790922435,metube,24954133462d,61665280,69197824,0,0,805306368,0 +1790922437,metube,24954133462d,61665280,68923392,0,0,805306368,0 +1790922439,metube,24954133462d,61665280,68923392,0,0,805306368,0 +1790922441,metube,24954133462d,61665280,68923392,0,0,805306368,0 +1790922444,metube,24954133462d,61665280,68923392,0,0,805306368,0 +1790922446,metube,24954133462d,61665280,68923392,0,0,805306368,0 +1790922448,metube,24954133462d,61665280,68337664,0,0,805306368,0 +1790922450,metube,24954133462d,85819392,95543296,0,0,805306368,0 +1790922452,metube,24954133462d,85819392,94728192,0,0,805306368,0 +1790922454,metube,24954133462d,86790144,95838208,0,0,805306368,0 +1790922456,metube,24954133462d,86790144,95834112,0,0,805306368,0 +1790922458,metube,24954133462d,86790144,95834112,0,0,805306368,0 +1790922460,metube,24954133462d,87060480,96108544,0,0,805306368,0 +1790922462,metube,24954133462d,87060480,96104448,0,0,805306368,0 +1790922464,metube,24954133462d,87060480,96104448,0,0,805306368,0 +1790922467,metube,24954133462d,87060480,96104448,0,0,805306368,0 +1790922469,metube,24954133462d,87060480,96104448,0,0,805306368,0 +1790922471,metube,24954133462d,87060480,96104448,0,0,805306368,0 +1790922473,metube,24954133462d,87060480,96378880,0,0,805306368,0 +1790922475,metube,24954133462d,87060480,96116736,0,0,805306368,0 +1790922477,metube,24954133462d,87064576,96649216,0,0,805306368,0 +1790922479,metube,24954133462d,87064576,96317440,0,0,805306368,0 +1790922481,metube,24954133462d,87060480,96083968,0,0,805306368,0 +1790922483,metube,24954133462d,87064576,96083968,0,0,805306368,0 +1790922550,metube,24954133462d,59383808,63049728,0,0,805306368,0 +1790922552,metube,24954133462d,59383808,62849024,0,0,805306368,0 +1790922555,metube,24954133462d,59428864,63455232,0,0,805306368,0 +1790922557,metube,24954133462d,59428864,63004672,0,0,805306368,0 +1790922559,metube,24954133462d,59428864,63000576,0,0,805306368,0 +1790922561,metube,24954133462d,59428864,63000576,0,0,805306368,0 +1790922563,metube,24954133462d,59428864,63000576,0,0,805306368,0 +1790922565,metube,24954133462d,59428864,63000576,0,0,805306368,0 +1790922567,metube,24954133462d,59428864,63000576,0,0,805306368,0 +1790922569,metube,24954133462d,59428864,63000576,0,0,805306368,0 +1790922571,metube,24954133462d,59428864,63000576,0,0,805306368,0 +1790922573,metube,24954133462d,59428864,63000576,0,0,805306368,0 +1790922575,metube,24954133462d,59428864,63000576,0,0,805306368,0 +1790922577,metube,24954133462d,59428864,63000576,0,0,805306368,0 +1790922579,metube,24954133462d,59428864,63000576,0,0,805306368,0 +1790922582,metube,24954133462d,59428864,63000576,0,0,805306368,0 +1790922584,metube,24954133462d,59428864,63000576,0,0,805306368,0 +1790922586,metube,24954133462d,59428864,63000576,0,0,805306368,0 +1790922588,metube,24954133462d,59428864,63000576,0,0,805306368,0 +1790922590,metube,94ba5b8a2311,16261120,21282816,0,0,805306368,0 +1790922592,metube,94ba5b8a2311,61636608,68218880,0,0,805306368,0 +1790922594,metube,94ba5b8a2311,61636608,68214784,0,0,805306368,0 +1790922596,metube,94ba5b8a2311,61669376,68333568,0,0,805306368,0 +1790922598,metube,94ba5b8a2311,61669376,68333568,0,0,805306368,0 +1790922600,metube,94ba5b8a2311,61669376,68333568,0,0,805306368,0 +1790922602,metube,94ba5b8a2311,61669376,68333568,0,0,805306368,0 +1790922605,metube,94ba5b8a2311,61669376,68333568,0,0,805306368,0 +1790922607,metube,94ba5b8a2311,61669376,68329472,0,0,805306368,0 +1790922609,metube,94ba5b8a2311,61669376,68329472,0,0,805306368,0 +1790922611,metube,94ba5b8a2311,61669376,68329472,0,0,805306368,0 +1790922613,metube,94ba5b8a2311,61669376,68329472,0,0,805306368,0 +1790922615,metube,94ba5b8a2311,57085952,63406080,0,0,805306368,0 +1790922618,metube,fbbc26704571,44728320,51138560,0,0,805306368,0 +1790922621,metube,fbbc26704571,61620224,68706304,0,0,805306368,0 +1790922623,metube,fbbc26704571,61657088,69545984,0,0,805306368,0 +1790922625,metube,fbbc26704571,61657088,68829184,0,0,805306368,0 +1790922627,metube,fbbc26704571,61657088,68829184,0,0,805306368,0 +1790922629,metube,fbbc26704571,61657088,68829184,0,0,805306368,0 +1790922631,metube,fbbc26704571,61657088,68829184,0,0,805306368,0 +1790922633,metube,fbbc26704571,61657088,68829184,0,0,805306368,0 +1790922635,metube,fbbc26704571,61661184,69570560,0,0,805306368,0 +1790922637,metube,fbbc26704571,61661184,69570560,0,0,805306368,0 +1790922639,metube,fbbc26704571,61661184,69570560,0,0,805306368,0 +1790922641,metube,fbbc26704571,61661184,69844992,0,0,805306368,0 +1790922643,metube,fbbc26704571,61661184,69844992,0,0,805306368,0 +1790922646,metube,fbbc26704571,61661184,69844992,0,0,805306368,0 +1790922648,metube,fbbc26704571,61661184,69844992,0,0,805306368,0 +1790922650,metube,fbbc26704571,61661184,69844992,0,0,805306368,0 +1790922652,metube,fbbc26704571,61661184,69844992,0,0,805306368,0 +1790922654,metube,fbbc26704571,61661184,70103040,0,0,805306368,0 +1790922656,metube,fbbc26704571,61730816,70615040,0,0,805306368,0 +1790922658,metube,fbbc26704571,61730816,70004736,0,0,805306368,0 +1790922660,metube,fbbc26704571,61730816,70004736,0,0,805306368,0 +1790922662,metube,fbbc26704571,61730816,70004736,0,0,805306368,0 +1790922664,metube,fbbc26704571,61730816,71557120,0,0,805306368,0 +1790922666,metube,fbbc26704571,61730816,71557120,0,0,805306368,0 +1790922668,metube,fbbc26704571,61730816,71557120,0,0,805306368,0 +1790922671,metube,fbbc26704571,61730816,71557120,0,0,805306368,0 +1790922673,metube,fbbc26704571,61730816,71557120,0,0,805306368,0 +1790922675,metube,fbbc26704571,61730816,71553024,0,0,805306368,0 +1790922677,metube,fbbc26704571,61730816,71553024,0,0,805306368,0 +1790922679,metube,fbbc26704571,61730816,71557120,0,0,805306368,0 +1790922681,metube,fbbc26704571,61730816,71557120,0,0,805306368,0 +1790922683,metube,fbbc26704571,61730816,71630848,0,0,805306368,0 +1790922685,metube,fbbc26704571,61730816,71561216,0,0,805306368,0 +1790922687,metube,fbbc26704571,61730816,71561216,0,0,805306368,0 +1790922689,metube,fbbc26704571,61730816,71561216,0,0,805306368,0 +1790922691,metube,fbbc26704571,61730816,71561216,0,0,805306368,0 +1790922693,metube,fbbc26704571,61730816,71557120,0,0,805306368,0 +1790922695,metube,fbbc26704571,61730816,71557120,0,0,805306368,0 +1790922698,metube,fbbc26704571,61730816,71557120,0,0,805306368,0 +1790922700,metube,fbbc26704571,61730816,71557120,0,0,805306368,0 +1790922702,metube,fbbc26704571,61730816,71557120,0,0,805306368,0 +1790922704,metube,fbbc26704571,61730816,71557120,0,0,805306368,0 +1790922706,metube,fbbc26704571,61730816,71557120,0,0,805306368,0 +1790922708,metube,fbbc26704571,61730816,71557120,0,0,805306368,0 +1790922710,metube,fbbc26704571,61730816,71553024,0,0,805306368,0 +1790922712,metube,fbbc26704571,61730816,71553024,0,0,805306368,0 +1790922714,metube,fbbc26704571,61730816,71553024,0,0,805306368,0 +1790922716,metube,fbbc26704571,61730816,71553024,0,0,805306368,0 +1790922718,metube,fbbc26704571,61730816,71553024,0,0,805306368,0 +1790922720,metube,fbbc26704571,61730816,71553024,0,0,805306368,0 +1790922723,metube,fbbc26704571,61730816,71553024,0,0,805306368,0 +1790922725,metube,fbbc26704571,61730816,71553024,0,0,805306368,0 +1790922727,metube,fbbc26704571,61730816,71553024,0,0,805306368,0 +1790922729,metube,fbbc26704571,61730816,71553024,0,0,805306368,0 +1790922731,metube,fbbc26704571,61730816,71553024,0,0,805306368,0 +1790922733,metube,fbbc26704571,61730816,71553024,0,0,805306368,0 +1790922735,metube,fbbc26704571,61730816,71553024,0,0,805306368,0 +1790922737,metube,fbbc26704571,61730816,71553024,0,0,805306368,0 +1790922739,metube,fbbc26704571,61730816,71553024,0,0,805306368,0 +1790922741,metube,fbbc26704571,61730816,71553024,0,0,805306368,0 +1790922743,metube,fbbc26704571,61730816,72122368,0,0,805306368,0 +1790922745,metube,fbbc26704571,61730816,71565312,0,0,805306368,0 +1790922748,metube,fbbc26704571,61730816,71553024,0,0,805306368,0 +1790922750,metube,fbbc26704571,61730816,71548928,0,0,805306368,0 +1790922752,metube,fbbc26704571,61730816,71548928,0,0,805306368,0 +1790922754,metube,fbbc26704571,61730816,71548928,0,0,805306368,0 +1790922756,metube,fbbc26704571,61730816,71548928,0,0,805306368,0 +1790922758,metube,fbbc26704571,61730816,71548928,0,0,805306368,0 +1790922760,metube,fbbc26704571,61730816,71548928,0,0,805306368,0 +1790922762,metube,fbbc26704571,61730816,71548928,0,0,805306368,0 +1790922764,metube,fbbc26704571,61730816,71548928,0,0,805306368,0 +1790922766,metube,fbbc26704571,61730816,71548928,0,0,805306368,0 +1790922768,metube,fbbc26704571,61730816,71548928,0,0,805306368,0 +1790922770,metube,fbbc26704571,61730816,71548928,0,0,805306368,0 +1790922773,metube,fbbc26704571,61730816,71548928,0,0,805306368,0 +1790922775,metube,fbbc26704571,61730816,71548928,0,0,805306368,0 +1790922777,metube,fbbc26704571,61730816,71548928,0,0,805306368,0 +1790922779,metube,fbbc26704571,61730816,71548928,0,0,805306368,0 +1790922781,metube,fbbc26704571,61730816,71548928,0,0,805306368,0 +1790922783,metube,fbbc26704571,61730816,71548928,0,0,805306368,0 +1790922785,metube,fbbc26704571,61730816,71548928,0,0,805306368,0 +1790922787,metube,fbbc26704571,61730816,71548928,0,0,805306368,0 +1790922789,metube,fbbc26704571,61730816,71548928,0,0,805306368,0 +1790922791,metube,fbbc26704571,61730816,71548928,0,0,805306368,0 +1790922793,metube,fbbc26704571,61730816,71548928,0,0,805306368,0 +1790922795,metube,fbbc26704571,61730816,71548928,0,0,805306368,0 +1790922797,metube,fbbc26704571,61730816,71548928,0,0,805306368,0 +1790922800,metube,fbbc26704571,61730816,71548928,0,0,805306368,0 +1790922802,metube,fbbc26704571,61730816,71548928,0,0,805306368,0 +1790922804,metube,fbbc26704571,61730816,72265728,0,0,805306368,0 +1790922806,metube,fbbc26704571,61730816,71548928,0,0,805306368,0 +1790922808,metube,fbbc26704571,61730816,71548928,0,0,805306368,0 +1790922810,metube,fbbc26704571,61730816,71548928,0,0,805306368,0 +1790922812,metube,fbbc26704571,61730816,71548928,0,0,805306368,0 +1790922814,metube,fbbc26704571,61730816,71548928,0,0,805306368,0 +1790922816,metube,fbbc26704571,61730816,71548928,0,0,805306368,0 +1790922818,metube,fbbc26704571,61730816,71548928,0,0,805306368,0 +1790922820,metube,fbbc26704571,61730816,71548928,0,0,805306368,0 +1790922822,metube,fbbc26704571,61730816,71548928,0,0,805306368,0 +1790922825,metube,fbbc26704571,61730816,71548928,0,0,805306368,0 +1790922827,metube,fbbc26704571,61730816,71548928,0,0,805306368,0 +1790922829,metube,fbbc26704571,61730816,71548928,0,0,805306368,0 +1790922831,metube,fbbc26704571,61730816,71548928,0,0,805306368,0 +1790922833,metube,fbbc26704571,61730816,71987200,0,0,805306368,0 +1790922835,metube,fbbc26704571,61730816,71548928,0,0,805306368,0 +1790922837,metube,fbbc26704571,61730816,71548928,0,0,805306368,0 +1790922839,metube,fbbc26704571,61730816,71548928,0,0,805306368,0 +1790922841,metube,fbbc26704571,61730816,71548928,0,0,805306368,0 +1790922843,metube,fbbc26704571,61730816,71548928,0,0,805306368,0 +1790922845,metube,fbbc26704571,61730816,71548928,0,0,805306368,0 +1790922847,metube,fbbc26704571,61730816,71548928,0,0,805306368,0 +1790922850,metube,fbbc26704571,61730816,71548928,0,0,805306368,0 +1790922852,metube,fbbc26704571,61730816,71548928,0,0,805306368,0 +1790922854,metube,fbbc26704571,61730816,71548928,0,0,805306368,0 +1790922856,metube,fbbc26704571,61730816,71548928,0,0,805306368,0 +1790922858,metube,fbbc26704571,61730816,71548928,0,0,805306368,0 +1790922860,metube,fbbc26704571,61730816,71548928,0,0,805306368,0 +1790922862,metube,fbbc26704571,61730816,71548928,0,0,805306368,0 +1790922864,metube,fbbc26704571,61730816,72355840,0,0,805306368,0 +1790922866,metube,fbbc26704571,61730816,71548928,0,0,805306368,0 +1790922868,metube,fbbc26704571,61730816,71548928,0,0,805306368,0 +1790922870,metube,fbbc26704571,61730816,71548928,0,0,805306368,0 +1790922872,metube,fbbc26704571,61730816,71548928,0,0,805306368,0 +1790922874,metube,fbbc26704571,61730816,71548928,0,0,805306368,0 +1790922877,metube,fbbc26704571,61730816,71548928,0,0,805306368,0 +1790922879,metube,fbbc26704571,61730816,71548928,0,0,805306368,0 +1790922881,metube,fbbc26704571,61730816,71548928,0,0,805306368,0 +1790922883,metube,fbbc26704571,61730816,71548928,0,0,805306368,0 +1790922885,metube,fbbc26704571,61730816,71548928,0,0,805306368,0 +1790922887,metube,fbbc26704571,61730816,71548928,0,0,805306368,0 +1790922889,metube,fbbc26704571,61730816,71548928,0,0,805306368,0 +1790922891,metube,fbbc26704571,61730816,71548928,0,0,805306368,0 +1790922893,metube,fbbc26704571,61730816,72036352,0,0,805306368,0 +1790922895,metube,fbbc26704571,61730816,71573504,0,0,805306368,0 +1790922897,metube,fbbc26704571,61730816,71548928,0,0,805306368,0 +1790922899,metube,fbbc26704571,61730816,71548928,0,0,805306368,0 +1790922901,metube,fbbc26704571,61730816,71548928,0,0,805306368,0 +1790922904,metube,fbbc26704571,61730816,71548928,0,0,805306368,0 +1790922906,metube,fbbc26704571,61730816,71548928,0,0,805306368,0 +1790922908,metube,fbbc26704571,61730816,71548928,0,0,805306368,0 +1790922910,metube,fbbc26704571,61730816,71548928,0,0,805306368,0 +1790922912,metube,fbbc26704571,61730816,71548928,0,0,805306368,0 +1790922914,metube,fbbc26704571,61730816,71548928,0,0,805306368,0 +1790922916,metube,fbbc26704571,61730816,71548928,0,0,805306368,0 +1790922918,metube,fbbc26704571,61730816,71548928,0,0,805306368,0 +1790922920,metube,fbbc26704571,61730816,71548928,0,0,805306368,0 +1790922922,metube,fbbc26704571,61730816,71548928,0,0,805306368,0 +1790922924,metube,fbbc26704571,61730816,71684096,0,0,805306368,0 +1790922927,metube,fbbc26704571,61730816,71553024,0,0,805306368,0 +1790922929,metube,fbbc26704571,61730816,71548928,0,0,805306368,0 +1790922931,metube,fbbc26704571,61730816,71548928,0,0,805306368,0 +1790922933,metube,fbbc26704571,61730816,71548928,0,0,805306368,0 +1790922935,metube,fbbc26704571,61730816,71548928,0,0,805306368,0 +1790922937,metube,fbbc26704571,61730816,71548928,0,0,805306368,0 +1790922939,metube,fbbc26704571,61730816,71548928,0,0,805306368,0 +1790922941,metube,fbbc26704571,61730816,71548928,0,0,805306368,0 +1790922943,metube,fbbc26704571,61730816,71548928,0,0,805306368,0 +1790922945,metube,fbbc26704571,61730816,71548928,0,0,805306368,0 +1790922947,metube,fbbc26704571,61730816,71548928,0,0,805306368,0 +1790922949,metube,fbbc26704571,61730816,71548928,0,0,805306368,0 +1790922952,metube,fbbc26704571,61730816,71548928,0,0,805306368,0 +1790922954,metube,fbbc26704571,61730816,71688192,0,0,805306368,0 +1790922956,metube,fbbc26704571,61730816,71548928,0,0,805306368,0 +1790922958,metube,fbbc26704571,61730816,71548928,0,0,805306368,0 +1790922960,metube,fbbc26704571,61730816,71548928,0,0,805306368,0 +1790922962,metube,fbbc26704571,61730816,71548928,0,0,805306368,0 +1790922964,metube,fbbc26704571,61730816,71548928,0,0,805306368,0 +1790922966,metube,fbbc26704571,61730816,71548928,0,0,805306368,0 +1790922968,metube,fbbc26704571,61730816,71548928,0,0,805306368,0 +1790922970,metube,fbbc26704571,61730816,71548928,0,0,805306368,0 +1790922972,metube,fbbc26704571,61730816,71548928,0,0,805306368,0 +1790922974,metube,fbbc26704571,61730816,71548928,0,0,805306368,0 +1790922977,metube,fbbc26704571,61730816,71548928,0,0,805306368,0 +1790922979,metube,fbbc26704571,61730816,71548928,0,0,805306368,0 +1790922981,metube,fbbc26704571,61730816,71548928,0,0,805306368,0 +1790922983,metube,fbbc26704571,61730816,71548928,0,0,805306368,0 +1790922985,metube,fbbc26704571,61730816,71548928,0,0,805306368,0 +1790922987,metube,fbbc26704571,61730816,71548928,0,0,805306368,0 +1790922989,metube,fbbc26704571,61730816,71548928,0,0,805306368,0 +1790922991,metube,fbbc26704571,61730816,71548928,0,0,805306368,0 +1790922993,metube,fbbc26704571,61730816,71548928,0,0,805306368,0 +1790922995,metube,fbbc26704571,61730816,71548928,0,0,805306368,0 +1790922997,metube,fbbc26704571,61730816,71548928,0,0,805306368,0 +1790922999,metube,fbbc26704571,61730816,71548928,0,0,805306368,0 +1790923001,metube,fbbc26704571,61730816,71548928,0,0,805306368,0 +1790923004,metube,fbbc26704571,61730816,71548928,0,0,805306368,0 +1790923006,metube,fbbc26704571,61730816,71548928,0,0,805306368,0 +1790923008,metube,fbbc26704571,61730816,71548928,0,0,805306368,0 +1790923010,metube,fbbc26704571,61730816,71548928,0,0,805306368,0 +1790923012,metube,fbbc26704571,61730816,71548928,0,0,805306368,0 +1790923014,metube,fbbc26704571,61730816,71630848,0,0,805306368,0 +1790923016,metube,fbbc26704571,61730816,71553024,0,0,805306368,0 +1790923018,metube,fbbc26704571,61730816,71548928,0,0,805306368,0 +1790923020,metube,fbbc26704571,61730816,71548928,0,0,805306368,0 +1790923022,metube,fbbc26704571,61730816,71548928,0,0,805306368,0 +1790923024,metube,fbbc26704571,61730816,71548928,0,0,805306368,0 +1790923026,metube,fbbc26704571,61730816,71548928,0,0,805306368,0 +1790923029,metube,fbbc26704571,61730816,71548928,0,0,805306368,0 +1790923031,metube,fbbc26704571,61730816,71548928,0,0,805306368,0 +1790923033,metube,fbbc26704571,61730816,71548928,0,0,805306368,0 +1790923035,metube,fbbc26704571,61730816,71548928,0,0,805306368,0 +1790923037,metube,fbbc26704571,61730816,71548928,0,0,805306368,0 +1790923039,metube,fbbc26704571,61730816,71548928,0,0,805306368,0 +1790923041,metube,fbbc26704571,61730816,71548928,0,0,805306368,0 +1790923043,metube,fbbc26704571,61730816,71811072,0,0,805306368,0 +1790923045,metube,fbbc26704571,61730816,71614464,0,0,805306368,0 +1790923047,metube,fbbc26704571,61730816,71548928,0,0,805306368,0 +1790923049,metube,fbbc26704571,61730816,71548928,0,0,805306368,0 +1790923051,metube,fbbc26704571,61730816,71548928,0,0,805306368,0 +1790923054,metube,fbbc26704571,61730816,71548928,0,0,805306368,0 +1790923056,metube,fbbc26704571,61730816,71548928,0,0,805306368,0 +1790923058,metube,fbbc26704571,61730816,71548928,0,0,805306368,0 +1790923060,metube,fbbc26704571,61730816,71548928,0,0,805306368,0 +1790923062,metube,fbbc26704571,61730816,71548928,0,0,805306368,0 +1790923064,metube,fbbc26704571,61730816,71548928,0,0,805306368,0 +1790923066,metube,fbbc26704571,61730816,71548928,0,0,805306368,0 +1790923068,metube,fbbc26704571,61730816,71548928,0,0,805306368,0 +1790923070,metube,fbbc26704571,61730816,71548928,0,0,805306368,0 +1790923072,metube,fbbc26704571,61730816,71548928,0,0,805306368,0 +1790923074,metube,fbbc26704571,61730816,71802880,0,0,805306368,0 +1790923077,metube,fbbc26704571,61730816,71802880,0,0,805306368,0 +1790923079,metube,fbbc26704571,61730816,71548928,0,0,805306368,0 +1790923081,metube,fbbc26704571,61730816,71548928,0,0,805306368,0 +1790923083,metube,fbbc26704571,61730816,71548928,0,0,805306368,0 +1790923085,metube,fbbc26704571,61730816,71548928,0,0,805306368,0 +1790923087,metube,fbbc26704571,61730816,71548928,0,0,805306368,0 +1790923089,metube,fbbc26704571,61730816,71548928,0,0,805306368,0 +1790923091,metube,fbbc26704571,61730816,71548928,0,0,805306368,0 +1790923093,metube,fbbc26704571,61730816,71548928,0,0,805306368,0 +1790923095,metube,fbbc26704571,61730816,71548928,0,0,805306368,0 +1790923097,metube,fbbc26704571,61730816,71548928,0,0,805306368,0 +1790923099,metube,fbbc26704571,61730816,71548928,0,0,805306368,0 +1790923101,metube,fbbc26704571,61730816,71548928,0,0,805306368,0 +1790923104,metube,fbbc26704571,61730816,71905280,0,0,805306368,0 +1790923106,metube,fbbc26704571,61730816,71671808,0,0,805306368,0 +1790923108,metube,fbbc26704571,61730816,71557120,0,0,805306368,0 +1790923110,metube,fbbc26704571,61730816,71548928,0,0,805306368,0 +1790923112,metube,fbbc26704571,61730816,71548928,0,0,805306368,0 +1790923114,metube,fbbc26704571,61730816,71548928,0,0,805306368,0 +1790923116,metube,fbbc26704571,61730816,71548928,0,0,805306368,0 +1790923118,metube,fbbc26704571,61730816,71548928,0,0,805306368,0 +1790923120,metube,fbbc26704571,61730816,71548928,0,0,805306368,0 +1790923122,metube,fbbc26704571,61730816,71548928,0,0,805306368,0 +1790923124,metube,fbbc26704571,61730816,71548928,0,0,805306368,0 +1790923126,metube,fbbc26704571,61730816,71548928,0,0,805306368,0 +1790923128,metube,fbbc26704571,61730816,71548928,0,0,805306368,0 +1790923131,metube,fbbc26704571,61730816,71548928,0,0,805306368,0 +1790923133,metube,fbbc26704571,61730816,71548928,0,0,805306368,0 +1790923135,metube,fbbc26704571,61730816,71909376,0,0,805306368,0 +1790923137,metube,fbbc26704571,61730816,71811072,0,0,805306368,0 +1790923139,metube,fbbc26704571,61730816,71548928,0,0,805306368,0 +1790923141,metube,fbbc26704571,61730816,71548928,0,0,805306368,0 +1790923143,metube,fbbc26704571,61730816,71548928,0,0,805306368,0 +1790923145,metube,fbbc26704571,61730816,71548928,0,0,805306368,0 +1790923147,metube,fbbc26704571,61730816,71548928,0,0,805306368,0 +1790923149,metube,fbbc26704571,61730816,71548928,0,0,805306368,0 +1790923151,metube,fbbc26704571,61730816,71548928,0,0,805306368,0 +1790923153,metube,fbbc26704571,61730816,71548928,0,0,805306368,0 +1790923155,metube,fbbc26704571,61730816,71548928,0,0,805306368,0 +1790923158,metube,fbbc26704571,61730816,71548928,0,0,805306368,0 +1790923160,metube,fbbc26704571,61730816,71548928,0,0,805306368,0 +1790923162,metube,fbbc26704571,61730816,71548928,0,0,805306368,0 +1790923164,metube,fbbc26704571,61730816,72138752,0,0,805306368,0 +1790923166,metube,fbbc26704571,61730816,71725056,0,0,805306368,0 +1790923168,metube,fbbc26704571,61730816,71725056,0,0,805306368,0 +1790923170,metube,fbbc26704571,61730816,71548928,0,0,805306368,0 +1790923172,metube,fbbc26704571,61730816,71548928,0,0,805306368,0 +1790923174,metube,fbbc26704571,61730816,71548928,0,0,805306368,0 +1790923176,metube,fbbc26704571,61730816,71548928,0,0,805306368,0 +1790923178,metube,fbbc26704571,61730816,71548928,0,0,805306368,0 +1790923180,metube,fbbc26704571,61730816,71548928,0,0,805306368,0 +1790923182,metube,fbbc26704571,61730816,71548928,0,0,805306368,0 +1790923185,metube,fbbc26704571,61730816,71548928,0,0,805306368,0 +1790923187,metube,fbbc26704571,61730816,71548928,0,0,805306368,0 +1790923189,metube,fbbc26704571,61730816,71548928,0,0,805306368,0 +1790923191,metube,fbbc26704571,61730816,71548928,0,0,805306368,0 +1790923193,metube,fbbc26704571,61730816,71548928,0,0,805306368,0 +1790923195,metube,fbbc26704571,61730816,71663616,0,0,805306368,0 +1790923197,metube,fbbc26704571,61730816,71548928,0,0,805306368,0 +1790923199,metube,fbbc26704571,61730816,71548928,0,0,805306368,0 +1790923201,metube,fbbc26704571,61730816,71548928,0,0,805306368,0 +1790923203,metube,fbbc26704571,61730816,71548928,0,0,805306368,0 +1790923205,metube,fbbc26704571,61730816,71548928,0,0,805306368,0 +1790923207,metube,fbbc26704571,61730816,71548928,0,0,805306368,0 +1790923210,metube,fbbc26704571,61730816,71548928,0,0,805306368,0 +1790923212,metube,fbbc26704571,61730816,71548928,0,0,805306368,0 +1790923214,metube,fbbc26704571,61730816,71548928,0,0,805306368,0 +1790923216,metube,fbbc26704571,61730816,71548928,0,0,805306368,0 +1790923218,metube,fbbc26704571,61730816,71548928,0,0,805306368,0 +1790923220,metube,fbbc26704571,61730816,71548928,0,0,805306368,0 +1790923222,metube,fbbc26704571,61730816,71548928,0,0,805306368,0 +1790923224,metube,fbbc26704571,61730816,72069120,0,0,805306368,0 +1790923226,metube,fbbc26704571,61730816,71548928,0,0,805306368,0 +1790923228,metube,fbbc26704571,61730816,71548928,0,0,805306368,0 +1790923230,metube,fbbc26704571,61730816,71548928,0,0,805306368,0 +1790923232,metube,fbbc26704571,61730816,71548928,0,0,805306368,0 +1790923234,metube,fbbc26704571,61730816,71548928,0,0,805306368,0 +1790923237,metube,fbbc26704571,61730816,71548928,0,0,805306368,0 +1790923239,metube,fbbc26704571,61730816,71548928,0,0,805306368,0 +1790923241,metube,fbbc26704571,61730816,71548928,0,0,805306368,0 +1790923243,metube,fbbc26704571,61730816,71548928,0,0,805306368,0 +1790923245,metube,fbbc26704571,61730816,71548928,0,0,805306368,0 +1790923247,metube,fbbc26704571,61730816,71548928,0,0,805306368,0 +1790923249,metube,fbbc26704571,61730816,71548928,0,0,805306368,0 +1790923251,metube,fbbc26704571,61730816,71548928,0,0,805306368,0 +1790923253,metube,fbbc26704571,61730816,71548928,0,0,805306368,0 +1790923255,metube,fbbc26704571,61730816,71798784,0,0,805306368,0 +1790923257,metube,fbbc26704571,61730816,71548928,0,0,805306368,0 +1790923259,metube,fbbc26704571,61730816,71548928,0,0,805306368,0 +1790923262,metube,fbbc26704571,61730816,71548928,0,0,805306368,0 +1790923264,metube,fbbc26704571,61730816,71548928,0,0,805306368,0 +1790923266,metube,fbbc26704571,61730816,71548928,0,0,805306368,0 +1790923268,metube,fbbc26704571,61730816,71548928,0,0,805306368,0 +1790923270,metube,fbbc26704571,61730816,71548928,0,0,805306368,0 +1790923272,metube,fbbc26704571,61730816,71548928,0,0,805306368,0 +1790923274,metube,fbbc26704571,61730816,71548928,0,0,805306368,0 +1790923276,metube,fbbc26704571,61730816,71548928,0,0,805306368,0 +1790923278,metube,fbbc26704571,61730816,71548928,0,0,805306368,0 +1790923280,metube,fbbc26704571,61730816,71548928,0,0,805306368,0 +1790923282,metube,fbbc26704571,61730816,71548928,0,0,805306368,0 +1790923284,metube,fbbc26704571,61730816,71815168,0,0,805306368,0 +1790923286,metube,fbbc26704571,61730816,71553024,0,0,805306368,0 +1790923289,metube,fbbc26704571,61730816,71548928,0,0,805306368,0 +1790923291,metube,fbbc26704571,61730816,71548928,0,0,805306368,0 +1790923293,metube,fbbc26704571,61730816,71548928,0,0,805306368,0 +1790923295,metube,fbbc26704571,61730816,71548928,0,0,805306368,0 +1790923297,metube,fbbc26704571,61730816,71548928,0,0,805306368,0 +1790923299,metube,fbbc26704571,61730816,71548928,0,0,805306368,0 +1790923301,metube,fbbc26704571,61730816,71548928,0,0,805306368,0 +1790923303,metube,fbbc26704571,61730816,71548928,0,0,805306368,0 +1790923305,metube,fbbc26704571,61730816,71548928,0,0,805306368,0 +1790923307,metube,fbbc26704571,61730816,71548928,0,0,805306368,0 +1790923309,metube,fbbc26704571,61730816,71548928,0,0,805306368,0 +1790923311,metube,fbbc26704571,61730816,71548928,0,0,805306368,0 +1790923314,metube,fbbc26704571,61730816,71548928,0,0,805306368,0 +1790923316,metube,fbbc26704571,61730816,71716864,0,0,805306368,0 +1790923318,metube,fbbc26704571,61730816,71548928,0,0,805306368,0 +1790923320,metube,fbbc26704571,61730816,71548928,0,0,805306368,0 +1790923322,metube,fbbc26704571,61730816,71548928,0,0,805306368,0 +1790923324,metube,fbbc26704571,61730816,71548928,0,0,805306368,0 +1790923326,metube,fbbc26704571,61730816,71548928,0,0,805306368,0 +1790923328,metube,fbbc26704571,61730816,71548928,0,0,805306368,0 +1790923330,metube,fbbc26704571,61730816,71548928,0,0,805306368,0 +1790923332,metube,fbbc26704571,61730816,71548928,0,0,805306368,0 +1790923334,metube,fbbc26704571,61730816,71548928,0,0,805306368,0 +1790923336,metube,fbbc26704571,61730816,71548928,0,0,805306368,0 +1790923338,metube,fbbc26704571,61730816,71548928,0,0,805306368,0 +1790923341,metube,fbbc26704571,61730816,71548928,0,0,805306368,0 +1790923343,metube,fbbc26704571,61730816,71548928,0,0,805306368,0 +1790923345,metube,fbbc26704571,61730816,71766016,0,0,805306368,0 +1790923347,metube,fbbc26704571,61730816,71548928,0,0,805306368,0 +1790923349,metube,fbbc26704571,61730816,71548928,0,0,805306368,0 +1790923351,metube,fbbc26704571,61730816,71548928,0,0,805306368,0 +1790923353,metube,fbbc26704571,61730816,71548928,0,0,805306368,0 +1790923355,metube,fbbc26704571,61730816,71548928,0,0,805306368,0 +1790923357,metube,fbbc26704571,61730816,71548928,0,0,805306368,0 +1790923359,metube,fbbc26704571,61730816,71548928,0,0,805306368,0 +1790923361,metube,fbbc26704571,61730816,71548928,0,0,805306368,0 +1790923363,metube,fbbc26704571,61730816,71548928,0,0,805306368,0 +1790923365,metube,fbbc26704571,61730816,71548928,0,0,805306368,0 +1790923368,metube,fbbc26704571,61730816,71548928,0,0,805306368,0 +1790923370,metube,fbbc26704571,61730816,71548928,0,0,805306368,0 +1790923372,metube,fbbc26704571,61730816,71548928,0,0,805306368,0 +1790923374,metube,fbbc26704571,64618496,76144640,0,0,805306368,0 +1790923376,metube,fbbc26704571,61730816,71553024,0,0,805306368,0 +1790923378,metube,fbbc26704571,61730816,71548928,0,0,805306368,0 +1790923380,metube,fbbc26704571,61730816,71548928,0,0,805306368,0 +1790923382,metube,fbbc26704571,61730816,71548928,0,0,805306368,0 +1790923384,metube,fbbc26704571,61730816,71548928,0,0,805306368,0 +1790923386,metube,fbbc26704571,61730816,71548928,0,0,805306368,0 +1790923388,metube,fbbc26704571,61730816,71548928,0,0,805306368,0 +1790923390,metube,fbbc26704571,61730816,71548928,0,0,805306368,0 +1790923392,metube,fbbc26704571,61730816,71548928,0,0,805306368,0 +1790923395,metube,fbbc26704571,61730816,71548928,0,0,805306368,0 +1790923397,metube,fbbc26704571,61730816,71548928,0,0,805306368,0 +1790923399,metube,fbbc26704571,61730816,71548928,0,0,805306368,0 +1790923401,metube,fbbc26704571,61730816,71548928,0,0,805306368,0 +1790923403,metube,fbbc26704571,61730816,71548928,0,0,805306368,0 +1790923405,metube,fbbc26704571,61730816,72024064,0,0,805306368,0 +1790923407,metube,fbbc26704571,61730816,71548928,0,0,805306368,0 +1790923409,metube,fbbc26704571,61730816,71548928,0,0,805306368,0 +1790923411,metube,fbbc26704571,61730816,71548928,0,0,805306368,0 +1790923413,metube,fbbc26704571,61730816,71548928,0,0,805306368,0 +1790923415,metube,fbbc26704571,61730816,71548928,0,0,805306368,0 +1790923417,metube,fbbc26704571,61730816,71548928,0,0,805306368,0 +1790923420,metube,fbbc26704571,61730816,71548928,0,0,805306368,0 +1790923422,metube,fbbc26704571,61730816,71548928,0,0,805306368,0 +1790923424,metube,fbbc26704571,61730816,71548928,0,0,805306368,0 +1790923426,metube,fbbc26704571,61730816,71548928,0,0,805306368,0 +1790923428,metube,fbbc26704571,61730816,71548928,0,0,805306368,0 +1790923430,metube,fbbc26704571,61730816,71548928,0,0,805306368,0 +1790923432,metube,fbbc26704571,61730816,71548928,0,0,805306368,0 +1790923434,metube,fbbc26704571,61730816,72056832,0,0,805306368,0 +1790923436,metube,fbbc26704571,61730816,71766016,0,0,805306368,0 +1790923438,metube,fbbc26704571,61730816,71553024,0,0,805306368,0 +1790923440,metube,fbbc26704571,61730816,71553024,0,0,805306368,0 +1790923442,metube,fbbc26704571,61730816,71548928,0,0,805306368,0 +1790923444,metube,fbbc26704571,61730816,71548928,0,0,805306368,0 +1790923447,metube,fbbc26704571,61730816,71548928,0,0,805306368,0 +1790923449,metube,fbbc26704571,61730816,71548928,0,0,805306368,0 +1790923451,metube,fbbc26704571,61730816,71548928,0,0,805306368,0 +1790923453,metube,fbbc26704571,61730816,71548928,0,0,805306368,0 +1790923455,metube,fbbc26704571,61730816,71548928,0,0,805306368,0 +1790923457,metube,fbbc26704571,61730816,71548928,0,0,805306368,0 +1790923459,metube,fbbc26704571,61730816,71548928,0,0,805306368,0 +1790923461,metube,fbbc26704571,61730816,71548928,0,0,805306368,0 +1790923463,metube,fbbc26704571,61730816,71548928,0,0,805306368,0 +1790923465,metube,fbbc26704571,61730816,71847936,0,0,805306368,0 +1790923467,metube,fbbc26704571,61730816,71553024,0,0,805306368,0 +1790923469,metube,fbbc26704571,61730816,71553024,0,0,805306368,0 +1790923471,metube,fbbc26704571,61730816,71553024,0,0,805306368,0 +1790923474,metube,fbbc26704571,61730816,71548928,0,0,805306368,0 +1790923476,metube,fbbc26704571,61730816,71548928,0,0,805306368,0 +1790923478,metube,fbbc26704571,61730816,71548928,0,0,805306368,0 +1790923480,metube,fbbc26704571,61730816,71548928,0,0,805306368,0 +1790923482,metube,fbbc26704571,61730816,71548928,0,0,805306368,0 +1790923484,metube,fbbc26704571,61730816,71548928,0,0,805306368,0 +1790923486,metube,fbbc26704571,61730816,71548928,0,0,805306368,0 +1790923488,metube,fbbc26704571,61730816,71548928,0,0,805306368,0 +1790923490,metube,fbbc26704571,61730816,71548928,0,0,805306368,0 +1790923492,metube,fbbc26704571,61730816,71548928,0,0,805306368,0 +1790923494,metube,fbbc26704571,61730816,71979008,0,0,805306368,0 +1790923496,metube,fbbc26704571,61730816,71553024,0,0,805306368,0 +1790923499,metube,fbbc26704571,61730816,71553024,0,0,805306368,0 +1790923501,metube,fbbc26704571,61730816,71548928,0,0,805306368,0 +1790923503,metube,fbbc26704571,61730816,71548928,0,0,805306368,0 +1790923505,metube,fbbc26704571,61730816,71548928,0,0,805306368,0 +1790923507,metube,fbbc26704571,61730816,71548928,0,0,805306368,0 +1790923509,metube,fbbc26704571,61730816,71548928,0,0,805306368,0 +1790923511,metube,fbbc26704571,61730816,71548928,0,0,805306368,0 +1790923513,metube,fbbc26704571,61730816,71548928,0,0,805306368,0 +1790923515,metube,fbbc26704571,61730816,71548928,0,0,805306368,0 +1790923517,metube,fbbc26704571,61730816,71548928,0,0,805306368,0 +1790923519,metube,fbbc26704571,61730816,71548928,0,0,805306368,0 +1790923521,metube,fbbc26704571,61730816,71548928,0,0,805306368,0 +1790923523,metube,fbbc26704571,61730816,71548928,0,0,805306368,0 +1790923526,metube,fbbc26704571,61730816,71802880,0,0,805306368,0 +1790923528,metube,fbbc26704571,61730816,71778304,0,0,805306368,0 +1790923530,metube,fbbc26704571,61730816,71548928,0,0,805306368,0 +1790923532,metube,fbbc26704571,61730816,71548928,0,0,805306368,0 +1790923534,metube,fbbc26704571,61730816,71548928,0,0,805306368,0 +1790923536,metube,fbbc26704571,61730816,71548928,0,0,805306368,0 +1790923538,metube,fbbc26704571,61730816,71548928,0,0,805306368,0 +1790923540,metube,fbbc26704571,61730816,71548928,0,0,805306368,0 +1790923542,metube,fbbc26704571,61730816,71548928,0,0,805306368,0 +1790923544,metube,fbbc26704571,61730816,71548928,0,0,805306368,0 +1790923546,metube,fbbc26704571,61730816,71548928,0,0,805306368,0 +1790923548,metube,fbbc26704571,61730816,71548928,0,0,805306368,0 +1790923550,metube,fbbc26704571,61730816,71548928,0,0,805306368,0 +1790923553,metube,fbbc26704571,61730816,71548928,0,0,805306368,0 +1790923555,metube,fbbc26704571,61730816,72073216,0,0,805306368,0 +1790923557,metube,fbbc26704571,61730816,71561216,0,0,805306368,0 +1790923559,metube,fbbc26704571,61730816,71557120,0,0,805306368,0 +1790923561,metube,fbbc26704571,61730816,71557120,0,0,805306368,0 +1790923563,metube,fbbc26704571,61730816,71553024,0,0,805306368,0 +1790923565,metube,fbbc26704571,61730816,71553024,0,0,805306368,0 +1790923567,metube,fbbc26704571,61730816,71553024,0,0,805306368,0 +1790923569,metube,fbbc26704571,61730816,71553024,0,0,805306368,0 +1790923571,metube,fbbc26704571,61730816,71553024,0,0,805306368,0 +1790923573,metube,fbbc26704571,61730816,71553024,0,0,805306368,0 +1790923575,metube,fbbc26704571,61730816,71553024,0,0,805306368,0 +1790923578,metube,fbbc26704571,61730816,71553024,0,0,805306368,0 +1790923580,metube,fbbc26704571,61730816,71553024,0,0,805306368,0 +1790923582,metube,fbbc26704571,61730816,71553024,0,0,805306368,0 +1790923584,metube,fbbc26704571,61730816,71553024,0,0,805306368,0 +1790923586,metube,fbbc26704571,61730816,71675904,0,0,805306368,0 +1790923588,metube,fbbc26704571,61730816,71585792,0,0,805306368,0 +1790923590,metube,fbbc26704571,61730816,71553024,0,0,805306368,0 +1790923592,metube,fbbc26704571,61730816,71553024,0,0,805306368,0 +1790923594,metube,fbbc26704571,61730816,71553024,0,0,805306368,0 +1790923596,metube,fbbc26704571,61730816,71553024,0,0,805306368,0 +1790923598,metube,fbbc26704571,61730816,71553024,0,0,805306368,0 +1790923600,metube,fbbc26704571,61730816,71553024,0,0,805306368,0 +1790923602,metube,fbbc26704571,61730816,71553024,0,0,805306368,0 +1790923605,metube,fbbc26704571,61730816,71553024,0,0,805306368,0 +1790923607,metube,fbbc26704571,61730816,71553024,0,0,805306368,0 +1790923609,metube,fbbc26704571,61730816,71553024,0,0,805306368,0 +1790923611,metube,fbbc26704571,61730816,71553024,0,0,805306368,0 +1790923613,metube,fbbc26704571,61730816,71553024,0,0,805306368,0 +1790923615,metube,fbbc26704571,61730816,71880704,0,0,805306368,0 +1790923617,metube,fbbc26704571,61730816,71553024,0,0,805306368,0 +1790923619,metube,fbbc26704571,61730816,71553024,0,0,805306368,0 +1790923621,metube,fbbc26704571,61730816,71553024,0,0,805306368,0 +1790923623,metube,fbbc26704571,61730816,71553024,0,0,805306368,0 +1790923625,metube,fbbc26704571,61730816,71553024,0,0,805306368,0 +1790923627,metube,fbbc26704571,61730816,71553024,0,0,805306368,0 +1790923630,metube,fbbc26704571,61730816,71553024,0,0,805306368,0 +1790923632,metube,fbbc26704571,61730816,71815168,0,0,805306368,0 +1790923634,metube,fbbc26704571,61730816,71553024,0,0,805306368,0 +1790923636,metube,fbbc26704571,61730816,71553024,0,0,805306368,0 +1790923638,metube,fbbc26704571,61730816,71553024,0,0,805306368,0 +1790923640,metube,fbbc26704571,61730816,71548928,0,0,805306368,0 +1790923642,metube,fbbc26704571,61730816,71548928,0,0,805306368,0 +1790923644,metube,fbbc26704571,61730816,71548928,0,0,805306368,0 +1790923646,metube,fbbc26704571,61730816,71548928,0,0,805306368,0 +1790923648,metube,fbbc26704571,61730816,71548928,0,0,805306368,0 +1790923650,metube,fbbc26704571,61730816,71548928,0,0,805306368,0 +1790923653,metube,fbbc26704571,61730816,71548928,0,0,805306368,0 +1790923655,metube,fbbc26704571,61730816,71548928,0,0,805306368,0 +1790923657,metube,fbbc26704571,61730816,71548928,0,0,805306368,0 +1790923659,metube,fbbc26704571,61730816,71548928,0,0,805306368,0 +1790923661,metube,fbbc26704571,61730816,71548928,0,0,805306368,0 +1790923663,metube,fbbc26704571,61730816,71548928,0,0,805306368,0 +1790923665,metube,fbbc26704571,61730816,71548928,0,0,805306368,0 +1790923667,metube,fbbc26704571,61730816,71548928,0,0,805306368,0 +1790923669,metube,fbbc26704571,61730816,71548928,0,0,805306368,0 +1790923671,metube,fbbc26704571,61730816,71548928,0,0,805306368,0 +1790923673,metube,fbbc26704571,61730816,71548928,0,0,805306368,0 +1790923675,metube,fbbc26704571,61730816,71548928,0,0,805306368,0 +1790923678,metube,fbbc26704571,61730816,71548928,0,0,805306368,0 +1790923680,metube,fbbc26704571,61730816,71548928,0,0,805306368,0 +1790923682,metube,fbbc26704571,61730816,71548928,0,0,805306368,0 +1790923684,metube,fbbc26704571,61730816,71548928,0,0,805306368,0 +1790923686,metube,fbbc26704571,61730816,71548928,0,0,805306368,0 +1790923688,metube,fbbc26704571,61730816,71548928,0,0,805306368,0 +1790923690,metube,fbbc26704571,61730816,71548928,0,0,805306368,0 +1790923692,metube,fbbc26704571,61730816,71548928,0,0,805306368,0 +1790923694,metube,fbbc26704571,61730816,71548928,0,0,805306368,0 +1790923696,metube,fbbc26704571,61730816,71548928,0,0,805306368,0 +1790923698,metube,fbbc26704571,61730816,71548928,0,0,805306368,0 +1790923700,metube,fbbc26704571,61730816,71548928,0,0,805306368,0 +1790923702,metube,fbbc26704571,61730816,71548928,0,0,805306368,0 +1790923705,metube,fbbc26704571,61730816,72134656,0,0,805306368,0 +1790923707,metube,fbbc26704571,61730816,71569408,0,0,805306368,0 +1790923709,metube,fbbc26704571,61730816,71548928,0,0,805306368,0 +1790923711,metube,fbbc26704571,61730816,71548928,0,0,805306368,0 +1790923713,metube,fbbc26704571,61730816,71548928,0,0,805306368,0 +1790923715,metube,fbbc26704571,61730816,71548928,0,0,805306368,0 +1790923717,metube,fbbc26704571,61730816,71548928,0,0,805306368,0 +1790923719,metube,fbbc26704571,61730816,71548928,0,0,805306368,0 +1790923723,metube,874b43d7ca42,41361408,47796224,0,0,805306368,0 +1790923725,metube,874b43d7ca42,61632512,68202496,0,0,805306368,0 +1790923727,metube,874b43d7ca42,61669376,68382720,0,0,805306368,0 +1790923730,metube,874b43d7ca42,61669376,68218880,0,0,805306368,0 +1790923732,metube,874b43d7ca42,61669376,68214784,0,0,805306368,0 +1790923734,metube,874b43d7ca42,61669376,68214784,0,0,805306368,0 +1790923736,metube,874b43d7ca42,61669376,68218880,0,0,805306368,0 +1790923738,metube,874b43d7ca42,61669376,68218880,0,0,805306368,0 +1790923740,metube,874b43d7ca42,61669376,68214784,0,0,805306368,0 +1790923742,metube,874b43d7ca42,61669376,68214784,0,0,805306368,0 +1790923744,metube,874b43d7ca42,61669376,68214784,0,0,805306368,0 +1790923746,metube,874b43d7ca42,61669376,68214784,0,0,805306368,0 +1790923748,metube,874b43d7ca42,61669376,68214784,0,0,805306368,0 +1790923750,metube,874b43d7ca42,61669376,68214784,0,0,805306368,0 +1790923753,metube,874b43d7ca42,61669376,68214784,0,0,805306368,0 +1790923755,metube,874b43d7ca42,61669376,68214784,0,0,805306368,0 +1790923757,metube,874b43d7ca42,61669376,68214784,0,0,805306368,0 +1790923759,metube,874b43d7ca42,61669376,68591616,0,0,805306368,0 +1790923761,metube,874b43d7ca42,61669376,68366336,0,0,805306368,0 +1790923763,metube,874b43d7ca42,61669376,68218880,0,0,805306368,0 +1790923765,metube,874b43d7ca42,61669376,68218880,0,0,805306368,0 +1790923767,metube,874b43d7ca42,61669376,68218880,0,0,805306368,0 +1790923769,metube,874b43d7ca42,61669376,68218880,0,0,805306368,0 +1790923771,metube,874b43d7ca42,61669376,68218880,0,0,805306368,0 +1790923774,metube,874b43d7ca42,61669376,68218880,0,0,805306368,0 +1790923776,metube,874b43d7ca42,61669376,68218880,0,0,805306368,0 +1790923778,metube,874b43d7ca42,61669376,68218880,0,0,805306368,0 +1790923780,metube,874b43d7ca42,61669376,68218880,0,0,805306368,0 +1790923782,metube,874b43d7ca42,61669376,68218880,0,0,805306368,0 +1790923784,metube,874b43d7ca42,61669376,68218880,0,0,805306368,0 +1790923786,metube,874b43d7ca42,61669376,68218880,0,0,805306368,0 +1790923788,metube,874b43d7ca42,61673472,68222976,0,0,805306368,0 +1790923790,metube,874b43d7ca42,61673472,68222976,0,0,805306368,0 +1790923792,metube,874b43d7ca42,61673472,68222976,0,0,805306368,0 +1790923794,metube,874b43d7ca42,61673472,68222976,0,0,805306368,0 +1790923796,metube,874b43d7ca42,61673472,68222976,0,0,805306368,0 diff --git a/documentation/audits/family-gate-2026-10-02/B/box/metube-fresh-attempt1.txt b/documentation/audits/family-gate-2026-10-02/B/box/metube-fresh-attempt1.txt new file mode 100644 index 00000000..9bfa42aa --- /dev/null +++ b/documentation/audits/family-gate-2026-10-02/B/box/metube-fresh-attempt1.txt @@ -0,0 +1,8 @@ +##### metube-fresh on 9202 — controller gitea.dooplex.hu/admin/felhom-controller:0.287.0 | catalog c3f433f DRILL: grimmory + metube behind the family gate (0.2 | 2026-10-02T06:12:26Z +remove metube (WITH data and backups) -> 409 {'ok': False, 'error': 'A(z) /mnt/felhom-drives/scratch_hdd/userdata/metube tárhely jelenleg nem elérhető — az alkalmazás nem távolítható el, amíg a meghajtó vissza nem csatlakozik.'} + left after: /opt/docker/stacks/metube | metube_metube_state | /mnt/felhom-drives/scratch_hdd/userdata/metube +drill: 08348b6 DRILL metube: ghcr.io/alexta69/metube:2026.09.28 (box proof, family gate 2026-10-02) +the box's catalog now pins: image: ghcr.io/alexta69/metube:2026.09.28 +guest swap: Swap: 512 19 492 (the swap-0 venue for 5.1 is the bench) +deploy metube -> True (0 s to `deployed`) + gate files right after the deploy: family-gate-grimmory.yml family-gate-metube.yml diff --git a/documentation/audits/family-gate-2026-10-02/B/box/metube-fresh-attempt2.txt b/documentation/audits/family-gate-2026-10-02/B/box/metube-fresh-attempt2.txt new file mode 100644 index 00000000..b53b759d --- /dev/null +++ b/documentation/audits/family-gate-2026-10-02/B/box/metube-fresh-attempt2.txt @@ -0,0 +1,5 @@ +##### metube-fresh on 9202 — controller gitea.dooplex.hu/admin/felhom-controller:0.287.0 | catalog 08348b6 DRILL metube: ghcr.io/alexta69/metube:2026.09.28 (bo | 2026-10-02T06:24:15Z +remove metube (WITH data and backups) -> 409 {'ok': False, 'error': 'A(z) /mnt/felhom-drives/scratch_hdd/userdata/metube tárhely jelenleg nem elérhető — az alkalmazás nem távolítható el, amíg a meghajtó vissza nem csatlakozik.'} + refused (R-442, no agent on 9202) — remove metube KEEPING drive data -> 200 {'ok': True, 'data': {'removed': 'metube', 'volumes_removed': ['metube_metube_state'], 'hdd_paths_removed': [], 'hdd_paths_preserved': [], 'backup_paths_removed': ['/mnt/felhom-drives/scratch_hdd/user + the harness removed its own test folder /mnt/felhom-drives/scratch_hdd/userdata/metube by hand + left after: /opt/docker/stacks/metube diff --git a/documentation/audits/family-gate-2026-10-02/B/box/metube-fresh.txt b/documentation/audits/family-gate-2026-10-02/B/box/metube-fresh.txt new file mode 100644 index 00000000..5910295e --- /dev/null +++ b/documentation/audits/family-gate-2026-10-02/B/box/metube-fresh.txt @@ -0,0 +1,22 @@ +##### metube-fresh on 9202 — controller gitea.dooplex.hu/admin/felhom-controller:0.287.0 | catalog 08348b6 DRILL metube: ghcr.io/alexta69/metube:2026.09.28 (bo | 2026-10-02T06:26:13Z +drill: already pins ghcr.io/alexta69/metube:2026.09.28 — nothing to commit +the box's catalog now pins: image: ghcr.io/alexta69/metube:2026.09.28 +guest swap: Swap: 512 19 492 (the swap-0 venue for 5.1 is the bench) +deploy metube -> True (43 s to `deployed`) + gate files right after the deploy: family-gate-grimmory.yml family-gate-metube.yml +4.3 healthy 50 s after the press; restarts: 0 | state: running | image: ghcr.io/alexta69/metube:2026.09.28 + metube: POST /add http=200 {"status": "ok"} + metube: the download finished (991017 bytes) + metube: history has it=True; GET /download/ http=200 +C1 the seed reads back: True +3.3/3.5 a stranger's 63 polls (GET / and POST /add, 1/s) from the press, no cookie: codes {'404 404': 37, '401 401': 26}; any app answer (200): False +2.2/2.8 where the download landed (the household's drive, browsable): /mnt/felhom-drives/scratch_hdd/userdata/metube/userdata/media/metube/Big_Buck_Bunny_360_10s_1MB.mp4 | owner: 1000:1000 644 +2.7 sizes: metube_metube_state=12K 984K /mnt/felhom-drives/scratch_hdd/userdata/metube +1.5 process: /usr/bin/tini -g -- ./docker-entrypoint.sh | children: OCI runtime exec failed: exec failed: unable to start container process: exec: "ps": executable file not found in $PATH +1.8 unknown page (a member): http=404 traceback=False +0.4 outbound connections held at rest: 050012AC:B05E +0.4/0.5 hosts named in the first-start log: ['test-videos.co.uk'] +4.4 negative control: docker STOP metube — state before: running + states while stopped (s, state): [(5, 'running'), (10, 'stopped')]; a member at / -> 404 + running again 10 s after docker start +1.5 (read again from /proc, 2026-10-02T06:29Z): the server process is `python3 app/main.py` (aiohttp, MeTube's production server) under tini; yt-dlp runs in-process — no development server diff --git a/documentation/audits/family-gate-2026-10-02/B/box/metube-step.txt b/documentation/audits/family-gate-2026-10-02/B/box/metube-step.txt new file mode 100644 index 00000000..5ed37836 --- /dev/null +++ b/documentation/audits/family-gate-2026-10-02/B/box/metube-step.txt @@ -0,0 +1,33 @@ +##### metube-step on 9202 — controller gitea.dooplex.hu/admin/felhom-controller:0.287.0 | catalog 08348b6 DRILL metube: ghcr.io/alexta69/metube:2026.09.28 (bo | 2026-10-02T06:29:39Z +before: pinned={'metube': 'ghcr.io/alexta69/metube:2026.09.28'} +drill: 3d8b76d DRILL metube: ghcr.io/alexta69/metube:2026.09.29 (box proof, family gate 2026-10-02) + phase +0.0s backing-up | err=None + phase +2.1s safety-dump | err=None + phase +3.1s pulling | err=None + phase +26.7s copying | err=None + phase +28.7s starting | err=None + phase +29.7s verifying | err=None + phase +34.9s done | err=None + metube: history has it=True; GET /download/ http=200 +2026/10/02 06:29:48 update.go:536: [INFO] [stacks] update metube: accepted — guarded update started +2026/10/02 06:29:48 update.go:1375: [INFO] [stacks] update metube: phase checking +2026/10/02 06:29:48 update.go:793: [INFO] [stacks] update metube: ladder — the last step (1 of 1) — the catalog's current definition +2026/10/02 06:29:48 update.go:823: [INFO] [stacks] update metube: no usable copy on any tier — younger than 24h0m0s and not older than this install's deploy (2026-10-02T06:26:35Z) (found: none) — backing up first +2026/10/02 06:29:48 update.go:1375: [INFO] [stacks] update metube: phase backing-up +2026/10/02 06:29:50 update.go:838: [INFO] [stacks] update metube: precondition met after the backup — Tier 2 (second drive) copy from 2026-10-02T06:29:49Z +2026/10/02 06:29:50 update.go:1375: [INFO] [stacks] update metube: phase safety-dump +2026/10/02 06:29:50 update.go:853: [INFO] [stacks] update metube: safety dump done (0 file(s)) [] +2026/10/02 06:29:50 undo.go:287: [INFO] [stacks] update metube: the undo copy will hold 1 named volume(s), 0.0 MiB +2026/10/02 06:29:50 update.go:1375: [INFO] [stacks] update metube: phase pinning +2026/10/02 06:29:50 pin.go:373: [INFO] [stacks] update metube: pin advanced to /opt/docker/felhom-controller/data/catalog-cache/templates/metube/docker-compose.yml (metube=ghcr.io/alexta69/metube:2026.09.29) +2026/10/02 06:29:50 update.go:1375: [INFO] [stacks] update metube: phase pulling +2026/10/02 06:30:15 update.go:1375: [INFO] [stacks] update metube: phase copying +2026/10/02 06:30:16 update.go:1375: [INFO] [stacks] update metube: phase copying +2026/10/02 06:30:17 undo.go:437: [INFO] [stacks] update metube: copied metube_metube_state → metube_metube_state.pre-update-20261002T063016Z in 453ms +2026/10/02 06:30:17 update.go:1375: [INFO] [stacks] update metube: phase starting +2026/10/02 06:30:17 update.go:1375: [INFO] [stacks] update metube: phase verifying +2026/10/02 06:30:22 update.go:1004: [INFO] [stacks] update metube: healthy after 5s (the app's health check passed) +2026/10/02 06:30:23 update.go:1040: [INFO] [stacks] update metube: DONE in 34s + + gate files after the step: family-gate-grimmory.yml family-gate-metube.yml | a stranger at / -> LAN 401 | tunnel 401 +RESULT final_phase=done after={'metube': 'ghcr.io/alexta69/metube:2026.09.29'} seed_after=True verdict=proven (34.9 s) diff --git a/documentation/audits/family-gate-2026-10-02/B/box/poll-metube.txt b/documentation/audits/family-gate-2026-10-02/B/box/poll-metube.txt new file mode 100644 index 00000000..c32435bd --- /dev/null +++ b/documentation/audits/family-gate-2026-10-02/B/box/poll-metube.txt @@ -0,0 +1,63 @@ +06:26:31 404 404 +06:26:32 404 404 +06:26:33 404 404 +06:26:34 404 404 +06:26:35 404 404 +06:26:36 404 404 +06:26:38 404 404 +06:26:39 404 404 +06:26:40 404 404 +06:26:41 404 404 +06:26:42 404 404 +06:26:43 404 404 +06:26:44 404 404 +06:26:45 404 404 +06:26:46 404 404 +06:26:47 404 404 +06:26:48 404 404 +06:26:49 404 404 +06:26:50 404 404 +06:26:51 404 404 +06:26:52 404 404 +06:26:53 404 404 +06:26:54 404 404 +06:26:55 404 404 +06:26:56 404 404 +06:26:57 404 404 +06:26:59 404 404 +06:27:00 404 404 +06:27:01 404 404 +06:27:02 404 404 +06:27:03 404 404 +06:27:04 404 404 +06:27:05 404 404 +06:27:06 404 404 +06:27:07 404 404 +06:27:08 404 404 +06:27:09 404 404 +06:27:10 401 401 +06:27:11 401 401 +06:27:12 401 401 +06:27:13 401 401 +06:27:14 401 401 +06:27:15 401 401 +06:27:16 401 401 +06:27:17 401 401 +06:27:19 401 401 +06:27:20 401 401 +06:27:21 401 401 +06:27:22 401 401 +06:27:23 401 401 +06:27:24 401 401 +06:27:25 401 401 +06:27:26 401 401 +06:27:27 401 401 +06:27:28 401 401 +06:27:29 401 401 +06:27:30 401 401 +06:27:31 401 401 +06:27:32 401 401 +06:27:33 401 401 +06:27:34 401 401 +06:27:35 401 401 +06:27:36 401 401 diff --git a/documentation/audits/family-gate-2026-10-02/B/catalog-gates-grimmory-run1.txt b/documentation/audits/family-gate-2026-10-02/B/catalog-gates-grimmory-run1.txt new file mode 100644 index 00000000..32d0964c --- /dev/null +++ b/documentation/audits/family-gate-2026-10-02/B/catalog-gates-grimmory-run1.txt @@ -0,0 +1,111 @@ +# catalog_gates.py grimmory - 2026-10-02T07:03:03Z, bench LXC 9401 on demo-hp (debian-13, swap 0, nvme-scratch), catalog-wt de0a9bd + uncommitted working tree (13 path(s), copied with its .git), sibling felhom.eu 68c2290 (git archive HEAD of DooPlex tree = origin/main) at ../felhom.eu +# sibling copy ALSO carries rsync/tar copies of DooPlex UNCOMMITTED documentation/audits/family-gate-2026-10-02/ and documentation/audits/licences-2026-10-02/; B/catalog-gates-grimmory.txt and B/catalog-gates-metube.txt were one-line PLACEHOLDER files in the bench sibling before the run (the onboarding records cite them) - this file is the real output that replaces the placeholder + M FIRST-ADMIN.md + M README.md + M REUSE.md + M scripts/box_walk.py + M scripts/catalog_gates.py + M scripts/copy_freeze/hu.json + M scripts/test_gate_decoys.py + M scripts/upgrade_fixtures_box.py +?? onboarding/grimmory.md +?? onboarding/metube.md +?? scripts/check-family-gate.py +?? templates/grimmory/ +?? templates/metube/ +catalog_gates — apps: grimmory + +============================================================================== +== gate: image-pins (check-image-pins.py) +============================================================================== +image-pin gate OK — 58 templates, 0 unpinned images + +============================================================================== +== gate: image-resolvable (check-image-resolvable.py grimmory) +============================================================================== +resolving 2 unique image pin(s)… +image-resolvability gate OK — 2 unique pins, all resolve + +============================================================================== +== gate: volume-persistence (check-volume-persistence.py grimmory) +============================================================================== +self-testing the prober (both directions)… + prober flags the R-156 signature and clears a correct template — trustworthy +grimmory CLEAN grimmory: this container's mounts are all empty while it created entries in ['/', '/app', '/etc'] — benign when a sibling container holds the state, w + +1 clean · 0 broken · 0 undetermined (of 1 in scope) +volume-persistence gate OK + +============================================================================== +== gate: engine-major (check-engine-major.py) +============================================================================== +engine-major gate — range origin/main..HEAD: 0 compose file(s) changed, 0 engine pin(s) compared +engine-major gate OK — no forbidden engine major, and no engine major bundled with another image move (rule: CLAUDE.md, amended by R-469 2026-09-21) + +============================================================================== +== gate: catalog-since (check-catalog-since.py) +============================================================================== +catalog-since gate — range origin/main..HEAD: 0 compose file(s) changed, 0 image move(s) dated +catalog-since gate OK — every image move in the range carries a catalog_since on or after its commit day + +============================================================================== +== gate: copy-i18n (check-copy-i18n.py grimmory) +============================================================================== +copy-i18n: matcher controls OK (positive „Jelentkezz be…" convicts, negative „Encrypted notes…" does not) +copy-i18n: retrieval-promise vocabulary matches the sibling clone +copy-i18n: 58 apps · 1155 Hungarian copy strings frozen · 1154 with English +copy-i18n: translated so far — actualbudget 15/15, adventurelog 18/18, audiobookshelf 21/21, bentopdf 14/14, bookstack 21/21, calcom 22/22, calibre-web 27/27, claper 20/20, code-server 20/20, crafty-controller 24/24, dawarich 21/21, docmost 19/19, emby 22/22, ghost 16/16, gitea 17/17, glance 15/15, gokapi 19/19, grafana 19/19, gramps-web 17/17, grimmory 24/24, home-assistant 18/18, homebox 17/17, homepage 15/15, immich 24/24, jellyfin 22/22, karakeep 19/19, kimai 20/20, komga 21/21, mealie 19/19, metube 19/19, n8n 17/17, navidrome 21/21, nextcloud 28/28, onlyoffice 24/24, opengist 16/16, outline 21/21, paperless-ngx 33/33, papra 14/15, plant-it 16/16, plex 25/25, privatebin 14/14, radarr 22/22, radicale 19/19, rallly 17/17, recipe-importer 19/19, romm 41/41, seerr 18/18, sonarr 22/22, sparkyfitness 21/21, tandoor 18/18, termix 12/12, uptime-kuma 16/16, vaultwarden 23/23, vikunja 18/18, wanderer 20/20, wger 20/20, wishlist 16/16, zipline 18/18 +copy-i18n: OK + +============================================================================== +== gate: probe-matches-compose (check-probe-matches-compose.py grimmory) +============================================================================== +probe-matches-compose: 1 app(s), mode: full + +probe-matches-compose: OK — every probe dials the port (and, where it can fail, the path) that the app's own compose healthcheck dials + +============================================================================== +== gate: probe-measured (check-probe-measured.py grimmory) +============================================================================== +probe-measured: OK — 1 probes, each with a measured before/after + +============================================================================== +== gate: test-record (check-test-record.py grimmory) +============================================================================== +test-record gate — 1 template(s) read, 1 carry a ladder, 0 convicted + +============================================================================== +== gate: test-record-move (check-test-record-move.py) +============================================================================== +test-record-move gate — range origin/main..HEAD: 0 compose file(s) changed + +============================================================================== +== gate: onboarding (check-onboarding.py) +============================================================================== +onboarding gate — 61 checklist ids; 58 template dirs: 53 exempt (published before 2026-10-01), 5 new; 2 exempt app(s) with a record (shape-checked): sparkyfitness, wger +onboarding gate OK + +============================================================================== +== gate: family-gate (check-family-gate.py) +============================================================================== +family-gate: newest baked golden 0.287.0 >= 0.287.0 +family-gate gate OK: 2 family-gated template(s) ['grimmory', 'metube'] + +============================================================================== +== summary +============================================================================== + image-pins OK (exit 0) + image-resolvable OK (exit 0) + volume-persistence OK (exit 0) + engine-major OK (exit 0) + catalog-since OK (exit 0) + copy-i18n OK (exit 0) + probe-matches-compose OK (exit 0) + probe-measured OK (exit 0) + test-record OK (exit 0) + test-record-move OK (exit 0) + onboarding OK (exit 0) + family-gate OK (exit 0) + +all catalog gates OK +catalog_gates exit code = 0 diff --git a/documentation/audits/family-gate-2026-10-02/B/catalog-gates-grimmory.txt b/documentation/audits/family-gate-2026-10-02/B/catalog-gates-grimmory.txt new file mode 100644 index 00000000..133891af --- /dev/null +++ b/documentation/audits/family-gate-2026-10-02/B/catalog-gates-grimmory.txt @@ -0,0 +1,117 @@ +# catalog_gates.py grimmory - 2026-10-02T07:29:41Z, after the R-801 port fix, bench LXC 9401 on demo-hp (debian-13, swap 0, nvme-scratch), catalog-wt de0a9bd + uncommitted working tree (17 path(s), REFRESHED copy with R-788 APP_EXERCISE + R-801 routed_ports, copied with its .git), sibling felhom.eu 68c2290 (git archive) at ../felhom.eu +# sibling copy ALSO carries tar copies of DooPlex UNCOMMITTED documentation/audits/family-gate-2026-10-02/ and documentation/audits/licences-2026-10-02/; in the bench sibling B/catalog-gates-grimmory.txt = copy of catalog-gates-grimmory-run1.txt and B/catalog-gates-metube.txt = copy of catalog-gates-metube-run2.txt so the onboarding citations resolve +# the previous output of this file is kept as catalog-gates-grimmory-run1.txt + M CHANGELOG.md + M CONTEXT.md + M FIRST-ADMIN.md + M README.md + M REUSE.md + M scripts/box_walk.py + M scripts/catalog_gates.py + M scripts/check-volume-persistence.py + M scripts/copy_freeze/hu.json + M scripts/test_check_volume_persistence.py + M scripts/test_gate_decoys.py + M scripts/upgrade_fixtures_box.py +?? onboarding/grimmory.md +?? onboarding/metube.md +?? scripts/check-family-gate.py +?? templates/grimmory/ +?? templates/metube/ +catalog_gates — apps: grimmory + +============================================================================== +== gate: image-pins (check-image-pins.py) +============================================================================== +image-pin gate OK — 58 templates, 0 unpinned images + +============================================================================== +== gate: image-resolvable (check-image-resolvable.py grimmory) +============================================================================== +resolving 2 unique image pin(s)… +image-resolvability gate OK — 2 unique pins, all resolve + +============================================================================== +== gate: volume-persistence (check-volume-persistence.py grimmory) +============================================================================== +self-testing the prober (both directions)… + prober flags the R-156 signature and clears a correct template — trustworthy +grimmory CLEAN grimmory: this container's mounts are all empty while it created entries in ['/', '/app', '/etc'] — benign when a sibling container holds the state, w + +1 clean · 0 broken · 0 undetermined (of 1 in scope) +volume-persistence gate OK + +============================================================================== +== gate: engine-major (check-engine-major.py) +============================================================================== +engine-major gate — range origin/main..HEAD: 0 compose file(s) changed, 0 engine pin(s) compared +engine-major gate OK — no forbidden engine major, and no engine major bundled with another image move (rule: CLAUDE.md, amended by R-469 2026-09-21) + +============================================================================== +== gate: catalog-since (check-catalog-since.py) +============================================================================== +catalog-since gate — range origin/main..HEAD: 0 compose file(s) changed, 0 image move(s) dated +catalog-since gate OK — every image move in the range carries a catalog_since on or after its commit day + +============================================================================== +== gate: copy-i18n (check-copy-i18n.py grimmory) +============================================================================== +copy-i18n: matcher controls OK (positive „Jelentkezz be…" convicts, negative „Encrypted notes…" does not) +copy-i18n: retrieval-promise vocabulary matches the sibling clone +copy-i18n: 58 apps · 1155 Hungarian copy strings frozen · 1154 with English +copy-i18n: translated so far — actualbudget 15/15, adventurelog 18/18, audiobookshelf 21/21, bentopdf 14/14, bookstack 21/21, calcom 22/22, calibre-web 27/27, claper 20/20, code-server 20/20, crafty-controller 24/24, dawarich 21/21, docmost 19/19, emby 22/22, ghost 16/16, gitea 17/17, glance 15/15, gokapi 19/19, grafana 19/19, gramps-web 17/17, grimmory 24/24, home-assistant 18/18, homebox 17/17, homepage 15/15, immich 24/24, jellyfin 22/22, karakeep 19/19, kimai 20/20, komga 21/21, mealie 19/19, metube 19/19, n8n 17/17, navidrome 21/21, nextcloud 28/28, onlyoffice 24/24, opengist 16/16, outline 21/21, paperless-ngx 33/33, papra 14/15, plant-it 16/16, plex 25/25, privatebin 14/14, radarr 22/22, radicale 19/19, rallly 17/17, recipe-importer 19/19, romm 41/41, seerr 18/18, sonarr 22/22, sparkyfitness 21/21, tandoor 18/18, termix 12/12, uptime-kuma 16/16, vaultwarden 23/23, vikunja 18/18, wanderer 20/20, wger 20/20, wishlist 16/16, zipline 18/18 +copy-i18n: OK + +============================================================================== +== gate: probe-matches-compose (check-probe-matches-compose.py grimmory) +============================================================================== +probe-matches-compose: 1 app(s), mode: full + +probe-matches-compose: OK — every probe dials the port (and, where it can fail, the path) that the app's own compose healthcheck dials + +============================================================================== +== gate: probe-measured (check-probe-measured.py grimmory) +============================================================================== +probe-measured: OK — 1 probes, each with a measured before/after + +============================================================================== +== gate: test-record (check-test-record.py grimmory) +============================================================================== +test-record gate — 1 template(s) read, 1 carry a ladder, 0 convicted + +============================================================================== +== gate: test-record-move (check-test-record-move.py) +============================================================================== +test-record-move gate — range origin/main..HEAD: 0 compose file(s) changed + +============================================================================== +== gate: onboarding (check-onboarding.py) +============================================================================== +onboarding gate — 61 checklist ids; 58 template dirs: 53 exempt (published before 2026-10-01), 5 new; 2 exempt app(s) with a record (shape-checked): sparkyfitness, wger +onboarding gate OK + +============================================================================== +== gate: family-gate (check-family-gate.py) +============================================================================== +family-gate: newest baked golden 0.287.0 >= 0.287.0 +family-gate gate OK: 2 family-gated template(s) ['grimmory', 'metube'] + +============================================================================== +== summary +============================================================================== + image-pins OK (exit 0) + image-resolvable OK (exit 0) + volume-persistence OK (exit 0) + engine-major OK (exit 0) + catalog-since OK (exit 0) + copy-i18n OK (exit 0) + probe-matches-compose OK (exit 0) + probe-measured OK (exit 0) + test-record OK (exit 0) + test-record-move OK (exit 0) + onboarding OK (exit 0) + family-gate OK (exit 0) + +all catalog gates OK +catalog_gates exit code = 0 +# leftover containers: 0 diff --git a/documentation/audits/family-gate-2026-10-02/B/catalog-gates-metube-run1.txt b/documentation/audits/family-gate-2026-10-02/B/catalog-gates-metube-run1.txt new file mode 100644 index 00000000..6bd38c65 --- /dev/null +++ b/documentation/audits/family-gate-2026-10-02/B/catalog-gates-metube-run1.txt @@ -0,0 +1,113 @@ +# catalog_gates.py metube - 2026-10-02T07:06:40Z, bench LXC 9401 on demo-hp (debian-13, swap 0, nvme-scratch), catalog-wt de0a9bd + uncommitted working tree (13 path(s), copied with its .git), sibling felhom.eu 68c2290 (git archive HEAD of DooPlex tree = origin/main) at ../felhom.eu +# sibling copy ALSO carries rsync/tar copies of DooPlex UNCOMMITTED documentation/audits/family-gate-2026-10-02/ and documentation/audits/licences-2026-10-02/; B/catalog-gates-grimmory.txt and B/catalog-gates-metube.txt were one-line PLACEHOLDER files in the bench sibling before the run (the onboarding records cite them) - this file is the real output that replaces the placeholder + M FIRST-ADMIN.md + M README.md + M REUSE.md + M scripts/box_walk.py + M scripts/catalog_gates.py + M scripts/copy_freeze/hu.json + M scripts/test_gate_decoys.py + M scripts/upgrade_fixtures_box.py +?? onboarding/grimmory.md +?? onboarding/metube.md +?? scripts/check-family-gate.py +?? templates/grimmory/ +?? templates/metube/ +catalog_gates — apps: metube + +============================================================================== +== gate: image-pins (check-image-pins.py) +============================================================================== +image-pin gate OK — 58 templates, 0 unpinned images + +============================================================================== +== gate: image-resolvable (check-image-resolvable.py metube) +============================================================================== +resolving 1 unique image pin(s)… +image-resolvability gate OK — 1 unique pins, all resolve + +============================================================================== +== gate: volume-persistence (check-volume-persistence.py metube) +============================================================================== +self-testing the prober (both directions)… + prober flags the R-156 signature and clears a correct template — trustworthy +metube UNDETERMINED NOTHING this app wrote landed in ANY folder the template preserves: all 2 mount(s) across 1 container(s) are empty, yet entries were created in ['/']. + +UNDETERMINED — NOT a pass. The question was not answered for these: + metube: NOTHING this app wrote landed in ANY folder the template preserves: all 2 mount(s) across 1 container(s) are empty, yet entries were created in ['/']. Needs adjudication.; metube: this container's mou + +0 clean · 0 broken · 1 undetermined (of 1 in scope) +INCOMPLETE — no broken template among those decided, but 1 were never decided. This is not a clean bill of health. + +============================================================================== +== gate: engine-major (check-engine-major.py) +============================================================================== +engine-major gate — range origin/main..HEAD: 0 compose file(s) changed, 0 engine pin(s) compared +engine-major gate OK — no forbidden engine major, and no engine major bundled with another image move (rule: CLAUDE.md, amended by R-469 2026-09-21) + +============================================================================== +== gate: catalog-since (check-catalog-since.py) +============================================================================== +catalog-since gate — range origin/main..HEAD: 0 compose file(s) changed, 0 image move(s) dated +catalog-since gate OK — every image move in the range carries a catalog_since on or after its commit day + +============================================================================== +== gate: copy-i18n (check-copy-i18n.py metube) +============================================================================== +copy-i18n: matcher controls OK (positive „Jelentkezz be…" convicts, negative „Encrypted notes…" does not) +copy-i18n: retrieval-promise vocabulary matches the sibling clone +copy-i18n: 58 apps · 1155 Hungarian copy strings frozen · 1154 with English +copy-i18n: translated so far — actualbudget 15/15, adventurelog 18/18, audiobookshelf 21/21, bentopdf 14/14, bookstack 21/21, calcom 22/22, calibre-web 27/27, claper 20/20, code-server 20/20, crafty-controller 24/24, dawarich 21/21, docmost 19/19, emby 22/22, ghost 16/16, gitea 17/17, glance 15/15, gokapi 19/19, grafana 19/19, gramps-web 17/17, grimmory 24/24, home-assistant 18/18, homebox 17/17, homepage 15/15, immich 24/24, jellyfin 22/22, karakeep 19/19, kimai 20/20, komga 21/21, mealie 19/19, metube 19/19, n8n 17/17, navidrome 21/21, nextcloud 28/28, onlyoffice 24/24, opengist 16/16, outline 21/21, paperless-ngx 33/33, papra 14/15, plant-it 16/16, plex 25/25, privatebin 14/14, radarr 22/22, radicale 19/19, rallly 17/17, recipe-importer 19/19, romm 41/41, seerr 18/18, sonarr 22/22, sparkyfitness 21/21, tandoor 18/18, termix 12/12, uptime-kuma 16/16, vaultwarden 23/23, vikunja 18/18, wanderer 20/20, wger 20/20, wishlist 16/16, zipline 18/18 +copy-i18n: OK + +============================================================================== +== gate: probe-matches-compose (check-probe-matches-compose.py metube) +============================================================================== +probe-matches-compose: 1 app(s), mode: full + +probe-matches-compose: OK — every probe dials the port (and, where it can fail, the path) that the app's own compose healthcheck dials + +============================================================================== +== gate: probe-measured (check-probe-measured.py metube) +============================================================================== +probe-measured: OK — 0 probes, each with a measured before/after + +============================================================================== +== gate: test-record (check-test-record.py metube) +============================================================================== +test-record gate — 1 template(s) read, 1 carry a ladder, 0 convicted + +============================================================================== +== gate: test-record-move (check-test-record-move.py) +============================================================================== +test-record-move gate — range origin/main..HEAD: 0 compose file(s) changed + +============================================================================== +== gate: onboarding (check-onboarding.py) +============================================================================== +onboarding gate — 61 checklist ids; 58 template dirs: 53 exempt (published before 2026-10-01), 5 new; 2 exempt app(s) with a record (shape-checked): sparkyfitness, wger +onboarding gate OK + +============================================================================== +== gate: family-gate (check-family-gate.py) +============================================================================== +family-gate: newest baked golden 0.287.0 >= 0.287.0 +family-gate gate OK: 2 family-gated template(s) ['grimmory', 'metube'] + +============================================================================== +== summary +============================================================================== + image-pins OK (exit 0) + image-resolvable OK (exit 0) + volume-persistence INCONCLUSIVE (exit 2) + engine-major OK (exit 0) + catalog-since OK (exit 0) + copy-i18n OK (exit 0) + probe-matches-compose OK (exit 0) + probe-measured OK (exit 0) + test-record OK (exit 0) + test-record-move OK (exit 0) + onboarding OK (exit 0) + family-gate OK (exit 0) +UNDETERMINED (never a pass): volume-persistence +catalog_gates exit code = 2 diff --git a/documentation/audits/family-gate-2026-10-02/B/catalog-gates-metube-run2.txt b/documentation/audits/family-gate-2026-10-02/B/catalog-gates-metube-run2.txt new file mode 100644 index 00000000..840aece0 --- /dev/null +++ b/documentation/audits/family-gate-2026-10-02/B/catalog-gates-metube-run2.txt @@ -0,0 +1,118 @@ +# catalog_gates.py metube - re-run after the R-788 exerciser - 2026-10-02T07:17:56Z, bench LXC 9401 on demo-hp (debian-13, swap 0, nvme-scratch), catalog-wt de0a9bd + uncommitted working tree (16 path(s), REFRESHED copy with check-volume-persistence.py APP_EXERCISE R-788, copied with its .git), sibling felhom.eu 68c2290 (git archive) at ../felhom.eu +# sibling copy ALSO carries tar copies of DooPlex UNCOMMITTED documentation/audits/family-gate-2026-10-02/ and documentation/audits/licences-2026-10-02/ (real B/catalog-gates-grimmory.txt; B/catalog-gates-metube.txt = the run-1 output, copied from catalog-gates-metube-run1.txt so the onboarding citation resolves) +# the previous output of this file is kept as catalog-gates-metube-run1.txt + M CHANGELOG.md + M CONTEXT.md + M FIRST-ADMIN.md + M README.md + M REUSE.md + M scripts/box_walk.py + M scripts/catalog_gates.py + M scripts/check-volume-persistence.py + M scripts/copy_freeze/hu.json + M scripts/test_gate_decoys.py + M scripts/upgrade_fixtures_box.py +?? onboarding/grimmory.md +?? onboarding/metube.md +?? scripts/check-family-gate.py +?? templates/grimmory/ +?? templates/metube/ +catalog_gates — apps: metube + +============================================================================== +== gate: image-pins (check-image-pins.py) +============================================================================== +image-pin gate OK — 58 templates, 0 unpinned images + +============================================================================== +== gate: image-resolvable (check-image-resolvable.py metube) +============================================================================== +resolving 1 unique image pin(s)… +image-resolvability gate OK — 1 unique pins, all resolve + +============================================================================== +== gate: volume-persistence (check-volume-persistence.py metube) +============================================================================== +self-testing the prober (both directions)… + prober flags the R-156 signature and clears a correct template — trustworthy +metube UNDETERMINED NOTHING this app wrote landed in ANY folder the template preserves: all 2 mount(s) across 1 container(s) are empty, yet entries were created in ['/']. + +UNDETERMINED — NOT a pass. The question was not answered for these: + metube: NOTHING this app wrote landed in ANY folder the template preserves: all 2 mount(s) across 1 container(s) are empty, yet entries were created in ['/']. Needs adjudication.; metube: this container's mou + +0 clean · 0 broken · 1 undetermined (of 1 in scope) +INCOMPLETE — no broken template among those decided, but 1 were never decided. This is not a clean bill of health. + +============================================================================== +== gate: engine-major (check-engine-major.py) +============================================================================== +engine-major gate — range origin/main..HEAD: 0 compose file(s) changed, 0 engine pin(s) compared +engine-major gate OK — no forbidden engine major, and no engine major bundled with another image move (rule: CLAUDE.md, amended by R-469 2026-09-21) + +============================================================================== +== gate: catalog-since (check-catalog-since.py) +============================================================================== +catalog-since gate — range origin/main..HEAD: 0 compose file(s) changed, 0 image move(s) dated +catalog-since gate OK — every image move in the range carries a catalog_since on or after its commit day + +============================================================================== +== gate: copy-i18n (check-copy-i18n.py metube) +============================================================================== +copy-i18n: matcher controls OK (positive „Jelentkezz be…" convicts, negative „Encrypted notes…" does not) +copy-i18n: retrieval-promise vocabulary matches the sibling clone +copy-i18n: 58 apps · 1155 Hungarian copy strings frozen · 1154 with English +copy-i18n: translated so far — actualbudget 15/15, adventurelog 18/18, audiobookshelf 21/21, bentopdf 14/14, bookstack 21/21, calcom 22/22, calibre-web 27/27, claper 20/20, code-server 20/20, crafty-controller 24/24, dawarich 21/21, docmost 19/19, emby 22/22, ghost 16/16, gitea 17/17, glance 15/15, gokapi 19/19, grafana 19/19, gramps-web 17/17, grimmory 24/24, home-assistant 18/18, homebox 17/17, homepage 15/15, immich 24/24, jellyfin 22/22, karakeep 19/19, kimai 20/20, komga 21/21, mealie 19/19, metube 19/19, n8n 17/17, navidrome 21/21, nextcloud 28/28, onlyoffice 24/24, opengist 16/16, outline 21/21, paperless-ngx 33/33, papra 14/15, plant-it 16/16, plex 25/25, privatebin 14/14, radarr 22/22, radicale 19/19, rallly 17/17, recipe-importer 19/19, romm 41/41, seerr 18/18, sonarr 22/22, sparkyfitness 21/21, tandoor 18/18, termix 12/12, uptime-kuma 16/16, vaultwarden 23/23, vikunja 18/18, wanderer 20/20, wger 20/20, wishlist 16/16, zipline 18/18 +copy-i18n: OK + +============================================================================== +== gate: probe-matches-compose (check-probe-matches-compose.py metube) +============================================================================== +probe-matches-compose: 1 app(s), mode: full + +probe-matches-compose: OK — every probe dials the port (and, where it can fail, the path) that the app's own compose healthcheck dials + +============================================================================== +== gate: probe-measured (check-probe-measured.py metube) +============================================================================== +probe-measured: OK — 0 probes, each with a measured before/after + +============================================================================== +== gate: test-record (check-test-record.py metube) +============================================================================== +test-record gate — 1 template(s) read, 1 carry a ladder, 0 convicted + +============================================================================== +== gate: test-record-move (check-test-record-move.py) +============================================================================== +test-record-move gate — range origin/main..HEAD: 0 compose file(s) changed + +============================================================================== +== gate: onboarding (check-onboarding.py) +============================================================================== +onboarding gate — 61 checklist ids; 58 template dirs: 53 exempt (published before 2026-10-01), 5 new; 2 exempt app(s) with a record (shape-checked): sparkyfitness, wger +onboarding gate OK + +============================================================================== +== gate: family-gate (check-family-gate.py) +============================================================================== +family-gate: newest baked golden 0.287.0 >= 0.287.0 +family-gate gate OK: 2 family-gated template(s) ['grimmory', 'metube'] + +============================================================================== +== summary +============================================================================== + image-pins OK (exit 0) + image-resolvable OK (exit 0) + volume-persistence INCONCLUSIVE (exit 2) + engine-major OK (exit 0) + catalog-since OK (exit 0) + copy-i18n OK (exit 0) + probe-matches-compose OK (exit 0) + probe-measured OK (exit 0) + test-record OK (exit 0) + test-record-move OK (exit 0) + onboarding OK (exit 0) + family-gate OK (exit 0) +UNDETERMINED (never a pass): volume-persistence +catalog_gates exit code = 2 +0 diff --git a/documentation/audits/family-gate-2026-10-02/B/catalog-gates-metube.txt b/documentation/audits/family-gate-2026-10-02/B/catalog-gates-metube.txt new file mode 100644 index 00000000..c98725cc --- /dev/null +++ b/documentation/audits/family-gate-2026-10-02/B/catalog-gates-metube.txt @@ -0,0 +1,117 @@ +# catalog_gates.py metube - 2026-10-02T07:32:48Z, after the R-801 port fix, bench LXC 9401 on demo-hp (debian-13, swap 0, nvme-scratch), catalog-wt de0a9bd + uncommitted working tree (17 path(s), REFRESHED copy with R-788 APP_EXERCISE + R-801 routed_ports, copied with its .git), sibling felhom.eu 68c2290 (git archive) at ../felhom.eu +# sibling copy ALSO carries tar copies of DooPlex UNCOMMITTED documentation/audits/family-gate-2026-10-02/ and documentation/audits/licences-2026-10-02/; in the bench sibling B/catalog-gates-grimmory.txt = copy of catalog-gates-grimmory-run1.txt and B/catalog-gates-metube.txt = copy of catalog-gates-metube-run2.txt so the onboarding citations resolve +# the previous output of this file is kept as catalog-gates-metube-run2.txt + M CHANGELOG.md + M CONTEXT.md + M FIRST-ADMIN.md + M README.md + M REUSE.md + M scripts/box_walk.py + M scripts/catalog_gates.py + M scripts/check-volume-persistence.py + M scripts/copy_freeze/hu.json + M scripts/test_check_volume_persistence.py + M scripts/test_gate_decoys.py + M scripts/upgrade_fixtures_box.py +?? onboarding/grimmory.md +?? onboarding/metube.md +?? scripts/check-family-gate.py +?? templates/grimmory/ +?? templates/metube/ +catalog_gates — apps: metube + +============================================================================== +== gate: image-pins (check-image-pins.py) +============================================================================== +image-pin gate OK — 58 templates, 0 unpinned images + +============================================================================== +== gate: image-resolvable (check-image-resolvable.py metube) +============================================================================== +resolving 1 unique image pin(s)… +image-resolvability gate OK — 1 unique pins, all resolve + +============================================================================== +== gate: volume-persistence (check-volume-persistence.py metube) +============================================================================== +self-testing the prober (both directions)… + prober flags the R-156 signature and clears a correct template — trustworthy +metube CLEAN + +1 clean · 0 broken · 0 undetermined (of 1 in scope) +volume-persistence gate OK + +============================================================================== +== gate: engine-major (check-engine-major.py) +============================================================================== +engine-major gate — range origin/main..HEAD: 0 compose file(s) changed, 0 engine pin(s) compared +engine-major gate OK — no forbidden engine major, and no engine major bundled with another image move (rule: CLAUDE.md, amended by R-469 2026-09-21) + +============================================================================== +== gate: catalog-since (check-catalog-since.py) +============================================================================== +catalog-since gate — range origin/main..HEAD: 0 compose file(s) changed, 0 image move(s) dated +catalog-since gate OK — every image move in the range carries a catalog_since on or after its commit day + +============================================================================== +== gate: copy-i18n (check-copy-i18n.py metube) +============================================================================== +copy-i18n: matcher controls OK (positive „Jelentkezz be…" convicts, negative „Encrypted notes…" does not) +copy-i18n: retrieval-promise vocabulary matches the sibling clone +copy-i18n: 58 apps · 1155 Hungarian copy strings frozen · 1154 with English +copy-i18n: translated so far — actualbudget 15/15, adventurelog 18/18, audiobookshelf 21/21, bentopdf 14/14, bookstack 21/21, calcom 22/22, calibre-web 27/27, claper 20/20, code-server 20/20, crafty-controller 24/24, dawarich 21/21, docmost 19/19, emby 22/22, ghost 16/16, gitea 17/17, glance 15/15, gokapi 19/19, grafana 19/19, gramps-web 17/17, grimmory 24/24, home-assistant 18/18, homebox 17/17, homepage 15/15, immich 24/24, jellyfin 22/22, karakeep 19/19, kimai 20/20, komga 21/21, mealie 19/19, metube 19/19, n8n 17/17, navidrome 21/21, nextcloud 28/28, onlyoffice 24/24, opengist 16/16, outline 21/21, paperless-ngx 33/33, papra 14/15, plant-it 16/16, plex 25/25, privatebin 14/14, radarr 22/22, radicale 19/19, rallly 17/17, recipe-importer 19/19, romm 41/41, seerr 18/18, sonarr 22/22, sparkyfitness 21/21, tandoor 18/18, termix 12/12, uptime-kuma 16/16, vaultwarden 23/23, vikunja 18/18, wanderer 20/20, wger 20/20, wishlist 16/16, zipline 18/18 +copy-i18n: OK + +============================================================================== +== gate: probe-matches-compose (check-probe-matches-compose.py metube) +============================================================================== +probe-matches-compose: 1 app(s), mode: full + +probe-matches-compose: OK — every probe dials the port (and, where it can fail, the path) that the app's own compose healthcheck dials + +============================================================================== +== gate: probe-measured (check-probe-measured.py metube) +============================================================================== +probe-measured: OK — 0 probes, each with a measured before/after + +============================================================================== +== gate: test-record (check-test-record.py metube) +============================================================================== +test-record gate — 1 template(s) read, 1 carry a ladder, 0 convicted + +============================================================================== +== gate: test-record-move (check-test-record-move.py) +============================================================================== +test-record-move gate — range origin/main..HEAD: 0 compose file(s) changed + +============================================================================== +== gate: onboarding (check-onboarding.py) +============================================================================== +onboarding gate — 61 checklist ids; 58 template dirs: 53 exempt (published before 2026-10-01), 5 new; 2 exempt app(s) with a record (shape-checked): sparkyfitness, wger +onboarding gate OK + +============================================================================== +== gate: family-gate (check-family-gate.py) +============================================================================== +family-gate: newest baked golden 0.287.0 >= 0.287.0 +family-gate gate OK: 2 family-gated template(s) ['grimmory', 'metube'] + +============================================================================== +== summary +============================================================================== + image-pins OK (exit 0) + image-resolvable OK (exit 0) + volume-persistence OK (exit 0) + engine-major OK (exit 0) + catalog-since OK (exit 0) + copy-i18n OK (exit 0) + probe-matches-compose OK (exit 0) + probe-measured OK (exit 0) + test-record OK (exit 0) + test-record-move OK (exit 0) + onboarding OK (exit 0) + family-gate OK (exit 0) + +all catalog gates OK +catalog_gates exit code = 0 +# leftover containers: 0 diff --git a/documentation/audits/family-gate-2026-10-02/B/family-gate-decoys-red.txt b/documentation/audits/family-gate-2026-10-02/B/family-gate-decoys-red.txt new file mode 100644 index 00000000..b42fd765 --- /dev/null +++ b/documentation/audits/family-gate-2026-10-02/B/family-gate-decoys-red.txt @@ -0,0 +1,6 @@ +## gate mutant: 'if not LITERAL.match(p) or' -> 'if False and not LITERAL.match(p) or' + XX FACT: an exception that is a regex rc=0 (expected 1) +## gate mutant: 'if baked:' -> 'if True:' + XX FACT: a golden DIRECTORY 0.287.0 with no bake log (a name is not a bake) rc=0 (expected 1) +## gate mutant: 'if TOP_TRUE.match(ln):' -> 'if "family_gate: true" in ln:' + XX DECOY: family_gate only in a COMMENT -> not gated, nothing owed rc=1 (expected 0) diff --git a/documentation/audits/family-gate-2026-10-02/B/grimmory-reads/registry.txt b/documentation/audits/family-gate-2026-10-02/B/grimmory-reads/registry.txt new file mode 100644 index 00000000..50ca5865 --- /dev/null +++ b/documentation/audits/family-gate-2026-10-02/B/grimmory-reads/registry.txt @@ -0,0 +1,8 @@ +# registry read from DooPlex, 2026-10-02T06:28:27Z — ghcr.io/grimmory-tools/grimmory (anonymous pull token, no image pulled) +## tags (all) +249 tags; non-sha: ['nightly-20260905-0279c15dc', 'nightly-20260906-703c7f29a', 'nightly-20260907-5186f850a', 'nightly-20260908-75495bf5f', 'nightly-20260909-53abc8bf8', 'nightly-20260910-11224eb9c', 'nightly-20260911-83dede522', 'nightly-20260912-7d768a287', 'nightly-20260913-71637619f', 'nightly-20260914-7a9cd3107', 'nightly-20260914-9b38a091e', 'v3.4.0', 'v3.4', 'nightly-20260915-e6ac37f03', 'nightly-20260915-8f19bbb07', 'v3.4.1', 'nightly-20260916-96702f0ef', 'nightly-20260917-2d7a3e9c5', 'nightly-20260918-5986e3660', 'nightly-20260919-6a778f82e', 'nightly-20260919-1042cad7a', 'nightly-20260920-569fd6687', 'nightly-20260920-402e89b44', 'v3.5.0', 'v3.5', 'nightly-20260921-bcac7ff16', 'nightly-20260922-6b70d5466', 'nightly-20260923-6b70d5466', 'nightly-20260924-6b70d5466', 'nightly-20260925-cb6fd48c7', 'nightly-20260926-839e02fc9', 'nightly-20260926-18b0c8cfb', 'nightly-20260927-bd94696eb', 'nightly-20260927-e933da740', 'nightly-20260928-e937943c4', 'nightly-20260928-5b3a8aa71', 'nightly-20260929-5b3a8aa71', 'nightly-20260930-5b3a8aa71', 'nightly-20261001-ab6d4af42', 'nightly-20261002-84a6097f8'] +## v3.5.0 manifest platforms +[('amd64', None), ('arm64', None)] +## mariadb:11.4 (Docker Hub) platforms +['amd64', 'arm64', 'ppc64le', 's390x'] +## release tags (v-prefixed, plus the floating v. and latest): ['v2.2.4', 'latest', 'v2.2.5', 'v2.2.6', 'v2.2.7', 'v2.3.0', 'v2.3.1', 'v3.0.0', 'v3.0.1', 'v3.0.2', 'v3.0.3', 'v3.0', 'v3.1.0', 'v3.1', 'v3.2.0', 'v3.2', 'v3.2.1', 'v3.2.2', 'v3.2.4', 'v3.3.0', 'v3.3', 'v3.3.1', 'v3.3.2', 'v3.3.3', 'v3.4.0', 'v3.4', 'v3.4.1', 'v3.5.0', 'v3.5'] diff --git a/documentation/audits/family-gate-2026-10-02/B/grimmory-reads/upstream-v3.5.0.txt b/documentation/audits/family-gate-2026-10-02/B/grimmory-reads/upstream-v3.5.0.txt new file mode 100644 index 00000000..a1cbdcc1 --- /dev/null +++ b/documentation/audits/family-gate-2026-10-02/B/grimmory-reads/upstream-v3.5.0.txt @@ -0,0 +1,189 @@ +# Grimmory v3.5.0 — upstream source READ over HTTPS (raw.githubusercontent.com at the tag), 2026-10-02T06:28:57Z. No box touched. + +## 1.7 the image's start — Dockerfile (runtime stage) and packaging/docker/entrypoint.sh +FROM eclipse-temurin:25-jre-alpine + +ENV JAVA_TOOL_OPTIONS="-XX:+UseShenandoahGC \ + -XX:ShenandoahGCHeuristics=compact \ + -XX:+UseCompactObjectHeaders \ + -XX:MaxRAMPercentage=60.0 \ + -XX:InitialRAMPercentage=8.0 \ + -XX:+ExitOnOutOfMemoryError \ + -XX:MaxMetaspaceSize=256m \ + -XX:ReservedCodeCacheSize=48m \ + -Xss512k \ + -XX:CICompilerCount=2 \ + -XX:+UnlockExperimentalVMOptions \ + -XX:+UseStringDeduplication \ + -XX:ShenandoahUncommitDelay=5000 \ + -XX:ShenandoahGuaranteedGCInterval=30000 \ + -XX:MaxDirectMemorySize=256m \ + --enable-native-access=ALL-UNNAMED \ + --enable-preview" + +RUN apk add --no-cache su-exec libstdc++ libgcc libarchive && \ + mkdir -p /bookdrop + +# Manually link `libarchive.so.13` so java and other libraries can see it +RUN ln -s /usr/lib/libarchive.so.13 /usr/lib/libarchive.so + +COPY packaging/docker/entrypoint.sh /usr/local/bin/entrypoint.sh +RUN chmod +x /usr/local/bin/entrypoint.sh + +COPY --from=ffprobe-layer /ffprobe /usr/local/bin/ffprobe +COPY --from=kepubify-layer /kepubify /usr/local/bin/kepubify + +COPY --from=backend-build /workspace/backend/app.jar /app/app.jar + +ARG APP_VERSION=development +ARG APP_REVISION=unknown + +LABEL org.opencontainers.image.title="Grimmory" \ + org.opencontainers.image.description="Grimmory: a self-hosted, multi-user digital library with smart shelves, auto metadata, Kobo and KOReader sync, BookDrop imports, OPDS support, and a built-in reader for EPUB, PDF, and comics." \ + org.opencontainers.image.source="https://github.com/grimmory-tools/grimmory" \ + org.opencontainers.image.url="https://github.com/grimmory-tools/grimmory" \ + org.opencontainers.image.documentation="https://grimmory.org/docs/getting-started" \ + org.opencontainers.image.version=$APP_VERSION \ + org.opencontainers.image.revision=$APP_REVISION \ + org.opencontainers.image.licenses="AGPL-3.0" \ + org.opencontainers.image.base.name="docker.io/library/eclipse-temurin:25-jre-alpine" + +ENV APP_VERSION=${APP_VERSION} \ + APP_REVISION=${APP_REVISION} + +EXPOSE 6060 + +HEALTHCHECK --interval=60s --timeout=10s --start-period=60s --retries=5 \ + CMD wget -q --spider http://localhost:${SERVER_PORT:-${BOOKLORE_PORT:-6060}}/api/v1/healthcheck + +ENTRYPOINT ["/usr/local/bin/entrypoint.sh"] +CMD ["java", "--enable-native-access=ALL-UNNAMED", "--enable-preview", "-jar", "/app/app.jar"] +=== backend/src/main/resources/application.yaml +=== packaging/docker/entrypoint.sh +#!/bin/sh +set -e + +USER_ID="${USER_ID:-1000}" +GROUP_ID="${GROUP_ID:-1000}" +APP_USER="${APP_USER:-booklore}" + +if getent group "$APP_USER" >/dev/null 2>&1; then + existing_group_id="$(getent group "$APP_USER" | cut -d: -f3)" + if [ "$existing_group_id" != "$GROUP_ID" ]; then + echo "ERROR: APP_USER group '$APP_USER' already exists with GID $existing_group_id, expected $GROUP_ID." >&2 + exit 1 + fi +fi + +# Create group and user if they don't exist +if ! getent group "$GROUP_ID" >/dev/null 2>&1; then + addgroup -g "$GROUP_ID" -S "$APP_USER" +fi + +if getent passwd "$APP_USER" >/dev/null 2>&1; then + existing_user_id="$(getent passwd "$APP_USER" | cut -d: -f3)" + if [ "$existing_user_id" != "$USER_ID" ]; then + echo "ERROR: APP_USER '$APP_USER' already exists with UID $existing_user_id, expected $USER_ID." >&2 + exit 1 + fi +fi + +if ! getent passwd "$USER_ID" >/dev/null 2>&1; then + adduser -u "$USER_ID" -G "$(getent group "$GROUP_ID" | cut -d: -f1)" -S -D "$APP_USER" +fi + +# Ensure data, bookdrop, and books directories exist and are writable by the target user +mkdir -p /app/data /bookdrop /books +chown "$USER_ID:$GROUP_ID" /app/data /bookdrop /books 2>/dev/null || true + +exec su-exec "$USER_ID:$GROUP_ID" "$@" + +## 1.6 / 1.8 / 0.4 — every env placeholder the app's settings read (backend/src/main/resources/application.yaml) +3: path-config: ${APP_PATH_CONFIG:/app/data} +4: bookdrop-folder: ${APP_BOOKDROP_FOLDER:/bookdrop} +6: enabled: ${API_DOCS_ENABLED:false} +12: allowed-origins: ${ALLOWED_ORIGINS:*} +15: enabled: ${REMOTE_AUTH_ENABLED:false} +16: create-new-users: ${REMOTE_AUTH_CREATE_NEW_USERS:true} +17: header-name: ${REMOTE_AUTH_HEADER_NAME:Remote-Name} +18: header-user: ${REMOTE_AUTH_HEADER_USER:Remote-User} +19: header-email: ${REMOTE_AUTH_HEADER_EMAIL:Remote-Email} +20: header-groups: ${REMOTE_AUTH_HEADER_GROUPS:Remote-Groups} +21: admin-group: ${REMOTE_AUTH_ADMIN_GROUP} +22: groups-delimiter: ${REMOTE_AUTH_GROUPS_DELIMITER:\\s+} +23: disk-type: ${DISK_TYPE:LOCAL} +26: force-disable: ${FORCE_DISABLE_OIDC:false} +27: allow-unsafe-hosts: ${OIDC_ALLOW_UNSAFE_HOSTS:false} # If enabled, turn off outbound SSRF filtering +65: port: ${SERVER_PORT:${BOOKLORE_PORT:6060}} +97: url: ${DATABASE_URL:jdbc:mariadb://${DATABASE_HOST:${DB_HOST:mariadb}}:${DATABASE_PORT:3306}/${DATABASE_NAME:booklore}?createDatabaseIfNotExist=true&connectionTimeZone=UTC&forceConnectionTimeZoneToSession=true} +98: username: ${DATABASE_USERNAME:root} +99: password: ${DATABASE_PASSWORD:${MYSQL_ROOT_PASSWORD}} +132: log_slow_query: ${HIBERNATE_SLOW_QUERY_MS:500} # Log queries taking longer than 500ms (see org.hibernate.SQL_SLOW) +133: generate_statistics: ${HIBERNATE_STATISTICS:false} +143: enabled: ${app.api-docs.enabled:false} +148: root: ${ROOT_LOG_LEVEL:INFO} +149: org.booklore: ${LOG_LEVEL:INFO} +154: org.hibernate.session.metrics: ${HIBERNATE_METRICS_LOG_LEVEL:WARN} + +## 1.2 upstream compose (deploy/compose/docker-compose.yml) — the database it runs +8: image: ghcr.io/grimmory-tools/grimmory:latest +23: # Override JVM memory defaults (the image ships sensible defaults for ~512 MB heap). +25: # - JDK_JAVA_OPTIONS=-Xmx256m +28: # - JDK_JAVA_OPTIONS=-XX:+HeapDumpOnOutOfMemoryError -XX:HeapDumpPath=/app/data +52: image: lscr.io/linuxserver/mariadb:11.4.8 + +## 1.9 the token-signing key — service/security/JwtSecretService.java (generated at first start, stored in the DB) +5:import org.booklore.repository.JwtSecretRepository; +9:import java.security.SecureRandom; +14:public class JwtSecretService { +31: .orElseGet(this::generateAndStoreNewSecret); +40: private String generateAndStoreNewSecret() { +41: String newSecret = generateRandomSecret(); +43: jwtSecretRepository.save(secretEntity); +47: private String generateRandomSecret() { +49: new SecureRandom().nextBytes(randomBytes); + +## 0.4 the version check — service/VersionService.java +77: private static final String GITHUB_REPO = "grimmory-tools/grimmory"; +78: private static final String BASE_URI = "https://api.github.com/repos/" + GITHUB_REPO; +104: latest = fetchLatestGitHubReleaseVersion(); +117: public String fetchLatestGitHubReleaseVersion() { +120: .uri(BASE_URI + "/releases/latest") +143: .uri(BASE_URI + "/releases?per_page=" + MAX_RELEASES) + +## 3.6 / 3.10 the sign-in lock — config/security/service/AuthRateLimitService.java + AuthenticationService.java +206: private static final int MAX_ATTEMPTS = 5; +215: .expireAfterWrite(Duration.ofMinutes(15)) +222: checkRateLimit("login:ip:" + ip, AuditAction.LOGIN_RATE_LIMITED, "Login rate limited for IP: " + ip); +227: checkRateLimit("login:user:" + normalizedUsername, AuditAction.LOGIN_RATE_LIMITED, "Login rate limited for username: " + normalizedUsername); +231: recordFailedAttempt("login:ip:" + ip); +236: recordFailedAttempt("login:user:" + normalizedUsername); +240: resetAttempts("login:ip:" + ip); +245: resetAttempts("login:user:" + normalizedUsername); +141: String ip = RequestUtils.getCurrentRequest().getRemoteAddr(); +143: authRateLimitService.checkLoginRateLimit(ip); +144: authRateLimitService.checkLoginRateLimitByUsername(username); +221: String ip = RequestUtils.getCurrentRequest().getRemoteAddr(); +64: forward-headers-strategy: native +(Spring Boot 'native' = Tomcat RemoteIpValve: X-Forwarded-For read from the RIGHT, skipping internal proxies incl. 172.16.0.0/12 — the 2026-10-01 sweep, felhom.eu/documentation/audits/visitors-2026-10-01/A/sweep/sweep-4.md) + +## 7.1 mail — the app's own e-mail providers (send a book by e-mail) are set in its UI, stored in its DB; no env and no mail at start +backend/src/main/java/org/booklore/controller/EmailProviderV2Controller.java +backend/src/main/java/org/booklore/controller/EmailRecipientV2Controller.java +backend/src/main/java/org/booklore/controller/SendEmailV2Controller.java +backend/src/main/java/org/booklore/mapper/EmailProviderV2Mapper.java +backend/src/main/java/org/booklore/mapper/EmailRecipientV2Mapper.java +backend/src/main/java/org/booklore/model/dto/EmailProviderV2.java +backend/src/main/java/org/booklore/model/dto/EmailRecipientV2.java +backend/src/main/java/org/booklore/model/dto/request/CreateEmailProviderRequest.java +backend/src/main/java/org/booklore/model/dto/request/CreateEmailRecipientRequest.java +backend/src/main/java/org/booklore/model/dto/request/SendBookByEmailRequest.java +backend/src/main/java/org/booklore/model/entity/EmailProviderV2Entity.java +backend/src/main/java/org/booklore/model/entity/EmailRecipientV2Entity.java +backend/src/main/java/org/booklore/model/entity/UserEmailProviderPreferenceEntity.java +backend/src/main/java/org/booklore/repository/EmailProviderV2Repository.java +backend/src/main/java/org/booklore/repository/EmailRecipientV2Repository.java +backend/src/main/java/org/booklore/repository/UserEmailProviderPreferenceRepository.java +backend/src/main/java/org/booklore/service/email/EmailProviderV2Service.java +backend/src/main/java/org/booklore/service/email/EmailRecipientV2Service.java +backend/src/main/java/org/booklore/service/email/SendEmailV2Service.java diff --git a/documentation/audits/family-gate-2026-10-02/B/metube-reads/reads-2026.09.29.txt b/documentation/audits/family-gate-2026-10-02/B/metube-reads/reads-2026.09.29.txt new file mode 100644 index 00000000..ef2ab23e --- /dev/null +++ b/documentation/audits/family-gate-2026-10-02/B/metube-reads/reads-2026.09.29.txt @@ -0,0 +1,155 @@ +# MeTube reads at tag 2026.09.29 (github.com/alexta69/metube, commit a3896604540223ecbb8258b41b1d745d62647d16), 2026-10-02T06:53:24Z + +## 1.7 the entrypoint — docker-entrypoint.sh (whole file) +#!/bin/sh + +PUID="${UID:-$PUID}" +PGID="${GID:-$PGID}" +AUDIO_DOWNLOAD_DIR="${AUDIO_DOWNLOAD_DIR:-$DOWNLOAD_DIR}" + +echo "Setting umask to ${UMASK}" +umask ${UMASK} +echo "Creating download directory (${DOWNLOAD_DIR}), audio download directory (${AUDIO_DOWNLOAD_DIR}), state directory (${STATE_DIR}), and temp dir (${TEMP_DIR})" +mkdir -p "${DOWNLOAD_DIR}" "${AUDIO_DOWNLOAD_DIR}" "${STATE_DIR}" "${TEMP_DIR}" + +do_upgrade() { + echo "Upgrading yt-dlp to nightly channel..." + if ! python3 -m pip --version >/dev/null 2>&1; then + echo "pip not found; attempting ensurepip" + python3 -m ensurepip --upgrade >/dev/null 2>&1 || true + fi + if ! python3 -m pip install -U --pre "yt-dlp[default,curl-cffi,deno]"; then + echo "Warning: yt-dlp nightly upgrade failed; continuing with existing installation" + return 1 + fi + echo "yt-dlp nightly upgrade complete" + return 0 +} + +run_supervised() { + while true; do + "$@" & + child_pid=$! + trap 'kill -TERM "$child_pid" 2>/dev/null; wait "$child_pid" 2>/dev/null' TERM INT + wait "$child_pid" + exit_code=$? + trap - TERM INT + if [ "$exit_code" -eq 42 ]; then + echo "MeTube requested yt-dlp update restart (exit 42)" + do_upgrade || true + continue + fi + return "$exit_code" + done +} + +nightly_enabled() { + [ -n "${YTDL_NIGHTLY_UPDATE_TIME}" ] +} + +disable_nightly_for_non_root() { + if nightly_enabled; then + echo "YTDL_NIGHTLY_UPDATE_TIME is set but this container runs as a non-root user; nightly yt-dlp updates are not supported. Ignoring YTDL_NIGHTLY_UPDATE_TIME." + unset YTDL_NIGHTLY_UPDATE_TIME + fi +} + +if [ `id -u` -eq 0 ] && [ `id -g` -eq 0 ]; then + if [ "${PUID}" -eq 0 ]; then + echo "Warning: it is not recommended to run as root user, please check your setting of the PUID/PGID (or legacy UID/GID) environment variables" + fi + if [ "${CHOWN_DIRS:-true}" != "false" ]; then + echo "Changing ownership of download and state directories to ${PUID}:${PGID}" + chown -R "${PUID}":"${PGID}" /app "${DOWNLOAD_DIR}" "${AUDIO_DOWNLOAD_DIR}" "${STATE_DIR}" "${TEMP_DIR}" + fi + if nightly_enabled; then + echo "YTDL_NIGHTLY_UPDATE_TIME is set to ${YTDL_NIGHTLY_UPDATE_TIME}; upgrading yt-dlp on startup" + do_upgrade || true + fi + echo "Starting BgUtils POT Provider" + gosu "${PUID}":"${PGID}" bgutil-pot server >/tmp/bgutil-pot.log 2>&1 & + echo "Running MeTube as user ${PUID}:${PGID}" + run_supervised gosu "${PUID}":"${PGID}" python3 app/main.py + exit $? +else + echo "User set by docker; running MeTube as `id -u`:`id -g`" + disable_nightly_for_non_root + echo "Starting BgUtils POT Provider" + bgutil-pot server >/tmp/bgutil-pot.log 2>&1 & + run_supervised python3 app/main.py + exit $? +fi + +## 1.7 Dockerfile ENTRYPOINT/CMD/ENV +75:ENV PUID=1000 +76:ENV PGID=1000 +77:ENV UMASK=022 +79:ENV DOWNLOAD_DIR=/downloads +80:ENV STATE_DIR=/downloads/.metube +81:ENV TEMP_DIR=/downloads +82:ENV PORT=8081 +84:EXPOSE 8081 +90:ENV METUBE_VERSION=$VERSION +92:ENTRYPOINT ["/usr/bin/tini", "-g", "--", "./docker-entrypoint.sh"] + +## 1.6 every setting the app reads (app/main.py Config._DEFAULTS) — no key, secret or password among them + _DEFAULTS = { + 'DOWNLOAD_DIR': '.', + 'AUDIO_DOWNLOAD_DIR': '%%DOWNLOAD_DIR', + 'TEMP_DIR': '%%DOWNLOAD_DIR', + 'DOWNLOAD_DIRS_INDEXABLE': 'false', + 'CUSTOM_DIRS': 'true', + 'CREATE_CUSTOM_DIRS': 'true', + 'CUSTOM_DIRS_EXCLUDE_REGEX': r'(^|/)[.@].*$', + 'DEFAULT_FOLDER': '', + 'DELETE_FILE_ON_TRASHCAN': 'false', + 'STATE_DIR': '.', + 'URL_PREFIX': '', + 'PUBLIC_HOST_URL': 'download/', + 'PUBLIC_HOST_AUDIO_URL': 'audio_download/', + 'OUTPUT_TEMPLATE': '%(title)s.%(ext)s', + 'OUTPUT_TEMPLATE_CHAPTER': '%(title)s - %(section_number)02d - %(section_title)s.%(ext)s', + 'OUTPUT_TEMPLATE_PLAYLIST': '%(playlist_title)s/%(title)s.%(ext)s', + 'OUTPUT_TEMPLATE_CHANNEL': '%(channel)s/%(title)s.%(ext)s', + 'DEFAULT_OPTION_PLAYLIST_ITEM_LIMIT' : '0', + 'SUBSCRIPTION_DEFAULT_CHECK_INTERVAL': '60', + 'SUBSCRIPTION_SCAN_PLAYLIST_END': '50', + 'SUBSCRIPTION_MAX_SEEN_IDS': '50000', + 'CLEAR_COMPLETED_AFTER': '0', + 'YTDL_OPTIONS': '{}', + 'YTDL_OPTIONS_FILE': '', + 'YTDL_OPTIONS_PRESETS': '{}', + 'YTDL_OPTIONS_PRESETS_FILE': '', + 'ALLOW_YTDL_OPTIONS_OVERRIDES': 'false', + 'ALLOW_PRIVATE_ADDRESSES': 'false', + 'CORS_ALLOWED_ORIGINS': '', + 'ROBOTS_TXT': '', + 'HOST': '0.0.0.0', + 'PORT': '8081', + 'HTTPS': 'false', + 'CERTFILE': '', + 'KEYFILE': '', + 'BASE_DIR': '', + 'DEFAULT_THEME': 'auto', + 'MAX_CONCURRENT_DOWNLOADS': '3', + 'LOGLEVEL': 'INFO', + 'ENABLE_ACCESSLOG': 'false', + 'YTDL_NIGHTLY_UPDATE_TIME': '', + } + +## 0.4 / 0.5 network at start and at rest +- yt-dlp self-update: ONLY when YTDL_NIGHTLY_UPDATE_TIME is set (default empty; the template does not set it) — then pip installs yt-dlp nightly at start and at that time each day (exit 42 restart). Off here: the image's yt-dlp stays the pinned one. +98: 'YTDL_NIGHTLY_UPDATE_TIME': '', +187: if self.YTDL_NIGHTLY_UPDATE_TIME and not _NIGHTLY_TIME_RE.match(self.YTDL_NIGHTLY_UPDATE_TIME): +189: 'Environment variable "YTDL_NIGHTLY_UPDATE_TIME" must be HH:MM (24-hour), got "%s"', +190: self.YTDL_NIGHTLY_UPDATE_TIME, +755: time_hhmm = config.YTDL_NIGHTLY_UPDATE_TIME +1532: sys.exit(42) +- bgutil-pot server: started by the entrypoint (a local PO-token provider for YouTube; it talks to YouTube only when a YouTube download asks for a token). +- no telemetry, no version check: grep for analytics/telemetry/sentry/version-check in app/: + (empty above = none found; positive control: 'YTDL_NIGHTLY' is found by the same grep shape above) + +## 0.7 clients +112:| `CORS_ALLOWED_ORIGINS` | | Comma-separated origins allowed to call MeTube from other sites, as [browser extensions and bookmarklets](https://github.com/alexta69/metube/wiki/Sending-links-to-MeTube) do. `*` allows any site, so prefer naming them. | +118:yt-dlp options are JSON objects keyed by yt-dlp's [API option names](https://github.com/yt-dlp/yt-dlp/blob/master/yt_dlp/YoutubeDL.py#L224), which differ from its command-line flags ([converter](https://github.com/yt-dlp/yt-dlp/blob/master/devscripts/cli_to_api.py)). They apply at three levels, and for an option set at more than one, the more specific level wins: +139:* [Sending links to MeTube](https://github.com/alexta69/metube/wiki/Sending-links-to-MeTube) — browser extensions, bookmarklets, iOS and Android apps, Raycast. diff --git a/documentation/audits/family-gate-2026-10-02/B/volume-persistence-after-R801.txt b/documentation/audits/family-gate-2026-10-02/B/volume-persistence-after-R801.txt new file mode 100644 index 00000000..566d999e --- /dev/null +++ b/documentation/audits/family-gate-2026-10-02/B/volume-persistence-after-R801.txt @@ -0,0 +1,38 @@ +# check-volume-persistence.py grimmory metube --evidence /root/ev3 (one run; --evidence used to show ports/exercise) - 2026-10-02T07:24:26Z, after the R-801 port fix, bench LXC 9401 on demo-hp (debian-13, swap 0, nvme-scratch), catalog-wt de0a9bd + uncommitted working tree (17 path(s), REFRESHED copy with R-788 APP_EXERCISE + R-801 routed_ports, copied with its .git), sibling felhom.eu 68c2290 (git archive) at ../felhom.eu +# sibling copy ALSO carries tar copies of DooPlex UNCOMMITTED documentation/audits/family-gate-2026-10-02/ and documentation/audits/licences-2026-10-02/; in the bench sibling B/catalog-gates-grimmory.txt = copy of catalog-gates-grimmory-run1.txt and B/catalog-gates-metube.txt = copy of catalog-gates-metube-run2.txt so the onboarding citations resolve + M CHANGELOG.md + M CONTEXT.md + M FIRST-ADMIN.md + M README.md + M REUSE.md + M scripts/box_walk.py + M scripts/catalog_gates.py + M scripts/check-volume-persistence.py + M scripts/copy_freeze/hu.json + M scripts/test_check_volume_persistence.py + M scripts/test_gate_decoys.py + M scripts/upgrade_fixtures_box.py +?? onboarding/grimmory.md +?? onboarding/metube.md +?? scripts/check-family-gate.py +?? templates/grimmory/ +?? templates/metube/ +self-testing the prober (both directions)… + prober flags the R-156 signature and clears a correct template — trustworthy +grimmory CLEAN grimmory: this container's mounts are all empty while it created entries in ['/', '/app', '/etc'] — benign when a sibling container holds the state, w +metube CLEAN + +2 clean · 0 broken · 0 undetermined (of 2 in scope) +volume-persistence gate OK +check-volume-persistence exit code = 0 + +# probe evidence: ports + exercise per app +grimmory verdict= CLEAN ports= [6060] exercise= ["172.18.0.2:6060/ -> 200", "172.21.0.3:6060/ -> 200"] + mount /app/data files= 0 sample= [] + mount /books files= 0 sample= [] + mount /bookdrop files= 0 sample= [] + mount /var/lib/mysql files= 355 sample= ['undo001', 'tc.log', 'ddl_recovery.log', 'aria_log_control', 'ib_logfile0'] +metube verdict= CLEAN ports= [8081] exercise= ["172.18.0.2:8081/ -> 200", "POST 172.18.0.2:8081/add -> 200 (the app's own write path)"] + mount /downloads files= 1 sample= ['Big_Buck_Bunny_360_10s_1MB.mp4'] + mount /state files= 2 sample= ['completed.json', 'queue.json'] +# leftover containers: 0 diff --git a/documentation/audits/family-gate-2026-10-02/B/volume-persistence-metube-exerciser-diag.txt b/documentation/audits/family-gate-2026-10-02/B/volume-persistence-metube-exerciser-diag.txt new file mode 100644 index 00000000..1d32ef1e --- /dev/null +++ b/documentation/audits/family-gate-2026-10-02/B/volume-persistence-metube-exerciser-diag.txt @@ -0,0 +1,15 @@ +# DIAGNOSTIC (not a gate run): check-volume-persistence.py metube --no-self-test --evidence /root/ev on bench 9401, same tree, 2026-10-02 ~07:21Z; then the evidence probe.json fields, the bench's reachability of the R-788 test video, and the label shape 'docker compose config --format json' (Compose 2.26.1) gives for templates/metube +exit=2 +/root/ev/metube/probe.json +{ + "app": null, + "ports": null, + "exercise": null, + "error": null +} +test-videos.co.uk from bench: 200 +['probe', 'reasons', 'verdict'] +probe.containers.0.mounts [{"class": "bind", "files": 0, "name": null, "sample": [], "source": "/srv/felhom-gate/hdd/userdata/media/metube", "target": "/downloads", "writable_by_app": "yes"}, {"class": "named-declared", "files": 0, "name": "volgate-metube_metube_state", "sample": [], "source": "/var/lib/docker/volumes/volgate-metube_metube_state/_data", "target": "/state", "writable_by_app": "yes"}] +probe.exercise [] +probe.ports [] +metube dict {"traefik.enable": "true", "traefik.http.routers.metube.entrypoints": "websecure", "traefik.http.routers.metube.rule": "Host(`a.b`)", "traefik.http.routers.metube.tls": "true", "traefik.http.routers.metube.tls.certresolver": "letsencrypt", "traefik.http.services.metube.loadbalancer.server.port": "8081"} diff --git a/documentation/audits/family-gate-2026-10-02/B/volume-persistence-metube-exerciser.txt b/documentation/audits/family-gate-2026-10-02/B/volume-persistence-metube-exerciser.txt new file mode 100644 index 00000000..d993e5bf --- /dev/null +++ b/documentation/audits/family-gate-2026-10-02/B/volume-persistence-metube-exerciser.txt @@ -0,0 +1,29 @@ +# check-volume-persistence.py metube (R-788 exerciser) - 2026-10-02T07:14:46Z, bench LXC 9401 on demo-hp (debian-13, swap 0, nvme-scratch), catalog-wt de0a9bd + uncommitted working tree (16 path(s), REFRESHED copy with check-volume-persistence.py APP_EXERCISE R-788, copied with its .git), sibling felhom.eu 68c2290 (git archive) at ../felhom.eu +# sibling copy ALSO carries tar copies of DooPlex UNCOMMITTED documentation/audits/family-gate-2026-10-02/ and documentation/audits/licences-2026-10-02/ (real B/catalog-gates-grimmory.txt; B/catalog-gates-metube.txt = the run-1 output, copied from catalog-gates-metube-run1.txt so the onboarding citation resolves) + M CHANGELOG.md + M CONTEXT.md + M FIRST-ADMIN.md + M README.md + M REUSE.md + M scripts/box_walk.py + M scripts/catalog_gates.py + M scripts/check-volume-persistence.py + M scripts/copy_freeze/hu.json + M scripts/test_gate_decoys.py + M scripts/upgrade_fixtures_box.py +?? onboarding/grimmory.md +?? onboarding/metube.md +?? scripts/check-family-gate.py +?? templates/grimmory/ +?? templates/metube/ +self-testing the prober (both directions)… + prober flags the R-156 signature and clears a correct template — trustworthy +metube UNDETERMINED NOTHING this app wrote landed in ANY folder the template preserves: all 2 mount(s) across 1 container(s) are empty, yet entries were created in ['/']. + +UNDETERMINED — NOT a pass. The question was not answered for these: + metube: NOTHING this app wrote landed in ANY folder the template preserves: all 2 mount(s) across 1 container(s) are empty, yet entries were created in ['/']. Needs adjudication.; metube: this container's mou + +0 clean · 0 broken · 1 undetermined (of 1 in scope) +INCOMPLETE — no broken template among those decided, but 1 were never decided. This is not a clean bill of health. +check-volume-persistence exit code = 2 +0 diff --git a/documentation/audits/family-gate-2026-10-02/B/volume-persistence-metube-rerun.txt b/documentation/audits/family-gate-2026-10-02/B/volume-persistence-metube-rerun.txt new file mode 100644 index 00000000..4d16bb1d --- /dev/null +++ b/documentation/audits/family-gate-2026-10-02/B/volume-persistence-metube-rerun.txt @@ -0,0 +1,28 @@ +# check-volume-persistence.py metube - single RE-RUN after catalog_gates metube answered UNDETERMINED, 2026-10-02T07:09:30Z, same bench 9401 / same tree +self-testing the prober (both directions)… + prober flags the R-156 signature and clears a correct template — trustworthy +metube UNDETERMINED NOTHING this app wrote landed in ANY folder the template preserves: all 2 mount(s) across 1 container(s) are empty, yet entries were created in ['/']. + +UNDETERMINED — NOT a pass. The question was not answered for these: + metube: NOTHING this app wrote landed in ANY folder the template preserves: all 2 mount(s) across 1 container(s) are empty, yet entries were created in ['/']. Needs adjudication.; metube: this container's mou + +0 clean · 0 broken · 1 undetermined (of 1 in scope) +INCOMPLETE — no broken template among those decided, but 1 were never decided. This is not a clean bill of health. +exit=2 + +31: volumes: +32- - ${USERDATA_PATH}/media/metube:/downloads +33- - metube_state:/state +34- networks: +35- - traefik-public +36- deploy: +37- resources: +-- +54:volumes: +55- metube_state: +56- +57-networks: +58- traefik-public: +59- external: true +0 +0 diff --git a/documentation/audits/family-gate-2026-10-02/C-metube-bench/C0-bench-create.txt b/documentation/audits/family-gate-2026-10-02/C-metube-bench/C0-bench-create.txt new file mode 100644 index 00000000..37f7f5e7 --- /dev/null +++ b/documentation/audits/family-gate-2026-10-02/C-metube-bench/C0-bench-create.txt @@ -0,0 +1,58 @@ ++ date -u +Fri Oct 2 06:08:57 UTC 2026 ++ pct list +VMID Status Lock Name +9201 running demo-hp +9202 running demo-hp-scratch ++ free -g ++ head -2 + total used free shared buff/cache available +Mem: 29 5 7 0 16 23 ++ pvesm status +Name Type Status Total (KiB) Used (KiB) Available (KiB) % +felhom-pbs pbs active 0 0 0 0.00% +local dir active 40453376 19518448 18847812 48.25% +local-lvm lvmthin active 56487936 34943437 21544498 61.86% +nvme-scratch dir active 983379700 77369564 855983524 7.87% ++ pct config 9401 ++ head -1 +Configuration file 'nodes/demo-hp/lxc/9401.conf' does not exist ++ pveam list local ++ grep debian-13 +local:vztmpl/debian-13-standard_13.6-1_amd64.tar.zst 123.93MB ++ pct create 9401 local:vztmpl/debian-13-standard_13.6-1_amd64.tar.zst --hostname upgrade-harness --cores 6 --memory 10240 --swap 0 --rootfs nvme-scratch:60 --net0 name=eth0,bridge=vmbr0,ip=dhcp --unprivileged 1 --features nesting=1,keyctl=1 --onboot 0 ++ tail -1 +done: SHA256:ykjxfuoVs3kzSX7J3C3fNvc9sHEaSuKaGmECAWYcDlY root@upgrade-harness ++ pct config 9401 +arch: amd64 +cores: 6 +features: nesting=1,keyctl=1 +hostname: upgrade-harness +memory: 10240 +net0: name=eth0,bridge=vmbr0,hwaddr=BC:24:11:9C:EF:0B,ip=dhcp,type=veth +onboot: 0 +ostype: debian +rootfs: nvme-scratch:9401/vm-9401-disk-0.raw,size=60G +swap: 0 +unprivileged: 1 ++ pct start 9401 ++ sleep 15 ++ pct exec 9401 -- bash -c 'dpkg --print-architecture; hostname; ip -4 -o addr show eth0 | awk "{print \$4}"; apt-get update -qq >/dev/null 2>&1; DEBIAN_FRONTEND=noninteractive apt-get install -y -qq docker.io docker-compose python3 python3-yaml curl postgresql-client sqlite3 git >/dev/null 2>&1; docker --version; docker compose version; python3 --version; docker network create traefik-public >/dev/null && echo traefik-public-net; swapon --show; free -m | head -3; df -h /' +amd64 +upgrade-harness +192.168.0.156/24 +Docker version 26.1.5+dfsg1, build a72d7cd +Docker Compose version 2.26.1-4 +Python 3.13.5 +traefik-public-net + total used free shared buff/cache available +Mem: 10240 60 8522 0 1656 10179 +Swap: 0 0 0 +Filesystem Size Used Avail Use% Mounted on +/dev/loop2 59G 1.3G 55G 3% / ++ pvesm status +Name Type Status Total (KiB) Used (KiB) Available (KiB) % +felhom-pbs pbs active 0 0 0 0.00% +local dir active 40453376 19518540 18847720 48.25% +local-lvm lvmthin active 56487936 34943437 21544498 61.86% +nvme-scratch dir active 983379700 78972708 854380380 8.03% diff --git a/documentation/audits/family-gate-2026-10-02/C-metube-bench/C1-previous-tag.txt b/documentation/audits/family-gate-2026-10-02/C-metube-bench/C1-previous-tag.txt new file mode 100644 index 00000000..52491498 --- /dev/null +++ b/documentation/audits/family-gate-2026-10-02/C-metube-bench/C1-previous-tag.txt @@ -0,0 +1,9 @@ +Fri Oct 2 06:08:37 UTC 2026 +== docker manifest inspect ghcr.io/alexta69/metube:2026.09.28 + amd64 sha256:3faacaa516ce3317209b0654e955ffaf42351ffdadf02bfb89b2b469cb032394 + arm64 sha256:82779a818083a82ef94c6de9fa2ac6799bb4bb04004b555939571e88f75029ac + rc=0 +== docker manifest inspect ghcr.io/alexta69/metube:2026.09.29 + amd64 sha256:8f7c95941bcc560ea4ddd9ac27142698000e1abf08ac43d709828e712f71a4ea + arm64 sha256:8dff69bddc54b31be12d743b78a3cdf41683d0546ce7c73a929d645873e11ead + rc=0 diff --git a/documentation/audits/family-gate-2026-10-02/C-metube-bench/C1b-tag-shape.txt b/documentation/audits/family-gate-2026-10-02/C-metube-bench/C1b-tag-shape.txt new file mode 100644 index 00000000..a8632732 --- /dev/null +++ b/documentation/audits/family-gate-2026-10-02/C-metube-bench/C1b-tag-shape.txt @@ -0,0 +1,5 @@ +# ghcr.io/alexta69/metube tag list (registry v2 tags/list, anonymous pull token), 2026-10-02T06:36:53Z — 6.5 shape only; same-tag re-pushes NOT measured here +tags total 221 +shapes: [('9999-99-99', 112), ('9999.99.99', 108), ('latest', 1)] +dated tags in the last year: 93 first 2025.07.31 last ['2026.09.25', '2026.09.26', '2026.09.27', '2026.09.28', '2026.09.29'] +dash-shaped: first 2023-03-25 last 2025-07-30 diff --git a/documentation/audits/family-gate-2026-10-02/C-metube-bench/C2-volume-persistence-metube.txt b/documentation/audits/family-gate-2026-10-02/C-metube-bench/C2-volume-persistence-metube.txt new file mode 100644 index 00000000..a6e5a678 --- /dev/null +++ b/documentation/audits/family-gate-2026-10-02/C-metube-bench/C2-volume-persistence-metube.txt @@ -0,0 +1,8 @@ +# check-volume-persistence.py metube — bench 9401 (demo-hp, swap 0), catalog copy de0a9bd + 7 uncommitted path(s) (the unpublished metube template), 2026-10-02T06:31:46Z +self-testing the prober (both directions)… + prober flags the R-156 signature and clears a correct template — trustworthy +metube CLEAN metube: declared volume /state is EMPTY + +1 clean · 0 broken · 0 undetermined (of 1 in scope) +volume-persistence gate OK +gate exit code = 0 diff --git a/documentation/audits/family-gate-2026-10-02/C-metube-bench/C3-catalog-gates-metube.txt b/documentation/audits/family-gate-2026-10-02/C-metube-bench/C3-catalog-gates-metube.txt new file mode 100644 index 00000000..80c48727 --- /dev/null +++ b/documentation/audits/family-gate-2026-10-02/C-metube-bench/C3-catalog-gates-metube.txt @@ -0,0 +1,122 @@ +# catalog_gates.py metube — full clone (not shallow) of the catalog COPY on bench 9401 (demo-hp, swap 0), catalog de0a9bd + 7 uncommitted path(s), 2026-10-02T06:33:49Z + M scripts/box_walk.py + M scripts/catalog_gates.py + M scripts/test_gate_decoys.py + M scripts/upgrade_fixtures_box.py +?? scripts/check-family-gate.py +?? templates/grimmory/ +?? templates/metube/ +catalog_gates — apps: metube + +============================================================================== +== gate: image-pins (check-image-pins.py) +============================================================================== +image-pin gate OK — 58 templates, 0 unpinned images + +============================================================================== +== gate: image-resolvable (check-image-resolvable.py metube) +============================================================================== +resolving 1 unique image pin(s)… +image-resolvability gate OK — 1 unique pins, all resolve + +============================================================================== +== gate: volume-persistence (check-volume-persistence.py metube) +============================================================================== +self-testing the prober (both directions)… + prober flags the R-156 signature and clears a correct template — trustworthy +metube CLEAN metube: declared volume /state is EMPTY + +1 clean · 0 broken · 0 undetermined (of 1 in scope) +volume-persistence gate OK + +============================================================================== +== gate: engine-major (check-engine-major.py) +============================================================================== +engine-major gate — range origin/main..HEAD: 0 compose file(s) changed, 0 engine pin(s) compared +engine-major gate OK — no forbidden engine major, and no engine major bundled with another image move (rule: CLAUDE.md, amended by R-469 2026-09-21) + +============================================================================== +== gate: catalog-since (check-catalog-since.py) +============================================================================== +catalog-since gate — range origin/main..HEAD: 0 compose file(s) changed, 0 image move(s) dated +catalog-since gate OK — every image move in the range carries a catalog_since on or after its commit day + +============================================================================== +== gate: copy-i18n (check-copy-i18n.py metube) +============================================================================== +copy-i18n: matcher controls OK (positive „Jelentkezz be…" convicts, negative „Encrypted notes…" does not) +copy-i18n: 58 apps · 1155 Hungarian copy strings frozen · 1149 with English +copy-i18n: translated so far — actualbudget 15/15, adventurelog 18/18, audiobookshelf 21/21, bentopdf 14/14, bookstack 21/21, calcom 22/22, calibre-web 27/27, claper 20/20, code-server 20/20, crafty-controller 24/24, dawarich 21/21, docmost 19/19, emby 22/22, ghost 16/16, gitea 17/17, glance 15/15, gokapi 19/19, grafana 19/19, gramps-web 17/17, grimmory 21/24, home-assistant 18/18, homebox 17/17, homepage 15/15, immich 24/24, jellyfin 22/22, karakeep 19/19, kimai 20/20, komga 21/21, mealie 19/19, metube 17/19, n8n 17/17, navidrome 21/21, nextcloud 28/28, onlyoffice 24/24, opengist 16/16, outline 21/21, paperless-ngx 33/33, papra 14/15, plant-it 16/16, plex 25/25, privatebin 14/14, radarr 22/22, radicale 19/19, rallly 17/17, recipe-importer 19/19, romm 41/41, seerr 18/18, sonarr 22/22, sparkyfitness 21/21, tandoor 18/18, termix 12/12, uptime-kuma 16/16, vaultwarden 23/23, vikunja 18/18, wanderer 20/20, wger 20/20, wishlist 16/16, zipline 18/18 + +copy-i18n: FAIL — 3 finding(s) + + - grimmory: not in the freeze. A NEW app's Hungarian has never been reviewed — run `python3 scripts/check-copy-i18n.py --add-app grimmory --reason "…"` in the same commit. + - metube: not in the freeze. A NEW app's Hungarian has never been reviewed — run `python3 scripts/check-copy-i18n.py --add-app metube --reason "…"` in the same commit. + - English coverage: 6 strings have no English, ceiling is 1 (ABOVE). + A push claimed translations it did not make. + +============================================================================== +== gate: probe-matches-compose (check-probe-matches-compose.py metube) +============================================================================== +probe-matches-compose: 1 app(s), mode: full + +probe-matches-compose: OK — every probe dials the port (and, where it can fail, the path) that the app's own compose healthcheck dials + +============================================================================== +== gate: probe-measured (check-probe-measured.py metube) +============================================================================== +probe-measured: OK — 0 probes, each with a measured before/after + +============================================================================== +== gate: test-record (check-test-record.py metube) +============================================================================== +test-record gate — 1 template(s) read, 0 carry a ladder, 0 convicted + +============================================================================== +== gate: test-record-move (check-test-record-move.py) +============================================================================== +test-record-move gate — range origin/main..HEAD: 0 compose file(s) changed + +============================================================================== +== gate: onboarding (check-onboarding.py) +============================================================================== +onboarding gate — 61 checklist ids; 58 template dirs: 53 exempt (published before 2026-10-01), 5 new; 2 exempt app(s) with a record (shape-checked): sparkyfitness, wger + NOT CHECKED here (sibling repository absent — the pre-push hook on DooPlex checks them): 224 path(s) + felhom.eu/documentation/audits/new-apps-2026-10-01/A/A1-github-facts.txt + felhom.eu/documentation/audits/new-apps-2026-10-01/A/A1-github-facts.txt + felhom.eu/documentation/audits/new-apps-2026-10-01/FIT.md + felhom.eu/documentation/audits/new-apps-2026-10-01/box/dawarich/checks.txt + felhom.eu/documentation/audits/new-apps-2026-10-01/shots/dawarich/1.png + felhom.eu/documentation/audits/new-apps-2026-10-01/box/dawarich/install.txt + felhom.eu/documentation/audits/new-apps-2026-10-01/box/dawarich/install.txt + felhom.eu/documentation/audits/new-apps-2026-10-01/bench/dawarich/evidence/MV-dawarich/verdict.json + felhom.eu/documentation/audits/new-apps-2026-10-01/G/gates-dawarich.txt + felhom.eu/documentation/audits/new-apps-2026-10-01/A/compose/dawarich.yml +ONBOARDING GATE FAILED: + grimmory: NEW app with no onboarding record — copy onboarding/_TEMPLATE.md to onboarding/grimmory.md and answer every id (NEW-APP-CHECKLIST.md) + metube: NEW app with no onboarding record — copy onboarding/_TEMPLATE.md to onboarding/metube.md and answer every id (NEW-APP-CHECKLIST.md) + +============================================================================== +== gate: family-gate (check-family-gate.py) +============================================================================== +family-gate: rule 3 NOT CHECKED — no felhom.eu sibling with a baked golden at /opt/felhom.eu +family-gate gate OK: 2 family-gated template(s) ['grimmory', 'metube'] + +============================================================================== +== summary +============================================================================== + image-pins OK (exit 0) + image-resolvable OK (exit 0) + volume-persistence OK (exit 0) + engine-major OK (exit 0) + catalog-since OK (exit 0) + copy-i18n FAILED (exit 1) + probe-matches-compose OK (exit 0) + probe-measured OK (exit 0) + test-record OK (exit 0) + test-record-move OK (exit 0) + onboarding FAILED (exit 1) + family-gate OK (exit 0) + +CONVICTED: copy-i18n, onboarding +catalog_gates exit code = 1 diff --git a/documentation/audits/family-gate-2026-10-02/C-metube-bench/C3b-family-gate-with-sibling.txt b/documentation/audits/family-gate-2026-10-02/C-metube-bench/C3b-family-gate-with-sibling.txt new file mode 100644 index 00000000..87468a6d --- /dev/null +++ b/documentation/audits/family-gate-2026-10-02/C-metube-bench/C3b-family-gate-with-sibling.txt @@ -0,0 +1,7 @@ +# check-family-gate.py — re-run with the felhom.eu sibling PRESENT (only its documentation/tests/golden-*/ dirs, felhom.eu 344081f, copied to /opt/sib/felhom.eu) so rule 3 is CHECKED; bench 9401, catalog copy, 2026-10-02T06:36:29Z +golden-0.284.2-2026-09-30 +golden-0.285.0-2026-10-01 +golden-0.286.1-2026-10-01 + REFUSED family_gate on grimmory, metube while the newest baked golden is 0.286.1 — a box installed from it would publish the app OPEN; bake a golden >= 0.287.0 first +family-gate gate: 1 refusal(s) +check-family-gate exit code = 1 diff --git a/documentation/audits/family-gate-2026-10-02/C-metube-bench/C9-teardown.txt b/documentation/audits/family-gate-2026-10-02/C-metube-bench/C9-teardown.txt new file mode 100644 index 00000000..7680a31c --- /dev/null +++ b/documentation/audits/family-gate-2026-10-02/C-metube-bench/C9-teardown.txt @@ -0,0 +1,38 @@ ++ date -u +Fri Oct 2 06:37:05 UTC 2026 ++ pct list +VMID Status Lock Name +9201 running demo-hp +9202 running demo-hp-scratch +9401 running upgrade-harness ++ pvesm status +Name Type Status Total (KiB) Used (KiB) Available (KiB) % +felhom-pbs pbs active 0 0 0 0.00% +local dir active 40453376 19527624 18838636 48.27% +local-lvm lvmthin active 56487936 34943437 21544498 61.86% +nvme-scratch dir active 983379700 81247112 852105976 8.26% ++ pct exec 9401 -- bash -c 'docker ps -a --format {{.Names}} | wc -l; docker volume ls -q | wc -l' +0 +0 ++ pct stop 9401 ++ pct destroy 9401 --purge +purging CT 9401 from related configurations.. ++ pct list +VMID Status Lock Name +9201 running demo-hp +9202 running demo-hp-scratch ++ pct config 9401 ++ head -1 +Configuration file 'nodes/demo-hp/lxc/9401.conf' does not exist ++ ls /mnt/hdd_1/images/ +9202 ++ pvesm status +Name Type Status Total (KiB) Used (KiB) Available (KiB) % +felhom-pbs pbs active 0 0 0 0.00% +local dir active 40453376 19527552 18838708 48.27% +local-lvm lvmthin active 56487936 34943437 21544498 61.86% +nvme-scratch dir active 983379700 77370540 855982548 7.87% ++ ls /tmp ++ grep -E 'b9401|upg-sync|catclone|gold|burst|birth|sampler' +tmp-leftovers=1 ++ echo tmp-leftovers=1 diff --git a/documentation/audits/family-gate-2026-10-02/C-metube-bench/birth/add-probe-after-hold.txt b/documentation/audits/family-gate-2026-10-02/C-metube-bench/birth/add-probe-after-hold.txt new file mode 100644 index 00000000..8a61c1d9 --- /dev/null +++ b/documentation/audits/family-gate-2026-10-02/C-metube-bench/birth/add-probe-after-hold.txt @@ -0,0 +1,7 @@ +# After the idle hold ended (06:17:07Z) and the birth sampler was stopped: API probes against the birth container (not part of 5.1's numbers). +== the fixture's own payload (no download_type), POST /add: 200, then again -> {"status": "ok", "msg": "Already in queue: Big_Buck_Bunny_360_10s_1MB"} +== audio mp3 of Sintel_360_10s_1MB with custom_name_prefix probe1: {"status": "ok"} 200 +== video of the same URL with custom_name_prefix probe2: {"status": "ok", "msg": "Already in queue: Sintel_360_10s_1MB"} 200 <- a prefix does NOT make a second item of the same URL while it is queued +history after 20 s: +done Big_Buck_Bunny_360_10s_1MB finished Big_Buck_Bunny_360_10s_1MB.mp4 991017 +done probe1.Sintel_360_10s_1MB error None None ERROR: Postprocessing: WARNING: unable to obtain file audio codec with ffprobe <- the test clips carry NO audio stream (ffprobe: stream,video only), so audio-only is not usable with them diff --git a/documentation/audits/family-gate-2026-10-02/C-metube-bench/birth/add-probe-readd.txt b/documentation/audits/family-gate-2026-10-02/C-metube-bench/birth/add-probe-readd.txt new file mode 100644 index 00000000..8e8077ee --- /dev/null +++ b/documentation/audits/family-gate-2026-10-02/C-metube-bench/birth/add-probe-readd.txt @@ -0,0 +1,5 @@ +== re-add a URL already in done, new prefix: +{"status": "ok"} 200 +done again1.Big_Buck_Bunny_360_10s_1MB finished again1.Big_Buck_Bunny_360_10s_1MB.mp4 991017 +done probe1.Sintel_360_10s_1MB error None None ERROR: Postprocessing: WARNING: unable to obtain file audio codec with ffprobe +stream,video diff --git a/documentation/audits/family-gate-2026-10-02/C-metube-bench/birth/birth.py b/documentation/audits/family-gate-2026-10-02/C-metube-bench/birth/birth.py new file mode 100644 index 00000000..8098cbc5 --- /dev/null +++ b/documentation/audits/family-gate-2026-10-02/C-metube-bench/birth/birth.py @@ -0,0 +1,48 @@ +"""birth.py — checklist 5.1 / 4.3 (bench half) for metube at the PIN, from birth, swap 0. +Renders the catalog template exactly as upgrade-test.py does (its own render/build_env/settle), starts the +from-birth sampler BEFORE `up -d`, times up -d -> every container healthy, then keeps sampling idle for HOLD s. +Images are pre-pulled, so the time is start time, not download time. Prints no secret. +(Copied from the visitors-2026-10-01 C bench birth.py; only the app/container names changed.)""" +import importlib.util, json, os, subprocess, sys, time +sys.path.insert(0, "/opt/upg"); os.chdir("/opt/upg") +spec = importlib.util.spec_from_file_location("ut", "/opt/upg/upgrade-test.py"); ut = importlib.util.module_from_spec(spec) +sys.argv = ["upgrade-test.py"]; spec.loader.exec_module(ut) +from pathlib import Path +HOLD = int(os.environ.get("HOLD", "300")) +app, project = "metube", "birth" +NAMES = ("metube",) +wd = Path("/opt/upg/work/BIRTH"); ev = Path("/opt/upg/evidence/BIRTH"); ev.mkdir(parents=True, exist_ok=True) +tdir = ut.TEMPLATES / app +env = ut.cvp.build_env(app, (tdir / ".felhom.yml").read_text(), (tdir / "docker-compose.yml").read_text()) +ut.render(app, {}, wd, env) +log = open(ev / "birth.log", "w", buffering=1) +def say(m): + l = time.strftime("[%H:%M:%S] ", time.gmtime()) + m; print(l, flush=True); log.write(l + "\n") +say("images: " + json.dumps(ut.template_images(app, ut.TEMPLATES))) +say("swap on the bench: " + (subprocess.run(["swapon", "--show"], capture_output=True, text=True).stdout.strip() or "none")) +subprocess.run("rm -f /root/mem-birth.csv.stop; (NAMES='metube' OUT=/root/mem-birth.csv DUR=1200 nohup bash /opt/upg/sampler.sh >/dev/null 2>&1 &)", shell=True) +time.sleep(1) +t0 = time.time() +up = ut.compose(wd, project, "up", "-d") +say(f"up -d rc={up.returncode} in {time.time()-t0:.1f}s") +ok, secs, states = ut.settle(project, wd, wait=600) +say(f"settled={ok} in {time.time()-t0:.1f}s after up -d (settle loop {secs}s) :: {json.dumps(states)}") +for c in NAMES: + r = subprocess.run(["docker", "inspect", "-f", "{{.State.StartedAt}} {{.State.Health.Status}} rc={{.RestartCount}} mem={{.HostConfig.Memory}}", c], capture_output=True, text=True) + say(f" {c}: {r.stdout.strip()}") + r = subprocess.run(["docker", "inspect", "-f", "{{json .State.Health.Log}}", c], capture_output=True, text=True) + try: + hl = json.loads(r.stdout) + say(f" {c}: first health probe {hl[0]['Start']} exit={hl[0]['ExitCode']}; probes so far {len(hl)}") + except Exception as e: + say(f" {c}: health log unreadable {e}") +say(f"holding {HOLD}s idle for the from-birth sampler") +time.sleep(HOLD) +for c in NAMES: + r = subprocess.run(["docker", "inspect", "-f", "{{.State.Status}} {{.State.Health.Status}} rc={{.RestartCount}} OOMKilled(not-used)={{.State.OOMKilled}}", c], capture_output=True, text=True) + say(f" end {c}: {r.stdout.strip()}") +r = subprocess.run("docker logs metube 2>&1 | head -60", shell=True, capture_output=True, text=True) +(ev / "metube-startlines.txt").write_text(r.stdout) +say("metube first 60 log lines saved, %d lines" % len(r.stdout.splitlines())) +r = subprocess.run("docker exec metube sh -c 'ls -la /state /downloads; id'", shell=True, capture_output=True, text=True) +(ev / "metube-dirs.txt").write_text(r.stdout + r.stderr) diff --git a/documentation/audits/family-gate-2026-10-02/C-metube-bench/birth/evidence/BIRTH/birth.log b/documentation/audits/family-gate-2026-10-02/C-metube-bench/birth/evidence/BIRTH/birth.log new file mode 100644 index 00000000..0309757d --- /dev/null +++ b/documentation/audits/family-gate-2026-10-02/C-metube-bench/birth/evidence/BIRTH/birth.log @@ -0,0 +1,9 @@ +[06:11:35] images: {"metube": "ghcr.io/alexta69/metube:2026.09.29"} +[06:11:35] swap on the bench: none +[06:11:36] up -d rc=0 in 0.4s +[06:12:07] settled=True in 31.2s after up -d (settle loop 30.8s) :: {"metube": {"status": "running", "health": "healthy", "restarts": 0, "exit": 0}} +[06:12:07] metube: 2026-10-02T06:11:36.683758201Z healthy rc=0 mem=805306368 +[06:12:07] metube: first health probe 2026-10-02T06:12:06.691609369Z exit=0; probes so far 1 +[06:12:07] holding 300s idle for the from-birth sampler +[06:17:07] end metube: running healthy rc=0 OOMKilled(not-used)=false +[06:17:07] metube first 60 log lines saved, 7 lines diff --git a/documentation/audits/family-gate-2026-10-02/C-metube-bench/birth/evidence/BIRTH/metube-dirs.txt b/documentation/audits/family-gate-2026-10-02/C-metube-bench/birth/evidence/BIRTH/metube-dirs.txt new file mode 100644 index 00000000..b36f3f2e --- /dev/null +++ b/documentation/audits/family-gate-2026-10-02/C-metube-bench/birth/evidence/BIRTH/metube-dirs.txt @@ -0,0 +1,10 @@ +/downloads: +total 8 +drwxr-xr-x 2 1000 1000 4096 Oct 2 08:11 . +drwxr-xr-x 1 root root 4096 Oct 2 08:11 .. + +/state: +total 8 +drwxr-xr-x 2 1000 1000 4096 Oct 2 08:11 . +drwxr-xr-x 1 root root 4096 Oct 2 08:11 .. +uid=0(root) gid=0(root) groups=0(root) diff --git a/documentation/audits/family-gate-2026-10-02/C-metube-bench/birth/evidence/BIRTH/metube-startlines.txt b/documentation/audits/family-gate-2026-10-02/C-metube-bench/birth/evidence/BIRTH/metube-startlines.txt new file mode 100644 index 00000000..3447e4a5 --- /dev/null +++ b/documentation/audits/family-gate-2026-10-02/C-metube-bench/birth/evidence/BIRTH/metube-startlines.txt @@ -0,0 +1,7 @@ +Setting umask to 022 +Creating download directory (/downloads), audio download directory (/downloads), state directory (/state), and temp dir (/downloads) +Changing ownership of download and state directories to 1000:1000 +Starting BgUtils POT Provider +Running MeTube as user 1000:1000 +INFO:main:Listening on 0.0.0.0:8081 +INFO:ytdl:Initializing DownloadQueue diff --git a/documentation/audits/family-gate-2026-10-02/C-metube-bench/birth/mem-birth.csv b/documentation/audits/family-gate-2026-10-02/C-metube-bench/birth/mem-birth.csv new file mode 100644 index 00000000..b0e16e91 --- /dev/null +++ b/documentation/audits/family-gate-2026-10-02/C-metube-bench/birth/mem-birth.csv @@ -0,0 +1,170 @@ +1790921497,metube,fd5f36bf31db,28708864,36356096,0,0,805306368,0 +1790921499,metube,fd5f36bf31db,61612032,69578752,0,0,805306368,0 +1790921501,metube,fd5f36bf31db,61612032,69578752,0,0,805306368,0 +1790921503,metube,fd5f36bf31db,61612032,69578752,0,0,805306368,0 +1790921505,metube,fd5f36bf31db,61612032,69578752,0,0,805306368,0 +1790921507,metube,fd5f36bf31db,61612032,69578752,0,0,805306368,0 +1790921509,metube,fd5f36bf31db,61612032,69550080,0,0,805306368,0 +1790921511,metube,fd5f36bf31db,61612032,69545984,0,0,805306368,0 +1790921513,metube,fd5f36bf31db,61612032,69545984,0,0,805306368,0 +1790921515,metube,fd5f36bf31db,61612032,69545984,0,0,805306368,0 +1790921517,metube,fd5f36bf31db,61612032,69545984,0,0,805306368,0 +1790921519,metube,fd5f36bf31db,61612032,69545984,0,0,805306368,0 +1790921521,metube,fd5f36bf31db,61612032,69545984,0,0,805306368,0 +1790921524,metube,fd5f36bf31db,61612032,69545984,0,0,805306368,0 +1790921526,metube,fd5f36bf31db,61612032,69545984,0,0,805306368,0 +1790921528,metube,fd5f36bf31db,61648896,69681152,0,0,805306368,0 +1790921530,metube,fd5f36bf31db,61648896,69681152,0,0,805306368,0 +1790921532,metube,fd5f36bf31db,61648896,69677056,0,0,805306368,0 +1790921534,metube,fd5f36bf31db,61648896,69951488,0,0,805306368,0 +1790921536,metube,fd5f36bf31db,61648896,69951488,0,0,805306368,0 +1790921538,metube,fd5f36bf31db,61648896,69951488,0,0,805306368,0 +1790921540,metube,fd5f36bf31db,61648896,69951488,0,0,805306368,0 +1790921542,metube,fd5f36bf31db,61648896,69951488,0,0,805306368,0 +1790921544,metube,fd5f36bf31db,61648896,69951488,0,0,805306368,0 +1790921546,metube,fd5f36bf31db,61648896,69951488,0,0,805306368,0 +1790921548,metube,fd5f36bf31db,61648896,69951488,0,0,805306368,0 +1790921550,metube,fd5f36bf31db,61648896,69951488,0,0,805306368,0 +1790921552,metube,fd5f36bf31db,61648896,69951488,0,0,805306368,0 +1790921554,metube,fd5f36bf31db,61648896,69951488,0,0,805306368,0 +1790921557,metube,fd5f36bf31db,61648896,70660096,0,0,805306368,0 +1790921559,metube,fd5f36bf31db,61648896,69951488,0,0,805306368,0 +1790921561,metube,fd5f36bf31db,61648896,69951488,0,0,805306368,0 +1790921563,metube,fd5f36bf31db,61648896,69951488,0,0,805306368,0 +1790921565,metube,fd5f36bf31db,61648896,69951488,0,0,805306368,0 +1790921567,metube,fd5f36bf31db,61648896,69951488,0,0,805306368,0 +1790921569,metube,fd5f36bf31db,61648896,69951488,0,0,805306368,0 +1790921571,metube,fd5f36bf31db,61648896,69951488,0,0,805306368,0 +1790921573,metube,fd5f36bf31db,61648896,69951488,0,0,805306368,0 +1790921575,metube,fd5f36bf31db,61648896,69951488,0,0,805306368,0 +1790921577,metube,fd5f36bf31db,61648896,69951488,0,0,805306368,0 +1790921579,metube,fd5f36bf31db,61648896,69951488,0,0,805306368,0 +1790921581,metube,fd5f36bf31db,61648896,69951488,0,0,805306368,0 +1790921583,metube,fd5f36bf31db,61648896,69951488,0,0,805306368,0 +1790921585,metube,fd5f36bf31db,61648896,69951488,0,0,805306368,0 +1790921587,metube,fd5f36bf31db,61652992,70221824,0,0,805306368,0 +1790921589,metube,fd5f36bf31db,61652992,69955584,0,0,805306368,0 +1790921592,metube,fd5f36bf31db,61652992,69955584,0,0,805306368,0 +1790921594,metube,fd5f36bf31db,61652992,69955584,0,0,805306368,0 +1790921596,metube,fd5f36bf31db,61652992,69955584,0,0,805306368,0 +1790921598,metube,fd5f36bf31db,61652992,69955584,0,0,805306368,0 +1790921600,metube,fd5f36bf31db,61652992,69955584,0,0,805306368,0 +1790921602,metube,fd5f36bf31db,61652992,69955584,0,0,805306368,0 +1790921604,metube,fd5f36bf31db,61652992,69955584,0,0,805306368,0 +1790921606,metube,fd5f36bf31db,61652992,69955584,0,0,805306368,0 +1790921608,metube,fd5f36bf31db,61652992,69955584,0,0,805306368,0 +1790921610,metube,fd5f36bf31db,61652992,69955584,0,0,805306368,0 +1790921612,metube,fd5f36bf31db,61652992,69955584,0,0,805306368,0 +1790921614,metube,fd5f36bf31db,61652992,69955584,0,0,805306368,0 +1790921616,metube,fd5f36bf31db,61652992,69955584,0,0,805306368,0 +1790921618,metube,fd5f36bf31db,61652992,69955584,0,0,805306368,0 +1790921620,metube,fd5f36bf31db,61652992,69955584,0,0,805306368,0 +1790921622,metube,fd5f36bf31db,61652992,69955584,0,0,805306368,0 +1790921625,metube,fd5f36bf31db,61652992,69955584,0,0,805306368,0 +1790921627,metube,fd5f36bf31db,61652992,69955584,0,0,805306368,0 +1790921629,metube,fd5f36bf31db,61652992,69955584,0,0,805306368,0 +1790921631,metube,fd5f36bf31db,61652992,69955584,0,0,805306368,0 +1790921633,metube,fd5f36bf31db,61652992,69955584,0,0,805306368,0 +1790921635,metube,fd5f36bf31db,61652992,69955584,0,0,805306368,0 +1790921637,metube,fd5f36bf31db,61652992,69955584,0,0,805306368,0 +1790921639,metube,fd5f36bf31db,61652992,69955584,0,0,805306368,0 +1790921641,metube,fd5f36bf31db,61652992,69955584,0,0,805306368,0 +1790921643,metube,fd5f36bf31db,61652992,69955584,0,0,805306368,0 +1790921645,metube,fd5f36bf31db,61652992,69955584,0,0,805306368,0 +1790921647,metube,fd5f36bf31db,61652992,69959680,0,0,805306368,0 +1790921649,metube,fd5f36bf31db,61652992,69955584,0,0,805306368,0 +1790921651,metube,fd5f36bf31db,61652992,69955584,0,0,805306368,0 +1790921653,metube,fd5f36bf31db,61652992,69955584,0,0,805306368,0 +1790921655,metube,fd5f36bf31db,61652992,69955584,0,0,805306368,0 +1790921657,metube,fd5f36bf31db,61652992,69955584,0,0,805306368,0 +1790921660,metube,fd5f36bf31db,61652992,69955584,0,0,805306368,0 +1790921662,metube,fd5f36bf31db,61652992,69955584,0,0,805306368,0 +1790921664,metube,fd5f36bf31db,61652992,69955584,0,0,805306368,0 +1790921666,metube,fd5f36bf31db,61652992,69955584,0,0,805306368,0 +1790921668,metube,fd5f36bf31db,61652992,69955584,0,0,805306368,0 +1790921670,metube,fd5f36bf31db,61652992,69955584,0,0,805306368,0 +1790921672,metube,fd5f36bf31db,61652992,69955584,0,0,805306368,0 +1790921674,metube,fd5f36bf31db,61652992,69955584,0,0,805306368,0 +1790921676,metube,fd5f36bf31db,61652992,69955584,0,0,805306368,0 +1790921678,metube,fd5f36bf31db,61652992,69959680,0,0,805306368,0 +1790921680,metube,fd5f36bf31db,61652992,69955584,0,0,805306368,0 +1790921682,metube,fd5f36bf31db,61652992,69955584,0,0,805306368,0 +1790921684,metube,fd5f36bf31db,61652992,69955584,0,0,805306368,0 +1790921686,metube,fd5f36bf31db,61652992,69955584,0,0,805306368,0 +1790921688,metube,fd5f36bf31db,61652992,69955584,0,0,805306368,0 +1790921690,metube,fd5f36bf31db,61652992,69955584,0,0,805306368,0 +1790921692,metube,fd5f36bf31db,61652992,69955584,0,0,805306368,0 +1790921695,metube,fd5f36bf31db,61652992,69955584,0,0,805306368,0 +1790921697,metube,fd5f36bf31db,61652992,69955584,0,0,805306368,0 +1790921699,metube,fd5f36bf31db,61652992,69955584,0,0,805306368,0 +1790921701,metube,fd5f36bf31db,61652992,69955584,0,0,805306368,0 +1790921703,metube,fd5f36bf31db,61652992,69955584,0,0,805306368,0 +1790921705,metube,fd5f36bf31db,61652992,69955584,0,0,805306368,0 +1790921707,metube,fd5f36bf31db,61652992,70434816,0,0,805306368,0 +1790921709,metube,fd5f36bf31db,61652992,69955584,0,0,805306368,0 +1790921711,metube,fd5f36bf31db,61652992,69955584,0,0,805306368,0 +1790921713,metube,fd5f36bf31db,61652992,69955584,0,0,805306368,0 +1790921715,metube,fd5f36bf31db,61652992,69955584,0,0,805306368,0 +1790921717,metube,fd5f36bf31db,61652992,69955584,0,0,805306368,0 +1790921719,metube,fd5f36bf31db,61652992,69955584,0,0,805306368,0 +1790921721,metube,fd5f36bf31db,61652992,69955584,0,0,805306368,0 +1790921723,metube,fd5f36bf31db,61652992,69955584,0,0,805306368,0 +1790921725,metube,fd5f36bf31db,61652992,69955584,0,0,805306368,0 +1790921728,metube,fd5f36bf31db,61652992,69955584,0,0,805306368,0 +1790921730,metube,fd5f36bf31db,61652992,69955584,0,0,805306368,0 +1790921732,metube,fd5f36bf31db,61652992,69955584,0,0,805306368,0 +1790921734,metube,fd5f36bf31db,61652992,69955584,0,0,805306368,0 +1790921736,metube,fd5f36bf31db,61652992,69955584,0,0,805306368,0 +1790921738,metube,fd5f36bf31db,61652992,69959680,0,0,805306368,0 +1790921740,metube,fd5f36bf31db,61652992,69955584,0,0,805306368,0 +1790921742,metube,fd5f36bf31db,61652992,69955584,0,0,805306368,0 +1790921744,metube,fd5f36bf31db,61652992,69955584,0,0,805306368,0 +1790921746,metube,fd5f36bf31db,61652992,69955584,0,0,805306368,0 +1790921748,metube,fd5f36bf31db,61652992,69955584,0,0,805306368,0 +1790921750,metube,fd5f36bf31db,61652992,69955584,0,0,805306368,0 +1790921752,metube,fd5f36bf31db,61652992,69955584,0,0,805306368,0 +1790921754,metube,fd5f36bf31db,61652992,69955584,0,0,805306368,0 +1790921756,metube,fd5f36bf31db,61652992,69955584,0,0,805306368,0 +1790921758,metube,fd5f36bf31db,61652992,69955584,0,0,805306368,0 +1790921760,metube,fd5f36bf31db,61652992,69955584,0,0,805306368,0 +1790921763,metube,fd5f36bf31db,61652992,69955584,0,0,805306368,0 +1790921765,metube,fd5f36bf31db,61652992,69955584,0,0,805306368,0 +1790921767,metube,fd5f36bf31db,61652992,69955584,0,0,805306368,0 +1790921769,metube,fd5f36bf31db,61652992,69955584,0,0,805306368,0 +1790921771,metube,fd5f36bf31db,61652992,69955584,0,0,805306368,0 +1790921773,metube,fd5f36bf31db,61652992,69955584,0,0,805306368,0 +1790921775,metube,fd5f36bf31db,61652992,69955584,0,0,805306368,0 +1790921777,metube,fd5f36bf31db,61652992,69955584,0,0,805306368,0 +1790921779,metube,fd5f36bf31db,61652992,69955584,0,0,805306368,0 +1790921781,metube,fd5f36bf31db,61652992,69955584,0,0,805306368,0 +1790921783,metube,fd5f36bf31db,61652992,69955584,0,0,805306368,0 +1790921785,metube,fd5f36bf31db,61652992,69955584,0,0,805306368,0 +1790921787,metube,fd5f36bf31db,61652992,69955584,0,0,805306368,0 +1790921789,metube,fd5f36bf31db,61652992,69955584,0,0,805306368,0 +1790921791,metube,fd5f36bf31db,61652992,69955584,0,0,805306368,0 +1790921793,metube,fd5f36bf31db,61652992,69955584,0,0,805306368,0 +1790921796,metube,fd5f36bf31db,61652992,69955584,0,0,805306368,0 +1790921798,metube,fd5f36bf31db,61652992,70107136,0,0,805306368,0 +1790921800,metube,fd5f36bf31db,61652992,69955584,0,0,805306368,0 +1790921802,metube,fd5f36bf31db,61652992,69955584,0,0,805306368,0 +1790921804,metube,fd5f36bf31db,61652992,69955584,0,0,805306368,0 +1790921806,metube,fd5f36bf31db,61652992,69955584,0,0,805306368,0 +1790921808,metube,fd5f36bf31db,61652992,69955584,0,0,805306368,0 +1790921810,metube,fd5f36bf31db,61652992,69955584,0,0,805306368,0 +1790921812,metube,fd5f36bf31db,61652992,69955584,0,0,805306368,0 +1790921814,metube,fd5f36bf31db,61652992,69955584,0,0,805306368,0 +1790921816,metube,fd5f36bf31db,61652992,69955584,0,0,805306368,0 +1790921818,metube,fd5f36bf31db,61652992,69955584,0,0,805306368,0 +1790921820,metube,fd5f36bf31db,61652992,69955584,0,0,805306368,0 +1790921822,metube,fd5f36bf31db,61652992,69955584,0,0,805306368,0 +1790921824,metube,fd5f36bf31db,61652992,69955584,0,0,805306368,0 +1790921826,metube,fd5f36bf31db,61652992,69955584,0,0,805306368,0 +1790921828,metube,fd5f36bf31db,61652992,69959680,0,0,805306368,0 +1790921831,metube,fd5f36bf31db,61652992,69959680,0,0,805306368,0 +1790921833,metube,fd5f36bf31db,61652992,69959680,0,0,805306368,0 +1790921835,metube,fd5f36bf31db,61652992,69959680,0,0,805306368,0 +1790921837,metube,fd5f36bf31db,61652992,69959680,0,0,805306368,0 +1790921839,metube,fd5f36bf31db,61652992,69959680,0,0,805306368,0 +1790921841,metube,fd5f36bf31db,61652992,69959680,0,0,805306368,0 +1790921843,metube,fd5f36bf31db,61652992,69959680,0,0,805306368,0 +1790921845,metube,fd5f36bf31db,61652992,69959680,0,0,805306368,0 diff --git a/documentation/audits/family-gate-2026-10-02/C-metube-bench/birth/mem-summary.txt b/documentation/audits/family-gate-2026-10-02/C-metube-bench/birth/mem-summary.txt new file mode 100644 index 00000000..44ca78b5 --- /dev/null +++ b/documentation/audits/family-gate-2026-10-02/C-metube-bench/birth/mem-summary.txt @@ -0,0 +1,2 @@ +5.1 from birth at the pin (ghcr.io/alexta69/metube:2026.09.29), bench 9401 swap 0, 2 s cgroup sampler started before up -d; idle 300 s after healthy; rows up to the end of the idle hold (06:17:07Z) — after it the sampler was stopped and one /add probe was sent, see birth.out +metube fd5f36bf31db 06:11:37-06:17:06 n= 161 peak anon 58.8 MiB = 7.7 % of 768 MiB; peak current 67.4 MiB; oom_kill 0; restarts 0; swap.current ['0'] diff --git a/documentation/audits/family-gate-2026-10-02/C-metube-bench/birth/sampler.sh b/documentation/audits/family-gate-2026-10-02/C-metube-bench/birth/sampler.sh new file mode 100644 index 00000000..85fe5898 --- /dev/null +++ b/documentation/audits/family-gate-2026-10-02/C-metube-bench/birth/sampler.sh @@ -0,0 +1,12 @@ +# sampler.sh (run INSIDE the bench or the box): sample the cgroup of each named container from its birth, every 2 s. +# usage: NAMES="a b c" OUT=/root/mem.csv DUR=900 bash sampler.sh & — stops at DUR or when $OUT.stop exists +: > $OUT; t0=$(date +%s) +while [ $(( $(date +%s) - t0 )) -lt ${DUR:-900} ] && [ ! -f $OUT.stop ]; do + for n in $NAMES; do + id=$(docker ps -q --no-trunc --filter name=^$n$ | head -1); [ -z "$id" ] && continue + cg=$(ls -d /sys/fs/cgroup/system.slice/docker-$id.scope 2>/dev/null || find /sys/fs/cgroup -maxdepth 4 -type d -name "*$id*" 2>/dev/null | head -1) + [ -f "$cg/memory.stat" ] || continue + echo "$(date +%s),$n,${id:0:12},$(awk '$1=="anon"{print $2}' $cg/memory.stat),$(cat $cg/memory.current),$(cat $cg/memory.swap.current 2>/dev/null || echo NA),$(awk '$1=="oom_kill"{print $2}' $cg/memory.events),$(cat $cg/memory.max),$(docker inspect -f '{{.RestartCount}}' $id)" >> $OUT + done + sleep 2 +done diff --git a/documentation/audits/family-gate-2026-10-02/C-metube-bench/move/MV-metube.log b/documentation/audits/family-gate-2026-10-02/C-metube-bench/move/MV-metube.log new file mode 100644 index 00000000..d472b22e --- /dev/null +++ b/documentation/audits/family-gate-2026-10-02/C-metube-bench/move/MV-metube.log @@ -0,0 +1,114 @@ +[06:19:20] scratch drive folders cleared before FROM (R-656): ['/srv/felhom-gate/hdd/userdata/media/metube'] +[06:19:20] MV-metube: deploying metube at FROM {'metube': 'ghcr.io/alexta69/metube:2026.09.28'} +[06:19:51] FROM settled=True in 30.8s :: {"metube": {"status": "running", "health": "healthy", "restarts": 0, "exit": 0}} +[06:19:51] fixture: the BOX walk's own (MeTube), through upgrade_boxport +[06:19:52] metube: POST /add http=200 {"status": "ok"} +[06:19:55] metube: the download finished (991017 bytes) +[06:19:55] metube: history has it=True; GET /download/ http=200 +[06:19:55] C1 (seed reads back BEFORE): True +[06:19:56] MV-metube: swapping to TO {'metube': 'ghcr.io/alexta69/metube:2026.09.29'} +[06:19:56] TO up -d rc=0 +[06:20:27] TO settled=True in 30.8s :: {"metube": {"status": "running", "health": "healthy", "restarts": 0, "exit": 0}} +[06:20:27] migration lines observed: 0 +[06:20:28] metube: history has it=True; GET /download/ http=200 +[06:20:28] RESULT (seed reads back AFTER): True +[06:20:28] memory watch: 600s, 4 callers on 1 path(s) at 172.18.0.2:8081 +[06:20:43] + 15s metube=208M/768M peak=255M kills=0 rs=0 reqs=262 +[06:20:58] + 30s metube=206M/768M peak=255M kills=0 rs=0 reqs=562 +[06:21:13] + 45s metube=206M/768M peak=255M kills=0 rs=0 reqs=858 +[06:21:28] + 60s metube=327M/768M peak=371M kills=0 rs=0 reqs=1147 +[06:21:44] + 76s metube=326M/768M peak=371M kills=0 rs=0 reqs=1446 +[06:21:59] + 91s metube=326M/768M peak=371M kills=0 rs=0 reqs=1743 +[06:22:14] + 106s metube=429M/768M peak=465M kills=0 rs=0 reqs=2030 +[06:22:29] + 121s metube=429M/768M peak=465M kills=0 rs=0 reqs=2330 +[06:22:44] + 136s metube=430M/768M peak=465M kills=0 rs=0 reqs=2630 +[06:22:59] + 151s metube=550M/768M peak=588M kills=0 rs=0 reqs=2914 +[06:23:14] + 166s metube=550M/768M peak=588M kills=0 rs=0 reqs=3214 +[06:23:30] + 182s metube=550M/768M peak=588M kills=0 rs=0 reqs=3514 +[06:23:45] + 197s metube=653M/768M peak=692M kills=0 rs=0 reqs=3802 +[06:24:00] + 212s metube=652M/768M peak=692M kills=0 rs=0 reqs=4098 +[06:24:15] + 227s metube=652M/768M peak=692M kills=0 rs=0 reqs=4398 +[06:24:30] + 242s metube=721M/768M peak=769M kills=0 rs=0 reqs=4683 +[06:24:45] + 257s metube=719M/768M peak=769M kills=0 rs=0 reqs=4982 +[06:25:00] + 272s metube=719M/768M peak=769M kills=0 rs=0 reqs=5282 +[06:25:15] + 287s metube=724M/768M peak=770M kills=0 rs=0 reqs=5570 +[06:25:31] + 302s metube=724M/768M peak=770M kills=0 rs=0 reqs=5870 +[06:25:46] + 318s metube=724M/768M peak=770M kills=0 rs=0 reqs=6167 +[06:26:01] + 333s metube=730M/768M peak=770M kills=0 rs=0 reqs=6454 +[06:26:16] + 348s metube=731M/768M peak=770M kills=0 rs=0 reqs=6754 +[06:26:31] + 363s metube=730M/768M peak=770M kills=0 rs=0 reqs=7054 +[06:26:46] + 378s metube=705M/768M peak=771M kills=0 rs=0 reqs=7342 +[06:27:01] + 393s metube=704M/768M peak=771M kills=0 rs=0 reqs=7638 +[06:27:16] + 408s metube=705M/768M peak=771M kills=0 rs=0 reqs=7938 +[06:27:31] + 424s metube=730M/768M peak=771M kills=0 rs=0 reqs=8226 +[06:27:47] + 439s metube=729M/768M peak=771M kills=0 rs=0 reqs=8522 +[06:28:02] + 454s metube=729M/768M peak=771M kills=0 rs=0 reqs=8822 +[06:28:17] + 469s metube=714M/768M peak=771M kills=0 rs=0 reqs=9111 +[06:28:32] + 484s metube=714M/768M peak=771M kills=0 rs=0 reqs=9411 +[06:28:47] + 499s metube=715M/768M peak=771M kills=0 rs=0 reqs=9707 +[06:29:02] + 514s metube=718M/768M peak=771M kills=0 rs=0 reqs=9996 +[06:29:17] + 529s metube=718M/768M peak=771M kills=0 rs=0 reqs=10294 +[06:29:32] + 544s metube=718M/768M peak=771M kills=0 rs=0 reqs=10593 +[06:29:48] + 560s metube=718M/768M peak=771M kills=0 rs=0 reqs=10892 +[06:30:03] + 575s metube=717M/768M peak=771M kills=0 rs=0 reqs=11190 +[06:30:18] + 590s metube=693M/768M peak=771M kills=0 rs=0 reqs=11490 +[06:30:33] + 605s metube=693M/768M peak=771M kills=0 rs=0 reqs=11790 +[06:30:33] memory watch: killed=False tight=[] requests=11790 codes={'200': 11790} +[06:30:33] MV-metube: ABORT — putting the FROM images back +[06:31:05] metube: history has it=True; GET /download/ http=200 +[06:31:05] ABORT: app came back in 30.8s; data present=True +{ + "harness_version": 4, + "edge": "MV-metube", + "app": "metube", + "note": "night 2026-09-23 within-a-major move: metube=ghcr.io/alexta69/metube:2026.09.29", + "from": { + "metube": "ghcr.io/alexta69/metube:2026.09.28" + }, + "to": { + "metube": "ghcr.io/alexta69/metube:2026.09.29" + }, + "verdict": "proven", + "seed_read_before": true, + "seed_read_after": true, + "healthy_after": true, + "migration_observed": null, + "abort": "starts-and-serves", + "abort_detail": null, + "engine_state_after": null, + "memory": { + "soak_s": 605.3, + "requested_s": 600, + "requests": 11790, + "codes": { + "200": 11790 + }, + "first_kill": null, + "containers": { + "metube": { + "limit": 805306368, + "peak": 809365504, + "peak_pct": 1.005, + "anon_peak_sampled": 226152448, + "anon_peak_pct": 0.281, + "oom_kills": 0, + "restarts": 0, + "oomkilled_flag": false, + "measured": true + } + }, + "unmeasured": [], + "load": "reached" + }, + "marks": [], + "bench_overrides": null, + "duration_s": 30.8, + "measured_at": "2026-10-02T06:31:05Z", + "evidence": "evidence/MV-metube", + "scratch_cleared": [ + "/srv/felhom-gate/hdd/userdata/media/metube" + ], + "files_changed": [], + "files_changed_detail": [], + "total_s": 705.3 +} diff --git a/documentation/audits/family-gate-2026-10-02/C-metube-bench/move/evidence/MV-metube/abort-states.json b/documentation/audits/family-gate-2026-10-02/C-metube-bench/move/evidence/MV-metube/abort-states.json new file mode 100644 index 00000000..b3053e83 --- /dev/null +++ b/documentation/audits/family-gate-2026-10-02/C-metube-bench/move/evidence/MV-metube/abort-states.json @@ -0,0 +1,8 @@ +{ + "metube": { + "status": "running", + "health": "healthy", + "restarts": 0, + "exit": 0 + } +} \ No newline at end of file diff --git a/documentation/audits/family-gate-2026-10-02/C-metube-bench/move/evidence/MV-metube/burst-metube.log b/documentation/audits/family-gate-2026-10-02/C-metube-bench/move/evidence/MV-metube/burst-metube.log new file mode 100644 index 00000000..b71cb022 --- /dev/null +++ b/documentation/audits/family-gate-2026-10-02/C-metube-bench/move/evidence/MV-metube/burst-metube.log @@ -0,0 +1,51 @@ +burst start 06:20:30Z base http://172.18.0.2:8081 ++ 3s round 1 posted 8 at once; adds {"200": 8}; history (200, {'queue': 8, 'pending': 0, 'done_finished': 1}) ++ 18s history (200, {'queue': 0, 'pending': 0, 'done_finished': 9}) ++ 33s history (200, {'queue': 0, 'pending': 0, 'done_finished': 9}) ++ 48s history (200, {'queue': 0, 'pending': 0, 'done_finished': 9}) ++ 49s round 2 posted 8 at once; adds {"200": 16}; history (200, {'queue': 8, 'pending': 0, 'done_finished': 9}) ++ 64s history (200, {'queue': 0, 'pending': 0, 'done_finished': 9}) ++ 79s history (200, {'queue': 0, 'pending': 0, 'done_finished': 9}) ++ 94s history (200, {'queue': 0, 'pending': 0, 'done_finished': 9}) ++ 95s round 3 posted 8 at once; adds {"200": 24}; history (200, {'queue': 8, 'pending': 0, 'done_finished': 9}) ++ 110s history (200, {'queue': 0, 'pending': 0, 'done_finished': 9}) ++ 125s history (200, {'queue': 0, 'pending': 0, 'done_finished': 9}) ++ 140s history (200, {'queue': 0, 'pending': 0, 'done_finished': 9}) ++ 141s round 4 posted 8 at once; adds {"200": 32}; history (200, {'queue': 8, 'pending': 0, 'done_finished': 9}) ++ 156s history (200, {'queue': 0, 'pending': 0, 'done_finished': 9}) ++ 171s history (200, {'queue': 0, 'pending': 0, 'done_finished': 9}) ++ 186s history (200, {'queue': 0, 'pending': 0, 'done_finished': 9}) ++ 187s round 5 posted 8 at once; adds {"200": 40}; history (200, {'queue': 8, 'pending': 0, 'done_finished': 9}) ++ 202s history (200, {'queue': 0, 'pending': 0, 'done_finished': 9}) ++ 217s history (200, {'queue': 0, 'pending': 0, 'done_finished': 9}) ++ 232s history (200, {'queue': 0, 'pending': 0, 'done_finished': 9}) ++ 232s round 6 posted 8 at once; adds {"200": 48}; history (200, {'queue': 8, 'pending': 0, 'done_finished': 9}) ++ 247s history (200, {'queue': 0, 'pending': 0, 'done_finished': 9}) ++ 262s history (200, {'queue': 0, 'pending': 0, 'done_finished': 9}) ++ 277s history (200, {'queue': 0, 'pending': 0, 'done_finished': 9}) ++ 278s round 7 posted 8 at once; adds {"200": 56}; history (200, {'queue': 8, 'pending': 0, 'done_finished': 9}) ++ 293s history (200, {'queue': 0, 'pending': 0, 'done_finished': 9}) ++ 308s history (200, {'queue': 0, 'pending': 0, 'done_finished': 9}) ++ 323s history (200, {'queue': 0, 'pending': 0, 'done_finished': 9}) ++ 324s round 8 posted 8 at once; adds {"200": 64}; history (200, {'queue': 8, 'pending': 0, 'done_finished': 9}) ++ 339s history (200, {'queue': 0, 'pending': 0, 'done_finished': 9}) ++ 354s history (200, {'queue': 0, 'pending': 0, 'done_finished': 9}) ++ 369s history (200, {'queue': 0, 'pending': 0, 'done_finished': 9}) ++ 370s round 9 posted 8 at once; adds {"200": 72}; history (200, {'queue': 8, 'pending': 0, 'done_finished': 9}) ++ 385s history (200, {'queue': 0, 'pending': 0, 'done_finished': 9}) ++ 400s history (200, {'queue': 0, 'pending': 0, 'done_finished': 9}) ++ 415s history (200, {'queue': 0, 'pending': 0, 'done_finished': 9}) ++ 416s round 10 posted 8 at once; adds {"200": 80}; history (200, {'queue': 8, 'pending': 0, 'done_finished': 9}) ++ 431s history (200, {'queue': 0, 'pending': 0, 'done_finished': 9}) ++ 446s history (200, {'queue': 0, 'pending': 0, 'done_finished': 9}) ++ 461s history (200, {'queue': 0, 'pending': 0, 'done_finished': 9}) ++ 462s round 11 posted 8 at once; adds {"200": 88}; history (200, {'queue': 8, 'pending': 0, 'done_finished': 9}) ++ 477s history (200, {'queue': 0, 'pending': 0, 'done_finished': 9}) ++ 492s history (200, {'queue': 0, 'pending': 0, 'done_finished': 9}) ++ 507s history (200, {'queue': 0, 'pending': 0, 'done_finished': 9}) ++ 508s round 12 posted 8 at once; adds {"200": 96}; history (200, {'queue': 8, 'pending': 0, 'done_finished': 9}) ++ 523s history (200, {'queue': 0, 'pending': 0, 'done_finished': 9}) ++ 538s history (200, {'queue': 0, 'pending': 0, 'done_finished': 9}) ++ 553s history (200, {'queue': 0, 'pending': 0, 'done_finished': 9}) +FINAL rounds 12 adds {"200": 96} history (200, {'queue': 0, 'pending': 0, 'done_finished': 9}) +downloads dir: files / size: ['97', '563M', '/downloads'] diff --git a/documentation/audits/family-gate-2026-10-02/C-metube-bench/move/evidence/MV-metube/burst.py b/documentation/audits/family-gate-2026-10-02/C-metube-bench/move/evidence/MV-metube/burst.py new file mode 100644 index 00000000..fdc815ef --- /dev/null +++ b/documentation/audits/family-gate-2026-10-02/C-metube-bench/move/evidence/MV-metube/burst.py @@ -0,0 +1,56 @@ +"""burst.py — checklist 5.2's "heaviest ordinary act" for MeTube on the bench: a household pasting several links at once. +Each ROUND posts 8 DISTINCT public test videos (test-videos.co.uk, 1-10 MB, h264, 360/720/1080p) to POST /add at the +same moment (8 threads), with a per-round custom_name_prefix so a re-added URL becomes a new item; MAX_CONCURRENT_DOWNLOADS=2 +queues the rest. Rounds every GAP s for DUR s. The fixture's own URL (BBB 360 1 MB) is deliberately NOT in the burst, so the +seed's history entry is never replaced. Talks to the container's own port 8081, the path traefik uses. Audio-only is not +used: the test clips have no audio stream (measured). Prints counts and codes only.""" +import json, os, subprocess, threading, time, urllib.request, urllib.error +DUR = int(os.environ.get("DUR", "540")); GAP = int(os.environ.get("GAP", "45")) +B = "https://test-videos.co.uk/vids/" +URLS = [B + p for p in ("bigbuckbunny/mp4/h264/720/Big_Buck_Bunny_720_10s_5MB.mp4", "bigbuckbunny/mp4/h264/1080/Big_Buck_Bunny_1080_10s_10MB.mp4", + "jellyfish/mp4/h264/360/Jellyfish_360_10s_1MB.mp4", "jellyfish/mp4/h264/720/Jellyfish_720_10s_5MB.mp4", + "jellyfish/mp4/h264/1080/Jellyfish_1080_10s_10MB.mp4", "sintel/mp4/h264/360/Sintel_360_10s_1MB.mp4", + "sintel/mp4/h264/720/Sintel_720_10s_5MB.mp4", "sintel/mp4/h264/1080/Sintel_1080_10s_10MB.mp4")] +ip = subprocess.run(["docker", "inspect", "-f", "{{range .NetworkSettings.Networks}}{{.IPAddress}} {{end}}", "metube"], + capture_output=True, text=True).stdout.split()[0] +BASE = f"http://{ip}:8081" +def req(method, path, body=None): + r = urllib.request.Request(BASE + path, data=json.dumps(body).encode() if body is not None else None, + headers={"Content-Type": "application/json"}, method=method) + try: + with urllib.request.urlopen(r, timeout=60) as resp: + d = resp.read(); return resp.status, json.loads(d) if d[:1] in (b"{", b"[") else d + except urllib.error.HTTPError as e: + return e.code, e.read()[:200] + except Exception as e: + return "err", str(e)[:200] +stats = {"add": {}}; lock = threading.Lock() +def add(url, prefix): + c, b = req("POST", "/add", {"url": url, "download_type": "video", "format": "any", "quality": "best", "auto_start": True, "custom_name_prefix": prefix}) + with lock: + stats["add"][str(c)] = stats["add"].get(str(c), 0) + 1 + if c != 200 or (isinstance(b, dict) and b.get("status") != "ok"): print("add", prefix, url.rsplit("/", 1)[1], c, b, flush=True) +def hist(): + c, h = req("GET", "/history") + if not isinstance(h, dict): return c, {} + out = {"queue": len(h.get("queue", [])), "pending": len(h.get("pending", []))} + for d in h.get("done", []): + out["done_" + str(d.get("status"))] = out.get("done_" + str(d.get("status")), 0) + 1 + return c, out +t0 = time.time(); n = 0 +print("burst start", time.strftime("%H:%M:%SZ", time.gmtime()), "base", BASE, flush=True) +while time.time() - t0 < DUR: + n += 1 + ts = [threading.Thread(target=add, args=(u, f"r{n:02d}")) for u in URLS] + [t.start() for t in ts]; [t.join() for t in ts] + print(f"+{time.time()-t0:4.0f}s round {n} posted 8 at once; adds {json.dumps(stats['add'])}; history {hist()}", flush=True) + end = time.time() + GAP + while time.time() < end: + time.sleep(15); print(f"+{time.time()-t0:4.0f}s history {hist()}", flush=True) +for _ in range(40): + c, h = hist() + if h.get("queue", 0) == 0 and h.get("pending", 0) == 0: break + time.sleep(5) +print("FINAL rounds", n, "adds", json.dumps(stats["add"]), "history", hist(), flush=True) +r = subprocess.run("docker exec metube sh -c 'ls /downloads | wc -l; du -sh /downloads'", shell=True, capture_output=True, text=True) +print("downloads dir: files / size:", r.stdout.split(), flush=True) diff --git a/documentation/audits/family-gate-2026-10-02/C-metube-bench/move/evidence/MV-metube/compose-final.log b/documentation/audits/family-gate-2026-10-02/C-metube-bench/move/evidence/MV-metube/compose-final.log new file mode 100644 index 00000000..cb88fc33 --- /dev/null +++ b/documentation/audits/family-gate-2026-10-02/C-metube-bench/move/evidence/MV-metube/compose-final.log @@ -0,0 +1,8 @@ +metube | Setting umask to 022 +metube | Creating download directory (/downloads), audio download directory (/downloads), state directory (/state), and temp dir (/downloads) +metube | Changing ownership of download and state directories to 1000:1000 +metube | Starting BgUtils POT Provider +metube | Running MeTube as user 1000:1000 +metube | INFO:main:Listening on 0.0.0.0:8081 +metube | INFO:ytdl:Initializing DownloadQueue +metube | INFO:main:Sending download history diff --git a/documentation/audits/family-gate-2026-10-02/C-metube-bench/move/evidence/MV-metube/engine-state.json b/documentation/audits/family-gate-2026-10-02/C-metube-bench/move/evidence/MV-metube/engine-state.json new file mode 100644 index 00000000..ec747fa4 --- /dev/null +++ b/documentation/audits/family-gate-2026-10-02/C-metube-bench/move/evidence/MV-metube/engine-state.json @@ -0,0 +1 @@ +null \ No newline at end of file diff --git a/documentation/audits/family-gate-2026-10-02/C-metube-bench/move/evidence/MV-metube/files-after-detail.json b/documentation/audits/family-gate-2026-10-02/C-metube-bench/move/evidence/MV-metube/files-after-detail.json new file mode 100644 index 00000000..c80623b5 --- /dev/null +++ b/documentation/audits/family-gate-2026-10-02/C-metube-bench/move/evidence/MV-metube/files-after-detail.json @@ -0,0 +1,5 @@ +{ + "/srv/felhom-gate/hdd/userdata/media/metube": { + "Big_Buck_Bunny_360_10s_1MB.mp4": "991017:77145c94c11f3754" + } +} \ No newline at end of file diff --git a/documentation/audits/family-gate-2026-10-02/C-metube-bench/move/evidence/MV-metube/files-after.json b/documentation/audits/family-gate-2026-10-02/C-metube-bench/move/evidence/MV-metube/files-after.json new file mode 100644 index 00000000..c910d7d8 --- /dev/null +++ b/documentation/audits/family-gate-2026-10-02/C-metube-bench/move/evidence/MV-metube/files-after.json @@ -0,0 +1,3 @@ +{ + "/srv/felhom-gate/hdd/userdata/media/metube": "d6e2574a091ef241a5adb382ad873254577e2799e55f7c4d7d45e626895af3f3" +} \ No newline at end of file diff --git a/documentation/audits/family-gate-2026-10-02/C-metube-bench/move/evidence/MV-metube/files-before-detail.json b/documentation/audits/family-gate-2026-10-02/C-metube-bench/move/evidence/MV-metube/files-before-detail.json new file mode 100644 index 00000000..c80623b5 --- /dev/null +++ b/documentation/audits/family-gate-2026-10-02/C-metube-bench/move/evidence/MV-metube/files-before-detail.json @@ -0,0 +1,5 @@ +{ + "/srv/felhom-gate/hdd/userdata/media/metube": { + "Big_Buck_Bunny_360_10s_1MB.mp4": "991017:77145c94c11f3754" + } +} \ No newline at end of file diff --git a/documentation/audits/family-gate-2026-10-02/C-metube-bench/move/evidence/MV-metube/files-before.json b/documentation/audits/family-gate-2026-10-02/C-metube-bench/move/evidence/MV-metube/files-before.json new file mode 100644 index 00000000..c910d7d8 --- /dev/null +++ b/documentation/audits/family-gate-2026-10-02/C-metube-bench/move/evidence/MV-metube/files-before.json @@ -0,0 +1,3 @@ +{ + "/srv/felhom-gate/hdd/userdata/media/metube": "d6e2574a091ef241a5adb382ad873254577e2799e55f7c4d7d45e626895af3f3" +} \ No newline at end of file diff --git a/documentation/audits/family-gate-2026-10-02/C-metube-bench/move/evidence/MV-metube/memory-samples.json b/documentation/audits/family-gate-2026-10-02/C-metube-bench/move/evidence/MV-metube/memory-samples.json new file mode 100644 index 00000000..cce76f58 --- /dev/null +++ b/documentation/audits/family-gate-2026-10-02/C-metube-bench/move/evidence/MV-metube/memory-samples.json @@ -0,0 +1,682 @@ +[ + { + "t": 15.1, + "containers": { + "metube": { + "limit": 805306368, + "current": 218394624, + "peak": 267948032, + "anon": 108826624, + "oom_kill": 0, + "restarts": 0, + "oomkilled_flag": false, + "status": "running", + "cgroup": true + } + }, + "requests": 262 + }, + { + "t": 30.2, + "containers": { + "metube": { + "limit": 805306368, + "current": 216952832, + "peak": 267948032, + "anon": 108990464, + "oom_kill": 0, + "restarts": 0, + "oomkilled_flag": false, + "status": "running", + "cgroup": true + } + }, + "requests": 562 + }, + { + "t": 45.4, + "containers": { + "metube": { + "limit": 805306368, + "current": 216985600, + "peak": 267948032, + "anon": 109019136, + "oom_kill": 0, + "restarts": 0, + "oomkilled_flag": false, + "status": "running", + "cgroup": true + } + }, + "requests": 858 + }, + { + "t": 60.5, + "containers": { + "metube": { + "limit": 805306368, + "current": 342884352, + "peak": 389664768, + "anon": 134897664, + "oom_kill": 0, + "restarts": 0, + "oomkilled_flag": false, + "status": "running", + "cgroup": true + } + }, + "requests": 1147 + }, + { + "t": 75.6, + "containers": { + "metube": { + "limit": 805306368, + "current": 342441984, + "peak": 389664768, + "anon": 134897664, + "oom_kill": 0, + "restarts": 0, + "oomkilled_flag": false, + "status": "running", + "cgroup": true + } + }, + "requests": 1446 + }, + { + "t": 90.7, + "containers": { + "metube": { + "limit": 805306368, + "current": 342208512, + "peak": 389664768, + "anon": 134905856, + "oom_kill": 0, + "restarts": 0, + "oomkilled_flag": false, + "status": "running", + "cgroup": true + } + }, + "requests": 1743 + }, + { + "t": 105.9, + "containers": { + "metube": { + "limit": 805306368, + "current": 450744320, + "peak": 488628224, + "anon": 142479360, + "oom_kill": 0, + "restarts": 0, + "oomkilled_flag": false, + "status": "running", + "cgroup": true + } + }, + "requests": 2030 + }, + { + "t": 121.0, + "containers": { + "metube": { + "limit": 805306368, + "current": 450269184, + "peak": 488628224, + "anon": 142479360, + "oom_kill": 0, + "restarts": 0, + "oomkilled_flag": false, + "status": "running", + "cgroup": true + } + }, + "requests": 2330 + }, + { + "t": 136.1, + "containers": { + "metube": { + "limit": 805306368, + "current": 450957312, + "peak": 488628224, + "anon": 142479360, + "oom_kill": 0, + "restarts": 0, + "oomkilled_flag": false, + "status": "running", + "cgroup": true + } + }, + "requests": 2630 + }, + { + "t": 151.3, + "containers": { + "metube": { + "limit": 805306368, + "current": 577544192, + "peak": 617148416, + "anon": 168550400, + "oom_kill": 0, + "restarts": 0, + "oomkilled_flag": false, + "status": "running", + "cgroup": true + } + }, + "requests": 2914 + }, + { + "t": 166.4, + "containers": { + "metube": { + "limit": 805306368, + "current": 577257472, + "peak": 617148416, + "anon": 168550400, + "oom_kill": 0, + "restarts": 0, + "oomkilled_flag": false, + "status": "running", + "cgroup": true + } + }, + "requests": 3214 + }, + { + "t": 181.5, + "containers": { + "metube": { + "limit": 805306368, + "current": 576757760, + "peak": 617148416, + "anon": 168550400, + "oom_kill": 0, + "restarts": 0, + "oomkilled_flag": false, + "status": "running", + "cgroup": true + } + }, + "requests": 3514 + }, + { + "t": 196.7, + "containers": { + "metube": { + "limit": 805306368, + "current": 685064192, + "peak": 726466560, + "anon": 175656960, + "oom_kill": 0, + "restarts": 0, + "oomkilled_flag": false, + "status": "running", + "cgroup": true + } + }, + "requests": 3802 + }, + { + "t": 211.8, + "containers": { + "metube": { + "limit": 805306368, + "current": 684240896, + "peak": 726466560, + "anon": 175656960, + "oom_kill": 0, + "restarts": 0, + "oomkilled_flag": false, + "status": "running", + "cgroup": true + } + }, + "requests": 4098 + }, + { + "t": 226.9, + "containers": { + "metube": { + "limit": 805306368, + "current": 683790336, + "peak": 726466560, + "anon": 175656960, + "oom_kill": 0, + "restarts": 0, + "oomkilled_flag": false, + "status": "running", + "cgroup": true + } + }, + "requests": 4398 + }, + { + "t": 242.0, + "containers": { + "metube": { + "limit": 805306368, + "current": 756105216, + "peak": 806682624, + "anon": 182927360, + "oom_kill": 0, + "restarts": 0, + "oomkilled_flag": false, + "status": "running", + "cgroup": true + } + }, + "requests": 4683 + }, + { + "t": 257.1, + "containers": { + "metube": { + "limit": 805306368, + "current": 754176000, + "peak": 806682624, + "anon": 182927360, + "oom_kill": 0, + "restarts": 0, + "oomkilled_flag": false, + "status": "running", + "cgroup": true + } + }, + "requests": 4982 + }, + { + "t": 272.3, + "containers": { + "metube": { + "limit": 805306368, + "current": 754192384, + "peak": 806682624, + "anon": 182927360, + "oom_kill": 0, + "restarts": 0, + "oomkilled_flag": false, + "status": "running", + "cgroup": true + } + }, + "requests": 5282 + }, + { + "t": 287.4, + "containers": { + "metube": { + "limit": 805306368, + "current": 759549952, + "peak": 807489536, + "anon": 190197760, + "oom_kill": 0, + "restarts": 0, + "oomkilled_flag": false, + "status": "running", + "cgroup": true + } + }, + "requests": 5570 + }, + { + "t": 302.5, + "containers": { + "metube": { + "limit": 805306368, + "current": 759554048, + "peak": 807489536, + "anon": 190197760, + "oom_kill": 0, + "restarts": 0, + "oomkilled_flag": false, + "status": "running", + "cgroup": true + } + }, + "requests": 5870 + }, + { + "t": 317.6, + "containers": { + "metube": { + "limit": 805306368, + "current": 759844864, + "peak": 807489536, + "anon": 190197760, + "oom_kill": 0, + "restarts": 0, + "oomkilled_flag": false, + "status": "running", + "cgroup": true + } + }, + "requests": 6167 + }, + { + "t": 332.7, + "containers": { + "metube": { + "limit": 805306368, + "current": 766046208, + "peak": 807489536, + "anon": 197423104, + "oom_kill": 0, + "restarts": 0, + "oomkilled_flag": false, + "status": "running", + "cgroup": true + } + }, + "requests": 6454 + }, + { + "t": 347.9, + "containers": { + "metube": { + "limit": 805306368, + "current": 766574592, + "peak": 807489536, + "anon": 197423104, + "oom_kill": 0, + "restarts": 0, + "oomkilled_flag": false, + "status": "running", + "cgroup": true + } + }, + "requests": 6754 + }, + { + "t": 363.0, + "containers": { + "metube": { + "limit": 805306368, + "current": 766267392, + "peak": 807489536, + "anon": 197423104, + "oom_kill": 0, + "restarts": 0, + "oomkilled_flag": false, + "status": "running", + "cgroup": true + } + }, + "requests": 7054 + }, + { + "t": 378.1, + "containers": { + "metube": { + "limit": 805306368, + "current": 739332096, + "peak": 809365504, + "anon": 204529664, + "oom_kill": 0, + "restarts": 0, + "oomkilled_flag": false, + "status": "running", + "cgroup": true + } + }, + "requests": 7342 + }, + { + "t": 393.2, + "containers": { + "metube": { + "limit": 805306368, + "current": 738820096, + "peak": 809365504, + "anon": 204529664, + "oom_kill": 0, + "restarts": 0, + "oomkilled_flag": false, + "status": "running", + "cgroup": true + } + }, + "requests": 7638 + }, + { + "t": 408.4, + "containers": { + "metube": { + "limit": 805306368, + "current": 739340288, + "peak": 809365504, + "anon": 204533760, + "oom_kill": 0, + "restarts": 0, + "oomkilled_flag": false, + "status": "running", + "cgroup": true + } + }, + "requests": 7938 + }, + { + "t": 423.5, + "containers": { + "metube": { + "limit": 805306368, + "current": 765554688, + "peak": 809365504, + "anon": 211742720, + "oom_kill": 0, + "restarts": 0, + "oomkilled_flag": false, + "status": "running", + "cgroup": true + } + }, + "requests": 8226 + }, + { + "t": 438.6, + "containers": { + "metube": { + "limit": 805306368, + "current": 765317120, + "peak": 809365504, + "anon": 211742720, + "oom_kill": 0, + "restarts": 0, + "oomkilled_flag": false, + "status": "running", + "cgroup": true + } + }, + "requests": 8522 + }, + { + "t": 453.7, + "containers": { + "metube": { + "limit": 805306368, + "current": 764755968, + "peak": 809365504, + "anon": 211742720, + "oom_kill": 0, + "restarts": 0, + "oomkilled_flag": false, + "status": "running", + "cgroup": true + } + }, + "requests": 8822 + }, + { + "t": 468.9, + "containers": { + "metube": { + "limit": 805306368, + "current": 749535232, + "peak": 809365504, + "anon": 218935296, + "oom_kill": 0, + "restarts": 0, + "oomkilled_flag": false, + "status": "running", + "cgroup": true + } + }, + "requests": 9111 + }, + { + "t": 484.0, + "containers": { + "metube": { + "limit": 805306368, + "current": 749416448, + "peak": 809365504, + "anon": 218935296, + "oom_kill": 0, + "restarts": 0, + "oomkilled_flag": false, + "status": "running", + "cgroup": true + } + }, + "requests": 9411 + }, + { + "t": 499.1, + "containers": { + "metube": { + "limit": 805306368, + "current": 750125056, + "peak": 809365504, + "anon": 218935296, + "oom_kill": 0, + "restarts": 0, + "oomkilled_flag": false, + "status": "running", + "cgroup": true + } + }, + "requests": 9707 + }, + { + "t": 514.2, + "containers": { + "metube": { + "limit": 805306368, + "current": 752951296, + "peak": 809365504, + "anon": 226152448, + "oom_kill": 0, + "restarts": 0, + "oomkilled_flag": false, + "status": "running", + "cgroup": true + } + }, + "requests": 9996 + }, + { + "t": 529.3, + "containers": { + "metube": { + "limit": 805306368, + "current": 753430528, + "peak": 809365504, + "anon": 226152448, + "oom_kill": 0, + "restarts": 0, + "oomkilled_flag": false, + "status": "running", + "cgroup": true + } + }, + "requests": 10294 + }, + { + "t": 544.5, + "containers": { + "metube": { + "limit": 805306368, + "current": 752881664, + "peak": 809365504, + "anon": 226152448, + "oom_kill": 0, + "restarts": 0, + "oomkilled_flag": false, + "status": "running", + "cgroup": true + } + }, + "requests": 10593 + }, + { + "t": 559.6, + "containers": { + "metube": { + "limit": 805306368, + "current": 753139712, + "peak": 809365504, + "anon": 226152448, + "oom_kill": 0, + "restarts": 0, + "oomkilled_flag": false, + "status": "running", + "cgroup": true + } + }, + "requests": 10892 + }, + { + "t": 574.7, + "containers": { + "metube": { + "limit": 805306368, + "current": 752640000, + "peak": 809365504, + "anon": 226152448, + "oom_kill": 0, + "restarts": 0, + "oomkilled_flag": false, + "status": "running", + "cgroup": true + } + }, + "requests": 11190 + }, + { + "t": 589.9, + "containers": { + "metube": { + "limit": 805306368, + "current": 727638016, + "peak": 809365504, + "anon": 226152448, + "oom_kill": 0, + "restarts": 0, + "oomkilled_flag": false, + "status": "running", + "cgroup": true + } + }, + "requests": 11490 + }, + { + "t": 605.0, + "containers": { + "metube": { + "limit": 805306368, + "current": 727384064, + "peak": 809365504, + "anon": 226152448, + "oom_kill": 0, + "restarts": 0, + "oomkilled_flag": false, + "status": "running", + "cgroup": true + } + }, + "requests": 11790 + } +] \ No newline at end of file diff --git a/documentation/audits/family-gate-2026-10-02/C-metube-bench/move/evidence/MV-metube/migration-lines.txt b/documentation/audits/family-gate-2026-10-02/C-metube-bench/move/evidence/MV-metube/migration-lines.txt new file mode 100644 index 00000000..e69de29b diff --git a/documentation/audits/family-gate-2026-10-02/C-metube-bench/move/evidence/MV-metube/run.log b/documentation/audits/family-gate-2026-10-02/C-metube-bench/move/evidence/MV-metube/run.log new file mode 100644 index 00000000..be2fef02 --- /dev/null +++ b/documentation/audits/family-gate-2026-10-02/C-metube-bench/move/evidence/MV-metube/run.log @@ -0,0 +1,59 @@ +[06:19:20] scratch drive folders cleared before FROM (R-656): ['/srv/felhom-gate/hdd/userdata/media/metube'] +[06:19:20] MV-metube: deploying metube at FROM {'metube': 'ghcr.io/alexta69/metube:2026.09.28'} +[06:19:51] FROM settled=True in 30.8s :: {"metube": {"status": "running", "health": "healthy", "restarts": 0, "exit": 0}} +[06:19:51] fixture: the BOX walk's own (MeTube), through upgrade_boxport +[06:19:52] metube: POST /add http=200 {"status": "ok"} +[06:19:55] metube: the download finished (991017 bytes) +[06:19:55] metube: history has it=True; GET /download/ http=200 +[06:19:55] C1 (seed reads back BEFORE): True +[06:19:56] MV-metube: swapping to TO {'metube': 'ghcr.io/alexta69/metube:2026.09.29'} +[06:19:56] TO up -d rc=0 +[06:20:27] TO settled=True in 30.8s :: {"metube": {"status": "running", "health": "healthy", "restarts": 0, "exit": 0}} +[06:20:27] migration lines observed: 0 +[06:20:28] metube: history has it=True; GET /download/ http=200 +[06:20:28] RESULT (seed reads back AFTER): True +[06:20:28] memory watch: 600s, 4 callers on 1 path(s) at 172.18.0.2:8081 +[06:20:43] + 15s metube=208M/768M peak=255M kills=0 rs=0 reqs=262 +[06:20:58] + 30s metube=206M/768M peak=255M kills=0 rs=0 reqs=562 +[06:21:13] + 45s metube=206M/768M peak=255M kills=0 rs=0 reqs=858 +[06:21:28] + 60s metube=327M/768M peak=371M kills=0 rs=0 reqs=1147 +[06:21:44] + 76s metube=326M/768M peak=371M kills=0 rs=0 reqs=1446 +[06:21:59] + 91s metube=326M/768M peak=371M kills=0 rs=0 reqs=1743 +[06:22:14] + 106s metube=429M/768M peak=465M kills=0 rs=0 reqs=2030 +[06:22:29] + 121s metube=429M/768M peak=465M kills=0 rs=0 reqs=2330 +[06:22:44] + 136s metube=430M/768M peak=465M kills=0 rs=0 reqs=2630 +[06:22:59] + 151s metube=550M/768M peak=588M kills=0 rs=0 reqs=2914 +[06:23:14] + 166s metube=550M/768M peak=588M kills=0 rs=0 reqs=3214 +[06:23:30] + 182s metube=550M/768M peak=588M kills=0 rs=0 reqs=3514 +[06:23:45] + 197s metube=653M/768M peak=692M kills=0 rs=0 reqs=3802 +[06:24:00] + 212s metube=652M/768M peak=692M kills=0 rs=0 reqs=4098 +[06:24:15] + 227s metube=652M/768M peak=692M kills=0 rs=0 reqs=4398 +[06:24:30] + 242s metube=721M/768M peak=769M kills=0 rs=0 reqs=4683 +[06:24:45] + 257s metube=719M/768M peak=769M kills=0 rs=0 reqs=4982 +[06:25:00] + 272s metube=719M/768M peak=769M kills=0 rs=0 reqs=5282 +[06:25:15] + 287s metube=724M/768M peak=770M kills=0 rs=0 reqs=5570 +[06:25:31] + 302s metube=724M/768M peak=770M kills=0 rs=0 reqs=5870 +[06:25:46] + 318s metube=724M/768M peak=770M kills=0 rs=0 reqs=6167 +[06:26:01] + 333s metube=730M/768M peak=770M kills=0 rs=0 reqs=6454 +[06:26:16] + 348s metube=731M/768M peak=770M kills=0 rs=0 reqs=6754 +[06:26:31] + 363s metube=730M/768M peak=770M kills=0 rs=0 reqs=7054 +[06:26:46] + 378s metube=705M/768M peak=771M kills=0 rs=0 reqs=7342 +[06:27:01] + 393s metube=704M/768M peak=771M kills=0 rs=0 reqs=7638 +[06:27:16] + 408s metube=705M/768M peak=771M kills=0 rs=0 reqs=7938 +[06:27:31] + 424s metube=730M/768M peak=771M kills=0 rs=0 reqs=8226 +[06:27:47] + 439s metube=729M/768M peak=771M kills=0 rs=0 reqs=8522 +[06:28:02] + 454s metube=729M/768M peak=771M kills=0 rs=0 reqs=8822 +[06:28:17] + 469s metube=714M/768M peak=771M kills=0 rs=0 reqs=9111 +[06:28:32] + 484s metube=714M/768M peak=771M kills=0 rs=0 reqs=9411 +[06:28:47] + 499s metube=715M/768M peak=771M kills=0 rs=0 reqs=9707 +[06:29:02] + 514s metube=718M/768M peak=771M kills=0 rs=0 reqs=9996 +[06:29:17] + 529s metube=718M/768M peak=771M kills=0 rs=0 reqs=10294 +[06:29:32] + 544s metube=718M/768M peak=771M kills=0 rs=0 reqs=10593 +[06:29:48] + 560s metube=718M/768M peak=771M kills=0 rs=0 reqs=10892 +[06:30:03] + 575s metube=717M/768M peak=771M kills=0 rs=0 reqs=11190 +[06:30:18] + 590s metube=693M/768M peak=771M kills=0 rs=0 reqs=11490 +[06:30:33] + 605s metube=693M/768M peak=771M kills=0 rs=0 reqs=11790 +[06:30:33] memory watch: killed=False tight=[] requests=11790 codes={'200': 11790} +[06:30:33] MV-metube: ABORT — putting the FROM images back +[06:31:05] metube: history has it=True; GET /download/ http=200 +[06:31:05] ABORT: app came back in 30.8s; data present=True \ No newline at end of file diff --git a/documentation/audits/family-gate-2026-10-02/C-metube-bench/move/evidence/MV-metube/sampler.sh b/documentation/audits/family-gate-2026-10-02/C-metube-bench/move/evidence/MV-metube/sampler.sh new file mode 100644 index 00000000..85fe5898 --- /dev/null +++ b/documentation/audits/family-gate-2026-10-02/C-metube-bench/move/evidence/MV-metube/sampler.sh @@ -0,0 +1,12 @@ +# sampler.sh (run INSIDE the bench or the box): sample the cgroup of each named container from its birth, every 2 s. +# usage: NAMES="a b c" OUT=/root/mem.csv DUR=900 bash sampler.sh & — stops at DUR or when $OUT.stop exists +: > $OUT; t0=$(date +%s) +while [ $(( $(date +%s) - t0 )) -lt ${DUR:-900} ] && [ ! -f $OUT.stop ]; do + for n in $NAMES; do + id=$(docker ps -q --no-trunc --filter name=^$n$ | head -1); [ -z "$id" ] && continue + cg=$(ls -d /sys/fs/cgroup/system.slice/docker-$id.scope 2>/dev/null || find /sys/fs/cgroup -maxdepth 4 -type d -name "*$id*" 2>/dev/null | head -1) + [ -f "$cg/memory.stat" ] || continue + echo "$(date +%s),$n,${id:0:12},$(awk '$1=="anon"{print $2}' $cg/memory.stat),$(cat $cg/memory.current),$(cat $cg/memory.swap.current 2>/dev/null || echo NA),$(awk '$1=="oom_kill"{print $2}' $cg/memory.events),$(cat $cg/memory.max),$(docker inspect -f '{{.RestartCount}}' $id)" >> $OUT + done + sleep 2 +done diff --git a/documentation/audits/family-gate-2026-10-02/C-metube-bench/move/evidence/MV-metube/to-full.log b/documentation/audits/family-gate-2026-10-02/C-metube-bench/move/evidence/MV-metube/to-full.log new file mode 100644 index 00000000..451bda9d --- /dev/null +++ b/documentation/audits/family-gate-2026-10-02/C-metube-bench/move/evidence/MV-metube/to-full.log @@ -0,0 +1,7 @@ +metube | Setting umask to 022 +metube | Creating download directory (/downloads), audio download directory (/downloads), state directory (/state), and temp dir (/downloads) +metube | Changing ownership of download and state directories to 1000:1000 +metube | Starting BgUtils POT Provider +metube | Running MeTube as user 1000:1000 +metube | INFO:main:Listening on 0.0.0.0:8081 +metube | INFO:ytdl:Initializing DownloadQueue diff --git a/documentation/audits/family-gate-2026-10-02/C-metube-bench/move/evidence/MV-metube/to-states.json b/documentation/audits/family-gate-2026-10-02/C-metube-bench/move/evidence/MV-metube/to-states.json new file mode 100644 index 00000000..b3053e83 --- /dev/null +++ b/documentation/audits/family-gate-2026-10-02/C-metube-bench/move/evidence/MV-metube/to-states.json @@ -0,0 +1,8 @@ +{ + "metube": { + "status": "running", + "health": "healthy", + "restarts": 0, + "exit": 0 + } +} \ No newline at end of file diff --git a/documentation/audits/family-gate-2026-10-02/C-metube-bench/move/evidence/MV-metube/verdict.json b/documentation/audits/family-gate-2026-10-02/C-metube-bench/move/evidence/MV-metube/verdict.json new file mode 100644 index 00000000..64da910d --- /dev/null +++ b/documentation/audits/family-gate-2026-10-02/C-metube-bench/move/evidence/MV-metube/verdict.json @@ -0,0 +1,55 @@ +{ + "harness_version": 4, + "edge": "MV-metube", + "app": "metube", + "note": "night 2026-09-23 within-a-major move: metube=ghcr.io/alexta69/metube:2026.09.29", + "from": { + "metube": "ghcr.io/alexta69/metube:2026.09.28" + }, + "to": { + "metube": "ghcr.io/alexta69/metube:2026.09.29" + }, + "verdict": "proven", + "seed_read_before": true, + "seed_read_after": true, + "healthy_after": true, + "migration_observed": null, + "abort": "starts-and-serves", + "abort_detail": null, + "engine_state_after": null, + "memory": { + "soak_s": 605.3, + "requested_s": 600, + "requests": 11790, + "codes": { + "200": 11790 + }, + "first_kill": null, + "containers": { + "metube": { + "limit": 805306368, + "peak": 809365504, + "peak_pct": 1.005, + "anon_peak_sampled": 226152448, + "anon_peak_pct": 0.281, + "oom_kills": 0, + "restarts": 0, + "oomkilled_flag": false, + "measured": true + } + }, + "unmeasured": [], + "load": "reached" + }, + "marks": [], + "bench_overrides": null, + "duration_s": 30.8, + "measured_at": "2026-10-02T06:31:05Z", + "evidence": "evidence/MV-metube", + "scratch_cleared": [ + "/srv/felhom-gate/hdd/userdata/media/metube" + ], + "files_changed": [], + "files_changed_detail": [], + "total_s": 705.3 +} \ No newline at end of file diff --git a/documentation/audits/family-gate-2026-10-02/C-metube-bench/move/mem-metube.csv b/documentation/audits/family-gate-2026-10-02/C-metube-bench/move/mem-metube.csv new file mode 100644 index 00000000..352fe975 --- /dev/null +++ b/documentation/audits/family-gate-2026-10-02/C-metube-bench/move/mem-metube.csv @@ -0,0 +1,342 @@ +1790921962,metube,95a197aba812,61616128,68952064,0,0,805306368,0 +1790921964,metube,95a197aba812,61616128,68763648,0,0,805306368,0 +1790921966,metube,95a197aba812,61616128,68763648,0,0,805306368,0 +1790921968,metube,95a197aba812,61616128,68763648,0,0,805306368,0 +1790921970,metube,95a197aba812,61616128,68763648,0,0,805306368,0 +1790921972,metube,95a197aba812,61616128,68763648,0,0,805306368,0 +1790921974,metube,95a197aba812,61616128,68763648,0,0,805306368,0 +1790921976,metube,95a197aba812,61616128,68763648,0,0,805306368,0 +1790921978,metube,95a197aba812,61616128,68763648,0,0,805306368,0 +1790921980,metube,95a197aba812,61616128,68763648,0,0,805306368,0 +1790921982,metube,95a197aba812,61616128,68763648,0,0,805306368,0 +1790921984,metube,95a197aba812,61616128,68763648,0,0,805306368,0 +1790921987,metube,95a197aba812,61616128,68763648,0,0,805306368,0 +1790921989,metube,95a197aba812,61616128,68763648,0,0,805306368,0 +1790921991,metube,95a197aba812,61648896,68890624,0,0,805306368,0 +1790921993,metube,95a197aba812,86163456,97509376,0,0,805306368,0 +1790921995,metube,95a197aba812,86163456,96661504,0,0,805306368,0 +1790921997,metube,f5a3771ddb27,20725760,26169344,0,0,805306368,0 +1790921999,metube,f5a3771ddb27,61612032,68534272,0,0,805306368,0 +1790922001,metube,f5a3771ddb27,61612032,68530176,0,0,805306368,0 +1790922003,metube,f5a3771ddb27,61612032,68530176,0,0,805306368,0 +1790922005,metube,f5a3771ddb27,61612032,68530176,0,0,805306368,0 +1790922007,metube,f5a3771ddb27,61612032,68530176,0,0,805306368,0 +1790922009,metube,f5a3771ddb27,61612032,68530176,0,0,805306368,0 +1790922011,metube,f5a3771ddb27,61612032,68530176,0,0,805306368,0 +1790922013,metube,f5a3771ddb27,61612032,68530176,0,0,805306368,0 +1790922015,metube,f5a3771ddb27,61612032,68530176,0,0,805306368,0 +1790922017,metube,f5a3771ddb27,61612032,68530176,0,0,805306368,0 +1790922019,metube,f5a3771ddb27,61612032,68526080,0,0,805306368,0 +1790922022,metube,f5a3771ddb27,61612032,68526080,0,0,805306368,0 +1790922024,metube,f5a3771ddb27,61612032,68526080,0,0,805306368,0 +1790922026,metube,f5a3771ddb27,61612032,68501504,0,0,805306368,0 +1790922028,metube,f5a3771ddb27,61652992,69349376,0,0,805306368,0 +1790922030,metube,f5a3771ddb27,61751296,69353472,0,0,805306368,0 +1790922032,metube,f5a3771ddb27,91914240,99594240,0,0,805306368,0 +1790922034,metube,f5a3771ddb27,155385856,200806400,0,0,805306368,0 +1790922036,metube,f5a3771ddb27,108822528,218771456,0,0,805306368,0 +1790922038,metube,f5a3771ddb27,108822528,218599424,0,0,805306368,0 +1790922040,metube,f5a3771ddb27,108822528,218128384,0,0,805306368,0 +1790922042,metube,f5a3771ddb27,108822528,218615808,0,0,805306368,0 +1790922044,metube,f5a3771ddb27,108826624,218632192,0,0,805306368,0 +1790922046,metube,f5a3771ddb27,108847104,218398720,0,0,805306368,0 +1790922048,metube,f5a3771ddb27,108871680,218681344,0,0,805306368,0 +1790922050,metube,f5a3771ddb27,108888064,218714112,0,0,805306368,0 +1790922052,metube,f5a3771ddb27,108888064,218161152,0,0,805306368,0 +1790922054,metube,f5a3771ddb27,108888064,218673152,0,0,805306368,0 +1790922057,metube,f5a3771ddb27,109916160,218210304,0,0,805306368,0 +1790922059,metube,f5a3771ddb27,108990464,217206784,0,0,805306368,0 +1790922061,metube,f5a3771ddb27,109002752,217198592,0,0,805306368,0 +1790922063,metube,f5a3771ddb27,109002752,216719360,0,0,805306368,0 +1790922065,metube,f5a3771ddb27,109010944,217460736,0,0,805306368,0 +1790922067,metube,f5a3771ddb27,109010944,216973312,0,0,805306368,0 +1790922069,metube,f5a3771ddb27,109010944,217513984,0,0,805306368,0 +1790922071,metube,f5a3771ddb27,109010944,217493504,0,0,805306368,0 +1790922073,metube,f5a3771ddb27,109019136,217231360,0,0,805306368,0 +1790922075,metube,f5a3771ddb27,109019136,216997888,0,0,805306368,0 +1790922077,metube,f5a3771ddb27,109019136,216977408,0,0,805306368,0 +1790922079,metube,f5a3771ddb27,185978880,318234624,0,0,805306368,0 +1790922081,metube,f5a3771ddb27,134897664,342880256,0,0,805306368,0 +1790922083,metube,f5a3771ddb27,134897664,343117824,0,0,805306368,0 +1790922085,metube,f5a3771ddb27,134897664,343400448,0,0,805306368,0 +1790922087,metube,f5a3771ddb27,134897664,343687168,0,0,805306368,0 +1790922090,metube,f5a3771ddb27,134897664,342208512,0,0,805306368,0 +1790922092,metube,f5a3771ddb27,134897664,343232512,0,0,805306368,0 +1790922094,metube,f5a3771ddb27,134897664,342183936,0,0,805306368,0 +1790922096,metube,f5a3771ddb27,134897664,342765568,0,0,805306368,0 +1790922098,metube,f5a3771ddb27,134897664,342196224,0,0,805306368,0 +1790922100,metube,f5a3771ddb27,134897664,342216704,0,0,805306368,0 +1790922102,metube,f5a3771ddb27,134897664,342441984,0,0,805306368,0 +1790922104,metube,f5a3771ddb27,134897664,342462464,0,0,805306368,0 +1790922106,metube,f5a3771ddb27,134897664,342470656,0,0,805306368,0 +1790922108,metube,f5a3771ddb27,134897664,342175744,0,0,805306368,0 +1790922110,metube,f5a3771ddb27,134905856,342458368,0,0,805306368,0 +1790922112,metube,f5a3771ddb27,134905856,341962752,0,0,805306368,0 +1790922114,metube,f5a3771ddb27,134905856,342220800,0,0,805306368,0 +1790922116,metube,f5a3771ddb27,134905856,342732800,0,0,805306368,0 +1790922118,metube,f5a3771ddb27,134905856,342478848,0,0,805306368,0 +1790922120,metube,f5a3771ddb27,134905856,342482944,0,0,805306368,0 +1790922123,metube,f5a3771ddb27,134905856,342732800,0,0,805306368,0 +1790922125,metube,f5a3771ddb27,190570496,401002496,0,0,805306368,0 +1790922127,metube,f5a3771ddb27,170270720,458723328,0,0,805306368,0 +1790922129,metube,f5a3771ddb27,142479360,450789376,0,0,805306368,0 +1790922131,metube,f5a3771ddb27,142479360,451551232,0,0,805306368,0 +1790922133,metube,f5a3771ddb27,142479360,451239936,0,0,805306368,0 +1790922135,metube,f5a3771ddb27,142479360,450732032,0,0,805306368,0 +1790922137,metube,f5a3771ddb27,142479360,451461120,0,0,805306368,0 +1790922139,metube,f5a3771ddb27,142479360,450768896,0,0,805306368,0 +1790922141,metube,f5a3771ddb27,142479360,451031040,0,0,805306368,0 +1790922143,metube,f5a3771ddb27,142479360,451039232,0,0,805306368,0 +1790922145,metube,f5a3771ddb27,142479360,451330048,0,0,805306368,0 +1790922147,metube,f5a3771ddb27,142479360,451297280,0,0,805306368,0 +1790922149,metube,f5a3771ddb27,142479360,450772992,0,0,805306368,0 +1790922151,metube,f5a3771ddb27,142479360,451530752,0,0,805306368,0 +1790922153,metube,f5a3771ddb27,142479360,451297280,0,0,805306368,0 +1790922155,metube,f5a3771ddb27,142479360,450785280,0,0,805306368,0 +1790922158,metube,f5a3771ddb27,142479360,450990080,0,0,805306368,0 +1790922160,metube,f5a3771ddb27,142479360,451276800,0,0,805306368,0 +1790922162,metube,f5a3771ddb27,142479360,451002368,0,0,805306368,0 +1790922164,metube,f5a3771ddb27,142479360,451190784,0,0,805306368,0 +1790922166,metube,f5a3771ddb27,142479360,450232320,0,0,805306368,0 +1790922168,metube,f5a3771ddb27,142479360,450965504,0,0,805306368,0 +1790922170,metube,f5a3771ddb27,143896576,451883008,0,0,805306368,0 +1790922172,metube,f5a3771ddb27,228179968,577183744,0,0,805306368,0 +1790922174,metube,f5a3771ddb27,168550400,577802240,0,0,805306368,0 +1790922176,metube,f5a3771ddb27,168550400,578060288,0,0,805306368,0 +1790922178,metube,f5a3771ddb27,168550400,577794048,0,0,805306368,0 +1790922180,metube,f5a3771ddb27,168550400,577540096,0,0,805306368,0 +1790922182,metube,f5a3771ddb27,168550400,578039808,0,0,805306368,0 +1790922184,metube,f5a3771ddb27,168550400,578060288,0,0,805306368,0 +1790922186,metube,f5a3771ddb27,168550400,577531904,0,0,805306368,0 +1790922188,metube,f5a3771ddb27,168550400,577015808,0,0,805306368,0 +1790922190,metube,f5a3771ddb27,168550400,577261568,0,0,805306368,0 +1790922193,metube,f5a3771ddb27,168550400,577568768,0,0,805306368,0 +1790922195,metube,f5a3771ddb27,168550400,577515520,0,0,805306368,0 +1790922197,metube,f5a3771ddb27,168550400,577269760,0,0,805306368,0 +1790922199,metube,f5a3771ddb27,168550400,577523712,0,0,805306368,0 +1790922201,metube,f5a3771ddb27,168550400,577220608,0,0,805306368,0 +1790922203,metube,f5a3771ddb27,168550400,577458176,0,0,805306368,0 +1790922205,metube,f5a3771ddb27,168550400,577003520,0,0,805306368,0 +1790922207,metube,f5a3771ddb27,168550400,577695744,0,0,805306368,0 +1790922209,metube,f5a3771ddb27,168550400,577257472,0,0,805306368,0 +1790922211,metube,f5a3771ddb27,168550400,577232896,0,0,805306368,0 +1790922213,metube,f5a3771ddb27,168550400,577769472,0,0,805306368,0 +1790922215,metube,f5a3771ddb27,168550400,577531904,0,0,805306368,0 +1790922217,metube,f5a3771ddb27,205242368,639512576,0,0,805306368,0 +1790922219,metube,f5a3771ddb27,175656960,684793856,0,0,805306368,0 +1790922221,metube,f5a3771ddb27,175656960,685002752,0,0,805306368,0 +1790922223,metube,f5a3771ddb27,175656960,685559808,0,0,805306368,0 +1790922225,metube,f5a3771ddb27,175656960,685334528,0,0,805306368,0 +1790922228,metube,f5a3771ddb27,175656960,685051904,0,0,805306368,0 +1790922230,metube,f5a3771ddb27,175656960,684498944,0,0,805306368,0 +1790922232,metube,f5a3771ddb27,175656960,685236224,0,0,805306368,0 +1790922234,metube,f5a3771ddb27,175656960,684515328,0,0,805306368,0 +1790922236,metube,f5a3771ddb27,175656960,685252608,0,0,805306368,0 +1790922238,metube,f5a3771ddb27,175656960,685109248,0,0,805306368,0 +1790922240,metube,f5a3771ddb27,175656960,684761088,0,0,805306368,0 +1790922242,metube,f5a3771ddb27,175656960,685469696,0,0,805306368,0 +1790922244,metube,f5a3771ddb27,175656960,685023232,0,0,805306368,0 +1790922246,metube,f5a3771ddb27,175656960,684527616,0,0,805306368,0 +1790922248,metube,f5a3771ddb27,175656960,685043712,0,0,805306368,0 +1790922250,metube,f5a3771ddb27,175611904,684167168,0,0,805306368,0 +1790922252,metube,f5a3771ddb27,175656960,683786240,0,0,805306368,0 +1790922254,metube,f5a3771ddb27,175656960,684486656,0,0,805306368,0 +1790922256,metube,f5a3771ddb27,175656960,683757568,0,0,805306368,0 +1790922258,metube,f5a3771ddb27,175656960,683835392,0,0,805306368,0 +1790922260,metube,f5a3771ddb27,175656960,684019712,0,0,805306368,0 +1790922263,metube,f5a3771ddb27,243482624,754290688,0,0,805306368,0 +1790922265,metube,f5a3771ddb27,182927360,756011008,0,0,805306368,0 +1790922267,metube,f5a3771ddb27,182927360,756854784,0,0,805306368,0 +1790922269,metube,f5a3771ddb27,182927360,756580352,0,0,805306368,0 +1790922271,metube,f5a3771ddb27,182927360,756105216,0,0,805306368,0 +1790922273,metube,f5a3771ddb27,182927360,756346880,0,0,805306368,0 +1790922275,metube,f5a3771ddb27,182927360,755613696,0,0,805306368,0 +1790922277,metube,f5a3771ddb27,182927360,756346880,0,0,805306368,0 +1790922279,metube,f5a3771ddb27,182927360,755949568,0,0,805306368,0 +1790922281,metube,f5a3771ddb27,182927360,756326400,0,0,805306368,0 +1790922283,metube,f5a3771ddb27,182927360,754925568,0,0,805306368,0 +1790922285,metube,f5a3771ddb27,182927360,754438144,0,0,805306368,0 +1790922287,metube,f5a3771ddb27,182927360,754966528,0,0,805306368,0 +1790922289,metube,f5a3771ddb27,182927360,754683904,0,0,805306368,0 +1790922291,metube,f5a3771ddb27,182927360,754712576,0,0,805306368,0 +1790922293,metube,f5a3771ddb27,182927360,754450432,0,0,805306368,0 +1790922295,metube,f5a3771ddb27,182927360,754286592,0,0,805306368,0 +1790922298,metube,f5a3771ddb27,182927360,754802688,0,0,805306368,0 +1790922300,metube,f5a3771ddb27,182927360,754724864,0,0,805306368,0 +1790922302,metube,f5a3771ddb27,182927360,754417664,0,0,805306368,0 +1790922304,metube,f5a3771ddb27,182927360,754188288,0,0,805306368,0 +1790922306,metube,f5a3771ddb27,182927360,754704384,0,0,805306368,0 +1790922308,metube,f5a3771ddb27,184786944,756314112,0,0,805306368,0 +1790922310,metube,f5a3771ddb27,244506624,799944704,0,0,805306368,0 +1790922312,metube,f5a3771ddb27,190197760,762036224,0,0,805306368,0 +1790922314,metube,f5a3771ddb27,190197760,762023936,0,0,805306368,0 +1790922316,metube,f5a3771ddb27,190197760,759812096,0,0,805306368,0 +1790922318,metube,f5a3771ddb27,190197760,759586816,0,0,805306368,0 +1790922320,metube,f5a3771ddb27,190197760,759803904,0,0,805306368,0 +1790922322,metube,f5a3771ddb27,190197760,760057856,0,0,805306368,0 +1790922324,metube,f5a3771ddb27,190197760,760020992,0,0,805306368,0 +1790922326,metube,f5a3771ddb27,190197760,760291328,0,0,805306368,0 +1790922328,metube,f5a3771ddb27,190197760,759508992,0,0,805306368,0 +1790922330,metube,f5a3771ddb27,190197760,759812096,0,0,805306368,0 +1790922333,metube,f5a3771ddb27,190197760,759844864,0,0,805306368,0 +1790922335,metube,f5a3771ddb27,190197760,759545856,0,0,805306368,0 +1790922337,metube,f5a3771ddb27,190197760,760053760,0,0,805306368,0 +1790922339,metube,f5a3771ddb27,190197760,759296000,0,0,805306368,0 +1790922341,metube,f5a3771ddb27,190197760,760082432,0,0,805306368,0 +1790922343,metube,f5a3771ddb27,190197760,760135680,0,0,805306368,0 +1790922345,metube,f5a3771ddb27,190197760,759586816,0,0,805306368,0 +1790922347,metube,f5a3771ddb27,190197760,760033280,0,0,805306368,0 +1790922349,metube,f5a3771ddb27,190197760,759541760,0,0,805306368,0 +1790922351,metube,f5a3771ddb27,190197760,759296000,0,0,805306368,0 +1790922353,metube,f5a3771ddb27,190197760,759808000,0,0,805306368,0 +1790922355,metube,f5a3771ddb27,249851904,805138432,0,0,805306368,0 +1790922357,metube,f5a3771ddb27,197423104,766578688,0,0,805306368,0 +1790922359,metube,f5a3771ddb27,197423104,766566400,0,0,805306368,0 +1790922361,metube,f5a3771ddb27,197423104,766279680,0,0,805306368,0 +1790922363,metube,f5a3771ddb27,197423104,766533632,0,0,805306368,0 +1790922366,metube,f5a3771ddb27,197423104,766857216,0,0,805306368,0 +1790922368,metube,f5a3771ddb27,197423104,766750720,0,0,805306368,0 +1790922370,metube,f5a3771ddb27,197423104,765816832,0,0,805306368,0 +1790922372,metube,f5a3771ddb27,197423104,766541824,0,0,805306368,0 +1790922374,metube,f5a3771ddb27,197423104,766799872,0,0,805306368,0 +1790922376,metube,f5a3771ddb27,197423104,766574592,0,0,805306368,0 +1790922378,metube,f5a3771ddb27,197423104,766287872,0,0,805306368,0 +1790922380,metube,f5a3771ddb27,197423104,766787584,0,0,805306368,0 +1790922382,metube,f5a3771ddb27,197423104,766267392,0,0,805306368,0 +1790922384,metube,f5a3771ddb27,197423104,766509056,0,0,805306368,0 +1790922386,metube,f5a3771ddb27,197423104,766496768,0,0,805306368,0 +1790922388,metube,f5a3771ddb27,197423104,766500864,0,0,805306368,0 +1790922390,metube,f5a3771ddb27,197423104,766521344,0,0,805306368,0 +1790922392,metube,f5a3771ddb27,197423104,766033920,0,0,805306368,0 +1790922394,metube,f5a3771ddb27,197423104,766287872,0,0,805306368,0 +1790922396,metube,f5a3771ddb27,197423104,766840832,0,0,805306368,0 +1790922398,metube,f5a3771ddb27,197423104,766787584,0,0,805306368,0 +1790922401,metube,f5a3771ddb27,231120896,762380288,0,0,805306368,0 +1790922403,metube,f5a3771ddb27,204529664,740036608,0,0,805306368,0 +1790922405,metube,f5a3771ddb27,204529664,739319808,0,0,805306368,0 +1790922407,metube,f5a3771ddb27,204529664,739577856,0,0,805306368,0 +1790922409,metube,f5a3771ddb27,204529664,739336192,0,0,805306368,0 +1790922411,metube,f5a3771ddb27,204529664,739344384,0,0,805306368,0 +1790922413,metube,f5a3771ddb27,204529664,739291136,0,0,805306368,0 +1790922415,metube,f5a3771ddb27,204529664,739827712,0,0,805306368,0 +1790922417,metube,f5a3771ddb27,204529664,739581952,0,0,805306368,0 +1790922419,metube,f5a3771ddb27,204529664,739336192,0,0,805306368,0 +1790922421,metube,f5a3771ddb27,204529664,739840000,0,0,805306368,0 +1790922423,metube,f5a3771ddb27,204533760,739336192,0,0,805306368,0 +1790922425,metube,f5a3771ddb27,204533760,739291136,0,0,805306368,0 +1790922427,metube,f5a3771ddb27,204533760,739233792,0,0,805306368,0 +1790922429,metube,f5a3771ddb27,204533760,739598336,0,0,805306368,0 +1790922431,metube,f5a3771ddb27,204533760,739876864,0,0,805306368,0 +1790922433,metube,f5a3771ddb27,204533760,739573760,0,0,805306368,0 +1790922436,metube,f5a3771ddb27,204533760,739065856,0,0,805306368,0 +1790922438,metube,f5a3771ddb27,204533760,739057664,0,0,805306368,0 +1790922440,metube,f5a3771ddb27,204533760,738803712,0,0,805306368,0 +1790922442,metube,f5a3771ddb27,204533760,739323904,0,0,805306368,0 +1790922444,metube,f5a3771ddb27,204533760,739569664,0,0,805306368,0 +1790922446,metube,f5a3771ddb27,211279872,746684416,0,0,805306368,0 +1790922448,metube,f5a3771ddb27,269770752,805326848,0,0,805306368,0 +1790922450,metube,f5a3771ddb27,211742720,765071360,0,0,805306368,0 +1790922452,metube,f5a3771ddb27,211742720,765288448,0,0,805306368,0 +1790922454,metube,f5a3771ddb27,211742720,765296640,0,0,805306368,0 +1790922456,metube,f5a3771ddb27,211742720,765317120,0,0,805306368,0 +1790922458,metube,f5a3771ddb27,211742720,765784064,0,0,805306368,0 +1790922460,metube,f5a3771ddb27,211742720,765288448,0,0,805306368,0 +1790922462,metube,f5a3771ddb27,211742720,765267968,0,0,805306368,0 +1790922464,metube,f5a3771ddb27,211742720,765739008,0,0,805306368,0 +1790922466,metube,f5a3771ddb27,211742720,765579264,0,0,805306368,0 +1790922469,metube,f5a3771ddb27,211742720,766054400,0,0,805306368,0 +1790922471,metube,f5a3771ddb27,211742720,765566976,0,0,805306368,0 +1790922473,metube,f5a3771ddb27,211742720,765571072,0,0,805306368,0 +1790922475,metube,f5a3771ddb27,211742720,765575168,0,0,805306368,0 +1790922477,metube,f5a3771ddb27,211742720,765833216,0,0,805306368,0 +1790922479,metube,f5a3771ddb27,211742720,766078976,0,0,805306368,0 +1790922481,metube,f5a3771ddb27,211742720,765554688,0,0,805306368,0 +1790922483,metube,f5a3771ddb27,211742720,765542400,0,0,805306368,0 +1790922485,metube,f5a3771ddb27,211742720,765259776,0,0,805306368,0 +1790922487,metube,f5a3771ddb27,211742720,765759488,0,0,805306368,0 +1790922489,metube,f5a3771ddb27,211742720,766226432,0,0,805306368,0 +1790922491,metube,f5a3771ddb27,213061632,766840832,0,0,805306368,0 +1790922493,metube,f5a3771ddb27,263483392,785580032,0,0,805306368,0 +1790922495,metube,f5a3771ddb27,218931200,749305856,0,0,805306368,0 +1790922497,metube,f5a3771ddb27,218935296,750055424,0,0,805306368,0 +1790922499,metube,f5a3771ddb27,218935296,749936640,0,0,805306368,0 +1790922501,metube,f5a3771ddb27,218935296,750116864,0,0,805306368,0 +1790922504,metube,f5a3771ddb27,218935296,749645824,0,0,805306368,0 +1790922506,metube,f5a3771ddb27,218935296,750329856,0,0,805306368,0 +1790922508,metube,f5a3771ddb27,220262400,751992832,0,0,805306368,0 +1790922510,metube,f5a3771ddb27,218935296,749592576,0,0,805306368,0 +1790922512,metube,f5a3771ddb27,218935296,749916160,0,0,805306368,0 +1790922514,metube,f5a3771ddb27,218935296,749637632,0,0,805306368,0 +1790922516,metube,f5a3771ddb27,218935296,749121536,0,0,805306368,0 +1790922518,metube,f5a3771ddb27,218935296,750116864,0,0,805306368,0 +1790922520,metube,f5a3771ddb27,218935296,750415872,0,0,805306368,0 +1790922522,metube,f5a3771ddb27,218935296,750108672,0,0,805306368,0 +1790922524,metube,f5a3771ddb27,218935296,750104576,0,0,805306368,0 +1790922526,metube,f5a3771ddb27,218935296,749834240,0,0,805306368,0 +1790922528,metube,f5a3771ddb27,218935296,750387200,0,0,805306368,0 +1790922530,metube,f5a3771ddb27,218935296,749891584,0,0,805306368,0 +1790922532,metube,f5a3771ddb27,218935296,749891584,0,0,805306368,0 +1790922534,metube,f5a3771ddb27,218935296,750415872,0,0,805306368,0 +1790922536,metube,f5a3771ddb27,218935296,750141440,0,0,805306368,0 +1790922539,metube,f5a3771ddb27,283312128,799088640,0,0,805306368,0 +1790922541,metube,f5a3771ddb27,226152448,753377280,0,0,805306368,0 +1790922543,metube,f5a3771ddb27,226152448,753451008,0,0,805306368,0 +1790922545,metube,f5a3771ddb27,226152448,753442816,0,0,805306368,0 +1790922547,metube,f5a3771ddb27,226152448,753983488,0,0,805306368,0 +1790922549,metube,f5a3771ddb27,226152448,753479680,0,0,805306368,0 +1790922551,metube,f5a3771ddb27,226152448,753426432,0,0,805306368,0 +1790922553,metube,f5a3771ddb27,226152448,753696768,0,0,805306368,0 +1790922555,metube,f5a3771ddb27,226152448,753209344,0,0,805306368,0 +1790922557,metube,f5a3771ddb27,226152448,753975296,0,0,805306368,0 +1790922559,metube,f5a3771ddb27,226152448,753192960,0,0,805306368,0 +1790922561,metube,f5a3771ddb27,226152448,752930816,0,0,805306368,0 +1790922563,metube,f5a3771ddb27,226152448,753680384,0,0,805306368,0 +1790922565,metube,f5a3771ddb27,226152448,753201152,0,0,805306368,0 +1790922567,metube,f5a3771ddb27,226152448,753750016,0,0,805306368,0 +1790922569,metube,f5a3771ddb27,226152448,753213440,0,0,805306368,0 +1790922571,metube,f5a3771ddb27,226152448,753741824,0,0,805306368,0 +1790922573,metube,f5a3771ddb27,226152448,753147904,0,0,805306368,0 +1790922576,metube,f5a3771ddb27,226152448,752627712,0,0,805306368,0 +1790922578,metube,f5a3771ddb27,226152448,752615424,0,0,805306368,0 +1790922580,metube,f5a3771ddb27,226152448,753401856,0,0,805306368,0 +1790922582,metube,f5a3771ddb27,226152448,753401856,0,0,805306368,0 +1790922584,metube,f5a3771ddb27,226152448,752902144,0,0,805306368,0 +1790922586,metube,f5a3771ddb27,226152448,753131520,0,0,805306368,0 +1790922588,metube,f5a3771ddb27,226152448,753397760,0,0,805306368,0 +1790922590,metube,f5a3771ddb27,226152448,753127424,0,0,805306368,0 +1790922592,metube,f5a3771ddb27,226152448,753389568,0,0,805306368,0 +1790922594,metube,f5a3771ddb27,226152448,753156096,0,0,805306368,0 +1790922596,metube,f5a3771ddb27,226152448,753135616,0,0,805306368,0 +1790922598,metube,f5a3771ddb27,226152448,753610752,0,0,805306368,0 +1790922600,metube,f5a3771ddb27,226152448,752898048,0,0,805306368,0 +1790922602,metube,f5a3771ddb27,226152448,752377856,0,0,805306368,0 +1790922604,metube,f5a3771ddb27,226152448,753147904,0,0,805306368,0 +1790922606,metube,f5a3771ddb27,226152448,753405952,0,0,805306368,0 +1790922609,metube,f5a3771ddb27,226152448,753397760,0,0,805306368,0 +1790922611,metube,f5a3771ddb27,226152448,752889856,0,0,805306368,0 +1790922613,metube,f5a3771ddb27,226152448,752635904,0,0,805306368,0 +1790922615,metube,f5a3771ddb27,226152448,753397760,0,0,805306368,0 +1790922617,metube,f5a3771ddb27,226152448,727658496,0,0,805306368,0 +1790922619,metube,f5a3771ddb27,226152448,727891968,0,0,805306368,0 +1790922621,metube,f5a3771ddb27,226152448,728174592,0,0,805306368,0 +1790922623,metube,f5a3771ddb27,226152448,727375872,0,0,805306368,0 +1790922625,metube,f5a3771ddb27,226152448,727654400,0,0,805306368,0 +1790922627,metube,f5a3771ddb27,226152448,728150016,0,0,805306368,0 +1790922629,metube,f5a3771ddb27,226152448,727650304,0,0,805306368,0 +1790922631,metube,f5a3771ddb27,226152448,727908352,0,0,805306368,0 +1790922633,metube,f5a3771ddb27,226152448,727384064,0,0,805306368,0 +1790922635,metube,428f55c58f33,44060672,51523584,0,0,805306368,0 +1790922637,metube,428f55c58f33,61603840,69406720,0,0,805306368,0 +1790922639,metube,428f55c58f33,61603840,69402624,0,0,805306368,0 +1790922642,metube,428f55c58f33,61603840,69398528,0,0,805306368,0 +1790922644,metube,428f55c58f33,61603840,69398528,0,0,805306368,0 +1790922646,metube,428f55c58f33,61603840,69398528,0,0,805306368,0 +1790922648,metube,428f55c58f33,61603840,69398528,0,0,805306368,0 +1790922650,metube,428f55c58f33,61603840,69394432,0,0,805306368,0 +1790922652,metube,428f55c58f33,61603840,69394432,0,0,805306368,0 +1790922654,metube,428f55c58f33,61603840,69394432,0,0,805306368,0 +1790922656,metube,428f55c58f33,61603840,69394432,0,0,805306368,0 +1790922658,metube,428f55c58f33,61603840,69394432,0,0,805306368,0 +1790922660,metube,428f55c58f33,61603840,69394432,0,0,805306368,0 +1790922662,metube,428f55c58f33,61603840,69394432,0,0,805306368,0 +1790922664,metube,428f55c58f33,61603840,69394432,0,0,805306368,0 diff --git a/documentation/audits/family-gate-2026-10-02/C-metube-bench/move/mem-summary.txt b/documentation/audits/family-gate-2026-10-02/C-metube-bench/move/mem-summary.txt new file mode 100644 index 00000000..0cbe053c --- /dev/null +++ b/documentation/audits/family-gate-2026-10-02/C-metube-bench/move/mem-summary.txt @@ -0,0 +1,4 @@ +from-birth 2 s sampler, MV-metube (one row per container INSTANCE: FROM 2026.09.28, TO 2026.09.29 under the harness watch + burst, ABORT 2026.09.28); swap 0 bench +metube 95a197aba812 06:19:22-06:19:55 n= 17 peak anon 82.2 MiB = 10.7 % of 768 MiB; peak current 93.0 MiB; oom_kill 0; restarts 0; swap.current ['0'] +metube f5a3771ddb27 06:19:57-06:30:33 n= 310 peak anon 270.2 MiB = 35.2 % of 768 MiB; peak current 768.0 MiB; oom_kill 0; restarts 0; swap.current ['0'] +metube 428f55c58f33 06:30:35-06:31:04 n= 15 peak anon 58.8 MiB = 7.6 % of 768 MiB; peak current 66.2 MiB; oom_kill 0; restarts 0; swap.current ['0'] diff --git a/documentation/audits/family-gate-2026-10-02/shots/familylogin-library.png b/documentation/audits/family-gate-2026-10-02/shots/familylogin-library.png new file mode 100644 index 00000000..a87a154f Binary files /dev/null and b/documentation/audits/family-gate-2026-10-02/shots/familylogin-library.png differ diff --git a/documentation/audits/family-gate-2026-10-02/shots/familylogin-video.png b/documentation/audits/family-gate-2026-10-02/shots/familylogin-video.png new file mode 100644 index 00000000..bbac1fed Binary files /dev/null and b/documentation/audits/family-gate-2026-10-02/shots/familylogin-video.png differ diff --git a/documentation/audits/family-gate-2026-10-02/shots/grimmory/1.png b/documentation/audits/family-gate-2026-10-02/shots/grimmory/1.png new file mode 100644 index 00000000..f7ce6588 Binary files /dev/null and b/documentation/audits/family-gate-2026-10-02/shots/grimmory/1.png differ diff --git a/documentation/audits/family-gate-2026-10-02/shots/grimmory/2.png b/documentation/audits/family-gate-2026-10-02/shots/grimmory/2.png new file mode 100644 index 00000000..91ae99a5 Binary files /dev/null and b/documentation/audits/family-gate-2026-10-02/shots/grimmory/2.png differ diff --git a/documentation/audits/family-gate-2026-10-02/shots/grimmory/3.png b/documentation/audits/family-gate-2026-10-02/shots/grimmory/3.png new file mode 100644 index 00000000..ee40e5c9 Binary files /dev/null and b/documentation/audits/family-gate-2026-10-02/shots/grimmory/3.png differ diff --git a/documentation/audits/family-gate-2026-10-02/shots/metube/1.png b/documentation/audits/family-gate-2026-10-02/shots/metube/1.png new file mode 100644 index 00000000..30ca0489 Binary files /dev/null and b/documentation/audits/family-gate-2026-10-02/shots/metube/1.png differ diff --git a/documentation/audits/family-gate-2026-10-02/shots/metube/2.png b/documentation/audits/family-gate-2026-10-02/shots/metube/2.png new file mode 100644 index 00000000..a3744133 Binary files /dev/null and b/documentation/audits/family-gate-2026-10-02/shots/metube/2.png differ diff --git a/documentation/audits/family-gate-2026-10-02/tools/fg.py b/documentation/audits/family-gate-2026-10-02/tools/fg.py new file mode 100644 index 00000000..de23210a --- /dev/null +++ b/documentation/audits/family-gate-2026-10-02/tools/fg.py @@ -0,0 +1,67 @@ +"""fg.py — the family gate's live client on 9202 (product endpoints only). LAN = curl to 9202's traefik with a Host header; +tunnel = a curl container at cloudflared's fixed address 172.16.253.2 sending what Cloudflare sends. Never prints secrets.""" +import json, os, re, subprocess, sys, urllib.parse +sys.path.insert(0, "/mnt/5_hdd/felhom.eu/git/app-catalog-felhom.eu/scripts") +import box_walk as w +DOM = w.DOMAIN; CTL = f"felhom.{DOM}" +GSH = "/tmp/claude-1000/-mnt-5-hdd-felhom-eu-git/05c3d295-b388-481a-8c36-44a2a80b7d7d/scratchpad/g.sh" + +def lan(host, path, *a, method=None, data=None, cookie=None, hdrs=(), timeout=15): + args = ["curl", "-sk", "--max-time", str(timeout), "-D", "-", "-H", f"Host: {host}"] + for h in hdrs: args += ["-H", h] + if cookie: args += ["-H", f"Cookie: {cookie}"] + if method: args += ["-X", method] + if data is not None: args += ["--data", data] + r = subprocess.run(args + list(a) + [f"{w.BASE}{path}"], capture_output=True, text=True, errors="replace") + head, _, body = r.stdout.replace("\r\n", "\n").partition("\n\n") + while re.match(r"HTTP/\S+ 100", head) and body: + head, _, body = body.partition("\n\n") + m = re.match(r"HTTP/\S+ (\d+)", head) + return (m.group(1) if m else "000"), head, body + +def tun(host, path, visitor, forged=None, *a, timeout=15): + xff = f"{forged}, {visitor}" if forged else visitor + extra = " ".join("'" + x.replace("'", "'\\''") + "'" for x in a) + cmd = (f"docker run --rm --network felhom-tunnel --ip 172.16.253.2 curlimages/curl:8.11.1 -sk --max-time {timeout} -D - " + f"'https://traefik{path}' -H 'Host: {host}' -H 'X-Forwarded-For: {xff}' -H 'CF-Connecting-IP: {visitor}' {extra}") + r = subprocess.run([GSH, "9202", cmd], capture_output=True, text=True, errors="replace") + head, _, body = r.stdout.replace("\r\n", "\n").partition("\n\n") + m = re.match(r"HTTP/\S+ (\d+)", head) + return (m.group(1) if m else "000"), head, body + +def loc(head): + m = re.search(r"(?im)^location: *(\S+)", head); return m.group(1) if m else "" + +def setc(head, name): + m = re.search(rf"(?im)^set-cookie: *({name}=[^;\n]*)", head); return m.group(1) if m else None + +def is_gate(code, head, body): + if code == "302" and f"https://{CTL}/__family/start" in loc(head): return True + return code == "401" and "sign in with your family login" in body + +def dash_session(): + return open(f"{w.SC}/sess{os.getpid()}.txt").read().strip() # "felhom_session=…" + +def family_login(name, pw, visitor_tunnel=None): + data = None + c, h, b = lan(CTL, "/__family/login") + ft = re.search(r'name="_ft" value="([^"]+)"', b) + form = urllib.parse.urlencode({"_ft": ft.group(1) if ft else "", "name": name, "password": pw, "rd": ""}) + if visitor_tunnel: + c, h, b = tun(CTL, "/__family/login", visitor_tunnel, None, "-X", "POST", "-H", "Content-Type: application/x-www-form-urlencoded", "--data", form) + else: + c, h, b = lan(CTL, "/__family/login", method="POST", data=form, hdrs=("Content-Type: application/x-www-form-urlencoded",)) + return c, setc(h, "felhom_family"), h, b + +def app_cookie(sub, session_cookie): + """Walk a browser holding session_cookie (family or dashboard) through start → callback → felhom_famgate.""" + host = f"{sub}.{DOM}" + c, h, b = lan(host, "/", hdrs=("Accept: text/html",)) + l = loc(h) + if "/__family/start" not in l: return None, f"app did not send to /__family/start ({c} {l[:60]})" + u = urllib.parse.urlsplit(l) + c, h, b = lan(u.hostname, f"{u.path}?{u.query}", cookie=session_cookie, hdrs=("Accept: text/html",)) + l = loc(h); u = urllib.parse.urlsplit(l) + if "/__felhom_gate/fcb" not in u.path: return None, f"start did not hand a callback ({c} {l[:60]})" + c, h, b = lan(u.hostname, f"{u.path}?{u.query}", hdrs=("Accept: text/html",)) + return setc(h, "felhom_famgate"), h diff --git a/documentation/audits/family-gate-2026-10-02/tools/fg_b4.py b/documentation/audits/family-gate-2026-10-02/tools/fg_b4.py new file mode 100644 index 00000000..e67b46bf --- /dev/null +++ b/documentation/audits/family-gate-2026-10-02/tools/fg_b4.py @@ -0,0 +1,160 @@ +#!/usr/bin/env python3 +"""fg_b4.py — Part B4: Grimmory and MeTube installed on ONE demo box (demo-hp, guest 9201, controller 0.287.0, the LIVE +catalog) through the product, measured, and removed WITH their data (the with-data half of record row 2.6 that 9202 cannot +show — R-442). The stranger and the family member go through the REAL internet path (Cloudflare → the box's tunnel), from +DooPlex, as well as the LAN. Secrets never printed; the test member's password lives in a 0600 scratch file and the member +is removed at the end. Env: SC (0600 scratch with .ctlpw = the demo password), EV. +Evidence: $EV/B4/-demo.txt + $EV/B4/demo-common.txt.""" +import json, os, re, secrets, subprocess, sys, time +os.environ.setdefault("GUEST", "9201") +os.environ.setdefault("BASE", "https://192.168.0.155") +sys.path.insert(0, "/mnt/5_hdd/felhom.eu/git/app-catalog-felhom.eu/scripts") +import box_walk as w +import upgrade_fixtures_box as fixtures +w.HP = "hp" # `ssh hp` is the working alias for demo-hp (memory: demo-hp-guest-controller-access) +DOM = w.DOMAIN; CTL = f"felhom.{DOM}" +DRIVE = "/mnt/felhom-drives/hdd_1" # what this box's other apps use as HDD_PATH (read 2026-10-02) +EVD = f"{w.EV}/B4"; os.makedirs(EVD, exist_ok=True) +APPS = {"grimmory": ("library", fixtures.FIXTURES["grimmory"]), "metube": ("video", fixtures.FIXTURES["metube"])} +logs = {a: open(f"{EVD}/{a}-demo.txt", "a", buffering=1) for a in APPS} +common = open(f"{EVD}/demo-common.txt", "a", buffering=1) + + +def say(app, *a): + w.say(*a); (logs[app] if app else common).write(" ".join(map(str, a)) + "\n") + + +def internet(host, path="/", cookie=None, data=None, hdrs=()): + """A request through the REAL public path: DooPlex → Cloudflare → the box's tunnel (no Host trick, real DNS).""" + a = ["curl", "-s", "--max-time", "20", "-o", "/dev/null", "-w", "%{http_code}"] + for h in hdrs: a += ["-H", h] + if cookie: a += ["-H", f"Cookie: {cookie}"] + if data is not None: a += ["--data", data] + return w.sh(a + [f"https://{host}{path}"]).stdout.strip() + + +def lan(host, path="/"): + return w.sh(["curl", "-sk", "--max-time", "15", "-o", "/dev/null", "-w", "%{http_code}", "-H", f"Host: {host}", f"{w.BASE}{path}"]).stdout.strip() + + +def gate_files(): + return w.guest("ls /opt/docker/stacks/traefik/dynamic/ | grep -E 'family-gate|setup-gate' | tr '\\n' ' '").strip() or "none" + + +def deploy(app, sub): + code, d = w.ctl("GET", f"/api/stacks/{app}/deploy-fields") + fields = (((d.get("data") or {}).get("metadata") or {}).get("deploy_fields")) or [] + values = {"DOMAIN": DOM, "SUBDOMAIN": sub} + for f in fields: + ev, ty = f.get("env_var"), f.get("type") + if ev in values or (not f.get("required") and ty != "password"): + continue + if ty == "path": + values[ev] = DRIVE + elif ty in ("secret", "password"): + values[ev] = "Demo-" + secrets.token_hex(12) + else: + values[ev] = f.get("default") or f"demo-{app}" + code, d = w.ctl("POST", f"/api/stacks/{app}/deploy", {"values": values}) + say(app, f"deploy {app} (fields beyond DOMAIN/SUBDOMAIN: {[k for k in values if k not in ('DOMAIN', 'SUBDOMAIN')]}, values not printed) -> {code} {str(d)[:100]}") + say(app, " gate files right after the press:", gate_files()) + t0 = time.time() + for _ in range(120): + time.sleep(5) + st = w.stack(app) + if st.get("deployed") and (st.get("app_config") or {}).get("pinned_images") and st.get("state") in ("running", "degraded", "unhealthy"): + break + say(app, f" deployed in {round(time.time()-t0)} s: state={st.get('state')} pinned={(st.get('app_config') or {}).get('pinned_images')}") + say(app, " family_gate record:", " ".join(w.guest(f"grep -A4 '^family_gate:' /opt/docker/stacks/{app}/app.yaml").split())) + + +w.login() +say(None, f"##### B4 on demo-hp 9201 — controller {w.guest('docker inspect felhom-controller --format {{.Config.Image}}').strip()} | catalog " + + w.guest("cd /opt/docker/felhom-controller/data/catalog-cache 2>/dev/null || cd /var/lib/docker/volumes/felhom-controller-data/_data/data/catalog-cache; git log --oneline -1").strip()[:90] + + f" | {time.strftime('%FT%TZ', time.gmtime())}") +say(None, "before: gate files:", gate_files(), "| drive:", " ".join(w.guest(f"df -h {DRIVE} | tail -1").split())) +say(None, "the public names resolve to:", w.sh(["getent", "hosts", f"video.{DOM}"]).stdout.strip(), "(Cloudflare — the REAL internet path)") +# a test family member through the Család card (the card's own call) +sess = open(f"{w.SC}/sess{os.getpid()}.txt").read().strip(); csrf = open(f"{w.SC}/csrf{os.getpid()}.txt").read().strip() +r = subprocess.run(["curl", "-sk", "-H", f"Host: {CTL}", "-H", f"Cookie: {sess}", "-H", f"X-CSRF-Token: {csrf}", "-X", "POST", "--data", "name=b4teszt", + f"{w.BASE}/family/members/add"], capture_output=True, text=True) +dd = json.loads(r.stdout); fpw = dd["data"]["password"] +open(f"{w.SC}/b4fam.json", "w").write(json.dumps({"b4teszt": fpw})); os.chmod(f"{w.SC}/b4fam.json", 0o600) +say(None, f"Család card: add b4teszt -> ok={dd['ok']} (password returned once, len {len(fpw)}, not printed)") + +toks = {} +for app, (sub, fx) in APPS.items(): + deploy(app, sub) + if app == "grimmory": + w.wait_app(sub, "/api/v1/healthcheck", want=("200",), tries=90) + pw = "Gm-" + secrets.token_hex(10) + rc, code, _ = w.app_curl(sub, "/api/v1/setup", "-H", "Content-Type: application/json", + data=json.dumps({"username": "admin", "password": pw, "email": "admin@felhom.invalid", "name": "Felhom"}), method="POST") + say(app, "the household makes the first admin through the setup gate ->", code) + for i in range(30): + if "setup-gate-grimmory" not in gate_files(): break + time.sleep(5) + say(app, f" setup gate opened by its probe after ~{5*i} s; gate files: {gate_files()}") + code, tok = fx._token(w, sub, "admin", pw) + auth = ["-H", f"Authorization: Bearer {tok}"] + rc, c, out = w.app_curl(sub, "/api/v1/libraries", *auth, "-H", "Content-Type: application/json", + data=json.dumps({"name": "Konyvek", "icon": "book", "iconType": "LUCIDE", "paths": [{"path": "/books"}], "watch": True, + "metadataSource": "EMBEDDED", "organizationMode": "BOOK_PER_FILE"}), method="POST") + lib = json.loads(out); title = "Felhom Demo " + secrets.token_hex(4) + import tempfile + fn = tempfile.mktemp(suffix=".epub"); open(fn, "wb").write(fx._epub(title)) + rc, c, _ = w.app_curl(sub, "/api/v1/files/upload", *auth, "-F", f"file=@{fn};type=application/epub+zip", + "-F", f"libraryId={lib['id']}", "-F", f"pathId={lib['paths'][0]['id']}", method="POST"); os.unlink(fn) + toks[app] = {"user": "admin", "pw": pw, "title": title} + say(app, "a book uploaded ->", c, "| read back:", fx.verify(w, sub, toks[app], lambda *a: say(app, *a))) + else: + toks[app] = fx.seed(w, sub, lambda *a: say(app, *a)) + say(app, "the seed reads back:", fx.verify(w, sub, toks[app], lambda *a: say(app, *a)) if toks[app] else "NO SEED") + say(app, "on the drive:", " | ".join(w.guest(f"find {DRIVE} -path '*{app}*' -type f \\( -name '*.epub' -o -name '*.mp4' \\) 2>/dev/null | head -3").split("\n")).strip()) + # a STRANGER through the real internet and the LAN + paths = ["/", "/api/v1/books", "/api/v1/opdsx", "/api/v1/opds"] if app == "grimmory" else ["/", "/history", "/socket.io/?EIO=4&transport=polling"] + for p in paths: + say(app, f"a STRANGER {p:40s} internet {internet(f'{sub}.{DOM}', p)} | LAN {lan(f'{sub}.{DOM}', p)}") + +# the family member signs in through the REAL internet (the box's own page), then reaches both apps +import http.cookiejar, urllib.request, urllib.parse, ssl +cj = http.cookiejar.CookieJar() +op = urllib.request.build_opener(urllib.request.HTTPCookieProcessor(cj)) +def get(url, data=None): + try: + r = op.open(urllib.request.Request(url, data=data, headers={"Accept": "text/html"}), timeout=30) + return r.status, r.geturl(), r.read().decode("utf-8", "replace") + except urllib.error.HTTPError as e: + return e.code, url, e.read().decode("utf-8", "replace") +for app, (sub, fx) in APPS.items(): + st, url, body = get(f"https://{sub}.{DOM}/") + m = re.search(r'name="_ft" value="([^"]+)"', body) + if m and "/__family/login" in url: + form = {"_ft": m.group(1), "name": "b4teszt", "password": fpw} + for k in re.findall(r' {code} {str(d)[:300]}") + time.sleep(8) + left = w.guest(f"docker ps -a --format '{{{{.Names}}}}' | grep -E '^{app}'; docker volume ls -q | grep -i {app}; " + f"find {DRIVE} -path '*{app}*' \\( -name '*.epub' -o -name '*.mp4' -o -name '*.sql' \\) 2>/dev/null | head; " + f"ls -d {DRIVE}/*/{app} {DRIVE}/*/*/{app} 2>/dev/null").strip() + say(app, f" left after: {' | '.join(left.splitlines()) or 'nothing (no container, no volume, no file of the app on the drive)'}") + say(app, " gate files:", gate_files(), f"| a stranger at {sub}/ -> internet {internet(f'{sub}.{DOM}')} | LAN {lan(f'{sub}.{DOM}')}") +r = subprocess.run(["curl", "-sk", "-H", f"Host: {CTL}", "-H", f"Cookie: {open(f'{w.SC}/sess{os.getpid()}.txt').read().strip()}", + "-H", f"X-CSRF-Token: {open(f'{w.SC}/csrf{os.getpid()}.txt').read().strip()}", "-X", "POST", "--data", "name=b4teszt", + f"{w.BASE}/family/members/remove"], capture_output=True, text=True) +say(None, "Család card: remove b4teszt ->", json.loads(r.stdout).get("ok"), "| members now:", json.loads(r.stdout).get("data", {}).get("members")) +os.unlink(f"{w.SC}/b4fam.json") +say(None, "after: gate files:", gate_files(), "| stacks:", w.guest("docker ps --format '{{.Names}}' | grep -c -E '^(grimmory|metube)' || true").strip()) diff --git a/documentation/audits/family-gate-2026-10-02/tools/fg_grim_mem.py b/documentation/audits/family-gate-2026-10-02/tools/fg_grim_mem.py new file mode 100644 index 00000000..5d08eb45 --- /dev/null +++ b/documentation/audits/family-gate-2026-10-02/tools/fg_grim_mem.py @@ -0,0 +1,53 @@ +#!/usr/bin/env python3 +"""fg_grim_mem.py — Grimmory's memory rows on 9202 (checklist 5.2 burst, 5.4 self-sizing), behind the family gate. +5.2: the household's heaviest ordinary act — a burst of book uploads (40 EPUBs through the app's own upload API, the +library re-reading each) — while grimmory's own cgroup is sampled every second (anon, oom_kill, restarts). +5.4: does the heap size itself from the limit? The image's JAVA_TOOL_OPTIONS carry MaxRAMPercentage=60; the JVM's own +answer (MaxHeapSize) is read at three container limits with the SAME image and options (`docker run --rm -m`, no app). +Evidence: $EV/B/box/grim-mem.txt.""" +import json, os, secrets, subprocess, sys, tempfile, threading, time +sys.path.insert(0, os.path.dirname(os.path.abspath(__file__))) +from fg import * # noqa +import upgrade_fixtures_box as fixtures +GM = fixtures.FIXTURES["grimmory"] +EVD = f"{w.EV}/B/box"; log = open(f"{EVD}/grim-mem.txt", "a", buffering=1) +def say(*a): + w.say(*a); log.write(" ".join(map(str, a)) + "\n") +w.login() +t = json.load(open(w.SC + "/seed-tokens-fg.json"))["grimmory"] +say(f"##### grimmory memory on 9202 — {time.strftime('%FT%TZ', time.gmtime())} | limit", w.guest("docker inspect grimmory --format '{{.HostConfig.Memory}}'").strip()) +w.guest("cat > /root/gm-s.sh <<'SEOF'\nid=$(docker ps -q --no-trunc --filter name=^grimmory$); cg=/sys/fs/cgroup/system.slice/docker-$id.scope; : > /root/gm-mem.csv\n" + "while [ ! -f /root/gm-mem.stop ]; do echo \"$(date +%s),$(awk '$1==\"anon\"{print $2}' $cg/memory.stat),$(awk '$1==\"oom_kill\"{print $2}' $cg/memory.events),$(cat $cg/memory.max),$(docker inspect -f '{{.RestartCount}}' $id)\" >> /root/gm-mem.csv; sleep 1; done\nSEOF\n" + "rm -f /root/gm-mem.stop; (nohup sh /root/gm-s.sh >/dev/null 2>&1 &)") +time.sleep(5) +code, tok = GM._token(w, "library", t["user"], t["pw"]) +auth = ["-H", f"Authorization: Bearer {tok}"] +rc, code, out = w.app_curl("library", "/api/v1/libraries", *auth) +lib = json.loads(out)[0]; lid, pid = lib["id"], lib["paths"][0]["id"] +t0 = time.time(); codes = {} +for i in range(40): + fn = tempfile.mktemp(suffix=".epub"); open(fn, "wb").write(GM._epub(f"Felhom Burst {i:02d} " + secrets.token_hex(3))) + rc, c, _ = w.app_curl("library", "/api/v1/files/upload", *auth, "-F", f"file=@{fn};type=application/epub+zip", + "-F", f"libraryId={lid}", "-F", f"pathId={pid}", method="POST") + os.unlink(fn); codes[c] = codes.get(c, 0) + 1 +say(f"5.2 burst: 40 EPUB uploads through the app's own API in {time.time()-t0:.1f} s: codes {codes}") +n = 0 +for _ in range(36): + rc, c, out = w.app_curl("library", "/api/v1/books", *auth) + try: n = len(json.loads(out)) + except Exception: n = -1 + if n >= 41: break + time.sleep(5) +say(f" the library lists {n} books after the burst ({time.time()-t0:.0f} s from its start); sampling 120 s more") +time.sleep(120) +w.guest("touch /root/gm-mem.stop"); time.sleep(2) +rows = [r.split(",") for r in w.guest("cat /root/gm-mem.csv").split() if r.count(",") == 4] +lim = int(rows[0][3]); peak = max(int(r[1]) for r in rows) +say(f"5.2 grimmory, {len(rows)} samples (1/s) across the burst: peak anon {peak/1048576:.1f} MiB = {100*peak/lim:.1f} % of {lim//1048576}M; " + f"oom_kill {max(int(r[2]) for r in rows)}; restarts {max(int(r[4]) for r in rows)}") +open(f"{EVD}/grim-mem-burst.csv", "w").write("epoch,anon,oom_kill,limit,restarts\n" + "\n".join(",".join(r) for r in rows) + "\n") +w.guest("rm -f /root/gm-s.sh /root/gm-mem.csv /root/gm-mem.stop") +img = w.guest("docker inspect grimmory --format '{{.Config.Image}}'").strip() +for m in ("768m", "1024m", "1536m"): + out = w.guest(f"docker run --rm -m {m} --memory-swap {m} --entrypoint java {img} -XX:+PrintFlagsFinal -version 2>/dev/null | grep -E ' MaxHeapSize | MaxRAMPercentage '") + say(f"5.4 {img} at a {m} container limit (the image's own JAVA_TOOL_OPTIONS): " + " | ".join(" ".join(l.split()) for l in out.strip().splitlines())) diff --git a/documentation/audits/family-gate-2026-10-02/tools/fg_items.py b/documentation/audits/family-gate-2026-10-02/tools/fg_items.py new file mode 100644 index 00000000..87ba883f --- /dev/null +++ b/documentation/audits/family-gate-2026-10-02/tools/fg_items.py @@ -0,0 +1,136 @@ +#!/usr/bin/env python3 +"""fg_items.py — the VERDICT's exit items 1–5 again, now THROUGH THE PRODUCT on 9202 (controller 0.287.0, no hand-made +traefik files), plus the build's own rules (reset/remove end access, controller down = closed) and the cost.""" +import hashlib, json, os, re, statistics, subprocess, sys, time +from fg import * +SC = w.SC +log = open(f"{w.EV}/items.txt", "a", buffering=1) +def say(*a): + w.say(*a); log.write(" ".join(map(str, a)) + "\n") +w.login() +fam = json.load(open(f"{SC}/fam.json")); gm = json.load(open(f"{SC}/gm.json")) +GMH, MTH = f"library.{DOM}", f"video.{DOM}" +say(f"##### exit items through the product — {time.strftime('%FT%TZ', time.gmtime())}") +WS = ("Connection: Upgrade", "Upgrade: websocket", "Sec-WebSocket-Version: 13", "Sec-WebSocket-Key: dGhlIHNhbXBsZSBub25jZQ==") +cases = [(GMH, "GET", "/", ("Accept: text/html",), None), (GMH, "GET", "/", (), None), (GMH, "GET", "/api/v1/books", (), None), + (GMH, "GET", "/api/v1/healthcheck", (), None), (GMH, "POST", "/api/v1/setup", ("Content-Type: application/json",), '{"username":"x","password":"Stranger-1234"}'), + (GMH, "POST", "/api/v1/auth/login", ("Content-Type: application/json",), '{"username":"admin","password":"guess"}'), + (GMH, "GET", "/ws/websocket", WS, None), (GMH, "GET", "/api/v1/opdsx", (), None), (GMH, "GET", "/api/v1/opds-evil", (), None), + (GMH, "GET", "/api/v1/opds/../../api/v1/books", ("X-Path: as-is",), None), (GMH, "GET", "/no-such-page", ("Accept: text/html",), None), + (MTH, "GET", "/", ("Accept: text/html",), None), (MTH, "GET", "/", (), None), (MTH, "GET", "/history", (), None), + (MTH, "POST", "/add", ("Content-Type: application/json",), '{"url":"https://example.com/v","quality":"best"}'), + (MTH, "GET", "/socket.io/?EIO=4&transport=polling", (), None), (MTH, "GET", "/socket.io/?EIO=4&transport=websocket", WS, None), + (MTH, "GET", "/download/x.mp4", (), None)] +say("## item 1 — a STRANGER (no cookie), LAN and simulated tunnel") +apps = 0 +for host, m, p, h, d in cases: + extra = ["--path-as-is"] if "as-is" in " ".join(h) else [] + c, hh, b = lan(host, p, *extra, method=m, data=d, hdrs=h) + tc, th, tb = tun(host, p, "198.51.100.66", None, *extra, "-X", m, *sum([["-H", x] for x in h], []), *(["--data", d] if d else [])) + g1, g2 = is_gate(c, hh, b), is_gate(tc, th, tb) + apps += (not g1) + (not g2) + say(f" {host.split('.')[0]:8s} {m:4s} {p:40s} LAN {c} {'GATE' if g1 else 'APP!'} | tunnel {tc} {'GATE' if g2 else 'APP!'}") +say(f"item 1: {apps} app answers of {2*len(cases)} -> {'PASS' if apps == 0 else 'FAIL'}") + +say("## item 2 — family members, their OWN logins, through the product's sign-in page") +c, sess, h, b = family_login("anna", fam["anna"]) +ma = re.search(r"(?i)max-age=(\d+)", h); path = re.search(r"(?i)path=([^;\n]+)", h) +say(f" anna signs in on {CTL}/__family/login: {c}; felhom_family {'set' if sess else 'NONE'}; Max-Age {int(ma.group(1))//86400 if ma else '?'} d; Path={path.group(1).strip() if path else '?'}; dashboard cookie set: {'felhom_session=' in h}") +ga = app_cookie("library", sess)[0]; mb_s = family_login("bela", fam["bela"])[1]; mt = app_cookie("video", mb_s)[0] +for host, ck, p, hh in ((GMH, ga, "/", ("Accept: text/html",)), (GMH, ga, "/api/v1/healthcheck", ()), (MTH, mt, "/", ()), + (MTH, mt, "/socket.io/?EIO=4&transport=polling", ()), (MTH, mt, "/history", ())): + c, h2, b2 = lan(host, p, cookie=ck, hdrs=hh) + say(f" member GET {host.split('.')[0]}{p} -> {c} {'GATE' if is_gate(c, h2, b2) else 'APP'}") +c, h2, b2 = lan(MTH, "/socket.io/?EIO=4&transport=websocket", "--http1.1", cookie=mt, hdrs=WS, timeout=4) +say(f" bela MeTube websocket upgrade -> {c} {'GATE' if is_gate(c, h2, b2) else 'APP'} {[l for l in h2.splitlines() if l.lower().startswith('upgrade')]}") +c, h2, b2 = lan(GMH, "/", cookie=mt, hdrs=("Accept: text/html",)) +say(f" bela's MeTube cookie at Grimmory -> {c} {'GATE' if is_gate(c, h2, b2) else 'APP!'}") +w.guest("docker restart felhom-controller >/dev/null; sleep 25") +c, h2, b2 = lan(GMH, "/", cookie=ga, hdrs=("Accept: text/html",)) +say(f" after the CONTROLLER restarted, anna's Grimmory cookie -> {c} {'GATE' if is_gate(c, h2, b2) else 'APP (survived)'}") +w.login() +c, h2, b2 = lan(CTL, "/__family/logout", method="POST", cookie=sess) +c2, h3, b3 = lan(GMH, "/", cookie=ga, hdrs=("Accept: text/html",)) +say(f" anna logs out ({c}) -> her Grimmory cookie {c2} {'GATE (refused)' if is_gate(c2, h3, b3) else 'APP! (still in)'}") + +say("## item 3 — a stranger's guesses at the family sign-in lock only the stranger (simulated tunnel)") +seq = [family_login("anna", f"guess-{i}", visitor_tunnel="198.51.100.66")[0] for i in range(1, 8)] +say(f" stranger 198.51.100.66, 7 wrong for anna: {seq}") +say(f" stranger with anna's RIGHT password while locked: {family_login('anna', fam['anna'], visitor_tunnel='198.51.100.66')[0]}") +c, s2, h, b = family_login("anna", fam["anna"], visitor_tunnel="203.0.113.10") +say(f" anna herself from 203.0.113.10 (tunnel), at once: {c}, session {'set' if s2 else 'NONE'}") +c, s3, h, b = family_login("bela", fam["bela"]) +say(f" bela from the LAN, at once: {c}, session {'set' if s3 else 'NONE'}") +say(" controller log:", w.guest("docker logs --since 2m felhom-controller 2>&1 | grep -E 'family sign-in' | tail -6")) + +say("## item 4 — Grimmory's e-reader exceptions keep Grimmory's OWN login (no family cookie)") +ha = app_cookie("library", w.dash_cookie() if hasattr(w, "dash_cookie") else dash_session())[0] +c, h, b = lan(GMH, "/api/v1/auth/login", method="POST", cookie=ha, hdrs=("Content-Type: application/json",), data=json.dumps({"username": "admin", "password": gm["admin_pw"]})) +tok = json.loads(b).get("accessToken"); A = ("Content-Type: application/json", f"Authorization: Bearer {tok}") +opw, kpw = __import__("secrets").token_hex(8), __import__("secrets").token_hex(8) +lan(GMH, "/api/v1/settings", method="PUT", cookie=ha, hdrs=A, data=json.dumps([{"name": "OPDS_SERVER_ENABLED", "value": True}])) +lan(GMH, "/api/v2/opds-users", method="POST", cookie=ha, hdrs=A, data=json.dumps({"username": "olvaso", "password": opw})) +c, h, b = lan(GMH, "/api/v1/kobo-settings/token", method="PUT", cookie=ha, hdrs=A); ktok = json.loads(b).get("token", "") +lan(GMH, "/api/v1/koreader-users/me", method="PUT", cookie=ha, hdrs=A, data=json.dumps({"username": "korolvaso", "password": kpw})) +lan(GMH, "/api/v1/koreader-users/me/sync?enabled=true", method="PATCH", cookie=ha, hdrs=A) +say(" (setup as the household through the gate: OPDS on + an OPDS user, a Kobo token, a KOReader user — values not printed)") +md5 = hashlib.md5(kpw.encode()).hexdigest() +def row(label, path, *a, via="LAN", hd=(), timeout=40): + c, h, b = lan(GMH, path, *a, hdrs=hd, timeout=timeout) if via == "LAN" else tun(GMH, path, "198.51.100.66", None, *a, *sum([["-H", x] for x in hd], []), timeout=timeout) + say(f" {label:52s} {via:6s} -> {c} {'GATE' if is_gate(c, h, b) else 'app'} {b[:60].strip()!r}") +row("OPDS, the OPDS user's own login", "/api/v1/opds", "-u", f"olvaso:{opw}") +row("OPDS, the OPDS user's own login", "/api/v1/opds", "-u", f"olvaso:{opw}", via="tunnel") +row("OPDS, a stranger with a wrong password", "/api/v1/opds", "-u", "olvaso:wrong", via="tunnel") +row("OPDS, a stranger with nothing", "/api/v1/opds", via="tunnel") +row("Kobo initialization, the device token", f"/api/kobo/{ktok}/v1/initialization") +row("Kobo, a made-up token", "/api/kobo/0123456789abcdef/v1/library/sync", via="tunnel") +row("KOReader sign-in, its own user + md5 key", "/api/koreader/users/auth", hd=("x-auth-user: korolvaso", f"x-auth-key: {md5}", "Accept: application/vnd.koreader.v1+json")) +row("KOReader, a stranger's wrong key", "/api/koreader/users/auth", hd=("x-auth-user: korolvaso", "x-auth-key: 00000000000000000000000000000000", "Accept: application/vnd.koreader.v1+json"), via="tunnel") +row("Komga API, a stranger with nothing", "/komga/api/v1/libraries", via="tunnel") +row("look-alike /api/v1/opdsx (F1)", "/api/v1/opdsx", via="tunnel") +row("look-alike /api/koreaderx", "/api/koreaderx/users/auth", via="tunnel") +row("../ out of an exception", "/api/v1/opds/../../api/v1/books", "--path-as-is", via="tunnel") +say("## R-775 re-measured — a stranger cannot reach Grimmory's web sign-in at all, so its 15-minute lock cannot be aimed from outside") +seq = [tun(GMH, "/api/v1/auth/login", "198.51.100.66", None, "-X", "POST", "-H", "Content-Type: application/json", "--data", '{"username":"admin","password":"guess"}')[0] for _ in range(6)] +c, h, b = lan(GMH, "/api/v1/auth/login", method="POST", cookie=ha, hdrs=("Content-Type: application/json",), data=json.dumps({"username": "admin", "password": gm["admin_pw"]})) +say(f" stranger x6 at /api/v1/auth/login (tunnel): {seq} -> the household then signs in: {c}") + +say("## item 5 — the family login and the box dashboard") +c, s4, h, b = family_login("anna", fam["anna"]) +c1, h1, b1 = lan(CTL, "/launcher", cookie=f"{s4}; {ga}") +c2, h2, b2 = lan(CTL, "/api/stacks", cookie=s4) +c3, h3, b3 = lan(CTL, "/login", method="POST", data=f"password={fam['anna']}", hdrs=("Content-Type: application/x-www-form-urlencoded",)) +say(f" family cookies at /launcher -> {c1} {loc(h1)} | at /api/stacks -> {c2} | anna's family password at the dashboard login -> {c3}, session {'SET!' if 'felhom_session=' in h3 else 'none'}") + +say("## reset and removal end access at the next request (every gated app)") +c, s5, h, b = family_login("bela", fam["bela"]); g5 = app_cookie("library", s5)[0]; m5 = app_cookie("video", s5)[0] +sess = dash_session(); csrf = open(f"{SC}/csrf{os.getpid()}.txt").read().strip() +def card(action, name): + r = subprocess.run(["curl", "-sk", "-H", f"Host: {CTL}", "-H", f"Cookie: {sess}", "-H", f"X-CSRF-Token: {csrf}", "-X", "POST", + "--data", f"name={name}", f"{w.BASE}/family/members/{action}"], capture_output=True, text=True) + return json.loads(r.stdout) +d = card("reset", "bela"); fam["bela"] = d["data"]["password"] +say(f" card: new password for bela -> ok={d['ok']}; bela's Grimmory cookie -> {lan(GMH, '/', cookie=g5, hdrs=('Accept: text/html',))[0]}, MeTube cookie -> {lan(MTH, '/', cookie=m5, hdrs=('Accept: text/html',))[0]} (302 = sent to sign in)") +c, s6, h, b = family_login("bela", fam["bela"]); g6 = app_cookie("video", s6)[0] +d = card("remove", "bela") +say(f" card: remove bela -> ok={d['ok']} members={d['data']['members']}; bela's MeTube cookie -> {lan(MTH, '/', cookie=g6)[0]}; bela signs in again -> {family_login('bela', fam['bela'])[0]}") +json.dump(fam, open(f"{SC}/fam.json", "w")) + +say("## cost — the same request through the gated name, and straight at the container (60 pairs)") +def t(cmd): + r = subprocess.run(cmd, capture_output=True, text=True).stdout.split(); return r[0], float(r[1]) * 1000 +g, d = [], [] +for _ in range(60): + c1, t1 = t(["curl", "-sk", "-o", "/dev/null", "-w", "%{http_code} %{time_total}", "-H", f"Host: {MTH}", "-H", f"Cookie: {mt}", f"{w.BASE}/version"]) + g.append(t1) +ip = w.guest("docker inspect metube --format '{{range .NetworkSettings.Networks}}{{.IPAddress}} {{end}}'").split()[0] +dd = w.guest(f"for i in $(seq 1 60); do curl -s -o /dev/null -w '%{{time_total}}\\n' http://{ip}:8081/version; done").split() +say(f" gated (LAN→traefik→forwardAuth→app) median {statistics.median(g):.1f} ms, p90 {sorted(g)[54]:.1f} ms; the app straight (guest→container) median {statistics.median([float(x)*1000 for x in dd]):.1f} ms — the honest number is the gate's own: see the controller's forwardAuth timing below") +say("## the controller DOWN — a gated app answers an error, never the app; an exception still reaches the app's own login") +w.guest("docker stop felhom-controller >/dev/null") +c1, h1, b1 = lan(MTH, "/", cookie=mt); c2, h2, b2 = lan(GMH, "/api/v1/books", cookie=ga); c3, h3, b3 = lan(GMH, "/api/v1/opds", "-u", "olvaso:wrong") +say(f" controller stopped: MeTube with a member cookie -> {c1} {b1[:40]!r}; Grimmory API -> {c2}; Grimmory OPDS exception (wrong password) -> {c3} (the app's own refusal)") +w.guest("docker start felhom-controller >/dev/null; sleep 25") +w.login() +c1, h1, b1 = lan(MTH, "/", cookie=mt) +say(f" controller back: MeTube with bela's earlier cookie (bela was removed) -> {c1} {'GATE' if is_gate(c1,h1,b1) else 'APP'}") diff --git a/documentation/audits/family-gate-2026-10-02/tools/fg_life.py b/documentation/audits/family-gate-2026-10-02/tools/fg_life.py new file mode 100644 index 00000000..611cb8dd --- /dev/null +++ b/documentation/audits/family-gate-2026-10-02/tools/fg_life.py @@ -0,0 +1,299 @@ +#!/usr/bin/env python3 +"""fg_life.py — Grimmory's and MeTube's record rows on scratch 9202 (drill catalog, controller 0.287.0), behind the +family gate, through the product's own endpoints. The family gate is passed as the household (box_walk.family_cookie); +strangers go through traefik with no cookie. Secrets never printed; seed tokens in a 0600 scratch file. Env: SC, EV. + + grim-seed — Grimmory: a library + a real EPUB through its own API (the admin the household made at setup), read back. + metube-fresh — MeTube removed (with its data), the DRILL template moved back to 2026.09.28, a FRESH install with the + memory sampler and a stranger's poll from the press; the seed (a download) and its read-back; checks. + metube-step — the product's guarded Update 2026.09.28 -> 2026.09.29 (drill ladder entry), the download read back. + life — the night chain; both apps removed KEEPING backups (family-gate files go); the restore button for each; + the seeds read back; the family-gate files and the stranger's refusal come back (the R-773 lesson). + final — both removed WITH their data and backups; nothing left. +Evidence: $EV/B/box/.txt (+ mem-metube.csv, poll-metube.txt, box-verdict-metube.json).""" +import json, os, re, secrets, subprocess, sys, time +sys.path.insert(0, os.path.dirname(os.path.abspath(__file__))) +from fg import * # noqa: F401,F403 (lan, tun, w, DOM, CTL) +import upgrade_fixtures_box as fixtures + +phase = sys.argv[1] +EVD = f"{w.EV}/B/box"; os.makedirs(EVD, exist_ok=True) +TOK = w.SC + "/seed-tokens-fg.json" +D = "/mnt/5_hdd/felhom.eu/drill/app-catalog-drill" +SAMPLER = open("/mnt/5_hdd/felhom.eu/git/felhom.eu/documentation/audits/new-apps-2026-10-01/tools/sampler.sh").read() +log = open(f"{EVD}/{phase}.txt", "a", buffering=1) +GM, MT = fixtures.FIXTURES["grimmory"], fixtures.FIXTURES["metube"] +APPS = {"grimmory": ("library", GM), "metube": ("video", MT)} + + +def say(*a): + w.say(*a); log.write(" ".join(map(str, a)) + "\n") + + +def toks(): + return json.load(open(TOK)) if os.path.exists(TOK) else {} + + +def save(t): + json.dump(t, open(TOK, "w")); os.chmod(TOK, 0o600) + + +def stranger(sub, path="/"): + c1 = lan(f"{sub}.{DOM}", path)[0] + c2 = tun(f"{sub}.{DOM}", path, "198.51.100.77")[0] + return f"LAN {c1} | tunnel {c2}" + + +def gate_files(): + return w.guest("ls /opt/docker/stacks/traefik/dynamic/ | grep -E 'family-gate|setup-gate' | tr '\\n' ' '").strip() or "none" + + +def ctlver(): + return w.guest("docker inspect felhom-controller --format {{.Config.Image}}").strip() + + +def drill_set_metube(ref, entry=None): + subprocess.run(["git", "-C", D, "pull", "-q", "--rebase", "origin", "main"], check=True) + comp, fy = f"{D}/templates/metube/docker-compose.yml", f"{D}/templates/metube/.felhom.yml" + s = re.sub(r"(image:\s*)ghcr\.io/alexta69/metube:\S+", r"\g<1>" + ref, open(comp).read()) + open(comp, "w").write(s) + if entry: + f = open(fy).read() + f = (f.rstrip("\n") + "\n - " + json.dumps(entry) + "\n") if "update_ladder:" in f else (f.rstrip("\n") + "\nupdate_ladder:\n - " + json.dumps(entry) + "\n") + open(fy, "w").write(f) + if subprocess.run(["git", "-C", D, "diff", "--quiet"]).returncode == 0: + say(f"drill: already pins {ref} — nothing to commit") + return + subprocess.run(["git", "-C", D, "commit", "-q", "-am", f"DRILL metube: {ref} (box proof, family gate 2026-10-02)"], check=True) + subprocess.run(["git", "-C", D, "push", "-q", "origin", "main"], check=True, capture_output=True) + say("drill:", subprocess.run(["git", "-C", D, "log", "--oneline", "-1"], capture_output=True, text=True).stdout.strip()) + + +def remove(app, data): + w.ctl("POST", f"/api/stacks/{app}/stop") + for _ in range(24): + time.sleep(5) + if w.stack(app).get("state") != "running": + break + code, d = w.ctl("POST", f"/api/stacks/{app}/remove", {"remove_hdd_data": data, "remove_backups": data}) + say(f"remove {app} ({'WITH data and backups' if data else 'KEEPING data and backups'}) -> {code} {str(d)[:260]}") + if data and code == "409": + # R-442's fail-closed guard: 9202 has no agent (`/api/disks` -> agent not configured), so the drive path never + # resolves and "remove with data" is REFUSED, the app kept. Measured here, then the household's other choice is + # taken and this harness tidies ITS OWN test folder by name (scratch guest; the with-data half is a demo-box row). + code, d = w.ctl("POST", f"/api/stacks/{app}/remove", {"remove_hdd_data": False, "remove_backups": True}) + say(f" refused (R-442, no agent on 9202) — remove {app} KEEPING drive data -> {code} {str(d)[:200]}") + w.guest(f"rm -rf /mnt/felhom-drives/scratch_hdd/userdata/{app}") + say(f" the harness removed its own test folder /mnt/felhom-drives/scratch_hdd/userdata/{app} by hand") + time.sleep(6) + left = w.guest(f"ls -d /opt/docker/stacks/{app} 2>/dev/null; docker ps -a --format '{{{{.Names}}}}' | grep -E '^{app}'; " + f"docker volume ls -q | grep -i {app}; ls -d /mnt/felhom-drives/*/userdata/{app} 2>/dev/null").strip() + say(f" left after: {' | '.join(left.splitlines()) or 'nothing'}") + return code + + +w.login() +say(f"##### {phase} on 9202 — controller {ctlver()} | catalog " + + w.guest("cd /var/lib/docker/volumes/felhom-controller-data/_data/data/catalog-cache && git log --oneline -1").strip()[:60] + + f" | {time.strftime('%FT%TZ', time.gmtime())}") + +if phase == "grim-seed": + t = toks() + pw = json.load(open(f"{w.SC}/gm.json"))["admin_pw"] + code, tok = GM._token(w, "library", "admin", pw) + auth = ["-H", f"Authorization: Bearer {tok}"] + rc, code, out = w.app_curl("library", "/api/v1/libraries", *auth, "-H", "Content-Type: application/json", + data=json.dumps({"name": "Konyvek", "icon": "book", "iconType": "LUCIDE", "paths": [{"path": "/books"}], + "watch": True, "metadataSource": "EMBEDDED", "organizationMode": "BOOK_PER_FILE"}), method="POST") + lib = json.loads(out) if code in ("200", "201") else {} + if not lib: # a library from an earlier run + rc, code, out = w.app_curl("library", "/api/v1/libraries", *auth) + lib = (json.loads(out) or [{}])[0] + lid, pid = lib["id"], lib["paths"][0]["id"] + title = "Felhom Csalad " + secrets.token_hex(4) + import tempfile + fn = tempfile.mktemp(suffix=".epub"); open(fn, "wb").write(GM._epub(title)) + rc, code, out = w.app_curl("library", "/api/v1/files/upload", *auth, "-F", f"file=@{fn};type=application/epub+zip", + "-F", f"libraryId={lid}", "-F", f"pathId={pid}", method="POST") + os.unlink(fn) + say(f"grimmory: library {lid}, the household uploads an EPUB through its own API (behind the family gate) -> {code}") + t["grimmory"] = {"user": "admin", "pw": pw, "title": title} + save(t) + say("grimmory: the seed reads back:", GM.verify(w, "library", t["grimmory"], say)) + say("2.2/2.8 the book on the household's drive:", " | ".join(w.guest( + "find /mnt/felhom-drives -path '*userdata*grimmory*' -name '*.epub' 2>/dev/null | head -3").strip().splitlines()) or "NOT FOUND") + say("2.7 sizes:", " ".join(w.guest("for v in $(docker volume ls -q | grep -i grimmory); do echo $v=$(du -sh $(docker volume inspect -f '{{.Mountpoint}}' $v) | cut -f1); done; " + "du -sh /mnt/felhom-drives/*/userdata/grimmory 2>/dev/null").split())) + +elif phase == "metube-fresh": + if w.stack("metube").get("deployed"): + remove("metube", True) + drill_set_metube("ghcr.io/alexta69/metube:2026.09.28") + w.sync_rescan() + for _ in range(24): + if "2026.09.28" in w.guest("grep image: /var/lib/docker/volumes/felhom-controller-data/_data/data/catalog-cache/templates/metube/docker-compose.yml"): + break + time.sleep(5); w.sync_rescan() + say("the box's catalog now pins:", w.guest("grep image: /var/lib/docker/volumes/felhom-controller-data/_data/data/catalog-cache/templates/metube/docker-compose.yml").strip()) + say("guest swap:", " ".join(w.guest("free -m | grep -i swap").split()), "(the swap-0 venue for 5.1 is the bench)") + w.guest(f"cat > /root/fg-sampler.sh <<'SEOF'\n{SAMPLER}\nSEOF\nrm -f /root/fg-mem-metube.csv.stop; " + "(NAMES='metube' OUT=/root/fg-mem-metube.csv DUR=1500 nohup bash /root/fg-sampler.sh >/dev/null 2>&1 &)") + w.guest(f"""cat > /root/fg-poll.sh <<'PEOF' +out=/root/fg-poll-metube.txt; : > $out; t0=$(date +%s) +while [ $(( $(date +%s) - t0 )) -lt 240 ]; do + a=$(curl -sk --max-time 4 -o /dev/null -w '%{{http_code}}' -H 'Host: video.{DOM}' https://127.0.0.1/) + b=$(curl -sk --max-time 4 -o /dev/null -w '%{{http_code}}' -H 'Host: video.{DOM}' -H 'Content-Type: application/json' -X POST --data '{{"url":"https://example.com/x.mp4"}}' https://127.0.0.1/add) + echo "$(date +%H:%M:%S) $a $b" >> $out; sleep 1 +done +PEOF +(nohup sh /root/fg-poll.sh >/dev/null 2>&1 &)""") + t0 = time.time(); ok = w.deploy("metube", "video"); say(f"deploy metube -> {ok} ({round(time.time()-t0)} s to `deployed`)") + say(" gate files right after the deploy:", gate_files()) + healthy = None + for _ in range(150): + h = w.guest("docker inspect -f '{{if .State.Health}}{{.State.Health.Status}}{{else}}nohc{{end}}' metube 2>/dev/null").strip() + if h == "healthy": + healthy = time.time() - t0; break + time.sleep(2) + say(f"4.3 healthy {round(healthy) if healthy else 'NEVER'} s after the press; restarts:", + w.guest("docker inspect -f '{{.RestartCount}}' metube").strip(), "| state:", w.stack("metube").get("state"), + "| image:", w.guest("docker inspect -f '{{.Config.Image}}' metube").strip()) + t = toks(); t["metube"] = MT.seed(w, "video", say); save(t) + say("C1 the seed reads back:", MT.verify(w, "video", t["metube"], say) if t["metube"] else "NO SEED " + str(getattr(MT, "tried", ""))) + time.sleep(3); w.guest("pkill -f fg-poll.sh || true") + poll = w.guest("cat /root/fg-poll-metube.txt").strip().splitlines() + codes = {} + for l in poll: + k = " ".join(l.split(" ")[1:3]); codes[k] = codes.get(k, 0) + 1 + say(f"3.3/3.5 a stranger's {len(poll)} polls (GET / and POST /add, 1/s) from the press, no cookie: codes {codes}; " + f"any app answer (200): {any(' 200' in l for l in poll)}") + open(f"{EVD}/poll-metube.txt", "w").write("\n".join(poll) + "\n") + say("2.2/2.8 where the download landed (the household's drive, browsable):", " | ".join(w.guest( + "find /mnt/felhom-drives -path '*userdata*' -path '*metube*' -name '*.mp4' 2>/dev/null | head -3").strip().splitlines()) or "NOT FOUND", + "| owner:", w.guest("find /mnt/felhom-drives -path '*userdata*' -path '*metube*' -name '*.mp4' -exec stat -c '%u:%g %a' {} \\; 2>/dev/null | head -1").strip()) + say("2.7 sizes:", " ".join(w.guest("for v in $(docker volume ls -q | grep -i metube); do echo $v=$(du -sh $(docker volume inspect -f '{{.Mountpoint}}' $v) | cut -f1); done; " + "du -sh /mnt/felhom-drives/*/userdata/metube 2>/dev/null").split())) + say("1.5 process:", w.guest("docker exec metube sh -c 'cat /proc/1/cmdline | tr \"\\000\" \" \"'").strip()[:200], + "| children:", " ".join(w.guest("docker exec metube ps -eo user,comm 2>/dev/null | sort -u | head -8").split())[:200]) + rc, code, body = w.app_curl("video", "/felhom-no-such-page-xyz/") + say(f"1.8 unknown page (a member): http={code} traceback={'Traceback' in body}") + say("0.4 outbound connections held at rest:", " ".join(w.guest( + "docker exec metube sh -c \"cat /proc/net/tcp /proc/net/tcp6 2>/dev/null\" | awk 'NR>1 && $4==\"01\"{print $3}' | sort -u | head -8").split()) or "none") + say("0.4/0.5 hosts named in the first-start log:", sorted(set(re.findall(r"https?://([a-z0-9.-]+\.[a-z]{2,})", w.guest("docker logs metube 2>&1 | head -300"))))[:12]) + say("4.4 negative control: docker STOP metube — state before:", w.stack("metube").get("state")) + w.guest("docker stop metube >/dev/null"); seen = [] + for i in range(12): + time.sleep(5); s = w.stack("metube").get("state") + if not seen or seen[-1][1] != s: seen.append((5 * (i + 1), s)) + say(f" states while stopped (s, state): {seen}; a member at / -> {w.app_curl('video', '/')[1]}") + w.guest("docker start metube >/dev/null") + for i in range(36): + time.sleep(5) + if w.stack("metube").get("state") == "running": + say(f" running again {5*(i+1)} s after docker start"); break + +elif phase == "metube-step": + t = toks() + before = (w.stack("metube").get("app_config") or {}).get("pinned_images") + say(f"before: pinned={before}") + entry = {"from": {"metube": "ghcr.io/alexta69/metube:2026.09.28"}, "to": {"metube": "ghcr.io/alexta69/metube:2026.09.29"}, + "verdict": "proven", "tested_at": "DRILL", "harness_version": 4, "evidence": "DRILL (box proof in progress)", + "marks": {"files_may_change": False, "needs_person": None, "memory_tight": False}} + drill_set_metube("ghcr.io/alexta69/metube:2026.09.29", entry) + w.sync_rescan("metube", "ghcr.io/alexta69/metube:2026.09.29") + since = w.guest("date -u +%Y-%m-%dT%H:%M:%SZ").strip() + res = w.press_update("metube", poll=1, cap_s=1800) + for p in res.get("phases", []): + log.write(f" phase +{p['t']}s {p['phase']} | err={p['error']}\n") + time.sleep(10) + read = MT.verify(w, "video", t["metube"], say) + log.write(w.guest(f"docker logs --since {since} felhom-controller 2>&1 | grep -E 'update metube' | grep -v DEBUG | cut -c1-300") + "\n") + st = w.stack("metube"); after = (st.get("app_config") or {}).get("pinned_images") + say(" gate files after the step:", gate_files(), "| a stranger at / ->", stranger("video")) + verdict = {"app": "metube", "venue": f"box 9202 (drill catalog, controller {ctlver().rsplit(':', 1)[-1]}), the product's guarded Update", + "from": before, "to": after, + "verdict": "proven" if (res.get("final_phase") == "done" and read and (after or {}).get("metube") == "ghcr.io/alexta69/metube:2026.09.29") else "failed", + "seed_read_before": True, "seed_read_after": read, "healthy_after": st.get("state") == "running", + "duration_s": res.get("duration_s"), "final_phase": res.get("final_phase"), "measured_at": since, + "evidence": "felhom.eu/documentation/audits/family-gate-2026-10-02/B/box/metube-step.txt"} + json.dump(verdict, open(f"{EVD}/box-verdict-metube.json", "w"), indent=2) + say(f"RESULT final_phase={res.get('final_phase')} after={after} seed_after={read} verdict={verdict['verdict']} ({res.get('duration_s')} s)") + +elif phase == "grim-step": + # 9202's Grimmory was installed at v3.4.1 (the drill template kept yesterday's FROM); step it to the published v3.5.0 + # through the product's guarded Update, behind the family gate, and read the seed back. + t = toks() + before = (w.stack("grimmory").get("app_config") or {}).get("pinned_images") + say(f"before: pinned={before}") + subprocess.run(["git", "-C", D, "pull", "-q", "--rebase", "origin", "main"], check=True) + comp, fy = f"{D}/templates/grimmory/docker-compose.yml", f"{D}/templates/grimmory/.felhom.yml" + src = open(comp).read() # read BEFORE opening for write — the one-line form truncated the file first (attempt 1) + assert "grimmory:v3.4.1" in src, "drill compose is not at v3.4.1" + open(comp, "w").write(src.replace("grimmory:v3.4.1", "grimmory:v3.5.0")) + entry = {"from": {"grimmory": "ghcr.io/grimmory-tools/grimmory:v3.4.1", "grimmory-db": "mariadb:11.4"}, + "to": {"grimmory": "ghcr.io/grimmory-tools/grimmory:v3.5.0", "grimmory-db": "mariadb:11.4"}, + "verdict": "proven", "tested_at": "DRILL", "harness_version": 4, "evidence": "DRILL (box proof in progress)", + "marks": {"files_may_change": False, "needs_person": None, "memory_tight": False}} + f = open(fy).read() + f = (f.rstrip("\n") + "\n - " + json.dumps(entry) + "\n") if "update_ladder:" in f else (f.rstrip("\n") + "\nupdate_ladder:\n - " + json.dumps(entry) + "\n") + open(fy, "w").write(f) + subprocess.run(["git", "-C", D, "commit", "-q", "-am", "DRILL grimmory: v3.5.0 (box step behind the family gate, 2026-10-02)"], check=True) + subprocess.run(["git", "-C", D, "push", "-q", "origin", "main"], check=True, capture_output=True) + say("drill:", subprocess.run(["git", "-C", D, "log", "--oneline", "-1"], capture_output=True, text=True).stdout.strip()) + w.sync_rescan("grimmory", "ghcr.io/grimmory-tools/grimmory:v3.5.0") + res = w.press_update("grimmory", poll=1, cap_s=1800) + for p in res.get("phases", []): + log.write(f" phase +{p['t']}s {p['phase']} | err={p['error']}\n") + time.sleep(10) + read = GM.verify(w, "library", t["grimmory"], say) + after = (w.stack("grimmory").get("app_config") or {}).get("pinned_images") + say(" gate files after the step:", gate_files(), "| a stranger at / ->", stranger("library"), + "| OPDS exception, a stranger ->", stranger("library", "/api/v1/opds")) + say(f"RESULT final_phase={res.get('final_phase')} after={after} seed_after={read} ({res.get('duration_s')} s)") + +elif phase == "life": + t = toks() + say("before: gate files:", gate_files()) + code, d = w.ctl("POST", "/api/debug/backup/night-chain"); say("2.5 the night chain (debug action) ->", code) + for _ in range(120): + if "finished in" in w.guest("docker logs --since 10m felhom-controller 2>&1 | grep 'night-chain\\] finished' | tail -1"): + break + time.sleep(5) + say(" ", w.guest("docker logs --since 10m felhom-controller 2>&1 | grep 'night-chain\\] finished' | tail -1").strip()[:200]) + for app in APPS: + say(f" {app} restore points:", [(s.get("id") or s.get("snapshot_id"), s.get("time") or s.get("created")) for s in w.snapshots(app)][:2]) + for app in APPS: + remove(app, False) + time.sleep(8) + say("after both removes: gate files:", gate_files(), "| a stranger at library/ ->", stranger("library"), "| video/ ->", stranger("video")) + for app, (sub, fx) in APPS.items(): + r = w.restore(app) + say(f"2.5 restore {app}:", {k: r.get(k) for k in ("ok", "snapshot_id", "seconds", "state_after", "hold_after")}) + time.sleep(10) + say("after both restores: gate files:", gate_files()) + say(" family_gate record:", " | ".join(" ".join(w.guest(f"grep -A4 '^family_gate:' /opt/docker/stacks/{a}/app.yaml").split()) for a in APPS)) + say(" a STRANGER (no cookie): library/ ->", stranger("library"), "| library/api/v1/books ->", stranger("library", "/api/v1/books"), + "| video/ ->", stranger("video"), "| video/history ->", stranger("video", "/history")) + for app, (sub, fx) in APPS.items(): + say(f"2.5 {app}: the seed reads back after remove + restore (a member, then the app's own login):", fx.verify(w, sub, t[app], say)) + +elif phase == "final": + for app in APPS: + remove(app, True) + time.sleep(8) + say("gate files:", gate_files()) + w.guest("touch /root/fg-mem-metube.csv.stop"); time.sleep(3) + csv = w.guest("cat /root/fg-mem-metube.csv 2>/dev/null").strip().splitlines() + if csv: + open(f"{EVD}/mem-metube.csv", "w").write("epoch,name,id,anon,current,swap,oom_kill,limit,restarts\n" + "\n".join(csv) + "\n") + per = {} + for l in csv: + p = l.split(",") + if len(p) >= 9 and p[3].isdigit(): + d = per.setdefault((p[1], p[2]), {"anon": 0, "oom": 0, "lim": p[7], "n": 0}) + d["anon"] = max(d["anon"], int(p[3])); d["n"] += 1 + d["oom"] = max(d["oom"], int(p[6]) if p[6].isdigit() else 0) + for (n, i), d in per.items(): + say(f"5.1 (box) {n} [{i}] {d['n']} samples: peak anon {d['anon']/1048576:.1f} MiB = " + f"{100.0*d['anon']/int(d['lim']) if d['lim'].isdigit() else -1:.1f} % of the limit; oom_kill {d['oom']}") + w.guest("rm -f /root/fg-sampler.sh /root/fg-poll.sh /root/fg-poll-metube.txt /root/fg-mem-metube.csv /root/fg-mem-metube.csv.stop") diff --git a/documentation/audits/family-gate-2026-10-02/tools/fg_setup.py b/documentation/audits/family-gate-2026-10-02/tools/fg_setup.py new file mode 100644 index 00000000..c4838326 --- /dev/null +++ b/documentation/audits/family-gate-2026-10-02/tools/fg_setup.py @@ -0,0 +1,37 @@ +#!/usr/bin/env python3 +"""fg_setup.py — on 9202 (controller 0.287.0, drill catalog): two family members through the dashboard's Család card +(POST /family/members/add — the card's own call), then MeTube and Grimmory installed through the product, and Grimmory's +first admin made by the household through the SETUP gate (it is setup-gated AND family-gated). Secrets → 0600 scratch.""" +import json, os, re, subprocess, sys, time +from fg import * +SC = w.SC +log = open(f"{w.EV}/setup.txt", "a", buffering=1) +def say(*a): + w.say(*a); log.write(" ".join(map(str, a)) + "\n") +w.login() +say(f"##### family gate live setup on 9202 — controller {w.guest('docker inspect felhom-controller --format {{.Config.Image}}').strip()} | {time.strftime('%FT%TZ', time.gmtime())}") +sess = dash_session(); csrf = open(f"{SC}/csrf{os.getpid()}.txt").read().strip() +fam = {} +for name in ("anna", "bela"): + r = subprocess.run(["curl", "-sk", "-H", f"Host: {CTL}", "-H", f"Cookie: {sess}", "-H", f"X-CSRF-Token: {csrf}", + "-X", "POST", "--data", f"name={name}", f"{w.BASE}/family/members/add"], capture_output=True, text=True) + d = json.loads(r.stdout) + fam[name] = d["data"]["password"] + say(f"Család card: add {name} -> ok={d['ok']} members={d['data']['members']} password returned ONCE (len {len(fam[name])}, not printed)") +r = subprocess.run(["curl", "-sk", "-H", f"Host: {CTL}", "-H", f"Cookie: {sess}", f"{w.BASE}/settings/security"], capture_output=True, text=True) +say("the security page carries the card:", 'id="family-card"' in r.stdout, "| and no member password:", not any(p in r.stdout for p in fam.values())) +json.dump(fam, open(f"{SC}/fam.json", "w")); os.chmod(f"{SC}/fam.json", 0o600) +for app, sub in (("metube", "video"), ("grimmory", "library")): + say(f"deploy {app} ->", w.deploy(app, sub)) + say(f" {app} family-gate file:", w.guest(f"ls /opt/docker/stacks/traefik/dynamic/ | grep -E 'family-gate-{app}|setup-gate-{app}' | tr '\\n' ' '")) +w.wait_app("library", "/api/v1/healthcheck", want=("200",), tries=90) +pw = "Gm-" + __import__("secrets").token_hex(10) +c, h, b = lan(f"library.{DOM}", "/api/v1/setup", method="POST", cookie=w.gate_cookie("library"), + hdrs=("Content-Type: application/json",), data=json.dumps({"username": "admin", "password": pw, "email": "admin@felhom.invalid", "name": "Felhom"})) +say("grimmory: the household makes the first admin THROUGH THE SETUP GATE ->", c) +json.dump({"admin_pw": pw}, open(f"{SC}/gm.json", "w")); os.chmod(f"{SC}/gm.json", 0o600) +for i in range(40): + files = w.guest("ls /opt/docker/stacks/traefik/dynamic/ | grep -E 'grimmory' | tr '\\n' ' '") + if "setup-gate-grimmory" not in files: break + time.sleep(5) +say(f"grimmory: setup gate opened by its probe after ~{5*i} s; files now: {files}") diff --git a/documentation/audits/family-gate-2026-10-02/tools/repoint.py b/documentation/audits/family-gate-2026-10-02/tools/repoint.py new file mode 100644 index 00000000..a49b4b42 --- /dev/null +++ b/documentation/audits/family-gate-2026-10-02/tools/repoint.py @@ -0,0 +1,55 @@ +#!/usr/bin/env python3 +"""repoint.py drill|restore — point scratch guest 9202 at the DRILL catalog, or back at the live one (`09` §6.5; R-615: +the cache directory goes too). Saved config: controller.yaml.pre-familygate1002. Three controls printed each way.""" +import io, re, subprocess, sys +import walk as w +VOL = "/var/lib/docker/volumes/felhom-controller-data/_data" +SAVE = f"{VOL}/controller.yaml.pre-familygate1002" +DRILL_REPO = "https://gitea.dooplex.hu/admin/app-catalog-drill.git" + + +def creds(): + for l in io.open("/home/kisfenyo/.git-credentials").read().strip().split("\n"): + m = re.match(r'https://(admin):([^@]+)@gitea\.dooplex\.hu', l) + if m: + return m.group(1), m.group(2) + raise SystemExit("no admin credential") + + +def controls(want): + print("CONTROL 1 — the box's clone:", w.guest(f"cd {VOL}/data/catalog-cache && git log --oneline -1 && git remote get-url origin | sed 's#//[^@]*@#//#'").strip()) + print("CONTROL 2 — live main:", subprocess.run(["git", "ls-remote", "https://gitea.dooplex.hu/admin/app-catalog-felhom.eu.git", "refs/heads/main"], capture_output=True, text=True).stdout.split()[:1]) + print("CONTROL 3 — expected:", want) + + +if sys.argv[1] == "drill": + u, t = creds() + print(w.guest(f""" +set -e +test -f {SAVE} || cp -p {VOL}/controller.yaml {SAVE} +python3 - <<'PY' +import re +p = "{VOL}/controller.yaml" +s = open(p).read() +s = re.sub(r'(^\\s+repo_url: ).*$', r'\\g<1>{DRILL_REPO}', s, count=1, flags=re.M) +s = re.sub(r'(^git:(?:\\n\\s+.*)*?\\n\\s+token: ).*$', r'\\g<1>"{t}"', s, count=1, flags=re.M) +s = re.sub(r'(^git:(?:\\n\\s+.*)*?\\n\\s+username: ).*$', r'\\g<1>"{u}"', s, count=1, flags=re.M) +open(p, "w").write(s) +PY +rm -rf {VOL}/catalog-cache {VOL}/data/catalog-cache +docker restart felhom-controller >/dev/null +sleep 20 +grep -A6 '^git:' {VOL}/controller.yaml | sed 's/token:.*/token: /' +""")) +else: + print(w.guest(f""" +set -e +cp -p {SAVE} {VOL}/controller.yaml +rm -rf {VOL}/catalog-cache {VOL}/data/catalog-cache +docker restart felhom-controller >/dev/null +sleep 20 +grep -A6 '^git:' {VOL}/controller.yaml | sed 's/token:.*/token: /' +""")) +w.login() +w.sync_rescan() +controls(sys.argv[1]) diff --git a/documentation/audits/family-gate-2026-10-02/tools/shots/familylogin.js b/documentation/audits/family-gate-2026-10-02/tools/shots/familylogin.js new file mode 100644 index 00000000..52dafaf9 --- /dev/null +++ b/documentation/audits/family-gate-2026-10-02/tools/shots/familylogin.js @@ -0,0 +1,15 @@ +// The family sign-in page a stranger (or a member without a session) meets at a family-gated app (checklist 8.3, the +// third screenshot of Grimmory and MeTube): headless Chrome through the box's own traefik, every name mapped to it. +const puppeteer = require('puppeteer'); +const sleep = ms => new Promise(r => setTimeout(r, ms)); +(async () => { + const b = await puppeteer.launch({args: ['--no-sandbox', '--ignore-certificate-errors', '--lang=' + (process.env.LANGX || 'hu-HU'), + '--host-resolver-rules=MAP * ' + process.env.TRAEFIK]}); + const p = await b.newPage(); + await p.setViewport({width: 390, height: 844, deviceScaleFactor: 2}); + await p.goto(process.env.URL, {waitUntil: 'networkidle2'}); + await sleep(1500); + console.log(p.url().replace(/\?.*/, '?…')); + await p.screenshot({path: '/out/3.png'}); + await b.close(); +})().catch(e => { console.error(e); process.exit(1); }); diff --git a/documentation/audits/family-gate-2026-10-02/tools/shots/grimmory.js b/documentation/audits/family-gate-2026-10-02/tools/shots/grimmory.js new file mode 100644 index 00000000..38705131 --- /dev/null +++ b/documentation/audits/family-gate-2026-10-02/tools/shots/grimmory.js @@ -0,0 +1,23 @@ +// Screenshots of Grimmory's own web UI (checklist 8.3): on scratch guest 9202, through traefik over https as a browser +// does (headless Chrome), signed in as the test household account. +const puppeteer = require('puppeteer'); +const sleep = ms => new Promise(r => setTimeout(r, ms)); +(async () => { + const b = await puppeteer.launch({args: ['--no-sandbox', '--ignore-certificate-errors', '--lang=hu-HU']}); + const p = await b.newPage(); + await p.setViewport({width: 1280, height: 800}); + await p.goto(process.env.URL + '/login', {waitUntil: 'networkidle2'}); + await sleep(1500); + await p.screenshot({path: '/out/3.png'}); + const user = await p.$('input[type=text], input[name=username], input[formcontrolname=username]'); + if (user) await user.type(process.env.U); + await p.type('input[type=password]', process.env.P); + await p.keyboard.press('Enter'); + await p.waitForNavigation({waitUntil: 'networkidle2', timeout: 30000}).catch(() => {}); + await sleep(5000); + await p.screenshot({path: '/out/1.png'}); + await p.setViewport({width: 390, height: 844, deviceScaleFactor: 2}); + await sleep(2500); + await p.screenshot({path: '/out/2.png'}); + await b.close(); +})().catch(e => { console.error(e); process.exit(1); }); diff --git a/documentation/audits/family-gate-2026-10-02/tools/shots/metube.js b/documentation/audits/family-gate-2026-10-02/tools/shots/metube.js new file mode 100644 index 00000000..eb59eb55 --- /dev/null +++ b/documentation/audits/family-gate-2026-10-02/tools/shots/metube.js @@ -0,0 +1,16 @@ +// Screenshots of MeTube's own web UI (checklist 8.3): on scratch guest 9202, headless Chrome on the app's own network +// (the family gate is in front of the public name; the picture is of the app). One download already in the history. +const puppeteer = require('puppeteer'); +const sleep = ms => new Promise(r => setTimeout(r, ms)); +(async () => { + const b = await puppeteer.launch({args: ['--no-sandbox', '--lang=hu-HU']}); + const p = await b.newPage(); + await p.setViewport({width: 1280, height: 800}); + await p.goto(process.env.URL + '/', {waitUntil: 'networkidle2'}); + await sleep(3000); + await p.screenshot({path: '/out/1.png'}); + await p.setViewport({width: 390, height: 844, deviceScaleFactor: 2}); + await sleep(2500); + await p.screenshot({path: '/out/2.png'}); + await b.close(); +})().catch(e => { console.error(e); process.exit(1); }); diff --git a/documentation/audits/family-gate-2026-10-02/tools/walk.py b/documentation/audits/family-gate-2026-10-02/tools/walk.py new file mode 100644 index 00000000..64299781 --- /dev/null +++ b/documentation/audits/family-gate-2026-10-02/tools/walk.py @@ -0,0 +1,7 @@ +"""walk.py — this session's shim over the catalog's box client (app-catalog-felhom.eu/scripts/box_walk.py), so the +earlier nights' boxmove/boxstep shape works unchanged. SC (0600 scratch, `.ctlpw`) and EV come from the environment.""" +import sys +sys.path.insert(0, "/mnt/5_hdd/felhom.eu/git/app-catalog-felhom.eu/scripts") +from box_walk import * # noqa: F401,F403 +import box_walk as _b +SC, EV, GENERATED = _b.SC, _b.EV, _b.GENERATED diff --git a/documentation/audits/licences-2026-10-02/EMAIL-DRAFT-sparkyfitness.md b/documentation/audits/licences-2026-10-02/EMAIL-DRAFT-sparkyfitness.md new file mode 100644 index 00000000..9de5ae46 --- /dev/null +++ b/documentation/audits/licences-2026-10-02/EMAIL-DRAFT-sparkyfitness.md @@ -0,0 +1,54 @@ +# Draft — a request to SparkyFitness's author for written permission (decision 65) — NOT SENT + +**Status: a draft for the operator. Nothing was sent.** The operator reads it, changes it, and sends it from their own +account if they agree. + +## Where to send it — the contact route, read 2026-10-02 + +- The licence's last section says: *"For commercial use, licensing, or partnership inquiries, please contact the author: + https://github.com/codewithcj"* (`LICENSE` on `main`, read 2026-10-02). +- That GitHub profile shows **no e-mail address, no website, no social link** (GitHub API `users/CodeWithCJ`: `email`, + `blog`, `twitter_username` all empty). +- The repository has **GitHub Discussions switched on**, a project site (`codewithcj.github.io/SparkyFitness`) and a + **Discord** server linked from the README. +- So there is no private e-mail route. Two routes exist: (1) a GitHub Discussion in the repository (public), or (2) a + direct message to the author on the project's Discord (private). **Recommendation: (2) first** — a licence question about + money is better asked privately; open a Discussion only if there is no answer in two weeks. + +## The text + +> **Subject:** Permission to offer SparkyFitness in a paid home-server service (Felhom, Hungary) +> +> Hello CJ, +> +> I run Felhom, a small service in Hungary. We set up and look after home servers for families. Each family owns its box, +> and it stands in their home. The family chooses apps from our catalogue, and we install and update them for a monthly +> fee. +> +> SparkyFitness is in our catalogue. We like it a lot. It is the best family health tracker we tested. +> +> We read your licence. It says that use in a service for money needs your written permission. Our fee is for the box and +> our care, not for SparkyFitness itself. But we think this is still the kind of use your licence asks about, so we ask +> you first. +> +> What we do: +> - We pull your official Docker images, unchanged. We do not fork, rebrand or resell the software. +> - Each family's copy runs only on their own box, for their own household. +> - We keep your name, licence and links in the app's description. +> - We have no paying customers yet. We will not offer SparkyFitness to a paying customer without your answer. +> +> May we keep SparkyFitness in our catalogue for paying families? If you want something in return — a mention, a link, +> a donation per family, or something else — please tell us. If your answer is no, we will take it out of our catalogue +> for new families, and we will thank you for the time. +> +> Thank you for SparkyFitness. +> +> Best regards, +> [operator's name] +> Felhom — https://felhom.eu + +## Notes for the operator + +- If no written permission arrives before the first paying customer, the template goes to `lifecycle: hidden` + (decision 65; STATUS standing item). Boxes already running it keep it. +- The answer, when it comes, is filed under this folder and the register row is closed with it. diff --git a/documentation/audits/licences-2026-10-02/TABLE.md b/documentation/audits/licences-2026-10-02/TABLE.md new file mode 100644 index 00000000..c7c0bdd4 --- /dev/null +++ b/documentation/audits/licences-2026-10-02/TABLE.md @@ -0,0 +1,82 @@ +# Licence read — all templates, one table (R-787), 2026-10-02 + +Every image of every template in the catalog (56 directories incl. the two new ones, Grimmory and MeTube), the licence +READ at the pinned tag. Merged from `read-1.md` and `read-2.md` (two readers, same method; the method and the exceptions +are stated at the top of each). *Inferred* is marked in the row. **Nothing was hidden or changed because of this read** +(the brief's rule) — the short list goes to the operator in `STATUS.md`. + +| app | image:tag | licence (SPDX) | OSI | limiting clause (quoted) | plain meaning | evidence | +|---|---|---|---|---|---|---| +| actualbudget | actualbudget/actual-server:26.9.0 | MIT | yes | — | — | github.com/actualbudget/actual/blob/v26.9.0/LICENSE.txt | +| adventurelog | adventurelog-backend/-frontend:v0.13.0 | GPL-3.0 (version from the text; -only/-or-later *inferred*) | yes | — | — | github.com/seanmorley15/AdventureLog/blob/v0.13.0/LICENSE | +| adventurelog, dawarich | postgis/postgis:16-3.5 / 17-3.5-alpine | GPL-2.0-or-later + PostgreSQL | yes | — | — | postgis LICENSE.TXT | +| audiobookshelf | ghcr.io/advplyr/audiobookshelf:2.37.1 | GPL-3.0 | yes | — | — | github.com/advplyr/audiobookshelf/blob/v2.37.1/LICENSE | +| bentopdf | ghcr.io/alam00000/bentopdf:v2.8.6 | AGPL-3.0 (dual: a commercial licence optional) | yes | — | AGPL; the README labels this image the "Commercial build" and points self-hosters to `bentopdf-simple` — a label, not a licence term | github.com/alam00000/bentopdf/blob/v2.8.6/LICENSE + README "Licensing" | +| bookstack | lscr.io/linuxserver/bookstack:26.09.1 | MIT (+ GPL-3.0 packaging) | yes | — | — | github.com/BookStackApp/BookStack/blob/v26.09.1/LICENSE | +| bookstack, kimai | mariadb:12.3 / 11.8 | GPL-2.0-only | yes | — | — | mariadb.com/kb/en/mariadb-license/ | +| calcom | calcom/cal.com:v6.2.0 | AGPL-3.0 AND Cal.com Commercial License (`packages/features/ee`, `apps/api/v2/src/ee`) | partly | EE: "may only be used in production, if you … have a valid Cal.com Enterprise Edition subscription … it is forbidden to copy, merge, publish, distribute, sublicense, and/or sell the Software." | EE code is in the image, off without a key (*inferred*); free features stay AGPL; any EE feature in production needs a subscription | github.com/calcom/cal.com/blob/v6.2.0/packages/features/ee/LICENSE | +| calcom, docmost | postgres:18-alpine | PostgreSQL | yes | — | — | — | +| calibre-web | crocodilestick/calibre-web-automated:v4.0.8 | GPL-3.0 | yes | — | — | github.com/crocodilestick/Calibre-Web-Automated/blob/v4.0.8/LICENSE | +| claper | ghcr.io/claperco/claper:2.5 | AGPL-3.0 (read at v2.5.0; tag mapping *inferred*) | yes | — | — | github.com/ClaperCo/Claper/blob/v2.5.0/LICENSE.txt | +| code-server | lscr.io/linuxserver/code-server:4.129.0 | MIT (+ GPL-3.0 packaging) | yes | — | no Microsoft Marketplace (*inferred*) | github.com/coder/code-server/blob/v4.129.0/LICENSE | +| crafty-controller | crafty-4:4.11.0 | GPL-3.0 | yes | — | Minecraft jars it downloads fall under Mojang's EULA, separately | gitlab.com/crafty-controller/crafty-4/-/blob/v4.11.0/LICENSE | +| dawarich | freikin/dawarich:1.15.3 | AGPL-3.0 | yes | — | — | github.com/Freika/dawarich/blob/1.15.3/LICENSE | +| dawarich, docmost, immich | redis:7.4-alpine / 7-alpine (→ 7.4.11) | RSALv2 OR SSPL-1.0 | no | RSALv2: "You may not make the functionality of the Software or a Modified version available to third parties as a service or distribute the Software … in a manner that makes the functionality of the Software available to third parties." | a private cache only its app uses — *inferred* permitted; redis:7.2 / valkey (BSD-3) or Redis 8 (AGPL option) removes the question | github.com/redis/redis/blob/7.4.0/LICENSE.txt | +| docmost | docmost/docmost:0.96.0 | AGPL-3.0 AND Docmost Enterprise License (`packages/ee`, `apps/client/src/ee`) | partly | EE: "may only be used in production, if you … have a valid Docmost Enterprise Edition subscription …" | as Cal.com: EE off without a key (*inferred*) | github.com/docmost/docmost/blob/v0.96.0/packages/ee/LICENSE | +| **emby** | emby/embyserver:4.11.0.4 | **proprietary** | no | "Emby grants you a personal, non-commercial … non-transferable, non-sublicensable … license … you may not … copy, modify, distribute, sell, or lease any part of the Software" | the household's private use fits; a company selling its installation and care works in a context the licence does not cover; the box pulls the image from Emby, so no copies are distributed — a grey area (*inferred*) | emby.media/terms.html | +| ghost | ghost:6.67.0-alpine | MIT | yes | — | name is a trademark | github.com/TryGhost/Ghost/blob/v6.67.0/LICENSE | +| gitea | gitea/gitea:1.27.3 | MIT | yes | — | — | github.com/go-gitea/gitea/blob/v1.27.3/LICENSE | +| glance | glanceapp/glance:v0.8.5 | AGPL-3.0 | yes | — | — | github.com/glanceapp/glance/blob/v0.8.5/LICENSE | +| gokapi | f0rc3/gokapi:v1.9.6 | AGPL-3.0 | yes | — | — | github.com/Forceu/Gokapi/blob/v1.9.6/LICENSE.md | +| grafana | grafana/grafana:13.2.3 | AGPL-3.0 (OSS image) | yes | — | trademarks | github.com/grafana/grafana/blob/v13.2.3/LICENSE | +| gramps-web | ghcr.io/gramps-project/grampsweb:v25.6.0 | AGPL-3.0 (Gramps core GPL-2.0-or-later) | yes | — | — | github.com/gramps-project/gramps-web/blob/v25.6.0/LICENSE | +| grimmory | ghcr.io/grimmory-tools/grimmory:v3.4.1 | AGPL-3.0 | yes | – | – | github.com/grimmory-tools/grimmory/blob/v3.4.1/LICENSE | +| grimmory | mariadb:11.4 | GPL-2.0 | yes | – | – | MariaDB COPYING | +| home-assistant | home-assistant:2026.9.4 | Apache-2.0 | yes | — | trademark | github.com/home-assistant/core/blob/2026.9.4/LICENSE.md | +| homebox | ghcr.io/sysadminsmedia/homebox:0.26.2 | AGPL-3.0 | yes | — | — | github.com/sysadminsmedia/homebox/blob/v0.26.2/LICENSE | +| homepage | ghcr.io/gethomepage/homepage:v1.13.2 | GPL-3.0 | yes | — | — | github.com/gethomepage/homepage/blob/v1.13.2/LICENSE | +| immich | immich-server / -machine-learning:v3.2.4 | AGPL-3.0 | yes | — | ML model weights carry their own licences (not checked) | github.com/immich-app/immich/blob/v3.2.4/LICENSE | +| immich | ghcr.io/immich-app/postgres:16-vectorchord0.4.3-pgvectors0.2.0 | PostgreSQL + (AGPL-3.0 OR Elastic-2.0) VectorChord + Apache-2.0 pgvecto.rs | yes (AGPL chosen for VectorChord) | ELv2 alternative only | — | github.com/tensorchord/VectorChord/blob/0.4.3/LICENSE | +| jellyfin | jellyfin/jellyfin:10.11.11 | GPL-2.0 (ffmpeg GPL-3.0) | yes | — | — | github.com/jellyfin/jellyfin/blob/v10.11.11/LICENSE | +| karakeep | ghcr.io/karakeep-app/karakeep:0.33.2 | AGPL-3.0 | yes | — | — | github.com/karakeep-app/karakeep/blob/v0.33.2/LICENSE | +| karakeep | karakeep-chrome:151.0.7922.47-r1 | BSD-3-Clause (Chromium headless-shell — *inferred* from Chromium) | yes | — | — | chromium LICENSE | +| karakeep | getmeili/meilisearch:v1.41.0 | MIT (Community image; BUSL EE code only in `meilisearch-enterprise`) | yes | — | never switch to the `-enterprise` image | github.com/meilisearch/meilisearch/blob/v1.41.0/LICENSE | +| kimai | kimai/kimai2:2.67.0 | AGPL-3.0 | yes | — | paid plugins are separate | github.com/kimai/kimai/blob/2.67.0/LICENSE | +| komga | gotson/komga:1.28.0 | MIT | yes | — | — | github.com/gotson/komga/blob/1.28.0/LICENSE | +| mealie | ghcr.io/mealie-recipes/mealie:v3.28.0 | AGPL-3.0 | yes | — | — | github.com/mealie-recipes/mealie/blob/v3.28.0/LICENSE | +| metube | ghcr.io/alexta69/metube:2026.09.29 | AGPL-3.0 | yes | – | – | github.com/alexta69/metube/blob/2026.09.29/LICENSE | +| n8n | n8nio/n8n:2.42.1 | custom: Sustainable Use License 1.0 (+ `.ee.` files: n8n Enterprise License) | no | "You may use or modify the software only for your own internal business purposes or for non-commercial or personal use. You may distribute the software or provide it to others only if you do so free of charge for non-commercial purposes." | the household's private use is allowed; Felhom charging while "providing it to others" is a grey zone (pulling the official image unchanged is arguably not distributing — *inferred*) | github.com/n8n-io/n8n/blob/n8n@2.42.1/LICENSE.md | +| navidrome | deluan/navidrome:0.64.2 | GPL-3.0 | yes | – | – | github.com/navidrome/navidrome/blob/v0.64.2/LICENSE | +| nextcloud | nextcloud:34.0.4-apache | AGPL-3.0 | yes | – | – | github.com/nextcloud/server/blob/v34.0.4/COPYING | +| nextcloud | mariadb:12.3 | GPL-2.0 | yes | – | – | MariaDB COPYING | +| nextcloud, outline, paperless-ngx, romm | redis:7-alpine (→ 7.4.11) | RSALv2 OR SSPL-1.0 | no | RSALv2: "You may not make the functionality of the Software or a Modified version available to third parties as a service" | an internal cache on the customer's own box, not Redis offered as a service — *inferred* not to bite; Valkey (BSD) or redis:8 (AGPL option) removes the question | github.com/redis/redis/blob/7.4.11/LICENSE.txt | +| onlyoffice | onlyoffice/documentserver:9.4.0 | AGPL-3.0 + §7 terms (logo, no trademark licence) | yes (base) | §7 keep the logo / no trademark licence | the image runs unchanged, so the terms are met | github.com/ONLYOFFICE/DocumentServer/blob/v9.4.0/LICENSE | +| opengist | ghcr.io/thomiceli/opengist:1.15 | AGPL-3.0 | yes | – | – | github.com/thomiceli/opengist/blob/v1.15.0/LICENSE | +| outline | outlinewiki/outline:1.10.1 | BUSL-1.1 (Change Date 2030-09-09 → Apache-2.0) | no | "you may not use the Licensed Work for a Document Service. A 'Document Service' is a commercial offering that allows third parties (other than your employees and contractors) to access the functionality of the Licensed Work by creating teams and documents controlled by such third parties." | the household's own instance on its own hardware is not a Document Service; Felhom hosting it for paying third parties itself would be — *inferred* | github.com/outline/outline/blob/v1.10.1/LICENSE | +| outline, paperless-ngx, rallly, sparkyfitness | postgres:18-alpine | PostgreSQL | yes | – | – | postgres COPYRIGHT | +| paperless-ngx | ghcr.io/paperless-ngx/paperless-ngx:2.20.15 | GPL-3.0 | yes | – | – | github.com/paperless-ngx/paperless-ngx/blob/v2.20.15/LICENSE | +| papra | ghcr.io/papra-hq/papra:26.6.2-rootless | AGPL-3.0 | yes | – | – | github.com/papra-hq/papra (tag @papra/app@26.6.2) LICENSE | +| plant-it | msdeluise/plant-it:0.10.0 | GPL-3.0 | yes | – | – | github.com/MDeLuise/plant-it/blob/0.10.0/LICENSE.txt | +| plex | plexinc/pms-docker:1.41.4.9463-630c9f557 | proprietary (Plex Terms of Service) | no | "personal (non-commercial), revocable, limited, non-exclusive, nontransferable, and non-sublicensable license to access and use the Plex Solution" (search snippet; the page 403'd) | the household uses it under its own Plex account; Felhom gets no right and installs it only as the household's agent; selling Plex or a Plex Pass is not covered | plex.tv/about/privacy-legal/plex-terms-of-service/ | +| privatebin | privatebin/pdo:2.0.6 | Zlib (bundled GPL-2.0, BSD-3, MIT, Apache, CC-BY) | yes | – | – | github.com/PrivateBin/PrivateBin/blob/2.0.6/LICENSE.md | +| radarr | lscr.io/linuxserver/radarr:6.4.4 | GPL-3.0 | yes | – | – | github.com/Radarr/Radarr LICENSE | +| radicale | ghcr.io/kozea/radicale:3.8.1 | GPL-3.0 | yes | – | – | github.com/Kozea/Radicale/blob/v3.8.1/COPYING.md | +| rallly | lukevella/rallly:4.15.3 | AGPL-3.0 | yes | – | – | github.com/lukevella/rallly/blob/v4.15.3/LICENSE | +| recipe-importer | gitea.dooplex.hu/admin/recipe-importer:v0.9.11 | none (all rights reserved — Felhom's own) | n/a | no licence file | Felhom owns it; dependencies (Flask, gunicorn, requests, bs4, lxml) permissive — *inferred* from requirements.txt | /home/kisfenyo/git/recipe-importer | +| romm | rommapp/romm:5.3.1 | AGPL-3.0 | yes | – | – | github.com/rommapp/romm/blob/5.3.1/LICENSE | +| romm | mariadb:11.8 | GPL-2.0 | yes | – | – | MariaDB COPYING | +| seerr | fallenbagel/jellyseerr:2.7.3 | MIT | yes | – | – | github.com/fallenbagel/jellyseerr/blob/v2.7.3/LICENSE | +| sonarr | lscr.io/linuxserver/sonarr:4.0.20 | GPL-3.0 | yes | – | – | github.com/Sonarr/Sonarr LICENSE.md | +| sparkyfitness | codewithcj/sparkyfitness(_server):v0.17.3 | custom non-commercial | no | "The Software may not be used, directly or indirectly, in any product, service, or project primarily intended for or resulting in commercial advantage or monetary compensation, without prior written permission from the author." §7 terminates on a breach | offering it in a paid install-and-care service is commercial use: written permission needed (decision 65) | github.com/CodeWithCJ/SparkyFitness/blob/v0.17.3/LICENSE | +| **tandoor** | ghcr.io/tandoorrecipes/recipes:2.6.15 | **AGPL-3.0 WITH Commons Clause v1.0 (custom)** | **no** | "the License does not grant to you, the right to Sell the Software… 'Sell' means… to provide to third parties, for a fee or other consideration (including without limitation fees for hosting or consulting/ support services related to the Software), a product or service whose value derives, entirely or substantially, from the functionality of the Software." | charging for installing or supporting Tandoor is what the clause names, if the paid service's value comes "substantially" from Tandoor; permission from the author, or keep it out of what Felhom charges for | github.com/TandoorRecipes/recipes/blob/2.6.15/LICENSE.md | +| tandoor | postgres:17-alpine | PostgreSQL | yes | – | – | – | +| termix | ghcr.io/lukegus/termix:2.8.0 | Apache-2.0 | yes | – | – | github.com/Termix-SSH/Termix LICENSE | +| uptime-kuma | louislam/uptime-kuma:2.5.5 | MIT | yes | – | – | github.com/louislam/uptime-kuma/blob/2.5.5/LICENSE | +| vaultwarden | vaultwarden/server:1.36.0-alpine | AGPL-3.0 (web-vault GPL-3.0) | yes | – | – | github.com/dani-garcia/vaultwarden/blob/1.36.0/LICENSE.txt | +| vikunja | vikunja/vikunja:2.6.0 | AGPL-3.0 | yes | – | – | github.com/go-vikunja/vikunja/blob/v2.6.0/LICENSE | +| wanderer | flomp/wanderer-web/-db:v0.20.0 | AGPL-3.0 (db = PocketBase MIT — *inferred*) | yes | – | – | github.com/Flomp/wanderer/blob/v0.20.0/LICENSE | +| wanderer | getmeili/meilisearch:v1.36.0 | MIT AND BUSL-1.1 (EE modules) | core yes; EE no | LICENSE-EE: "non-production purposes only… Production use of the Licensed Work requires a commercial license" | Enterprise modules only (e.g. sharding); plain search is the MIT core — fine unless an EE feature is on (*inferred*) | github.com/meilisearch/meilisearch/blob/v1.36.0/LICENSE | +| wger | wger/server:2.7 | AGPL-3.0 | yes | – | – | github.com/wger-project/wger/blob/2.7/LICENSE.txt | +| wishlist | ghcr.io/cmintey/wishlist:v0.67.1 | MIT | yes | – | – | github.com/cmintey/wishlist/blob/v0.67.1/LICENSE | +| zipline | ghcr.io/diced/zipline:4.8.0 | MIT | yes | – | – | github.com/diced/zipline/blob/v4.8.0/LICENSE | +| zipline | postgres:16-alpine | PostgreSQL | yes | – | – | – | diff --git a/documentation/audits/licences-2026-10-02/read-1.md b/documentation/audits/licences-2026-10-02/read-1.md new file mode 100644 index 00000000..0427231f --- /dev/null +++ b/documentation/audits/licences-2026-10-02/read-1.md @@ -0,0 +1,48 @@ +# Licence read, part 1 of 2 (R-787) — READ at each pinned tag, 2026-10-02 (subagent, verbatim in substance) + +Apps: actualbudget adventurelog audiobookshelf bentopdf bookstack calcom calibre-web claper code-server crafty-controller +dawarich docmost emby ghost gitea glance gokapi grafana gramps-web home-assistant homebox homepage immich jellyfin karakeep +kimai komga mealie. Licences read from the upstream file at the pinned tag, except linuxserver packaging, PostGIS and +immich base-images (read from `master`/`main`) and the MariaDB, Postgres, Chromium and Emby pages. *Inferred* is marked. + +| app | image:tag | licence (SPDX) | OSI | limiting clause (quoted) | plain meaning | evidence | +|---|---|---|---|---|---|---| +| actualbudget | actualbudget/actual-server:26.9.0 | MIT | yes | — | — | github.com/actualbudget/actual/blob/v26.9.0/LICENSE.txt | +| adventurelog | adventurelog-backend/-frontend:v0.13.0 | GPL-3.0 (version from the text; -only/-or-later *inferred*) | yes | — | — | github.com/seanmorley15/AdventureLog/blob/v0.13.0/LICENSE | +| adventurelog, dawarich | postgis/postgis:16-3.5 / 17-3.5-alpine | GPL-2.0-or-later + PostgreSQL | yes | — | — | postgis LICENSE.TXT | +| audiobookshelf | ghcr.io/advplyr/audiobookshelf:2.37.1 | GPL-3.0 | yes | — | — | github.com/advplyr/audiobookshelf/blob/v2.37.1/LICENSE | +| bentopdf | ghcr.io/alam00000/bentopdf:v2.8.6 | AGPL-3.0 (dual: a commercial licence optional) | yes | — | AGPL; the README labels this image the "Commercial build" and points self-hosters to `bentopdf-simple` — a label, not a licence term | github.com/alam00000/bentopdf/blob/v2.8.6/LICENSE + README "Licensing" | +| bookstack | lscr.io/linuxserver/bookstack:26.09.1 | MIT (+ GPL-3.0 packaging) | yes | — | — | github.com/BookStackApp/BookStack/blob/v26.09.1/LICENSE | +| bookstack, kimai | mariadb:12.3 / 11.8 | GPL-2.0-only | yes | — | — | mariadb.com/kb/en/mariadb-license/ | +| calcom | calcom/cal.com:v6.2.0 | AGPL-3.0 AND Cal.com Commercial License (`packages/features/ee`, `apps/api/v2/src/ee`) | partly | EE: "may only be used in production, if you … have a valid Cal.com Enterprise Edition subscription … it is forbidden to copy, merge, publish, distribute, sublicense, and/or sell the Software." | EE code is in the image, off without a key (*inferred*); free features stay AGPL; any EE feature in production needs a subscription | github.com/calcom/cal.com/blob/v6.2.0/packages/features/ee/LICENSE | +| calcom, docmost | postgres:18-alpine | PostgreSQL | yes | — | — | — | +| calibre-web | crocodilestick/calibre-web-automated:v4.0.8 | GPL-3.0 | yes | — | — | github.com/crocodilestick/Calibre-Web-Automated/blob/v4.0.8/LICENSE | +| claper | ghcr.io/claperco/claper:2.5 | AGPL-3.0 (read at v2.5.0; tag mapping *inferred*) | yes | — | — | github.com/ClaperCo/Claper/blob/v2.5.0/LICENSE.txt | +| code-server | lscr.io/linuxserver/code-server:4.129.0 | MIT (+ GPL-3.0 packaging) | yes | — | no Microsoft Marketplace (*inferred*) | github.com/coder/code-server/blob/v4.129.0/LICENSE | +| crafty-controller | crafty-4:4.11.0 | GPL-3.0 | yes | — | Minecraft jars it downloads fall under Mojang's EULA, separately | gitlab.com/crafty-controller/crafty-4/-/blob/v4.11.0/LICENSE | +| dawarich | freikin/dawarich:1.15.3 | AGPL-3.0 | yes | — | — | github.com/Freika/dawarich/blob/1.15.3/LICENSE | +| dawarich, docmost, immich | redis:7.4-alpine / 7-alpine (→ 7.4.11) | RSALv2 OR SSPL-1.0 | no | RSALv2: "You may not make the functionality of the Software or a Modified version available to third parties as a service or distribute the Software … in a manner that makes the functionality of the Software available to third parties." | a private cache only its app uses — *inferred* permitted; redis:7.2 / valkey (BSD-3) or Redis 8 (AGPL option) removes the question | github.com/redis/redis/blob/7.4.0/LICENSE.txt | +| docmost | docmost/docmost:0.96.0 | AGPL-3.0 AND Docmost Enterprise License (`packages/ee`, `apps/client/src/ee`) | partly | EE: "may only be used in production, if you … have a valid Docmost Enterprise Edition subscription …" | as Cal.com: EE off without a key (*inferred*) | github.com/docmost/docmost/blob/v0.96.0/packages/ee/LICENSE | +| **emby** | emby/embyserver:4.11.0.4 | **proprietary** | no | "Emby grants you a personal, non-commercial … non-transferable, non-sublicensable … license … you may not … copy, modify, distribute, sell, or lease any part of the Software" | the household's private use fits; a company selling its installation and care works in a context the licence does not cover; the box pulls the image from Emby, so no copies are distributed — a grey area (*inferred*) | emby.media/terms.html | +| ghost | ghost:6.67.0-alpine | MIT | yes | — | name is a trademark | github.com/TryGhost/Ghost/blob/v6.67.0/LICENSE | +| gitea | gitea/gitea:1.27.3 | MIT | yes | — | — | github.com/go-gitea/gitea/blob/v1.27.3/LICENSE | +| glance | glanceapp/glance:v0.8.5 | AGPL-3.0 | yes | — | — | github.com/glanceapp/glance/blob/v0.8.5/LICENSE | +| gokapi | f0rc3/gokapi:v1.9.6 | AGPL-3.0 | yes | — | — | github.com/Forceu/Gokapi/blob/v1.9.6/LICENSE.md | +| grafana | grafana/grafana:13.2.3 | AGPL-3.0 (OSS image) | yes | — | trademarks | github.com/grafana/grafana/blob/v13.2.3/LICENSE | +| gramps-web | ghcr.io/gramps-project/grampsweb:v25.6.0 | AGPL-3.0 (Gramps core GPL-2.0-or-later) | yes | — | — | github.com/gramps-project/gramps-web/blob/v25.6.0/LICENSE | +| home-assistant | home-assistant:2026.9.4 | Apache-2.0 | yes | — | trademark | github.com/home-assistant/core/blob/2026.9.4/LICENSE.md | +| homebox | ghcr.io/sysadminsmedia/homebox:0.26.2 | AGPL-3.0 | yes | — | — | github.com/sysadminsmedia/homebox/blob/v0.26.2/LICENSE | +| homepage | ghcr.io/gethomepage/homepage:v1.13.2 | GPL-3.0 | yes | — | — | github.com/gethomepage/homepage/blob/v1.13.2/LICENSE | +| immich | immich-server / -machine-learning:v3.2.4 | AGPL-3.0 | yes | — | ML model weights carry their own licences (not checked) | github.com/immich-app/immich/blob/v3.2.4/LICENSE | +| immich | ghcr.io/immich-app/postgres:16-vectorchord0.4.3-pgvectors0.2.0 | PostgreSQL + (AGPL-3.0 OR Elastic-2.0) VectorChord + Apache-2.0 pgvecto.rs | yes (AGPL chosen for VectorChord) | ELv2 alternative only | — | github.com/tensorchord/VectorChord/blob/0.4.3/LICENSE | +| jellyfin | jellyfin/jellyfin:10.11.11 | GPL-2.0 (ffmpeg GPL-3.0) | yes | — | — | github.com/jellyfin/jellyfin/blob/v10.11.11/LICENSE | +| karakeep | ghcr.io/karakeep-app/karakeep:0.33.2 | AGPL-3.0 | yes | — | — | github.com/karakeep-app/karakeep/blob/v0.33.2/LICENSE | +| karakeep | karakeep-chrome:151.0.7922.47-r1 | BSD-3-Clause (Chromium headless-shell — *inferred* from Chromium) | yes | — | — | chromium LICENSE | +| karakeep | getmeili/meilisearch:v1.41.0 | MIT (Community image; BUSL EE code only in `meilisearch-enterprise`) | yes | — | never switch to the `-enterprise` image | github.com/meilisearch/meilisearch/blob/v1.41.0/LICENSE | +| kimai | kimai/kimai2:2.67.0 | AGPL-3.0 | yes | — | paid plugins are separate | github.com/kimai/kimai/blob/2.67.0/LICENSE | +| komga | gotson/komga:1.28.0 | MIT | yes | — | — | github.com/gotson/komga/blob/1.28.0/LICENSE | +| mealie | ghcr.io/mealie-recipes/mealie:v3.28.0 | AGPL-3.0 | yes | — | — | github.com/mealie-recipes/mealie/blob/v3.28.0/LICENSE | + +**Not OSI-approved (part 1):** emby (proprietary, personal non-commercial — the real business risk in this set); calcom and +docmost (proprietary EE folders inside the AGPL image, off without a key); redis 7.4.11 in dawarich, docmost, immich +(RSALv2/SSPL — internal cache). diff --git a/documentation/audits/licences-2026-10-02/read-2.md b/documentation/audits/licences-2026-10-02/read-2.md new file mode 100644 index 00000000..7b82c6ba --- /dev/null +++ b/documentation/audits/licences-2026-10-02/read-2.md @@ -0,0 +1,52 @@ +# Licence read, part 2 of 2 (R-787) — READ at each pinned tag, 2026-10-02 (subagent, verbatim in substance) + +Apps: n8n navidrome nextcloud onlyoffice opengist outline paperless-ngx papra plant-it plex privatebin radarr radicale +rallly recipe-importer romm seerr sonarr sparkyfitness tandoor termix uptime-kuma vaultwarden vikunja wanderer wger +wishlist zipline grimmory (held WIP) metube (new). Upstream LICENSE files read from raw.githubusercontent.com at the pinned +tag, except Plex (ToS page 403 — quoted from a search snippet) and the rows marked *inferred*. + +| app | image:tag | licence (SPDX) | OSI | limiting clause (quoted) | plain meaning | evidence | +|---|---|---|---|---|---|---| +| n8n | n8nio/n8n:2.42.1 | custom: Sustainable Use License 1.0 (+ `.ee.` files: n8n Enterprise License) | no | "You may use or modify the software only for your own internal business purposes or for non-commercial or personal use. You may distribute the software or provide it to others only if you do so free of charge for non-commercial purposes." | the household's private use is allowed; Felhom charging while "providing it to others" is a grey zone (pulling the official image unchanged is arguably not distributing — *inferred*) | github.com/n8n-io/n8n/blob/n8n@2.42.1/LICENSE.md | +| navidrome | deluan/navidrome:0.64.2 | GPL-3.0 | yes | – | – | github.com/navidrome/navidrome/blob/v0.64.2/LICENSE | +| nextcloud | nextcloud:34.0.4-apache | AGPL-3.0 | yes | – | – | github.com/nextcloud/server/blob/v34.0.4/COPYING | +| nextcloud | mariadb:12.3 | GPL-2.0 | yes | – | – | MariaDB COPYING | +| nextcloud, outline, paperless-ngx, romm | redis:7-alpine (→ 7.4.11) | RSALv2 OR SSPL-1.0 | no | RSALv2: "You may not make the functionality of the Software or a Modified version available to third parties as a service" | an internal cache on the customer's own box, not Redis offered as a service — *inferred* not to bite; Valkey (BSD) or redis:8 (AGPL option) removes the question | github.com/redis/redis/blob/7.4.11/LICENSE.txt | +| onlyoffice | onlyoffice/documentserver:9.4.0 | AGPL-3.0 + §7 terms (logo, no trademark licence) | yes (base) | §7 keep the logo / no trademark licence | the image runs unchanged, so the terms are met | github.com/ONLYOFFICE/DocumentServer/blob/v9.4.0/LICENSE | +| opengist | ghcr.io/thomiceli/opengist:1.15 | AGPL-3.0 | yes | – | – | github.com/thomiceli/opengist/blob/v1.15.0/LICENSE | +| outline | outlinewiki/outline:1.10.1 | BUSL-1.1 (Change Date 2030-09-09 → Apache-2.0) | no | "you may not use the Licensed Work for a Document Service. A 'Document Service' is a commercial offering that allows third parties (other than your employees and contractors) to access the functionality of the Licensed Work by creating teams and documents controlled by such third parties." | the household's own instance on its own hardware is not a Document Service; Felhom hosting it for paying third parties itself would be — *inferred* | github.com/outline/outline/blob/v1.10.1/LICENSE | +| outline, paperless-ngx, rallly, sparkyfitness | postgres:18-alpine | PostgreSQL | yes | – | – | postgres COPYRIGHT | +| paperless-ngx | ghcr.io/paperless-ngx/paperless-ngx:2.20.15 | GPL-3.0 | yes | – | – | github.com/paperless-ngx/paperless-ngx/blob/v2.20.15/LICENSE | +| papra | ghcr.io/papra-hq/papra:26.6.2-rootless | AGPL-3.0 | yes | – | – | github.com/papra-hq/papra (tag @papra/app@26.6.2) LICENSE | +| plant-it | msdeluise/plant-it:0.10.0 | GPL-3.0 | yes | – | – | github.com/MDeLuise/plant-it/blob/0.10.0/LICENSE.txt | +| plex | plexinc/pms-docker:1.41.4.9463-630c9f557 | proprietary (Plex Terms of Service) | no | "personal (non-commercial), revocable, limited, non-exclusive, nontransferable, and non-sublicensable license to access and use the Plex Solution" (search snippet; the page 403'd) | the household uses it under its own Plex account; Felhom gets no right and installs it only as the household's agent; selling Plex or a Plex Pass is not covered | plex.tv/about/privacy-legal/plex-terms-of-service/ | +| privatebin | privatebin/pdo:2.0.6 | Zlib (bundled GPL-2.0, BSD-3, MIT, Apache, CC-BY) | yes | – | – | github.com/PrivateBin/PrivateBin/blob/2.0.6/LICENSE.md | +| radarr | lscr.io/linuxserver/radarr:6.4.4 | GPL-3.0 | yes | – | – | github.com/Radarr/Radarr LICENSE | +| radicale | ghcr.io/kozea/radicale:3.8.1 | GPL-3.0 | yes | – | – | github.com/Kozea/Radicale/blob/v3.8.1/COPYING.md | +| rallly | lukevella/rallly:4.15.3 | AGPL-3.0 | yes | – | – | github.com/lukevella/rallly/blob/v4.15.3/LICENSE | +| recipe-importer | gitea.dooplex.hu/admin/recipe-importer:v0.9.11 | none (all rights reserved — Felhom's own) | n/a | no licence file | Felhom owns it; dependencies (Flask, gunicorn, requests, bs4, lxml) permissive — *inferred* from requirements.txt | /home/kisfenyo/git/recipe-importer | +| romm | rommapp/romm:5.3.1 | AGPL-3.0 | yes | – | – | github.com/rommapp/romm/blob/5.3.1/LICENSE | +| romm | mariadb:11.8 | GPL-2.0 | yes | – | – | MariaDB COPYING | +| seerr | fallenbagel/jellyseerr:2.7.3 | MIT | yes | – | – | github.com/fallenbagel/jellyseerr/blob/v2.7.3/LICENSE | +| sonarr | lscr.io/linuxserver/sonarr:4.0.20 | GPL-3.0 | yes | – | – | github.com/Sonarr/Sonarr LICENSE.md | +| sparkyfitness | codewithcj/sparkyfitness(_server):v0.17.3 | custom non-commercial | no | "The Software may not be used, directly or indirectly, in any product, service, or project primarily intended for or resulting in commercial advantage or monetary compensation, without prior written permission from the author." §7 terminates on a breach | offering it in a paid install-and-care service is commercial use: written permission needed (decision 65) | github.com/CodeWithCJ/SparkyFitness/blob/v0.17.3/LICENSE | +| **tandoor** | ghcr.io/tandoorrecipes/recipes:2.6.15 | **AGPL-3.0 WITH Commons Clause v1.0 (custom)** | **no** | "the License does not grant to you, the right to Sell the Software… 'Sell' means… to provide to third parties, for a fee or other consideration (including without limitation fees for hosting or consulting/ support services related to the Software), a product or service whose value derives, entirely or substantially, from the functionality of the Software." | charging for installing or supporting Tandoor is what the clause names, if the paid service's value comes "substantially" from Tandoor; permission from the author, or keep it out of what Felhom charges for | github.com/TandoorRecipes/recipes/blob/2.6.15/LICENSE.md | +| tandoor | postgres:17-alpine | PostgreSQL | yes | – | – | – | +| termix | ghcr.io/lukegus/termix:2.8.0 | Apache-2.0 | yes | – | – | github.com/Termix-SSH/Termix LICENSE | +| uptime-kuma | louislam/uptime-kuma:2.5.5 | MIT | yes | – | – | github.com/louislam/uptime-kuma/blob/2.5.5/LICENSE | +| vaultwarden | vaultwarden/server:1.36.0-alpine | AGPL-3.0 (web-vault GPL-3.0) | yes | – | – | github.com/dani-garcia/vaultwarden/blob/1.36.0/LICENSE.txt | +| vikunja | vikunja/vikunja:2.6.0 | AGPL-3.0 | yes | – | – | github.com/go-vikunja/vikunja/blob/v2.6.0/LICENSE | +| wanderer | flomp/wanderer-web/-db:v0.20.0 | AGPL-3.0 (db = PocketBase MIT — *inferred*) | yes | – | – | github.com/Flomp/wanderer/blob/v0.20.0/LICENSE | +| wanderer | getmeili/meilisearch:v1.36.0 | MIT AND BUSL-1.1 (EE modules) | core yes; EE no | LICENSE-EE: "non-production purposes only… Production use of the Licensed Work requires a commercial license" | Enterprise modules only (e.g. sharding); plain search is the MIT core — fine unless an EE feature is on (*inferred*) | github.com/meilisearch/meilisearch/blob/v1.36.0/LICENSE | +| wger | wger/server:2.7 | AGPL-3.0 | yes | – | – | github.com/wger-project/wger/blob/2.7/LICENSE.txt | +| wishlist | ghcr.io/cmintey/wishlist:v0.67.1 | MIT | yes | – | – | github.com/cmintey/wishlist/blob/v0.67.1/LICENSE | +| zipline | ghcr.io/diced/zipline:4.8.0 | MIT | yes | – | – | github.com/diced/zipline/blob/v4.8.0/LICENSE | +| zipline | postgres:16-alpine | PostgreSQL | yes | – | – | – | +| grimmory | ghcr.io/grimmory-tools/grimmory:v3.4.1 | AGPL-3.0 | yes | – | – | github.com/grimmory-tools/grimmory/blob/v3.4.1/LICENSE | +| grimmory | mariadb:11.4 | GPL-2.0 | yes | – | – | MariaDB COPYING | +| metube | ghcr.io/alexta69/metube:2026.09.29 | AGPL-3.0 | yes | – | – | github.com/alexta69/metube/blob/2026.09.29/LICENSE | + +**Not OSI-approved (part 2):** tandoor (Commons Clause — the most serious: it names paid hosting and support), sparkyfitness +(non-commercial; decision 65), n8n (Sustainable Use — grey zone), plex (proprietary, personal use; the household is the +licensee), outline (BUSL-1.1 — only a commercial "Document Service"), redis:7-alpine 7.4.11 in four apps (RSALv2/SSPL — +internal cache), meilisearch v1.36.0 EE modules (wanderer — not used), recipe-importer (Felhom's own, no licence file). diff --git a/documentation/audits/new-apps-2026-10-01/FIT.md b/documentation/audits/new-apps-2026-10-01/FIT.md index 11485d50..eecc6d24 100644 --- a/documentation/audits/new-apps-2026-10-01/FIT.md +++ b/documentation/audits/new-apps-2026-10-01/FIT.md @@ -17,7 +17,7 @@ SparkyFitness and Calibre-Web Automated are already in the catalog (`templates/s | **Dawarich** (Freika/dawarich → `freikin/dawarich`) | AGPL-3.0 | 1.15.3 2026-09-30 · 56 | `1.15.3` … · amd64, arm, arm64 | telemetry removed; PostHog/Sentry only if configured [U]; **map tiles from `tyles.dwri.xyz` by default** (the household's browser asks it for the areas it views) [U] | reverse geocoding OFF unless Photon/Nominatim/Geoapify/LocationIQ is set [U] | none | phone apps (Dawarich, Overland, OwnTracks, GPSLogger) POST with `?api_key=` / Bearer over HTTPS [U] — the gate must be open after setup | `postgis/postgis:17-3.5-alpine` (ARM: `imresamu/postgis`), `redis:7.4`, sidekiq | app limit 4 G upstream [U]; to measure | **`db:seed` creates `demo@dawarich.app` / `safepassword` on first boot** [U] → `after_install` must replace it | **build with a note** (seeded known login; tile host; ~3 image moves a month) | | **Grimmory** (grimmory-tools/grimmory → `grimmory/grimmory` / `ghcr.io/grimmory-tools/grimmory`) | AGPL-3.0 | v3.5.0 2026-09-21 · 24 | `v3.5.0` … · amd64, arm64 | version check → api.github.com [U]; metadata lookups send titles/ISBNs to Google Books, Open Library, Amazon, Goodreads, Hardcover … [U]; Kobo sync proxies to kobo.com [U] | for metadata/Kobo only | none (HTTP 6060) | OPDS v1/v2 + Komga API (basic auth), KOReader sync, Kobo sync token — all HTTP [U] | MariaDB 11.4 (`lscr.io/linuxserver/mariadb`) | Java, ~512 MB heap (`MaxRAMPercentage=60`) [U]; ~1 GB + 256 MB [I] | `POST /api/v1/setup` once, then 403 [U] → setup gate | **build with a note** (metadata lookups leave the box) | | **Grimoire** (goniszewski/grimoire) | MIT | v1.3.0 2026-09-28 · 4 | **no published 1.x image** (compose uses `build:`; Docker Hub is the old 0.x line) [U] | local update checks [U] | — | — | bookmarklet with a token | SQLite, one Bun daemon | small | **"Public-network exposure is not a supported Grimoire mode"; REST routes "loopback-only and tokenless"** [U] | **stop** — upstream rules out exactly how we publish apps, and there is no image | -| **MeTube** (alexta69/metube → `ghcr.io/alexta69/metube`) | AGPL-3.0 | 2026.09.29 · 80 tags (≈10 are yt-dlp releases; the rest MeTube's own changes) [U] | date tags · amd64, arm64 | none documented [U] | yes — YouTube etc., **from the household's IP** | none | web only | none (files in `/downloads`) | not documented | **no login at all, by design** ("deliberately has no login system", wiki) [U] — anyone who finds the subdomain downloads through the household's IP and can use uploaded cookies | **stop as specified** — the box has no permanent household-only door (`setup_gate`/`signup_block` only); a login proxy sidecar inside the template (basic auth from a generated password) is a catalog-only route, not built today (row) | +| **MeTube** (alexta69/metube → `ghcr.io/alexta69/metube`) | AGPL-3.0 | 2026.09.29 · 80 tags (≈10 are yt-dlp releases; the rest MeTube's own changes) [U] | date tags · amd64, arm64 | none documented [U] | yes — YouTube etc., **from the household's IP** | none | web only | none (files in `/downloads`) | not documented | **no login at all, by design** ("deliberately has no login system", wiki) [U] — anyone who finds the subdomain downloads through the household's IP and can use uploaded cookies | ~~**stop as specified**~~ → **build behind the family gate** (changed 2026-10-02, `09` §3 decision 64). **Why:** the stop had ONE reason — the box had no permanent household-only door. Controller 0.287.0 has one: the family gate, every path (the websocket included) behind each member's own login, measured on 9202 (`audits/family-gate-2026-10-02/A/items.txt`). MeTube gets NO exception — it has no login to fall back on. The download-through-the-household's-IP risk stays the household's, now limited to its own members; the page says the household downloads only what it may (own use, copyright). | | **Pinchflat** (kieraneglin/pinchflat → `ghcr.io/kieraneglin/pinchflat`) | AGPL-3.0 | v2025.9.26 (2025-09-26) · **0 in 2026**; last push 2025-12-16 | **no `v2025.9.26` tag; newest version tag `v2025.6.6`; `latest` is amd64 only** [U] | none documented | yes (YouTube; yt-dlp self-updates daily [U]) | none | web only | SQLite | undocumented; an unanswered report of a 30 GB yt-dlp leak (#866) [R] | optional `BASIC_AUTH_*` [U]; runs as root by default [U] | **stop** — upstream paused (checklist 0.2: no release in 6 months); forks exist (Pinchflat-NGX, MorganKryze) — a different upstream is the operator's choice | | **Invidious** (iv-org/invidious → `quay.io/invidious/invidious`) — **fit only** | AGPL-3.0 | v2.20260804.1 2026-08-05 · 5 (+ rolling dated builds) | `2.20260804.1`, dated `YYYY.MM.DD-sha` · amd64, arm64; **companion has no version tags** (rolling `latest`) [U] | statistics off by default [U] | yes — youtube.com, googlevideo.com … from the **household's IP** | none | Clipious, Yattee, FreeTube over HTTPS [I] | **needs `invidious-companion`** (replaces sig-helper and the po_token generator) [U]; PostgreSQL **14** (EOL Nov 2026) | "2 GB free RAM, restart regularly" [U] | `registration_enabled: true` by default [U] | **stop (recommended; operator decides)** — upstream: a bot check means "your IP is blocked from YouTube", 429s "for 24 hours", triggered by "someone on your network" [U]; on our boxes that IP is the household's. One bad period in 2026 (March, ~2 weeks, companion #274/#290) [R]. No report found of a family's phones being bot-checked [I: plausible, not shown] | | **moonlight-web** — two unrelated projects — **fit only** | GPL-3.0 both | MrCreativ3001/moonlight-web-stream: v2.10.0 (May) + v3 prereleases to 09-16 · linckosz/moonlight-web: v0.3.1 09-25 (since Jul) | `mrcreativ3001/moonlight-web-stream`, `ghcr.io/linckosz/moonlight-web` · amd64, arm64 | linckosz: update check + session metrics by default (`MW_NO_TELEMETRY=1`) [U] | linckosz: remote access through the author's rendezvous server [U] | **WebRTC over UDP** (40000-40100/udp; linckosz 48550-48573/udp, recommends `--network host`) [U] | a **WebSocket fallback exists in both** (needs HTTPS; more latency) [U] | needs **Sunshine/Apollo/Wolf on a gaming PC on the LAN** [U] | — | first login becomes admin (MrCreativ3001); PIN + admin password (linckosz) [U] | **stop** — usable latency needs UDP or host networking; through an HTTP tunnel only the high-latency fallback; a logged-in user controls the gaming PC's desktop [I] | diff --git a/documentation/backlog/OPEN-ITEMS.md b/documentation/backlog/OPEN-ITEMS.md index b02c0725..cf3dfa41 100644 --- a/documentation/backlog/OPEN-ITEMS.md +++ b/documentation/backlog/OPEN-ITEMS.md @@ -878,7 +878,7 @@ class (an image `VOLUME` at an unmounted path) is still live — `immich-server` | **R-764** | **[P3-LOW] wger sends no mail: its mail backend is the console, so a password-reset mail never leaves the box, and the template maps no SMTP.** READ 2026-10-01 inside the app on 9202 (checklist row 7.1): `EMAIL_BACKEND django.core.mail.backends.console.EmailBackend`; the settings read `ENABLE_EMAIL`, `EMAIL_HOST`, `EMAIL_PORT`, `FROM_EMAIL` …; the template carries no `smtp_mapping`. The household's admin can reset another member's password in the app; a member who forgets theirs and asks wger by e-mail gets nothing, and the page does not say so. Not measured: what wger shows after a reset request. **Needs:** an `smtp_mapping` (vaultwarden's shape, a fresh install with mail OFF booting — REUSE.md §2), or the page saying mail is not available. `audits/new-app-checklist-2026-10-01/C/C2-static-reads.txt` | **READY — rank P3-LOW; owner: CC (catalog)** | | **R-765** | **[P2-MEDIUM] A new app that builds its login from a `type: password` value at EVERY start loses the household's login on a restore after a remove — found on Radicale's first template, fixed before publishing.** MEASURED 2026-10-01 on 9202 (drill catalog): Radicale's start command rewrote its login file from `RADICALE_PASSWORD` at every start. Remove (keeping backups) + the household's restore → the right password answered 401. Cause, read in the controller: a restore carries only `type: secret` values (`stacks.PortableSecretEnvVars`, the D5 ruling — a `type: password` is an internet-reachable login and stays out of the drive's backup) and makes a NEW password when the guest's own copy is gone (`restore_unit.go:538`, „generated replacement … the page will not show the new value as the password”), expecting the app's login to come back WITH ITS DATA. Fix (catalog, same session): the login file is written on the FIRST start only and lives on the data volume; re-measured: remove + restore → the original login reads the event back. The checklist row that caught it is 2.5 (restore round trip); 1.9 names the shape. `audits/new-apps-2026-10-01/box/radicale-attempt1/restore.txt`, `box/radicale/restore.txt` | **CLOSED 2026-10-01 — Radicale's template, before publishing** | | **R-766** | **[P3-LOW] A new app's logo and screenshots reach the boxes only with the next HUB release — the website alone is not enough.** READ 2026-10-01: the box's asset syncer reads the hub's `/api/v1/assets/manifest` (`felhom-controller internal/assets/syncer.go`); the hub serves its PVC, seeded at start from `/usr/share/felhom/assets-seed/` baked into the hub IMAGE (`hub/Dockerfile:27`; the hub build copies `website/assets/*-logo.{svg,png}` and `*-screenshot-*.webp`, `hub/README.md`). So Radicale's assets (pushed `40f0742`) answer 200 on felhom.eu, and a box shows an app card without a logo until a hub build carries them. Today's fence kept the hub untouched. **Needs:** the next hub release (any) carries them — say so in its report; or a ruling that the hub reseeds from the website without a release. `audits/new-apps-2026-10-01/S/S3-assets.txt` | **READY — rank P3-LOW; owner: CC (next hub release)** | -| **R-767** | **[P3-LOW] MeTube has no login at all, by design, and the box has no permanent household-only door — so it is not built.** READ 2026-10-01 (fit table): upstream „MeTube deliberately has no login system … use HTTP basic auth, Authelia, or your proxy” (wiki, Reverse-proxy-configurations). Published on the household's subdomain, anyone who finds it can make the box download through the household's IP and use uploaded YouTube cookies. The box offers `setup_gate` (until setup) and `signup_block` only. **Two routes:** (a) a login-proxy sidecar inside the template (basic auth from a generated password, catalog-only — measure the phone/browser experience); (b) a controller feature: a permanent household-only door. Also owed before any build: the operator's word on the YouTube sentence (STATUS). `audits/new-apps-2026-10-01/FIT.md` | **WAITING-ON-OPERATOR — rank P3-LOW; owner: operator (route), CC builds** | +| **R-767** | **[P3-LOW] MeTube has no login at all, by design, and the box has no permanent household-only door — so it is not built.** READ 2026-10-01 (fit table): upstream „MeTube deliberately has no login system … use HTTP basic auth, Authelia, or your proxy” (wiki, Reverse-proxy-configurations). Published on the household's subdomain, anyone who finds it can make the box download through the household's IP and use uploaded YouTube cookies. The box offers `setup_gate` (until setup) and `signup_block` only. **Two routes:** (a) a login-proxy sidecar inside the template (basic auth from a generated password, catalog-only — measure the phone/browser experience); (b) a controller feature: a permanent household-only door. Also owed before any build: the operator's word on the YouTube sentence (STATUS). `audits/new-apps-2026-10-01/FIT.md` **UPDATE 2026-10-02 — CLOSED by route (b):** the operator said GO (`09` §3 decision 64); controller 0.287.0 has the family gate and MeTube is published behind it with NO exception (every path, the websocket included, measured on 9202: `audits/family-gate-2026-10-02/A/items.txt`, `audits/family-gate-2026-10-02/B/box/metube-fresh.txt`); the own-use sentence is on the page in both languages; `onboarding/metube.md`. | **CLOSED 2026-10-02 — controller v0.287.0 + catalog (MeTube published)** | | **R-768** | **[P3-LOW] Grimoire is not built: upstream rules out public exposure and publishes no image for its current line.** READ 2026-10-01: SECURITY.md „Public-network exposure is not a supported Grimoire mode”; docs/06-remote-access.md „General REST routes remain loopback-only and tokenless”; v1.x has no published image (the compose builds from source; Docker Hub is the old 0.x). Karakeep covers the bookmark need. **Reopens if** upstream publishes an image and supports authenticated remote use. `audits/new-apps-2026-10-01/FIT.md` | **WATCHING — rank P3-LOW; owner: CC (re-read at the next catalog campaign)** | | **R-769** | **[P3-LOW] Pinchflat is not built: upstream is paused (no release in 2026, last push 2025-12-16) and its last release has no image tag.** READ 2026-10-01: ghcr's newest version tag `v2025.6.6`, `latest` amd64 only; runs as root by default; an unanswered 30 GB yt-dlp memory report (#866); third parties call it unmaintained (community-scripts #15968). Forks with images exist (Pinchflat-NGX, MorganKryze). **Needs:** the operator's word on a fork (a new upstream), after the YouTube sentence (R-767). `audits/new-apps-2026-10-01/FIT.md` | **WAITING-ON-OPERATOR — rank P3-LOW; owner: operator** | | **R-770** | **[P3-LOW] Invidious — fit check only; the recommendation is not to build it.** READ 2026-10-01: playback needs `invidious-companion` (rolling `latest`, no version tags); PostgreSQL 14 (EOL 2026-11); `registration_enabled: true` by default; upstream: a bot check means „your IP is blocked from YouTube”, a 429 can last 24 h, triggered by „someone on your network” — on our boxes that IP is the household's. One bad period in 2026 (March, ~2 weeks). No report found of a family's other devices being bot-checked (inference). **Needs:** the operator's go / no-go. `audits/new-apps-2026-10-01/FIT.md` | **WAITING-ON-OPERATOR — rank P3-LOW; owner: operator** | @@ -886,19 +886,33 @@ class (an image `VOLUME` at an unmounted path) is still live — `immich-server` | **R-772** | **[P3-LOW] A health probe that finds NO container to probe records `healthy: true` — and the next check is 5 minutes away.** MEASURED 2026-10-01 on 9202 (controller 0.285.0, Karakeep): with the app container stopped, the probe's record read `{type: none, target: karakeep, healthy: true, message_key: health.no_probe_container}` and stayed so 4+ minutes (`RunHealthProbes: skipping karakeep — last check …, effective interval 5m0s, healthy=true`). The app's STATE did read `degraded` within 10 s, so the household saw it; the probe record alone says healthy about a check that did not run (the presence-is-not-success shape). Not checked: which readers use `health_probe.healthy` (the guarded Update's `verifying`? the hub report?). **Needs:** a not-run probe recorded as not-healthy (or `unknown`), with a test that pins it. `audits/new-apps-2026-10-01/box/karakeep-768M/neg-and-crawl.txt` **-- FIXED controller v0.286.1:** a not-run probe records `healthy: false, not_checked: true`, is looked at on the next tick (0.286.0 still waited out the last healthy record's 5-minute interval — found live, fixed in .1), the state stays the containers' (R-630 holds), the app page says so. Readers checked: `health_probe` feeds only the state override and the API/page; the hub gets container states, never the probe record. **Live on 9202 (0.286.1):** paperless-webserver stopped 19:48:33 → `healthy false, not_checked true` at 19:48:47; started 19:49:05 → healthy at 19:49:47. Red-proofs RP-D1, RP-D1b. `audits/visitors-2026-10-01/D/`. | **CLOSED 2026-10-01 — controller v0.286.1** | | **R-773** | **[P3-LOW] After a remove + restore, an app's sign-up route block (decision 47) is gone; only the app's own switch still refuses.** MEASURED 2026-10-01 on 9202 (Karakeep): before — `/signup` 403 and tRPC `users.create` 403; after the household removed the app (keeping backups) and pressed restore — `/signup` answers 200, `users.create` still 403 (the restored env keeps `DISABLE_SIGNUPS=true`). For an app with NO own switch (opengist, wishlist: block only) a remove + restore would reopen sign-up entirely — not measured. **Needs:** the restore re-applies the lock record's block (or the gate) for an app whose template has `signup_block`, with a test; then measured on a block-only app. `audits/new-apps-2026-10-01/box/karakeep/restore.txt` **-- FIXED controller v0.286.0:** a REMOVED app restored from its backup gets the lock record (`opened_by: restore`) and the block written before anything starts; the loop sets the app's own switch; an installed app the household never closed keeps what it had (decision 49). **Live on 9202:** Karakeep — before: `/signup` 403, `users.create` 403; removed keeping backups; restored → record `opened_by: restore`, `native_lock: applied`, block file present, a stranger's `/signup` 403 and `users.create` 403, the bookmark read back. Red-proof RP-D2. Block-only apps (opengist, wishlist) share the code path, not measured. `audits/visitors-2026-10-01/D/r773-live.txt`. | **CLOSED 2026-10-01 — controller v0.286.0** | | **R-774** | **[P3-LOW] Two things the new apps' pages do not show yet: Karakeep's mail-ON path is unproven, and its official phone app reports crashes to its makers.** READ/MEASURED 2026-10-01: Karakeep's `smtp_mapping` (plaintext :2526, as Cal.com) was proven only with mail OFF (a fresh install boots) — 9202 has no hub, so the relay cannot be exercised there; the official mobile app ships Sentry crash reporting with a hard-coded DSN (`apps/mobile/app/_layout.tsx`, FIT.md). **Needs:** one password-reset mail from Karakeep on a hub-enabled box (demo-hp 9201, a throwaway install); and a sentence on the page about the phone app (copy, freeze). `audits/new-apps-2026-10-01/bench/karakeep-mail-off-boot.txt` | **READY — rank P3-LOW; owner: CC (catalog)** | -| **R-775** | **[P2-MEDIUM] Grimmory: a stranger's 5 wrong sign-ins lock EVERY visitor out of the web login for 15 minutes — so Grimmory was not published.** MEASURED 2026-10-01 on 9202 (drill catalog, v3.4.1, through traefik): after 5 wrong tries for `admin` every further sign-in answered 429 — the household's right password AND a different name — and stayed 429 for 10+ minutes of retries. Read in the jar: `AuthRateLimitService` — Caffeine `expireAfterWrite(ofMinutes(15))`, `MAX_ATTEMPTS 5`, keys `login:ip:` and `login:user:`; Spring `forward-headers-strategy: native` takes the address from X-Forwarded-For, and behind the tunnel every visitor is the tunnel container's address (R-753) — the wger shape (R-752), with no setting to change it. Everything else in the checklist passed (bench + box step v3.4.1 → v3.5.0, gate by its own probe, OPDS through traefik); two smaller findings for the publishing session: on a reinstall over the first install's kept books, a new upload was saved to the drive but not added to the library (`box/grimmory/reinstall-c1.txt`, not investigated); and the remove + restore round trip (2.5) cannot be shown on 9202 for a drive app — its backup lives on the scratch drive, which is not a registered drive (R-756). The template waits in `audits/new-apps-2026-10-01/wip/grimmory/`. **Needs (operator):** (A) publish with a sentence on the page that wrong guesses by others can lock the login for 15 minutes (MEASURED: during the lock an e-reader's OPDS feed still answered 200 with its own login, wrong 401 — `box/grimmory/opds-under-lock.txt`), or (B) wait until the box passes each visitor's real address (R-753). `audits/new-apps-2026-10-01/box/grimmory/throttle.txt` **-- 2026-10-01 (evening):** option B's precondition SHIPPED (controller v0.286.1, R-753): Grimmory's Tomcat RemoteIpValve walks from the right and counts `172.16.0.0/12` as a proxy (READ in source, Spring Boot 4.1.1 — not yet measured with Grimmory's own lock), so `login:ip:` becomes per visitor; `login:user:` still lets a stranger lock the public name `admin` 15 min. A third route was spiked and passed: Grimmory behind the permanent family gate with its e-reader paths excepted (R-780). Recommendation: publish behind the family gate if R-780 is built; otherwise B with a measured 3.6. | **WAITING-ON-OPERATOR — rank P2-MEDIUM; owner: operator (A / B / behind R-780), CC publishes** | +| **R-775** | **[P2-MEDIUM] Grimmory: a stranger's 5 wrong sign-ins lock EVERY visitor out of the web login for 15 minutes — so Grimmory was not published.** MEASURED 2026-10-01 on 9202 (drill catalog, v3.4.1, through traefik): after 5 wrong tries for `admin` every further sign-in answered 429 — the household's right password AND a different name — and stayed 429 for 10+ minutes of retries. Read in the jar: `AuthRateLimitService` — Caffeine `expireAfterWrite(ofMinutes(15))`, `MAX_ATTEMPTS 5`, keys `login:ip:` and `login:user:`; Spring `forward-headers-strategy: native` takes the address from X-Forwarded-For, and behind the tunnel every visitor is the tunnel container's address (R-753) — the wger shape (R-752), with no setting to change it. Everything else in the checklist passed (bench + box step v3.4.1 → v3.5.0, gate by its own probe, OPDS through traefik); two smaller findings for the publishing session: on a reinstall over the first install's kept books, a new upload was saved to the drive but not added to the library (`box/grimmory/reinstall-c1.txt`, not investigated); and the remove + restore round trip (2.5) cannot be shown on 9202 for a drive app — its backup lives on the scratch drive, which is not a registered drive (R-756). The template waits in `audits/new-apps-2026-10-01/wip/grimmory/`. **Needs (operator):** (A) publish with a sentence on the page that wrong guesses by others can lock the login for 15 minutes (MEASURED: during the lock an e-reader's OPDS feed still answered 200 with its own login, wrong 401 — `box/grimmory/opds-under-lock.txt`), or (B) wait until the box passes each visitor's real address (R-753). `audits/new-apps-2026-10-01/box/grimmory/throttle.txt` **-- 2026-10-01 (evening):** option B's precondition SHIPPED (controller v0.286.1, R-753): Grimmory's Tomcat RemoteIpValve walks from the right and counts `172.16.0.0/12` as a proxy (READ in source, Spring Boot 4.1.1 — not yet measured with Grimmory's own lock), so `login:ip:` becomes per visitor; `login:user:` still lets a stranger lock the public name `admin` 15 min. A third route was spiked and passed: Grimmory behind the permanent family gate with its e-reader paths excepted (R-780). Recommendation: publish behind the family gate if R-780 is built; otherwise B with a measured 3.6. **UPDATE 2026-10-02 — NARROWED, Grimmory PUBLISHED behind the family gate:** a stranger cannot reach Grimmory's web sign-in at all (6 tries through the simulated tunnel: the gate's 401, then the household signs in 200 — `audits/family-gate-2026-10-02/A/items.txt`), and the e-reader exceptions keep Grimmory's own login. The 2.5 round trip now WORKS on 9202 (`audits/family-gate-2026-10-02/B/box/life.txt`). **What is left:** a family member past the gate can still lock a NAME (the admin's) for 15 minutes with 5 wrong tries — hard-coded in Grimmory; and the reinstall-over-kept-books finding (`new-apps-2026-10-01/box/grimmory/reinstall-c1.txt`) is still not investigated. | **WATCHING — rank P3-LOW; owner: CC** | | **R-776** | **[P3-LOW] Right-walking catalog apps need ONE setting to see each visitor since v0.286 (R-753); without it they keep the tunnel's one address (a stranger can still trip their per-address limits for everyone).** READ in source (`audits/visitors-2026-10-01/A/sweep/`): kimai `TRUSTED_PROXIES=127.0.0.1,172.16.0.0/12`; zipline `CORE_TRUST_PROXY=true` + `CORE_TRUSTED_PROXIES=172.16.0.0/12`; vikunja `VIKUNJA_SERVICE_IPEXTRACTIONMETHOD=xff`; nextcloud `TRUSTED_PROXIES=172.16.0.0/12`; n8n `N8N_PROXY_HOPS=2` (optional). BookStack `APP_PROXIES` measured this session (see its catalog commit). **Needs:** per app, the setting + checklist 3.6 re-measured on 9202 through the simulated tunnel (the BookStack shape, `audits/visitors-2026-10-01/tools/bookstack_36.py`). Count readers (calibre-web, tandoor, wger) stay as they are — a fixed count is wrong for one of the two paths. | **READY — rank P3-LOW; owner: CC (catalog)** | | **R-777** | **[P2-MEDIUM] Emby and Jellyfin treat every internet visitor as being on the LAN — users with "remote access" off can sign in from the internet, IP filters and remote limits are skipped.** READ in source (`audits/visitors-2026-10-01/A/sweep/sweep-1.md`, `sweep-2.md`), not measured live: Jellyfin with `KnownProxies` empty uses the TCP peer (traefik, private) → "LAN"; Emby reads the leftmost XFF (its chain is now removed by the R-753 reset, so it sees cloudflared's private address → "LAN", as before). True before R-753 too; R-753 neither caused nor fixed it. **Needs:** measure on 9202 (a user with remote access off, through the simulated tunnel); Jellyfin: `KnownProxies` `172.16.0.0/12` in `network.xml` (no env — an `after_install` or a seed file); Emby: no setting fixes it (its `LocalNetworkSubnets` still counts private ranges) — a page sentence or a decision. | **READY — rank P2-MEDIUM; owner: CC (measure), operator (Emby route)** | | **R-778** | **[P3-LOW] A box that rolls back to a controller ≤ 0.285 after v0.286 keeps the new traefik (an old controller never rewrites a running traefik) — and the old `clientIP` believes the LEFTMOST X-Forwarded-For, which a stranger then writes: the dashboard's login counter becomes dodgeable until the box moves forward again.** Reasoned from the code (old `claim.go` clientIP + v0.286 traefik trust), not measured. The floor never moves back; the window is the self-update's crash roll-back. **Needs:** decide whether that window matters (it closes at the next floor); if it does, a 0.285.x patch that reads the rightmost hop, or the self-update refusing to roll back across v0.286. | **OPEN — rank P3-LOW; owner: CC** | | **R-779** | **[P3-LOW] Part A's "two outside addresses seen as two" is proven through the simulated tunnel only; on the REAL tunnel the second outside address (ep0, one request allowed) was refused by Cloudflare's edge with 403 and never reached the box.** Measured 2026-10-01 19:51 UTC (`audits/visitors-2026-10-01/A/L2-demo-hp-real-tunnel.txt`): no log line on demo-hp; demo-hp's box has no geo restriction in its settings, so a Cloudflare ZONE rule (country or bot, not read) refused a German datacenter address. DooPlex's own address on the real tunnel was seen as itself. **Needs:** one sign-in from a second Hungarian address (the operator's phone off wifi) while DooPlex is locked out — 2 minutes; and say which Cloudflare rule refused ep0. | **WAITING-ON-OPERATOR — rank P3-LOW; owner: operator (a phone), CC reads the logs** | -| **R-780** | **[P2-MEDIUM] A permanent household gate with family accounts — the spike PASSED; the build waits for the operator's go (`09` §3 decision 63).** Measured on 9202 2026-10-01 (`audits/permanent-gate-2026-10-01/VERDICT.md`, exit test committed before): a stranger reached nothing of Grimmory or MeTube (36 requests incl. websockets); family members with their own logins got in for 30 days, logout worked; a stranger's guesses locked only the stranger; Grimmory's OPDS/Kobo/KOReader worked through anchored path exceptions with the app's own login; the family login never opened the dashboard; +0.4 ms per request; the gate down → apps refuse (500). **Build requirement F1:** exceptions must be anchored (`PathPrefix(/api/v1/opds)` also matched `/api/v1/opdsx`). **Cost:** 2 sessions (controller release; catalog — Grimmory and MeTube published behind it). If nothing is decided: Grimmory stays held (R-775) and MeTube unpublished (R-767). | **WAITING-ON-OPERATOR — rank P2-MEDIUM; owner: operator (go/no-go), CC builds** | +| **R-780** | **[P2-MEDIUM] A permanent household gate with family accounts — the spike PASSED; the build waits for the operator's go (`09` §3 decision 63).** Measured on 9202 2026-10-01 (`audits/permanent-gate-2026-10-01/VERDICT.md`, exit test committed before): a stranger reached nothing of Grimmory or MeTube (36 requests incl. websockets); family members with their own logins got in for 30 days, logout worked; a stranger's guesses locked only the stranger; Grimmory's OPDS/Kobo/KOReader worked through anchored path exceptions with the app's own login; the family login never opened the dashboard; +0.4 ms per request; the gate down → apps refuse (500). **Build requirement F1:** exceptions must be anchored (`PathPrefix(/api/v1/opds)` also matched `/api/v1/opdsx`). **Cost:** 2 sessions (controller release; catalog — Grimmory and MeTube published behind it). If nothing is decided: Grimmory stays held (R-775) and MeTube unpublished (R-767). **UPDATE 2026-10-02 — BUILT (decision 64):** controller v0.287.0 — `internal/family` (own name + password per member, bcrypt, sessions asked on every request), the Család card, `family_gate:` / `family_gate_except:` (anchored, F1) / `min_controller:` in the template, the catalog gate `check-family-gate.py`. Exit items 1-5 proven live on 9202 through the product (`audits/family-gate-2026-10-02/A/items.txt`); both demo boxes on 0.287.0 by the floor (`audits/family-gate-2026-10-02/E/floor.txt`); Grimmory and MeTube published behind it. | **CLOSED 2026-10-02 — controller v0.287.0** | | **R-781** | **[P3-LOW] The catalog's `scripts/test_gate_decoys.py` fails 4 of its own "genuine" onboarding cases — on the untouched tree.** Measured 2026-10-01 (same 4 FAIL lines before and after this session's checklist edit): the harness copies the catalog into a temp tree with a fake sibling `felhom.eu`, and the REAL published records (radicale, karakeep, dawarich) name evidence that the fake sibling lacks, so a complete record reads incomplete. The gate itself (`onboarding`) is green on the real tree. **Needs:** the genuine cases build their own records, or the fake sibling mirrors the cited evidence. | **READY — rank P3-LOW; owner: CC (catalog)** | | **R-782** | **[P3-LOW] Two side observations of the R-753 sweep, inferred, not measured:** glance's seeded `glance.yml` has no `auth:` block (the dashboard is public to anyone with the address), and homepage's `/api/*` refuses a Host not in `HOMEPAGE_ALLOWED_HOSTS`, which the template does not set (widgets may 400). **Needs:** measure both on 9202; glance: decide whether a public link dashboard is intended (the setup gate does not cover it after setup). | **READY — rank P3-LOW; owner: CC (catalog)** | | **R-783** | **[P3-LOW] SparkyFitness: three wrong sign-ins by anyone shut EVERY visitor out of sign-in for ~10 s — a stranger retrying every 10 s keeps the household out.** MEASURED 2026-10-01 on 9202 through the simulated tunnel (`audits/visitors-2026-10-01/C/box/sparky-box.txt`): better-auth's sign-in limit (3 per 10 s) is keyed on one address — it reads the LEFTMOST X-Forwarded-For, whose chain the R-753 router reset removes, so its frontend nginx hands it traefik's address; the household from another address got 429 at 3 s and 10 s, in at 16 s. Not forgeable (a rotating forged address did not escape). better-auth's header setting is not exposed as an env by SparkyFitness. **Needs:** accept (10 s), or an upstream setting for better-auth's `ipAddressHeaders` + a right-walking reader. | **OPEN — rank P3-LOW; owner: CC** | -| **R-784** | **[P2-MEDIUM] SparkyFitness's licence forbids commercial use: "may not be used, directly or indirectly, in any product, service … intended for … commercial advantage … without prior written permission from the author" — and Felhom is a paid service that offers it in its catalog.** READ 2026-10-01 (`audits/visitors-2026-10-01/C/C0-license.txt`): a custom licence (GitHub: NOASSERTION), the same at the pinned tag v0.17.3 and at `main`; termination clause 7 ("cease all use"). SparkyFitness was named as wger's replacement for fitness. **Needs (operator):** (A) hide it from new installs (`lifecycle: hidden`) until the author gives written permission, and ask; (B) ask first and keep it offered meanwhile; (C) keep it. Recommended A. If nothing is decided it stays offered. | **WAITING-ON-OPERATOR — rank P2-MEDIUM; owner: operator** | +| **R-784** | **[P2-MEDIUM] SparkyFitness's licence forbids commercial use: "may not be used, directly or indirectly, in any product, service … intended for … commercial advantage … without prior written permission from the author" — and Felhom is a paid service that offers it in its catalog.** READ 2026-10-01 (`audits/visitors-2026-10-01/C/C0-license.txt`): a custom licence (GitHub: NOASSERTION), the same at the pinned tag v0.17.3 and at `main`; termination clause 7 ("cease all use"). SparkyFitness was named as wger's replacement for fitness. **Needs (operator):** (A) hide it from new installs (`lifecycle: hidden`) until the author gives written permission, and ask; (B) ask first and keep it offered meanwhile; (C) keep it. Recommended A. If nothing is decided it stays offered. **UPDATE 2026-10-02 — DECIDED (`09` §3 decision 65, option B):** SparkyFitness stays offered; the operator asks the author. The request is drafted (NOT sent): `audits/licences-2026-10-02/EMAIL-DRAFT-sparkyfitness.md` — the author publishes no e-mail; the routes are the project's Discord (private, recommended) or a GitHub Discussion. **Trigger:** no written permission before the first paying customer → `lifecycle: hidden` (a STATUS standing item). | **WAITING-ON-OPERATOR — rank P2-MEDIUM; owner: operator (send the request; the answer)** | | **R-785** | **[P3-LOW] SparkyFitness is pinned 11 releases and a major behind upstream (v0.17.3; upstream v1.7.3, v1.6.0 dated 2026-07-24).** READ 2026-10-01 (`audits/visitors-2026-10-01/C/bench/C1-previous-tag.txt`). **Needs:** an update walk 0.17 → 1.x through the ladder (bench + box), after R-784 is decided. | **OPEN — rank P3-LOW; owner: CC (after R-784)** | | **R-786** | **[P3-LOW] SparkyFitness's onboarding record has six open rows** (`app-catalog-felhom.eu/onboarding/sparkyfitness.md`): 0.5 runtime internet (food search providers), 0.7 the phone app's sign-in route through traefik, 1.6 the env names the server reads, 1.7 the entrypoint read, 5.4 a second memory watch at another limit, 8.3 no logo/screenshots on felhom.eu (404). Everything else measured this session (bench + 9202). **Needs:** each row measured, or n/a with a reason. | **READY — rank P3-LOW; owner: CC (catalog)** | -| **R-787** | **[P2-MEDIUM] No catalog app's LICENCE has been checked against Felhom being a paid service; the first look (SparkyFitness, R-784) found a non-commercial-only licence.** The new-app checklist row 0.1 binds apps opened from 2026-10-01; the 53 exempt apps were never read. **Needs:** a read of every template's licence at its pinned tag (SPDX + any custom terms), one table, and the non-open ones to the operator. | **READY — rank P2-MEDIUM; owner: CC (read), operator (decisions)** | +| **R-787** | **[P2-MEDIUM] No catalog app's LICENCE has been checked against Felhom being a paid service; the first look (SparkyFitness, R-784) found a non-commercial-only licence.** The new-app checklist row 0.1 binds apps opened from 2026-10-01; the 53 exempt apps were never read. **Needs:** a read of every template's licence at its pinned tag (SPDX + any custom terms), one table, and the non-open ones to the operator. **UPDATE 2026-10-02 — READ:** all 58 templates (56 published + Grimmory + MeTube), every image, at the pinned tag — one table: `audits/licences-2026-10-02/TABLE.md` (method in `read-1.md`, `read-2.md`). OSI-approved: all but the rows below. Not OSI / limiting, each now its own row: Tandoor (Commons Clause, R-789), Emby (R-790), n8n (R-791), Plex (R-792), the EE/BUSL parts of Cal.com, Docmost, Outline and meilisearch (R-793), redis 7.4 in seven apps (R-794), recipe-importer has no licence file (R-795); SparkyFitness is R-784. Nothing was hidden or changed because of the read (the brief's rule). New apps carry their licence in record row 0.1. | **CLOSED 2026-10-02 — the read (decisions are R-784, R-789..R-795)** | +| **R-788** | **[P3-LOW] The volume-persistence gate reads an EMPTY declared volume as CLEAN — MeTube's `/state` passed while the app had written nothing there.** MEASURED 2026-10-02 on the bench (`audits/family-gate-2026-10-02/C-metube-bench/C2-volume-persistence-metube.txt`): "declared volume /state is EMPTY" → exit 0. The catalog's CLAUDE.md says an app that wrote nothing is UNDETERMINED (exit 2), never a pass. MeTube's real proof came from elsewhere (the history read back after a recreate and after a restore). **Needs:** the gate treats an empty declared volume as undecided, or the app gets an exerciser (MeTube: one `POST /add`); a decoy that is an app writing nothing. **UPDATE 2026-10-02:** part of the cause was R-801 (no port → no exercise at all); the catalog now also has `APP_EXERCISE` (MeTube: one `POST /add`). The empty-volume → CLEAN path itself is still to be checked. | **READY — rank P3-LOW; owner: CC** | +| **R-789** | **[P2-MEDIUM] Tandoor's licence is AGPL-3.0 WITH the Commons Clause: it forbids selling "a product or service whose value derives, entirely or substantially, from the functionality of the Software" — fees for hosting or support included.** READ 2026-10-02 at 2.6.15 (`audits/licences-2026-10-02/TABLE.md`). Felhom charges for installing and caring for the household's apps; whether that value comes "substantially" from Tandoor is the question. **Needs (operator):** keep (the fee is for the box, not Tandoor), hide for new installs, or ask the authors. Nothing changed meanwhile. | **WAITING-ON-OPERATOR — rank P2-MEDIUM; owner: operator** | +| **R-790** | **[P2-MEDIUM] Emby is proprietary: a personal, non-commercial, non-transferable licence.** READ 2026-10-02 (emby.media/terms.html; `audits/licences-2026-10-02/TABLE.md`). The household's private use fits; Felhom installs the official image from Emby (no copy distributed) as part of a paid service, a context the licence does not cover. Jellyfin (GPL) offers the same. **Needs (operator):** keep, hide, or ask Emby. | **WAITING-ON-OPERATOR — rank P2-MEDIUM; owner: operator** | +| **R-791** | **[P3-LOW] n8n's Sustainable Use License allows own internal or personal use, and distribution only free of charge for non-commercial purposes.** READ 2026-10-02 at 2.42.1 (`audits/licences-2026-10-02/TABLE.md`). The household's own use is allowed; Felhom pulling the unchanged official image onto the household's box is arguably not distributing — a grey zone. **Needs (operator):** keep (recommended reading) or ask n8n. | **WAITING-ON-OPERATOR — rank P3-LOW; owner: operator** | +| **R-792** | **[P3-LOW] Plex is proprietary (Plex Terms of Service): the HOUSEHOLD is the licensee under its own Plex account.** READ 2026-10-02 (the ToS page answered 403; quoted from a search snippet — `audits/licences-2026-10-02/TABLE.md`). Felhom gets no right and installs it as the household's agent; selling Plex or Plex Pass is not covered. **Needs (operator):** keep as is, or a page sentence that the Plex account is the household's own. | **WAITING-ON-OPERATOR — rank P3-LOW; owner: operator** | +| **R-793** | **[P3-LOW] Enterprise / BUSL code ships inside four open images — Cal.com and Docmost (EE folders, off without a key), Outline (BUSL-1.1: no commercial "Document Service"), meilisearch v1.36 in Wanderer (EE modules).** READ 2026-10-02 (`audits/licences-2026-10-02/TABLE.md`). Each is fine as the catalog runs them: no EE key, the household's own Outline is not a Document Service, Wanderer uses plain search. **Watch:** never turn on an EE feature, never switch Karakeep's/Wanderer's meilisearch to the `-enterprise` image, and re-read on each major. | **WATCHING — rank P3-LOW; owner: CC** | +| **R-794** | **[P3-LOW] redis 7.4 (RSALv2 / SSPL, not OSI) runs as a private cache in seven apps: dawarich, docmost, immich, nextcloud, outline, paperless-ngx, romm.** READ 2026-10-02 (`audits/licences-2026-10-02/TABLE.md`). Read as permitted (a private cache only its app uses is not Redis offered as a service — inferred). Valkey (BSD-3) or redis 8 (AGPL option) removes the question. **Needs:** a ladder step per app to valkey or redis 8, through the harness — no hurry. | **READY — rank P3-LOW; owner: CC** | +| **R-795** | **[P3-LOW] recipe-importer, Felhom's own image, has no licence file.** READ 2026-10-02 (`audits/licences-2026-10-02/TABLE.md`): all rights reserved by default; its dependencies are permissive (inferred from requirements.txt). **Needs (operator):** pick a licence (or none, on purpose) and add the file. | **WAITING-ON-OPERATOR — rank P3-LOW; owner: operator** | +| **R-796** | **[P3-LOW] MeTube's "send to MeTube" helpers (browser extensions, bookmarklets, phone apps) cannot work behind the family gate.** READ 2026-10-02 (`audits/family-gate-2026-10-02/B/metube-reads/reads-2026.09.29.txt`): they call `/add` from another site and carry no family login; the template sets no CORS origin and MeTube has NO exception by design (an exception on an app with no login would be an open door). The household pastes links in the page (first_steps). **Needs:** nothing unless households ask; a way would be a per-member token the gate accepts on `/add` only — a design question, not a fix. | **WATCHING — rank P3-LOW; owner: CC** | +| **R-797** | **[P3-LOW] `check-family-gate.py` rule 3 (a family_gate template needs a baked golden ≥ 0.287.0) is checked only where the felhom.eu sibling exists — CI's single clone cannot.** MEASURED 2026-10-02 (`audits/family-gate-2026-10-02/C-metube-bench/C3b-family-gate-with-sibling.txt`): without the sibling the gate printed NOT CHECKED and its summary read OK. The summary line now says "rule 3 … NOT CHECKED here". The pre-push hook (with the sibling) is where it bites. **Needs:** nothing more unless CI gets the sibling. | **WATCHING — rank P3-LOW; owner: CC** | +| **R-798** | **[P3-LOW] Grimmory's template sets `SWAGGER_ENABLED=false`, which v3.5.0 does not read (it reads `API_DOCS_ENABLED`, default false).** READ 2026-10-02 (`audits/family-gate-2026-10-02/B/grimmory-reads/upstream-v3.5.0.txt`). Harmless today — the API docs are off by default. **Needs:** remove the dead line or set the right name, on the next Grimmory step. | **READY — rank P3-LOW; owner: CC** | +| **R-799** | **[P3-LOW] The MeTube fixture's `POST /add` leaves out `download_type`, which upstream's validator lists as required.** MEASURED 2026-10-02 (`audits/family-gate-2026-10-02/C-metube-bench/`): both 2026.09.28 and .29 answered 200 and downloaded anyway. Fragile if a later tag enforces it. **Needs:** add `download_type: video` to the fixture with the next MeTube step. | **READY — rank P3-LOW; owner: CC** | +| **R-800** | **[P3-LOW] Removing MeTube "keeping data" answers `hdd_paths_preserved: []` although its downloads stay on the drive.** MEASURED 2026-10-02 on 9202 (`audits/family-gate-2026-10-02/B/box/life.txt`, `audits/family-gate-2026-10-02/B/box/final.txt`): the remove parses MeTube's compose as "0 HDD mounts" — its only drive mount is `${USERDATA_PATH}`, not `${HDD_PATH}` — so the result lists nothing kept; the files WERE kept and the restore used them. Other userdata-only apps (jellyfin, komga, …) have the same shape. **Needs:** check what the household's remove dialog says for such an app; list userdata mounts as kept. | **READY — rank P3-LOW; owner: CC** | +| **R-801** | **[P2-MEDIUM] The volume-persistence gate never sent a request to ANY app: it read the routed port from label VALUES while `docker compose config --format json` puts the port in the label NAME.** MEASURED 2026-10-02 on the bench (`audits/family-gate-2026-10-02/B/volume-persistence-metube-exerciser-diag.txt`: `"ports": []`, `"exercise": []` for MeTube; the label shape read on Compose 2.26.1). So every verdict since the gate exists came only from what an app writes at start by itself; the GET exercise and the deep second pass never ran. **Fixed in the catalog** (`routed_ports()`, key=value; unit tests, red-proofed: `audits/family-gate-2026-10-02/B/RP-R801-routed-ports.txt`). **Still owed:** a full re-sweep of all 58 templates on the bench with the fixed gate — an app whose data path is written only on a request may now read differently. | **READY — rank P2-MEDIUM; owner: CC (the re-sweep)** |