Part C/D: gate built + proven live (v0.280.0), defaults fixed, floor 0.280.0; decision 46 outcome; 01 §5 who may reach an app; R-707 narrowed, R-708/R-709/R-712 closed, R-713 filed
gates / gates (push) Successful in 26s

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_0159rPz1ZhFKsS53msqPYxtS
This commit is contained in:
2026-09-29 09:20:47 +02:00
parent 14ae67f0b8
commit b0f8ffb801
46 changed files with 1632 additions and 4 deletions
@@ -124,6 +124,18 @@ a trust boundary too: a default password, or a "first visitor creates the admin"
household acts. Rule and per-app status: `09` §3 decision 45 and `app-catalog-felhom.eu/FIRST-ADMIN.md` (the audit
of all 53 apps, 2026-09-28).
**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
game ports; none uses host networking — read from the catalog 2026-09-29). traefik routes by host name: the tunnel's
`*.domain` and the LAN both land there. The dashboard (`felhom.<domain>`) has its own password; its session cookie is
**host-only** and never reaches an app host. An app answers anyone who reaches its host, with the app's own login —
**except while its setup gate is closed** (`09` §3 decision 46, controller ≥ 0.280.0): then traefik asks the
controller first (`forwardAuth`), and only a browser holding a gate cookie for that one host gets through. The cookie
is minted after a valid dashboard session vouched for the browser (a 60-second, one-use token bound to the host, on
the dashboard's own `/__gate/start`). The controller is in an app's request path ONLY while its gate is closed; once
open, the gate's traefik router is removed and the app is reached exactly as without it. The gate decides who creates
the first admin; it does not decide who may sign up afterwards (R-711).
---
## 6. Enrollment & identity
@@ -502,7 +502,15 @@ R-636's louder repeated alarm.
app is not yet set up, the box lets only a person logged in to the household's dashboard reach it; the gate opens
when the app's own status says an admin exists, or when the household presses "Done, I set it up". Option B: one
fix per app (route (b), R-707). A is built only if the spike passes its exit test in writing; otherwise B continues
and the spike's result is the recorded reason. Outcome: *(filled in by the 2026-09-29 session)*.
and the spike's result is the recorded reason. **Outcome (2026-09-29): the spike PASSED** (`audits/login-gate-2026-09-29/
B/B-VERDICT.md`, written before any build): on 9202 a stranger never reached a first-setup screen (~530 polls during
two installs, 0 app answers), the household passed with its dashboard session in 0.2 s, both probes flipped on the
setup, immich's phone-app API worked unchanged once the gate's router was removed, and the dashboard cookie was never
widened (a redirect handshake mints a 60-second, one-use token per app host instead). **Built in controller v0.280.0**
and proven live on immich, n8n, audiobookshelf (probe) and uptime-kuma (button). Costs, stated: ~2 ms per gated
request; a gated app answers 500 while the controller is down (closed, not open); a phone app cannot reach a gated
app; an app with no probe waits for the household's press, and the press trusts the household. Not covered by the
gate: open sign-up after the setup (R-711). Design record: `01-topology-and-trust.md` §5.
Same day, operator: CC changes the admin passwords of demo-hp's installed bookstack and calibre-web and stores them
in the operator's credentials file (not in any repo).
@@ -0,0 +1,5 @@
before: gitea.dooplex.hu/admin/felhom-controller:0.279.0
gitea.dooplex.hu/admin/felhom-controller:0.280.0 Up 8 seconds (healthy)
2026/09/29 06:53:08 undo.go:675: [INFO] [stacks] applied-meta backfill (R-646): recorded 0 []; skipped 0 [] — not current with the catalog, their pinned version's .felhom.yml is no longer on the box
2026/09/29 06:53:08 scheduler.go:102: [INFO] [scheduler] Registered periodic job: conversion-copy-release (every 1h0m0s)
2026/09/29 06:53:08 scheduler.go:67: [DEBUG] [scheduler] periodic job registered: name="conversion-copy-release" interval=1h0m0s totalJobs=10
@@ -0,0 +1,16 @@
09:03:37 immich catalog meta: setup_gate = True probe = isInitialized
09:03:40 [1] made the drive paths this app requires: ['/mnt/felhom-drives/scratch_hdd/userdata/immich']
09:03:40 [1] required fields filled beyond DOMAIN/SUBDOMAIN: ['HDD_PATH']
09:03:41 immich deploy -> 202 {'ok': True, 'message': 'Telepítés elindítva – az állapot a kártyán követhető'}
09:03:41 n8n catalog meta: setup_gate = True probe = data.userManagement.showSetupOnFirstLoad
09:03:41 n8n deploy -> 202 {'ok': True, 'message': 'Telepítés elindítva – az állapot a kártyán követhető'}
09:03:41 audiobookshelf catalog meta: setup_gate = True probe = isInit
09:03:44 [1] made the drive paths this app requires: ['/mnt/felhom-drives/scratch_hdd/userdata/audiobookshelf']
09:03:44 [1] required fields filled beyond DOMAIN/SUBDOMAIN: ['HDD_PATH']
09:03:44 audiobookshelf deploy -> 202 {'ok': True, 'message': 'Telepítés elindítva – az állapot a kártyán követhető'}
09:03:45 uptime-kuma catalog meta: setup_gate = True probe = None
09:03:45 uptime-kuma deploy -> 202 {'ok': True, 'message': 'Telepítés elindítva – az állapot a kártyán követhető'}
09:04:29 immich state running | gate file present | record {'state': 'closed', 'since': '2026-09-29T07:03:40Z', 'hosts': ['g-immich.enkisfelhom.hu']}
09:04:32 n8n state running | gate file present | record {'state': 'closed', 'since': '2026-09-29T07:03:41Z', 'hosts': ['g-n8n.enkisfelhom.hu']}
09:04:35 audiobookshelf state running | gate file present | record {'state': 'closed', 'since': '2026-09-29T07:03:44Z', 'hosts': ['g-abs.enkisfelhom.hu']}
09:04:49 uptime-kuma state running | gate file present | record {'state': 'closed', 'since': '2026-09-29T07:03:45Z', 'hosts': ['g-kuma.enkisfelhom.hu']}
@@ -0,0 +1,837 @@
# stranger4.sh, 9202, 2026-09-29 07:03:31-07:08Z (UTC): per second, each gated host, API-style + browser-style. Gates opened: immich 07:06:02, n8n+audiobookshelf 07:06:22 (probe), uptime-kuma 07:07:08 (press). The one 500 at 07:06:53 is the controller restart (C6): closed, not open.
07:03:30 g-n8n api=[404 page not found|404] browser=404
07:03:30 g-immich api=[404 page not found|404] browser=404
07:03:30 g-abs api=[404 page not found|404] browser=404
07:03:30 g-kuma api=[404 page not found|404] browser=404
07:03:31 g-n8n api=[404 page not found|404] browser=404
07:03:31 g-immich api=[404 page not found|404] browser=404
07:03:31 g-abs api=[404 page not found|404] browser=404
07:03:31 g-kuma api=[404 page not found|404] browser=404
07:03:32 g-n8n api=[404 page not found|404] browser=404
07:03:32 g-immich api=[404 page not found|404] browser=404
07:03:32 g-abs api=[404 page not found|404] browser=404
07:03:32 g-kuma api=[404 page not found|404] browser=404
07:03:33 g-n8n api=[404 page not found|404] browser=404
07:03:33 g-immich api=[404 page not found|404] browser=404
07:03:33 g-abs api=[404 page not found|404] browser=404
07:03:33 g-kuma api=[404 page not found|404] browser=404
07:03:34 g-n8n api=[404 page not found|404] browser=404
07:03:35 g-immich api=[404 page not found|404] browser=404
07:03:35 g-abs api=[404 page not found|404] browser=404
07:03:35 g-kuma api=[404 page not found|404] browser=404
07:03:36 g-n8n api=[404 page not found|404] browser=404
07:03:36 g-immich api=[404 page not found|404] browser=404
07:03:36 g-abs api=[404 page not found|404] browser=404
07:03:36 g-kuma api=[404 page not found|404] browser=404
07:03:37 g-n8n api=[404 page not found|404] browser=404
07:03:37 g-immich api=[404 page not found|404] browser=404
07:03:37 g-abs api=[404 page not found|404] browser=404
07:03:37 g-kuma api=[404 page not found|404] browser=404
07:03:38 g-n8n api=[404 page not found|404] browser=404
07:03:38 g-immich api=[404 page not found|404] browser=404
07:03:38 g-abs api=[404 page not found|404] browser=404
07:03:38 g-kuma api=[404 page not found|404] browser=404
07:03:39 g-n8n api=[404 page not found|404] browser=404
07:03:39 g-immich api=[404 page not found|404] browser=404
07:03:39 g-abs api=[404 page not found|404] browser=404
07:03:39 g-kuma api=[404 page not found|404] browser=404
07:03:40 g-n8n api=[404 page not found|404] browser=404
07:03:41 g-immich api=[404 page not found|404] browser=404
07:03:41 g-abs api=[404 page not found|404] browser=404
07:03:41 g-kuma api=[404 page not found|404] browser=404
07:03:42 g-n8n api=[404 page not found|404] browser=404
07:03:42 g-immich api=[404 page not found|404] browser=404
07:03:42 g-abs api=[404 page not found|404] browser=404
07:03:42 g-kuma api=[404 page not found|404] browser=404
07:03:43 g-n8n api=[404 page not found|404] browser=404
07:03:43 g-immich api=[404 page not found|404] browser=404
07:03:43 g-abs api=[404 page not found|404] browser=404
07:03:43 g-kuma api=[404 page not found|404] browser=404
07:03:44 g-n8n api=[404 page not found|404] browser=404
07:03:44 g-immich api=[404 page not found|404] browser=404
07:03:44 g-abs api=[404 page not found|404] browser=404
07:03:44 g-kuma api=[404 page not found|404] browser=404
07:03:45 g-n8n api=[404 page not found|404] browser=404
07:03:45 g-immich api=[404 page not found|404] browser=404
07:03:45 g-abs api=[404 page not found|404] browser=404
07:03:45 g-kuma api=[404 page not found|404] browser=404
07:03:46 g-n8n api=[404 page not found|404] browser=404
07:03:47 g-immich api=[404 page not found|404] browser=404
07:03:47 g-abs api=[404 page not found|404] browser=404
07:03:47 g-kuma api=[404 page not found|404] browser=404
07:03:48 g-n8n api=[404 page not found|404] browser=404
07:03:48 g-immich api=[404 page not found|404] browser=404
07:03:48 g-abs api=[404 page not found|404] browser=404
07:03:48 g-kuma api=[404 page not found|404] browser=404
07:03:49 g-n8n api=[404 page not found|404] browser=404
07:03:49 g-immich api=[404 page not found|404] browser=404
07:03:49 g-abs api=[404 page not found|404] browser=404
07:03:49 g-kuma api=[404 page not found|404] browser=404
07:03:50 g-n8n api=[404 page not found|404] browser=404
07:03:50 g-immich api=[404 page not found|404] browser=404
07:03:50 g-abs api=[404 page not found|404] browser=404
07:03:50 g-kuma api=[404 page not found|404] browser=404
07:03:51 g-n8n api=[404 page not found|404] browser=404
07:03:51 g-immich api=[404 page not found|404] browser=404
07:03:51 g-abs api=[404 page not found|404] browser=404
07:03:51 g-kuma api=[404 page not found|404] browser=404
07:03:52 g-n8n api=[404 page not found|404] browser=404
07:03:52 g-immich api=[404 page not found|404] browser=404
07:03:53 g-abs api=[404 page not found|404] browser=404
07:03:53 g-kuma api=[404 page not found|404] browser=404
07:03:54 g-n8n api=[{"error":"this app is waiting for its first setup"}|401] browser=302
07:03:54 g-immich api=[404 page not found|404] browser=404
07:03:54 g-abs api=[404 page not found|404] browser=404
07:03:54 g-kuma api=[404 page not found|404] browser=404
07:03:55 g-n8n api=[{"error":"this app is waiting for its first setup"}|401] browser=302
07:03:55 g-immich api=[404 page not found|404] browser=404
07:03:55 g-abs api=[404 page not found|404] browser=404
07:03:55 g-kuma api=[404 page not found|404] browser=404
07:03:56 g-n8n api=[{"error":"this app is waiting for its first setup"}|401] browser=302
07:03:56 g-immich api=[404 page not found|404] browser=404
07:03:56 g-abs api=[404 page not found|404] browser=404
07:03:56 g-kuma api=[404 page not found|404] browser=404
07:03:57 g-n8n api=[{"error":"this app is waiting for its first setup"}|401] browser=302
07:03:57 g-immich api=[404 page not found|404] browser=404
07:03:57 g-abs api=[404 page not found|404] browser=404
07:03:57 g-kuma api=[404 page not found|404] browser=404
07:03:58 g-n8n api=[{"error":"this app is waiting for its first setup"}|401] browser=302
07:03:59 g-immich api=[404 page not found|404] browser=404
07:03:59 g-abs api=[404 page not found|404] browser=404
07:03:59 g-kuma api=[404 page not found|404] browser=404
07:04:00 g-n8n api=[{"error":"this app is waiting for its first setup"}|401] browser=302
07:04:00 g-immich api=[404 page not found|404] browser=404
07:04:00 g-abs api=[404 page not found|404] browser=404
07:04:00 g-kuma api=[404 page not found|404] browser=404
07:04:01 g-n8n api=[{"error":"this app is waiting for its first setup"}|401] browser=302
07:04:01 g-immich api=[404 page not found|404] browser=404
07:04:01 g-abs api=[404 page not found|404] browser=404
07:04:01 g-kuma api=[404 page not found|404] browser=404
07:04:02 g-n8n api=[{"error":"this app is waiting for its first setup"}|401] browser=302
07:04:02 g-immich api=[404 page not found|404] browser=404
07:04:02 g-abs api=[404 page not found|404] browser=404
07:04:02 g-kuma api=[404 page not found|404] browser=404
07:04:03 g-n8n api=[{"error":"this app is waiting for its first setup"}|401] browser=302
07:04:03 g-immich api=[404 page not found|404] browser=404
07:04:03 g-abs api=[404 page not found|404] browser=404
07:04:03 g-kuma api=[404 page not found|404] browser=404
07:04:04 g-n8n api=[{"error":"this app is waiting for its first setup"}|401] browser=302
07:04:05 g-immich api=[404 page not found|404] browser=404
07:04:05 g-abs api=[404 page not found|404] browser=404
07:04:05 g-kuma api=[404 page not found|404] browser=404
07:04:06 g-n8n api=[{"error":"this app is waiting for its first setup"}|401] browser=302
07:04:06 g-immich api=[404 page not found|404] browser=404
07:04:06 g-abs api=[404 page not found|404] browser=404
07:04:06 g-kuma api=[404 page not found|404] browser=404
07:04:07 g-n8n api=[{"error":"this app is waiting for its first setup"}|401] browser=302
07:04:07 g-immich api=[404 page not found|404] browser=404
07:04:07 g-abs api=[404 page not found|404] browser=404
07:04:07 g-kuma api=[404 page not found|404] browser=404
07:04:08 g-n8n api=[{"error":"this app is waiting for its first setup"}|401] browser=302
07:04:08 g-immich api=[404 page not found|404] browser=404
07:04:08 g-abs api=[404 page not found|404] browser=404
07:04:08 g-kuma api=[404 page not found|404] browser=404
07:04:09 g-n8n api=[{"error":"this app is waiting for its first setup"}|401] browser=302
07:04:09 g-immich api=[404 page not found|404] browser=404
07:04:09 g-abs api=[404 page not found|404] browser=404
07:04:09 g-kuma api=[404 page not found|404] browser=404
07:04:11 g-n8n api=[{"error":"this app is waiting for its first setup"}|401] browser=302
07:04:11 g-immich api=[404 page not found|404] browser=404
07:04:11 g-abs api=[404 page not found|404] browser=404
07:04:11 g-kuma api=[404 page not found|404] browser=404
07:04:12 g-n8n api=[{"error":"this app is waiting for its first setup"}|401] browser=302
07:04:12 g-immich api=[404 page not found|404] browser=404
07:04:12 g-abs api=[404 page not found|404] browser=404
07:04:12 g-kuma api=[404 page not found|404] browser=404
07:04:13 g-n8n api=[{"error":"this app is waiting for its first setup"}|401] browser=302
07:04:13 g-immich api=[404 page not found|404] browser=404
07:04:13 g-abs api=[{"error":"this app is waiting for its first setup"}|401] browser=302
07:04:13 g-kuma api=[404 page not found|404] browser=404
07:04:14 g-n8n api=[{"error":"this app is waiting for its first setup"}|401] browser=302
07:04:14 g-immich api=[{"error":"this app is waiting for its first setup"}|401] browser=302
07:04:14 g-abs api=[{"error":"this app is waiting for its first setup"}|401] browser=302
07:04:14 g-kuma api=[404 page not found|404] browser=404
07:04:15 g-n8n api=[{"error":"this app is waiting for its first setup"}|401] browser=302
07:04:15 g-immich api=[{"error":"this app is waiting for its first setup"}|401] browser=302
07:04:15 g-abs api=[{"error":"this app is waiting for its first setup"}|401] browser=302
07:04:15 g-kuma api=[404 page not found|404] browser=404
07:04:17 g-n8n api=[{"error":"this app is waiting for its first setup"}|401] browser=302
07:04:17 g-immich api=[{"error":"this app is waiting for its first setup"}|401] browser=302
07:04:17 g-abs api=[{"error":"this app is waiting for its first setup"}|401] browser=302
07:04:17 g-kuma api=[404 page not found|404] browser=404
07:04:18 g-n8n api=[{"error":"this app is waiting for its first setup"}|401] browser=302
07:04:18 g-immich api=[{"error":"this app is waiting for its first setup"}|401] browser=302
07:04:18 g-abs api=[{"error":"this app is waiting for its first setup"}|401] browser=302
07:04:18 g-kuma api=[404 page not found|404] browser=404
07:04:19 g-n8n api=[{"error":"this app is waiting for its first setup"}|401] browser=302
07:04:19 g-immich api=[{"error":"this app is waiting for its first setup"}|401] browser=302
07:04:19 g-abs api=[{"error":"this app is waiting for its first setup"}|401] browser=302
07:04:19 g-kuma api=[404 page not found|404] browser=404
07:04:20 g-n8n api=[{"error":"this app is waiting for its first setup"}|401] browser=302
07:04:20 g-immich api=[{"error":"this app is waiting for its first setup"}|401] browser=302
07:04:20 g-abs api=[{"error":"this app is waiting for its first setup"}|401] browser=302
07:04:20 g-kuma api=[404 page not found|404] browser=404
07:04:21 g-n8n api=[{"error":"this app is waiting for its first setup"}|401] browser=302
07:04:21 g-immich api=[{"error":"this app is waiting for its first setup"}|401] browser=302
07:04:21 g-abs api=[{"error":"this app is waiting for its first setup"}|401] browser=302
07:04:21 g-kuma api=[404 page not found|404] browser=404
07:04:23 g-n8n api=[{"error":"this app is waiting for its first setup"}|401] browser=302
07:04:23 g-immich api=[{"error":"this app is waiting for its first setup"}|401] browser=302
07:04:23 g-abs api=[{"error":"this app is waiting for its first setup"}|401] browser=302
07:04:23 g-kuma api=[404 page not found|404] browser=404
07:04:24 g-n8n api=[{"error":"this app is waiting for its first setup"}|401] browser=302
07:04:24 g-immich api=[{"error":"this app is waiting for its first setup"}|401] browser=302
07:04:24 g-abs api=[{"error":"this app is waiting for its first setup"}|401] browser=302
07:04:24 g-kuma api=[404 page not found|404] browser=404
07:04:25 g-n8n api=[{"error":"this app is waiting for its first setup"}|401] browser=302
07:04:25 g-immich api=[{"error":"this app is waiting for its first setup"}|401] browser=302
07:04:25 g-abs api=[{"error":"this app is waiting for its first setup"}|401] browser=302
07:04:25 g-kuma api=[404 page not found|404] browser=404
07:04:26 g-n8n api=[{"error":"this app is waiting for its first setup"}|401] browser=302
07:04:26 g-immich api=[{"error":"this app is waiting for its first setup"}|401] browser=302
07:04:26 g-abs api=[{"error":"this app is waiting for its first setup"}|401] browser=302
07:04:26 g-kuma api=[404 page not found|404] browser=404
07:04:27 g-n8n api=[{"error":"this app is waiting for its first setup"}|401] browser=302
07:04:27 g-immich api=[{"error":"this app is waiting for its first setup"}|401] browser=302
07:04:27 g-abs api=[{"error":"this app is waiting for its first setup"}|401] browser=302
07:04:28 g-kuma api=[404 page not found|404] browser=404
07:04:29 g-n8n api=[{"error":"this app is waiting for its first setup"}|401] browser=302
07:04:29 g-immich api=[{"error":"this app is waiting for its first setup"}|401] browser=302
07:04:29 g-abs api=[{"error":"this app is waiting for its first setup"}|401] browser=302
07:04:29 g-kuma api=[404 page not found|404] browser=404
07:04:30 g-n8n api=[{"error":"this app is waiting for its first setup"}|401] browser=302
07:04:30 g-immich api=[{"error":"this app is waiting for its first setup"}|401] browser=302
07:04:30 g-abs api=[{"error":"this app is waiting for its first setup"}|401] browser=302
07:04:30 g-kuma api=[404 page not found|404] browser=404
07:04:31 g-n8n api=[{"error":"this app is waiting for its first setup"}|401] browser=302
07:04:31 g-immich api=[{"error":"this app is waiting for its first setup"}|401] browser=302
07:04:31 g-abs api=[{"error":"this app is waiting for its first setup"}|401] browser=302
07:04:31 g-kuma api=[404 page not found|404] browser=404
07:04:32 g-n8n api=[{"error":"this app is waiting for its first setup"}|401] browser=302
07:04:32 g-immich api=[{"error":"this app is waiting for its first setup"}|401] browser=302
07:04:32 g-abs api=[{"error":"this app is waiting for its first setup"}|401] browser=302
07:04:32 g-kuma api=[404 page not found|404] browser=404
07:04:33 g-n8n api=[{"error":"this app is waiting for its first setup"}|401] browser=302
07:04:33 g-immich api=[{"error":"this app is waiting for its first setup"}|401] browser=302
07:04:33 g-abs api=[{"error":"this app is waiting for its first setup"}|401] browser=302
07:04:34 g-kuma api=[{"error":"this app is waiting for its first setup"}|401] browser=302
07:04:35 g-n8n api=[{"error":"this app is waiting for its first setup"}|401] browser=302
07:04:35 g-immich api=[{"error":"this app is waiting for its first setup"}|401] browser=302
07:04:35 g-abs api=[{"error":"this app is waiting for its first setup"}|401] browser=302
07:04:35 g-kuma api=[{"error":"this app is waiting for its first setup"}|401] browser=302
07:04:36 g-n8n api=[{"error":"this app is waiting for its first setup"}|401] browser=302
07:04:36 g-immich api=[{"error":"this app is waiting for its first setup"}|401] browser=302
07:04:36 g-abs api=[{"error":"this app is waiting for its first setup"}|401] browser=302
07:04:36 g-kuma api=[{"error":"this app is waiting for its first setup"}|401] browser=302
07:04:37 g-n8n api=[{"error":"this app is waiting for its first setup"}|401] browser=302
07:04:37 g-immich api=[{"error":"this app is waiting for its first setup"}|401] browser=302
07:04:37 g-abs api=[{"error":"this app is waiting for its first setup"}|401] browser=302
07:04:37 g-kuma api=[{"error":"this app is waiting for its first setup"}|401] browser=302
07:04:38 g-n8n api=[{"error":"this app is waiting for its first setup"}|401] browser=302
07:04:38 g-immich api=[{"error":"this app is waiting for its first setup"}|401] browser=302
07:04:38 g-abs api=[{"error":"this app is waiting for its first setup"}|401] browser=302
07:04:38 g-kuma api=[{"error":"this app is waiting for its first setup"}|401] browser=302
07:04:39 g-n8n api=[{"error":"this app is waiting for its first setup"}|401] browser=302
07:04:39 g-immich api=[{"error":"this app is waiting for its first setup"}|401] browser=302
07:04:39 g-abs api=[{"error":"this app is waiting for its first setup"}|401] browser=302
07:04:40 g-kuma api=[{"error":"this app is waiting for its first setup"}|401] browser=302
07:04:41 g-n8n api=[{"error":"this app is waiting for its first setup"}|401] browser=302
07:04:41 g-immich api=[{"error":"this app is waiting for its first setup"}|401] browser=302
07:04:41 g-abs api=[{"error":"this app is waiting for its first setup"}|401] browser=302
07:04:41 g-kuma api=[{"error":"this app is waiting for its first setup"}|401] browser=302
07:04:42 g-n8n api=[{"error":"this app is waiting for its first setup"}|401] browser=302
07:04:42 g-immich api=[{"error":"this app is waiting for its first setup"}|401] browser=302
07:04:42 g-abs api=[{"error":"this app is waiting for its first setup"}|401] browser=302
07:04:42 g-kuma api=[{"error":"this app is waiting for its first setup"}|401] browser=302
07:04:43 g-n8n api=[{"error":"this app is waiting for its first setup"}|401] browser=302
07:04:43 g-immich api=[{"error":"this app is waiting for its first setup"}|401] browser=302
07:04:43 g-abs api=[{"error":"this app is waiting for its first setup"}|401] browser=302
07:04:43 g-kuma api=[{"error":"this app is waiting for its first setup"}|401] browser=302
07:04:44 g-n8n api=[{"error":"this app is waiting for its first setup"}|401] browser=302
07:04:44 g-immich api=[{"error":"this app is waiting for its first setup"}|401] browser=302
07:04:44 g-abs api=[{"error":"this app is waiting for its first setup"}|401] browser=302
07:04:44 g-kuma api=[{"error":"this app is waiting for its first setup"}|401] browser=302
07:04:45 g-n8n api=[{"error":"this app is waiting for its first setup"}|401] browser=302
07:04:45 g-immich api=[{"error":"this app is waiting for its first setup"}|401] browser=302
07:04:45 g-abs api=[{"error":"this app is waiting for its first setup"}|401] browser=302
07:04:46 g-kuma api=[{"error":"this app is waiting for its first setup"}|401] browser=302
07:04:47 g-n8n api=[{"error":"this app is waiting for its first setup"}|401] browser=302
07:04:47 g-immich api=[{"error":"this app is waiting for its first setup"}|401] browser=302
07:04:47 g-abs api=[{"error":"this app is waiting for its first setup"}|401] browser=302
07:04:47 g-kuma api=[{"error":"this app is waiting for its first setup"}|401] browser=302
07:04:48 g-n8n api=[{"error":"this app is waiting for its first setup"}|401] browser=302
07:04:48 g-immich api=[{"error":"this app is waiting for its first setup"}|401] browser=302
07:04:48 g-abs api=[{"error":"this app is waiting for its first setup"}|401] browser=302
07:04:48 g-kuma api=[{"error":"this app is waiting for its first setup"}|401] browser=302
07:04:49 g-n8n api=[{"error":"this app is waiting for its first setup"}|401] browser=302
07:04:49 g-immich api=[{"error":"this app is waiting for its first setup"}|401] browser=302
07:04:49 g-abs api=[{"error":"this app is waiting for its first setup"}|401] browser=302
07:04:49 g-kuma api=[{"error":"this app is waiting for its first setup"}|401] browser=302
07:04:50 g-n8n api=[{"error":"this app is waiting for its first setup"}|401] browser=302
07:04:50 g-immich api=[{"error":"this app is waiting for its first setup"}|401] browser=302
07:04:50 g-abs api=[{"error":"this app is waiting for its first setup"}|401] browser=302
07:04:50 g-kuma api=[{"error":"this app is waiting for its first setup"}|401] browser=302
07:04:51 g-n8n api=[{"error":"this app is waiting for its first setup"}|401] browser=302
07:04:51 g-immich api=[{"error":"this app is waiting for its first setup"}|401] browser=302
07:04:51 g-abs api=[{"error":"this app is waiting for its first setup"}|401] browser=302
07:04:51 g-kuma api=[{"error":"this app is waiting for its first setup"}|401] browser=302
07:04:53 g-n8n api=[{"error":"this app is waiting for its first setup"}|401] browser=302
07:04:53 g-immich api=[{"error":"this app is waiting for its first setup"}|401] browser=302
07:04:53 g-abs api=[{"error":"this app is waiting for its first setup"}|401] browser=302
07:04:53 g-kuma api=[{"error":"this app is waiting for its first setup"}|401] browser=302
07:04:54 g-n8n api=[{"error":"this app is waiting for its first setup"}|401] browser=302
07:04:54 g-immich api=[{"error":"this app is waiting for its first setup"}|401] browser=302
07:04:54 g-abs api=[{"error":"this app is waiting for its first setup"}|401] browser=302
07:04:54 g-kuma api=[{"error":"this app is waiting for its first setup"}|401] browser=302
07:04:55 g-n8n api=[{"error":"this app is waiting for its first setup"}|401] browser=302
07:04:55 g-immich api=[{"error":"this app is waiting for its first setup"}|401] browser=302
07:04:55 g-abs api=[{"error":"this app is waiting for its first setup"}|401] browser=302
07:04:55 g-kuma api=[{"error":"this app is waiting for its first setup"}|401] browser=302
07:04:56 g-n8n api=[{"error":"this app is waiting for its first setup"}|401] browser=302
07:04:56 g-immich api=[{"error":"this app is waiting for its first setup"}|401] browser=302
07:04:56 g-abs api=[{"error":"this app is waiting for its first setup"}|401] browser=302
07:04:56 g-kuma api=[{"error":"this app is waiting for its first setup"}|401] browser=302
07:04:57 g-n8n api=[{"error":"this app is waiting for its first setup"}|401] browser=302
07:04:57 g-immich api=[{"error":"this app is waiting for its first setup"}|401] browser=302
07:04:57 g-abs api=[{"error":"this app is waiting for its first setup"}|401] browser=302
07:04:58 g-kuma api=[{"error":"this app is waiting for its first setup"}|401] browser=302
07:04:59 g-n8n api=[{"error":"this app is waiting for its first setup"}|401] browser=302
07:04:59 g-immich api=[{"error":"this app is waiting for its first setup"}|401] browser=302
07:04:59 g-abs api=[{"error":"this app is waiting for its first setup"}|401] browser=302
07:04:59 g-kuma api=[{"error":"this app is waiting for its first setup"}|401] browser=302
07:05:00 g-n8n api=[{"error":"this app is waiting for its first setup"}|401] browser=302
07:05:00 g-immich api=[{"error":"this app is waiting for its first setup"}|401] browser=302
07:05:00 g-abs api=[{"error":"this app is waiting for its first setup"}|401] browser=302
07:05:00 g-kuma api=[{"error":"this app is waiting for its first setup"}|401] browser=302
07:05:01 g-n8n api=[{"error":"this app is waiting for its first setup"}|401] browser=302
07:05:01 g-immich api=[{"error":"this app is waiting for its first setup"}|401] browser=302
07:05:01 g-abs api=[{"error":"this app is waiting for its first setup"}|401] browser=302
07:05:01 g-kuma api=[{"error":"this app is waiting for its first setup"}|401] browser=302
07:05:02 g-n8n api=[{"error":"this app is waiting for its first setup"}|401] browser=302
07:05:02 g-immich api=[{"error":"this app is waiting for its first setup"}|401] browser=302
07:05:02 g-abs api=[{"error":"this app is waiting for its first setup"}|401] browser=302
07:05:02 g-kuma api=[{"error":"this app is waiting for its first setup"}|401] browser=302
07:05:03 g-n8n api=[{"error":"this app is waiting for its first setup"}|401] browser=302
07:05:03 g-immich api=[{"error":"this app is waiting for its first setup"}|401] browser=302
07:05:04 g-abs api=[{"error":"this app is waiting for its first setup"}|401] browser=302
07:05:04 g-kuma api=[{"error":"this app is waiting for its first setup"}|401] browser=302
07:05:05 g-n8n api=[{"error":"this app is waiting for its first setup"}|401] browser=302
07:05:05 g-immich api=[{"error":"this app is waiting for its first setup"}|401] browser=302
07:05:05 g-abs api=[{"error":"this app is waiting for its first setup"}|401] browser=302
07:05:05 g-kuma api=[{"error":"this app is waiting for its first setup"}|401] browser=302
07:05:06 g-n8n api=[{"error":"this app is waiting for its first setup"}|401] browser=302
07:05:06 g-immich api=[{"error":"this app is waiting for its first setup"}|401] browser=302
07:05:06 g-abs api=[{"error":"this app is waiting for its first setup"}|401] browser=302
07:05:06 g-kuma api=[{"error":"this app is waiting for its first setup"}|401] browser=302
07:05:07 g-n8n api=[{"error":"this app is waiting for its first setup"}|401] browser=302
07:05:07 g-immich api=[{"error":"this app is waiting for its first setup"}|401] browser=302
07:05:07 g-abs api=[{"error":"this app is waiting for its first setup"}|401] browser=302
07:05:07 g-kuma api=[{"error":"this app is waiting for its first setup"}|401] browser=302
07:05:08 g-n8n api=[{"error":"this app is waiting for its first setup"}|401] browser=302
07:05:08 g-immich api=[{"error":"this app is waiting for its first setup"}|401] browser=302
07:05:08 g-abs api=[{"error":"this app is waiting for its first setup"}|401] browser=302
07:05:08 g-kuma api=[{"error":"this app is waiting for its first setup"}|401] browser=302
07:05:09 g-n8n api=[{"error":"this app is waiting for its first setup"}|401] browser=302
07:05:09 g-immich api=[{"error":"this app is waiting for its first setup"}|401] browser=302
07:05:10 g-abs api=[{"error":"this app is waiting for its first setup"}|401] browser=302
07:05:10 g-kuma api=[{"error":"this app is waiting for its first setup"}|401] browser=302
07:05:11 g-n8n api=[{"error":"this app is waiting for its first setup"}|401] browser=302
07:05:11 g-immich api=[{"error":"this app is waiting for its first setup"}|401] browser=302
07:05:11 g-abs api=[{"error":"this app is waiting for its first setup"}|401] browser=302
07:05:11 g-kuma api=[{"error":"this app is waiting for its first setup"}|401] browser=302
07:05:12 g-n8n api=[{"error":"this app is waiting for its first setup"}|401] browser=302
07:05:12 g-immich api=[{"error":"this app is waiting for its first setup"}|401] browser=302
07:05:12 g-abs api=[{"error":"this app is waiting for its first setup"}|401] browser=302
07:05:12 g-kuma api=[{"error":"this app is waiting for its first setup"}|401] browser=302
07:05:13 g-n8n api=[{"error":"this app is waiting for its first setup"}|401] browser=302
07:05:13 g-immich api=[{"error":"this app is waiting for its first setup"}|401] browser=302
07:05:13 g-abs api=[{"error":"this app is waiting for its first setup"}|401] browser=302
07:05:13 g-kuma api=[{"error":"this app is waiting for its first setup"}|401] browser=302
07:05:14 g-n8n api=[{"error":"this app is waiting for its first setup"}|401] browser=302
07:05:14 g-immich api=[{"error":"this app is waiting for its first setup"}|401] browser=302
07:05:14 g-abs api=[{"error":"this app is waiting for its first setup"}|401] browser=302
07:05:14 g-kuma api=[{"error":"this app is waiting for its first setup"}|401] browser=302
07:05:15 g-n8n api=[{"error":"this app is waiting for its first setup"}|401] browser=302
07:05:16 g-immich api=[{"error":"this app is waiting for its first setup"}|401] browser=302
07:05:16 g-abs api=[{"error":"this app is waiting for its first setup"}|401] browser=302
07:05:16 g-kuma api=[{"error":"this app is waiting for its first setup"}|401] browser=302
07:05:17 g-n8n api=[{"error":"this app is waiting for its first setup"}|401] browser=302
07:05:17 g-immich api=[{"error":"this app is waiting for its first setup"}|401] browser=302
07:05:17 g-abs api=[{"error":"this app is waiting for its first setup"}|401] browser=302
07:05:17 g-kuma api=[{"error":"this app is waiting for its first setup"}|401] browser=302
07:05:18 g-n8n api=[{"error":"this app is waiting for its first setup"}|401] browser=302
07:05:18 g-immich api=[{"error":"this app is waiting for its first setup"}|401] browser=302
07:05:18 g-abs api=[{"error":"this app is waiting for its first setup"}|401] browser=302
07:05:18 g-kuma api=[{"error":"this app is waiting for its first setup"}|401] browser=302
07:05:19 g-n8n api=[{"error":"this app is waiting for its first setup"}|401] browser=302
07:05:19 g-immich api=[{"error":"this app is waiting for its first setup"}|401] browser=302
07:05:19 g-abs api=[{"error":"this app is waiting for its first setup"}|401] browser=302
07:05:19 g-kuma api=[{"error":"this app is waiting for its first setup"}|401] browser=302
07:05:20 g-n8n api=[{"error":"this app is waiting for its first setup"}|401] browser=302
07:05:20 g-immich api=[{"error":"this app is waiting for its first setup"}|401] browser=302
07:05:20 g-abs api=[{"error":"this app is waiting for its first setup"}|401] browser=302
07:05:20 g-kuma api=[{"error":"this app is waiting for its first setup"}|401] browser=302
07:05:21 g-n8n api=[{"error":"this app is waiting for its first setup"}|401] browser=302
07:05:21 g-immich api=[{"error":"this app is waiting for its first setup"}|401] browser=302
07:05:22 g-abs api=[{"error":"this app is waiting for its first setup"}|401] browser=302
07:05:22 g-kuma api=[{"error":"this app is waiting for its first setup"}|401] browser=302
07:05:23 g-n8n api=[{"error":"this app is waiting for its first setup"}|401] browser=302
07:05:23 g-immich api=[{"error":"this app is waiting for its first setup"}|401] browser=302
07:05:23 g-abs api=[{"error":"this app is waiting for its first setup"}|401] browser=302
07:05:23 g-kuma api=[{"error":"this app is waiting for its first setup"}|401] browser=302
07:05:24 g-n8n api=[{"error":"this app is waiting for its first setup"}|401] browser=302
07:05:24 g-immich api=[{"error":"this app is waiting for its first setup"}|401] browser=302
07:05:24 g-abs api=[{"error":"this app is waiting for its first setup"}|401] browser=302
07:05:24 g-kuma api=[{"error":"this app is waiting for its first setup"}|401] browser=302
07:05:25 g-n8n api=[{"error":"this app is waiting for its first setup"}|401] browser=302
07:05:25 g-immich api=[{"error":"this app is waiting for its first setup"}|401] browser=302
07:05:25 g-abs api=[{"error":"this app is waiting for its first setup"}|401] browser=302
07:05:25 g-kuma api=[{"error":"this app is waiting for its first setup"}|401] browser=302
07:05:26 g-n8n api=[{"error":"this app is waiting for its first setup"}|401] browser=302
07:05:26 g-immich api=[{"error":"this app is waiting for its first setup"}|401] browser=302
07:05:26 g-abs api=[{"error":"this app is waiting for its first setup"}|401] browser=302
07:05:26 g-kuma api=[{"error":"this app is waiting for its first setup"}|401] browser=302
07:05:27 g-n8n api=[{"error":"this app is waiting for its first setup"}|401] browser=302
07:05:28 g-immich api=[{"error":"this app is waiting for its first setup"}|401] browser=302
07:05:28 g-abs api=[{"error":"this app is waiting for its first setup"}|401] browser=302
07:05:28 g-kuma api=[{"error":"this app is waiting for its first setup"}|401] browser=302
07:05:29 g-n8n api=[{"error":"this app is waiting for its first setup"}|401] browser=302
07:05:29 g-immich api=[{"error":"this app is waiting for its first setup"}|401] browser=302
07:05:29 g-abs api=[{"error":"this app is waiting for its first setup"}|401] browser=302
07:05:29 g-kuma api=[{"error":"this app is waiting for its first setup"}|401] browser=302
07:05:30 g-n8n api=[{"error":"this app is waiting for its first setup"}|401] browser=302
07:05:30 g-immich api=[{"error":"this app is waiting for its first setup"}|401] browser=302
07:05:30 g-abs api=[{"error":"this app is waiting for its first setup"}|401] browser=302
07:05:30 g-kuma api=[{"error":"this app is waiting for its first setup"}|401] browser=302
07:05:31 g-n8n api=[{"error":"this app is waiting for its first setup"}|401] browser=302
07:05:31 g-immich api=[{"error":"this app is waiting for its first setup"}|401] browser=302
07:05:31 g-abs api=[{"error":"this app is waiting for its first setup"}|401] browser=302
07:05:31 g-kuma api=[{"error":"this app is waiting for its first setup"}|401] browser=302
07:05:32 g-n8n api=[{"error":"this app is waiting for its first setup"}|401] browser=302
07:05:32 g-immich api=[{"error":"this app is waiting for its first setup"}|401] browser=302
07:05:32 g-abs api=[{"error":"this app is waiting for its first setup"}|401] browser=302
07:05:32 g-kuma api=[{"error":"this app is waiting for its first setup"}|401] browser=302
07:05:33 g-n8n api=[{"error":"this app is waiting for its first setup"}|401] browser=302
07:05:34 g-immich api=[{"error":"this app is waiting for its first setup"}|401] browser=302
07:05:34 g-abs api=[{"error":"this app is waiting for its first setup"}|401] browser=302
07:05:34 g-kuma api=[{"error":"this app is waiting for its first setup"}|401] browser=302
07:05:35 g-n8n api=[{"error":"this app is waiting for its first setup"}|401] browser=302
07:05:35 g-immich api=[{"error":"this app is waiting for its first setup"}|401] browser=302
07:05:35 g-abs api=[{"error":"this app is waiting for its first setup"}|401] browser=302
07:05:35 g-kuma api=[{"error":"this app is waiting for its first setup"}|401] browser=302
07:05:36 g-n8n api=[{"error":"this app is waiting for its first setup"}|401] browser=302
07:05:36 g-immich api=[{"error":"this app is waiting for its first setup"}|401] browser=302
07:05:36 g-abs api=[{"error":"this app is waiting for its first setup"}|401] browser=302
07:05:36 g-kuma api=[{"error":"this app is waiting for its first setup"}|401] browser=302
07:05:37 g-n8n api=[{"error":"this app is waiting for its first setup"}|401] browser=302
07:05:37 g-immich api=[{"error":"this app is waiting for its first setup"}|401] browser=302
07:05:37 g-abs api=[{"error":"this app is waiting for its first setup"}|401] browser=302
07:05:37 g-kuma api=[{"error":"this app is waiting for its first setup"}|401] browser=302
07:05:38 g-n8n api=[{"error":"this app is waiting for its first setup"}|401] browser=302
07:05:38 g-immich api=[{"error":"this app is waiting for its first setup"}|401] browser=302
07:05:38 g-abs api=[{"error":"this app is waiting for its first setup"}|401] browser=302
07:05:38 g-kuma api=[{"error":"this app is waiting for its first setup"}|401] browser=302
07:05:39 g-n8n api=[{"error":"this app is waiting for its first setup"}|401] browser=302
07:05:40 g-immich api=[{"error":"this app is waiting for its first setup"}|401] browser=302
07:05:40 g-abs api=[{"error":"this app is waiting for its first setup"}|401] browser=302
07:05:40 g-kuma api=[{"error":"this app is waiting for its first setup"}|401] browser=302
07:05:41 g-n8n api=[{"error":"this app is waiting for its first setup"}|401] browser=302
07:05:41 g-immich api=[{"error":"this app is waiting for its first setup"}|401] browser=302
07:05:41 g-abs api=[{"error":"this app is waiting for its first setup"}|401] browser=302
07:05:41 g-kuma api=[{"error":"this app is waiting for its first setup"}|401] browser=302
07:05:42 g-n8n api=[{"error":"this app is waiting for its first setup"}|401] browser=302
07:05:42 g-immich api=[{"error":"this app is waiting for its first setup"}|401] browser=302
07:05:42 g-abs api=[{"error":"this app is waiting for its first setup"}|401] browser=302
07:05:42 g-kuma api=[{"error":"this app is waiting for its first setup"}|401] browser=302
07:05:43 g-n8n api=[{"error":"this app is waiting for its first setup"}|401] browser=302
07:05:43 g-immich api=[{"error":"this app is waiting for its first setup"}|401] browser=302
07:05:43 g-abs api=[{"error":"this app is waiting for its first setup"}|401] browser=302
07:05:43 g-kuma api=[{"error":"this app is waiting for its first setup"}|401] browser=302
07:05:44 g-n8n api=[{"error":"this app is waiting for its first setup"}|401] browser=302
07:05:44 g-immich api=[{"error":"this app is waiting for its first setup"}|401] browser=302
07:05:44 g-abs api=[{"error":"this app is waiting for its first setup"}|401] browser=302
07:05:44 g-kuma api=[{"error":"this app is waiting for its first setup"}|401] browser=302
07:05:46 g-n8n api=[{"error":"this app is waiting for its first setup"}|401] browser=302
07:05:46 g-immich api=[{"error":"this app is waiting for its first setup"}|401] browser=302
07:05:46 g-abs api=[{"error":"this app is waiting for its first setup"}|401] browser=302
07:05:46 g-kuma api=[{"error":"this app is waiting for its first setup"}|401] browser=302
07:05:47 g-n8n api=[{"error":"this app is waiting for its first setup"}|401] browser=302
07:05:47 g-immich api=[{"error":"this app is waiting for its first setup"}|401] browser=302
07:05:47 g-abs api=[{"error":"this app is waiting for its first setup"}|401] browser=302
07:05:47 g-kuma api=[{"error":"this app is waiting for its first setup"}|401] browser=302
07:05:48 g-n8n api=[{"error":"this app is waiting for its first setup"}|401] browser=302
07:05:48 g-immich api=[{"error":"this app is waiting for its first setup"}|401] browser=302
07:05:48 g-abs api=[{"error":"this app is waiting for its first setup"}|401] browser=302
07:05:48 g-kuma api=[{"error":"this app is waiting for its first setup"}|401] browser=302
07:05:49 g-n8n api=[{"error":"this app is waiting for its first setup"}|401] browser=302
07:05:49 g-immich api=[{"error":"this app is waiting for its first setup"}|401] browser=302
07:05:49 g-abs api=[{"error":"this app is waiting for its first setup"}|401] browser=302
07:05:49 g-kuma api=[{"error":"this app is waiting for its first setup"}|401] browser=302
07:05:50 g-n8n api=[{"error":"this app is waiting for its first setup"}|401] browser=302
07:05:50 g-immich api=[{"error":"this app is waiting for its first setup"}|401] browser=302
07:05:50 g-abs api=[{"error":"this app is waiting for its first setup"}|401] browser=302
07:05:50 g-kuma api=[{"error":"this app is waiting for its first setup"}|401] browser=302
07:05:51 g-n8n api=[{"error":"this app is waiting for its first setup"}|401] browser=302
07:05:52 g-immich api=[{"error":"this app is waiting for its first setup"}|401] browser=302
07:05:52 g-abs api=[{"error":"this app is waiting for its first setup"}|401] browser=302
07:05:52 g-kuma api=[{"error":"this app is waiting for its first setup"}|401] browser=302
07:05:53 g-n8n api=[{"error":"this app is waiting for its first setup"}|401] browser=302
07:05:53 g-immich api=[{"error":"this app is waiting for its first setup"}|401] browser=302
07:05:53 g-abs api=[{"error":"this app is waiting for its first setup"}|401] browser=302
07:05:53 g-kuma api=[{"error":"this app is waiting for its first setup"}|401] browser=302
07:05:54 g-n8n api=[{"error":"this app is waiting for its first setup"}|401] browser=302
07:05:54 g-immich api=[{"error":"this app is waiting for its first setup"}|401] browser=302
07:05:54 g-abs api=[{"error":"this app is waiting for its first setup"}|401] browser=302
07:05:54 g-kuma api=[{"error":"this app is waiting for its first setup"}|401] browser=302
07:05:55 g-n8n api=[{"error":"this app is waiting for its first setup"}|401] browser=302
07:05:55 g-immich api=[{"error":"this app is waiting for its first setup"}|401] browser=302
07:05:55 g-abs api=[{"error":"this app is waiting for its first setup"}|401] browser=302
07:05:55 g-kuma api=[{"error":"this app is waiting for its first setup"}|401] browser=302
07:05:56 g-n8n api=[{"error":"this app is waiting for its first setup"}|401] browser=302
07:05:56 g-immich api=[{"error":"this app is waiting for its first setup"}|401] browser=302
07:05:56 g-abs api=[{"error":"this app is waiting for its first setup"}|401] browser=302
07:05:56 g-kuma api=[{"error":"this app is waiting for its first setup"}|401] browser=302
07:05:57 g-n8n api=[{"error":"this app is waiting for its first setup"}|401] browser=302
07:05:58 g-immich api=[{"error":"this app is waiting for its first setup"}|401] browser=302
07:05:58 g-abs api=[{"error":"this app is waiting for its first setup"}|401] browser=302
07:05:58 g-kuma api=[{"error":"this app is waiting for its first setup"}|401] browser=302
07:05:59 g-n8n api=[{"error":"this app is waiting for its first setup"}|401] browser=302
07:05:59 g-immich api=[{"error":"this app is waiting for its first setup"}|401] browser=302
07:05:59 g-abs api=[{"error":"this app is waiting for its first setup"}|401] browser=302
07:05:59 g-kuma api=[{"error":"this app is waiting for its first setup"}|401] browser=302
07:06:00 g-n8n api=[{"error":"this app is waiting for its first setup"}|401] browser=302
07:06:00 g-immich api=[{"error":"this app is waiting for its first setup"}|401] browser=302
07:06:00 g-abs api=[{"error":"this app is waiting for its first setup"}|401] browser=302
07:06:00 g-kuma api=[{"error":"this app is waiting for its first setup"}|401] browser=302
07:06:01 g-n8n api=[{"error":"this app is waiting for its first setup"}|401] browser=302
07:06:01 g-immich api=[{"error":"this app is waiting for its first setup"}|401] browser=302
07:06:01 g-abs api=[{"error":"this app is waiting for its first setup"}|401] browser=302
07:06:01 g-kuma api=[{"error":"this app is waiting for its first setup"}|401] browser=302
07:06:02 g-n8n api=[{"error":"this app is waiting for its first setup"}|401] browser=302
07:06:02 g-immich api=[{"message":"Cannot GET /api/x"}|404] browser=200
07:06:02 g-abs api=[{"error":"this app is waiting for its first setup"}|401] browser=302
07:06:02 g-kuma api=[{"error":"this app is waiting for its first setup"}|401] browser=302
07:06:03 g-n8n api=[{"error":"this app is waiting for its first setup"}|401] browser=302
07:06:04 g-immich api=[{"message":"Cannot GET /api/x"}|404] browser=200
07:06:04 g-abs api=[{"error":"this app is waiting for its first setup"}|401] browser=302
07:06:04 g-kuma api=[{"error":"this app is waiting for its first setup"}|401] browser=302
07:06:05 g-n8n api=[{"error":"this app is waiting for its first setup"}|401] browser=302
07:06:05 g-immich api=[{"message":"Cannot GET /api/x"}|404] browser=200
07:06:05 g-abs api=[{"error":"this app is waiting for its first setup"}|401] browser=302
07:06:05 g-kuma api=[{"error":"this app is waiting for its first setup"}|401] browser=302
07:06:06 g-n8n api=[{"error":"this app is waiting for its first setup"}|401] browser=302
07:06:06 g-immich api=[{"message":"Cannot GET /api/x"}|404] browser=200
07:06:06 g-abs api=[{"error":"this app is waiting for its first setup"}|401] browser=302
07:06:06 g-kuma api=[{"error":"this app is waiting for its first setup"}|401] browser=302
07:06:07 g-n8n api=[{"error":"this app is waiting for its first setup"}|401] browser=302
07:06:07 g-immich api=[{"message":"Cannot GET /api/x"}|404] browser=200
07:06:07 g-abs api=[{"error":"this app is waiting for its first setup"}|401] browser=302
07:06:07 g-kuma api=[{"error":"this app is waiting for its first setup"}|401] browser=302
07:06:08 g-n8n api=[{"error":"this app is waiting for its first setup"}|401] browser=302
07:06:08 g-immich api=[{"message":"Cannot GET /api/x"}|404] browser=200
07:06:08 g-abs api=[{"error":"this app is waiting for its first setup"}|401] browser=302
07:06:08 g-kuma api=[{"error":"this app is waiting for its first setup"}|401] browser=302
07:06:09 g-n8n api=[{"error":"this app is waiting for its first setup"}|401] browser=302
07:06:10 g-immich api=[{"message":"Cannot GET /api/x"}|404] browser=200
07:06:10 g-abs api=[{"error":"this app is waiting for its first setup"}|401] browser=302
07:06:10 g-kuma api=[{"error":"this app is waiting for its first setup"}|401] browser=302
07:06:11 g-n8n api=[{"error":"this app is waiting for its first setup"}|401] browser=302
07:06:11 g-immich api=[{"message":"Cannot GET /api/x"}|404] browser=200
07:06:11 g-abs api=[{"error":"this app is waiting for its first setup"}|401] browser=302
07:06:11 g-kuma api=[{"error":"this app is waiting for its first setup"}|401] browser=302
07:06:12 g-n8n api=[{"error":"this app is waiting for its first setup"}|401] browser=302
07:06:12 g-immich api=[{"message":"Cannot GET /api/x"}|404] browser=200
07:06:12 g-abs api=[{"error":"this app is waiting for its first setup"}|401] browser=302
07:06:12 g-kuma api=[{"error":"this app is waiting for its first setup"}|401] browser=302
07:06:13 g-n8n api=[{"error":"this app is waiting for its first setup"}|401] browser=302
07:06:13 g-immich api=[{"message":"Cannot GET /api/x"}|404] browser=200
07:06:13 g-abs api=[{"error":"this app is waiting for its first setup"}|401] browser=302
07:06:13 g-kuma api=[{"error":"this app is waiting for its first setup"}|401] browser=302
07:06:14 g-n8n api=[{"error":"this app is waiting for its first setup"}|401] browser=302
07:06:14 g-immich api=[{"message":"Cannot GET /api/x"}|404] browser=200
07:06:14 g-abs api=[{"error":"this app is waiting for its first setup"}|401] browser=302
07:06:14 g-kuma api=[{"error":"this app is waiting for its first setup"}|401] browser=302
07:06:16 g-n8n api=[{"error":"this app is waiting for its first setup"}|401] browser=302
07:06:16 g-immich api=[{"message":"Cannot GET /api/x"}|404] browser=200
07:06:16 g-abs api=[{"error":"this app is waiting for its first setup"}|401] browser=302
07:06:16 g-kuma api=[{"error":"this app is waiting for its first setup"}|401] browser=302
07:06:17 g-n8n api=[{"error":"this app is waiting for its first setup"}|401] browser=302
07:06:17 g-immich api=[{"message":"Cannot GET /api/x"}|404] browser=200
07:06:17 g-abs api=[{"error":"this app is waiting for its first setup"}|401] browser=302
07:06:17 g-kuma api=[{"error":"this app is waiting for its first setup"}|401] browser=302
07:06:18 g-n8n api=[{"error":"this app is waiting for its first setup"}|401] browser=302
07:06:18 g-immich api=[{"message":"Cannot GET /api/x"}|404] browser=200
07:06:18 g-abs api=[{"error":"this app is waiting for its first setup"}|401] browser=302
07:06:18 g-kuma api=[{"error":"this app is waiting for its first setup"}|401] browser=302
07:06:19 g-n8n api=[{"error":"this app is waiting for its first setup"}|401] browser=302
07:06:19 g-immich api=[{"message":"Cannot GET /api/x"}|404] browser=200
07:06:19 g-abs api=[{"error":"this app is waiting for its first setup"}|401] browser=302
07:06:19 g-kuma api=[{"error":"this app is waiting for its first setup"}|401] browser=302
07:06:20 g-n8n api=[{"error":"this app is waiting for its first setup"}|401] browser=302
07:06:20 g-immich api=[{"message":"Cannot GET /api/x"}|404] browser=200
07:06:20 g-abs api=[{"error":"this app is waiting for its first setup"}|401] browser=302
07:06:20 g-kuma api=[{"error":"this app is waiting for its first setup"}|401] browser=302
07:06:22 g-n8n api=[{"error":"this app is waiting for its first setup"}|401] browser=302
07:06:22 g-immich api=[{"message":"Cannot GET /api/x"}|404] browser=200
07:06:22 g-abs api=[{"error":"this app is waiting for its first setup"}|401] browser=302
07:06:22 g-kuma api=[{"error":"this app is waiting for its first setup"}|401] browser=302
07:06:23 g-n8n api=[<!DOCTYPE html><html lang="en"> <head> <meta charset="utf-8" /> <meta http-equiv="X-UA-C] browser=200
07:06:23 g-immich api=[{"message":"Cannot GET /api/x"}|404] browser=200
07:06:23 g-abs api=[Unauthorized|401] browser=200
07:06:23 g-kuma api=[{"error":"this app is waiting for its first setup"}|401] browser=302
07:06:24 g-n8n api=[<!DOCTYPE html><html lang="en"> <head> <meta charset="utf-8" /> <meta http-equiv="X-UA-C] browser=200
07:06:24 g-immich api=[{"message":"Cannot GET /api/x"}|404] browser=200
07:06:24 g-abs api=[Unauthorized|401] browser=200
07:06:24 g-kuma api=[{"error":"this app is waiting for its first setup"}|401] browser=302
07:06:25 g-n8n api=[<!DOCTYPE html><html lang="en"> <head> <meta charset="utf-8" /> <meta http-equiv="X-UA-C] browser=200
07:06:25 g-immich api=[{"message":"Cannot GET /api/x"}|404] browser=200
07:06:25 g-abs api=[Unauthorized|401] browser=200
07:06:25 g-kuma api=[{"error":"this app is waiting for its first setup"}|401] browser=302
07:06:26 g-n8n api=[<!DOCTYPE html><html lang="en"> <head> <meta charset="utf-8" /> <meta http-equiv="X-UA-C] browser=200
07:06:26 g-immich api=[{"message":"Cannot GET /api/x"}|404] browser=200
07:06:27 g-abs api=[Unauthorized|401] browser=200
07:06:27 g-kuma api=[{"error":"this app is waiting for its first setup"}|401] browser=302
07:06:28 g-n8n api=[<!DOCTYPE html><html lang="en"> <head> <meta charset="utf-8" /> <meta http-equiv="X-UA-C] browser=200
07:06:28 g-immich api=[{"message":"Cannot GET /api/x"}|404] browser=200
07:06:28 g-abs api=[Unauthorized|401] browser=200
07:06:28 g-kuma api=[{"error":"this app is waiting for its first setup"}|401] browser=302
07:06:29 g-n8n api=[<!DOCTYPE html><html lang="en"> <head> <meta charset="utf-8" /> <meta http-equiv="X-UA-C] browser=200
07:06:29 g-immich api=[{"message":"Cannot GET /api/x"}|404] browser=200
07:06:29 g-abs api=[Unauthorized|401] browser=200
07:06:29 g-kuma api=[{"error":"this app is waiting for its first setup"}|401] browser=302
07:06:30 g-n8n api=[<!DOCTYPE html><html lang="en"> <head> <meta charset="utf-8" /> <meta http-equiv="X-UA-C] browser=200
07:06:30 g-immich api=[{"message":"Cannot GET /api/x"}|404] browser=200
07:06:30 g-abs api=[Unauthorized|401] browser=200
07:06:30 g-kuma api=[{"error":"this app is waiting for its first setup"}|401] browser=302
07:06:31 g-n8n api=[<!DOCTYPE html><html lang="en"> <head> <meta charset="utf-8" /> <meta http-equiv="X-UA-C] browser=200
07:06:31 g-immich api=[{"message":"Cannot GET /api/x"}|404] browser=200
07:06:31 g-abs api=[Unauthorized|401] browser=200
07:06:31 g-kuma api=[{"error":"this app is waiting for its first setup"}|401] browser=302
07:06:32 g-n8n api=[<!DOCTYPE html><html lang="en"> <head> <meta charset="utf-8" /> <meta http-equiv="X-UA-C] browser=200
07:06:33 g-immich api=[{"message":"Cannot GET /api/x"}|404] browser=200
07:06:33 g-abs api=[Unauthorized|401] browser=200
07:06:33 g-kuma api=[{"error":"this app is waiting for its first setup"}|401] browser=302
07:06:34 g-n8n api=[<!DOCTYPE html><html lang="en"> <head> <meta charset="utf-8" /> <meta http-equiv="X-UA-C] browser=200
07:06:34 g-immich api=[{"message":"Cannot GET /api/x"}|404] browser=200
07:06:34 g-abs api=[Unauthorized|401] browser=200
07:06:34 g-kuma api=[{"error":"this app is waiting for its first setup"}|401] browser=302
07:06:35 g-n8n api=[<!DOCTYPE html><html lang="en"> <head> <meta charset="utf-8" /> <meta http-equiv="X-UA-C] browser=200
07:06:35 g-immich api=[{"message":"Cannot GET /api/x"}|404] browser=200
07:06:35 g-abs api=[Unauthorized|401] browser=200
07:06:35 g-kuma api=[{"error":"this app is waiting for its first setup"}|401] browser=302
07:06:36 g-n8n api=[<!DOCTYPE html><html lang="en"> <head> <meta charset="utf-8" /> <meta http-equiv="X-UA-C] browser=200
07:06:36 g-immich api=[{"message":"Cannot GET /api/x"}|404] browser=200
07:06:36 g-abs api=[Unauthorized|401] browser=200
07:06:36 g-kuma api=[{"error":"this app is waiting for its first setup"}|401] browser=302
07:06:37 g-n8n api=[<!DOCTYPE html><html lang="en"> <head> <meta charset="utf-8" /> <meta http-equiv="X-UA-C] browser=200
07:06:37 g-immich api=[{"message":"Cannot GET /api/x"}|404] browser=200
07:06:37 g-abs api=[Unauthorized|401] browser=200
07:06:37 g-kuma api=[{"error":"this app is waiting for its first setup"}|401] browser=302
07:06:39 g-n8n api=[<!DOCTYPE html><html lang="en"> <head> <meta charset="utf-8" /> <meta http-equiv="X-UA-C] browser=200
07:06:39 g-immich api=[{"message":"Cannot GET /api/x"}|404] browser=200
07:06:39 g-abs api=[Unauthorized|401] browser=200
07:06:39 g-kuma api=[{"error":"this app is waiting for its first setup"}|401] browser=302
07:06:40 g-n8n api=[<!DOCTYPE html><html lang="en"> <head> <meta charset="utf-8" /> <meta http-equiv="X-UA-C] browser=200
07:06:40 g-immich api=[{"message":"Cannot GET /api/x"}|404] browser=200
07:06:40 g-abs api=[Unauthorized|401] browser=200
07:06:40 g-kuma api=[{"error":"this app is waiting for its first setup"}|401] browser=302
07:06:41 g-n8n api=[<!DOCTYPE html><html lang="en"> <head> <meta charset="utf-8" /> <meta http-equiv="X-UA-C] browser=200
07:06:41 g-immich api=[{"message":"Cannot GET /api/x"}|404] browser=200
07:06:41 g-abs api=[Unauthorized|401] browser=200
07:06:41 g-kuma api=[{"error":"this app is waiting for its first setup"}|401] browser=302
07:06:42 g-n8n api=[<!DOCTYPE html><html lang="en"> <head> <meta charset="utf-8" /> <meta http-equiv="X-UA-C] browser=200
07:06:42 g-immich api=[{"message":"Cannot GET /api/x"}|404] browser=200
07:06:42 g-abs api=[Unauthorized|401] browser=200
07:06:42 g-kuma api=[{"error":"this app is waiting for its first setup"}|401] browser=302
07:06:43 g-n8n api=[<!DOCTYPE html><html lang="en"> <head> <meta charset="utf-8" /> <meta http-equiv="X-UA-C] browser=200
07:06:43 g-immich api=[{"message":"Cannot GET /api/x"}|404] browser=200
07:06:43 g-abs api=[Unauthorized|401] browser=200
07:06:44 g-kuma api=[{"error":"this app is waiting for its first setup"}|401] browser=302
07:06:45 g-n8n api=[<!DOCTYPE html><html lang="en"> <head> <meta charset="utf-8" /> <meta http-equiv="X-UA-C] browser=200
07:06:45 g-immich api=[{"message":"Cannot GET /api/x"}|404] browser=200
07:06:45 g-abs api=[Unauthorized|401] browser=200
07:06:45 g-kuma api=[{"error":"this app is waiting for its first setup"}|401] browser=302
07:06:46 g-n8n api=[<!DOCTYPE html><html lang="en"> <head> <meta charset="utf-8" /> <meta http-equiv="X-UA-C] browser=200
07:06:46 g-immich api=[{"message":"Cannot GET /api/x"}|404] browser=200
07:06:46 g-abs api=[Unauthorized|401] browser=200
07:06:46 g-kuma api=[{"error":"this app is waiting for its first setup"}|401] browser=302
07:06:47 g-n8n api=[<!DOCTYPE html><html lang="en"> <head> <meta charset="utf-8" /> <meta http-equiv="X-UA-C] browser=200
07:06:47 g-immich api=[{"message":"Cannot GET /api/x"}|404] browser=200
07:06:47 g-abs api=[Unauthorized|401] browser=200
07:06:47 g-kuma api=[{"error":"this app is waiting for its first setup"}|401] browser=302
07:06:48 g-n8n api=[<!DOCTYPE html><html lang="en"> <head> <meta charset="utf-8" /> <meta http-equiv="X-UA-C] browser=200
07:06:48 g-immich api=[{"message":"Cannot GET /api/x"}|404] browser=200
07:06:48 g-abs api=[Unauthorized|401] browser=200
07:06:48 g-kuma api=[{"error":"this app is waiting for its first setup"}|401] browser=302
07:06:49 g-n8n api=[<!DOCTYPE html><html lang="en"> <head> <meta charset="utf-8" /> <meta http-equiv="X-UA-C] browser=200
07:06:50 g-immich api=[{"message":"Cannot GET /api/x"}|404] browser=200
07:06:50 g-abs api=[Unauthorized|401] browser=200
07:06:50 g-kuma api=[{"error":"this app is waiting for its first setup"}|401] browser=302
07:06:51 g-n8n api=[<!DOCTYPE html><html lang="en"> <head> <meta charset="utf-8" /> <meta http-equiv="X-UA-C] browser=200
07:06:51 g-immich api=[{"message":"Cannot GET /api/x"}|404] browser=200
07:06:51 g-abs api=[Unauthorized|401] browser=200
07:06:51 g-kuma api=[{"error":"this app is waiting for its first setup"}|401] browser=302
07:06:52 g-n8n api=[<!DOCTYPE html><html lang="en"> <head> <meta charset="utf-8" /> <meta http-equiv="X-UA-C] browser=200
07:06:52 g-immich api=[{"message":"Cannot GET /api/x"}|404] browser=200
07:06:52 g-abs api=[Unauthorized|401] browser=200
07:06:52 g-kuma api=[{"error":"this app is waiting for its first setup"}|401] browser=302
07:06:53 g-n8n api=[<!DOCTYPE html><html lang="en"> <head> <meta charset="utf-8" /> <meta http-equiv="X-UA-C] browser=200
07:06:53 g-immich api=[{"message":"Cannot GET /api/x"}|404] browser=200
07:06:53 g-abs api=[Unauthorized|401] browser=200
07:06:53 g-kuma api=[|500] browser=500
07:06:54 g-n8n api=[<!DOCTYPE html><html lang="en"> <head> <meta charset="utf-8" /> <meta http-equiv="X-UA-C] browser=200
07:06:54 g-immich api=[{"message":"Cannot GET /api/x"}|404] browser=200
07:06:55 g-abs api=[Unauthorized|401] browser=200
07:06:55 g-kuma api=[{"error":"this app is waiting for its first setup"}|401] browser=302
07:06:56 g-n8n api=[<!DOCTYPE html><html lang="en"> <head> <meta charset="utf-8" /> <meta http-equiv="X-UA-C] browser=200
07:06:56 g-immich api=[{"message":"Cannot GET /api/x"}|404] browser=200
07:06:56 g-abs api=[Unauthorized|401] browser=200
07:06:56 g-kuma api=[{"error":"this app is waiting for its first setup"}|401] browser=302
07:06:57 g-n8n api=[<!DOCTYPE html><html lang="en"> <head> <meta charset="utf-8" /> <meta http-equiv="X-UA-C] browser=200
07:06:57 g-immich api=[{"message":"Cannot GET /api/x"}|404] browser=200
07:06:57 g-abs api=[Unauthorized|401] browser=200
07:06:57 g-kuma api=[{"error":"this app is waiting for its first setup"}|401] browser=302
07:06:58 g-n8n api=[<!DOCTYPE html><html lang="en"> <head> <meta charset="utf-8" /> <meta http-equiv="X-UA-C] browser=200
07:06:58 g-immich api=[{"message":"Cannot GET /api/x"}|404] browser=200
07:06:58 g-abs api=[Unauthorized|401] browser=200
07:06:58 g-kuma api=[{"error":"this app is waiting for its first setup"}|401] browser=302
07:06:59 g-n8n api=[<!DOCTYPE html><html lang="en"> <head> <meta charset="utf-8" /> <meta http-equiv="X-UA-C] browser=200
07:06:59 g-immich api=[{"message":"Cannot GET /api/x"}|404] browser=200
07:06:59 g-abs api=[Unauthorized|401] browser=200
07:06:59 g-kuma api=[{"error":"this app is waiting for its first setup"}|401] browser=302
07:07:00 g-n8n api=[<!DOCTYPE html><html lang="en"> <head> <meta charset="utf-8" /> <meta http-equiv="X-UA-C] browser=200
07:07:01 g-immich api=[{"message":"Cannot GET /api/x"}|404] browser=200
07:07:01 g-abs api=[Unauthorized|401] browser=200
07:07:01 g-kuma api=[{"error":"this app is waiting for its first setup"}|401] browser=302
07:07:02 g-n8n api=[<!DOCTYPE html><html lang="en"> <head> <meta charset="utf-8" /> <meta http-equiv="X-UA-C] browser=200
07:07:02 g-immich api=[{"message":"Cannot GET /api/x"}|404] browser=200
07:07:02 g-abs api=[Unauthorized|401] browser=200
07:07:02 g-kuma api=[{"error":"this app is waiting for its first setup"}|401] browser=302
07:07:03 g-n8n api=[<!DOCTYPE html><html lang="en"> <head> <meta charset="utf-8" /> <meta http-equiv="X-UA-C] browser=200
07:07:03 g-immich api=[{"message":"Cannot GET /api/x"}|404] browser=200
07:07:03 g-abs api=[Unauthorized|401] browser=200
07:07:03 g-kuma api=[{"error":"this app is waiting for its first setup"}|401] browser=302
07:07:04 g-n8n api=[<!DOCTYPE html><html lang="en"> <head> <meta charset="utf-8" /> <meta http-equiv="X-UA-C] browser=200
07:07:04 g-immich api=[{"message":"Cannot GET /api/x"}|404] browser=200
07:07:04 g-abs api=[Unauthorized|401] browser=200
07:07:04 g-kuma api=[{"error":"this app is waiting for its first setup"}|401] browser=302
07:07:05 g-n8n api=[<!DOCTYPE html><html lang="en"> <head> <meta charset="utf-8" /> <meta http-equiv="X-UA-C] browser=200
07:07:05 g-immich api=[{"message":"Cannot GET /api/x"}|404] browser=200
07:07:05 g-abs api=[Unauthorized|401] browser=200
07:07:05 g-kuma api=[{"error":"this app is waiting for its first setup"}|401] browser=302
07:07:07 g-n8n api=[<!DOCTYPE html><html lang="en"> <head> <meta charset="utf-8" /> <meta http-equiv="X-UA-C] browser=200
07:07:07 g-immich api=[{"message":"Cannot GET /api/x"}|404] browser=200
07:07:07 g-abs api=[Unauthorized|401] browser=200
07:07:07 g-kuma api=[{"error":"this app is waiting for its first setup"}|401] browser=302
07:07:08 g-n8n api=[<!DOCTYPE html><html lang="en"> <head> <meta charset="utf-8" /> <meta http-equiv="X-UA-C] browser=200
07:07:08 g-immich api=[{"message":"Cannot GET /api/x"}|404] browser=200
07:07:08 g-abs api=[Unauthorized|401] browser=200
07:07:08 g-kuma api=[{"error":"this app is waiting for its first setup"}|401] browser=302
07:07:09 g-n8n api=[<!DOCTYPE html><html lang="en"> <head> <meta charset="utf-8" /> <meta http-equiv="X-UA-C] browser=200
07:07:09 g-immich api=[{"message":"Cannot GET /api/x"}|404] browser=200
07:07:09 g-abs api=[Unauthorized|401] browser=200
07:07:09 g-kuma api=[<!DOCTYPE html><html lang="en"><head> <meta charset="UTF-8" /> <meta name="viewport"] browser=302
07:07:10 g-n8n api=[<!DOCTYPE html><html lang="en"> <head> <meta charset="utf-8" /> <meta http-equiv="X-UA-C] browser=200
07:07:10 g-immich api=[{"message":"Cannot GET /api/x"}|404] browser=200
07:07:10 g-abs api=[Unauthorized|401] browser=200
07:07:10 g-kuma api=[<!DOCTYPE html><html lang="en"><head> <meta charset="UTF-8" /> <meta name="viewport"] browser=302
07:07:11 g-n8n api=[<!DOCTYPE html><html lang="en"> <head> <meta charset="utf-8" /> <meta http-equiv="X-UA-C] browser=200
07:07:11 g-immich api=[{"message":"Cannot GET /api/x"}|404] browser=200
07:07:11 g-abs api=[Unauthorized|401] browser=200
07:07:12 g-kuma api=[<!DOCTYPE html><html lang="en"><head> <meta charset="UTF-8" /> <meta name="viewport"] browser=302
07:07:13 g-n8n api=[<!DOCTYPE html><html lang="en"> <head> <meta charset="utf-8" /> <meta http-equiv="X-UA-C] browser=200
07:07:13 g-immich api=[{"message":"Cannot GET /api/x"}|404] browser=200
07:07:13 g-abs api=[Unauthorized|401] browser=200
07:07:13 g-kuma api=[<!DOCTYPE html><html lang="en"><head> <meta charset="UTF-8" /> <meta name="viewport"] browser=302
07:07:14 g-n8n api=[<!DOCTYPE html><html lang="en"> <head> <meta charset="utf-8" /> <meta http-equiv="X-UA-C] browser=200
07:07:14 g-immich api=[{"message":"Cannot GET /api/x"}|404] browser=200
07:07:14 g-abs api=[Unauthorized|401] browser=200
07:07:14 g-kuma api=[<!DOCTYPE html><html lang="en"><head> <meta charset="UTF-8" /> <meta name="viewport"] browser=302
07:07:15 g-n8n api=[<!DOCTYPE html><html lang="en"> <head> <meta charset="utf-8" /> <meta http-equiv="X-UA-C] browser=200
07:07:15 g-immich api=[{"message":"Cannot GET /api/x"}|404] browser=200
07:07:15 g-abs api=[Unauthorized|401] browser=200
07:07:15 g-kuma api=[<!DOCTYPE html><html lang="en"><head> <meta charset="UTF-8" /> <meta name="viewport"] browser=302
07:07:16 g-n8n api=[<!DOCTYPE html><html lang="en"> <head> <meta charset="utf-8" /> <meta http-equiv="X-UA-C] browser=200
07:07:16 g-immich api=[{"message":"Cannot GET /api/x"}|404] browser=200
07:07:16 g-abs api=[Unauthorized|401] browser=200
07:07:16 g-kuma api=[<!DOCTYPE html><html lang="en"><head> <meta charset="UTF-8" /> <meta name="viewport"] browser=302
07:07:17 g-n8n api=[<!DOCTYPE html><html lang="en"> <head> <meta charset="utf-8" /> <meta http-equiv="X-UA-C] browser=200
07:07:18 g-immich api=[{"message":"Cannot GET /api/x"}|404] browser=200
07:07:18 g-abs api=[Unauthorized|401] browser=200
07:07:18 g-kuma api=[<!DOCTYPE html><html lang="en"><head> <meta charset="UTF-8" /> <meta name="viewport"] browser=302
07:07:19 g-n8n api=[<!DOCTYPE html><html lang="en"> <head> <meta charset="utf-8" /> <meta http-equiv="X-UA-C] browser=200
07:07:19 g-immich api=[{"message":"Cannot GET /api/x"}|404] browser=200
07:07:19 g-abs api=[Unauthorized|401] browser=200
07:07:19 g-kuma api=[<!DOCTYPE html><html lang="en"><head> <meta charset="UTF-8" /> <meta name="viewport"] browser=302
07:07:20 g-n8n api=[<!DOCTYPE html><html lang="en"> <head> <meta charset="utf-8" /> <meta http-equiv="X-UA-C] browser=200
07:07:20 g-immich api=[{"message":"Cannot GET /api/x"}|404] browser=200
07:07:20 g-abs api=[Unauthorized|401] browser=200
07:07:20 g-kuma api=[<!DOCTYPE html><html lang="en"><head> <meta charset="UTF-8" /> <meta name="viewport"] browser=302
07:07:21 g-n8n api=[<!DOCTYPE html><html lang="en"> <head> <meta charset="utf-8" /> <meta http-equiv="X-UA-C] browser=200
07:07:21 g-immich api=[{"message":"Cannot GET /api/x"}|404] browser=200
07:07:21 g-abs api=[Unauthorized|401] browser=200
07:07:21 g-kuma api=[<!DOCTYPE html><html lang="en"><head> <meta charset="UTF-8" /> <meta name="viewport"] browser=302
07:07:22 g-n8n api=[<!DOCTYPE html><html lang="en"> <head> <meta charset="utf-8" /> <meta http-equiv="X-UA-C] browser=200
07:07:22 g-immich api=[{"message":"Cannot GET /api/x"}|404] browser=200
07:07:22 g-abs api=[Unauthorized|401] browser=200
07:07:22 g-kuma api=[<!DOCTYPE html><html lang="en"><head> <meta charset="UTF-8" /> <meta name="viewport"] browser=302
07:07:23 g-n8n api=[<!DOCTYPE html><html lang="en"> <head> <meta charset="utf-8" /> <meta http-equiv="X-UA-C] browser=200
07:07:24 g-immich api=[{"message":"Cannot GET /api/x"}|404] browser=200
07:07:24 g-abs api=[Unauthorized|401] browser=200
07:07:24 g-kuma api=[<!DOCTYPE html><html lang="en"><head> <meta charset="UTF-8" /> <meta name="viewport"] browser=302
07:07:25 g-n8n api=[<!DOCTYPE html><html lang="en"> <head> <meta charset="utf-8" /> <meta http-equiv="X-UA-C] browser=200
07:07:25 g-immich api=[{"message":"Cannot GET /api/x"}|404] browser=200
07:07:25 g-abs api=[Unauthorized|401] browser=200
07:07:25 g-kuma api=[<!DOCTYPE html><html lang="en"><head> <meta charset="UTF-8" /> <meta name="viewport"] browser=302
07:07:26 g-n8n api=[<!DOCTYPE html><html lang="en"> <head> <meta charset="utf-8" /> <meta http-equiv="X-UA-C] browser=200
07:07:26 g-immich api=[{"message":"Cannot GET /api/x"}|404] browser=200
07:07:26 g-abs api=[Unauthorized|401] browser=200
07:07:26 g-kuma api=[<!DOCTYPE html><html lang="en"><head> <meta charset="UTF-8" /> <meta name="viewport"] browser=302
07:07:27 g-n8n api=[<!DOCTYPE html><html lang="en"> <head> <meta charset="utf-8" /> <meta http-equiv="X-UA-C] browser=200
07:07:27 g-immich api=[{"message":"Cannot GET /api/x"}|404] browser=200
07:07:27 g-abs api=[Unauthorized|401] browser=200
07:07:27 g-kuma api=[<!DOCTYPE html><html lang="en"><head> <meta charset="UTF-8" /> <meta name="viewport"] browser=302
07:07:28 g-n8n api=[<!DOCTYPE html><html lang="en"> <head> <meta charset="utf-8" /> <meta http-equiv="X-UA-C] browser=200
07:07:28 g-immich api=[{"message":"Cannot GET /api/x"}|404] browser=200
07:07:28 g-abs api=[Unauthorized|401] browser=200
07:07:29 g-kuma api=[<!DOCTYPE html><html lang="en"><head> <meta charset="UTF-8" /> <meta name="viewport"] browser=302
07:07:30 g-n8n api=[<!DOCTYPE html><html lang="en"> <head> <meta charset="utf-8" /> <meta http-equiv="X-UA-C] browser=200
07:07:30 g-immich api=[{"message":"Cannot GET /api/x"}|404] browser=200
07:07:30 g-abs api=[Unauthorized|401] browser=200
07:07:30 g-kuma api=[<!DOCTYPE html><html lang="en"><head> <meta charset="UTF-8" /> <meta name="viewport"] browser=302
07:07:31 g-n8n api=[<!DOCTYPE html><html lang="en"> <head> <meta charset="utf-8" /> <meta http-equiv="X-UA-C] browser=200
07:07:31 g-immich api=[{"message":"Cannot GET /api/x"}|404] browser=200
07:07:31 g-abs api=[Unauthorized|401] browser=200
07:07:31 g-kuma api=[<!DOCTYPE html><html lang="en"><head> <meta charset="UTF-8" /> <meta name="viewport"] browser=302
07:07:32 g-n8n api=[<!DOCTYPE html><html lang="en"> <head> <meta charset="utf-8" /> <meta http-equiv="X-UA-C] browser=200
07:07:32 g-immich api=[{"message":"Cannot GET /api/x"}|404] browser=200
07:07:32 g-abs api=[Unauthorized|401] browser=200
07:07:32 g-kuma api=[<!DOCTYPE html><html lang="en"><head> <meta charset="UTF-8" /> <meta name="viewport"] browser=302
07:07:33 g-n8n api=[<!DOCTYPE html><html lang="en"> <head> <meta charset="utf-8" /> <meta http-equiv="X-UA-C] browser=200
07:07:33 g-immich api=[{"message":"Cannot GET /api/x"}|404] browser=200
07:07:33 g-abs api=[Unauthorized|401] browser=200
07:07:33 g-kuma api=[<!DOCTYPE html><html lang="en"><head> <meta charset="UTF-8" /> <meta name="viewport"] browser=302
07:07:34 g-n8n api=[<!DOCTYPE html><html lang="en"> <head> <meta charset="utf-8" /> <meta http-equiv="X-UA-C] browser=200
07:07:34 g-immich api=[{"message":"Cannot GET /api/x"}|404] browser=200
07:07:35 g-abs api=[Unauthorized|401] browser=200
07:07:35 g-kuma api=[<!DOCTYPE html><html lang="en"><head> <meta charset="UTF-8" /> <meta name="viewport"] browser=302
07:07:36 g-n8n api=[<!DOCTYPE html><html lang="en"> <head> <meta charset="utf-8" /> <meta http-equiv="X-UA-C] browser=200
07:07:36 g-immich api=[{"message":"Cannot GET /api/x"}|404] browser=200
07:07:36 g-abs api=[Unauthorized|401] browser=200
07:07:36 g-kuma api=[<!DOCTYPE html><html lang="en"><head> <meta charset="UTF-8" /> <meta name="viewport"] browser=302
07:07:37 g-n8n api=[<!DOCTYPE html><html lang="en"> <head> <meta charset="utf-8" /> <meta http-equiv="X-UA-C] browser=200
07:07:37 g-immich api=[{"message":"Cannot GET /api/x"}|404] browser=200
07:07:37 g-abs api=[Unauthorized|401] browser=200
07:07:37 g-kuma api=[<!DOCTYPE html><html lang="en"><head> <meta charset="UTF-8" /> <meta name="viewport"] browser=302
07:07:38 g-n8n api=[<!DOCTYPE html><html lang="en"> <head> <meta charset="utf-8" /> <meta http-equiv="X-UA-C] browser=200
07:07:38 g-immich api=[{"message":"Cannot GET /api/x"}|404] browser=200
07:07:38 g-abs api=[Unauthorized|401] browser=200
07:07:38 g-kuma api=[<!DOCTYPE html><html lang="en"><head> <meta charset="UTF-8" /> <meta name="viewport"] browser=302
07:07:39 g-n8n api=[<!DOCTYPE html><html lang="en"> <head> <meta charset="utf-8" /> <meta http-equiv="X-UA-C] browser=200
07:07:39 g-immich api=[{"message":"Cannot GET /api/x"}|404] browser=200
07:07:39 g-abs api=[Unauthorized|401] browser=200
07:07:39 g-kuma api=[<!DOCTYPE html><html lang="en"><head> <meta charset="UTF-8" /> <meta name="viewport"] browser=302
07:07:41 g-n8n api=[<!DOCTYPE html><html lang="en"> <head> <meta charset="utf-8" /> <meta http-equiv="X-UA-C] browser=200
07:07:41 g-immich api=[{"message":"Cannot GET /api/x"}|404] browser=200
07:07:41 g-abs api=[Unauthorized|401] browser=200
07:07:41 g-kuma api=[<!DOCTYPE html><html lang="en"><head> <meta charset="UTF-8" /> <meta name="viewport"] browser=302
@@ -0,0 +1,4 @@
09:05:53 immich STRANGER: browser -> 200 302 g-immich.enkisfelhom.hu/ -> 200 felhom.enkisfelhom.hu/__gate/start gate-page(en,hu-ascii)=(False, True) | api POST -> 401 '{"error":"this app is waiting for its first setup"}'
09:05:53 n8n STRANGER: browser -> 200 302 g-n8n.enkisfelhom.hu/ -> 200 felhom.enkisfelhom.hu/__gate/start gate-page(en,hu-ascii)=(False, True) | api POST -> 401 '{"error":"this app is waiting for its first setup"}'
09:05:53 audiobookshelf STRANGER: browser -> 200 302 g-abs.enkisfelhom.hu/ -> 200 felhom.enkisfelhom.hu/__gate/start gate-page(en,hu-ascii)=(False, True) | api POST -> 401 '{"error":"this app is waiting for its first setup"}'
09:05:53 uptime-kuma STRANGER: browser -> 200 302 g-kuma.enkisfelhom.hu/ -> 200 felhom.enkisfelhom.hu/__gate/start gate-page(en,hu-ascii)=(False, True) | api POST -> 401 '{"error":"this app is waiting for its first setup"}'
@@ -0,0 +1,6 @@
09:05:54 dashboard login -> 200 302 felhom.enkisfelhom.hu/login -> 302 felhom.enkisfelhom.hu/ -> 200 felhom.enkisfelhom.hu/launcher
09:05:54 immich HOUSEHOLD browser -> 200 in 0.20s | 302 g-immich.enkisfelhom.hu/ -> 302 felhom.enkisfelhom.hu/__gate/start -> 302 g-immich.enkisfelhom.hu/__felhom_gate/cb -> 200 g-immich.enkisfelhom.hu/ | gate page: False | len 10699
09:05:54 n8n HOUSEHOLD browser -> 200 in 0.21s | 302 g-n8n.enkisfelhom.hu/ -> 302 felhom.enkisfelhom.hu/__gate/start -> 302 g-n8n.enkisfelhom.hu/__felhom_gate/cb -> 200 g-n8n.enkisfelhom.hu/ | gate page: False | len 52122
09:05:54 audiobookshelf HOUSEHOLD browser -> 200 in 0.20s | 302 g-abs.enkisfelhom.hu/ -> 302 felhom.enkisfelhom.hu/__gate/start -> 302 g-abs.enkisfelhom.hu/__felhom_gate/cb -> 200 g-abs.enkisfelhom.hu/ | gate page: False | len 4136
09:05:54 uptime-kuma HOUSEHOLD browser -> 200 in 0.26s | 302 g-kuma.enkisfelhom.hu/ -> 302 felhom.enkisfelhom.hu/__gate/start -> 302 g-kuma.enkisfelhom.hu/__felhom_gate/cb -> 302 g-kuma.enkisfelhom.hu/ -> 200 g-kuma.enkisfelhom.hu/dashboard | gate page: False | len 1200
09:05:54 cookies per host: {'felhom.enkisfelhom.hu': ['felhom_session'], 'g-immich.enkisfelhom.hu': ['felhom_gate'], 'g-n8n.enkisfelhom.hu': ['felhom_gate'], 'g-abs.enkisfelhom.hu': ['felhom_gate'], 'g-kuma.enkisfelhom.hu': ['felhom_gate']}
@@ -0,0 +1,3 @@
09:06:02 immich admin-sign-up through the gate -> 201
09:06:02 n8n owner setup through the gate -> 200
09:06:02 audiobookshelf POST /init through the gate -> 200 OK
@@ -0,0 +1,4 @@
09:06:06 immich gate OPEN after 0s: record {'state': 'open', 'since': '2026-09-29T07:03:40Z', 'hosts': ['g-immich.enkisfelhom.hu'], 'opened_at': '2026-09-29T07:06:02Z', 'opened_by': 'probe'} | file absent
09:06:30 audiobookshelf gate OPEN after 24s: record {'state': 'open', 'since': '2026-09-29T07:03:44Z', 'hosts': ['g-abs.enkisfelhom.hu'], 'opened_at': '2026-09-29T07:06:22Z', 'opened_by': 'probe'} | file absent
09:06:33 n8n gate OPEN after 27s: record {'state': 'open', 'since': '2026-09-29T07:03:41Z', 'hosts': ['g-n8n.enkisfelhom.hu'], 'opened_at': '2026-09-29T07:06:22Z', 'opened_by': 'probe'} | file absent
09:06:41 uptime-kuma (no probe): {'state': 'closed', 'since': '2026-09-29T07:03:45Z', 'hosts': ['g-kuma.enkisfelhom.hu']} | file present
@@ -0,0 +1,6 @@
controller: Up 6 seconds (healthy)
controller.yml
serverstransports.yml
setup-gate-uptime-kuma.yml
after restart, stranger -> 200 302 g-kuma.enkisfelhom.hu/ -> 200 felhom.enkisfelhom.hu/__gate/start | gate page (hu-ascii): True
after restart, household with its gate cookie only (no dashboard session) -> 200 200 g-kuma.enkisfelhom.hu/dashboard
@@ -0,0 +1,5 @@
09:07:08 uptime-kuma app page: gate card True | button True
09:07:08 household presses 'Done, I set it up' -> 200 {'data': {'opened': True}, 'error': '', 'ok': True}
09:07:11 uptime-kuma record {'state': 'open', 'since': '2026-09-29T07:03:45Z', 'hosts': ['g-kuma.enkisfelhom.hu'], 'opened_at': '2026-09-29T07:07:08Z', 'opened_by': 'household'} | file absent
09:07:11 second press -> 409 {'data': None, 'error': 'Ez az alkalmazás már nyitva van.', 'ok': False}
09:07:14 uptime-kuma, stranger after the press -> 200 302 g-kuma.enkisfelhom.hu/ -> 200 g-kuma.enkisfelhom.hu/dashboard | gate page: False
@@ -0,0 +1,9 @@
09:07:14 immich AFTER OPEN, stranger browser -> 200 200 g-immich.enkisfelhom.hu/ | gate page: False
09:07:14 n8n AFTER OPEN, stranger browser -> 200 200 g-n8n.enkisfelhom.hu/ | gate page: False
09:07:14 audiobookshelf AFTER OPEN, stranger browser -> 200 200 g-abs.enkisfelhom.hu/ | gate page: False
09:07:15 immich phone app: login -> 201 token
09:07:15 immich phone app GET /api/users/me -> 200
09:07:15 immich phone app GET /api/server/ping -> 200
09:07:15 immich phone app GET /api/server/version -> 200
09:07:15 n8n login, no browser gate cookie -> 200
09:07:15 audiobookshelf login (its phone app's route), no gate cookie -> 200
@@ -0,0 +1,4 @@
## 2026-09-29T07:19:29Z floor 0.279.0 -> 0.280.0 (min_agent 0.131.0 from the v0.280.0 header)
HTTP/1.1 303 See Other
Location: /configuration?flash=floor_set
name="min_controller_version" value="0.280.0"
@@ -0,0 +1,127 @@
# Part C live proof (9202, controller 0.280.0, drill catalog): the BUILT gate on four class-4 apps.
# Stranger vs household through the product's own route; the gate opens by probe (immich, n8n, audiobookshelf) or by
# the household's press (uptime-kuma); the app and immich's phone-app API after. Test passwords are generated here,
# kept in memory only, never printed.
import json, secrets, sys, time, urllib.parse
sys.path.insert(0, '/mnt/5_hdd/felhom.eu/git/felhom.eu/documentation/audits/pg-calcom-claper-2026-09-28/tools')
sys.path.insert(0, '.')
import walk as w
from browser import Browser, hopstr
D = "enkisfelhom.hu"
PW = open(w.SC + "/.ctlpw").read().strip()
APPS = {"immich": "g-immich", "n8n": "g-n8n", "audiobookshelf": "g-abs", "uptime-kuma": "g-kuma"}
phase = sys.argv[1]
def gatefile(app):
return w.guest(f"test -f /opt/docker/stacks/traefik/dynamic/setup-gate-{app}.yml && echo present || echo absent").strip()
def record(app):
return ((w.stack(app).get("app_config") or {}).get("setup_gate")) or None
def stranger(app):
s = Browser("stranger")
st, body, hops = s.req(f"https://{APPS[app]}.{D}/")
b = ("waiting for its first setup" in body, "Jelentkezz be a Felhom" in body)
st2, body2, _ = s.req(f"https://{APPS[app]}.{D}/api/x", "POST", body={"a": 1}, accept="application/json")
return f"browser -> {st} {hopstr(hops)} gate-page(en,hu-ascii)={b} | api POST -> {st2} {body2[:55]!r}"
w.login()
if phase == "deploy":
for app, sub in APPS.items():
meta = (w.ctl("GET", f"/api/stacks/{app}/deploy-fields")[1].get("data") or {}).get("metadata") or {}
w.say(app, "catalog meta: setup_gate =", meta.get("setup_gate"), "probe =", (meta.get("setup_done_probe") or {}).get("field"))
v = w.deploy_values(app, sub)
code, d = w.ctl("POST", f"/api/stacks/{app}/deploy", {"values": v, "kept_data": "fresh"})
w.say(app, "deploy ->", code, str(d)[:90])
for app in APPS:
for _ in range(120):
st = w.stack(app)
if st.get("deployed") and (st.get("app_config") or {}).get("pinned_images") and st.get("state") in ("running", "unhealthy", "degraded"):
break
time.sleep(5)
w.say(app, "state", st.get("state"), "| gate file", gatefile(app), "| record", record(app))
if phase == "stranger":
for app in APPS:
w.say(app, "STRANGER:", stranger(app))
if phase == "household":
hh = Browser("household")
st, b, hops = hh.login_dashboard(D, PW)
w.say("dashboard login ->", st, hopstr(hops))
for app in APPS:
t0 = time.time(); st, body, hops = hh.req(f"https://{APPS[app]}.{D}/"); dt = time.time() - t0
w.say(app, f"HOUSEHOLD browser -> {st} in {dt:.2f}s | {hopstr(hops)} | gate page: {'waiting for its first setup' in body} | len {len(body)}")
w.say("cookies per host:", {h: sorted(v) for h, v in hh.jar.items()})
json.dump(hh.jar, open("c_jar.json", "w"))
if phase == "setup":
hh = Browser("household"); hh.jar = json.load(open("c_jar.json"))
creds = {a: ("admin@spike.hu", "Hh" + secrets.token_hex(10) + "7") for a in APPS}
json.dump(creds, open("c_creds.json", "w"))
st, b, _ = hh.req(f"https://g-immich.{D}/api/auth/admin-sign-up", "POST", accept="application/json",
body={"email": creds["immich"][0], "password": creds["immich"][1], "name": "Spike"})
w.say("immich admin-sign-up through the gate ->", st)
st, b, _ = hh.req(f"https://g-n8n.{D}/rest/owner/setup", "POST", accept="application/json",
body={"email": creds["n8n"][0], "firstName": "S", "lastName": "O", "password": creds["n8n"][1]})
w.say("n8n owner setup through the gate ->", st)
st, b, _ = hh.req(f"https://g-abs.{D}/init", "POST", accept="application/json",
body={"newRoot": {"username": "root", "password": creds["audiobookshelf"][1]}})
w.say("audiobookshelf POST /init through the gate ->", st, b[:40])
if phase == "watch":
t0 = time.time()
left = {"immich", "n8n", "audiobookshelf"}
while left and time.time() - t0 < 180:
for app in sorted(left):
r = record(app)
if r and r.get("state") == "open":
w.say(app, f"gate OPEN after {time.time() - t0:.0f}s: record {r} | file {gatefile(app)}")
left.discard(app)
time.sleep(5)
for app in left:
w.say(app, "gate still closed after 180 s:", record(app), gatefile(app))
w.say("uptime-kuma (no probe):", record("uptime-kuma"), "| file", gatefile("uptime-kuma"))
if phase == "after":
creds = json.load(open("c_creds.json"))
for app in ("immich", "n8n", "audiobookshelf"):
s = Browser("stranger")
st, body, hops = s.req(f"https://{APPS[app]}.{D}/")
w.say(app, f"AFTER OPEN, stranger browser -> {st} {hopstr(hops)} | gate page: {'waiting for its first setup' in body}")
m = Browser("immich-mobile")
st, b, _ = m.req(f"https://g-immich.{D}/api/auth/login", "POST", accept="application/json",
body={"email": creds["immich"][0], "password": creds["immich"][1]})
tok = json.loads(b).get("accessToken") if st in (200, 201) else None
w.say("immich phone app: login ->", st, "token" if tok else b[:60])
if tok:
for p in ("/api/users/me", "/api/server/ping", "/api/server/version"):
st, b, _ = m.req(f"https://g-immich.{D}{p}", accept="application/json", headers={"Authorization": "Bearer " + tok})
w.say(" immich phone app GET", p, "->", st)
n = Browser("n8n-api")
st, b, _ = n.req(f"https://g-n8n.{D}/rest/login", "POST", accept="application/json",
body={"emailOrLdapLoginId": creds["n8n"][0], "password": creds["n8n"][1]})
w.say("n8n login, no browser gate cookie ->", st)
a = Browser("abs-app")
st, b, _ = a.req(f"https://g-abs.{D}/login", "POST", accept="application/json",
body={"username": "root", "password": creds["audiobookshelf"][1]})
w.say("audiobookshelf login (its phone app's route), no gate cookie ->", st)
if phase == "press":
code, d = w.ctl("GET", "/apps/uptime-kuma", raw=True)
page = d if isinstance(d, str) else ""
w.say("uptime-kuma app page: gate card", 'id="setup-gate-card"' in page, "| button", "/apps/uptime-kuma/setup-gate/open" in page)
code, d = w.ctl("POST", "/apps/uptime-kuma/setup-gate/open", {})
w.say("household presses 'Done, I set it up' ->", code, str(d)[:80])
w.say("uptime-kuma record", record("uptime-kuma"), "| file", gatefile("uptime-kuma"))
code, d = w.ctl("POST", "/apps/uptime-kuma/setup-gate/open", {})
w.say("second press ->", code, str(d)[:80])
s = Browser("stranger")
time.sleep(3)
st, body, hops = s.req(f"https://g-kuma.{D}/")
w.say("uptime-kuma, stranger after the press ->", st, hopstr(hops), "| gate page:", "waiting for its first setup" in body)
@@ -0,0 +1,9 @@
# RP1 — gate file written before the first start
# mutation in internal/stacks/deploy.go:
# - '\tif meta.SetupGate {\n\t\tg, err := m.prepareSetupGate('
# + '\tif false && meta.SetupGate { // RED-PROOF RP1\n\t\tg, err := m.prepareSetupGate('
# go test -run ^TestSetupGate_WrittenBeforeTheFirstStartAndRecordedClosed$ ./internal/stacks
# verdict: RED (assertion)
=== RUN TestSetupGate_WrittenBeforeTheFirstStartAndRecordedClosed
setup_gate_test.go:82: the gate file did not exist when the app was first started — a stranger could reach its first-setup screen
--- FAIL: TestSetupGate_WrittenBeforeTheFirstStartAndRecordedClosed (0.01s)
@@ -0,0 +1,9 @@
# RP10 — the pass is bound to its app host
# mutation in internal/web/setup_gate.go:
# - 'hmac.Equal([]byte(mac), []byte(s.gateMAC("cookie", host, exp)))'
# + 'hmac.Equal([]byte(mac), []byte(s.gateMAC("cookie", "gapp.example.hu", exp))) // RED-PROOF RP10'
# go test -run ^TestSetupGate_TheHouseholdPassesWithItsSession$ ./internal/web
# verdict: RED (assertion)
=== RUN TestSetupGate_TheHouseholdPassesWithItsSession
setup_gate_test.go:166: the pass for gapp opened gapp-db: 200
--- FAIL: TestSetupGate_TheHouseholdPassesWithItsSession (0.06s)
@@ -0,0 +1,9 @@
# RP11 — the key survives a restart
# mutation in internal/web/setup_gate.go:
# - '\t\t\tif err := os.WriteFile(p, []byte(hex.EncodeToString(k)), 0o600); err != nil {'
# + '\t\t\tif err := error(nil); err != nil { // RED-PROOF RP11'
# go test -run ^TestSetupGate_ARestartKeepsTheHouseholdsPass$ ./internal/web
# verdict: RED (assertion)
=== RUN TestSetupGate_ARestartKeepsTheHouseholdsPass
setup_gate_test.go:197: after a restart the household's pass was refused: 401
--- FAIL: TestSetupGate_ARestartKeepsTheHouseholdsPass (0.06s)
@@ -0,0 +1,9 @@
# RP12 — an opened gate lets everything through
# mutation in internal/web/setup_gate.go:
# - '\tif !closed {\n\t\t// Opened;'
# + '\tif false && !closed { // RED-PROOF RP12\n\t\t// Opened;'
# go test -run ^TestSetupGate_AnOpenedGateLetsEverythingThrough$ ./internal/web
# verdict: RED (assertion)
=== RUN TestSetupGate_AnOpenedGateLetsEverythingThrough
setup_gate_test.go:212: an opened gate: 401, want 200
--- FAIL: TestSetupGate_AnOpenedGateLetsEverythingThrough (0.07s)
@@ -0,0 +1,10 @@
# RP13 — R-710: an absent record on an old install warns
# mutation in internal/web/known_login.go:
# - '\treturn err != nil || knownLoginNow().Sub(at) > afterInstallWindow\n'
# + '\t_ = at\n\treturn err != nil && false // RED-PROOF RP13 (the 0.279.0 shape)\n'
# go test -run ^TestKnownLogin_InEffectOnlyUntilReplaced$ ./internal/web
# verdict: RED (assertion)
=== RUN TestKnownLogin_InEffectOnlyUntilReplaced
known_login_test.go:46: R-710: installed long before the after_install existed: in effect = false, want true
known_login_test.go:46: R-710: no install time on record: in effect = false, want true
--- FAIL: TestKnownLogin_InEffectOnlyUntilReplaced (0.01s)
@@ -0,0 +1,9 @@
# RP14 — R-709: the page never carries an installed password
# mutation in internal/web/templates/deploy.html:
# - '<input type="password" id="field-{{.EnvVar}}" class="form-control" value="" placeholder="••••••••••••" disabled>'
# + '<input type="password" id="field-{{.EnvVar}}" class="form-control" value="{{index $.DeployedFieldValues .EnvVar}}" placeholder="••••••••••••" disabled>'
# go test -run ^TestR709_AnInstalledAppsPasswordIsNotInThePage$ ./internal/web
# verdict: RED (assertion)
=== RUN TestR709_AnInstalledAppsPasswordIsNotInThePage
r709_password_field_test.go:49: R-709: the installed app's admin password is in the HTML of its settings page
--- FAIL: TestR709_AnInstalledAppsPasswordIsNotInThePage (0.07s)
@@ -0,0 +1,9 @@
# RP15 — R-709: the reveal endpoint serves an installed password
# mutation in internal/web/handlers.go:
# - '\tif !allowed && stack.Deployed {'
# + '\tif false && !allowed && stack.Deployed { // RED-PROOF RP15'
# go test -run ^TestR709_TheRevealEndpointServesAnInstalledPassword$ ./internal/web
# verdict: RED (assertion)
=== RUN TestR709_TheRevealEndpointServesAnInstalledPassword
r709_password_field_test.go:86: installed password: 403 {"data":null,"error":"Ez a mező nem kérhető le.","ok":false}
--- FAIL: TestR709_TheRevealEndpointServesAnInstalledPassword (0.06s)
@@ -0,0 +1,9 @@
# RP16 — special generator
# mutation in internal/stacks/deploy.go:
# - '\t\tcase "special":\n\t\t\treturn randomWithSpecial(length)'
# + '\t\tcase "special":\n\t\t\treturn randomAlphanumeric(length) // RED-PROOF RP16'
# go test -run ^TestGenerateValue_PasswordWithASpecialCharacter$ ./internal/stacks
# verdict: RED (assertion)
=== RUN TestGenerateValue_PasswordWithASpecialCharacter
setup_gate_test.go:315: no special character
--- FAIL: TestGenerateValue_PasswordWithASpecialCharacter (0.00s)
@@ -0,0 +1,9 @@
# RP2 — an unwritable gate refuses the install
# mutation in internal/stacks/deploy.go:
# - '\t\tg, err := m.prepareSetupGate(req.StackName, stack.ComposePath, env)\n\t\tif err != nil {'
# + '\t\tg, err := m.prepareSetupGate(req.StackName, stack.ComposePath, env)\n\t\tif err != nil && false { // RED-PROOF RP2'
# go test -run ^TestSetupGate_AnUnwritableGateRefusesTheInstall$ ./internal/stacks
# verdict: RED (assertion)
=== RUN TestSetupGate_AnUnwritableGateRefusesTheInstall
setup_gate_test.go:114: an install whose gate could not be written was accepted
--- FAIL: TestSetupGate_AnUnwritableGateRefusesTheInstall (0.01s)
@@ -0,0 +1,9 @@
# RP3 — the probe opens only when the app says set up
# mutation in internal/stacks/setup_gate.go:
# - '\treturn got == done, got\n'
# + '\treturn true, got // RED-PROOF RP3\n'
# go test -run ^TestSetupGate_TheProbeOpensItOnlyWhenTheAppSaysSetUp$ ./internal/stacks
# verdict: RED (assertion)
=== RUN TestSetupGate_TheProbeOpensItOnlyWhenTheAppSaysSetUp
setup_gate_test.go:176: not yet set up, but the gate opened
--- FAIL: TestSetupGate_TheProbeOpensItOnlyWhenTheAppSaysSetUp (0.00s)
@@ -0,0 +1,9 @@
# RP4 — opening removes the traefik file
# mutation in internal/stacks/setup_gate.go:
# - '\tif err := m.removeSetupGateFile(name); err != nil {\n\t\tm.logger.Printf("[ERROR] [stacks] %s: setup gate opened'
# + '\tif err := error(nil); err != nil { // RED-PROOF RP4\n\t\tm.logger.Printf("[ERROR] [stacks] %s: setup gate opened'
# go test -run ^TestSetupGate_TheButtonOpensItOnce$ ./internal/stacks
# verdict: RED (assertion)
=== RUN TestSetupGate_TheButtonOpensItOnce
setup_gate_test.go:210: file still there after the press
--- FAIL: TestSetupGate_TheButtonOpensItOnce (0.00s)
@@ -0,0 +1,9 @@
# RP5 — the button opens once
# mutation in internal/stacks/setup_gate.go:
# - '\tif st.AppConfig == nil || !st.AppConfig.SetupGate.Closed() {\n\t\treturn ErrSetupGateNotClosed'
# + '\tif st.AppConfig == nil { // RED-PROOF RP5\n\t\treturn ErrSetupGateNotClosed'
# go test -run ^TestSetupGate_TheButtonOpensItOnce$ ./internal/stacks
# verdict: RED (assertion)
=== RUN TestSetupGate_TheButtonOpensItOnce
setup_gate_test.go:213: second press: setup gate gapp: the record could not be written
--- FAIL: TestSetupGate_TheButtonOpensItOnce (0.00s)
@@ -0,0 +1,9 @@
# RP6 — a restart rewrites the gate file from the record
# mutation in internal/stacks/setup_gate.go:
# - '\t\t\tif _, err := m.writeSetupGate(it.name, it.compose, cfg.Env); err != nil {'
# + '\t\t\tif _, err := "", error(nil); err != nil { // RED-PROOF RP6'
# go test -run ^TestSetupGate_ARestartKeepsItAndStaleFilesGo$ ./internal/stacks
# verdict: RED (assertion)
=== RUN TestSetupGate_ARestartKeepsItAndStaleFilesGo
setup_gate_test.go:234: the restart left the app open: the gate file was not rewritten from the record
--- FAIL: TestSetupGate_ARestartKeepsItAndStaleFilesGo (0.00s)
@@ -0,0 +1,10 @@
# RP7 — a restore keeps the gate record
# mutation in internal/stacks/life_records.go:
# - '\tcfg.SetupGate = prior.SetupGate\n'
# + '\t// RED-PROOF RP7\n'
# go test -run ^TestSetupGate_ARestoreKeepsTheRecord$ ./internal/stacks
# verdict: RED (assertion)
=== RUN TestSetupGate_ARestoreKeepsTheRecord
setup_gate_test.go:264: opened before: after the restore "", want "open"
setup_gate_test.go:264: still closed: after the restore "", want "closed"
--- FAIL: TestSetupGate_ARestoreKeepsTheRecord (0.00s)
@@ -0,0 +1,9 @@
# RP8 — a stranger is refused
# mutation in internal/web/setup_gate.go:
# - '\tif s.gateCookieValid(r, host) {'
# + '\tif true || s.gateCookieValid(r, host) { // RED-PROOF RP8'
# go test -run ^TestSetupGate_AStrangerIsRefused$ ./internal/web
# verdict: RED (assertion)
=== RUN TestSetupGate_AStrangerIsRefused
setup_gate_test.go:100: a stranger's browser: 200 "" — want 302 to the dashboard's gate page
--- FAIL: TestSetupGate_AStrangerIsRefused (0.06s)
@@ -0,0 +1,9 @@
# RP9 — a token is one-use
# mutation in internal/web/setup_gate.go:
# - '\tif _, seen := s.gate.used[t.Nonce]; seen {'
# + '\tif _, seen := s.gate.used[t.Nonce]; seen && false { // RED-PROOF RP9'
# go test -run ^TestSetupGate_TheHouseholdPassesWithItsSession$ ./internal/web
# verdict: RED (assertion)
=== RUN TestSetupGate_TheHouseholdPassesWithItsSession
setup_gate_test.go:163: a token worked twice: 302
--- FAIL: TestSetupGate_TheHouseholdPassesWithItsSession (0.06s)
@@ -0,0 +1,11 @@
# A stranger polls both gated app hosts every second during the deploys: no cookies, API style and browser style.
B=https://192.168.0.114; D=enkisfelhom.hu
end=$(( $(date +%s) + ${1:-900} ))
while [ $(date +%s) -lt $end ]; do
for h in g-n8n g-immich g-abs g-kuma; do
p=/api/x
r=$(curl -sk -m 5 -H "Host: $h.$D" -w '|%{http_code}' $B$p | tr -d '\n' | cut -c1-90)
b=$(curl -sk -m 5 -H "Host: $h.$D" -H 'Accept: text/html' -o /dev/null -w '%{http_code}' $B/)
echo "$(date -u +%T) $h api=[$r] browser=$b"
done; sleep 1
done
@@ -0,0 +1,22 @@
mealie BEFORE: default -> 200 | wrong -> 401
wger BEFORE: default -> 404 | wrong -> 404
mealie route output: FELHOM_AFTER_INSTALL_OK rc=0
mealie AFTER: default -> 401 | new -> 200 | wrong -> 401
wger route output: FELHOM_AFTER_INSTALL_OK rc=0
wger AFTER: default -> 404 | new -> 404 | wrong -> 404
uid=1000(wger) gid=1000(wger) groups=1000(wger),100(users)
/home/wger/src
wger (after the first set_password above): default -> 200-> | wrong -> 200->
wger route output: FELHOM_AFTER_INSTALL_OK rc=0
wger AFTER: default -> 200-> | new -> 200-> | wrong -> 200->
# wger re-run: the form's field is 'credential'; no Origin header (a browser's https Origin is refused by CSRF — R-712)
wger (after the first set_password above): default -> 200-> | wrong -> 200->
wger route output: FELHOM_AFTER_INSTALL_OK rc=0
wger AFTER: default -> 200-> | new -> 200-> | wrong -> 200->
# wger re-run 2: the username field is 'login'
wger (after the first set_password above): default -> 200-> | wrong -> 200->
wger route output: FELHOM_AFTER_INSTALL_OK rc=0
wger AFTER: default -> 200-> | new -> 302->/ | wrong -> 200->
calibre-web BEFORE: default -> 302->/ | wrong -> 200->
route output (no cd, absolute path, exec array): Password for user 'admin' changed rc=0
calibre-web AFTER: default -> 200-> | new -> 302->/ | wrong -> 200->
@@ -0,0 +1,18 @@
09:08:43 sync -> 200
09:08:48 catalog synced: wger after_install passes the password as argv
09:08:48 [1] required fields filled beyond DOMAIN/SUBDOMAIN: ['ADMIN_PASSWORD']
09:08:48 mealie deploy -> 202
09:08:48 [1] required fields filled beyond DOMAIN/SUBDOMAIN: ['ADMIN_PASSWORD']
09:08:48 wger deploy -> 202
09:08:52 [1] made the drive paths this app requires: ['/mnt/felhom-drives/scratch_hdd/userdata/calibre-web']
09:08:52 [1] required fields filled beyond DOMAIN/SUBDOMAIN: ['HDD_PATH', 'ADMIN_PASSWORD']
09:08:52 calibre-web deploy -> 202
09:09:23 calibre-web after_install record after 30s: ok=True detail='None'
09:09:23 mealie after_install record after 30s: ok=True detail='None'
09:09:53 wger after_install record after 60s: ok=True detail='None'
mealie (200 = signed in) default MyPassword -> 401 | generated -> 200 | wrong -> 401
wger (302 = signed in; the browser's https Origin, R-712) default adminadmin -> 200 | generated -> 302 | wrong -> 200
calibre-web (302 = signed in) default admin123 -> 200 | generated -> 302 | wrong -> 200
09:10:11 mealie app page carries the known-login sentence (ASCII fragment "ismert, k"): False
09:10:11 wger app page carries the known-login sentence (ASCII fragment "ismert, k"): False
09:10:11 calibre-web app page carries the known-login sentence (ASCII fragment "ismert, k"): False
@@ -0,0 +1,2 @@
Status: Downloaded newer image for node:22-alpine
{"runs":2000,"bad":0,"plainLen":16,"plainHasSpecial":false}
@@ -0,0 +1,10 @@
18: - GF_SECURITY_ADMIN_PASSWORD=${GF_SECURITY_ADMIN_PASSWORD:?the admin password is required (R-708)}
--- empty password:
rc=1
error while interpolating services.grafana.environment.[]: required variable GF_SECURITY_ADMIN_PASSWORD is missing a value: the admin password is required (R-708)
--- unset:
rc=1
error while interpolating services.grafana.environment.[]: required variable GF_SECURITY_ADMIN_PASSWORD is missing a value: the admin password is required (R-708)
--- set (control):
1
rc=0
@@ -0,0 +1,4 @@
romm 9202 drill catalog: known-login sentence ('ismert, k'): False | 'admin / admin' on page: False | page bytes 40950
zipline 9202 drill catalog: known-login sentence ('ismert, k'): False | 'admin / admin' on page: False | page bytes 40440
bookstack 9202 drill catalog: known-login sentence ('ismert, k'): False | 'admin / admin' on page: False | page bytes 40553
control: demo-hp live catalog 2026-09-29 06:10Z showed romm's sentence (A/A2-page-warning-after.txt)
@@ -0,0 +1,3 @@
mealie: reveal -> ok=True value length 30 | settings page 68582 bytes | the value in the page HTML: False | reveal control present: True
wger: reveal -> ok=True value length 30 | settings page 68080 bytes | the value in the page HTML: False | reveal control present: True
calibre-web: reveal -> ok=True value length 30 | settings page 71491 bytes | the value in the page HTML: False | reveal control present: True
@@ -0,0 +1,215 @@
# Applies the 2026-09-29 catalog changes (decision 46 gate + Part D) to a catalog checkout: argv[1] = repo root.
# Identical on the drill repo and the live repo, so the drill proves exactly what ships.
import sys, os
R = sys.argv[1]
T = lambda *p: os.path.join(R, "templates", *p)
def edit(path, old, new, count=1):
s = open(path).read()
n = s.count(old)
if n != count:
sys.exit(f"{path}: expected {count} of {old[:60]!r}, found {n}")
open(path, "w").write(s.replace(old, new))
def after_line(path, anchor, text):
edit(path, anchor, anchor + text)
GATE_NOTE = """
# --- The setup gate (controller >= 0.280.0, `09` §3 decision 46) ---
# The first visitor would create the admin. So a fresh install is closed to everyone but the household (a browser
# signed in to the dashboard) until the first setup is done%s
setup_gate: true
"""
# --- decision 46: the gate, on four class-4 apps -------------------------------------------------------------
probes = {
"immich": ("; immich's own status says so (measured on 9202 2026-09-29:\n# isInitialized false -> true once the admin exists).",
"setup_done_probe:\n url: http://immich-server:2283/api/server/config\n field: isInitialized\n done: \"true\"\n"),
"n8n": ("; n8n's own settings say so (measured on 9202 2026-09-29:\n# showSetupOnFirstLoad true -> false once the owner exists).",
"setup_done_probe:\n url: http://n8n:5678/rest/settings\n field: data.userManagement.showSetupOnFirstLoad\n done: \"false\"\n"),
"audiobookshelf": ("; audiobookshelf's own status says so (`/status` isInit — upstream, measured on 9202 by the\n# 2026-09-29 live proof).",
"setup_done_probe:\n url: http://audiobookshelf:80/status\n field: isInit\n done: \"true\"\n"),
"uptime-kuma": (". uptime-kuma says it only over socket.io, so the household presses \"Done, I set it up\"\n# on the app page.", ""),
}
for app, (why, probe) in probes.items():
p = T(app, ".felhom.yml")
s = open(p).read()
assert "setup_gate" not in s, app
i = s.index("\n# --- App info")
s = s[:i] + "\n" + (GATE_NOTE % why).rstrip("\n") + "\n" + probe + s[i:]
open(p, "w").write(s)
# --- Part D1: mealie ------------------------------------------------------------------------------------------
p = T("mealie", ".felhom.yml")
edit(p, """ description: "Az alkalmazás aldomainje"
# --- App info (info page content) ---""", """ description: "Az alkalmazás aldomainje"
# `09` §3 decision 45: Mealie starts with changeme@example.com / MyPassword. The box replaces that password with
# this generated one right after the install (after_install below); the app page shows it as the first password.
- env_var: ADMIN_PASSWORD
label: "Admin jelszó (changeme@example.com)"
type: password
generate: "password:24"
description: "Az első bejelentkezéshez: changeme@example.com és ez a jelszó. Utána a profilodban módosítható."
locked_after_deploy: true
# --- App info (info page content) ---""")
edit(p, " - 'Jelentkezz be: changeme@example.com / MyPassword'\n - 'Változtasd meg azonnal az email címet és jelszót'\n",
" - 'Jelentkezz be: changeme@example.com és a Beállítások oldalon látható első jelszó'\n - 'Változtasd meg az email címet a sajátodra'\n")
edit(p, " - 'Sign in: changeme@example.com / MyPassword'\n - 'Change the e-mail address and password straight away'\n",
" - 'Sign in: changeme@example.com and the first password shown on the settings page'\n - 'Change the e-mail address to your own'\n")
edit(p, """# --- Controller-side health probe ---
healthcheck:
checks:
- type: tcp
port: 9000""", """# --- After a fresh install (controller >= 0.279.0, decision 45) ---
# Mealie's OWN user repository sets the seeded user's password (what its scripts/change_password.py does, without the
# prompts). Measured on 9202 2026-09-29: afterwards MyPassword answers 401 at /api/auth/token, the new one 200.
after_install:
service: mealie
env: [ADMIN_PASSWORD]
# The password is the LAST ARGUMENT (sys.argv[1]), never pasted into the code: a quote in it cannot break or change
# the program (security review 2026-09-29).
command: ["python3", "-c", "import sys\\nfrom mealie.core.security.security import hash_password\\nfrom mealie.db.db_setup import session_context\\nfrom mealie.repos.repository_factory import AllRepositories\\nwith session_context() as s:\\n r = AllRepositories(s, group_id=None, household_id=None)\\n u = r.users.get_one('changeme@example.com', 'email')\\n r.users.update_password(u.id, hash_password(sys.argv[1]))\\n print('FELHOM_AFTER_INSTALL_OK')\\n", "${ADMIN_PASSWORD}"]
success: "FELHOM_AFTER_INSTALL_OK"
# --- Controller-side health probe ---
healthcheck:
checks:
- type: tcp
port: 9000""")
edit(p, """ - env_var: SUBDOMAIN
label: 'Subdomain'
description: 'The subdomain this app answers on'
""", """ - env_var: SUBDOMAIN
label: 'Subdomain'
description: 'The subdomain this app answers on'
- env_var: ADMIN_PASSWORD
label: 'Admin password (changeme@example.com)'
description: 'For the first sign-in: changeme@example.com and this password. Change it in your profile afterwards.'
""")
# --- Part D1: wger (+ R-712: a browser's https Origin was refused by CSRF) --------------------------------------
p = T("wger", ".felhom.yml")
edit(p, """ generate: "hex:32"
locked_after_deploy: true
# --- App info (info page content) ---""", """ generate: "hex:32"
locked_after_deploy: true
# `09` §3 decision 45: wger starts with admin / adminadmin. The box replaces that password with this generated
# one right after the install (after_install below); the app page shows it as the first password.
- env_var: ADMIN_PASSWORD
label: "Admin jelszó (admin)"
type: password
generate: "password:24"
description: "Az első bejelentkezéshez: admin és ez a jelszó. Utána a beállításokban módosítható."
locked_after_deploy: true
# --- App info (info page content) ---""")
edit(p, " - 'Jelentkezz be: admin / adminadmin'\n - 'Változtasd meg azonnal a jelszót'\n",
" - 'Jelentkezz be: admin és a Beállítások oldalon látható első jelszó'\n - 'Add meg az email címedet a beállításokban'\n")
edit(p, " - 'Sign in: admin / adminadmin'\n - 'Change the password straight away'\n",
" - 'Sign in: admin and the first password shown on the settings page'\n - 'Add your e-mail address in the settings'\n")
edit(p, "\n# --- Controller-side health probe ---\nhealthcheck:", """
# --- After a fresh install (controller >= 0.279.0, decision 45) ---
# Django's own set_password on the seeded admin. Measured on 9202 2026-09-29: afterwards adminadmin no longer signs in
# at /en/user/login, the new one does.
after_install:
service: wger
env: [ADMIN_PASSWORD]
# The password is the LAST ARGUMENT (sys.argv[1]), never pasted into the code: a quote in it cannot break or change
# the program (security review 2026-09-29). Django is set up the way manage.py does it (settings.main).
command: ["python3", "-c", "import os, sys; sys.path.insert(0, '/home/wger/src'); os.chdir('/home/wger/src'); os.environ.setdefault('DJANGO_SETTINGS_MODULE', 'settings.main'); import django; django.setup(); from django.contrib.auth.models import User; u = User.objects.get(username='admin'); u.set_password(sys.argv[1]); u.save(); print('FELHOM_AFTER_INSTALL_OK')", "${ADMIN_PASSWORD}"]
success: "FELHOM_AFTER_INSTALL_OK"
# --- Controller-side health probe ---
healthcheck:""")
edit(p, """ - env_var: SECRET_KEY
label: 'Encryption key'
""", """ - env_var: SECRET_KEY
label: 'Encryption key'
- env_var: ADMIN_PASSWORD
label: 'Admin password (admin)'
description: 'For the first sign-in: admin and this password. Change it in the settings afterwards.'
""")
p = T("wger", "docker-compose.yml")
edit(p, " - DJANGO_DB_ENGINE=django.db.backends.sqlite3\n", """ # R-712 (measured 2026-09-29 on 9202): behind traefik wger saw the request as http and refused a browser's
# https Origin with "CSRF verification failed" — nobody could sign in from a browser.
- CSRF_TRUSTED_ORIGINS=https://${SUBDOMAIN}.${DOMAIN}
- X_FORWARDED_PROTO_HEADER_SET=True
- DJANGO_DB_ENGINE=django.db.backends.sqlite3
""")
# --- Part D2: calibre-web -----------------------------------------------------------------------------------------
p = T("calibre-web", ".felhom.yml")
edit(p, """ description: "A külső merevlemez elérési útja, ahol a Calibre könyvtár található"
locked_after_deploy: true
# --- App info (info page content) ---""", """ description: "A külső merevlemez elérési útja, ahol a Calibre könyvtár található"
locked_after_deploy: true
# `09` §3 decision 45: Calibre-Web starts with admin / admin123. The box replaces that password with this
# generated one right after the install (after_install below). Its password policy demands a special character,
# hence `:special` (controller >= 0.280.0).
- env_var: ADMIN_PASSWORD
label: "Admin jelszó (admin)"
type: password
generate: "password:24:special"
description: "Az első bejelentkezéshez: admin és ez a jelszó. Kell benne kis- és nagybetű, szám és egy különleges karakter."
locked_after_deploy: true
# --- App info (info page content) ---""")
edit(p, " - 'Jelentkezz be: admin / admin123'\n - 'Változtasd meg azonnal a jelszót'\n",
" - 'Jelentkezz be: admin és a Beállítások oldalon látható első jelszó'\n")
edit(p, " - 'Sign in: admin / admin123'\n - 'Change the password straight away'\n",
" - 'Sign in: admin and the first password shown on the settings page'\n")
edit(p, "\n# --- Controller-side health probe ---\nhealthcheck:", """
# --- After a fresh install (controller >= 0.280.0, decision 45) ---
# Calibre-Web's OWN `cps.py -s user:password`, as the app user. Measured on 9202 2026-09-29: afterwards admin123 no
# longer signs in, the new one does; a password without a special character is refused by its policy.
after_install:
service: calibre-web
user: abc
env: [ADMIN_PASSWORD]
command: ["python3", "/app/calibre-web-automated/cps.py", "-p", "/config/app.db", "-s", "admin:${ADMIN_PASSWORD}"]
success: "Password for user 'admin' changed"
# --- Controller-side health probe ---
healthcheck:""")
edit(p, """ label: 'E-book library path'
description: 'The path to the external hard drive where the Calibre library lives'
placeholder: '/mnt/felhom-drives/hdd_1'
""", """ label: 'E-book library path'
description: 'The path to the external hard drive where the Calibre library lives'
placeholder: '/mnt/felhom-drives/hdd_1'
- env_var: ADMIN_PASSWORD
label: 'Admin password (admin)'
description: 'For the first sign-in: admin and this password. It needs a lower and an upper case letter, a digit and a special character.'
""")
# --- Part D3: romm and zipline — their default_creds notes are stale (they are class 4) ---------------------------
import re
for app, cred in (("romm", "admin / admin"), ("zipline", "admin / zipline")):
p = T(app, ".felhom.yml")
s = open(p).read()
s, n = re.subn(r"(?m)^[ \t]*default_creds: ['\"]" + re.escape(cred) + r"['\"][ \t]*\n", "", s)
assert n == 2 and "default_creds" not in s, (app, n)
open(p, "w").write(s)
edit(T("romm", ".felhom.yml"), ' - "Jelentkezz be az alapértelmezett admin / admin fiókkal"\n',
' - "Az első megnyitáskor hozd létre az admin fiókodat"\n')
edit(T("romm", ".felhom.yml"), ' - "Sign in with the default admin / admin account"\n',
' - "On the first visit, create your admin account"\n')
edit(T("zipline", ".felhom.yml"), " - 'Jelentkezz be: admin / zipline'\n - 'Változtasd meg azonnal a jelszót'\n",
" - 'Az első megnyitáskor hozd létre az admin fiókodat'\n")
edit(T("zipline", ".felhom.yml"), " - 'Sign in: admin / zipline'\n - 'Change the password straight away'\n",
" - 'On the first visit, create your admin account'\n")
# --- Part D4: R-708 — grafana refuses to start without its admin password; never `admin` ---------------------------
edit(T("grafana", "docker-compose.yml"), "GF_SECURITY_ADMIN_PASSWORD=${GF_SECURITY_ADMIN_PASSWORD:-admin}",
"GF_SECURITY_ADMIN_PASSWORD=${GF_SECURITY_ADMIN_PASSWORD:?the admin password is required (R-708)}")
print("catalog patched:", R)
@@ -0,0 +1,13 @@
set -u
DOM=enkisfelhom.hu; J=/tmp/cwj.$$
login() { rm -f $J; T=$(curl -sk -c $J -b $J -H "Host: c-cw.$DOM" https://127.0.0.1/login | grep -o 'name="csrf_token" value="[^"]*"' | head -1 | sed 's/.*value="//;s/"$//')
curl -sk -o /dev/null -w '%{http_code}->%{redirect_url}' -c $J -b $J -H "Host: c-cw.$DOM" -X POST --data-urlencode "csrf_token=$T" --data-urlencode "username=admin" --data-urlencode "password=$1" --data-urlencode "remember_me=on" https://127.0.0.1/login | sed "s|https\?://[^/]*||"; }
echo "calibre-web BEFORE: default -> $(login admin123) | wrong -> $(login wrongxyz123)"
# a password in the shape of generate: password:24:special (made here, never printed)
S='-_.!@#%+='
while :; do P=$(head -c 400 /dev/urandom | tr -dc 'A-Za-z0-9_.!@#%+=-' | head -c 24); case "$P" in [A-Za-z0-9]*) ;; *) continue;; esac
echo "$P" | grep -q '[a-z]' && echo "$P" | grep -q '[A-Z]' && echo "$P" | grep -q '[0-9]' && echo "$P" | grep -q '[-_.!@#%+=]' && break; done
OUT=$(docker exec -u abc calibre-web python3 /app/calibre-web-automated/cps.py -p /config/app.db -s "admin:$P" 2>&1; echo "rc=$?")
echo "route output (no cd, absolute path, exec array): $(echo "$OUT" | grep -v -iE 'ProxyFix|magic shel|SESSION_COOKIE' | tr '\n' ' ' | sed "s/$(printf '%s' "$P" | sed 's/[.[\*^$/]/\\&/g')/<redacted>/g" | cut -c1-200)"
echo "calibre-web AFTER: default -> $(login admin123) | new -> $(login "$P") | wrong -> $(login wrongxyz123)"
unset P; rm -f $J
@@ -0,0 +1,46 @@
# Part D live proof (9202, controller 0.280.0, drill catalog): fresh installs of mealie, wger, calibre-web; the box's
# after_install replaces each default login; then default / generated / wrong through each app's own login.
# The generated passwords live in this process and reach the guest over STDIN only; never printed.
import json, os, subprocess, sys, time
sys.path.insert(0, '/mnt/5_hdd/felhom.eu/git/felhom.eu/documentation/audits/pg-calcom-claper-2026-09-28/tools')
import walk as w
APPS = {"mealie": "d-mealie", "wger": "d-wger", "calibre-web": "d-cw"}
w.login()
code, _ = w.ctl("POST", "/api/sync", {})
w.say("sync ->", code)
for _ in range(24):
time.sleep(5)
meta = (w.ctl("GET", "/api/stacks/wger/deploy-fields")[1].get("data") or {}).get("metadata") or {}
ai = meta.get("after_install") or {}
if ai and "sys.argv[1]" in " ".join(ai.get("command") or []):
w.say("catalog synced: wger after_install passes the password as argv"); break
for app, sub in APPS.items():
v = w.deploy_values(app, sub)
code, d = w.ctl("POST", f"/api/stacks/{app}/deploy", {"values": v, "kept_data": "fresh"})
w.say(app, "deploy ->", code)
t0 = time.time()
left = set(APPS)
while left and time.time() - t0 < 900:
for app in sorted(left):
rec = (w.stack(app).get("app_config") or {}).get("after_install")
if rec:
w.say(app, f"after_install record after {time.time() - t0:.0f}s: ok={rec.get('ok')} detail={str(rec.get('detail'))[:80]!r}")
left.discard(app)
time.sleep(10)
for app in left:
w.say(app, "NO after_install record after 900 s")
pw = {a: w.GENERATED[a]["ADMIN_PASSWORD"] for a in APPS}
script = open(os.path.join(os.path.dirname(__file__), "d_login.sh")).read()
subprocess.run(["ssh", "hp", "cat > /root/d_login.sh && pct push 9202 /root/d_login.sh /root/d_login.sh && rm /root/d_login.sh"],
input=script, text=True, check=True, capture_output=True)
r = subprocess.run(["ssh", "hp", "pct exec 9202 -- bash /root/d_login.sh; pct exec 9202 -- rm -f /root/d_login.sh"],
input="\n".join(pw[a] for a in ("mealie", "wger", "calibre-web")) + "\n", text=True, capture_output=True, timeout=300)
out = r.stdout
for a in pw.values():
out = out.replace(a, "<redacted>")
print(out, end="")
# the page: no known-login sentence once replaced (ASCII fragment; control: a class-3 app not installed still shows it)
for app in APPS:
page = w.page(f"/apps/{app}")
w.say(app, "app page carries the known-login sentence (ASCII fragment \"ismert, k\"):", "ismert, k" in page)
@@ -0,0 +1,13 @@
# Part D live: default / generated / wrong through each app's OWN login. Generated passwords on STDIN.
set -u
DOM=enkisfelhom.hu; J=/tmp/dl.$$
read -r PM; read -r PW; read -r PC
mealie() { curl -sk -o /dev/null -w '%{http_code}' -H "Host: d-mealie.$DOM" -X POST --data-urlencode "username=changeme@example.com" --data-urlencode "password=$1" https://127.0.0.1/api/auth/token; }
wger() { rm -f $J; T=$(curl -sk -c $J -b $J -H "Host: d-wger.$DOM" https://127.0.0.1/en/user/login | grep -o 'name="csrfmiddlewaretoken" value="[^"]*"' | head -1 | sed 's/.*value="//;s/"$//')
curl -sk -o /dev/null -w '%{http_code}' -c $J -b $J -H "Host: d-wger.$DOM" -H "Origin: https://d-wger.$DOM" -H "Referer: https://d-wger.$DOM/en/user/login" -H "X-Forwarded-Proto: https" -X POST --data-urlencode "csrfmiddlewaretoken=$T" --data-urlencode "login=admin" --data-urlencode "password=$1" https://127.0.0.1/en/user/login; }
cw() { rm -f $J; T=$(curl -sk -c $J -b $J -H "Host: d-cw.$DOM" https://127.0.0.1/login | grep -o 'name="csrf_token" value="[^"]*"' | head -1 | sed 's/.*value="//;s/"$//')
curl -sk -o /dev/null -w '%{http_code}' -c $J -b $J -H "Host: d-cw.$DOM" -X POST --data-urlencode "csrf_token=$T" --data-urlencode "username=admin" --data-urlencode "password=$1" --data-urlencode "remember_me=on" https://127.0.0.1/login; }
echo "mealie (200 = signed in) default MyPassword -> $(mealie MyPassword) | generated -> $(mealie "$PM") | wrong -> $(mealie wrongxyz123)"
echo "wger (302 = signed in; the browser's https Origin, R-712) default adminadmin -> $(wger adminadmin) | generated -> $(wger "$PW") | wrong -> $(wger wrongxyz123)"
echo "calibre-web (302 = signed in) default admin123 -> $(cw admin123) | generated -> $(cw "$PC") | wrong -> $(cw wrongxyz123)"
unset PM PW PC; rm -f $J
@@ -0,0 +1,27 @@
# Part D1 probe (9202): the default login before, the app's own route with a generated password (made HERE, never
# printed), then default / new / wrong through the app's own login API via traefik.
set -u
DOM=enkisfelhom.hu
mealie_login() { curl -sk -o /dev/null -w '%{http_code}' -H "Host: c-mealie.$DOM" -X POST --data-urlencode "username=changeme@example.com" --data-urlencode "password=$1" https://127.0.0.1/api/auth/token; }
wger_login() { curl -sk -o /dev/null -w '%{http_code}' -H "Host: c-wger.$DOM" -H 'Content-Type: application/json' -X POST -d "{\"username\":\"admin\",\"password\":\"$1\"}" https://127.0.0.1/api/v2/login/; }
echo "mealie BEFORE: default -> $(mealie_login MyPassword) | wrong -> $(mealie_login wrongxyz123)"
echo "wger BEFORE: default -> $(wger_login adminadmin) | wrong -> $(wger_login wrongxyz123)"
P=$(head -c 300 /dev/urandom | tr -dc A-Za-z0-9 | head -c 24)
OUT=$(docker exec mealie python3 -c "
from mealie.core.security.security import hash_password
from mealie.db.db_setup import session_context
from mealie.repos.repository_factory import AllRepositories
with session_context() as s:
r = AllRepositories(s, group_id=None, household_id=None)
u = r.users.get_one('changeme@example.com', 'email')
r.users.update_password(u.id, hash_password('$P'))
print('FELHOM_AFTER_INSTALL_OK')
" 2>&1; echo "rc=$?")
echo "mealie route output: $(echo "$OUT" | tail -3 | tr '\n' ' ' | sed "s/$P/<redacted>/g")"
echo "mealie AFTER: default -> $(mealie_login MyPassword) | new -> $(mealie_login "$P") | wrong -> $(mealie_login wrongxyz123)"
Q=$(head -c 300 /dev/urandom | tr -dc A-Za-z0-9 | head -c 24)
OUT=$(docker exec wger sh -c "cd /home/wger/src && python3 manage.py shell -c \"from django.contrib.auth.models import User; u = User.objects.get(username='admin'); u.set_password('$Q'); u.save(); print('FELHOM_AFTER_INSTALL_OK')\"" 2>&1; echo "rc=$?")
echo "wger route output: $(echo "$OUT" | tail -3 | tr '\n' ' ' | sed "s/$Q/<redacted>/g")"
echo "wger AFTER: default -> $(wger_login adminadmin) | new -> $(wger_login "$Q") | wrong -> $(wger_login wrongxyz123)"
docker exec wger sh -c 'id; pwd' | head -2
unset P Q
@@ -0,0 +1,10 @@
set -u
DOM=enkisfelhom.hu; J=/tmp/wj.$$
wger_login() { rm -f $J; T=$(curl -sk -c $J -b $J -H "Host: c-wger.$DOM" https://127.0.0.1/en/user/login | grep -o 'name="csrfmiddlewaretoken" value="[^"]*"' | head -1 | sed 's/.*value="//;s/"$//')
curl -sk -o /dev/null -w '%{http_code}->%{redirect_url}' -c $J -b $J -H "Host: c-wger.$DOM" -X POST --data-urlencode "csrfmiddlewaretoken=$T" --data-urlencode "login=admin" --data-urlencode "password=$1" https://127.0.0.1/en/user/login | sed "s|https\?://[^/]*||"; }
echo "wger (after the first set_password above): default -> $(wger_login adminadmin) | wrong -> $(wger_login wrongxyz123)"
Q=$(head -c 300 /dev/urandom | tr -dc A-Za-z0-9 | head -c 24)
OUT=$(docker exec wger sh -c "cd /home/wger/src && python3 manage.py shell -c \"from django.contrib.auth.models import User; u = User.objects.get(username='admin'); u.set_password('$Q'); u.save(); print('FELHOM_AFTER_INSTALL_OK')\"" 2>&1; echo "rc=$?")
echo "wger route output: $(echo "$OUT" | tail -2 | tr '\n' ' ' | sed "s/$Q/<redacted>/g")"
echo "wger AFTER: default -> $(wger_login adminadmin) | new -> $(wger_login "$Q") | wrong -> $(wger_login wrongxyz123)"
unset Q; rm -f $J
@@ -0,0 +1,34 @@
function generatePassword(fieldId, confirmFieldId, spec) {
const letters = 'abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789';
const SPECIAL = '-_.!@#%+=';
var parts = (spec || '').split(':');
var n = parseInt(parts[1], 10);
if (parts[0] !== 'password' || !(n >= 12 && n <= 64)) { n = 16; }
var special = parts[2] === 'special';
var chars = special ? letters + SPECIAL : letters;
let pass = '';
for (;;) {
pass = '';
const arr = new Uint32Array(n);
crypto.getRandomValues(arr);
for (let i = 0; i < n; i++) {
pass += chars[arr[i] % chars.length];
}
if (!special || (/[a-z]/.test(pass) && /[A-Z]/.test(pass) && /[0-9]/.test(pass) && /[-_.!@#%+=]/.test(pass) && /^[A-Za-z0-9]/.test(pass))) {
break;
}
}
document.getElementById(fieldId).value = pass;
if (confirmFieldId) {
var ce = document.getElementById(confirmFieldId);
if (ce) ce.value = pass;
}
}
var out={};
function field(){return {value:''};}
global.document={getElementById:function(id){out[id]=out[id]||{value:''};return out[id];}};
var bad=0, n=0, specialSeen=0;
for (var k=0;k<2000;k++){ generatePassword('f','c','password:24:special'); var p=out['f'].value; n++;
if (p.length!==24||!/[a-z]/.test(p)||!/[A-Z]/.test(p)||!/[0-9]/.test(p)||!/[-_.!@#%+=]/.test(p)||!/^[A-Za-z0-9]/.test(p)||/['"$\\: `]/.test(p)||out['c'].value!==p) bad++; }
generatePassword('f','c',''); var plain=out['f'].value;
console.log(JSON.stringify({runs:n, bad:bad, plainLen:plain.length, plainHasSpecial:/[^A-Za-z0-9]/.test(plain)}));
@@ -0,0 +1,6 @@
cd /opt/docker/stacks/grafana || exit 1
grep -n 'GF_SECURITY_ADMIN_PASSWORD=' docker-compose.yml
echo "--- empty password:"; GF_SECURITY_ADMIN_PASSWORD= DOMAIN=x SUBDOMAIN=y docker compose -f docker-compose.yml config >/dev/null 2>/tmp/gerr; echo "rc=$?"; head -2 /tmp/gerr
echo "--- unset:"; env -u GF_SECURITY_ADMIN_PASSWORD DOMAIN=x SUBDOMAIN=y docker compose -f docker-compose.yml config >/dev/null 2>/tmp/gerr; echo "rc=$?"; head -2 /tmp/gerr
echo "--- set (control):"; GF_SECURITY_ADMIN_PASSWORD=Xy12345678 DOMAIN=x SUBDOMAIN=y docker compose -f docker-compose.yml config 2>/dev/null | grep -c 'GF_SECURITY_ADMIN_PASSWORD'; echo "rc=${PIPESTATUS[0]}"
rm -f /tmp/gerr
+5 -3
View File
@@ -818,11 +818,13 @@ class (an image `VOLUME` at an unmounted path) is still live — `immich-server`
| **R-704** | **[P3-LOW] The box's crash-loop stop (decision 28) outlives the app: after remove and reinstall, the new install is still held.** Measured 2026-09-28 on 9202: calcom crash-looped at 08:22 and 08:28 (`unhealthy_stop`, `crash_loop`, trip 2, recorded 08:28:59Z); it was then REMOVED through the product twice and installed fresh twice (09:14:51Z the last). At 09:45 the new, healthy install's Update was refused `409 held` with the crash-loop sentence („…újra és újra összeomlott…"), and `GET /api/stacks/calcom` carried the old `hold_reason` while `state=running`. Start lifted it (`the unhealthy stop is LIFTED by Start`). So a household that removes a crash-looping app and installs it again (the obvious fix) finds its updates refused for a crash of a previous install. Not measured: whether the nightly update leg also skips it; whether other holds (restore hold) behave the same. **Fix direction:** the remove clears the app's box-set holds, as `DeleteAppBackupPrefs` clears its backup preferences (R-474). Evidence: `audits/pg-calcom-claper-2026-09-28/box/calcom/hold.txt`, `…/box/calcom/move.txt`. **-- 2026-09-28 later: SECOND and worse instance, then FIXED in controller v0.278.0.** demo-hp's fresh nextcloud (installed 10:13) carried an UPDATE hold from a nextcloud of 2026-09-13 (set before v0.242.0 made removals clear update holds; nothing ever swept it). At the manual off-site run (15:17) the backup leg logged `Skipping volume dump for nextcloud — the app is HELD stopped`, captured no unit, and pushed a snapshot that `carried NO database dump and NO volume tar` — a freshly installed app silently NOT backed up. **Fix:** a removal also clears the crash-loop stop (`settings.ClearUpdateHold`), and a new install (plain or "use my kept data") drops a leftover update/crash-loop hold of an app that is not installed (`Router.dropLeftoverHold`); restore holds (R-379) untouched. Red-proofed RP4–RP6 (`audits/kept-offsite-2026-09-28/redproofs/`). Floor 0.278.0. **STILL OPEN: live proof of the install-time drop** (a box with a leftover hold on an uninstalled app). | **WATCHING — P2; owner: CC (install-time drop, live)** |
| **R-705** | **[P3-LOW] There is no way to run the night's chain now — only its pieces.** Asked by the operator 2026-09-28 (to finish a proof in the day). What exists (read from source, controller v0.278.0): the backup page's off-site run-now (`POST /backup/offbox/run`) runs the dump leg first (the R-44 pre-phase: DB dumps, volume dumps with brief app stops, unit capture) and then the push — used live on demo-hp 2026-09-28 15:17, 3m57s; the debug API has `backup/dbdump`, `backup/crossdrive` (Tier 2), `backup/integrity`, `backup/offsite-proof`. **Missing:** the automatic update leg (`RunUpdateLeg`, chained only to the scheduled off-site job) and the whole-guest backup (the agent's, on its own 24 h / 7 d cadence) have no manual trigger; the only way to run the chain in order is to move the backup window (`POST /backups/window`), which takes W..W+2h at least. **Needs:** a debug action "run tonight's chain now" (dump → Tier 2 → off-site → update leg, in order, one at a time), and an agent-side "whole-guest backup now" for demo boxes. Not built. **-- 2026-09-28 evening: the controller half BUILT (v0.279.0):** debug `POST /api/debug/backup/night-chain` runs dump → Tier 2 → off-site → update leg in order, one at a time, refused while anything else runs; the leg gets its normal length from its own start. Proven on 9202: 44 s, a second press 409, the leg deadline 22:00. **Still open: the whole-guest backup (agent side) has no manual trigger.** | **OPEN — P3, agent half only; owner: CC** |
| **R-706** | **[P3-LOW] Removing an app "with its backups" leaves its off-site verification copy on the drive.** Measured 2026-09-28 on demo-hp: after a full off-site restore of nextcloud (which leaves the downloaded copy in `backups/offsite-restore/nextcloud`, ~1 GB, by design, for the household to inspect), `POST /api/stacks/nextcloud/remove` with `remove_backups: true` removed the unit and listed `backup_paths_removed` WITHOUT the verification copy; it stayed until the restore page's own delete (`POST /backup/offbox/verify-copy/delete`, 302 `scratch_deleted`). A household that removes an app to free space keeps 1 GB it cannot see on the app list. **Fix direction:** the removal with backups also deletes the app's verification copy (the same `DeleteOffsiteRestoreCopy`). Evidence: `audits/kept-offsite-2026-09-28/E/E9-teardown.txt`. **-- 2026-09-28 evening: FIXED in controller v0.279.0** — a removal with its backups also deletes the verification copy and lists it among the removed paths (`TestR706_…`, red-proofed RP7). Not yet seen live (needs a full off-site restore then a removal). | **WATCHING — P3; owner: CC (live)** |
| **R-707** | **[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). | **OPEN — P2; owner: CC** |
| **R-708** | **[P3-LOW] grafana falls back to password `admin` when its admin field is empty.** `templates/grafana/docker-compose.yml:18` `GF_SECURITY_ADMIN_PASSWORD=${…:-admin}` (read 2026-09-28, the audit). Today the field is generated and required, so it is never empty on a normal install — but an edit, an import or a restore that drops the value would publish grafana with `admin / admin`. **Fix direction:** no default in the compose (`${GF_SECURITY_ADMIN_PASSWORD:?}` refuses to start instead). | **OPEN — P3; owner: CC** |
| **R-709** | **[P3-LOW] The deploy page writes the generated admin passwords of installed apps into its HTML.** `internal/web/templates/deploy.html` renders a `type: password` field's decrypted value into a disabled `<input value=…>` (read 2026-09-28; used by the proofs of R-702/R-707 to read the first password as the household sees it). `type: secret` fields got a fetch-on-demand reveal in R-254; `type: password` fields did not. The page needs a login, so this is exposure to a logged-in session's HTML (browser cache, a shared screen, a saved page), not to strangers. **Fix direction:** the R-254 reveal for password fields too. | **OPEN — P3; owner: CC** |
| **R-707** | **[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). | **OPEN — P2; owner: CC; 30 of 37 left** |
| **R-708** | **[P3-LOW] grafana falls back to password `admin` when its admin field is empty.** `templates/grafana/docker-compose.yml:18` `GF_SECURITY_ADMIN_PASSWORD=${…:-admin}` (read 2026-09-28, the audit). Today the field is generated and required, so it is never empty on a normal install — but an edit, an import or a restore that drops the value would publish grafana with `admin / admin`. **Fix direction:** no default in the compose (`${GF_SECURITY_ADMIN_PASSWORD:?}` refuses to start instead). **Fixed 2026-09-29** (catalog `d0e7e2e`): `${GF_SECURITY_ADMIN_PASSWORD:?…}` — `docker compose config` with it empty or unset exits 1 naming R-708, set → 0 (`audits/login-gate-2026-09-29/D/D4-grafana-r708.txt`). | **CLOSED — 2026-09-29** |
| **R-709** | **[P3-LOW] The deploy page writes the generated admin passwords of installed apps into its HTML.** `internal/web/templates/deploy.html` renders a `type: password` field's decrypted value into a disabled `<input value=…>` (read 2026-09-28; used by the proofs of R-702/R-707 to read the first password as the household sees it). `type: secret` fields got a fetch-on-demand reveal in R-254; `type: password` fields did not. The page needs a login, so this is exposure to a logged-in session's HTML (browser cache, a shared screen, a saved page), not to strangers. **Fix direction:** the R-254 reveal for password fields too. **Fixed in controller v0.280.0:** an installed app's password field renders empty with a reveal eye (`/stacks/<n>/auto-field/reveal` now serves `type: password` of an installed app, never a restore-generated one). Red-proofs RP14/RP15; live on 9202: mealie, wger, calibre-web — the revealed value is NOT in the settings page HTML (`…/D/D6-r709-live.txt`). | **CLOSED — 2026-09-29** |
| **R-710** | **[P2-MEDIUM] An app installed before its template gained an `after_install:` is never warned about its default login.** MEASURED 2026-09-29 on demo-hp: bookstack's page carried no known-login sentence although its default `admin@admin.com / password` still logged in (the app was installed before the catalog added bookstack's `after_install` on 2026-09-28; the command never runs for an installed app). `internal/web/known_login.go` reads an ABSENT `after_install` record as "not run yet" for ever. Evidence `audits/login-gate-2026-09-29/A/A2-page-warning-after.txt`. Also: the page has no way to learn of a password the household changed by hand (the brief's Part A4). **Fix direction:** absent record + installed longer than the command's window = in effect; a household "I changed it" press recorded in `app.yaml`. | **OPEN — P2; owner: CC** |
| **R-711** | **[P2-MEDIUM] About a dozen class-4 apps keep open sign-up after their first admin exists — the setup gate (decision 46) does not close that.** FOUND 2026-09-29 by the gate spike (`audits/login-gate-2026-09-29/B/B-VERDICT.md` F3). The gate decides who becomes the admin; once it opens, a stranger can still make an ordinary account on adventurelog, homebox, papra, plant-it, sparkyfitness, vikunja, wanderer, rallly, opengist, wishlist, termix, docmost (READ from `app-catalog-felhom.eu/FIRST-ADMIN.md`, not measured). **Fix direction:** per app, route (a) — disable sign-up after the first user (env or the app's own setting), measured on 9202. | **OPEN — P2; owner: CC** |
| **R-712** | **[P2-MEDIUM] wger refused every browser sign-in behind traefik: "CSRF verification failed".** MEASURED 2026-09-29 on 9202 (live catalog wger 2.6): a POST to `/en/user/login` with the browser's `Origin: https://…` answered 403 — Django saw the request as http (no trusted proxy header) and no `CSRF_TRUSTED_ORIGINS`. Found while proving R-707's wger route. **Fixed** (catalog `d0e7e2e`): `CSRF_TRUSTED_ORIGINS=https://${SUBDOMAIN}.${DOMAIN}` + `X_FORWARDED_PROTO_HEADER_SET=True`; proven on a fresh install: the generated password signs in (302) with the https Origin (`audits/login-gate-2026-09-29/D/D2-live.txt`). | **CLOSED — 2026-09-29** |
| **R-713** | **[P3-LOW] claper's `after_install` pastes the household's password into Elixir code, and the controller does not refuse a value that would break such code.** FOUND 2026-09-29 by a background security review of the drill commit (mealie/wger had the same shape and were changed to pass the password as `sys.argv[1]`). claper's `bin/claper rpc '… "${ADMIN_PASSWORD}" …'` has no argv: a household-typed password with `"` or `#{` breaks the command (recorded as failed; the page then warns) or changes the Elixir it runs — inside the household's own claper container, as that app. The generated value (letters + digits) is safe. **Fix direction:** (1) controller: `expandAfterInstall` refuses a value holding a quote, a backslash, `$`, `{`, `}`, a backtick or a newline — or a declared per-field encoding; (2) claper: read the value some other way (a file the command reads, or `System.get_env` from a one-shot env). | **OPEN — P3; owner: CC** |
<!-- DUE-CHECKS-BEGIN — machine-readable. Parsed by scripts/due_checks_gate.py.
One row per dated check. The R-number must have a row above. Dates are UTC.