Files
bin/docs/reverse-proxy.md
2026-07-24 09:45:06 +02:00

12 KiB

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):

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://<host>: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:

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

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
<Directory /home/fbwl/public_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
</Directory>
<Directory /home/fbwl/cgi-bin>
    Require all granted
    AllowOverride All Options=ExecCGI,Includes,IncludesNOEXEC,Indexes,MultiViews,SymLinksIfOwnerMatch
</Directory>
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

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
<Directory /home/fbwl/public_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
</Directory>
<Directory /home/fbwl/cgi-bin>
    Require all granted
    AllowOverride All Options=ExecCGI,Includes,IncludesNOEXEC,Indexes,MultiViews,SymLinksIfOwnerMatch
</Directory>
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

# 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.