docs: Reverse-Proxy-Doku auf reales Live-Setup (fbwl.creature-go.com via sv005/Traefik)

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
2026-07-24 09:45:06 +02:00
parent 6ba6ffa9df
commit 81a0ebaf9d
2 changed files with 235 additions and 64 deletions

View File

@@ -131,11 +131,15 @@ App muss sie **in-place** überschreiben (`open` im Modus `r+`, `flock`,
`truncate` — siehe `services/admin.py::_rewrite_env_file`), niemals über
Temp-Datei+`rename` (neuer Inode, vom laufenden Mount nicht mehr gesehen).
Host-seitige `sed`-Edits in `create_pod_finance.sh` selbst sind unkritisch,
weil das Skript die Container ohnehin bei jedem Lauf neu erstellt. Für den
Betrieb hinter einem Reverse-Proxy (eine Subdomain, Grafana als
`/grafana/`-Unterpfad) genügen zwei optionale `.env`-Variablen
(`FB_GRAFANA_PUBLIC_URL`, `FB_SESSION_COOKIE_SECURE`) — siehe
`docs/reverse-proxy.md`.
weil das Skript die Container ohnehin bei jedem Lauf neu erstellt. Das Tool ist
**produktiv über den Reverse-Proxy erreichbar** unter
`https://fbwl.creature-go.com/` (App) + `/grafana/` (Grafana-Unterpfad);
aktiviert rein über die zwei optionalen `.env`-Variablen
(`FB_GRAFANA_PUBLIC_URL`, `FB_SESSION_COOKIE_SECURE`), leer ⇒ Lokalbetrieb.
Kette sv005 (Apache/TLS) → WireGuard → sv006 Traefik → Pod, **vollständig
dokumentiert in `docs/reverse-proxy.md`** (dort auch die Regel: Traefik-Backends
`10.0.2.2:PORT`, nicht `127.0.0.1`). Achtung: mit `FB_SESSION_COOKIE_SECURE=true`
geht Browser-Login nur noch über die HTTPS-Domain (curl-Tests weiter ok).
**Disaster Recovery = `BIND_DIR`-Backup + Repo + Skript.** Für vollständige
Wiederherstellung werden **beide** gebraucht: ein Backup von

View File

@@ -1,91 +1,258 @@
# Reverse-Proxy-Betrieb (Ausbaustufe 10)
# Reverse-Proxy-Betrieb (Ausbaustufe 10) — LIVE
Ziel: Erreichbarkeit über **eine** Subdomain
`https://fb.wolfundlaemmlein.de/`, Grafana als Unterpfad `/grafana/`.
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).
Kette: Internet → **sv003** (Apache, TLS-Terminierung) → WireGuard-VPN →
**sv006** (Traefik, ohne TLS, `10.8.0.6:8080`) → finance_pod
(`127.0.0.1:8096` GUI/API, `127.0.0.1:8097` Grafana).
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.
Die Proxy-Konfiguration selbst (Traefik, Apache) liegt **außerhalb** dieses
Repos — die folgenden Snippets sind Vorlagen. Tool-seitig genügt es, in der
`.env` unter `~/.local/share/finance_pod/.env` zwei Variablen zu setzen und
`./create_pod_finance.sh` erneut auszuführen.
## Kette (Datenfluss)
## `.env`-Schalter (single-quoted!)
```sh
FB_GRAFANA_PUBLIC_URL='https://fb.wolfundlaemmlein.de/grafana/'
FB_SESSION_COOKIE_SECURE='true'
```
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)
```
- `FB_GRAFANA_PUBLIC_URL` — öffentliche Grafana-Basis-URL (mit `/grafana/`,
abschließender Slash empfohlen). Bewirkt:
- `create_pod_finance.sh` startet Grafana mit `GF_SERVER_ROOT_URL` +
`GF_SERVER_SERVE_FROM_SUB_PATH=true`;
- interner Health-Check und interne Grafana-API-URL (`FB_GRAFANA_URL`, für
die GUI-Passwortänderung) erhalten automatisch den `/grafana`-Präfix;
- alle GUI-Links/iframes zeigen auf die öffentliche URL statt `<host>:8097`.
- **Leer lassen** = unveränderter Direktbetrieb auf `127.0.0.1`.
- `FB_SESSION_COOKIE_SECURE='true'` — Session-Cookie nur über HTTPS. Vor der
Internet-Freigabe setzen. Default (leer/`false`) für lokalen HTTP-Zugriff.
**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`.
Nach dem Editieren: `./create_pod_finance.sh` (übernimmt die `.env`
unverändert, baut Pod/Container neu). HTTPS/HSTS und Login-Rate-Limiting sind
Proxy-Sache (Apache auf sv003 bzw. fail2ban) — außerhalb dieses Repos.
**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.
## Traefik (sv006) — Datei-Provider, Beispiel
## Teil 1 — finance_pod (`.env` auf sv006)
Kein Prefix-Stripping für `/grafana` (Grafana serviert dank
`serve_from_sub_path` selbst unter dem Sub-Pfad):
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://<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`:
```yaml
# /etc/traefik/dynamic/finance.yml
http:
routers:
finance-grafana:
rule: "Host(`fb.wolfundlaemmlein.de`) && PathPrefix(`/grafana`)"
priority: 20
rule: "Host(`fbwl.creature-go.com`) && PathPrefix(`/grafana`)"
priority: 120 # ueber dem Black Hole (priority 1)
service: finance-grafana
entryPoints: [web]
entryPoints: ["wghttp"]
finance-app:
rule: "Host(`fb.wolfundlaemmlein.de`)"
priority: 10
rule: "Host(`fbwl.creature-go.com`)"
priority: 110
service: finance-app
entryPoints: [web]
entryPoints: ["wghttp"]
services:
finance-grafana:
loadBalancer:
servers:
- url: "http://127.0.0.1:8097"
- url: "http://10.0.2.2:8097" # slirp4netns-Host-Loopback, NICHT 127.0.0.1
finance-app:
loadBalancer:
servers:
- url: "http://127.0.0.1:8096"
- url: "http://10.0.2.2:8096"
```
## Apache (sv003) — VHost, Beispiel
## Teil 3 — sv005 Apache (Virtualmin, Domain fbwl.creature-go.com)
TLS terminiert hier; Weiterleitung an Traefik über die WireGuard-IP. `X-
Forwarded-Proto https` ist wichtig, damit uvicorn (mit `--proxy-headers`) das
öffentliche Schema erkennt:
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
<VirtualHost *:443>
ServerName fb.wolfundlaemmlein.de
SSLEngine on
SSLCertificateFile /etc/letsencrypt/live/fb.wolfundlaemmlein.de/fullchain.pem
SSLCertificateKeyFile /etc/letsencrypt/live/fb.wolfundlaemmlein.de/privkey.pem
ProxyPreserveHost On
RequestHeader set X-Forwarded-Proto "https"
ProxyPass / http://10.8.0.6:8080/
ProxyPassReverse / http://10.8.0.6:8080/
</VirtualHost>
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
```
## Zurück in den Lokalbetrieb
### 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
<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
```
`FB_GRAFANA_PUBLIC_URL` und `FB_SESSION_COOKIE_SECURE` aus der `.env`
entfernen (oder leeren) und `./create_pod_finance.sh` erneut ausführen.
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.