#+TITLE: Infrastructure Documentation — gharbeia.net #+AUTHOR: Amr Gharbeia #+DATE: 2026-05-15 * Architecture ** Hosts - =production-1= (10.10.10.201) :: Docker host, runs all services - Hermes Agent :: Management/automation host ** Network - Docker network =networking= (172.28.10.0/24) - Proxmox VLANs: 1/10/20/30/40/50 - Services VLAN: 10.10.10.0/24 - Domain: gharbeia.net via Cloudflare (orange cloud/proxied) ** External Access Architecture Cloudflare (edge, orange cloud) └─ Cloudflare Tunnel "home" (cloudflared on production-1) └─ Traefik (entrypoint=tunnel, port 8081) ├─ Authentik Forward Auth (external routers) ├─ gharbeia-site (nginx) ├─ jellyfin (SSO via plugin + OIDC) ├─ gitea (native OIDC) └─ *.gharbeia.net services ** Internal Access Architecture LAN client (browser) └─ Traefik (entrypoint=secureweb, port 443) ├─ Authentik Forward Auth (internal.yaml routers) ├─ gharbeia-site (public, no auth) ├─ jellyfin (SSO via plugin) └─ *.gharbeia.net services Service-to-service / automation / cross-VLAN └─ Traefik (entrypoint=internal, port 8083 — NO auth) └─ Same routing as secureweb, from =traefik-internal-noauth.yaml= Key distinction: =:443= = browsers/humans with Authentik auth. =:8083= = runners, automated tooling, services on other VLANs. * Traefik — Reverse Proxy Traefik is the edge router for all HTTP traffic. It handles TLS termination via Let's Encrypt (DNS-01 challenge through Cloudflare), routes traffic to the right container, and applies middleware chains for auth, security, and rate limiting. Three entrypoints: - =tunnel= (=:8081=) :: Receives traffic from the Cloudflare tunnel. All routers here have Authentik Forward Auth. - =secureweb= (=:443=) :: Internal LAN traffic with TLS. Also has Authentik Forward Auth for browser access. - =internal= (=:8083=) :: Service-to-service and cross-VLAN traffic. No auth. HTTP only. For runners, automation, and API calls that shouldn't hit Authentik. ** Static Configuration The static config sets entrypoints, TLS resolvers, providers, and plugins. It is the foundation everything else builds on. #+BEGIN_SRC yaml :tangle /docker/compose/traefik-static.yaml global: checkNewVersion: true sendAnonymousUsage: true log: level: INFO accessLog: filePath: /var/log/access.log format: json api: dashboard: true insecure: true entryPoints: web: address: :80 http: redirections: entryPoint: to: secureweb scheme: https permanent: true tunnel: address: :8081 secureweb: address: :443 http: tls: options: default certResolver: letsencrypt domains: - main: gharbeia.net sans: - "*.gharbeia.net" internal: address: :8083 metrics: address: :8082 metrics: prometheus: entryPoint: metrics manualRouting: true headerLabels: useragent: User-Agent buckets: - 0.1 - 0.3 - 1.2 - 5.0 providers: docker: exposedByDefault: false file: directory: /etc/traefik watch: true certificatesResolvers: letsencrypt: acme: storage: /letsencrypt/acme.json email: gharbeia@riseup.net keyType: EC384 caServer: https://acme-v02.api.letsencrypt.org/directory dnsChallenge: provider: cloudflare resolvers: - 1.1.1.1:53 - 1.0.0.1:53 propagation: delayBeforeChecks: 60s experimental: plugins: crowdsec-bouncer-traefik-plugin: moduleName: github.com/maxlerebourg/crowdsec-bouncer-traefik-plugin version: v1.4.2 #+END_SRC Why each piece: - =web= (=:80=) exists only to redirect to HTTPS. No TLS. - =tunnel= (=:8081=) is inbound-only from cloudflared, never exposed to LAN. Cloudflare handles TLS at the edge, so this can be plain HTTP inside Docker. - =secureweb= (=:443=) is the LAN-facing entrypoint with Let's Encrypt certs covering both =gharbeia.net= and =*.gharbeia.net=. - =internal= (=:8083=) is plain HTTP for service-to-service traffic. TLS overhead is unnecessary on the internal bridge network. - =metrics= (=:8082=) exposes Prometheus metrics, manually routed. - =dnsChallenge= with Cloudflare provider issues wildcard certs. The 60s propagation delay avoids rate-limit issues with Cloudflare's API. ** Dynamic Configuration — Middleware Shared middleware used by all routers. Defined once here, referenced by name in every router block. #+BEGIN_SRC yaml :tangle /docker/compose/traefik-dynamic.yaml http: middlewares: authentik-forwardauth: forwardAuth: address: http://authentik-server:9000/outpost.goauthentik.io/auth/traefik trustForwardHeader: true authResponseHeaders: - X-authentik-username - X-authentik-groups - X-authentik-email - X-authentik-name - X-authentik-uid security-headers: headers: customFrameOptionsValue: SAMEORIGIN contentTypeNosniff: true browserXssFilter: true referrerPolicy: no-referrer permissionsPolicy: "" customResponseHeaders: X-Robots-Tag: "noindex, nofollow" Server: "" traefik-bouncer: plugin: crowdsec-bouncer-traefik-plugin: enabled: "true" crowdsecMode: live crowdsecLapiKey: __CROWDSEC_LAPI_KEY__ crowdsecLapiHost: crowdsec:8080 crowdsecLapiScheme: http updateFrequencySec: 5 defaultDecisionLifetimeSec: 60 compress: compress: excludedContentTypes: - text/event-stream ratelimit: rateLimit: average: 100 burst: 50 #+END_SRC The auth flow: Authentik's outpost runs as a sidecar (/authentik-server/ in Docker) that validates session cookies. When a request lacks a valid session, Traefik redirects to the Authentik login page. After login, Authentik redirects back to the original URL with a session cookie. =security-headers= locks down XSS, clickjacking, and fingerprinting. The empty =permissionsPolicy= disables all browser APIs by default. =traefik-bouncer= runs CrowdSec's LAPI bouncer as a Traefik plugin. IPs flagged by CrowdSec get blocked. The LAPI key is a placeholder — fill from vault. ** Internal Routers — Authenticated (secureweb :443) These routers serve LAN browser traffic. All have Authentik Forward Auth. Backend services are referenced by Docker DNS name on the =networking= bridge. #+BEGIN_SRC yaml :tangle /docker/compose/traefik-internal.yaml http: routers: # ── Media & Streaming ───────────────────────────────────────── jellyfin: rule: "Host(`jellyfin.gharbeia.net`)" service: jellyfin-internal entryPoints: - secureweb tls: certResolver: letsencrypt middlewares: - authentik-forwardauth@file - security-headers@file - traefik-bouncer@file jellyseerr: rule: "Host(`jellyseerr.gharbeia.net`)" service: jellyseerr-internal entryPoints: - secureweb tls: certResolver: letsencrypt middlewares: - authentik-forwardauth@file - security-headers@file - traefik-bouncer@file # ── *arr Suite ──────────────────────────────────────────────── radarr: rule: "Host(`radarr.gharbeia.net`)" service: radarr-internal entryPoints: - secureweb tls: certResolver: letsencrypt middlewares: - authentik-forwardauth@file - security-headers@file - traefik-bouncer@file sonarr: rule: "Host(`sonarr.gharbeia.net`)" service: sonarr-internal entryPoints: - secureweb tls: certResolver: letsencrypt middlewares: - authentik-forwardauth@file - security-headers@file - traefik-bouncer@file lidarr: rule: "Host(`lidarr.gharbeia.net`)" service: lidarr-internal entryPoints: - secureweb tls: certResolver: letsencrypt middlewares: - authentik-forwardauth@file - security-headers@file - traefik-bouncer@file prowlarr: rule: "Host(`prowlarr.gharbeia.net`)" service: prowlarr-internal entryPoints: - secureweb tls: certResolver: letsencrypt middlewares: - authentik-forwardauth@file - security-headers@file - traefik-bouncer@file whisparr: rule: "Host(`whisparr.gharbeia.net`)" service: whisparr-internal entryPoints: - secureweb tls: certResolver: letsencrypt middlewares: - authentik-forwardauth@file - security-headers@file - traefik-bouncer@file mylar: rule: "Host(`mylar.gharbeia.net`)" service: mylar-internal entryPoints: - secureweb tls: certResolver: letsencrypt middlewares: - authentik-forwardauth@file - security-headers@file - traefik-bouncer@file lazylibrarian: rule: "Host(`lazylibrarian.gharbeia.net`)" service: lazylibrarian-internal entryPoints: - secureweb tls: certResolver: letsencrypt middlewares: - authentik-forwardauth@file - security-headers@file - traefik-bouncer@file # ── Downloaders ─────────────────────────────────────────────── sabnzbd: rule: "Host(`sabnzbd.gharbeia.net`)" service: sabnzbd-internal entryPoints: - secureweb tls: certResolver: letsencrypt middlewares: - authentik-forwardauth@file - security-headers@file - traefik-bouncer@file qbittorrent: rule: "Host(`qbittorrent.gharbeia.net`)" service: qbittorrent-internal entryPoints: - secureweb tls: certResolver: letsencrypt middlewares: - authentik-forwardauth@file - security-headers@file - traefik-bouncer@file flaresolverr: rule: "Host(`flaresolverr.gharbeia.net`)" service: flaresolverr-internal entryPoints: - secureweb tls: certResolver: letsencrypt middlewares: - authentik-forwardauth@file - security-headers@file - traefik-bouncer@file # ── Homepage / Dashboards ───────────────────────────────────── homepage: rule: "Host(`homepage.gharbeia.net`)" service: homepage-internal entryPoints: - secureweb tls: certResolver: letsencrypt middlewares: - authentik-forwardauth@file - security-headers@file - traefik-bouncer@file homarr: rule: "Host(`homarr.gharbeia.net`)" service: homarr-internal entryPoints: - secureweb tls: certResolver: letsencrypt middlewares: - authentik-forwardauth@file - security-headers@file - traefik-bouncer@file heimdall: rule: "Host(`heimdall.gharbeia.net`)" service: heimdall-internal entryPoints: - secureweb tls: certResolver: letsencrypt middlewares: - authentik-forwardauth@file - security-headers@file - traefik-bouncer@file # ── Monitoring ──────────────────────────────────────────────── grafana: rule: "Host(`grafana.gharbeia.net`)" service: grafana-internal entryPoints: - secureweb tls: certResolver: letsencrypt middlewares: - authentik-forwardauth@file - security-headers@file - traefik-bouncer@file prometheus: rule: "Host(`prometheus.gharbeia.net`)" service: prometheus-internal entryPoints: - secureweb tls: certResolver: letsencrypt middlewares: - authentik-forwardauth@file - security-headers@file - traefik-bouncer@file # ── Website (public, no auth) ───────────────────────────────── gharbeia-site: rule: "Host(`gharbeia.net`) || Host(`www.gharbeia.net`)" service: gharbeia-site-internal entryPoints: - secureweb tls: certResolver: letsencrypt # ── Management ──────────────────────────────────────────────── gitea: rule: "Host(`git.gharbeia.net`)" service: gitea-internal entryPoints: - secureweb tls: certResolver: letsencrypt middlewares: - security-headers@file - traefik-bouncer@file # No authentik-forwardauth — Gitea has native OIDC portainer: rule: "Host(`portainer.gharbeia.net`)" service: portainer-internal entryPoints: - secureweb tls: certResolver: letsencrypt middlewares: - authentik-forwardauth@file - security-headers@file - traefik-bouncer@file guacamole: rule: "Host(`guacamole.gharbeia.net`)" service: guacamole-internal entryPoints: - secureweb tls: certResolver: letsencrypt middlewares: - authentik-forwardauth@file - security-headers@file - traefik-bouncer@file headplane: rule: "Host(`headplane.gharbeia.net`) && PathPrefix(`/admin/`)" service: headplane-internal entryPoints: - secureweb tls: certResolver: letsencrypt middlewares: - authentik-forwardauth@file - security-headers@file - traefik-bouncer@file traefik-dashboard: rule: "Host(`traefik.gharbeia.net`) && (PathPrefix(`/api`) || PathPrefix(`/dashboard`))" service: traefik-dashboard-internal entryPoints: - secureweb tls: certResolver: letsencrypt middlewares: - authentik-forwardauth@file - security-headers@file - traefik-bouncer@file # ── Other ───────────────────────────────────────────────────── bazarr: rule: "Host(`bazarr.gharbeia.net`)" service: bazarr-internal entryPoints: - secureweb tls: certResolver: letsencrypt middlewares: - authentik-forwardauth@file - security-headers@file - traefik-bouncer@file tdarr: rule: "Host(`tdarr.gharbeia.net`)" service: tdarr-internal entryPoints: - secureweb tls: certResolver: letsencrypt middlewares: - authentik-forwardauth@file - security-headers@file - traefik-bouncer@file ddns-updater: rule: "Host(`ddns-updater.gharbeia.net`)" service: ddns-updater-internal entryPoints: - secureweb tls: certResolver: letsencrypt middlewares: - authentik-forwardauth@file - security-headers@file - traefik-bouncer@file stash: rule: "Host(`stash.gharbeia.net`)" service: stash-internal entryPoints: - secureweb tls: certResolver: letsencrypt middlewares: - authentik-forwardauth@file - security-headers@file - traefik-bouncer@file audiobookshelf: rule: "Host(`audiobookshelf.gharbeia.net`)" service: audiobookshelf-internal entryPoints: - secureweb tls: certResolver: letsencrypt middlewares: - authentik-forwardauth@file - security-headers@file - traefik-bouncer@file # ── External hardware (via LAN) ─────────────────────────────── synology: rule: "Host(`synology.gharbeia.net`)" service: synology-internal entryPoints: - secureweb tls: certResolver: letsencrypt middlewares: - authentik-forwardauth@file - security-headers@file - traefik-bouncer@file gateway: rule: "Host(`gateway.gharbeia.net`)" service: gateway-internal entryPoints: - secureweb tls: certResolver: letsencrypt middlewares: - authentik-forwardauth@file - security-headers@file - traefik-bouncer@file # ── Services (Docker DNS backends) ────────────────────────────── services: # VPN-routed (through Gluetun network namespace) jellyfin-internal: loadBalancer: servers: - url: "http://gluetun:8096" jellyseerr-internal: loadBalancer: servers: - url: "http://gluetun:5055" radarr-internal: loadBalancer: servers: - url: "http://gluetun:7878" sonarr-internal: loadBalancer: servers: - url: "http://gluetun:8989" lidarr-internal: loadBalancer: servers: - url: "http://gluetun:8686" prowlarr-internal: loadBalancer: servers: - url: "http://gluetun:9696" whisparr-internal: loadBalancer: servers: - url: "http://gluetun:6969" mylar-internal: loadBalancer: servers: - url: "http://gluetun:8090" lazylibrarian-internal: loadBalancer: servers: - url: "http://gluetun:5299" sabnzbd-internal: loadBalancer: servers: - url: "http://gluetun:8080" qbittorrent-internal: loadBalancer: servers: - url: "http://gluetun:8200" flaresolverr-internal: loadBalancer: servers: - url: "http://gluetun:8191" bazarr-internal: loadBalancer: servers: - url: "http://gluetun:6767" tdarr-internal: loadBalancer: servers: - url: "http://gluetun:8265" stash-internal: loadBalancer: servers: - url: "http://gluetun:7777" audiobookshelf-internal: loadBalancer: servers: - url: "http://gluetun:13378" # Direct Docker DNS homepage-internal: loadBalancer: servers: - url: "http://homepage:3000" homarr-internal: loadBalancer: servers: - url: "http://homarr:7575" heimdall-internal: loadBalancer: servers: - url: "http://heimdall:80" grafana-internal: loadBalancer: servers: - url: "http://grafana:3000" prometheus-internal: loadBalancer: servers: - url: "http://prometheus:9090" gitea-internal: loadBalancer: servers: - url: "http://gitea:3000" portainer-internal: loadBalancer: servers: - url: "http://portainer:9000" guacamole-internal: loadBalancer: servers: - url: "http://guacamole:8080" headplane-internal: loadBalancer: servers: - url: "http://headplane:3000" traefik-dashboard-internal: loadBalancer: servers: - url: "http://traefik:8080" ddns-updater-internal: loadBalancer: servers: - url: "http://ddns-updater:8310" gharbeia-site-internal: loadBalancer: servers: - url: "http://gharbeia-site:80" # External hardware synology-internal: loadBalancer: servers: - url: "https://192.168.1.8:5001" passHostHeader: true serversTransport: insecure-no-verify gateway-internal: loadBalancer: servers: - url: "https://192.168.1.1" passHostHeader: true serversTransport: insecure-no-verify serversTransports: insecure-no-verify: insecureSkipVerify: true #+END_SRC Services behind Gluetun use =http://gluetun:PORT= because those containers share Gluetun's network namespace via =network_mode: service:gluetun=. Traefik reaches them via Docker DNS through the =networking= bridge. External hardware (Synology, gateway) use =serversTransport: insecure-no-verify= because their self-signed certs would otherwise fail Traefik's TLS verification. ** Internal Routers — Authless (internal :8083) Identical routing to the authenticated routers, but on entrypoint =internal= (port 8083) and without =authentik-forwardauth@file= middleware. Used by automation, runners, and cross-VLAN tooling. #+BEGIN_SRC yaml :tangle /docker/compose/traefik-internal-noauth.yaml http: routers: jellyfin-noauth: rule: "Host(`jellyfin.gharbeia.net`)" service: jellyfin-internal entryPoints: - internal middlewares: - security-headers@file - traefik-bouncer@file jellyseerr-noauth: rule: "Host(`jellyseerr.gharbeia.net`)" service: jellyseerr-internal entryPoints: - internal middlewares: - security-headers@file - traefik-bouncer@file radarr-noauth: rule: "Host(`radarr.gharbeia.net`)" service: radarr-internal entryPoints: - internal middlewares: - security-headers@file - traefik-bouncer@file sonarr-noauth: rule: "Host(`sonarr.gharbeia.net`)" service: sonarr-internal entryPoints: - internal middlewares: - security-headers@file - traefik-bouncer@file lidarr-noauth: rule: "Host(`lidarr.gharbeia.net`)" service: lidarr-internal entryPoints: - internal middlewares: - security-headers@file - traefik-bouncer@file prowlarr-noauth: rule: "Host(`prowlarr.gharbeia.net`)" service: prowlarr-internal entryPoints: - internal middlewares: - security-headers@file - traefik-bouncer@file whisparr-noauth: rule: "Host(`whisparr.gharbeia.net`)" service: whisparr-internal entryPoints: - internal middlewares: - security-headers@file - traefik-bouncer@file mylar-noauth: rule: "Host(`mylar.gharbeia.net`)" service: mylar-internal entryPoints: - internal middlewares: - security-headers@file - traefik-bouncer@file lazylibrarian-noauth: rule: "Host(`lazylibrarian.gharbeia.net`)" service: lazylibrarian-internal entryPoints: - internal middlewares: - security-headers@file - traefik-bouncer@file sabnzbd-noauth: rule: "Host(`sabnzbd.gharbeia.net`)" service: sabnzbd-internal entryPoints: - internal middlewares: - security-headers@file - traefik-bouncer@file qbittorrent-noauth: rule: "Host(`qbittorrent.gharbeia.net`)" service: qbittorrent-internal entryPoints: - internal middlewares: - security-headers@file - traefik-bouncer@file flaresolverr-noauth: rule: "Host(`flaresolverr.gharbeia.net`)" service: flaresolverr-internal entryPoints: - internal middlewares: - security-headers@file - traefik-bouncer@file homepage-noauth: rule: "Host(`homepage.gharbeia.net`)" service: homepage-internal entryPoints: - internal middlewares: - security-headers@file - traefik-bouncer@file homarr-noauth: rule: "Host(`homarr.gharbeia.net`)" service: homarr-internal entryPoints: - internal middlewares: - security-headers@file - traefik-bouncer@file heimdall-noauth: rule: "Host(`heimdall.gharbeia.net`)" service: heimdall-internal entryPoints: - internal middlewares: - security-headers@file - traefik-bouncer@file grafana-noauth: rule: "Host(`grafana.gharbeia.net`)" service: grafana-internal entryPoints: - internal middlewares: - security-headers@file - traefik-bouncer@file prometheus-noauth: rule: "Host(`prometheus.gharbeia.net`)" service: prometheus-internal entryPoints: - internal middlewares: - security-headers@file - traefik-bouncer@file gharbeia-site-noauth: rule: "Host(`gharbeia.net`) || Host(`www.gharbeia.net`)" service: gharbeia-site-internal entryPoints: - internal middlewares: - security-headers@file - traefik-bouncer@file gitea-noauth: rule: "Host(`git.gharbeia.net`)" service: gitea-internal entryPoints: - internal middlewares: - security-headers@file - traefik-bouncer@file portainer-noauth: rule: "Host(`portainer.gharbeia.net`)" service: portainer-internal entryPoints: - internal middlewares: - security-headers@file - traefik-bouncer@file guacamole-noauth: rule: "Host(`guacamole.gharbeia.net`)" service: guacamole-internal entryPoints: - internal middlewares: - security-headers@file - traefik-bouncer@file headplane-noauth: rule: "Host(`headplane.gharbeia.net`) && PathPrefix(`/admin/`)" service: headplane-internal entryPoints: - internal middlewares: - security-headers@file - traefik-bouncer@file traefik-dashboard-noauth: rule: "Host(`traefik.gharbeia.net`) && (PathPrefix(`/api`) || PathPrefix(`/dashboard`))" service: traefik-dashboard-internal entryPoints: - internal middlewares: - security-headers@file - traefik-bouncer@file bazarr-noauth: rule: "Host(`bazarr.gharbeia.net`)" service: bazarr-internal entryPoints: - internal middlewares: - security-headers@file - traefik-bouncer@file tdarr-noauth: rule: "Host(`tdarr.gharbeia.net`)" service: tdarr-internal entryPoints: - internal middlewares: - security-headers@file - traefik-bouncer@file ddns-updater-noauth: rule: "Host(`ddns-updater.gharbeia.net`)" service: ddns-updater-internal entryPoints: - internal middlewares: - security-headers@file - traefik-bouncer@file stash-noauth: rule: "Host(`stash.gharbeia.net`)" service: stash-internal entryPoints: - internal middlewares: - security-headers@file - traefik-bouncer@file audiobookshelf-noauth: rule: "Host(`audiobookshelf.gharbeia.net`)" service: audiobookshelf-internal entryPoints: - internal middlewares: - security-headers@file - traefik-bouncer@file synology-noauth: rule: "Host(`synology.gharbeia.net`)" service: synology-internal entryPoints: - internal middlewares: - security-headers@file - traefik-bouncer@file gateway-noauth: rule: "Host(`gateway.gharbeia.net`)" service: gateway-internal entryPoints: - internal middlewares: - security-headers@file - traefik-bouncer@file #+END_SRC * Services ** gharbeia-site (Static Website) - Container: =gharbeia-site= (nginx:stable-alpine3.17-perl) - Purpose: Landing page for gharbeia.net - Docroot: =/docker/appdata/gharbeia-site/html= - Nginx config: =/docker/appdata/gharbeia-site/nginx.conf= - Traefik router: =gharbeia-site= on entrypoints =tunnel= and =secureweb= ~www.gharbeia.net~ → 301 redirect → ~gharbeia.net~ (handled by nginx) Both domains in Traefik router rule: ~Host(\`gharbeia.net\`) || Host(\`www.gharbeia.net\`)~ ** Cloudflare Tunnel "home" - Container: =cloudflared= (cloudflare/cloudflared:latest) - Config: =/docker/compose/cloudflared-config.yml= (local, unused at runtime) - Runtime: =docker compose up -d cloudflared= with =--token= (remote config from dashboard) - Local config is IGNORED when running with =--token= — ingress rules come from Cloudflare Zero Trust dashboard's public hostname configuration - DNS CNAME records must point to =.cfargotunnel.com= - Tunnel UUID: =c29295c5-946a-4ddf-bdfe-7eafcd74faa3= *** Public Hostnames (Cloudflare Dashboard) These must be added in Cloudflare Zero Trust > Networks > Tunnels > home > Public Hostnames: - *.gharbeia.net → https://traefik:443 - gharbeia.net → https://traefik:443 (must be explicit, wildcard doesn't cover root) - www.gharbeia.net → https://traefik:443 *** DNS Records gharbeia.net: - CNAME → c29295c5-946a-4ddf-bdfe-7eafcd74faa3.cfargotunnel.com (proxied) - MX → in1-smtp.messagingengine.com - MX → in2-smtp.messagingengine.com - TXT → v=spf1 include:spf.messagingengine.com ?all www.gharbeia.net: - CNAME → c29295c5-946a-4ddf-bdfe-7eafcd74faa3.cfargotunnel.com (proxied) * Authentication ** Authentik (IdP) - Provides SSO for all services - Two modes: Forward Auth (proxy-level) and OIDC (service-level) - External tunnel traffic: Forward Auth on all routers in compose labels - Internal LAN: Forward Auth on all routers in internal.yaml - Exceptions: Jellyfin (SSO plugin), Gitea (native OIDC) ** Gitea — Native OIDC - Configured in Gitea → Site Administration → Authentication Sources - Authentik OIDC provider registered - Works with native Gitea clients (no browser redirect needed) ** Jellyfin — SSO-Auth Plugin v4.0.0.4 - Plugin: SSO-Auth (via Jellyfin plugin catalog) - Authentik OIDC provider created, redirect URI: ~https://jellyfin.gharbeia.net/sso/OID/redirect/Authentik~ - Scope mapping sends ~groups~ claim in OpenID token - Plugin configured via API (=docker cp= XML into container) - SSO button added to login page via Jellyfin branding config - No Forward Auth — Jellyfin handles auth itself via plugin * LOGBOOK ** [2026-05-15 Thu 06:10] Static site launched - Setup gharbeia.net and www.gharbeia.net with nginx container - Tunnel + Traefik wiring - www → root 301 redirect in nginx config - Traefik router on both tunnel and secureweb entrypoints ** [2026-05-15 Thu 06:38] Internal authless entrypoint + domain migration - Added Traefik =internal= entrypoint (port 8083) for authless service-to-service traffic - Created =/docker/compose/traefik-internal-noauth.yaml= with 28 router copies - Exposed port 8083 (=INTERNAL_PORT_TRAEFIK=8083=) - Unbound already resolves =*.gharbeia.net= → =10.10.10.201= via =local-zone redirect= — no changes needed - Updated Gitea runner: =GITEA_INSTANCE_URL= → =http://git.gharbeia.net:8083= - Architecture settled: =:443= = browsers with auth, =:8083= = services without ** [2026-05-15 Thu 06:18] Error 1033 on gharbeia.net - Problem: CNAME for ~gharbeia.net~ pointed to old tunnel (2cd53dc4-...), not "home" tunnel (c29295c5-...) - www.gharbeia.net worked because its CNAME was correct - www → root redirect → Cloudflare tried old tunnel → 1033 - Fix: Updated CNAME DNS record via Cloudflare API (DNS token) - Lesson: Bare domain DNS must point to the same tunnel UUID as subdomains - Lesson: The local cloudflared-config.yml is decorative when running with =--token= ** [2026-05-15 Thu 03:07] Literate infrastructure established - infrastructure.org becomes the source of truth — all config files are tangle targets embedded as =#+BEGIN_SRC= blocks with absolute paths - =tangle-deploy= script installed at =/usr/local/bin/tangle-deploy= on production-1; run after git push to regenerate configs and restart services - Gitea repo: =git@git.gharbeia.net:amr/infrastructure.git= - Emacs-nox installed on production-1 for headless tangle