Part B: setup-gate spike on 9202 — verdict PASS written before any build; R-711 filed
gates / gates (push) Successful in 25s

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 08:20:42 +02:00
parent 2b1cb246cf
commit 14ae67f0b8
21 changed files with 1215 additions and 0 deletions
@@ -0,0 +1,112 @@
# Part B — SPIKE: the setup gate (decision 46). Verdict: **PASS**, written before any build.
Scratch guest 9202 (controller 0.279.0, traefik v3.6.7), 2026-09-29 06:11–06:19Z. Two real class-4 apps from the live
catalog, installed through the product: **n8n** (web only) and **immich** (has a phone app). The gate was a throwaway
prototype (`gate.py`, python in a container on `traefik-public`) plus one traefik file-provider file (`gate-spike.yml`).
Nothing was released. Both apps, the prototype and the file were removed at the end (`td.sh`, product remove).
Architecture read first: `01-topology-and-trust.md` §5 (trust boundaries), `04-control-plane-authorization.md`
(nothing there covers who may reach an app — see finding F1), controller `internal/infra/infra.go`
(`RenderControllerRoute`), `internal/stacks/infra.go` (`wireController`), `internal/web/auth.go` (session cookie).
## How the prototype works (the shape a build would copy)
- A traefik **file-provider router** per gated app: `Host(<app host>)`, priority 100000, middleware `forwardAuth`,
service `<app>@docker` (the app's own docker-label service). The app's compose and labels are **not touched**.
- The forwardAuth answerer lets a request through only with a **gate cookie** for that app host.
- Without one: a browser `GET` is sent to `https://felhom.<domain>/__gate/start?rd=…` (the dashboard host, where the
household's dashboard cookie already is). With a valid dashboard session the answerer mints a **60-second, one-use
token bound to the app host** and sends the browser to `https://<app>/__felhom_gate/cb?t=…`; the answerer swaps it
for a **host-only** gate cookie (`HttpOnly; Secure; SameSite=Lax`, HMAC over the host) and sends the browser back.
Anything else (API style, non-GET) gets `401 {"error":"this app is waiting for its first setup"}`.
- **Open** = the gate's router is removed. The app's own router is then the only one — exactly the never-gated state.
## Answers
**1. Can the gate see the dashboard session on the app's own address? — No, and it does not need to.**
The dashboard cookie `felhom_session` is set with no `Domain` (`auth.go:207-215`), so it is **host-only**:
`felhom.<domain>` only. Measured: after the household flow the browser holds `felhom_session` for `felhom.<domain>` only,
and a `felhom_gate` for each app host (`B2-…txt`, "dashboard cookie on an app host? False"). The smallest honest route is
the **redirect handshake** above — the dashboard cookie is never widened. Widening it (`Domain=<domain>`) would have
sent the household's full dashboard session to **every app's backend on every request** — third-party software that
logs and handles headers as it likes. Not done, not needed.
**2. Browser, logged in / not logged in.**
- Household, logged in to the dashboard: `302 app → 302 felhom/__gate/start → 302 app/__felhom_gate/cb → 200 app`,
**0.21–0.22 s**, no extra click. n8n's page (52 kB) and immich's (10.7 kB) load (`B2d`).
- Household on a phone, not logged in: the gate page → its sign-in link → the dashboard login → straight back to the
app (`B2e`: `302 /login → 302 /__gate/start → 302 cb → 200 app`). One sign-in, nothing else.
- Stranger, browser: the gate page, 200, both sentences (checked with an English fragment and the ASCII fragment
`Felhom vez`); **no app HTML** (`B2a`). Stranger, API style: `401` on `GET /rest/settings`, `POST /rest/owner/setup`,
`GET /api/server/config`, `POST /api/auth/admin-sign-up` (`B2b`). A forged token → 403; `rd=https://evil.example/` →
400; n8n's gate cookie presented to immich → 401 (`B2c`, `B2f`).
- **During the install** (`B1-stranger-during-install.log`): a stranger polled both hosts every second, API- and
browser-style, from before the deploy until after the setup. 06:11:53–06:16:47: **~530 polls, 0 app answers** — each
host went straight from traefik's `404` (app not yet up) to the gate's `401/302`. This held because the gate's file
was written **before** the app existed: traefik logged `the service "n8n@docker" does not exist` and enabled the
router the moment the app's service appeared. **A build must write the gate before the app's first start** (F2).
**3. "Setup done" — how the box knows.** Measured on both apps, read-only, from the docker network (not through the gate):
| app | probe | before setup | after the household's setup |
|---|---|---|---|
| n8n | `GET /rest/settings` → `data.userManagement.showSetupOnFirstLoad` | `True` | `False` |
| immich | `GET /api/server/config` → `isInitialized` | `False` | `True` |
The household's setup went **through the gate** (`B3-setup.txt`: n8n owner setup 200, immich admin sign-up 201).
The 34 class-4 apps — **M** measured here; **U** upstream, read or remembered, NOT measured; "–" none known:
| probe | apps |
|---|---|
| **M** | n8n, immich |
| **U** — a read-only status field exists upstream | actualbudget (`/account/needs-bootstrap`), audiobookshelf (`/status` `isInit`), emby + jellyfin (`/System/Info/Public` `StartupWizardCompleted`), ghost (`/ghost/api/admin/authentication/setup/`), home-assistant (`/api/onboarding`), komga (`/api/v1/claim` `isClaimed`), romm (`/api/heartbeat` setup-wizard flag), seerr (`/api/v1/settings/public` `initialized`), zipline (`/api/setup` `firstSetup`), navidrome (`firstTime` in the served app config), gitea (install lock) |
| – none known: the household's button | adventurelog, calcom (its measured signal is a POST, not a read), docmost, gramps-web, homebox, opengist, outline, papra, plant-it, radarr, rallly, recipe-importer (ours — can gain one), sonarr, sparkyfitness, tandoor, termix, uptime-kuma (socket.io only), vikunja, wanderer, wishlist |
**14 of 34** have a probe (2 measured, 12 upstream). **20 need the button.** The brief's "most class-4 apps expose a
setup-done status" is wrong.
**4. After the gate opens.** Opening = removing the gate's router (`B4-open.txt`): the app answered a cookie-less
request **0.2 s** later; the gate answerer was then **stopped**, and the app kept answering; immich's container labels
carry no middleware (the gate never touched them). immich's **phone-app flow** (no browser cookie, Bearer token):
`POST /api/auth/login` 201, `GET /api/users/me`, `/api/server/ping`, `/api/server/version` all 200. n8n's login without a
browser cookie: 200 (`B4-open-after.txt`). While gated, the same calls got `401` from the gate (`B4-gated.txt`).
Nothing of the gate remains in the request path.
**5. Cost.**
- Latency while gated: household `GET /api/server/ping` ×40, median **52.2 ms** gated vs **50.1 ms** open (new TLS
connection each) — **~2 ms**, from a python prototype.
- The answerer down (models "the controller is down"): gated apps answer **500, empty** (`B5`) — closed, not open.
Acceptable only because a gated app is not in use yet; an opened app does not depend on the controller at all.
- A household that installs immich and opens the **phone app first**: the phone app gets 401 and shows an error until
the web setup is done. immich needs the web setup for its first admin anyway; the app page must say "finish the
setup in the browser first".
- An app with **no probe** stays gated until the household presses the button: family members without the dashboard
password, and phone apps, cannot reach it until then. The page must say so.
**6. Exit test.**
| condition | held? | evidence |
|---|---|---|
| a stranger never reaches the first-setup screen | **yes** — browser and API, during install and after, both apps | B1, B2a–c |
| the household reaches it with no extra step beyond being logged in | **yes** — 3 redirects, 0.2 s; one sign-in when not logged in | B2d, B2e |
| the gate opens by itself (probe) or by one press (button) | **yes, in principle** — both probes flip on the setup; the open itself was done by hand in the spike (the controller's polling is the build) | B3, B4-open |
| after opening, the app and its phone app work unchanged | **yes** | B4-open-after |
| nothing widens who can reach anything else | **yes** — dashboard cookie stays host-only; gate cookie bound to one app host; the one new path on the dashboard host (`/__gate/start`) refuses non-gated targets | B2c, B2d, B2f |
**Verdict: PASS.** Part C may be built.
## Findings for the build (and the record)
- **F1 — no architecture document says who may reach an app and through what.** `01` §5 has one row ("end-user ↔
apps: Tunnel → Traefik (Host routing)"); `04` covers the control plane only. The gate adds the controller in front of
a not-yet-set-up app — the design record belongs in `01` §5 (Part C4).
- **F2 — order matters.** The gate must exist before the app's first start. Written first, traefik holds the router
and enables it with the app's service — no window (measured). Written after, the app is open until it lands.
- **F3 — the gate answers "who becomes the admin", not "who may sign up".** About a dozen class-4 apps keep **open
registration** after setup (adventurelog, homebox, papra, plant-it, sparkyfitness, vikunja, wanderer, rallly, opengist,
wishlist, termix, docmost — READ from `FIRST-ADMIN.md`, not measured). Once the gate opens, a stranger can still make an ordinary account there. That is a
separate risk (route (a): disable sign-up after the first user) — recorded as a row, not solved by the gate.
- **F4 — a second host.** `wanderer` publishes its database admin on a second subdomain (`SUBDOMAIN_DB`); a gate must
cover every `Host(…)` an app's labels publish, and a path-scoped router (adventurelog's backend router) — hence the
explicit high priority.
- **F5 — the dashboard's sessions live in memory** (`auth.go`, `s.sessions`): a controller restart signs everyone out.
The gate cookie's key must be persisted, or a restart also re-gates every browser (harmless — one more sign-in).
@@ -0,0 +1,659 @@
# stranger.sh output, 06:11:53-06:19Z: per line time, host, API-style answer, browser-style answer. Gate opened 06:16:47.
06:11:53 g-n8n api=[404 page not found|404] browser=404
06:11:53 g-immich api=[404 page not found|404] browser=404
06:11:54 g-n8n api=[404 page not found|404] browser=404
06:11:54 g-immich api=[404 page not found|404] browser=404
06:11:55 g-n8n api=[404 page not found|404] browser=404
06:11:55 g-immich api=[404 page not found|404] browser=404
06:11:56 g-n8n api=[404 page not found|404] browser=404
06:11:56 g-immich api=[404 page not found|404] browser=404
06:11:57 g-n8n api=[404 page not found|404] browser=404
06:11:57 g-immich api=[404 page not found|404] browser=404
06:11:58 g-n8n api=[404 page not found|404] browser=404
06:11:58 g-immich api=[404 page not found|404] browser=404
06:11:59 g-n8n api=[404 page not found|404] browser=404
06:12:00 g-immich api=[404 page not found|404] browser=404
06:12:01 g-n8n api=[404 page not found|404] browser=404
06:12:01 g-immich api=[404 page not found|404] browser=404
06:12:02 g-n8n api=[404 page not found|404] browser=404
06:12:02 g-immich api=[404 page not found|404] browser=404
06:12:03 g-n8n api=[404 page not found|404] browser=404
06:12:03 g-immich api=[404 page not found|404] browser=404
06:12:04 g-n8n api=[404 page not found|404] browser=404
06:12:04 g-immich api=[404 page not found|404] browser=404
06:12:05 g-n8n api=[404 page not found|404] browser=404
06:12:05 g-immich api=[404 page not found|404] browser=404
06:12:06 g-n8n api=[404 page not found|404] browser=404
06:12:06 g-immich api=[404 page not found|404] browser=404
06:12:07 g-n8n api=[404 page not found|404] browser=404
06:12:07 g-immich api=[404 page not found|404] browser=404
06:12:08 g-n8n api=[404 page not found|404] browser=404
06:12:08 g-immich api=[404 page not found|404] browser=404
06:12:09 g-n8n api=[404 page not found|404] browser=404
06:12:09 g-immich api=[404 page not found|404] browser=404
06:12:10 g-n8n api=[404 page not found|404] browser=404
06:12:11 g-immich api=[404 page not found|404] browser=404
06:12:12 g-n8n api=[404 page not found|404] browser=404
06:12:12 g-immich api=[404 page not found|404] browser=404
06:12:13 g-n8n api=[{"error":"this app is waiting for its first setup"}|401] browser=302
06:12:13 g-immich api=[404 page not found|404] browser=404
06:12:14 g-n8n api=[{"error":"this app is waiting for its first setup"}|401] browser=302
06:12:14 g-immich api=[404 page not found|404] browser=404
06:12:15 g-n8n api=[{"error":"this app is waiting for its first setup"}|401] browser=302
06:12:15 g-immich api=[404 page not found|404] browser=404
06:12:16 g-n8n api=[{"error":"this app is waiting for its first setup"}|401] browser=302
06:12:16 g-immich api=[404 page not found|404] browser=404
06:12:17 g-n8n api=[{"error":"this app is waiting for its first setup"}|401] browser=302
06:12:17 g-immich api=[404 page not found|404] browser=404
06:12:18 g-n8n api=[{"error":"this app is waiting for its first setup"}|401] browser=302
06:12:18 g-immich api=[404 page not found|404] browser=404
06:12:19 g-n8n api=[{"error":"this app is waiting for its first setup"}|401] browser=302
06:12:19 g-immich api=[404 page not found|404] browser=404
06:12:20 g-n8n api=[{"error":"this app is waiting for its first setup"}|401] browser=302
06:12:20 g-immich api=[404 page not found|404] browser=404
06:12:22 g-n8n api=[{"error":"this app is waiting for its first setup"}|401] browser=302
06:12:22 g-immich api=[404 page not found|404] browser=404
06:12:23 g-n8n api=[{"error":"this app is waiting for its first setup"}|401] browser=302
06:12:23 g-immich api=[404 page not found|404] browser=404
06:12:24 g-n8n api=[{"error":"this app is waiting for its first setup"}|401] browser=302
06:12:24 g-immich api=[404 page not found|404] browser=404
06:12:25 g-n8n api=[{"error":"this app is waiting for its first setup"}|401] browser=302
06:12:25 g-immich api=[404 page not found|404] browser=404
06:12:26 g-n8n api=[{"error":"this app is waiting for its first setup"}|401] browser=302
06:12:26 g-immich api=[404 page not found|404] browser=404
06:12:27 g-n8n api=[{"error":"this app is waiting for its first setup"}|401] browser=302
06:12:27 g-immich api=[404 page not found|404] browser=404
06:12:28 g-n8n api=[{"error":"this app is waiting for its first setup"}|401] browser=302
06:12:28 g-immich api=[404 page not found|404] browser=404
06:12:29 g-n8n api=[{"error":"this app is waiting for its first setup"}|401] browser=302
06:12:29 g-immich api=[404 page not found|404] browser=404
06:12:30 g-n8n api=[{"error":"this app is waiting for its first setup"}|401] browser=302
06:12:30 g-immich api=[404 page not found|404] browser=404
06:12:32 g-n8n api=[{"error":"this app is waiting for its first setup"}|401] browser=302
06:12:32 g-immich api=[404 page not found|404] browser=404
06:12:33 g-n8n api=[{"error":"this app is waiting for its first setup"}|401] browser=302
06:12:33 g-immich api=[404 page not found|404] browser=404
06:12:34 g-n8n api=[{"error":"this app is waiting for its first setup"}|401] browser=302
06:12:34 g-immich api=[404 page not found|404] browser=404
06:12:35 g-n8n api=[{"error":"this app is waiting for its first setup"}|401] browser=302
06:12:35 g-immich api=[404 page not found|404] browser=404
06:12:36 g-n8n api=[{"error":"this app is waiting for its first setup"}|401] browser=302
06:12:36 g-immich api=[404 page not found|404] browser=404
06:12:37 g-n8n api=[{"error":"this app is waiting for its first setup"}|401] browser=302
06:12:37 g-immich api=[404 page not found|404] browser=404
06:12:38 g-n8n api=[{"error":"this app is waiting for its first setup"}|401] browser=302
06:12:38 g-immich api=[404 page not found|404] browser=404
06:12:39 g-n8n api=[{"error":"this app is waiting for its first setup"}|401] browser=302
06:12:39 g-immich api=[404 page not found|404] browser=404
06:12:40 g-n8n api=[{"error":"this app is waiting for its first setup"}|401] browser=302
06:12:40 g-immich api=[404 page not found|404] browser=404
06:12:41 g-n8n api=[{"error":"this app is waiting for its first setup"}|401] browser=302
06:12:41 g-immich api=[404 page not found|404] browser=404
06:12:43 g-n8n api=[{"error":"this app is waiting for its first setup"}|401] browser=302
06:12:43 g-immich api=[404 page not found|404] browser=404
06:12:44 g-n8n api=[{"error":"this app is waiting for its first setup"}|401] browser=302
06:12:44 g-immich api=[404 page not found|404] browser=404
06:12:45 g-n8n api=[{"error":"this app is waiting for its first setup"}|401] browser=302
06:12:45 g-immich api=[404 page not found|404] browser=404
06:12:46 g-n8n api=[{"error":"this app is waiting for its first setup"}|401] browser=302
06:12:46 g-immich api=[404 page not found|404] browser=404
06:12:47 g-n8n api=[{"error":"this app is waiting for its first setup"}|401] browser=302
06:12:47 g-immich api=[404 page not found|404] browser=404
06:12:48 g-n8n api=[{"error":"this app is waiting for its first setup"}|401] browser=302
06:12:48 g-immich api=[404 page not found|404] browser=404
06:12:49 g-n8n api=[{"error":"this app is waiting for its first setup"}|401] browser=302
06:12:49 g-immich api=[404 page not found|404] browser=404
06:12:50 g-n8n api=[{"error":"this app is waiting for its first setup"}|401] browser=302
06:12:50 g-immich api=[404 page not found|404] browser=404
06:12:51 g-n8n api=[{"error":"this app is waiting for its first setup"}|401] browser=302
06:12:51 g-immich api=[404 page not found|404] browser=404
06:12:52 g-n8n api=[{"error":"this app is waiting for its first setup"}|401] browser=302
06:12:53 g-immich api=[404 page not found|404] browser=404
06:12:54 g-n8n api=[{"error":"this app is waiting for its first setup"}|401] browser=302
06:12:54 g-immich api=[404 page not found|404] browser=404
06:12:55 g-n8n api=[{"error":"this app is waiting for its first setup"}|401] browser=302
06:12:55 g-immich api=[404 page not found|404] browser=404
06:12:56 g-n8n api=[{"error":"this app is waiting for its first setup"}|401] browser=302
06:12:56 g-immich api=[404 page not found|404] browser=404
06:12:57 g-n8n api=[{"error":"this app is waiting for its first setup"}|401] browser=302
06:12:57 g-immich api=[404 page not found|404] browser=404
06:12:58 g-n8n api=[{"error":"this app is waiting for its first setup"}|401] browser=302
06:12:58 g-immich api=[404 page not found|404] browser=404
06:12:59 g-n8n api=[{"error":"this app is waiting for its first setup"}|401] browser=302
06:12:59 g-immich api=[404 page not found|404] browser=404
06:13:00 g-n8n api=[{"error":"this app is waiting for its first setup"}|401] browser=302
06:13:00 g-immich api=[404 page not found|404] browser=404
06:13:01 g-n8n api=[{"error":"this app is waiting for its first setup"}|401] browser=302
06:13:01 g-immich api=[404 page not found|404] browser=404
06:13:02 g-n8n api=[{"error":"this app is waiting for its first setup"}|401] browser=302
06:13:02 g-immich api=[404 page not found|404] browser=404
06:13:04 g-n8n api=[{"error":"this app is waiting for its first setup"}|401] browser=302
06:13:04 g-immich api=[404 page not found|404] browser=404
06:13:05 g-n8n api=[{"error":"this app is waiting for its first setup"}|401] browser=302
06:13:05 g-immich api=[404 page not found|404] browser=404
06:13:06 g-n8n api=[{"error":"this app is waiting for its first setup"}|401] browser=302
06:13:06 g-immich api=[404 page not found|404] browser=404
06:13:07 g-n8n api=[{"error":"this app is waiting for its first setup"}|401] browser=302
06:13:07 g-immich api=[404 page not found|404] browser=404
06:13:08 g-n8n api=[{"error":"this app is waiting for its first setup"}|401] browser=302
06:13:08 g-immich api=[404 page not found|404] browser=404
06:13:09 g-n8n api=[{"error":"this app is waiting for its first setup"}|401] browser=302
06:13:09 g-immich api=[404 page not found|404] browser=404
06:13:10 g-n8n api=[{"error":"this app is waiting for its first setup"}|401] browser=302
06:13:10 g-immich api=[404 page not found|404] browser=404
06:13:11 g-n8n api=[{"error":"this app is waiting for its first setup"}|401] browser=302
06:13:11 g-immich api=[404 page not found|404] browser=404
06:13:12 g-n8n api=[{"error":"this app is waiting for its first setup"}|401] browser=302
06:13:12 g-immich api=[404 page not found|404] browser=404
06:13:13 g-n8n api=[{"error":"this app is waiting for its first setup"}|401] browser=302
06:13:14 g-immich api=[404 page not found|404] browser=404
06:13:15 g-n8n api=[{"error":"this app is waiting for its first setup"}|401] browser=302
06:13:15 g-immich api=[404 page not found|404] browser=404
06:13:16 g-n8n api=[{"error":"this app is waiting for its first setup"}|401] browser=302
06:13:16 g-immich api=[404 page not found|404] browser=404
06:13:17 g-n8n api=[{"error":"this app is waiting for its first setup"}|401] browser=302
06:13:17 g-immich api=[404 page not found|404] browser=404
06:13:18 g-n8n api=[{"error":"this app is waiting for its first setup"}|401] browser=302
06:13:18 g-immich api=[404 page not found|404] browser=404
06:13:19 g-n8n api=[{"error":"this app is waiting for its first setup"}|401] browser=302
06:13:19 g-immich api=[404 page not found|404] browser=404
06:13:20 g-n8n api=[{"error":"this app is waiting for its first setup"}|401] browser=302
06:13:20 g-immich api=[404 page not found|404] browser=404
06:13:21 g-n8n api=[{"error":"this app is waiting for its first setup"}|401] browser=302
06:13:21 g-immich api=[404 page not found|404] browser=404
06:13:22 g-n8n api=[{"error":"this app is waiting for its first setup"}|401] browser=302
06:13:22 g-immich api=[404 page not found|404] browser=404
06:13:23 g-n8n api=[{"error":"this app is waiting for its first setup"}|401] browser=302
06:13:23 g-immich api=[404 page not found|404] browser=404
06:13:25 g-n8n api=[{"error":"this app is waiting for its first setup"}|401] browser=302
06:13:25 g-immich api=[404 page not found|404] browser=404
06:13:26 g-n8n api=[{"error":"this app is waiting for its first setup"}|401] browser=302
06:13:26 g-immich api=[404 page not found|404] browser=404
06:13:27 g-n8n api=[{"error":"this app is waiting for its first setup"}|401] browser=302
06:13:27 g-immich api=[404 page not found|404] browser=404
06:13:28 g-n8n api=[{"error":"this app is waiting for its first setup"}|401] browser=302
06:13:28 g-immich api=[404 page not found|404] browser=404
06:13:29 g-n8n api=[{"error":"this app is waiting for its first setup"}|401] browser=302
06:13:29 g-immich api=[404 page not found|404] browser=404
06:13:30 g-n8n api=[{"error":"this app is waiting for its first setup"}|401] browser=302
06:13:30 g-immich api=[404 page not found|404] browser=404
06:13:31 g-n8n api=[{"error":"this app is waiting for its first setup"}|401] browser=302
06:13:31 g-immich api=[404 page not found|404] browser=404
06:13:32 g-n8n api=[{"error":"this app is waiting for its first setup"}|401] browser=302
06:13:32 g-immich api=[404 page not found|404] browser=404
06:13:33 g-n8n api=[{"error":"this app is waiting for its first setup"}|401] browser=302
06:13:33 g-immich api=[404 page not found|404] browser=404
06:13:34 g-n8n api=[{"error":"this app is waiting for its first setup"}|401] browser=302
06:13:35 g-immich api=[404 page not found|404] browser=404
06:13:36 g-n8n api=[{"error":"this app is waiting for its first setup"}|401] browser=302
06:13:36 g-immich api=[404 page not found|404] browser=404
06:13:37 g-n8n api=[{"error":"this app is waiting for its first setup"}|401] browser=302
06:13:37 g-immich api=[404 page not found|404] browser=404
06:13:38 g-n8n api=[{"error":"this app is waiting for its first setup"}|401] browser=302
06:13:38 g-immich api=[404 page not found|404] browser=404
06:13:39 g-n8n api=[{"error":"this app is waiting for its first setup"}|401] browser=302
06:13:39 g-immich api=[404 page not found|404] browser=404
06:13:40 g-n8n api=[{"error":"this app is waiting for its first setup"}|401] browser=302
06:13:40 g-immich api=[404 page not found|404] browser=404
06:13:41 g-n8n api=[{"error":"this app is waiting for its first setup"}|401] browser=302
06:13:41 g-immich api=[404 page not found|404] browser=404
06:13:42 g-n8n api=[{"error":"this app is waiting for its first setup"}|401] browser=302
06:13:42 g-immich api=[404 page not found|404] browser=404
06:13:43 g-n8n api=[{"error":"this app is waiting for its first setup"}|401] browser=302
06:13:43 g-immich api=[404 page not found|404] browser=404
06:13:44 g-n8n api=[{"error":"this app is waiting for its first setup"}|401] browser=302
06:13:44 g-immich api=[404 page not found|404] browser=404
06:13:46 g-n8n api=[{"error":"this app is waiting for its first setup"}|401] browser=302
06:13:46 g-immich api=[404 page not found|404] browser=404
06:13:47 g-n8n api=[{"error":"this app is waiting for its first setup"}|401] browser=302
06:13:47 g-immich api=[404 page not found|404] browser=404
06:13:48 g-n8n api=[{"error":"this app is waiting for its first setup"}|401] browser=302
06:13:48 g-immich api=[404 page not found|404] browser=404
06:13:49 g-n8n api=[{"error":"this app is waiting for its first setup"}|401] browser=302
06:13:49 g-immich api=[404 page not found|404] browser=404
06:13:50 g-n8n api=[{"error":"this app is waiting for its first setup"}|401] browser=302
06:13:50 g-immich api=[404 page not found|404] browser=404
06:13:51 g-n8n api=[{"error":"this app is waiting for its first setup"}|401] browser=302
06:13:51 g-immich api=[404 page not found|404] browser=404
06:13:52 g-n8n api=[{"error":"this app is waiting for its first setup"}|401] browser=302
06:13:52 g-immich api=[404 page not found|404] browser=404
06:13:53 g-n8n api=[{"error":"this app is waiting for its first setup"}|401] browser=302
06:13:53 g-immich api=[404 page not found|404] browser=404
06:13:54 g-n8n api=[{"error":"this app is waiting for its first setup"}|401] browser=302
06:13:54 g-immich api=[404 page not found|404] browser=404
06:13:55 g-n8n api=[{"error":"this app is waiting for its first setup"}|401] browser=302
06:13:55 g-immich api=[404 page not found|404] browser=404
06:13:57 g-n8n api=[{"error":"this app is waiting for its first setup"}|401] browser=302
06:13:57 g-immich api=[404 page not found|404] browser=404
06:13:58 g-n8n api=[{"error":"this app is waiting for its first setup"}|401] browser=302
06:13:58 g-immich api=[404 page not found|404] browser=404
06:13:59 g-n8n api=[{"error":"this app is waiting for its first setup"}|401] browser=302
06:13:59 g-immich api=[404 page not found|404] browser=404
06:14:00 g-n8n api=[{"error":"this app is waiting for its first setup"}|401] browser=302
06:14:00 g-immich api=[404 page not found|404] browser=404
06:14:01 g-n8n api=[{"error":"this app is waiting for its first setup"}|401] browser=302
06:14:01 g-immich api=[404 page not found|404] browser=404
06:14:02 g-n8n api=[{"error":"this app is waiting for its first setup"}|401] browser=302
06:14:02 g-immich api=[404 page not found|404] browser=404
06:14:03 g-n8n api=[{"error":"this app is waiting for its first setup"}|401] browser=302
06:14:03 g-immich api=[404 page not found|404] browser=404
06:14:04 g-n8n api=[{"error":"this app is waiting for its first setup"}|401] browser=302
06:14:04 g-immich api=[404 page not found|404] browser=404
06:14:05 g-n8n api=[{"error":"this app is waiting for its first setup"}|401] browser=302
06:14:05 g-immich api=[404 page not found|404] browser=404
06:14:06 g-n8n api=[{"error":"this app is waiting for its first setup"}|401] browser=302
06:14:07 g-immich api=[404 page not found|404] browser=404
06:14:08 g-n8n api=[{"error":"this app is waiting for its first setup"}|401] browser=302
06:14:08 g-immich api=[404 page not found|404] browser=404
06:14:09 g-n8n api=[{"error":"this app is waiting for its first setup"}|401] browser=302
06:14:09 g-immich api=[404 page not found|404] browser=404
06:14:10 g-n8n api=[{"error":"this app is waiting for its first setup"}|401] browser=302
06:14:10 g-immich api=[404 page not found|404] browser=404
06:14:11 g-n8n api=[{"error":"this app is waiting for its first setup"}|401] browser=302
06:14:11 g-immich api=[404 page not found|404] browser=404
06:14:12 g-n8n api=[{"error":"this app is waiting for its first setup"}|401] browser=302
06:14:12 g-immich api=[{"error":"this app is waiting for its first setup"}|401] browser=302
06:14:13 g-n8n api=[{"error":"this app is waiting for its first setup"}|401] browser=302
06:14:13 g-immich api=[{"error":"this app is waiting for its first setup"}|401] browser=302
06:14:14 g-n8n api=[{"error":"this app is waiting for its first setup"}|401] browser=302
06:14:14 g-immich api=[{"error":"this app is waiting for its first setup"}|401] browser=302
06:14:15 g-n8n api=[{"error":"this app is waiting for its first setup"}|401] browser=302
06:14:15 g-immich api=[{"error":"this app is waiting for its first setup"}|401] browser=302
06:14:16 g-n8n api=[{"error":"this app is waiting for its first setup"}|401] browser=302
06:14:17 g-immich api=[{"error":"this app is waiting for its first setup"}|401] browser=302
06:14:18 g-n8n api=[{"error":"this app is waiting for its first setup"}|401] browser=302
06:14:18 g-immich api=[{"error":"this app is waiting for its first setup"}|401] browser=302
06:14:19 g-n8n api=[{"error":"this app is waiting for its first setup"}|401] browser=302
06:14:19 g-immich api=[{"error":"this app is waiting for its first setup"}|401] browser=302
06:14:20 g-n8n api=[{"error":"this app is waiting for its first setup"}|401] browser=302
06:14:20 g-immich api=[{"error":"this app is waiting for its first setup"}|401] browser=302
06:14:21 g-n8n api=[{"error":"this app is waiting for its first setup"}|401] browser=302
06:14:21 g-immich api=[{"error":"this app is waiting for its first setup"}|401] browser=302
06:14:22 g-n8n api=[{"error":"this app is waiting for its first setup"}|401] browser=302
06:14:22 g-immich api=[{"error":"this app is waiting for its first setup"}|401] browser=302
06:14:23 g-n8n api=[{"error":"this app is waiting for its first setup"}|401] browser=302
06:14:23 g-immich api=[{"error":"this app is waiting for its first setup"}|401] browser=302
06:14:24 g-n8n api=[{"error":"this app is waiting for its first setup"}|401] browser=302
06:14:24 g-immich api=[{"error":"this app is waiting for its first setup"}|401] browser=302
06:14:25 g-n8n api=[{"error":"this app is waiting for its first setup"}|401] browser=302
06:14:25 g-immich api=[{"error":"this app is waiting for its first setup"}|401] browser=302
06:14:26 g-n8n api=[{"error":"this app is waiting for its first setup"}|401] browser=302
06:14:27 g-immich api=[{"error":"this app is waiting for its first setup"}|401] browser=302
06:14:28 g-n8n api=[{"error":"this app is waiting for its first setup"}|401] browser=302
06:14:28 g-immich api=[{"error":"this app is waiting for its first setup"}|401] browser=302
06:14:29 g-n8n api=[{"error":"this app is waiting for its first setup"}|401] browser=302
06:14:29 g-immich api=[{"error":"this app is waiting for its first setup"}|401] browser=302
06:14:30 g-n8n api=[{"error":"this app is waiting for its first setup"}|401] browser=302
06:14:30 g-immich api=[{"error":"this app is waiting for its first setup"}|401] browser=302
06:14:31 g-n8n api=[{"error":"this app is waiting for its first setup"}|401] browser=302
06:14:31 g-immich api=[{"error":"this app is waiting for its first setup"}|401] browser=302
06:14:32 g-n8n api=[{"error":"this app is waiting for its first setup"}|401] browser=302
06:14:32 g-immich api=[{"error":"this app is waiting for its first setup"}|401] browser=302
06:14:33 g-n8n api=[{"error":"this app is waiting for its first setup"}|401] browser=302
06:14:33 g-immich api=[{"error":"this app is waiting for its first setup"}|401] browser=302
06:14:34 g-n8n api=[{"error":"this app is waiting for its first setup"}|401] browser=302
06:14:34 g-immich api=[{"error":"this app is waiting for its first setup"}|401] browser=302
06:14:35 g-n8n api=[{"error":"this app is waiting for its first setup"}|401] browser=302
06:14:35 g-immich api=[{"error":"this app is waiting for its first setup"}|401] browser=302
06:14:36 g-n8n api=[{"error":"this app is waiting for its first setup"}|401] browser=302
06:14:37 g-immich api=[{"error":"this app is waiting for its first setup"}|401] browser=302
06:14:38 g-n8n api=[{"error":"this app is waiting for its first setup"}|401] browser=302
06:14:38 g-immich api=[{"error":"this app is waiting for its first setup"}|401] browser=302
06:14:39 g-n8n api=[{"error":"this app is waiting for its first setup"}|401] browser=302
06:14:39 g-immich api=[{"error":"this app is waiting for its first setup"}|401] browser=302
06:14:40 g-n8n api=[{"error":"this app is waiting for its first setup"}|401] browser=302
06:14:40 g-immich api=[{"error":"this app is waiting for its first setup"}|401] browser=302
06:14:41 g-n8n api=[{"error":"this app is waiting for its first setup"}|401] browser=302
06:14:41 g-immich api=[{"error":"this app is waiting for its first setup"}|401] browser=302
06:14:42 g-n8n api=[{"error":"this app is waiting for its first setup"}|401] browser=302
06:14:42 g-immich api=[{"error":"this app is waiting for its first setup"}|401] browser=302
06:14:43 g-n8n api=[{"error":"this app is waiting for its first setup"}|401] browser=302
06:14:43 g-immich api=[{"error":"this app is waiting for its first setup"}|401] browser=302
06:14:44 g-n8n api=[{"error":"this app is waiting for its first setup"}|401] browser=302
06:14:44 g-immich api=[{"error":"this app is waiting for its first setup"}|401] browser=302
06:14:45 g-n8n api=[{"error":"this app is waiting for its first setup"}|401] browser=302
06:14:45 g-immich api=[{"error":"this app is waiting for its first setup"}|401] browser=302
06:14:46 g-n8n api=[{"error":"this app is waiting for its first setup"}|401] browser=302
06:14:47 g-immich api=[{"error":"this app is waiting for its first setup"}|401] browser=302
06:14:48 g-n8n api=[{"error":"this app is waiting for its first setup"}|401] browser=302
06:14:48 g-immich api=[{"error":"this app is waiting for its first setup"}|401] browser=302
06:14:49 g-n8n api=[{"error":"this app is waiting for its first setup"}|401] browser=302
06:14:49 g-immich api=[{"error":"this app is waiting for its first setup"}|401] browser=302
06:14:50 g-n8n api=[{"error":"this app is waiting for its first setup"}|401] browser=302
06:14:50 g-immich api=[{"error":"this app is waiting for its first setup"}|401] browser=302
06:14:51 g-n8n api=[{"error":"this app is waiting for its first setup"}|401] browser=302
06:14:51 g-immich api=[{"error":"this app is waiting for its first setup"}|401] browser=302
06:14:52 g-n8n api=[{"error":"this app is waiting for its first setup"}|401] browser=302
06:14:52 g-immich api=[{"error":"this app is waiting for its first setup"}|401] browser=302
06:14:53 g-n8n api=[{"error":"this app is waiting for its first setup"}|401] browser=302
06:14:53 g-immich api=[{"error":"this app is waiting for its first setup"}|401] browser=302
06:14:54 g-n8n api=[{"error":"this app is waiting for its first setup"}|401] browser=302
06:14:54 g-immich api=[{"error":"this app is waiting for its first setup"}|401] browser=302
06:14:55 g-n8n api=[{"error":"this app is waiting for its first setup"}|401] browser=302
06:14:55 g-immich api=[{"error":"this app is waiting for its first setup"}|401] browser=302
06:14:56 g-n8n api=[{"error":"this app is waiting for its first setup"}|401] browser=302
06:14:56 g-immich api=[{"error":"this app is waiting for its first setup"}|401] browser=302
06:14:58 g-n8n api=[{"error":"this app is waiting for its first setup"}|401] browser=302
06:14:58 g-immich api=[{"error":"this app is waiting for its first setup"}|401] browser=302
06:14:59 g-n8n api=[{"error":"this app is waiting for its first setup"}|401] browser=302
06:14:59 g-immich api=[{"error":"this app is waiting for its first setup"}|401] browser=302
06:15:00 g-n8n api=[{"error":"this app is waiting for its first setup"}|401] browser=302
06:15:00 g-immich api=[{"error":"this app is waiting for its first setup"}|401] browser=302
06:15:01 g-n8n api=[{"error":"this app is waiting for its first setup"}|401] browser=302
06:15:01 g-immich api=[{"error":"this app is waiting for its first setup"}|401] browser=302
06:15:02 g-n8n api=[{"error":"this app is waiting for its first setup"}|401] browser=302
06:15:02 g-immich api=[{"error":"this app is waiting for its first setup"}|401] browser=302
06:15:03 g-n8n api=[{"error":"this app is waiting for its first setup"}|401] browser=302
06:15:03 g-immich api=[{"error":"this app is waiting for its first setup"}|401] browser=302
06:15:04 g-n8n api=[{"error":"this app is waiting for its first setup"}|401] browser=302
06:15:04 g-immich api=[{"error":"this app is waiting for its first setup"}|401] browser=302
06:15:05 g-n8n api=[{"error":"this app is waiting for its first setup"}|401] browser=302
06:15:05 g-immich api=[{"error":"this app is waiting for its first setup"}|401] browser=302
06:15:06 g-n8n api=[{"error":"this app is waiting for its first setup"}|401] browser=302
06:15:06 g-immich api=[{"error":"this app is waiting for its first setup"}|401] browser=302
06:15:07 g-n8n api=[{"error":"this app is waiting for its first setup"}|401] browser=302
06:15:08 g-immich api=[{"error":"this app is waiting for its first setup"}|401] browser=302
06:15:09 g-n8n api=[{"error":"this app is waiting for its first setup"}|401] browser=302
06:15:09 g-immich api=[{"error":"this app is waiting for its first setup"}|401] browser=302
06:15:10 g-n8n api=[{"error":"this app is waiting for its first setup"}|401] browser=302
06:15:10 g-immich api=[{"error":"this app is waiting for its first setup"}|401] browser=302
06:15:11 g-n8n api=[{"error":"this app is waiting for its first setup"}|401] browser=302
06:15:11 g-immich api=[{"error":"this app is waiting for its first setup"}|401] browser=302
06:15:12 g-n8n api=[{"error":"this app is waiting for its first setup"}|401] browser=302
06:15:12 g-immich api=[{"error":"this app is waiting for its first setup"}|401] browser=302
06:15:13 g-n8n api=[{"error":"this app is waiting for its first setup"}|401] browser=302
06:15:13 g-immich api=[{"error":"this app is waiting for its first setup"}|401] browser=302
06:15:14 g-n8n api=[{"error":"this app is waiting for its first setup"}|401] browser=302
06:15:14 g-immich api=[{"error":"this app is waiting for its first setup"}|401] browser=302
06:15:15 g-n8n api=[{"error":"this app is waiting for its first setup"}|401] browser=302
06:15:15 g-immich api=[{"error":"this app is waiting for its first setup"}|401] browser=302
06:15:16 g-n8n api=[{"error":"this app is waiting for its first setup"}|401] browser=302
06:15:16 g-immich api=[{"error":"this app is waiting for its first setup"}|401] browser=302
06:15:17 g-n8n api=[{"error":"this app is waiting for its first setup"}|401] browser=302
06:15:18 g-immich api=[{"error":"this app is waiting for its first setup"}|401] browser=302
06:15:19 g-n8n api=[{"error":"this app is waiting for its first setup"}|401] browser=302
06:15:19 g-immich api=[{"error":"this app is waiting for its first setup"}|401] browser=302
06:15:20 g-n8n api=[{"error":"this app is waiting for its first setup"}|401] browser=302
06:15:20 g-immich api=[{"error":"this app is waiting for its first setup"}|401] browser=302
06:15:21 g-n8n api=[{"error":"this app is waiting for its first setup"}|401] browser=302
06:15:21 g-immich api=[{"error":"this app is waiting for its first setup"}|401] browser=302
06:15:22 g-n8n api=[{"error":"this app is waiting for its first setup"}|401] browser=302
06:15:22 g-immich api=[{"error":"this app is waiting for its first setup"}|401] browser=302
06:15:23 g-n8n api=[{"error":"this app is waiting for its first setup"}|401] browser=302
06:15:23 g-immich api=[{"error":"this app is waiting for its first setup"}|401] browser=302
06:15:24 g-n8n api=[{"error":"this app is waiting for its first setup"}|401] browser=302
06:15:24 g-immich api=[{"error":"this app is waiting for its first setup"}|401] browser=302
06:15:25 g-n8n api=[{"error":"this app is waiting for its first setup"}|401] browser=302
06:15:25 g-immich api=[{"error":"this app is waiting for its first setup"}|401] browser=302
06:15:26 g-n8n api=[{"error":"this app is waiting for its first setup"}|401] browser=302
06:15:26 g-immich api=[{"error":"this app is waiting for its first setup"}|401] browser=302
06:15:27 g-n8n api=[{"error":"this app is waiting for its first setup"}|401] browser=302
06:15:28 g-immich api=[{"error":"this app is waiting for its first setup"}|401] browser=302
06:15:29 g-n8n api=[{"error":"this app is waiting for its first setup"}|401] browser=302
06:15:29 g-immich api=[{"error":"this app is waiting for its first setup"}|401] browser=302
06:15:30 g-n8n api=[{"error":"this app is waiting for its first setup"}|401] browser=302
06:15:30 g-immich api=[{"error":"this app is waiting for its first setup"}|401] browser=302
06:15:31 g-n8n api=[{"error":"this app is waiting for its first setup"}|401] browser=302
06:15:31 g-immich api=[{"error":"this app is waiting for its first setup"}|401] browser=302
06:15:32 g-n8n api=[{"error":"this app is waiting for its first setup"}|401] browser=302
06:15:32 g-immich api=[{"error":"this app is waiting for its first setup"}|401] browser=302
06:15:33 g-n8n api=[{"error":"this app is waiting for its first setup"}|401] browser=302
06:15:33 g-immich api=[{"error":"this app is waiting for its first setup"}|401] browser=302
06:15:34 g-n8n api=[{"error":"this app is waiting for its first setup"}|401] browser=302
06:15:34 g-immich api=[{"error":"this app is waiting for its first setup"}|401] browser=302
06:15:35 g-n8n api=[{"error":"this app is waiting for its first setup"}|401] browser=302
06:15:35 g-immich api=[{"error":"this app is waiting for its first setup"}|401] browser=302
06:15:36 g-n8n api=[{"error":"this app is waiting for its first setup"}|401] browser=302
06:15:36 g-immich api=[{"error":"this app is waiting for its first setup"}|401] browser=302
06:15:37 g-n8n api=[{"error":"this app is waiting for its first setup"}|401] browser=302
06:15:37 g-immich api=[{"error":"this app is waiting for its first setup"}|401] browser=302
06:15:39 g-n8n api=[{"error":"this app is waiting for its first setup"}|401] browser=302
06:15:39 g-immich api=[{"error":"this app is waiting for its first setup"}|401] browser=302
06:15:40 g-n8n api=[{"error":"this app is waiting for its first setup"}|401] browser=302
06:15:40 g-immich api=[{"error":"this app is waiting for its first setup"}|401] browser=302
06:15:41 g-n8n api=[{"error":"this app is waiting for its first setup"}|401] browser=302
06:15:41 g-immich api=[{"error":"this app is waiting for its first setup"}|401] browser=302
06:15:42 g-n8n api=[{"error":"this app is waiting for its first setup"}|401] browser=302
06:15:42 g-immich api=[{"error":"this app is waiting for its first setup"}|401] browser=302
06:15:43 g-n8n api=[{"error":"this app is waiting for its first setup"}|401] browser=302
06:15:43 g-immich api=[{"error":"this app is waiting for its first setup"}|401] browser=302
06:15:44 g-n8n api=[{"error":"this app is waiting for its first setup"}|401] browser=302
06:15:44 g-immich api=[{"error":"this app is waiting for its first setup"}|401] browser=302
06:15:45 g-n8n api=[{"error":"this app is waiting for its first setup"}|401] browser=302
06:15:45 g-immich api=[{"error":"this app is waiting for its first setup"}|401] browser=302
06:15:46 g-n8n api=[{"error":"this app is waiting for its first setup"}|401] browser=302
06:15:46 g-immich api=[{"error":"this app is waiting for its first setup"}|401] browser=302
06:15:47 g-n8n api=[{"error":"this app is waiting for its first setup"}|401] browser=302
06:15:47 g-immich api=[{"error":"this app is waiting for its first setup"}|401] browser=302
06:15:49 g-n8n api=[{"error":"this app is waiting for its first setup"}|401] browser=302
06:15:49 g-immich api=[{"error":"this app is waiting for its first setup"}|401] browser=302
06:15:50 g-n8n api=[{"error":"this app is waiting for its first setup"}|401] browser=302
06:15:50 g-immich api=[{"error":"this app is waiting for its first setup"}|401] browser=302
06:15:51 g-n8n api=[{"error":"this app is waiting for its first setup"}|401] browser=302
06:15:51 g-immich api=[{"error":"this app is waiting for its first setup"}|401] browser=302
06:15:52 g-n8n api=[{"error":"this app is waiting for its first setup"}|401] browser=302
06:15:52 g-immich api=[{"error":"this app is waiting for its first setup"}|401] browser=302
06:15:53 g-n8n api=[{"error":"this app is waiting for its first setup"}|401] browser=302
06:15:53 g-immich api=[{"error":"this app is waiting for its first setup"}|401] browser=302
06:15:54 g-n8n api=[{"error":"this app is waiting for its first setup"}|401] browser=302
06:15:54 g-immich api=[{"error":"this app is waiting for its first setup"}|401] browser=302
06:15:55 g-n8n api=[{"error":"this app is waiting for its first setup"}|401] browser=302
06:15:55 g-immich api=[{"error":"this app is waiting for its first setup"}|401] browser=302
06:15:56 g-n8n api=[{"error":"this app is waiting for its first setup"}|401] browser=302
06:15:56 g-immich api=[{"error":"this app is waiting for its first setup"}|401] browser=302
06:15:57 g-n8n api=[{"error":"this app is waiting for its first setup"}|401] browser=302
06:15:57 g-immich api=[{"error":"this app is waiting for its first setup"}|401] browser=302
06:15:59 g-n8n api=[{"error":"this app is waiting for its first setup"}|401] browser=302
06:15:59 g-immich api=[{"error":"this app is waiting for its first setup"}|401] browser=302
06:16:00 g-n8n api=[{"error":"this app is waiting for its first setup"}|401] browser=302
06:16:00 g-immich api=[{"error":"this app is waiting for its first setup"}|401] browser=302
06:16:01 g-n8n api=[{"error":"this app is waiting for its first setup"}|401] browser=302
06:16:01 g-immich api=[{"error":"this app is waiting for its first setup"}|401] browser=302
06:16:02 g-n8n api=[{"error":"this app is waiting for its first setup"}|401] browser=302
06:16:02 g-immich api=[{"error":"this app is waiting for its first setup"}|401] browser=302
06:16:03 g-n8n api=[{"error":"this app is waiting for its first setup"}|401] browser=302
06:16:03 g-immich api=[{"error":"this app is waiting for its first setup"}|401] browser=302
06:16:04 g-n8n api=[{"error":"this app is waiting for its first setup"}|401] browser=302
06:16:04 g-immich api=[{"error":"this app is waiting for its first setup"}|401] browser=302
06:16:05 g-n8n api=[{"error":"this app is waiting for its first setup"}|401] browser=302
06:16:05 g-immich api=[{"error":"this app is waiting for its first setup"}|401] browser=302
06:16:06 g-n8n api=[{"error":"this app is waiting for its first setup"}|401] browser=302
06:16:06 g-immich api=[{"error":"this app is waiting for its first setup"}|401] browser=302
06:16:07 g-n8n api=[{"error":"this app is waiting for its first setup"}|401] browser=302
06:16:07 g-immich api=[{"error":"this app is waiting for its first setup"}|401] browser=302
06:16:08 g-n8n api=[{"error":"this app is waiting for its first setup"}|401] browser=302
06:16:09 g-immich api=[{"error":"this app is waiting for its first setup"}|401] browser=302
06:16:10 g-n8n api=[{"error":"this app is waiting for its first setup"}|401] browser=302
06:16:10 g-immich api=[{"error":"this app is waiting for its first setup"}|401] browser=302
06:16:11 g-n8n api=[{"error":"this app is waiting for its first setup"}|401] browser=302
06:16:11 g-immich api=[{"error":"this app is waiting for its first setup"}|401] browser=302
06:16:12 g-n8n api=[{"error":"this app is waiting for its first setup"}|401] browser=302
06:16:12 g-immich api=[{"error":"this app is waiting for its first setup"}|401] browser=302
06:16:13 g-n8n api=[{"error":"this app is waiting for its first setup"}|401] browser=302
06:16:13 g-immich api=[{"error":"this app is waiting for its first setup"}|401] browser=302
06:16:14 g-n8n api=[{"error":"this app is waiting for its first setup"}|401] browser=302
06:16:14 g-immich api=[{"error":"this app is waiting for its first setup"}|401] browser=302
06:16:15 g-n8n api=[{"error":"this app is waiting for its first setup"}|401] browser=302
06:16:15 g-immich api=[{"error":"this app is waiting for its first setup"}|401] browser=302
06:16:16 g-n8n api=[{"error":"this app is waiting for its first setup"}|401] browser=302
06:16:16 g-immich api=[{"error":"this app is waiting for its first setup"}|401] browser=302
06:16:17 g-n8n api=[{"error":"this app is waiting for its first setup"}|401] browser=302
06:16:17 g-immich api=[{"error":"this app is waiting for its first setup"}|401] browser=302
06:16:18 g-n8n api=[{"error":"this app is waiting for its first setup"}|401] browser=302
06:16:19 g-immich api=[{"error":"this app is waiting for its first setup"}|401] browser=302
06:16:20 g-n8n api=[{"error":"this app is waiting for its first setup"}|401] browser=302
06:16:20 g-immich api=[{"error":"this app is waiting for its first setup"}|401] browser=302
06:16:21 g-n8n api=[{"error":"this app is waiting for its first setup"}|401] browser=302
06:16:21 g-immich api=[{"error":"this app is waiting for its first setup"}|401] browser=302
06:16:22 g-n8n api=[{"error":"this app is waiting for its first setup"}|401] browser=302
06:16:22 g-immich api=[{"error":"this app is waiting for its first setup"}|401] browser=302
06:16:23 g-n8n api=[{"error":"this app is waiting for its first setup"}|401] browser=302
06:16:23 g-immich api=[{"error":"this app is waiting for its first setup"}|401] browser=302
06:16:24 g-n8n api=[{"error":"this app is waiting for its first setup"}|401] browser=302
06:16:24 g-immich api=[{"error":"this app is waiting for its first setup"}|401] browser=302
06:16:25 g-n8n api=[{"error":"this app is waiting for its first setup"}|401] browser=302
06:16:25 g-immich api=[{"error":"this app is waiting for its first setup"}|401] browser=302
06:16:26 g-n8n api=[{"error":"this app is waiting for its first setup"}|401] browser=302
06:16:26 g-immich api=[{"error":"this app is waiting for its first setup"}|401] browser=302
06:16:27 g-n8n api=[{"error":"this app is waiting for its first setup"}|401] browser=302
06:16:27 g-immich api=[{"error":"this app is waiting for its first setup"}|401] browser=302
06:16:28 g-n8n api=[{"error":"this app is waiting for its first setup"}|401] browser=302
06:16:29 g-immich api=[{"error":"this app is waiting for its first setup"}|401] browser=302
06:16:30 g-n8n api=[{"error":"this app is waiting for its first setup"}|401] browser=302
06:16:30 g-immich api=[{"error":"this app is waiting for its first setup"}|401] browser=302
06:16:31 g-n8n api=[{"error":"this app is waiting for its first setup"}|401] browser=302
06:16:31 g-immich api=[{"error":"this app is waiting for its first setup"}|401] browser=302
06:16:32 g-n8n api=[{"error":"this app is waiting for its first setup"}|401] browser=302
06:16:32 g-immich api=[{"error":"this app is waiting for its first setup"}|401] browser=302
06:16:33 g-n8n api=[{"error":"this app is waiting for its first setup"}|401] browser=302
06:16:33 g-immich api=[{"error":"this app is waiting for its first setup"}|401] browser=302
06:16:34 g-n8n api=[{"error":"this app is waiting for its first setup"}|401] browser=302
06:16:34 g-immich api=[{"error":"this app is waiting for its first setup"}|401] browser=302
06:16:35 g-n8n api=[{"error":"this app is waiting for its first setup"}|401] browser=302
06:16:35 g-immich api=[{"error":"this app is waiting for its first setup"}|401] browser=302
06:16:36 g-n8n api=[{"error":"this app is waiting for its first setup"}|401] browser=302
06:16:36 g-immich api=[{"error":"this app is waiting for its first setup"}|401] browser=302
06:16:37 g-n8n api=[{"error":"this app is waiting for its first setup"}|401] browser=302
06:16:37 g-immich api=[{"error":"this app is waiting for its first setup"}|401] browser=302
06:16:38 g-n8n api=[{"error":"this app is waiting for its first setup"}|401] browser=302
06:16:38 g-immich api=[{"error":"this app is waiting for its first setup"}|401] browser=302
06:16:40 g-n8n api=[{"error":"this app is waiting for its first setup"}|401] browser=302
06:16:40 g-immich api=[{"error":"this app is waiting for its first setup"}|401] browser=302
06:16:41 g-n8n api=[{"error":"this app is waiting for its first setup"}|401] browser=302
06:16:41 g-immich api=[{"error":"this app is waiting for its first setup"}|401] browser=302
06:16:42 g-n8n api=[{"error":"this app is waiting for its first setup"}|401] browser=302
06:16:42 g-immich api=[{"error":"this app is waiting for its first setup"}|401] browser=302
06:16:43 g-n8n api=[{"error":"this app is waiting for its first setup"}|401] browser=302
06:16:43 g-immich api=[{"error":"this app is waiting for its first setup"}|401] browser=302
06:16:44 g-n8n api=[{"error":"this app is waiting for its first setup"}|401] browser=302
06:16:44 g-immich api=[{"error":"this app is waiting for its first setup"}|401] browser=302
06:16:45 g-n8n api=[{"error":"this app is waiting for its first setup"}|401] browser=302
06:16:45 g-immich api=[{"error":"this app is waiting for its first setup"}|401] browser=302
06:16:46 g-n8n api=[{"error":"this app is waiting for its first setup"}|401] browser=302
06:16:46 g-immich api=[{"error":"this app is waiting for its first setup"}|401] browser=302
06:16:47 g-n8n api=[{"error":"this app is waiting for its first setup"}|401] browser=302
06:16:47 g-immich api=[{"loginPageMessage":"","trashDays":30,"userDeleteDelay":7,"oauthButtonText":"Login with OA] browser=200
06:16:48 g-n8n api=[{"data":{"settingsMode":"public","defaultLocale":"en","userManagement":{"authenticationMet] browser=200
06:16:49 g-immich api=[{"loginPageMessage":"","trashDays":30,"userDeleteDelay":7,"oauthButtonText":"Login with OA] browser=200
06:16:50 g-n8n api=[{"data":{"settingsMode":"public","defaultLocale":"en","userManagement":{"authenticationMet] browser=200
06:16:50 g-immich api=[{"loginPageMessage":"","trashDays":30,"userDeleteDelay":7,"oauthButtonText":"Login with OA] browser=200
06:16:51 g-n8n api=[{"data":{"settingsMode":"public","defaultLocale":"en","userManagement":{"authenticationMet] browser=200
06:16:51 g-immich api=[{"loginPageMessage":"","trashDays":30,"userDeleteDelay":7,"oauthButtonText":"Login with OA] browser=200
06:16:52 g-n8n api=[{"data":{"settingsMode":"public","defaultLocale":"en","userManagement":{"authenticationMet] browser=200
06:16:52 g-immich api=[{"loginPageMessage":"","trashDays":30,"userDeleteDelay":7,"oauthButtonText":"Login with OA] browser=200
06:16:53 g-n8n api=[{"data":{"settingsMode":"public","defaultLocale":"en","userManagement":{"authenticationMet] browser=200
06:16:53 g-immich api=[{"loginPageMessage":"","trashDays":30,"userDeleteDelay":7,"oauthButtonText":"Login with OA] browser=200
06:16:54 g-n8n api=[{"data":{"settingsMode":"public","defaultLocale":"en","userManagement":{"authenticationMet] browser=200
06:16:54 g-immich api=[{"loginPageMessage":"","trashDays":30,"userDeleteDelay":7,"oauthButtonText":"Login with OA] browser=200
06:16:55 g-n8n api=[{"data":{"settingsMode":"public","defaultLocale":"en","userManagement":{"authenticationMet] browser=200
06:16:55 g-immich api=[{"loginPageMessage":"","trashDays":30,"userDeleteDelay":7,"oauthButtonText":"Login with OA] browser=200
06:16:56 g-n8n api=[{"data":{"settingsMode":"public","defaultLocale":"en","userManagement":{"authenticationMet] browser=200
06:16:56 g-immich api=[{"loginPageMessage":"","trashDays":30,"userDeleteDelay":7,"oauthButtonText":"Login with OA] browser=200
06:16:57 g-n8n api=[{"data":{"settingsMode":"public","defaultLocale":"en","userManagement":{"authenticationMet] browser=200
06:16:58 g-immich api=[{"loginPageMessage":"","trashDays":30,"userDeleteDelay":7,"oauthButtonText":"Login with OA] browser=200
06:16:59 g-n8n api=[{"data":{"settingsMode":"public","defaultLocale":"en","userManagement":{"authenticationMet] browser=200
06:16:59 g-immich api=[{"loginPageMessage":"","trashDays":30,"userDeleteDelay":7,"oauthButtonText":"Login with OA] browser=200
06:17:00 g-n8n api=[{"data":{"settingsMode":"public","defaultLocale":"en","userManagement":{"authenticationMet] browser=200
06:17:00 g-immich api=[{"loginPageMessage":"","trashDays":30,"userDeleteDelay":7,"oauthButtonText":"Login with OA] browser=200
06:17:01 g-n8n api=[{"data":{"settingsMode":"public","defaultLocale":"en","userManagement":{"authenticationMet] browser=200
06:17:01 g-immich api=[{"loginPageMessage":"","trashDays":30,"userDeleteDelay":7,"oauthButtonText":"Login with OA] browser=200
06:17:02 g-n8n api=[{"data":{"settingsMode":"public","defaultLocale":"en","userManagement":{"authenticationMet] browser=200
06:17:02 g-immich api=[{"loginPageMessage":"","trashDays":30,"userDeleteDelay":7,"oauthButtonText":"Login with OA] browser=200
06:17:03 g-n8n api=[{"data":{"settingsMode":"public","defaultLocale":"en","userManagement":{"authenticationMet] browser=200
06:17:03 g-immich api=[{"loginPageMessage":"","trashDays":30,"userDeleteDelay":7,"oauthButtonText":"Login with OA] browser=200
06:17:04 g-n8n api=[{"data":{"settingsMode":"public","defaultLocale":"en","userManagement":{"authenticationMet] browser=200
06:17:04 g-immich api=[{"loginPageMessage":"","trashDays":30,"userDeleteDelay":7,"oauthButtonText":"Login with OA] browser=200
06:17:05 g-n8n api=[{"data":{"settingsMode":"public","defaultLocale":"en","userManagement":{"authenticationMet] browser=200
06:17:06 g-immich api=[{"loginPageMessage":"","trashDays":30,"userDeleteDelay":7,"oauthButtonText":"Login with OA] browser=200
06:17:07 g-n8n api=[{"data":{"settingsMode":"public","defaultLocale":"en","userManagement":{"authenticationMet] browser=200
06:17:07 g-immich api=[{"loginPageMessage":"","trashDays":30,"userDeleteDelay":7,"oauthButtonText":"Login with OA] browser=200
06:17:08 g-n8n api=[{"data":{"settingsMode":"public","defaultLocale":"en","userManagement":{"authenticationMet] browser=200
06:17:08 g-immich api=[{"loginPageMessage":"","trashDays":30,"userDeleteDelay":7,"oauthButtonText":"Login with OA] browser=200
06:17:09 g-n8n api=[{"data":{"settingsMode":"public","defaultLocale":"en","userManagement":{"authenticationMet] browser=200
06:17:09 g-immich api=[{"loginPageMessage":"","trashDays":30,"userDeleteDelay":7,"oauthButtonText":"Login with OA] browser=200
06:17:10 g-n8n api=[{"data":{"settingsMode":"public","defaultLocale":"en","userManagement":{"authenticationMet] browser=200
06:17:10 g-immich api=[{"loginPageMessage":"","trashDays":30,"userDeleteDelay":7,"oauthButtonText":"Login with OA] browser=200
06:17:11 g-n8n api=[{"data":{"settingsMode":"public","defaultLocale":"en","userManagement":{"authenticationMet] browser=200
06:17:11 g-immich api=[{"loginPageMessage":"","trashDays":30,"userDeleteDelay":7,"oauthButtonText":"Login with OA] browser=200
06:17:12 g-n8n api=[{"data":{"settingsMode":"public","defaultLocale":"en","userManagement":{"authenticationMet] browser=200
06:17:12 g-immich api=[{"loginPageMessage":"","trashDays":30,"userDeleteDelay":7,"oauthButtonText":"Login with OA] browser=200
06:17:13 g-n8n api=[{"data":{"settingsMode":"public","defaultLocale":"en","userManagement":{"authenticationMet] browser=200
06:17:13 g-immich api=[{"loginPageMessage":"","trashDays":30,"userDeleteDelay":7,"oauthButtonText":"Login with OA] browser=200
06:17:15 g-n8n api=[|500] browser=500
06:17:15 g-immich api=[|500] browser=500
06:17:16 g-n8n api=[|500] browser=500
06:17:16 g-immich api=[|500] browser=500
06:17:17 g-n8n api=[|500] browser=500
06:17:17 g-immich api=[|500] browser=500
06:17:18 g-n8n api=[{"data":{"settingsMode":"public","defaultLocale":"en","userManagement":{"authenticationMet] browser=200
06:17:18 g-immich api=[{"loginPageMessage":"","trashDays":30,"userDeleteDelay":7,"oauthButtonText":"Login with OA] browser=200
06:17:19 g-n8n api=[{"data":{"settingsMode":"public","defaultLocale":"en","userManagement":{"authenticationMet] browser=200
06:17:19 g-immich api=[{"loginPageMessage":"","trashDays":30,"userDeleteDelay":7,"oauthButtonText":"Login with OA] browser=200
06:17:20 g-n8n api=[{"data":{"settingsMode":"public","defaultLocale":"en","userManagement":{"authenticationMet] browser=200
06:17:20 g-immich api=[{"loginPageMessage":"","trashDays":30,"userDeleteDelay":7,"oauthButtonText":"Login with OA] browser=200
06:17:21 g-n8n api=[{"data":{"settingsMode":"public","defaultLocale":"en","userManagement":{"authenticationMet] browser=200
06:17:22 g-immich api=[{"loginPageMessage":"","trashDays":30,"userDeleteDelay":7,"oauthButtonText":"Login with OA] browser=200
06:17:23 g-n8n api=[{"data":{"settingsMode":"public","defaultLocale":"en","userManagement":{"authenticationMet] browser=200
06:17:23 g-immich api=[{"loginPageMessage":"","trashDays":30,"userDeleteDelay":7,"oauthButtonText":"Login with OA] browser=200
06:17:24 g-n8n api=[{"data":{"settingsMode":"public","defaultLocale":"en","userManagement":{"authenticationMet] browser=200
06:17:24 g-immich api=[{"loginPageMessage":"","trashDays":30,"userDeleteDelay":7,"oauthButtonText":"Login with OA] browser=200
06:17:25 g-n8n api=[{"data":{"settingsMode":"public","defaultLocale":"en","userManagement":{"authenticationMet] browser=200
06:17:25 g-immich api=[{"loginPageMessage":"","trashDays":30,"userDeleteDelay":7,"oauthButtonText":"Login with OA] browser=200
06:17:26 g-n8n api=[{"data":{"settingsMode":"public","defaultLocale":"en","userManagement":{"authenticationMet] browser=200
06:17:26 g-immich api=[{"loginPageMessage":"","trashDays":30,"userDeleteDelay":7,"oauthButtonText":"Login with OA] browser=200
06:17:27 g-n8n api=[{"data":{"settingsMode":"public","defaultLocale":"en","userManagement":{"authenticationMet] browser=200
06:17:27 g-immich api=[{"loginPageMessage":"","trashDays":30,"userDeleteDelay":7,"oauthButtonText":"Login with OA] browser=200
06:17:28 g-n8n api=[{"data":{"settingsMode":"public","defaultLocale":"en","userManagement":{"authenticationMet] browser=200
06:17:28 g-immich api=[{"loginPageMessage":"","trashDays":30,"userDeleteDelay":7,"oauthButtonText":"Login with OA] browser=200
06:17:29 g-n8n api=[{"data":{"settingsMode":"public","defaultLocale":"en","userManagement":{"authenticationMet] browser=200
06:17:29 g-immich api=[{"loginPageMessage":"","trashDays":30,"userDeleteDelay":7,"oauthButtonText":"Login with OA] browser=200
06:17:30 g-n8n api=[{"data":{"settingsMode":"public","defaultLocale":"en","userManagement":{"authenticationMet] browser=200
06:17:31 g-immich api=[{"loginPageMessage":"","trashDays":30,"userDeleteDelay":7,"oauthButtonText":"Login with OA] browser=200
06:17:32 g-n8n api=[{"data":{"settingsMode":"public","defaultLocale":"en","userManagement":{"authenticationMet] browser=200
06:17:32 g-immich api=[{"loginPageMessage":"","trashDays":30,"userDeleteDelay":7,"oauthButtonText":"Login with OA] browser=200
06:17:33 g-n8n api=[{"data":{"settingsMode":"public","defaultLocale":"en","userManagement":{"authenticationMet] browser=200
06:17:33 g-immich api=[{"loginPageMessage":"","trashDays":30,"userDeleteDelay":7,"oauthButtonText":"Login with OA] browser=200
06:17:34 g-n8n api=[{"data":{"settingsMode":"public","defaultLocale":"en","userManagement":{"authenticationMet] browser=200
06:17:34 g-immich api=[{"loginPageMessage":"","trashDays":30,"userDeleteDelay":7,"oauthButtonText":"Login with OA] browser=200
06:17:35 g-n8n api=[{"data":{"settingsMode":"public","defaultLocale":"en","userManagement":{"authenticationMet] browser=200
06:17:35 g-immich api=[{"loginPageMessage":"","trashDays":30,"userDeleteDelay":7,"oauthButtonText":"Login with OA] browser=200
06:17:36 g-n8n api=[{"data":{"settingsMode":"public","defaultLocale":"en","userManagement":{"authenticationMet] browser=200
06:17:36 g-immich api=[{"loginPageMessage":"","trashDays":30,"userDeleteDelay":7,"oauthButtonText":"Login with OA] browser=200
06:17:37 g-n8n api=[{"data":{"settingsMode":"public","defaultLocale":"en","userManagement":{"authenticationMet] browser=200
06:17:37 g-immich api=[{"loginPageMessage":"","trashDays":30,"userDeleteDelay":7,"oauthButtonText":"Login with OA] browser=200
06:17:38 g-n8n api=[{"data":{"settingsMode":"public","defaultLocale":"en","userManagement":{"authenticationMet] browser=200
06:17:38 g-immich api=[{"loginPageMessage":"","trashDays":30,"userDeleteDelay":7,"oauthButtonText":"Login with OA] browser=200
06:17:39 g-n8n api=[{"data":{"settingsMode":"public","defaultLocale":"en","userManagement":{"authenticationMet] browser=200
06:17:40 g-immich api=[{"loginPageMessage":"","trashDays":30,"userDeleteDelay":7,"oauthButtonText":"Login with OA] browser=200
06:17:41 g-n8n api=[{"data":{"settingsMode":"public","defaultLocale":"en","userManagement":{"authenticationMet] browser=200
06:17:41 g-immich api=[{"loginPageMessage":"","trashDays":30,"userDeleteDelay":7,"oauthButtonText":"Login with OA] browser=200
06:17:42 g-n8n api=[{"data":{"settingsMode":"public","defaultLocale":"en","userManagement":{"authenticationMet] browser=200
06:17:42 g-immich api=[{"loginPageMessage":"","trashDays":30,"userDeleteDelay":7,"oauthButtonText":"Login with OA] browser=200
06:17:43 g-n8n api=[{"data":{"settingsMode":"public","defaultLocale":"en","userManagement":{"authenticationMet] browser=200
06:17:43 g-immich api=[{"loginPageMessage":"","trashDays":30,"userDeleteDelay":7,"oauthButtonText":"Login with OA] browser=200
06:17:44 g-n8n api=[{"data":{"settingsMode":"public","defaultLocale":"en","userManagement":{"authenticationMet] browser=200
06:17:44 g-immich api=[{"loginPageMessage":"","trashDays":30,"userDeleteDelay":7,"oauthButtonText":"Login with OA] browser=200
06:17:45 g-n8n api=[{"data":{"settingsMode":"public","defaultLocale":"en","userManagement":{"authenticationMet] browser=200
06:17:45 g-immich api=[{"loginPageMessage":"","trashDays":30,"userDeleteDelay":7,"oauthButtonText":"Login with OA] browser=200
06:17:46 g-n8n api=[{"data":{"settingsMode":"public","defaultLocale":"en","userManagement":{"authenticationMet] browser=200
06:17:46 g-immich api=[{"loginPageMessage":"","trashDays":30,"userDeleteDelay":7,"oauthButtonText":"Login with OA] browser=200
06:17:47 g-n8n api=[{"data":{"settingsMode":"public","defaultLocale":"en","userManagement":{"authenticationMet] browser=200
06:17:47 g-immich api=[{"loginPageMessage":"","trashDays":30,"userDeleteDelay":7,"oauthButtonText":"Login with OA] browser=200
06:17:48 g-n8n api=[{"data":{"settingsMode":"public","defaultLocale":"en","userManagement":{"authenticationMet] browser=200
06:17:49 g-immich api=[{"loginPageMessage":"","trashDays":30,"userDeleteDelay":7,"oauthButtonText":"Login with OA] browser=200
06:17:50 g-n8n api=[{"data":{"settingsMode":"public","defaultLocale":"en","userManagement":{"authenticationMet] browser=200
06:17:50 g-immich api=[{"loginPageMessage":"","trashDays":30,"userDeleteDelay":7,"oauthButtonText":"Login with OA] browser=200
06:17:51 g-n8n api=[{"data":{"settingsMode":"public","defaultLocale":"en","userManagement":{"authenticationMet] browser=200
06:17:51 g-immich api=[{"loginPageMessage":"","trashDays":30,"userDeleteDelay":7,"oauthButtonText":"Login with OA] browser=200
06:17:52 g-n8n api=[{"data":{"settingsMode":"public","defaultLocale":"en","userManagement":{"authenticationMet] browser=200
06:17:52 g-immich api=[{"loginPageMessage":"","trashDays":30,"userDeleteDelay":7,"oauthButtonText":"Login with OA] browser=200
06:17:53 g-n8n api=[{"data":{"settingsMode":"public","defaultLocale":"en","userManagement":{"authenticationMet] browser=200
06:17:53 g-immich api=[{"loginPageMessage":"","trashDays":30,"userDeleteDelay":7,"oauthButtonText":"Login with OA] browser=200
06:17:54 g-n8n api=[{"data":{"settingsMode":"public","defaultLocale":"en","userManagement":{"authenticationMet] browser=200
06:17:54 g-immich api=[{"loginPageMessage":"","trashDays":30,"userDeleteDelay":7,"oauthButtonText":"Login with OA] browser=200
06:17:55 g-n8n api=[{"data":{"settingsMode":"public","defaultLocale":"en","userManagement":{"authenticationMet] browser=200
06:17:55 g-immich api=[{"loginPageMessage":"","trashDays":30,"userDeleteDelay":7,"oauthButtonText":"Login with OA] browser=200
06:17:56 g-n8n api=[{"data":{"settingsMode":"public","defaultLocale":"en","userManagement":{"authenticationMet] browser=200
06:17:56 g-immich api=[{"loginPageMessage":"","trashDays":30,"userDeleteDelay":7,"oauthButtonText":"Login with OA] browser=200
06:17:57 g-n8n api=[{"data":{"settingsMode":"public","defaultLocale":"en","userManagement":{"authenticationMet] browser=200
06:17:58 g-immich api=[{"loginPageMessage":"","trashDays":30,"userDeleteDelay":7,"oauthButtonText":"Login with OA] browser=200
@@ -0,0 +1,26 @@
== B2a STRANGER, browser, no dashboard session
https://g-n8n.enkisfelhom.hu/ : final 200 | 302 g-n8n.enkisfelhom.hu/ -> 200 felhom.enkisfelhom.hu/__gate/start | gate page (en, hu-ascii): (True, True) | app html seen: False
cookies the stranger holds: {}
https://g-immich.enkisfelhom.hu/ : final 200 | 302 g-immich.enkisfelhom.hu/ -> 200 felhom.enkisfelhom.hu/__gate/start | gate page (en, hu-ascii): (True, True) | app html seen: False
cookies the stranger holds: {}
== B2b STRANGER, API style (what a script would do)
GET https://g-n8n.enkisfelhom.hu/rest/settings -> 401 '{"error":"this app is waiting for its first setup"}'
POST https://g-n8n.enkisfelhom.hu/rest/owner/setup -> 401 '{"error":"this app is waiting for its first setup"}'
GET https://g-immich.enkisfelhom.hu/api/server/config -> 401 '{"error":"this app is waiting for its first setup"}'
POST https://g-immich.enkisfelhom.hu/api/auth/admin-sign-up -> 401 '{"error":"this app is waiting for its first setup"}'
== B2c STRANGER tricks
open-redirect try rd=evil -> 400 'not a gated app'
forged token -> 403 'token refused'
== B2d HOUSEHOLD, logged in to the dashboard first (the usual path: open the app from the dashboard)
dashboard login -> 200 | 302 felhom.enkisfelhom.hu/login -> 302 felhom.enkisfelhom.hu/ -> 200 felhom.enkisfelhom.hu/launcher
https://g-n8n.enkisfelhom.hu/ -> final 200 in 0.22s | 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
https://g-immich.enkisfelhom.hu/ -> final 200 in 0.21s | 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
cookies per host: {'felhom.enkisfelhom.hu': ['felhom_session'], 'g-n8n.enkisfelhom.hu': ['felhom_gate'], 'g-immich.enkisfelhom.hu': ['felhom_gate']}
dashboard cookie on an app host? False
== B2e HOUSEHOLD on a phone, NOT logged in: opens the app link first
step 1 -> 200 gate page (True, True) | 302 g-n8n.enkisfelhom.hu/ -> 200 felhom.enkisfelhom.hu/__gate/start
step 2 sign-in link -> 200 | login form shown: True
step 3 sign in -> final 200 | 302 felhom.enkisfelhom.hu/login -> 302 felhom.enkisfelhom.hu/__gate/start -> 302 g-n8n.enkisfelhom.hu/__felhom_gate/cb -> 200 g-n8n.enkisfelhom.hu/ | gate page: False
== B2f token replay + cookie reuse on another app
household2 reached n8n: 200 302 felhom.enkisfelhom.hu/__gate/start -> 302 g-n8n.enkisfelhom.hu/__felhom_gate/cb -> 200 g-n8n.enkisfelhom.hu/
n8n's gate cookie presented to immich -> 401 '{"error":"this app is waiting for its first setup"'
@@ -0,0 +1,2 @@
n8n /rest/settings userManagement.showSetupOnFirstLoad = False
immich /api/server/config isInitialized = True
@@ -0,0 +1,2 @@
n8n /rest/settings userManagement.showSetupOnFirstLoad = True
immich /api/server/config isInitialized = False
@@ -0,0 +1,2 @@
n8n POST /rest/owner/setup (household, through the gate) -> 200 {"data":{"createdAt":"2026-09-29T06:12:10.719Z","id":"a0bb2e
immich POST /api/auth/admin-sign-up (household, through the gate) -> 201 {"id":"f8c0d515-c748-468e-89d7-ee40964f9379","email":"admin@
@@ -0,0 +1,3 @@
[gated] immich phone app: POST /api/auth/login -> 401 token=no body={"error":"this app is waiting for its first setup"}
[gated] n8n login without a browser gate cookie: POST /rest/login -> 401
[gated] latency household GET /api/server/ping x40: last=200 median=52.2 ms p90=53.0 ms (new TLS connection each)
@@ -0,0 +1,6 @@
[open] immich phone app: POST /api/auth/login -> 201 token=yes body=<token>
[open] GET /api/users/me (Bearer) -> 200 '{"id":"f8c0d515-c748-468e-89d7-ee40964f9379","emai'
[open] GET /api/server/ping (Bearer) -> 200 '{"res":"pong"}'
[open] GET /api/server/version (Bearer) -> 200 '{"major":3,"minor":2,"patch":2,"prerelease":null}'
[open] n8n login without a browser gate cookie: POST /rest/login -> 200
[open] latency household GET /api/server/ping x40: last=200 median=50.1 ms p90=50.9 ms (new TLS connection each)
@@ -0,0 +1,6 @@
06:16:47.695
controller.yml
serverstransports.yml
immich answers a cookie-less request after 2 x 0.1s: 06:16:47.859
gate prototype STOPPED (nothing of it can be in the path now)
immich-server labels: no middleware label (unchanged by the gate)
@@ -0,0 +1,4 @@
g-n8n gated, answerer down: browser -> 500 body=[]
g-immich gated, answerer down: browser -> 500 body=[]
dashboard itself (controller UP; only the answerer is down): 200
after removing the gate again: immich -> 200
@@ -0,0 +1,70 @@
# Part B2/B3 — stranger vs household, both apps, through the gate. Passwords generated here, kept in memory only.
import json, secrets, sys, time
sys.path.insert(0, '.')
from browser import Browser, hopstr
D = "enkisfelhom.hu"
PW = open('../.ctlpw').read().strip()
N8N, IMM = f"https://g-n8n.{D}", f"https://g-immich.{D}"
def gatepage(body):
return ("waiting for its first setup" in body, "Felhom vez" in body)
print("== B2a STRANGER, browser, no dashboard session")
for base in (N8N, IMM):
s = Browser("stranger")
st, body, hops = s.req(base + "/")
print(f" {base}/ : final {st} | {hopstr(hops)} | gate page (en, hu-ascii): {gatepage(body)} | app html seen: {'n8n' in body.lower()[:3000] and 'waiting' not in body or '<title>Immich' in body}")
print(f" cookies the stranger holds: {s.hosts_with_cookies()}")
print("== B2b STRANGER, API style (what a script would do)")
s = Browser("stranger")
for url, meth, body in ((N8N + "/rest/settings", "GET", None),
(N8N + "/rest/owner/setup", "POST", {"email": "x@x.hu", "firstName": "x", "lastName": "x", "password": "Xx" + secrets.token_hex(8)}),
(IMM + "/api/server/config", "GET", None),
(IMM + "/api/auth/admin-sign-up", "POST", {"email": "x@x.hu", "password": secrets.token_hex(8), "name": "x"})):
st, b, hops = s.req(url, meth, body=body, accept="application/json")
print(f" {meth} {url} -> {st} {b[:70]!r}")
print("== B2c STRANGER tricks")
st, b, h = s.req(f"https://felhom.{D}/__gate/start?rd=https://evil.example/", follow=False)
print(f" open-redirect try rd=evil -> {st} {b[:40]!r}")
st, b, h = s.req(N8N + "/__felhom_gate/cb?t=%5B%22g-n8n.enkisfelhom.hu%22%2C%229999999999%22%2C%22n%22%2C%22https%3A//g-n8n.enkisfelhom.hu/%22%2C%22forged%22%5D", follow=False)
print(f" forged token -> {st} {b[:40]!r}")
print("== B2d HOUSEHOLD, logged in to the dashboard first (the usual path: open the app from the dashboard)")
hh = Browser("household")
st, b, hops = hh.login_dashboard(D, PW)
print(f" dashboard login -> {st} | {hopstr(hops)}")
for base in (N8N, IMM):
t0 = time.time(); st, body, hops = hh.req(base + "/"); dt = time.time() - t0
print(f" {base}/ -> final {st} in {dt:.2f}s | {hopstr(hops)} | gate page: {gatepage(body)[0]} | len {len(body)}")
print(f" cookies per host: {hh.hosts_with_cookies()}")
print(" dashboard cookie on an app host?", any('felhom_session' in v for h, v in hh.hosts_with_cookies().items() if not h.startswith('felhom.')))
print("== B2e HOUSEHOLD on a phone, NOT logged in: opens the app link first")
ph = Browser("phone")
st, body, hops = ph.req(N8N + "/")
print(f" step 1 -> {st} gate page {gatepage(body)} | {hopstr(hops)}")
import re, html as H
link = H.unescape(re.search(r'href="([^"]+)"', body).group(1))
st, body, hops = ph.req(f"https://felhom.{D}{link}", follow=True)
print(f" step 2 sign-in link -> {st} | login form shown: {'password' in body.lower()}")
import urllib.parse
nxt = urllib.parse.parse_qs(urllib.parse.urlsplit(link).query)['next'][0]
st, body, hops = ph.req(f"https://felhom.{D}/login?next=" + urllib.parse.quote(nxt), "POST",
body="password=" + urllib.parse.quote(PW) + "&next=" + urllib.parse.quote(nxt),
headers={"Content-Type": "application/x-www-form-urlencoded"})
print(f" step 3 sign in -> final {st} | {hopstr(hops)} | gate page: {gatepage(body)[0]}")
print("== B2f token replay + cookie reuse on another app")
hh2 = Browser("household2"); hh2.login_dashboard(D, PW)
st, b, hops = hh2.req(f"https://felhom.{D}/__gate/start?rd=" + urllib.parse.quote(N8N + "/"), follow=False)
cb = dict((k.lower(), v) for k, v in []) # placeholder
loc_hops = hops
import http.client
# follow once by hand to capture the callback URL
st2, b2, h2 = hh2.req(f"https://felhom.{D}/__gate/start?rd=" + urllib.parse.quote(N8N + "/"), follow=True)
print(f" household2 reached n8n: {st2} {hopstr(h2)}")
gc = hh2.jar.get(f"g-n8n.{D}", {}).get("felhom_gate")
x = Browser("thief"); x.jar[f"g-immich.{D}"] = {"felhom_gate": gc}
st, b, hops = x.req(IMM + "/api/server/config", accept="application/json")
print(f" n8n's gate cookie presented to immich -> {st} {b[:50]!r}")
json.dump({"hh": hh.jar, "ph": ph.jar}, open('jars.json', 'w'))
@@ -0,0 +1,16 @@
# Part B3 — the household completes each app's first setup THROUGH the gate (browser session = gate cookie).
import json, secrets, string, sys
sys.path.insert(0, '.')
from browser import Browser, hopstr
D = "enkisfelhom.hu"
hh = Browser("household"); hh.jar = json.load(open('jars.json'))["hh"]
def pw():
return "Hh" + secrets.token_hex(10) + "7"
creds = {"n8n": ("owner@spike.hu", pw()), "immich": ("admin@spike.hu", pw())}
json.dump(creds, open('creds.json', 'w')) # scratch only, 0600, deleted at teardown
st, b, h = hh.req(f"https://g-n8n.{D}/rest/owner/setup", "POST", accept="application/json",
body={"email": creds["n8n"][0], "firstName": "Spike", "lastName": "Owner", "password": creds["n8n"][1]})
print("n8n POST /rest/owner/setup (household, through the gate) ->", st, b[:60].replace(creds['n8n'][1], '<pw>'))
st, b, h = 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"})
print("immich POST /api/auth/admin-sign-up (household, through the gate) ->", st, b[:60])
@@ -0,0 +1,25 @@
# Part B4/B5 — immich's phone app (no browser cookies; Bearer token) and latency. Run with arg "gated" or "open".
import json, statistics, sys, time
sys.path.insert(0, '.')
from browser import Browser
D = "enkisfelhom.hu"; phase = sys.argv[1]
creds = json.load(open('creds.json'))
app = Browser("immich-mobile") # the phone app: no cookie jar from any browser
st, b, h = app.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
print(f"[{phase}] immich phone app: POST /api/auth/login -> {st} token={'yes' if tok else 'no'} body={b[:60] if not tok else '<token>'}")
if tok:
for p in ("/api/users/me", "/api/server/ping", "/api/server/version"):
st, b, h = app.req(f"https://g-immich.{D}{p}", accept="application/json", headers={"Authorization": "Bearer " + tok})
print(f"[{phase}] GET {p} (Bearer) -> {st} {b[:50].replace(creds['immich'][0],'<email>')!r}")
n8 = Browser("n8n-api")
st, b, h = n8.req(f"https://g-n8n.{D}/rest/login", "POST", accept="application/json",
body={"emailOrLdapLoginId": creds["n8n"][0], "password": creds["n8n"][1]})
print(f"[{phase}] n8n login without a browser gate cookie: POST /rest/login -> {st}")
# latency: the household browser (gate cookie when gated), 40 x GET /api/server/ping
hh = Browser("household"); hh.jar = json.load(open('jars.json'))["hh"]
ts = []
for _ in range(40):
t0 = time.perf_counter(); st, b, h = hh.req(f"https://g-immich.{D}/api/server/ping", accept="application/json"); ts.append((time.perf_counter() - t0) * 1000)
print(f"[{phase}] latency household GET /api/server/ping x40: last={st} median={statistics.median(ts):.1f} ms p90={sorted(ts)[35]:.1f} ms (new TLS connection each)")
@@ -0,0 +1,74 @@
# SPIKE evidence tool (2026-09-29): a minimal "browser" — per-host cookie jar (host-only cookies, as a browser keeps
# them), follows redirects across hosts, every host resolved to guest 9202's traefik. Never prints a password or a cookie.
import http.client, json, re, ssl, urllib.parse
IP = "192.168.0.114"
CTX = ssl._create_unverified_context() # scratch guest 9202 serves traefik's self-signed default cert on the LAN
class Browser:
def __init__(self, name):
self.name = name
self.jar = {} # host -> {cookie: value}
def req(self, url, method="GET", body=None, headers=None, follow=True, accept="text/html"):
hops = []
for _ in range(12):
u = urllib.parse.urlsplit(url)
host = u.hostname
path = u.path + ("?" + u.query if u.query else "")
h = {"Host": host, "Accept": accept, "User-Agent": "felhom-spike-browser"}
ck = self.jar.get(host, {})
if ck:
h["Cookie"] = "; ".join(f"{k}={v}" for k, v in ck.items())
h.update(headers or {})
data = body
if isinstance(body, dict):
data = json.dumps(body).encode()
h["Content-Type"] = "application/json"
elif isinstance(body, str):
data = body.encode()
conn = http.client.HTTPSConnection(IP, 443, context=CTX, timeout=30)
conn.connect = _sni_connect(conn, host)
conn.request(method, path, body=data, headers=h)
r = conn.getresponse()
rb = r.read()
for k, v in r.getheaders():
if k.lower() == "set-cookie":
nv = v.split(";", 1)[0]
n, _, val = nv.partition("=")
if "max-age=-1" in v.lower() or val == "":
self.jar.setdefault(host, {}).pop(n, None)
else:
self.jar.setdefault(host, {})[n] = val
hops.append((r.status, host, u.path))
loc = r.getheader("Location")
if follow and r.status in (301, 302, 303, 307, 308) and loc:
url = urllib.parse.urljoin(url, loc)
if r.status in (301, 302, 303):
method, body = "GET", None
continue
return r.status, rb.decode("utf-8", "replace"), hops
return 0, "", hops
def login_dashboard(self, domain, password):
# The dashboard's own login form (POST /login), exactly as walk.login() does it.
return self.req(f"https://felhom.{domain}/login", "POST",
body="password=" + urllib.parse.quote(password),
headers={"Content-Type": "application/x-www-form-urlencoded"})
def hosts_with_cookies(self):
return {h: sorted(v) for h, v in self.jar.items()}
def _sni_connect(conn, host):
import socket
def connect():
sock = socket.create_connection((IP, 443), timeout=30)
conn.sock = CTX.wrap_socket(sock, server_hostname=host)
return connect
def hopstr(hops):
return " -> ".join(f"{s} {h}{p}" for s, h, p in hops)
@@ -0,0 +1,6 @@
# B5: the gate's answerer is DOWN while an app is gated (models "the controller is down").
cp /root/gate-spike/gate-spike.yml.removed /opt/docker/stacks/traefik/dynamic/gate-spike.yml; sleep 3
for h in g-n8n g-immich; do echo "$h gated, answerer down: browser -> $(curl -sk -o /tmp/dn.$$ -w '%{http_code}' -H "Host: $h.enkisfelhom.hu" -H 'Accept: text/html' https://127.0.0.1/) body=[$(head -c 60 /tmp/dn.$$)]"; done
echo "dashboard itself (controller UP; only the answerer is down): $(curl -sk -o /dev/null -w '%{http_code}' -H 'Host: felhom.enkisfelhom.hu' https://127.0.0.1/login)"
rm -f /opt/docker/stacks/traefik/dynamic/gate-spike.yml /tmp/dn.$$; sleep 3
echo "after removing the gate again: immich -> $(curl -sk -o /dev/null -w '%{http_code}' -H 'Host: g-immich.enkisfelhom.hu' https://127.0.0.1/api/server/ping)"
@@ -0,0 +1,32 @@
# SPIKE ONLY (2026-09-29, decision 46) — scratch guest 9202. Removed at the end of the spike.
http:
middlewares:
felhom-gate-spike:
forwardAuth:
address: "http://felhom-gate-spike:8000/auth"
routers:
gate-spike-start:
rule: "Host(`felhom.enkisfelhom.hu`) && PathPrefix(`/__gate/`)"
priority: 100000
entryPoints: [websecure]
tls: {}
service: gate-spike
gate-spike-n8n:
rule: "Host(`g-n8n.enkisfelhom.hu`)"
priority: 100000
entryPoints: [websecure]
tls: {}
middlewares: [felhom-gate-spike]
service: n8n@docker
gate-spike-immich:
rule: "Host(`g-immich.enkisfelhom.hu`)"
priority: 100000
entryPoints: [websecure]
tls: {}
middlewares: [felhom-gate-spike]
service: immich@docker
services:
gate-spike:
loadBalancer:
servers:
- url: "http://felhom-gate-spike:8000"
@@ -0,0 +1,141 @@
# SPIKE ONLY (2026-09-29, decision 46) — a throwaway prototype of the setup gate, run on scratch guest 9202.
# It is NOT product code. In a build, these handlers would live in the controller itself.
#
# GET /auth traefik forwardAuth target for a GATED app host.
# GET /__gate/start on the DASHBOARD host (felhom.<domain>): checks the household's dashboard session
# (host-only cookie, never widened) by asking the controller, then hands the app host a
# 60-second one-use token.
# <app>/__felhom_gate/cb?t=… answered by /auth: swaps the token for a host-only gate cookie on the app host.
#
# The dashboard cookie never reaches an app host. The gate cookie is host-only, HMAC-bound to ONE app host.
import hashlib, hmac, html, http.client, json, os, secrets, sys, time, urllib.parse
from http.server import BaseHTTPRequestHandler, ThreadingHTTPServer
DOMAIN = os.environ["GATE_DOMAIN"]
GATED = set(h.strip() for h in os.environ["GATE_HOSTS"].split(",") if h.strip()) # full host names
KEY = secrets.token_bytes(32)
USED = {}
COOKIE = "felhom_gate"
DASH = "felhom." + DOMAIN
COUNT = {"auth": 0}
def log(*a):
print(time.strftime("%H:%M:%S"), *a, flush=True)
def sign(*parts):
return hmac.new(KEY, "|".join(parts).encode(), hashlib.sha256).hexdigest()
def dashboard_session_ok(cookie_header):
"""Ask the controller whether this dashboard session is valid (401 without one, 200 with one)."""
sess = ""
for c in (cookie_header or "").split(";"):
k, _, v = c.strip().partition("=")
if k == "felhom_session":
sess = v
if not sess:
return False
conn = http.client.HTTPConnection("felhom-controller", 8080, timeout=5)
conn.request("GET", "/api/stacks", headers={"Host": DASH, "Cookie": "felhom_session=" + sess})
ok = conn.getresponse().status == 200
conn.close()
return ok
PAGE = """<!doctype html><html lang="hu"><meta charset="utf-8"><title>Felhom</title>
<body style="font-family:sans-serif;max-width:32rem;margin:4rem auto">
<p>Ez az alkalmazás még beállításra vár. Jelentkezz be a Felhom vezérlőpultba.</p>
<p lang="en">This app is waiting for its first setup. Sign in to the Felhom dashboard.</p>
<p><a href="{login}">Bejelentkezés / Sign in</a></p></body></html>"""
class H(BaseHTTPRequestHandler):
def log_message(self, *a):
pass
def send(self, code, body=b"", headers=()):
self.send_response(code)
for k, v in headers:
self.send_header(k, v)
self.send_header("Content-Length", str(len(body)))
self.end_headers()
self.wfile.write(body)
def do_GET(self):
u = urllib.parse.urlsplit(self.path)
if u.path == "/auth":
return self.auth()
if u.path == "/__gate/start":
return self.start(urllib.parse.parse_qs(u.query))
self.send(404)
# forwardAuth may call with the original method; the body is never needed.
def _any(self):
n = int(self.headers.get("Content-Length") or 0)
if n:
self.rfile.read(n)
self.do_GET()
do_POST = do_PUT = do_PATCH = do_DELETE = do_HEAD = do_OPTIONS = _any
# forwardAuth: traefik forwards every method as a GET here, with X-Forwarded-* headers.
def auth(self):
COUNT["auth"] += 1
host = self.headers.get("X-Forwarded-Host", "")
uri = self.headers.get("X-Forwarded-Uri", "/")
method = self.headers.get("X-Forwarded-Method", "GET")
if host not in GATED:
log("auth", host, "not gated -> 403 (fail closed)")
return self.send(403)
u = urllib.parse.urlsplit(uri)
if u.path == "/__felhom_gate/cb":
t = urllib.parse.parse_qs(u.query).get("t", [""])[0]
try:
h, exp, nonce, rd, mac = json.loads(urllib.parse.unquote(t))
except Exception:
return self.send(400, b"bad token")
if (h != host or int(exp) < time.time() or nonce in USED
or not hmac.compare_digest(mac, sign("tok", h, str(exp), nonce, rd))):
log("cb", host, "token refused")
return self.send(403, b"token refused")
USED[nonce] = 1
cexp = str(int(time.time()) + 7 * 86400)
val = cexp + "." + sign("cookie", host, cexp)
log("cb", host, "token ok -> gate cookie, back to", rd)
return self.send(302, headers=[("Location", rd), ("Set-Cookie",
f"{COOKIE}={val}; Path=/; Max-Age=604800; HttpOnly; Secure; SameSite=Lax")])
for c in (self.headers.get("Cookie") or "").split(";"):
k, _, v = c.strip().partition("=")
if k == COOKIE and "." in v:
cexp, mac = v.split(".", 1)
if cexp.isdigit() and int(cexp) > time.time() and hmac.compare_digest(mac, sign("cookie", host, cexp)):
return self.send(200)
wants_html = "text/html" in (self.headers.get("Accept") or "") and method == "GET"
log("auth", host, method, uri[:60], "no gate cookie ->", "302 to dashboard" if wants_html else "401")
if wants_html:
rd = f"https://{host}{uri}"
return self.send(302, headers=[("Location", f"https://{DASH}/__gate/start?" +
urllib.parse.urlencode({"rd": rd}))])
return self.send(401, b'{"error":"this app is waiting for its first setup"}',
[("Content-Type", "application/json")])
def start(self, q):
rd = (q.get("rd") or [""])[0]
host = urllib.parse.urlsplit(rd).hostname or ""
if host not in GATED or not rd.startswith("https://" + host + "/"):
return self.send(400, b"not a gated app")
if not dashboard_session_ok(self.headers.get("Cookie")):
login = "/login?" + urllib.parse.urlencode({"next": "/__gate/start?" + urllib.parse.urlencode({"rd": rd})})
log("start", host, "no dashboard session -> gate page")
return self.send(200, PAGE.format(login=html.escape(login)).encode(),
[("Content-Type", "text/html; charset=utf-8")])
exp, nonce = str(int(time.time()) + 60), secrets.token_hex(8)
t = urllib.parse.quote(json.dumps([host, exp, nonce, rd, sign("tok", host, exp, nonce, rd)]))
log("start", host, "dashboard session ok -> token")
return self.send(302, headers=[("Location", f"https://{host}/__felhom_gate/cb?t={t}")])
if __name__ == "__main__":
log("gate spike up; domain", DOMAIN, "gated", sorted(GATED))
ThreadingHTTPServer(("0.0.0.0", 8000), H).serve_forever()
@@ -0,0 +1,6 @@
# Open the gate for both apps: the gate's routers go; the app's own docker-label routers remain, untouched.
date -u +%T.%N | cut -c1-12; mv /opt/docker/stacks/traefik/dynamic/gate-spike.yml /root/gate-spike/gate-spike.yml.removed
ls /opt/docker/stacks/traefik/dynamic
for i in $(seq 1 50); do c=$(curl -sk -o /dev/null -w '%{http_code}' -H 'Host: g-immich.enkisfelhom.hu' -H 'Accept: application/json' https://127.0.0.1/api/server/ping); [ "$c" = 200 ] && { echo "immich answers a cookie-less request after $i x 0.1s: $(date -u +%T.%N | cut -c1-12)"; break; }; sleep 0.1; done
docker stop felhom-gate-spike >/dev/null && echo "gate prototype STOPPED (nothing of it can be in the path now)"
docker inspect -f '{{range $k,$v := .Config.Labels}}{{if eq (slice $k 0 7) "traefik"}}{{$k}}={{$v}}{{"\n"}}{{end}}{{end}}' immich-server | grep -i middle || echo "immich-server labels: no middleware label (unchanged by the gate)"
@@ -0,0 +1,11 @@
# Read-only "setup done?" probes, straight from the docker network (not through traefik, not through the gate).
docker exec felhom-gate-spike python -c '
import json, urllib.request as u
def g(url):
try: return json.load(u.urlopen(url, timeout=10))
except Exception as e: return {"_err": str(e)[:80]}
s = g("http://n8n:5678/rest/settings")
print("n8n /rest/settings userManagement.showSetupOnFirstLoad =", (s.get("data") or {}).get("userManagement", {}).get("showSetupOnFirstLoad"), s.get("_err",""))
c = g("http://immich-server:2283/api/server/config")
print("immich /api/server/config isInitialized =", c.get("isInitialized"), c.get("_err",""))
'
@@ -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; do
p=/rest/settings; [ $h = g-immich ] && p=/api/server/config
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
+1
View File
@@ -822,6 +822,7 @@ class (an image `VOLUME` at an unmounted path) is still live — `immich-server`
| **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-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** |
<!-- 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.