Part B: setup-gate spike on 9202 — verdict PASS written before any build; R-711 filed
gates / gates (push) Successful in 25s
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:
@@ -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
|
||||||
@@ -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-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-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-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.
|
<!-- 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.
|
One row per dated check. The R-number must have a row above. Dates are UTC.
|
||||||
|
|||||||
Reference in New Issue
Block a user