# Reverse-Proxy-Betrieb (Ausbaustufe 10) — LIVE Das Finanzberatungs-Tool ist öffentlich erreichbar unter **`https://fbwl.creature-go.com/`** (App/GUI) mit **Grafana als Unterpfad `https://fbwl.creature-go.com/grafana/`**. Aktiviert wird das ausschließlich über zwei `.env`-Schalter auf sv006; ohne sie läuft der unveränderte Lokalbetrieb auf `127.0.0.1` weiter (per Live-Redeploy + Sub-Pfad-Smoke belegt). Die Proxy-Konfiguration (sv005 Apache, sv006 Traefik) liegt außerhalb dieses Repos — dieses Dokument hält den realen Stand fest, damit Änderungen am Tool die Kette nicht brechen. ## Kette (Datenfluss) ``` Browser ──HTTPS──► sv005 (DesTEngSsv005) Virtualmin/Apache, TLS-Terminierung, WireGuard-IP 10.8.0.1 ServerName fbwl.creature-go.com ──ProxyPass──► http://10.8.0.6:8080 ──WireGuard (wg0)──► sv006 (DesTEngSsv006 — DIESER Rechner) Traefik: rootless-Podman-Pod "traefik_pod" (User trf), Entrypoint "wghttp" published auf 10.8.0.6:8080 Router Host(fbwl.creature-go.com) ──► http://10.0.2.2:8096 (App/API) Router Host(fbwl.creature-go.com) && /grafana ──► http://10.0.2.2:8097 (Grafana) finance_pod: 127.0.0.1:8096 (App), 127.0.0.1:8097 (Grafana) ``` **Warum `10.0.2.2`?** Traefik läuft in einem *rootless*-Podman-Pod mit `slirp4netns:allow_host_loopback=true`. Aus Sicht des Traefik-Containers ist `127.0.0.1` der **Container selbst**; die auf dem Host an `127.0.0.1:8096/8097` veröffentlichten Pod-Ports erreicht er über die slirp4netns-Host-Loopback-Adresse **`10.0.2.2`**. Backend-URLs im Traefik-Router deshalb IMMER `10.0.2.2:PORT`, niemals `127.0.0.1:PORT`. **Warum funktioniert `X-Forwarded-Proto` sauber?** sv005 hat die WireGuard-IP `10.8.0.1`, und Traefiks Entrypoint `wghttp` vertraut per `forwardedHeaders.trustedIPs: 10.8.0.1/32` genau dieser Quelle. Das von sv005 gesetzte `X-Forwarded-Proto: https` wird also übernommen und (via uvicorn `--proxy-headers`) bis zur App durchgereicht. ## Teil 1 — finance_pod (`.env` auf sv006) In `~/.local/share/finance_pod/.env` (alle Werte **single-quoted**): ```sh FB_GRAFANA_PUBLIC_URL='https://fbwl.creature-go.com/grafana/' FB_SESSION_COOKIE_SECURE='true' ``` Übernehmen: `./stop_finance_pod.sh` → `.env` editieren → `./create_pod_finance.sh`. Wirkung von `FB_GRAFANA_PUBLIC_URL` (leer ⇒ Lokalbetrieb, Grafana-Fallback `http://:8097`, Grafana an der Wurzel): - `create_pod_finance.sh` startet Grafana mit `GF_SERVER_ROOT_URL` + `GF_SERVER_SERVE_FROM_SUB_PATH=true`; - der **Pfadanteil** der URL (`/grafana`) wird zum Präfix für den internen Grafana-Health-Check UND für die interne `FB_GRAFANA_URL` (`http://localhost:3000/grafana`, für die GUI-Passwortänderung) — beides in `create_pod_finance.sh` automatisch abgeleitet; - alle GUI-Links/iframes bilden sich aus dieser URL (Jinja-Global `grafana_public_base`, siehe `app/routers/gui.py`). **Achtung Secure-Cookie:** Mit `FB_SESSION_COOKIE_SECURE='true'` akzeptiert ein **Browser** das Session-Cookie nur über HTTPS — ein direkter Login über `http://127.0.0.1:8096` funktioniert dann nicht mehr, nur noch über die HTTPS-Domain. Automatisierte Tests mit `curl` (`-c/-b`) laufen weiter, weil curl das Secure-Flag ignoriert (bewährt bei Redeploy-Smoke-Tests). ## Teil 2 — Traefik-Router (sv006, verwaltet als User `trf`) Traefik ist ein **geteilter** rootless-Podman-Pod `traefik_pod` (User `trf`, Config unter `/home/trf/.local/share/traefik_pod/`; als `wlfb` NICHT lesbar). Statik: Entrypoint `wghttp` auf `:8080` (published `10.8.0.6:8080`), `forwardedHeaders.trustedIPs: 10.8.0.1/32`; File-Provider `dynamic/` mit `watch: true` (Änderungen laden automatisch, kein Neustart, andere Dienste unberührt). **Wichtig — Black Hole:** Es gibt einen Catch-all-Router (`HostRegexp('{any:.*}')`, `priority: 1`, Middleware `ipAllowList 127.0.0.1/32`), der jeden **nicht explizit gerouteten** Host sofort mit **403** abweist. Jeder öffentliche Dienst braucht daher einen eigenen Router mit `entryPoints: ["wghttp"]` und `priority > 1`. Router-Datei `dynamic/fbwl.yml`: ```yaml http: routers: finance-grafana: rule: "Host(`fbwl.creature-go.com`) && PathPrefix(`/grafana`)" priority: 120 # ueber dem Black Hole (priority 1) service: finance-grafana entryPoints: ["wghttp"] finance-app: rule: "Host(`fbwl.creature-go.com`)" priority: 110 service: finance-app entryPoints: ["wghttp"] services: finance-grafana: loadBalancer: servers: - url: "http://10.0.2.2:8097" # slirp4netns-Host-Loopback, NICHT 127.0.0.1 finance-app: loadBalancer: servers: - url: "http://10.0.2.2:8096" ``` ## Teil 3 — sv005 Apache (Virtualmin, Domain fbwl.creature-go.com) Virtualmin → Server `fbwl.creature-go.com` → „Edit Directives" (bearbeitet `/etc/apache2/sites-available/fbwl.creature-go.com.conf`). Aufgebaut nach dem Muster des bestehenden `affine.creature-go.com`-vhosts: `/.well-known` lokal (ACME/Let's-Encrypt-Erneuerung), HTTP→HTTPS-Redirect, dann Weiterleitung der ganzen Domain an Traefik (`10.8.0.6:8080`) mit erhaltenem Host-Header. TLS-Cert verwaltet Virtualmin/Let's Encrypt. ### Port 80 ```apache SuexecUserGroup #1015 #1011 ServerName fbwl.creature-go.com ServerAlias www.fbwl.creature-go.com ServerAlias mail.fbwl.creature-go.com ServerAlias webmail.fbwl.creature-go.com ServerAlias admin.fbwl.creature-go.com DocumentRoot /home/fbwl/public_html ErrorLog /var/log/virtualmin/fbwl.creature-go.com_error_log CustomLog /var/log/virtualmin/fbwl.creature-go.com_access_log combined ScriptAlias /cgi-bin/ /home/fbwl/cgi-bin/ DirectoryIndex index.php index.htm index.html Options -Indexes +IncludesNOEXEC +SymLinksIfOwnerMatch +ExecCGI Require all granted AllowOverride All Options=ExecCGI,Includes,IncludesNOEXEC,Indexes,MultiViews,SymLinksIfOwnerMatch AddHandler fcgid-script .php AddHandler fcgid-script .php8.4 FCGIWrapper /home/fbwl/fcgi-bin/php8.4.fcgi .php FCGIWrapper /home/fbwl/fcgi-bin/php8.4.fcgi .php8.4 Require all granted AllowOverride All Options=ExecCGI,Includes,IncludesNOEXEC,Indexes,MultiViews,SymLinksIfOwnerMatch ProxyPass /.well-known ! RewriteEngine on RewriteCond %{HTTP_HOST} =webmail.fbwl.creature-go.com RewriteRule ^/(?!\.well-known)(.*)$ https://fbwl.creature-go.com:20000/ [R=301,L] RewriteCond %{HTTP_HOST} =admin.fbwl.creature-go.com RewriteRule ^/(?!\.well-known)(.*)$ https://fbwl.creature-go.com:10000/ [R=301,L] # --- Finanzberatungs-Tool: HTTP -> HTTPS (ausser ACME-Challenge) --- RewriteCond %{HTTPS} off RewriteCond %{REQUEST_URI} !^/\.well-known/ RewriteRule ^(.*)$ https://%{HTTP_HOST}$1 [R=301,L] RemoveHandler .php RemoveHandler .php8.4 FcgidMaxRequestLen 1073741824 ``` ### Port 443 ```apache SuexecUserGroup #1015 #1011 ServerName fbwl.creature-go.com ServerAlias www.fbwl.creature-go.com ServerAlias mail.fbwl.creature-go.com ServerAlias webmail.fbwl.creature-go.com ServerAlias admin.fbwl.creature-go.com DocumentRoot /home/fbwl/public_html ErrorLog /var/log/virtualmin/fbwl.creature-go.com_error_log CustomLog /var/log/virtualmin/fbwl.creature-go.com_access_log combined ScriptAlias /cgi-bin/ /home/fbwl/cgi-bin/ DirectoryIndex index.php index.htm index.html Options -Indexes +IncludesNOEXEC +SymLinksIfOwnerMatch +ExecCGI Require all granted AllowOverride All Options=ExecCGI,Includes,IncludesNOEXEC,Indexes,MultiViews,SymLinksIfOwnerMatch AddHandler fcgid-script .php AddHandler fcgid-script .php8.4 FCGIWrapper /home/fbwl/fcgi-bin/php8.4.fcgi .php FCGIWrapper /home/fbwl/fcgi-bin/php8.4.fcgi .php8.4 Require all granted AllowOverride All Options=ExecCGI,Includes,IncludesNOEXEC,Indexes,MultiViews,SymLinksIfOwnerMatch ProxyPass /.well-known ! RewriteEngine on RewriteCond %{HTTP_HOST} =webmail.fbwl.creature-go.com RewriteRule ^/(?!\.well-known)(.*)$ https://fbwl.creature-go.com:20000/ [R=301,L] RewriteCond %{HTTP_HOST} =admin.fbwl.creature-go.com RewriteRule ^/(?!\.well-known)(.*)$ https://fbwl.creature-go.com:10000/ [R=301,L] # --- Finanzberatungs-Tool: Weiterleitung an Traefik (sv006, wghttp) --- ProxyPreserveHost On RequestHeader set X-Forwarded-Proto "https" # WebSocket (Grafana Live) an Traefik durchreichen RewriteCond %{HTTP:Upgrade} websocket [NC] RewriteCond %{HTTP:Connection} upgrade [NC] RewriteRule .* ws://10.8.0.6:8080%{REQUEST_URI} [P,L] # Haupt-Weiterleitung (Traefik trennt App vs. /grafana selbst) ProxyPass / http://10.8.0.6:8080/ ProxyPassReverse / http://10.8.0.6:8080/ RemoveHandler .php RemoveHandler .php8.4 FcgidMaxRequestLen 1073741824 SSLEngine on SSLCertificateFile /etc/ssl/virtualmin/17848735353695059/ssl.cert SSLCertificateKeyFile /etc/ssl/virtualmin/17848735353695059/ssl.key SSLProtocol all -SSLv2 -SSLv3 -TLSv1 -TLSv1.1 SSLCACertificateFile /etc/ssl/virtualmin/17848735353695059/ssl.ca ``` Hinweis: `www.`/`mail.fbwl…` gehen (wie bei affine) über den Proxy an Traefik und landen dort mangels Router im Black Hole (403) — fürs Tool irrelevant. ## Cutover-Reihenfolge (falls die Kette neu aufgebaut wird) 1. DNS `fbwl.creature-go.com` → sv005; Cert via Virtualmin/Let's Encrypt. 2. Traefik-Router (Teil 2) ablegen — `watch` lädt automatisch. 3. sv005 Apache (Teil 3) eintragen — Apache reload. 4. Testen (unten), solange Tool noch im Lokalmodus (`/grafana` kommt erst mit Schritt 5). 5. `.env`-Cutover (Teil 1) + Redeploy — **`FB_SESSION_COOKIE_SECURE='true'` zuletzt**. ## Verifikation ```bash # Von aussen: curl -sI https://fbwl.creature-go.com/login | head -1 # -> 200 curl -sI https://fbwl.creature-go.com/grafana/api/health | head -1 # -> 200 curl -I http://fbwl.creature-go.com/ 2>&1 | head -3 # -> 301 auf https # Auf sv006 (Kette bis Traefik, Host-Header simuliert): for p in /login /grafana/api/health; do curl -sS --max-time 5 -o /dev/null -w "$p -> %{http_code}\n" \ -H 'Host: fbwl.creature-go.com' "http://10.8.0.6:8080$p" done ``` Der Grafana-iframe erfordert eine Grafana-Session (Embedding an, anonym aus): in der GUI einmal „Grafana anmelden" (→ `…/grafana`) mit dem gemeinsamen Passwort. ## Für künftige Claude-Sessions — was das für Änderungen am Tool bedeutet - **Domain/Links des Tools hängen ausschließlich an `FB_GRAFANA_PUBLIC_URL`** (+ Templates via `grafana_public_base`). Kein Hostname im Code/Image — ein Domainwechsel ist eine reine `.env`-Änderung + Redeploy (plus je ein Eintrag in Traefik-Router und sv005-Apache). - **Neuer öffentlicher Pfad/Dienst hinter derselben Domain**: Traefik-Router in `dynamic/` mit `entryPoints: ["wghttp"]`, `priority > 1`, Backend `http://10.0.2.2:PORT`; an sv005 meist nichts nötig, weil `ProxyPass /` die ganze Domain weiterreicht und Traefik nach Pfad trennt. - **Neuer Port am Pod**: in `create_pod_finance.sh` als `127.0.0.1:PORT` veröffentlichen → Traefik erreicht ihn als `10.0.2.2:PORT`. - **Grafana-Sub-Pfad**: `GF_SERVER_ROOT_URL` + `serve_from_sub_path` und die präfixierten internen URLs werden in `create_pod_finance.sh` aus `FB_GRAFANA_PUBLIC_URL` abgeleitet — den Pfad (`/grafana`) nicht ohne Grund ändern; er steckt auch im Health-Check und in `FB_GRAFANA_URL`. - **Lokaler Test trotz Live-Proxy**: `.env`-Schalter leeren (echter Lokalmodus) ODER mit `curl` testen; ein echter Browser braucht die HTTPS-Domain, weil das Session-Cookie `Secure` ist. - **Traefik ist geteilt** (User `trf`): dessen Config ist als `wlfb` nicht editierbar. Router-/Entrypoint-Änderungen müssen als `trf` erfolgen; den Black-Hole-Router und fremde Dienste nicht anfassen.