diff --git a/infrastructure.org b/infrastructure.org index 61c7234..73adef5 100644 --- a/infrastructure.org +++ b/infrastructure.org @@ -70,6 +70,7 @@ if [ ! -f "$ORG_FILE" ]; then echo "ERROR: $ORG_FILE not found in $REPO_DIR" exit 1 fi +chmod +x "$0" echo "=== Tangling $ORG_FILE ===" emacs --batch -Q --load /usr/share/emacs/28.2/lisp/org/org-loaddefs.el \ --eval "(require 'org)" \ @@ -84,6 +85,10 @@ if [ -f /docker/appdata/traefik/traefik.yaml ] || \ echo 'Traefik config changed -- restarting...' docker compose up -d traefik fi +if [ -f /etc/systemd/system/oidc-proxy.service ]; then + echo 'Systemd unit changed -- reloading...' + systemctl daemon-reload +fi if [ -f /docker/compose/docker-compose.yaml ]; then echo 'Docker compose changed -- restarting all services' docker compose up -d 2>&1 | tail -5 @@ -1238,6 +1243,101 @@ services: - traefik.http.services.gitea.loadbalancer.server.port=3000 #+END_SRC +*** Gitea OIDC — Discovery Rewrite Proxy + +Gitea's OIDC provider uses Authentik's auto-discovery URL to fetch +endpoint metadata. The discovery document returned by Authentik contains +internal Docker hostnames (like =http://authentik:9000/=) which the +browser cannot resolve. Gitea's =openidConnect= provider in v1.25.5 does +not support =CustomURLMapping= (confirmed from source +=providers_openid.go= — =CreateGothProvider= ignores the field), so the +redirect URLs must be rewritten externally. + +The fix is a small HTTP proxy at =172.28.10.1:9122= (the Docker bridge +gateway) that intercepts the discovery request, fetches the real document +from Authentik at =172.28.10.33:9000=, and rewrites all internal URLs to +=https://auth.gharbeia.net=. + +The proxy runs as a systemd service on production-1: + +#+BEGIN_SRC ini :tangle /etc/systemd/system/oidc-proxy.service +[Unit] +Description=OIDC discovery rewrite proxy for Gitea +After=network-online.target docker.service +Wants=network-online.target + +[Service] +Type=simple +ExecStart=/usr/bin/python3 /docker/compose/oidc-rewrite-proxy.py +Restart=always +RestartSec=5 +User=root + +[Install] +WantedBy=multi-user.target +#+END_SRC + +The proxy lives at =/docker/compose/oidc-rewrite-proxy.py= on production-1: + +#+BEGIN_SRC python :tangle /docker/compose/oidc-rewrite-proxy.py +#!/usr/bin/env python3 +\"\"\"Minimal OIDC discovery rewrite proxy for Gitea's openidConnect provider. + +Listens on 0.0.0.0:9122. Rewrites internal Authentik URLs to the public +HTTPS URL so Gitea redirects browsers to auth.gharbeia.net. +\"\"\" + +import json +import sys +from http.server import HTTPServer, BaseHTTPRequestHandler +from urllib.request import urlopen, Request + +UPSTREAM = "http://172.28.10.33:9000" +PUBLIC = "https://auth.gharbeia.net" +DISCOVERY_PATH = "/application/o/gitea-oidc/.well-known/openid-configuration" + + +class RewriteHandler(BaseHTTPRequestHandler): + def do_GET(self): + if self.path != DISCOVERY_PATH: + self.send_response(404) + self.end_headers() + self.wfile.write(b"Not found") + return + + try: + upstream_url = f"{UPSTREAM}{DISCOVERY_PATH}" + req = Request(upstream_url, headers={"Accept": "application/json"}) + with urlopen(req, timeout=5) as resp: + body = resp.read().decode() + body = body.replace(UPSTREAM, PUBLIC) + self.send_response(200) + self.send_header("Content-Type", "application/json") + self.end_headers() + self.wfile.write(body.encode()) + except Exception as e: + self.send_response(502) + self.end_headers() + self.wfile.write(f"Proxy error: {e}".encode()) + + def log_message(self, format, *args): + pass + + +if __name__ == "__main__": + server = HTTPServer(("0.0.0.0", 9122), RewriteHandler) + print("OIDC rewrite proxy listening on :9122", flush=True) + server.serve_forever() +#+END_SRC + +The systemd service is tangled from the block above. After deploying, enable +with =systemctl enable --now oidc-proxy=. + +Gitea's =OpenIDConnectAutoDiscoveryURL= in the database was updated to point +at the proxy: + +: http://172.28.10.1:9122/application/o/gitea-oidc/.well-known/openid-configuration + ** Infrastructure Services Core data and networking services that everything depends on. @@ -1632,6 +1732,23 @@ wp-config: =/docker/appdata/khaledfahmy-site/html/wp-config.php= - Created infrastructure.org as source of truth - Added Forward Auth to internal LAN routers +** [2026-06-06 Sat] Systemd tangle-deploy fixed — ob-shell, chmod, OIDC proxy +- Replaced stale /usr/local/bin/tangle-deploy (missing (require 'ob-shell)) + with the correct repo version. Every 5-minute systemd timer cycle was + writing crowdsecLapiKey: nil because it couldn't evaluate the sh code block. +- Added chmod +x "$0" to tangle-deploy.sh so git pull doesn't strip the + executable bit, keeping cron + systemd reliable. +- Created and deployed oidc-rewrite-proxy.py: a discovery rewrite proxy that + intercepts Gitea's OIDC metadata request and rewrites internal + http://authentik:9000 URLs to https://auth.gharbeia.net. +- Registered oidc-proxy as a systemd service on production-1 (port 9122). +- Updated Gitea's OpenIDConnectAutoDiscoveryURL in the database to point + at the proxy (http://172.28.10.1:9122/...). +- Reverted Gitea's CustomURLMapping to {} (confirmed unused by source code). +- Fixed qBittorrent v5.2.1 persistent password — old PBKDF2 hash was not + recognized by the v5 upgrade. Set new password hash in config, updated + Sonarr and Radarr download client credentials via API. + ** [2026-06-02 Tue] Gluetun port cleanup — only downloaders behind VPN - Removed 20+ stale port mappings from gluetun (services migrated off VPN) - Updated all internal router URLs: standalone services now referenced by