commit 26e95185e3b3763fc227b9667d93ad83ef311169 Author: Hermes Date: Fri May 15 07:07:19 2026 +0000 chore: initial infrastructure docs scaffold diff --git a/infrastructure.org b/infrastructure.org new file mode 100644 index 0000000..f37e9b3 --- /dev/null +++ b/infrastructure.org @@ -0,0 +1,168 @@ +#+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. + +* 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 + +* Traefik Configuration + +** Source of Truth +- Compose labels: =/docker/compose/docker-compose.yaml= and companion files +- Internal routers (auth): =/docker/compose/traefik-internal.yaml= +- Internal routers (noauth): =/docker/compose/traefik-internal-noauth.yaml= +- Runtime copy: =/docker/appdata/traefik/internal.yaml= and =internal-noauth.yaml= +- Deploy: =restart.sh= copies all yaml files to =/docker/appdata/traefik/= then =docker compose up -d traefik= + +** Entrypoints +- =tunnel= (port 8081) :: Cloudflare Tunnel traffic (external) +- =secureweb= (port 443) :: Internal LAN traffic, TLS, with Authentik Forward Auth +- =internal= (port 8083) :: Internal service-to-service, NO Authentik auth, HTTP only + +** External Routers (tunnel entrypoint) +- Defined in compose labels +- All behind Authentik Forward Auth middleware (except Jellyfin, Gitea) + +** Internal Routers — Authenticated + +Defined in =traefik-internal.yaml= (=internal.yaml= at runtime) +All on entrypoint =secureweb= (port 443, TLS) +All behind Authentik Forward Auth middleware +Common middleware chain: =auth@file, security-headers@file, traefik-bouncer@file= + +** Internal Routers — Authless + +Defined in =traefik-internal-noauth.yaml= (=internal-noauth.yaml= at runtime) +All on entrypoint =internal= (port 8083, HTTP) +No Authentik Forward Auth (no =authentik-forwardauth@file= middleware) +Same services as the authenticated routers, same backend URLs +Common middleware chain: =security-headers@file, traefik-bouncer@file= +Used for: runners, cross-VLAN tooling, service-to-service API calls + +Services already authless on secureweb (Gitea, gharbeia-site) also have +noauth copies for consistency. + +* 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 +- Copied to runtime: `/docker/appdata/traefik/internal-noauth.yaml` +- Exposed port 8083 on traefik container (`INTERNAL_PORT_TRAEFIK=8083`) +- Added to restart.sh deployment: `sudo cp traefik-internal-noauth.yaml $FOLDER_FOR_DATA/traefik/internal-noauth.yaml` +- Unbound already resolves `*.gharbeia.net` → `10.10.10.201` via `local-zone redirect` — no changes needed +- Docker containers already inherit this DNS through embedded DNS (127.0.0.11) → host DNS → Unbound +- Updated Gitea runner: `GITEA_INSTANCE_URL: http://10.10.10.201:3001` → `http://git.gharbeia.net:8083` +- Verified: runner registers and communicates through domain-based URL +- Gitea config already used domains: `ROOT_URL = https://git.gharbeia.net/` +- Gluetun extra_hosts kept as-is (safety net for VPN namespace DNS leaks) +- Architecture: browsers use =:443= (secureweb, with auth); services/automation use =:8083= (internal, no auth) + +** [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) +- Config.yml on disk had correct entries but ISN'T used at runtime (tunnel runs with =--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=