| 1 | # Darwin → REDACTED-HOST (Authentik SSO) - execution plan |
| 2 | |
| 3 | Verified-safe plan from edge recon (2026-06-24). COEXISTS with Odysseus chat.lab - NOT the teardown. |
| 4 | Execute AFTER the workspace build lands (Face must proxy backends same-origin first). Tailscale gives |
| 5 | on-the-go access NOW in the meantime (phone on tailnet → REDACTED-IP:3000 via CT215 subnet route). |
| 6 | |
| 7 | ## Already done for us (zero work) |
| 8 | - **DNS:** Technitium on CT244:53 is a true wildcard - `REDACTED-HOST` already → REDACTED-IP. |
| 9 | (NOT AdGuard/CT201, which NXDOMAINs `*.lab`.) |
| 10 | - **TLS:** wildcard `*.lab.zionboggan.com` cert via Cloudflare DNS-01 ACME (resolver `cloudflare`). Covers it. |
| 11 | - **WS at the edge:** Traefik v3 proxies WebSockets transparently (chat.lab/Odysseus proves it, no WS config). |
| 12 | |
| 13 | ## The catch (do FIRST, on the Face / CT252) |
| 14 | Face uses build-time `NEXT_PUBLIC_*` → raw LAN IPs (`http://REDACTED-IP:9890`, `ws://REDACTED-IP:9893`, |
| 15 | + term :9894). Behind https that's mixed-content + not remote-reachable + bypasses SSO. FIX = Face proxies |
| 16 | same-origin so the browser only ever talks to `darwin.lab`: |
| 17 | - Add `next.config.mjs` `rewrites()`: `/cortex/* → :9890`, `/deck/* → REDACTED-IP:9893`, `/term/* → REDACTED-IP:9894`. |
| 18 | - Build with relative `NEXT_PUBLIC_CORTEX_URL=/cortex`; for WS use `wss://darwin.lab.../deck/stream` etc. |
| 19 | - **WS-over-next-rewrite is not guaranteed.** If WS misbehaves → OPTION B: give Deck+Term their own |
| 20 | SSO-gated subdomains (`deck.lab`→CT215:9893, `term.lab`→CT215:9894); Traefik passes WS fine. Cortex stays |
| 21 | behind the Face rewrite (same-box HTTP). |
| 22 | - Coordinate this with the workspace build (it adds the term component) - do it as the workspace finalize. |
| 23 | |
| 24 | ## Edge change (CT244, LAST) - additive, hot-reloaded |
| 25 | `cp /opt/edge/traefik/dynamic/services.yml{,.bak.pre-darwin.TIMESTAMP}` then append, inside the existing |
| 26 | `routers:` and `services:` maps (mirror the `dashboard` SSO router + `odysseus` backend): |
| 27 | ```yaml |
| 28 | # router (in routers:) |
| 29 | darwin: |
| 30 | rule: "Host(`REDACTED-HOST`)" |
| 31 | entrypoints: [websecure] |
| 32 | tls: { certResolver: cloudflare } |
| 33 | service: darwin |
| 34 | middlewares: [ sso-auth@file ] |
| 35 | # service (in services:) |
| 36 | darwin: |
| 37 | loadBalancer: |
| 38 | servers: [ { url: "http://REDACTED-IP:3000" } ] |
| 39 | ``` |
| 40 | (+ deck/term routers if Option B.) Save → hot-reload. **YAML-FRAGILE: a parse error makes Traefik drop the |
| 41 | WHOLE file → 502s jellyfin/dashboard/chat.lab/etc. Back up, append carefully inside the existing maps, watch |
| 42 | `docker logs traefik` for clean reload.** |
| 43 | |
| 44 | ## Authentik (CT246, UI at https://REDACTED-HOST, akadmin) - mirror "Master Dashboard" |
| 45 | 1. Proxy Provider `Darwin forwardAuth`: mode `forward_single`, `external_host=https://REDACTED-HOST`, |
| 46 | authflow `default-provider-authorization-implicit-consent`. |
| 47 | 2. Application `Darwin` → that provider. |
| 48 | 3. **Assign the provider to the authentik Embedded Outpost** (forwardAuth → CT246:9000). Required or it errors. |
| 49 | (Try the Authentik REST API with an akadmin token to automate; else it's a ~2-min manual click for Zion.) |
| 50 | |
| 51 | ## Verify |
| 52 | `curl -I https://REDACTED-HOST` → 302 to auth; browser login flow; cortex/deck/term work same-origin. |
| 53 | |
| 54 | ## Rollback |
| 55 | Remove the `darwin` router+service blocks (or restore the .bak) → hot-reload, darwin.lab stops routing, zero |
| 56 | impact elsewhere. Direct `:3000` stays LAN-reachable. Authentik app/provider harmless to leave. |
| 57 | |
| 58 | ## Key paths |
| 59 | - Edge route: `/opt/edge/traefik/dynamic/services.yml` (CT244) · SSO mw def: `dynamic/sso-gating.yml` (reference only) |
| 60 | - Face: `/shared/projects/darwin/face/next.config.mjs` (+ NEXT_PUBLIC_* build vars) |