Files
felhom.eu/REPORT-visitors-2026-10-01.md

9.5 KiB
Raw Permalink Blame History

REPORT — the box tells visitors apart; the permanent-gate spike; SparkyFitness; R-772/R-773; release + golden (2026-10-01 night)

The Part table

Part Result Notes
A — visitors apart (build) DONE, with one change to the brief's sketch and one leg unmeasured The sketch plus 2 costs paid in the same rollout: a header clean-up at traefik, and 19 catalog router resets for the apps that read the LEFTMOST address. The brief named neither. ep0's one request was refused by Cloudflare before the box (R-779).
B — permanent family gate (spike) DONE — PASS on exit items 1–5; costs measured Exit test committed before the run. Build plan: 2 sessions. Waits for the operator (R-780).
C — SparkyFitness record DONE, with 7 rows open Bench (9401 rebuilt and destroyed) and 9202 measured. Its licence forbids commercial use (R-784, operator).
D — R-772, R-773 DONE Both red-proven and live on 9202. R-772 needed a second fix found live (0.286.0 → 0.286.1).
E — release, floor, golden DONE Controller 0.286.1 floored (min_agent 0.131.0); both demo boxes took it and reconciled traefik/cloudflared by themselves. Golden 0.286.1 baked, round-tripped and vouched; the currency gate is OK, not waived.

Baselines (re-verified): controller c1b123c64955, agent d766666ff8cf, felhom.eu bc9c7b49346b, catalog 94477cba435a. Ends at: controller a4a753b (v0.286.1), catalog de0a9bd, felhom.eu (this commit). Agent untouched. Architecture read: 01-topology-and-trust.md §5 and §7 (both corrected), 09 §3 decisions 45–49, 57–62; decision 63 added first (operator ruling).

Claims in the brief that turned out wrong (or right), named

  • clientIP takes the leftmost hop. Right (claim.go:232, pre-0.286).
  • A stranger can lock the household out of the dashboard. Right, through the tunnel only. Every tunnel visitor shared cloudflared's address as the key; a LAN visitor always had its own key.
  • Cloudflare appends to a client-sent X-Forwarded-For. Right, measured: 6.6.6.6,37.191.56.193 (M2). Also measured, not in the brief:
    • Cloudflare passes a client's X-Forwarded-Host and X-Forwarded-Port unchanged.
    • It strips a client's X-Real-IP.
    • It refuses a client-sent CF-Connecting-IP at the edge (403).
  • cloudflared's address is docker-assigned. Right (172.18.0.5, no ipv4_address).
  • The setup gate's token is minted only from a dashboard session. Right (ServeGateStart, one caller). But "the setup gate uses [the visitor address]" was wrong: the gate read no client address at all, so there was nothing to switch over. It now logs the visitor.
  • "Any reader takes the address at a fixed distance from the RIGHT." Half wrong. The distance differs by path: second from the right through the tunnel, first on the LAN. So a fixed count is wrong for one of the two paths, and readers must skip trusted proxies from the right. Count readers (calibre-web, tandoor, wger) were left alone for that reason.
  • The sketch (traefik trusts cloudflared) is enough. Not on its own. Trusting cloudflared makes the leftmost entry — written by a stranger — reach every app. 19 apps read exactly that one (several for rate limits: audiobookshelf and docmost by address only, papra skips its limit on a junk value). It also lets a stranger's X-Forwarded-Host through. Both were closed in the same rollout.
  • calibre-web TRUSTED_PROXY_COUNT, wger AXES_* proxy count. Left as they are. Both are count readers, and both lock per NAME (decisions 58, 61). A count of 2 would break the LAN path, and for calibre-web also its proto/host.
  • Grimmory improves. Right as read in source; not measured in this session (R-775 updated).

Part A — the box tells visitors apart

  • Paths: audits/visitors-2026-10-01/A/ — M1–M4 before, M5 after, DESIGN.md (the path table, the options, the choice, the docs quoted).
  • Built (controller v0.286.0/.1):
    • A felhom-tunnel network 172.16.253.0/29 with docker's allocation confined to .4/30. The hand prototype found traefik grabbing .2, so cloudflared now sits at a fixed .2 and traefik at a fixed .3.
    • traefik trusts 172.16.253.2/32 only, and runs felhom-forwarded@file.
    • EnsureBaseStack reconciles a running traefik or cloudflared.
    • clientaddr.go, plus IPv6 counted per /64.
    • The dashboard login messages are informal, in both languages.
  • Red-proofs: RP-A1, RP-A2 and RP-A3 each fail on their mutant.
  • Live:
    • 9202, simulated tunnel: a stranger rotating a forged leftmost address was locked after 5 tries; the household from another address got in at once; LAN and impostor forgeries were counted as themselves (L1).
    • demo-hp, real tunnel: an app sees 6.6.6.6,37.191.56.193, 172.16.253.2. The dashboard counter was keyed on 37.191.56.193 and locked after 5. Every app answered (L2, M5, E/).
    • ep0's one request: refused 403 by Cloudflare's edge, with no line in the box's log, so the second outside address is unmeasured on the real tunnel → R-779.
  • Apps (sweep of all 56 apps plus Grimmory and MeTube, read in source: A/sweep/):
    • 19 router resets, one commit each, shipped before the release. docmost on the real tunnel still throttles a rotating forger.
    • BookStack APP_PROXIES, with 3.6 re-measured before and after: the household is no longer throttled by the stranger.
    • Gains with no change: Home Assistant (it treated every internet visitor as "local"), actualbudget, immich, dawarich, claper, termix, Grimmory.
    • Open: kimai, zipline, vikunja, nextcloud (R-776); Emby and Jellyfin LAN rights (R-777).
    • Checklist 3.10 added.

Part B — the permanent gate (spike)

audits/permanent-gate-2026-10-01/VERDICT.md. PASS on items 1–5.

  • 36 stranger requests, 0 reached an app.
  • Family members got their own 30-day logins, websockets worked, and logout worked.
  • A stranger's guesses locked only him.
  • OPDS, Kobo and KOReader worked through anchored exceptions, with the app's own login still in force.
  • The family login never opened the dashboard.

Costs: +0.4 ms per request; with the gate down, apps answer 500 (closed); the build is 2 sessions. One build requirement: anchor every exception (/api/v1/opdsx slipped past an unanchored prefix; Grimmory's own login still refused it). Fully torn down.

Part C — SparkyFitness

app-catalog-felhom.eu/onboarding/sparkyfitness.md.

  • Bench: 5.1 server 33 %; 5.2 server 38 % and db 53 % under a heavy burst, 0 kills; 1.4 v0.17.2 → v0.17.3 proven; 2.1 CLEAN; 9.1 green.
  • 9202:
    • A stranger got nothing during install.
    • Sign-up was blocked in 3 spellings, and blocked again after remove + restore.
    • Sign-in limit (3.6): ~10 s for everyone (R-783).
    • 4.4: the state read degraded within 10 s.
    • Backup → remove → restore read the data back.
  • Open: 0.1 licence (R-784 — non-commercial only), 0.5, 0.7, 1.6, 1.7, 5.4, 8.3 (R-786). The pin is 11 releases and a major behind (R-785). A licence sweep of the whole catalog is owed (R-787).

Part D — R-772, R-773

  • R-772: not-run → healthy:false, not_checked:true, plus a line on the app page. 0.286.0 still waited for the last healthy record's 5-minute interval; 0.286.1 sees it on the next tick. Live: not_checked 14 s after the stop, healthy 42 s after the start. Red-proofs RP-D1 and RP-D1b. Readers of health_probe: the state override and the API/page only; the hub never sees it.
  • R-773: a restore of a removed app writes the lock record (opened_by: restore) and the block before the start. Live on Karakeep and SparkyFitness: a stranger got 403 after remove + restore. Red-proof RP-D2.

Part E — release, floor, golden

  • Release: controller 0.286.1. Full suite rc 0, gates green. 0.286.0 ran on 9202 only.
  • Floor: 0.286.1 + min_agent 0.131.0 → demo-hp and demo-felhom on 0.286.1 within ~60 s. Each created the network, recreated traefik and moved cloudflared by itself (E/rollout-*.txt).
  • Golden 0.286.1 (documentation/tests/golden-0.286.1-2026-10-01/):
    • sha 1dec051d…a89; the round trip is equal; token leaks 0, with the positive control at 1.
    • Vouched: golden 0.286.1 / agent 0.138.0 / min_agent 0.131.0. The hub then refused golden 0.285.0.
    • golden_currency_gate: OK (not waived). The waiver was not renewed — the bake ran.

Rows

  • Closed: R-753, R-754, R-772, R-773.
  • Narrowed: R-775.
  • Opened: R-776..R-787 (12).
  • Register: 410 → 422.
  • Capability map: two rows added (PARTIAL: visitors apart; MISSING/spiked: the family gate).
  • unproven.py: unchanged — 35 of 55 not walked.

Teardown (three layers)

  • Machine:
    • 9202: karakeep, bookstack and sparkyfitness removed with their data and backups through the product; the echo containers, the spike (gate, Grimmory, MariaDB, MeTube, volumes, network, dynamic file), test images and helper files removed. 9202 keeps controller 0.286.1, its new traefik config and felhom-tunnel — the product's own.
    • demo-hp 9201: a1-echo and its image removed; its traefik config was restored after the M2 minute, and is now the release's.
  • Host: the bench 9401 was rebuilt and destroyed (pvesm before/after in C/bench/C0, C9). The drill VM is powered off and reverted to virgin; CT 9100 destroyed.
  • Hub: the floor and the artifact manifest changed on purpose (above); no customer or appliance records created.
  • ep0: one HTTP request, nothing else.