Part C: R-644, R-763, R-764 closed (wger's fixes proven on 9202), R-762/R-717 stopped with reasons; STATUS, CONTEXT, REPORT (142 -> 137; 0 opened, 5 closed)
gates / gates (push) Successful in 2m43s
gates / gates (push) Successful in 2m43s
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_0159rPz1ZhFKsS53msqPYxtS
This commit is contained in:
@@ -24,6 +24,8 @@
|
||||
> five conditions; the invite runs inside the box, the token never leaves it); the Tester 1 box is allowed but the walk
|
||||
> has no route there. Vaultwarden's first ladder step 1.36.0-alpine → 1.37.4-alpine is live in the catalog (`ecb8552`).
|
||||
> R-469 re-read: NOT removable — the engine gate is now decision 35's permanent per-app check (operator: close or keep).
|
||||
> Part C: R-644, R-763, R-764 closed (wger's two fixes proven on 9202; wger stays hidden for R-762); R-717 needs a lift
|
||||
> command in `after_setup` first. Register 142 → 137 (0 opened, 5 closed).
|
||||
|
||||
> **2026-10-06 (midday) — the operator's ten answers (`09` §3 139–148, „A" for all; CC's own pick differed on 140, 146,
|
||||
> 147).** Register 150 → 142 (2 opened: R-890 vaultwarden ladder, R-891 a stale CLAUDE.md line; 10 closed). Releases:
|
||||
|
||||
@@ -1,82 +1,147 @@
|
||||
# REPORT — the operator's ten answers built (2026-10-06, rulings `09` §3 139–148)
|
||||
# REPORT — instruction files kept true; vaultwarden on the ladder; the burn-down continued (2026-10-06 afternoon)
|
||||
|
||||
| Part | Result |
|
||||
|---|---|
|
||||
| **A** — the box changes (1, 4, 5) | **done** — R-444 weekly trim (measured on demo-hp first, then built, delivered, seen working by itself), R-645 version skip, R-856 crash-boot grace; agent v0.149.0 + bundle, controller v0.300.0 + golden 0.300.0, hub v0.139.0, all on demo-hp, demo-felhom, Tester 1 |
|
||||
| **B** — the backup leftovers (2) | **done** — runbook written; read-only listing of ep0: **no leftover exists**, nothing deleted, real counts unchanged; the box's warning names the runbook |
|
||||
| **C** — the page sentences (6, 10) | **done** — mealie and Karakeep, hu + en, live in the catalog |
|
||||
| **D** — the test tools (3, 7, 8, 9) | **done** — R-618 closed by ruling; R-734 marker list; R-624 bench seed proven on a recreated bench; R-502 full-run gate, first real run green with its decoy convicted |
|
||||
| **A** — the instruction-file rule (R-891, R-469, sweep) | **done** — rule in all four copies of `unprompted-work.md` §5 and `PROMPT-TEMPLATE.md` §9 rule 9; R-891 fixed and closed; R-469 re-read: NOT removable, narrowed (an operator question); sweep: 26 factual edits in four repos (list below). No permission prompt or refusal came up. |
|
||||
| **B** — vaultwarden on the update ladder (R-890) | **done** — the test-box admin seed built (catalog `6b4877d`, red-proved); 1.36.0-alpine → 1.37.4-alpine proven on bench 9401 and on 9202; written by `--write-ladder` (catalog `ecb8552`); R-890 closed |
|
||||
| **C** — the burn-down | **3 closed** (R-644, R-763, R-764), **2 stopped with the reason written** (R-762 medium, R-717 needs a design) |
|
||||
|
||||
| Rows before | Rows after | Opened | Closed |
|
||||
|---|---|---|---|
|
||||
| **150** | **142** | **2** (R-890, R-891) | **10** |
|
||||
|
||||
Closed: R-444, R-99, R-618, R-645, R-856, R-747, R-734, R-624, R-502, R-774.
|
||||
| **142** | **137** | **0** | **5** (R-890, R-891, R-644, R-763, R-764) |
|
||||
|
||||
## Baselines and rulings
|
||||
|
||||
Verified 11:07: felhom.eu `8f40b3ce26` (the operator's own „cleaned reports" commit on top of `fe998b8`), controller
|
||||
`13bda270c3` (v0.299.0), agent `37e98f452b` (v0.148.0), catalog `d1a148408f`, register 150. The ten rulings were recorded
|
||||
first (`09` §3 139–148, `8c65ff0c`), with the note that CC's own pick differed on 2, 8 and 9.
|
||||
|
||||
**The operator's cleanup removed every `REPORT*.md`.** Two felhom.eu checks read `REPORT.md`: the decoy test now plants a
|
||||
missing file and removes it again (`8c65ff0c`), and this file is the session's own report, as the repo rule says.
|
||||
Verified at the start: felhom.eu `7d0dffcf34`, controller `36088fd82e` (v0.300.0), agent `cefdc731a4` (v0.149.0),
|
||||
catalog `65130c6c03`; register 142. Read: every repo's `CLAUDE.md` and `.claude/rules/`, `REPORT.md`, R-890, R-891,
|
||||
R-469. The two rulings were recorded first as `09` §3 decisions 149 and 150, with the reviewer's error recorded in 150.
|
||||
Architecture read: `09-update-architecture.md` §3 (decisions 16, 35, 42, 146), §6.4 part 4 (the ladder writer), §6.5
|
||||
(the drill catalog).
|
||||
|
||||
## Part A
|
||||
|
||||
- **R-444, measured first** (demo-hp, 09:14Z, `audits/ten-answers-2026-10-06/r444-measure.txt`): `pct fstrim 9201` rc 0 in
|
||||
24.4 s; thin pool 65.53 % → 33.40 %; 18/18 app probes 200, slowest 1.1 s. **Built** (agent `ee71abd`): weekly, due
|
||||
Wednesday from 10:00 host-local, starts only 10:00–20:59, under the one-heavy-op gate (backup, restore-test, OS steps),
|
||||
3 tries a week, persisted, reported as `guest_disk_trim`; ONE sudo rule `/usr/sbin/pct ^fstrim [0-9]+$`. Hub
|
||||
(`a411cde7`): System page „Last disk trim". **Live:** `sudo -l` on demo-hp and demo-felhom allows the trim and refuses
|
||||
`--ignore-mountpoints`, `;x`, a second vmid and `pct destroy`. The job's first catch-up try ran 2 minutes after the agent
|
||||
update and failed (the bundle had not landed — expected); the hourly retry at 12:56 local logged
|
||||
`fstrim: guest 9201 trimmed 2.1 GiB in 2.4s` and the hub page read „10 min ago · 2.1 GiB" (`r444-live.txt`).
|
||||
- **R-645** (controller `2d63714`): every night leg skips an app whose pin is not what it runs; one amber line on the
|
||||
backups page. Red-proved on the hand-lift shape. **Residual, stated:** once the boot reconciler starts the app on the
|
||||
new version, pin = running again — a design question, not built.
|
||||
- **R-856** (controller `c393d85` + agent route `GET /host/crash-guard`): after a crash boot, app mails wait 15 min.
|
||||
Live through the real route: demo-hp logged the normal 90 s because its last crash boot (2026-10-05) was not this
|
||||
start; demo-felhom logged a clean boot. **The crash branch was not shown live** (no crash allowed); tests cover it.
|
||||
**The rule** — `.claude/rules/unprompted-work.md` §5 „Instruction files", the operator's text verbatim plus the
|
||||
permission-check sentence; identical in felhom.eu, felhom-controller, app-catalog-felhom.eu and the workspace root's
|
||||
unversioned copy (md5 `c1e6c881…` for all four). `documentation/PROMPT-TEMPLATE.md` §9, rule 9. felhom-agent has no copy
|
||||
of the shared rule file (it never had one); adding one is a rule change, left for the operator.
|
||||
|
||||
## Part B — ep0, read only
|
||||
**R-469 — not met.** R-463 closed (8 of 11 PostgreSQL apps converted by the box; zipline, adventurelog, immich stay on 16
|
||||
by decision 42), but the engine gate now enforces decision 35: a PostgreSQL major passes only with a two-venue ladder
|
||||
entry carrying `engine_conversion`. Removing it would let an unproven major ship, and the image refuses to start on the
|
||||
old datadir. That is a loosened fence. Nothing is left for CC to build; the row asks the operator to close it (CC's pick)
|
||||
or keep it.
|
||||
|
||||
Every snapshot of datastore `felhom-offsite`, both from the server's API and from the directories: 9 snapshots in 5
|
||||
namespaces, all ≥ 369,808,250 B, all verification `ok`, every directory with its manifest. **No phantom; nothing deleted.**
|
||||
Runbook `runbooks/pbs-phantom-cleanup.md` (the list command, the two-part test, one `api delete` per proven phantom, the
|
||||
before/after count control) + `runbooks/pbs-phantom-list.py`; the agent's WARN now names the runbook (`be398f9`).
|
||||
**Every instruction-file edit** (before → after, why). All factual; none loosens a rule.
|
||||
|
||||
## Part C and D
|
||||
felhom.eu
|
||||
1. `CLAUDE.md` „Gates — ONE entry point": a list of ten gates + „`--fast` … today that is all of them" → the `GATES`
|
||||
table is the list (nineteen gates); `--fast` skips the full-run-only gates and names them (today `iso-bootstrap`). Why:
|
||||
`repo_gates.py` imported: 19 gates, `iso-bootstrap` not fast. **R-891.**
|
||||
2. `CLAUDE.md` design pointer `architecture/01..05-*.md` → `01..11-*.md` (00 = the capability map). Why: 06–11 exist.
|
||||
3. `.claude/rules/docs.md` „the locked design" `01..05` → `01..11`. Same reason.
|
||||
4. `.claude/rules/website.md`: the list of what `site_gates.py` asserts gains „no embedded `<style>` blocks". Why: its
|
||||
gate 7 (`site_gates.py` docstring).
|
||||
5. `.claude/rules/unprompted-work.md`: §5 added; „Same wording lives in all three repos'" → names the three repos and the
|
||||
workspace root. Why: four copies exist.
|
||||
6. `documentation/runbooks/workspace-CLAUDE.md` (the workspace root `CLAUDE.md` links to it): standing rule 3 carried the
|
||||
R-286 sentence group twice in a row; the second copy removed.
|
||||
|
||||
- **R-747, R-774** (catalog `ec72c9d`, live `1938921`): one first-step sentence each, hu + en, freeze updated.
|
||||
- **R-734** (catalog `b0939cf`): the update test ignores listed marker files (immich's six `.immich`), each with a reason,
|
||||
only when changed/added and ≤ 64 bytes; an unlisted file still counts (red-proved).
|
||||
- **R-624** (catalog `aed80ee`): bench-only vaultwarden seed through its admin invite. The bench LXC 9401 no longer
|
||||
existed; recreated by its recorded recipe (stopped again afterwards). Proven: admin sign-in, invite and registration
|
||||
200; the seed read back before and after; `.env` shredded, secret greps 0 with a working control; without the run flag
|
||||
the admin route is not tried (`inconclusive`). The move used to drive it (1.36.0-alpine → 1.36.0) failed its health
|
||||
check — the non-alpine image fails the alpine health check; that is the chosen target, not the seed. **New question:
|
||||
R-890.**
|
||||
- **R-502** (felhom.eu `9d39faab`): `iso-bootstrap`, full runs only, NOT CHECKED without docker or the image; the image
|
||||
`felhom-iso-assistant:trixie` was built on DooPlex; **first real run: 73 checks green, the built-in decoy convicted.**
|
||||
- **R-618:** closed by the ruling; nothing to build.
|
||||
felhom-agent
|
||||
7. `CLAUDE.md` gates: „`reuse_refs_check` and `instructions_gate` … today that is all of them" → every gate in `GATES`
|
||||
(five: three shared, `published`, `release-complete`); `--fast` skips `published`. Why: `agent_gates.py:52-60`.
|
||||
8. `CLAUDE.md` decoy paragraph: `scripts/decoy_coverage_gate.py`, `documentation/audits/AUDIT-gate-decoys-…` → with
|
||||
`felhom.eu/`. Why: neither exists in this repo.
|
||||
9. `.claude/rules/health-checks.md` (comment): „The single source is now felhom.eu/CLAUDE.md 'Code quality rules'" → the
|
||||
copies are felhom.eu `hub.md` and the controller's `gates.md`. Why: that section holds no health-check rule.
|
||||
|
||||
felhom-controller
|
||||
10. `.claude/rules/gates.md`: `python3 controller/scripts/controller_gates.py` (from `controller/`) →
|
||||
`python3 scripts/controller_gates.py` from `controller/` (or the long path from the repo root). Why: the old path does
|
||||
not exist from `controller/`; the runner's usage line and CI use the new one.
|
||||
11. `.claude/rules/gates.md`: the list of ten local gates → a pointer to `GATES` (fourteen local + three shared; the
|
||||
advisory `golden-notice`). Why: `controller_gates.py:71-101`.
|
||||
12. `.claude/rules/gates.md`: the shared list gains `observations_gate.py`. Why: `controller_gates.py:87`.
|
||||
13. `.claude/rules/gates.md` (two lines): decoy gate and audit paths with `felhom.eu/`.
|
||||
14. `CLAUDE.md` Commands table: the same command fix as 10.
|
||||
15. `CLAUDE.md`: „Protected stacks (traefik, cloudflared, felhom-controller)" → „…, and always samba". Why:
|
||||
`internal/config/config.go` `alwaysProtectedStacks`.
|
||||
16. `CLAUDE.md` design pointer `architecture/01/02/03-*.md` → `architecture/NN-*.md` (01–03; 07, 09, 10).
|
||||
17. `.claude/rules/unprompted-work.md`: as 5.
|
||||
|
||||
app-catalog-felhom.eu
|
||||
18. `CLAUDE.md`: „each holding exactly `docker-compose.yml` + `.felhom.yml`" → without „exactly", plus `steps/<StepKey>.yml`
|
||||
for a superseded ladder step. Why: 20 templates have `steps/`.
|
||||
19. „Only the two template files sync" → „Only the template files sync".
|
||||
20. „60 checks in 10 groups" → 61. Why: `NEW-APP-CHECKLIST.md` row-count table.
|
||||
21. „runs all four gates … scopes the two gates that accept scoping" → every gate in `GATES` (fourteen); scopes every gate
|
||||
that accepts an app scope (eight). Why: `catalog_gates.py:88-131`.
|
||||
22. „CI was rejected for now … R-161 stays open at reduced scope" → CI exists and re-runs `--fast`; R-161 closed 2026-10-05.
|
||||
23. „`--fast` … is gate 1 and the engine-major gate … the other two" → every gate except image-resolvable and
|
||||
volume-persistence.
|
||||
24. The paragraph „There is no gate for this yet and that is a known gap" removed. Why: `check-catalog-since.py` is a
|
||||
registered gate (R-452, closed); the same section already names it.
|
||||
25. „The four MariaDB services (…)" → the five (grimmory added), noting the gate judges by glob; „The eleven PostgreSQL
|
||||
services" → twelve, postgis and immich's image counted. Why: `templates/*/docker-compose.yml`.
|
||||
26. Decoy gate and audit paths with `felhom.eu/`; `.claude/rules/unprompted-work.md` as 5.
|
||||
|
||||
Sweep notes not acted on: word-for-word repeated paragraphs (R-421 in three CLAUDE.md files; „Presence is not success" in
|
||||
the controller's `backup-paths.md`) — repetition, not a wrong fact.
|
||||
|
||||
## Part B — R-890
|
||||
|
||||
**Built** (catalog `6b4877d`): `box_admin_seed_allowed(w, app)` allows the admin seed on a box walk only when all five
|
||||
hold — not the bench venue; `FELHOM_BOX_ADMIN_SEED=1`; the walk targets demo-hp guest 9202 (never 9201, a demo box's
|
||||
household-shaped guest); THIS run installed the app (`box_walk.DEPLOYED_THIS_RUN`, set on the deploy's 202); the box's
|
||||
`controller.yaml` names the drill catalog. The invite runs inside the box: the token is read from the container's
|
||||
environment into a shell variable and handed to curl on stdin; the admin cookie is in a 0600 file that is shredded
|
||||
(vaultwarden marks it Secure, so it is sent by hand over the container's plain-http address); only HTTP codes come back.
|
||||
**The Tester 1 box is allowed by the ruling, but the walk has no route to it** (`box_walk.py` drives demo-hp guests
|
||||
only) — written in the code, not built. Tests `BoxAdminSeedGuard` (6); **red-proof:** a guard returning `(True, "")`
|
||||
fails three tests (`test_each_condition_alone_refuses`, `test_a_household_shaped_box_is_never_seeded`, and the bench's
|
||||
`test_the_box_walk_never_signs_in_as_admin`).
|
||||
|
||||
**Proven:**
|
||||
- Bench LXC 9401 (harness v5, 600 s memory watch): verdict `proven`; seed read back before and after; healthy in 30.9 s;
|
||||
anon peak 16.3 % (cgroup 22.3 %); 0 kills, 0 restarts; no file changed.
|
||||
- Box 9202 (drill catalog): self-registration 400; the in-box admin sign-in and invite → (200, session yes, 200);
|
||||
invited registration 200; seed read back; guarded Update backing-up → pulling → copying → verifying → done in 12.3 s;
|
||||
seed read back; badge „Frissítés elérhető — 80 napja" → „Naprakész"; removed through the product.
|
||||
- `upgrade-test.py --write-ladder` from both verdicts → `vaultwarden/server:1.37.4-alpine`, `catalog_since` 2026-10-06,
|
||||
the first ladder entry (catalog `ecb8552`); `check-image-resolvable.py vaultwarden` OK.
|
||||
- **Who gets it:** no box reports vaultwarden today (hub DB copy with its WAL: last vaultwarden telemetry 2026-09-18,
|
||||
demo-hp; control: the latest telemetry row of any app 13:51 today; the copy shredded). Read directly: demo-hp 9201 and
|
||||
demo-felhom 9201 run no vaultwarden container.
|
||||
|
||||
## Part C
|
||||
|
||||
- **R-644 closed:** no gokapi on 9202 (no container, no volume; control: the six running containers are listed).
|
||||
- **R-763 closed** (fix from 2026-10-05, proven now): wger installed fresh on 9202; its own process reads
|
||||
`ALLOW_REGISTRATION False`, `ALLOW_GUEST_USERS False`; straight at the app, a stranger's sign-up GET and a CSRF-valid
|
||||
POST both redirect to the features page (control: the same CSRF token passes on the login form, 200); three anonymous
|
||||
dashboard visits → login; users 1 before and after (2 → 4 before the fix).
|
||||
- **R-764 closed** (proven now): mail off → healthy, 0 restarts, console backend; mail on (the toggle's injection) →
|
||||
settings load, smtp backend, port 2526, TLS off. Not measured: a real reset mail (9202 has no app-mail).
|
||||
- **R-762 stopped:** still 404 on wger 2.7; the image has no whitenoise and runs Django's `runserver`, so it needs a
|
||||
second container for `/static` and `/media` — medium. Note added to the row.
|
||||
- **R-717 stopped — needs a design:** the controller's `after_setup` command form runs only when the lock is set; the
|
||||
household's 15-minute window cannot reopen a database switch. Note added to the row.
|
||||
|
||||
## Said plainly
|
||||
|
||||
- **CI run 1423 (felhom.eu, the hub-manifest commit) was red** on the golden gate: it read controller v0.300.0 a few minutes
|
||||
before the golden 0.300.0 record was committed. Run 1425 on the next push was green.
|
||||
- The agent update's first trim attempt failed by design (the sudo rule rides the bundle, which came 10 minutes later).
|
||||
On a box that gets the agent but not the bundle, the trim warns weekly and its capability reads degraded.
|
||||
- **R-891:** felhom.eu `CLAUDE.md` still says `--fast` runs every gate — untrue since `iso-bootstrap`. An instruction file;
|
||||
left for the operator.
|
||||
- **A merge conflict was committed into the DRILL catalog** (vaultwarden's `.felhom.yml`, by a `commit -am` after a
|
||||
failed merge). Found at once by its conflict markers and fixed in the next drill commit; the drill was then reset to
|
||||
live. The live catalog was never touched by it.
|
||||
- My first wger user-count command had a quoting bug (it printed a Python NameError); the reads were repeated through a
|
||||
file. The stranger checks were not affected.
|
||||
|
||||
## CI, last commit of every repo
|
||||
|
||||
felhom.eu: this commit (checked by its commit after the push); controller `36088fd` → 1426 success; agent `cefdc73` → 1427
|
||||
success; catalog `65130c6` → 1428 success.
|
||||
catalog `ecb8552` → 1433 success (and `6b4877d` → 1430 success); agent `3e8ebeb` → 1431 success; controller `3124278`
|
||||
→ 1432 success; felhom.eu `a29ee9dd` → 1434 (checked before the next push), and this commit (checked after the push).
|
||||
|
||||
## Teardown
|
||||
|
||||
Drill VM: build guest destroyed, secrets shredded, off, `virgin`. Bench 9401: stopped (kept; its evidence copied off).
|
||||
Scratch 9202: not used. ep0: read only. demo-hp / demo-felhom: the deliveries, one measured trim, `sudo -l` checks.
|
||||
Hub: the deploy, the vouches, three floors per release. Scratch secrets shredded at the end.
|
||||
Machine: 9202 back on the live catalog (`controller.yaml` restored byte-identical), vaultwarden and wger removed through
|
||||
the product (no container, no volume), the same six containers as at the start. Bench 9401: 0 containers, `.env` gone,
|
||||
stopped. Drill: reset to live (`ecb8552` = `ecb8552`). Host: nothing else. Hub: nothing changed (one read-only DB copy,
|
||||
shredded). Scratch secrets shredded at the end.
|
||||
|
||||
@@ -2,8 +2,25 @@
|
||||
|
||||
**Ready for the first real tester (Tester-2): yes. Tester 2 (a laptop) is off; nothing was sent to it.**
|
||||
|
||||
**Updated 2026-10-06 13:15: every box of ours healthy on hub 0.139.0, agent 0.149.0, controller 0.300.0. The open-items
|
||||
list is at 142. Report: `REPORT.md`.**
|
||||
**Updated 2026-10-06 14:15: every box of ours healthy on hub 0.139.0, agent 0.149.0, controller 0.300.0. The open-items
|
||||
list is at 137. Report: `REPORT.md`.**
|
||||
|
||||
## Afternoon (2026-10-06): your two answers built; the list at 137
|
||||
|
||||
- **New standing rule (your answer):** a session now fixes a wrong fact in an instruction file by itself and lists the
|
||||
change in its report. It still may not loosen a safety rule. The rule is in all four copies of the shared rule file and
|
||||
in the brief template. No permission prompt came up.
|
||||
- **The wrong line is fixed**, and twenty-five more wrong facts across the four repos (gate lists, paths, counts).
|
||||
Every edit is listed in the report.
|
||||
- **Vaultwarden is on the update list:** its first step (1.36.0 → 1.37.4) passed on the test bench and on the scratch box,
|
||||
using the admin seed you allowed. No box runs vaultwarden today, so no household gets it now.
|
||||
- **wger's two fixes are proven** on the scratch box: a stranger cannot sign up, visits make no guest accounts, and mail
|
||||
works when switched on. wger stays hidden: it still shows no styles or photos.
|
||||
|
||||
**Needs you (none urgent):**
|
||||
1. **R-469** — the catalog's database-version check cannot be removed any more: it is now how decision 35's per-app
|
||||
proof is enforced. (A) close the row and keep the check (my pick), or (B) keep the row open. If nothing: the row
|
||||
stays open; the check works either way.
|
||||
|
||||
## Midday (2026-10-06): your ten answers built; the list at 142
|
||||
|
||||
@@ -15,7 +32,7 @@ list is at 142. Report: `REPORT.md`.**
|
||||
for the day one appears.
|
||||
- **Three of your answers differed from my picks (2, 8 and 9).** Your answer governs, and each is recorded.
|
||||
|
||||
**Needs you (none urgent):**
|
||||
**Needs you (none urgent):** *(both answered 2026-10-06 13:25 and done)*
|
||||
1. **R-890** — a vaultwarden update can still not be written to the update list: the list needs a proof on the scratch box
|
||||
too, and your ruling keeps the admin password on the test bench only. Pick: allow it on scratch box 9202 as well. If
|
||||
nothing: vaultwarden updates stay manual.
|
||||
|
||||
@@ -0,0 +1,45 @@
|
||||
lifecycle on the box: None deployed=False
|
||||
settings read by wger's own process:
|
||||
before: NameError: name 'USERS' is not defined
|
||||
stranger, straight at the app: GET registration: 302 http://172.18.0.6:8000/en/software/features
|
||||
stranger, straight at the app: POST registration: 403
|
||||
stranger, straight at the app: anonymous GET dashboard 1: 302 http://172.18.0.6:8000/user/login?next=/en/dashboard
|
||||
stranger, straight at the app: anonymous GET dashboard 2: 302 http://172.18.0.6:8000/user/login?next=/en/dashboard
|
||||
stranger, straight at the app: static css: 404
|
||||
stranger, straight at the app: static root size: 4.0K /home/wger/static
|
||||
after: NameError: name 'USERS' is not defined
|
||||
stranger account exists: STRANGER False
|
||||
restarts: 0 healthy
|
||||
read 1: level=INFO ts=2026-10-06 13:56:58,292 module=apps path=/home/wger/.local/lib/python3.12/site-packages/axes/apps.py line=53 message=AXES: BEGIN version 8.3.1, blocking by username
|
||||
SETTINGS ALLOW_REGISTRATION=False ALLOW_GUEST_USERS=False EMAIL_BACKEND=django.core.mail.backends.console.EmailBackend ENABLE_EMAIL-env=False DEBUG=False
|
||||
USERS 1 ['admin']
|
||||
anonymous GET /en/dashboard 1: 302 http://172.18.0.6:8000/user/login?next=/en/dashboard
|
||||
anonymous GET /en/dashboard 2: 302 http://172.18.0.6:8000/user/login?next=/en/dashboard
|
||||
anonymous GET /en/dashboard 3: 302 http://172.18.0.6:8000/user/login?next=/en/dashboard
|
||||
anonymous GET /en/user/demo-entries: 302 http://172.18.0.6:8000/en/software/features
|
||||
read 2: level=INFO ts=2026-10-06 13:57:06,003 module=apps path=/home/wger/.local/lib/python3.12/site-packages/axes/apps.py line=53 message=AXES: BEGIN version 8.3.1, blocking by username
|
||||
SETTINGS ALLOW_REGISTRATION=False ALLOW_GUEST_USERS=False EMAIL_BACKEND=django.core.mail.backends.console.EmailBackend ENABLE_EMAIL-env=False DEBUG=False
|
||||
USERS 1 ['admin']
|
||||
GET login (for a CSRF cookie): 200
|
||||
csrf cookie: yes
|
||||
POST registration with a valid CSRF: 302 http://172.18.0.6:8000/en/software/features
|
||||
CONTROL - POST login with the same CSRF and a wrong password: 200
|
||||
USERS 1
|
||||
settings loaded with the mail toggle's injection (ENABLE_EMAIL=True, EMAIL_HOST set), in the running wger:
|
||||
/home/wger/src/settings/main.py:117: UserWarning: JWT_PRIVATE_KEY / JWT_PUBLIC_KEY are not set. JWT authentication will not work until you run `./manage.py generate-jwt-keys` and add the output to your environment.
|
||||
warnings.warn(
|
||||
MAIL-ON SETTINGS: ENABLE_EMAIL-env=True EMAIL_BACKEND=django.core.mail.backends.smtp.EmailBackend EMAIL_HOST=felhom-relay.invalid EMAIL_PORT=2526 USE_TLS=False
|
||||
rc=0
|
||||
Traceback (most recent call last):
|
||||
File "<stdin>", line 14, in <module>
|
||||
File "/mnt/5_hdd/felhom.eu/git/app-catalog-felhom.eu/scripts/box_walk.py", line 502, in remove
|
||||
c1, d1 = ctl("POST", f"/api/stacks/{name}/stop")
|
||||
~~~^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
|
||||
File "/mnt/5_hdd/felhom.eu/git/app-catalog-felhom.eu/scripts/box_walk.py", line 88, in ctl
|
||||
sess = open(f"{SC}/sess{os.getpid()}.txt").read().strip()
|
||||
~~~~^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
|
||||
FileNotFoundError: [Errno 2] No such file or directory: '/tmp/claude-1000/-mnt-5-hdd-felhom-eu-git/da0e2df0-3072-4d3e-bf10-c475ac1e5ae4/scratchpad/sc890/sess4190727.txt'
|
||||
13:58:17 [X] stop -> 200 {'ok': True, 'message': 'Stack wger stop completed'}
|
||||
13:58:49 [X] remove (with drive data) -> 200 {'ok': True, 'data': {'removed': 'wger', 'volumes_removed': ['wger_wger_data', 'wger_wger_media'], 'hdd_paths_removed': [], 'hdd_paths_preserved': [], 'hdd_note
|
||||
13:58:57 [X] after remove: deployed=False leftovers='/opt/docker/stacks/wger'
|
||||
remove -> 200
|
||||
@@ -21,3 +21,9 @@ harness); the bench has 0 containers and is stopped.
|
||||
## Part C
|
||||
|
||||
- R-644: `C/R-644-9202-live.txt` — no gokapi on 9202 any more.
|
||||
- R-763 / R-764 / R-762: `C/wger.txt` (`tools/wgerwalk.py`; wger un-hidden in the DRILL only, removed after).
|
||||
|
||||
## Teardown
|
||||
|
||||
`box/T1-repoint-live.txt` (9202 back on the live catalog, byte-identical `controller.yaml`; the same six containers),
|
||||
`box/T2-drill-reset.txt` (drill = live).
|
||||
|
||||
@@ -0,0 +1,11 @@
|
||||
26: repo_url: https://gitea.dooplex.hu/admin/app-catalog-felhom.eu.git
|
||||
3
|
||||
RESTORED-IDENTICAL
|
||||
|
||||
0
|
||||
filebrowser Up 5 hours (healthy)
|
||||
felhom-controller Up 16 seconds (healthy)
|
||||
paperless-webserver Up 11 hours (healthy)
|
||||
paperless-postgres Up 11 hours (healthy)
|
||||
paperless-redis Up 11 hours (healthy)
|
||||
traefik Up 28 hours
|
||||
@@ -0,0 +1 @@
|
||||
drill=ecb8552 live=ecb8552
|
||||
@@ -0,0 +1,62 @@
|
||||
"""wgerwalk.py — R-763 / R-764 / R-762 on scratch 9202 (drill catalog = live + wger un-hidden, DRILL only).
|
||||
|
||||
Install wger through the product, then, STRAIGHT AT THE APP inside the box (a stranger who got past the box's own gate —
|
||||
the strictest case; the family gate would stop him first): the sign-up page and form, two anonymous dashboard visits,
|
||||
the user count before and after (wger's own ORM). The settings wger read (its own process). The static and media read
|
||||
(R-762). Removed through the product at the end. Evidence: ../C/wger.txt."""
|
||||
import os, sys
|
||||
sys.path.insert(0, "/mnt/5_hdd/felhom.eu/git/app-catalog-felhom.eu/scripts")
|
||||
import box_walk as w
|
||||
|
||||
HERE = os.path.dirname(os.path.abspath(__file__))
|
||||
log = open(os.path.join(HERE, "..", "C", "wger.txt"), "a", buffering=1)
|
||||
|
||||
|
||||
def say(*a):
|
||||
w.say(*a); log.write(" ".join(map(str, a)) + "\n")
|
||||
|
||||
|
||||
ORM = ("cd /home/wger/src && DJANGO_SETTINGS_MODULE=settings.main python3 -c "
|
||||
"\"import django; django.setup(); from django.contrib.auth.models import User; print('USERS', User.objects.count())\"")
|
||||
SETTINGS = ("cd /home/wger/src && DJANGO_SETTINGS_MODULE=settings.main python3 -c "
|
||||
"\"import django; django.setup(); from django.conf import settings as s; "
|
||||
"print('ALLOW_REGISTRATION', s.WGER_SETTINGS.get('ALLOW_REGISTRATION'), 'ALLOW_GUEST_USERS', s.WGER_SETTINGS.get('ALLOW_GUEST_USERS'), "
|
||||
"'EMAIL_BACKEND', s.EMAIL_BACKEND, 'DEBUG', s.DEBUG)\"")
|
||||
|
||||
|
||||
def users():
|
||||
out = w.guest(f"docker exec wger sh -c '{ORM}' 2>&1 | tail -1")
|
||||
return out.strip()
|
||||
|
||||
|
||||
STRANGER = r"""set -u
|
||||
ip=$(docker inspect -f '{{range .NetworkSettings.Networks}}{{.IPAddress}} {{end}}' wger | awk '{print $1}')
|
||||
H="Host: fitness.enkisfelhom.hu"; P="X-Forwarded-Proto: https"
|
||||
J=$(mktemp)
|
||||
echo "GET registration: $(curl -s -o /dev/null -c $J -b $J -H "$H" -H "$P" -w '%{http_code} %{redirect_url}' http://$ip:8000/en/user/registration)"
|
||||
tok=$(grep csrftoken $J | awk '{print $7}')
|
||||
echo "POST registration: $(curl -s -o /dev/null -c $J -b $J -H "$H" -H "$P" -H "Referer: https://fitness.enkisfelhom.hu/en/user/registration" -w '%{http_code} %{redirect_url}' --data "csrfmiddlewaretoken=$tok&username=stranger1&email=stranger1@gate.invalid&password1=Str4nger-Pass-991&password2=Str4nger-Pass-991" http://$ip:8000/en/user/registration)"
|
||||
rm -f $J
|
||||
for i in 1 2; do echo "anonymous GET dashboard $i: $(curl -s -o /dev/null -H "$H" -H "$P" -w '%{http_code} %{redirect_url}' http://$ip:8000/en/dashboard)"; done
|
||||
echo "static css: $(curl -s -o /dev/null -H "$H" -H "$P" -w '%{http_code}' http://$ip:8000/static/css/workout-manager.css)"
|
||||
echo "static root size: $(docker exec wger du -sh /home/wger/static 2>&1 | tail -1)"
|
||||
"""
|
||||
|
||||
w.login()
|
||||
w.sync_rescan("wger")
|
||||
st = w.stack("wger")
|
||||
say(f"lifecycle on the box: {(st.get('metadata') or {}).get('lifecycle')} deployed={st.get('deployed')}")
|
||||
if not w.deploy("wger", "fitness"):
|
||||
sys.exit(say("RESULT install did not complete") or 1)
|
||||
w.wait_app("fitness", "/", tries=60)
|
||||
say("settings read by wger's own process:", w.guest(f"docker exec wger sh -c \"{SETTINGS}\" 2>&1 | tail -1").strip())
|
||||
say("before:", users())
|
||||
for line in w.guest(STRANGER).strip().splitlines():
|
||||
say(" stranger, straight at the app:", line)
|
||||
say("after:", users())
|
||||
say("stranger account exists:", w.guest("docker exec wger sh -c \"cd /home/wger/src && DJANGO_SETTINGS_MODULE=settings.main python3 -c "
|
||||
"\\\"import django; django.setup(); from django.contrib.auth.models import User; "
|
||||
"print('STRANGER', User.objects.filter(username='stranger1').exists())\\\"\" 2>&1 | tail -1").strip())
|
||||
say("restarts:", w.guest("docker inspect -f '{{.RestartCount}} {{.State.Health.Status}}' wger").strip())
|
||||
if not os.environ.get("KEEP"):
|
||||
say(f"remove -> {w.remove('wger')}")
|
||||
@@ -26,6 +26,15 @@
|
||||
|
||||
---
|
||||
|
||||
## 2026-10-06 (afternoon) — wger's two fixes proven on 9202
|
||||
|
||||
The full text of every row below: `git show a29ee9dd33:documentation/backlog/OPEN-ITEMS.md`.
|
||||
|
||||
| Row | What | Closed | Evidence |
|
||||
|---|---|---|---|
|
||||
| **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.** (P4) | CLOSED 2026-10-06 — PROVEN ON 9202 (the fix pushed 2026-10-05, catalog `db88ee9`) | Mail OFF (a fresh install): healthy, 0 restarts, console backend, ENABLE_EMAIL False. Mail ON (the toggle's injection, ENABLE_EMAIL=True + EMAIL_HOST, in the running wger): settings load, smtp backend, port 2526, TLS off — `audits/r890-instructions-2026-10-06/C/wger.txt`. Not measured: a reset mail through a real relay (9202 has no app-mail). |
|
||||
| **R-763** | **[P2-MEDIUM] On wger a stranger can make an account after the household's setup, and every anonymous visit to the dashboard creates a guest account.** (P3) | CLOSED 2026-10-06 — PROVEN ON 9202 (the fix pushed 2026-10-05, catalog `1968527`) | wger installed fresh on 9202 (drill = live + un-hidden): wger's own process reads ALLOW_REGISTRATION False, ALLOW_GUEST_USERS False; straight at the app a stranger's sign-up GET and a CSRF-valid POST both redirect to /en/software/features (control: the same CSRF passes on the login form), three anonymous dashboard visits redirect to login, users 1 before and after (was 2 -> 4 before the fix) — `audits/r890-instructions-2026-10-06/C/wger.txt`. Family-member adding (checklist 3.7) not measured: R-759. wger stays hidden (R-762). |
|
||||
|
||||
## 2026-10-06 (afternoon) — instruction files kept true; vaultwarden on the ladder
|
||||
|
||||
The full text of every row below: `git show 7d0dffcf34:documentation/backlog/OPEN-ITEMS.md`.
|
||||
|
||||
@@ -129,11 +129,10 @@ stopping line that lies.
|
||||
| **R-562** | Apps & catalog | P3 | **[P3-LOW] Dates and sizes are not formatted for any locale — and the Hungarian pages disagree with themselves.** FOUND 2026-09-17 by the i18n inventory §2.8: the two template date layouts differ (`2006. 01. 02. 15:04` Hungarian vs `2006-01-02 15:04` ISO); 10 layout literals in `internal/web` Go and 25 elsewhere pick formats ad hoc; sizes print a decimal POINT (`%.1f GB`, 4 helpers) where Hungarian uses a comma; `timeAgo`/`nextRunLabel`/`pruneLabel` produce Hungarian words outside the three converted pages. Not changed by v0.247.0 (Hungarian bytes are frozen by the parity rule). **Fix shape:** one date and one size formatter per language in `internal/i18n`, the Hungarian output deliberately changed in ONE reviewed release with the parity fixtures re-captured for that release only and the change named in its CHANGELOG. Needs an operator word on the Hungarian format (comma, date style). | **READY - rank P3-LOW; owner: CC** | — | — | CC |
|
||||
| **R-612** | Apps & catalog | P3 | **[P1-HIGH] `wishlist` cannot be signed up to on a fresh Felhom install, the deploy reports SUCCESS, and the error the customer sees is a LIE.** MEASURED 2026-09-21 on guest 9202 while seeding for the power-cut drill. The image's first-boot `pnpm prisma db seed` is **`Killed` — OOM at the catalog's `mem_limit: 128M`**. Without it the `Role` and `Group` rows are absent, so **every** signup fails. **The message the user is shown is `User with username or email already exists`** while the container log says the real cause: `FOREIGN KEY constraint violated`. A household would conclude the account already exists and try to recover a password that was never created. **The controller reports the app running and HEALTHY throughout, and the deploy reported successful** — so nothing on the box says anything is wrong. Repaired on the scratch guest only, to unblock seeding: memory raised to 512 M, the image's own seed re-run, memory put back to 128 M. **The catalog was NOT changed** — the fix is a memory-limit question for the catalog and is deliberately left to a session that can measure the real ceiling rather than guess it. **Needs: the actual peak RSS of that seed, then a `mem_limit` that clears it, plus a check that the seed's failure is not silent.** **— FIXED 2026-09-23 night (catalog `a5a729a`): 512M.** Measured on the bench: the seed peaks at 312–345M and was OOM-killed at 128M on every run (kernel `oom_kill` 3); running, the app sits at ~115M (90 % of the old limit alone). On 9202 at 128M the kill came AFTER the Role/Group rows this time, so the sign-up lie did NOT reproduce — the kill is timing-dependent, the fault is memory (the brief's claim held). At 512M the seed completes (`The seed command has been executed`), and wishlist then moved v0.66.0 → v0.67.1 with its test record. **Still open:** a failed first-boot seed is invisible to the box — nothing reads the seed's exit. | **READY — P3, narrowed to making a failed seed visible; owner: CC (catalog)** **Re-ranked 2026-10-03: P1->P3: the memory fix shipped (catalog a5a729a); only the silent-seed detection remains.** | — | — | CC |
|
||||
| **R-676** | Apps & catalog | P3 | **[P3-LOW] Watch: immich's first start restarted 12 times — decision 28's crash-loop stop (6 in 10 min) would stop it.** From the 2026-09-17 chaos night (DB connection dropped during the first-start geocoding import on a 6 GB guest; it did not recover that night). No healthy app in any drill evidence restarts on a first start (1831 samples, 40 live containers), so the threshold stands; this row exists so the first immich install under v0.269.x is watched. `audits/night-2026-09-24/A3/40-first-start-restarts.txt` **2026-09-25 night (read from source, v0.271.0): a DEPLOY's first start is NOT covered by decision 28's suppression** — `Deploying` clears when `compose up -d` returns (`deploy.go` "Clear deploying flag"), and `ObserveUnhealthy` then samples the app; an automatic update's step, verify and undo ARE covered (`Updating`, pinned by `TestD28_NoCrashLoopStopDuringAnAutomaticStep`). So a first start that restarts ≥ 6 times in 10 min is stopped — which R-676 already accepts for a broken first start; a healthy slow first start would be stopped too. **-- 2026-09-30: the first-start restarts are explained.** immich's first-start geodata import OOM-kills its database at 512M on a guest with no swap (R-732, measured: 61–104 kills); the 2026-09-17 chaos-night case (DB connection dropped during the import on a 6 GB guest) fits it. Fixed in the catalog (`56c4888`, 768M). The watch itself (decision 28 on a DEPLOY's first start) is unchanged. | **OPEN — P3; owner: CC (watch)** | — | — | CC |
|
||||
| **R-762** | Apps & catalog | P3 | **[P2-MEDIUM] wger serves no CSS or JavaScript and no uploaded photo: every static file and every `/media/` file answers 404.** MEASURED 2026-10-01 on 9202 (drill catalog, the live template `82fff32`, wger 2.7), found by checklist rows 1.7 and 2.8: the login page links `/static/css/workout-manager.css`, `/static/bootstrap-compiled.css` — both 404 through traefik; the static root inside the container is empty (4 KB). A progress photo posted to `/api/v2/gallery/` answered 201 and the file is on the media volume, but `GET /media/gallery/…png` answers 404 signed in, without a session, and straight at the app inside the container. **Cause, read in the image:** the entrypoint runs `collectstatic` only when `DJANGO_DEBUG == "False"` and the template sets no `DJANGO_DEBUG`; and wger serves `/media/` only in development (`urls.py:393` „served like this during development only”) — upstream's production setup puts nginx in front for `/static` and `/media`. So the household gets an unstyled app and photos that never show. The same lines stand since the template was written (the 2026-09-29 template too). Not checked: whether any box runs wger (on 2026-09-30 none reported to the hub). **Needs:** `DJANGO_DEBUG=False` (collectstatic) and something that serves `/static` + `/media` (upstream's nginx sidecar, or the gunicorn switch of R-755 plus a static server), proven on the bench and on 9202 with a page that loads its CSS and a photo read back. Owner decides together with R-755 (same server question). `audits/new-app-checklist-2026-10-01/C/C8-signup-guest-media-static.txt`, `C/C4-seed-photo-size.txt` **-- 2026-10-01 (operator):** wger is `lifecycle: hidden` until this and its twin are fixed (catalog `55b8c8a`; read back on 9202: not on the app list, mealie control present). **Merged 2026-10-05 from R-755 (duplicate):** `templates/wger/docker-compose.yml` still sets no `WGER_USE_GUNICORN` — the gunicorn switch is the same server question. | **READY — rank P2-MEDIUM; owner: CC (catalog)** **Re-ranked 2026-10-03: P2→P3: wger is hidden from installs and no box runs it; needed only before it is offered again.** | — | — | CC |
|
||||
| **R-762** | Apps & catalog | P3 | **[P2-MEDIUM] wger serves no CSS or JavaScript and no uploaded photo: every static file and every `/media/` file answers 404.** MEASURED 2026-10-01 on 9202 (drill catalog, the live template `82fff32`, wger 2.7), found by checklist rows 1.7 and 2.8: the login page links `/static/css/workout-manager.css`, `/static/bootstrap-compiled.css` — both 404 through traefik; the static root inside the container is empty (4 KB). A progress photo posted to `/api/v2/gallery/` answered 201 and the file is on the media volume, but `GET /media/gallery/…png` answers 404 signed in, without a session, and straight at the app inside the container. **Cause, read in the image:** the entrypoint runs `collectstatic` only when `DJANGO_DEBUG == "False"` and the template sets no `DJANGO_DEBUG`; and wger serves `/media/` only in development (`urls.py:393` „served like this during development only”) — upstream's production setup puts nginx in front for `/static` and `/media`. So the household gets an unstyled app and photos that never show. The same lines stand since the template was written (the 2026-09-29 template too). Not checked: whether any box runs wger (on 2026-09-30 none reported to the hub). **Needs:** `DJANGO_DEBUG=False` (collectstatic) and something that serves `/static` + `/media` (upstream's nginx sidecar, or the gunicorn switch of R-755 plus a static server), proven on the bench and on 9202 with a page that loads its CSS and a photo read back. Owner decides together with R-755 (same server question). `audits/new-app-checklist-2026-10-01/C/C8-signup-guest-media-static.txt`, `C/C4-seed-photo-size.txt` **-- 2026-10-01 (operator):** wger is `lifecycle: hidden` until this and its twin are fixed (catalog `55b8c8a`; read back on 9202: not on the app list, mealie control present). **Merged 2026-10-05 from R-755 (duplicate):** `templates/wger/docker-compose.yml` still sets no `WGER_USE_GUNICORN` — the gunicorn switch is the same server question. **-- 2026-10-06 (afternoon), measured again on 9202 (live template, wger 2.7):** `/static/css/workout-manager.css` 404 straight at the app, `/home/wger/static` 4 KB, settings `DEBUG False`; the image has gunicorn but no whitenoise and runs Django's `runserver` (no `WGER_USE_GUNICORN`, R-755). So `DJANGO_DEBUG=False` alone would collect the files and still serve none: the fix needs a server for `/static` + `/media` (a second container) — medium, not taken. `audits/r890-instructions-2026-10-06/C/wger.txt`. | **READY — rank P2-MEDIUM; owner: CC (catalog)** **Re-ranked 2026-10-03: P2→P3: wger is hidden from installs and no box runs it; needed only before it is offered again.** | — | — | CC |
|
||||
| **R-76** | Apps & catalog | P4 | **FileBrowser-created folders break the setgid chain, and a drop-zone's mode is not stable** **MIGRATED FROM `ROADMAP.md` 2026-08-22 (R-369) — originally filed 2026-07-26, size S, roadmap state `idea (surfaced by the R-75 spike, 2026-07-26)`.** Moved verbatim; nothing added or reinterpreted. The roadmap keeps its copy as history, marked moved. | **OPEN — migrated from ROADMAP 2026-08-22, rank unchanged** | — | Two related findings from `audits/SPIKE-catalog-data-paths-2026-07-26.md` P3/P5, both **pre-existing** and deliberately left alone by that spike. **(a)** FileBrowser Quantum 1.3.3 creates files `0644` and folders `0755` and does **not** propagate the setgid bit — even though the entrypoint wrapper's `umask 002` really is in effect (`/proc/1/status` `Umask: 0002`). Group inheritance itself works (a file uploaded into a 2775 group-100 dir landed group 100, not the process gid 1000), so the convention's *group* half holds and only its *mode* half is lost. The consequence is proven with a control: inside a UI-created `0755` folder a gid-1000 process's file landed group **1000**, while the identical write into the 2775 parent landed group **100**. So **any folder a customer creates through FileBrowser breaks the shared-group chain one level down.** Latent today — every userdata-touching catalog app that declares an identity declares uid/gid **1000**, the same uid FileBrowser runs as, so owner permissions mask it; it bites the day a content app runs as a different non-root uid with gid 1000. The comment at `infra/infra.go:156` is right that the image ignores `-e UMASK` but does not say t | CC |
|
||||
| **R-577** | Apps & catalog | P4 | **[P3-LOW] A guest SHARE visitor has no way to pick a language, and the household's setting is the wrong default for them.** FOUND 2026-09-18 by localisation slice 2 release C (R-557, controller v0.254.0): every other page a person can reach now carries a language globe — the dashboard (the household's setting), and the sign-in and claim pages (the visitor's own cookie). The two guest share pages (`launcher_shared`, `launcher_share_password`) deliberately do NOT, and `TestGuestSharePagesHaveNoGlobe` pins that so it stays a decision rather than an oversight. **Why it is the operator's and not CC's:** a share visitor is a stranger the household sent a link to, and what language they are shown is a promise the SHARE FEATURE makes, not an implementation detail. The `felhom_lang` cookie already built would fit them exactly (display-only, their own browser, never the household's setting). **Fix shape, if the operator says yes:** add `{{template "lang_globe" .}}` to both shells with the anonymous form, and one render case per page per language. | **READY - rank P3-LOW; owner: operator (the decision), CC (the change)** **Re-ranked 2026-10-03: P3->P4: feature decision for the operator; Hungarian default works today.** | — | — | operator |
|
||||
| **R-707** | Apps & catalog | P4 | **[P2] 37 apps still start with a login a stranger can take (`09` §3 decision 45).** Audit of all 53 apps: `app-catalog-felhom.eu/FIRST-ADMIN.md` (class, fix route, status, measured or read). Open: **3 hard-coded defaults** — calibre-web (`admin / admin123`, measured working on demo-hp and 9202; its own `cps.py -s` route needs a generated password WITH a special character — our generator is letters+digits, a controller change), mealie (`changeme@example.com / MyPassword`), wger (`admin / adminadmin`); **34 open first-run screens** (the first visitor creates the admin: actualbudget, adventurelog, audiobookshelf, calcom, docmost, emby, ghost, gitea, gramps-web, home-assistant, homebox, immich, jellyfin, komga, n8n, navidrome, opengist, outline, papra, plant-it, radarr, rallly, recipe-importer, romm, seerr, sonarr, sparkyfitness, tandoor, termix, uptime-kuma, vikunja, wanderer, wishlist, zipline). **Stale notes:** romm's `default_creds` `admin / admin` answers 401 on demo-hp (like a wrong password) — the page now warns with a login that does not exist; zipline's looks stale too. **Measured on demo-hp 2026-09-28 (read-only):** bookstack's default still logs in on the INSTALLED app (the fix is for new installs; the page now warns). Each fix: route (a) env or (b) the app's own CLI/API via `after_install:`, proven on 9202 with the default failing and the generated password working; route (c) a page sentence. Several sessions (operator, 2026-09-28). **2026-09-29 (controller v0.280.0, catalog `d0e7e2e`):** every class-3 app fixed — mealie, wger, calibre-web by `after_install` (calibre-web with the new `password:24:special`), proven on 9202 fresh installs (`audits/login-gate-2026-09-29/D/`); the setup gate (decision 46, spike PASSED) built and live on immich, n8n, audiobookshelf (probes measured) and uptime-kuma (button) (`…/C/`); romm's and zipline's stale notes removed. **Left: 30 class-4 apps** — gate each (probe measured on 9202 where one exists — 11 upstream candidates listed in `…/B/B-VERDICT.md` §3; the button otherwise). **2026-09-29 afternoon (controller v0.281.0, catalog `6faf432`):** 28 more class-4 apps gated — 32 of 34 — each proven on 9202 (`audits/gate-rollout-2026-09-29/`B): stranger → gate page / 401, household reached the first-setup screen, the gate opened (9 by a measured probe, the rest by the press), the app answered after. seerr, outline, rallly: gated, their opening needs a media server / e-mail (not proven). **Left:** wanderer (R-714); plant-it is not installable. | **NARROWED** (2026-10-03 triage: the row's verdict was finished, but it names open work no other row carries — seerr, outline and rallly are gated, but the gate OPENING is not proven (needs a media server / e-mail)) — **CLOSED — 2026-09-29 (the rest → R-714)** | — | — | CC |
|
||||
| **R-764** | Apps & catalog | P4 | **[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)** **Re-ranked 2026-10-03: P3→P4: wger is hidden and no box runs it.** **2026-10-06: PUSHED** to the live catalog (`c265b37`; wger stays hidden). | — | — | CC |
|
||||
| **R-769** | Apps & catalog | P4 | **[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** **Re-ranked 2026-10-03: P3→P4: a new-app idea waiting on a decision.** | — | — | operator |
|
||||
| **R-770** | Apps & catalog | P4 | **[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** **Re-ranked 2026-10-03: P3→P4: a new-app idea waiting on a decision.** | — | — | operator |
|
||||
| **R-771** | Apps & catalog | P4 | **[P3-LOW] moonlight-web — fit check only; not buildable through an HTTP-only tunnel at usable latency.** READ 2026-10-01: two unrelated projects (MrCreativ3001/moonlight-web-stream, the original; linckosz/moonlight-web); both need Sunshine/Apollo/Wolf on a gaming PC on the LAN and WebRTC over UDP (40000-40100/udp; linckosz recommends host networking and sends telemetry by default); both have a WebSocket fallback (high latency, all video through the tunnel); a logged-in user controls the PC's desktop. **Needs:** the operator's go / no-go (LAN-only use would need a different publishing model). `audits/new-apps-2026-10-01/FIT.md` | **WAITING-ON-OPERATOR — rank P3-LOW; owner: operator** **Re-ranked 2026-10-03: P3→P4: a new-app idea waiting on a decision.** | — | — | operator |
|
||||
@@ -209,8 +208,7 @@ stopping line that lies.
|
||||
| **R-255** | Security & access | P3 | **The check that would catch a fourth secret-in-the-body covers 4 of 27 pages, and the cheap gate that covers all 36 templates is blind to the shape that actually shipped.** Filed 2026-08-08 while closing R-254, **because a partial guard reported as complete is worse than no guard — it stops the next person looking.** **Two nets, both measured.** **(1) `scripts/secret_in_markup_gate.py`** reads all 36 templates and convicts any `{{ … }}` naming a secret unless allowlisted with a reason. It catches `{{.RetrievalPassword}}` and `{{.InitialCreds.Password}}`, **and it catches a launder through a local variable** because the assignment itself names the secret (`{{$v := .InitialCreds.Password}}` is convicted — verified). **It is blind to a secret arriving under a NEUTRAL PAGE-DATA KEY** — `data["Tagline"] = creds.Password` then `{{.AppInfo.Tagline}}` passes it cleanly, also verified. **That is exactly the shape of R-254 site two** (`value="{{$val}}"` inside an `{{if eq .Type "secret"}}` branch), so the gate **would not have caught one of the three instances it was written for.** **(2) The runtime body assertion** — render the page and grep the response for a sentinel — catches every shape, including that one (demonstrated on the same planted leak the gate missed). But it needs each page's data to be constructible in a test, and **only 4 of 27 page templates have that today**: `settings_security`, `app_info`, `deploy`, `backups_restore` — the four that were touched by R-249/R-252/R-253/R-254 and therefore got their own tests. **The other 23 pages have no runtime coverage at all.** **What closing this needs, so the cost is not re-estimated:** a per-page data fixture for the remaining 23 (most need a wired `Server` — `stackMgr`, `backupMgr`, agent seams), then one table-driven test that renders each with a sentinel substituted for every string in its data and asserts the sentinel is absent. **That is real scaffolding, which is why it was NOT built inside R-254's session** rather than half-built and declared done. | **READY** — owner Viktor | — | — | operator |
|
||||
| **R-338** | Security & access | P3 | **`demo-hp` is not on the R-50 island at all, and `operations/nodes.md` states that it is.** The page records both fleet boxes as island-migrated 2026-07-25. True of `felhom-pve`; **false of `demo-hp`**, whose `agent.json` has `listen_addr: 192.168.0.87:8443` — the customer LAN address — and **no `island_bridge`/`island_guest_addr` keys at all**, whose guest 9201 has `net0` only (no `eth1`), and whose `vmbr9` exists with **zero members**. The controller's `controller.yaml` points at the LAN address, so the box works; this is inventory drift, not breakage. **Two costs.** A session trusting the page addresses the wrong endpoint — that happened on 2026-08-18 and the resulting timeout was briefly read as a fault. And the agent's local API is **bound to the customer LAN on this box** rather than to a point-to-point island, which is the exposure R-50 was built to remove — so a documented security property is claimed for a box that does not have it **Checked from source 2026-10-05 (burn-down round 2):** nodes.md:86-88 still claims demo-hp is on the R-50 island (`local_api` on 169.254.253.1:8443/vmbr9, guest eth1). git blame: that claim dates from e6b5fa1e (2026-07-30); the 2026-09-21 edit bcdd5b20 re-read addresses but only reworded the lan_resolver clause -- the island claim was NOT re-verified after the reprovision. Agent config path /etc/felhom-agent/agent.json (felhom-agent cmd/felhom-agent/main.go:171), island keys island_bridge (internal/config/config.go:246). | **READY (S) — NEW 2026-08-18** | — | Decide which is true: migrate `demo-hp` to the island, or correct `nodes.md`. Leaving both is the one option that keeps the doc lying | Viktor decides; CC executes |
|
||||
| **R-616** | Security & access | P3 | **[P3-LOW] The catalog credentials are stored in PLAINTEXT in the box's catalog clone and are printed by an ordinary `git remote -v`.** FOUND 2026-09-21 on guest 9202 while pointing it at a private drill catalog. `Syncer.buildRepoURL` injects `username:token` into the HTTPS URL, and `git clone` persists that URL as the clone's `origin`, so `<data>/catalog-cache/.git/config` holds the token in the clear and **any** diagnostic that prints the remote leaks it — which is what happened in this session's own transcript, and is the same shape as R-580 (`curl -w '%{redirect_url}'`). `maskRepoURL` exists and is used for the LOG lines, so the masking intent is already there; the stored remote is the half that was missed. **INERT ON THE FLEET TODAY** — the live catalog is public and `git.token` is empty on every real box — which is exactly why it should be fixed before it is not: the day the catalog goes private, every box carries a readable credential and every support session that runs `git remote -v` prints it. **Fix shape:** store the remote WITHOUT credentials and supply them per-fetch (a credential helper, `http.extraHeader`, or `GIT_ASKPASS`), and a test asserting the clone's stored `origin` contains no `@`. **Operator action from tonight, unrelated to the fix:** the Gitea `admin` token used for the drill repo was printed by that command and must be rotated. Evidence: `audits/update-night-2026-09-21/05-9202-follows-drill.txt` (redacted). | **READY — rank P3-LOW; owner: CC (controller); one operator action (rotate the Gitea admin token)** **2026-10-05 (burn-down night): FIXED on controller `main`** (`28a5203`; the catalog clone stores no credentials; the token is supplied per fetch; R-615's repo comparison ignores credentials on both sides, so a token never re-clones (`TestR616_TokenSetSameRepoNoRecloneOriginClean`) and a credentialed origin is cleaned at the next pull. The operator's Gitea admin token rotation (the row's second half) is still owed). Ships with the next controller release; close after delivery. **2026-10-06: DELIVERED** in controller v0.298.0 (the clone stores no credentials). Left: the operator's Gitea admin token rotation. | — | — | CC + operator |
|
||||
| **R-717** | Security & access | P3 | **[P3-LOW] opengist and wishlist keep their sign-up switch only in their own database — the box closes them with the address block alone.** MEASURED 2026-09-29: opengist `disable-signup` is an admin-panel setting (no env, no CLI); wishlist `system_config.enableSignup` (Prisma). Their blocks are case-insensitive and refused every trick shape (`audits/signup-lock-2026-09-29/B/`). **Fix direction:** an `after_setup` command that sets the database value (wishlist: a Node/Prisma one-liner; opengist: needs its sqlite with the app stopped). | **OPEN — P3; owner: CC** | — | — | CC |
|
||||
| **R-763** | Security & access | P3 | **[P2-MEDIUM] On wger a stranger can make an account after the household's setup, and every anonymous visit to the dashboard creates a guest account.** MEASURED 2026-10-01 on 9202 (live template `82fff32`), found by checklist row 3.4: after the admin existed, a stranger with no dashboard session `POST /en/user/registration` → 302, signed in with it → 302, read the API → 200; two anonymous `GET /en/dashboard` raised the user count from 2 to 4 (wger's middleware `create_temporary_user`, `utils/middleware.py:69`). Settings read inside the app: `ALLOW_REGISTRATION True`, `ALLOW_GUEST_USERS True` (the image defaults; the template sets neither). `GET /en/user/demo-entries` as a stranger answered 500. wger is FIRST-ADMIN class 3 (a known default login, fixed by `after_install`), so it never got decision 47's sign-up lock — it was not in R-711's list. Every crawler visit adds a user row to the household's database. **Needs:** per decision 47, close it after the first admin: `ALLOW_REGISTRATION=False` and `ALLOW_GUEST_USERS=False` (env switches the settings read — measure that the admin can still add family members, row 3.7), proven on 9202 as a stranger. `audits/new-app-checklist-2026-10-01/C/C8-signup-guest-media-static.txt` **-- 2026-10-01 (operator):** wger is `lifecycle: hidden` until this and its twin are fixed (catalog `55b8c8a`; read back on 9202: not on the app list, mealie control present). | **READY — rank P2-MEDIUM; owner: CC (catalog)** **Re-ranked 2026-10-03: P2→P3: wger is hidden from installs and no box runs it; needed only before it is offered again.** **2026-10-06: PUSHED** to the live catalog (`c265b37`; wger stays hidden). | — | — | CC |
|
||||
| **R-717** | Security & access | P3 | **[P3-LOW] opengist and wishlist keep their sign-up switch only in their own database — the box closes them with the address block alone.** MEASURED 2026-09-29: opengist `disable-signup` is an admin-panel setting (no env, no CLI); wishlist `system_config.enableSignup` (Prisma). Their blocks are case-insensitive and refused every trick shape (`audits/signup-lock-2026-09-29/B/`). **Fix direction:** an `after_setup` command that sets the database value (wishlist: a Node/Prisma one-liner; opengist: needs its sqlite with the app stopped). **-- 2026-10-06 (afternoon): NEEDS A DESIGN.** The controller's `after_setup` command form runs only when the lock is SET (`internal/stacks/after_setup.go` `applyNativeLock`, `lock && len(spec.Command) > 0`); the household's 15-minute window lifts only the env form. A database switch closed by a command would stay closed through the window, so the household could not let a family member sign up. Needs a lift command (an `open` twin) in the controller first. | **OPEN — P3; owner: CC** | — | — | CC |
|
||||
| **R-775** | Security & access | P3 | **[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** **Re-ranked 2026-10-03: P2→P3: Grimmory is now behind the family gate; only a family member can still lock a name.** | — | — | CC |
|
||||
| **R-782** | Security & access | P3 | **[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)** | — | — | CC |
|
||||
| **R-831** | Security & access | P3 | **The Hetzner storage API token (`HETZNER_TOKEN`, the storage project's token in `Secret/storagebox`) was printed into the 2026-10-03 session transcript** — CC read the gitignored `manifests/storagebox.secret.yaml` and its redaction pattern missed the quoted value. It can create, reset and delete Storage Box sub-accounts. Not rotated by the operator's choice (decision 73). **Rotation, whenever chosen (3 steps):** create a new token in the storage project in the Hetzner console → patch `Secret/storagebox` key `HETZNER_TOKEN` in `felhom-system` and `kubectl rollout restart deployment/hub` → delete the old token in the console. Rule for sessions: never print a file that holds secrets — read the one field needed. | **WAITING-ON-OPERATOR — rotation is his call** **Not rotated by the operator's rulings (2026-10-04 „keep using the current one"; 2026-10-05 option B) — restated 2026-10-05 18:23; the steps stay here.** | — | rotate when chosen | operator |
|
||||
|
||||
Reference in New Issue
Block a user