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 `truncate` — siehe `services/admin.py::_rewrite_env_file`), niemals über
Temp-Datei+`rename` (neuer Inode, vom laufenden Mount nicht mehr gesehen). Temp-Datei+`rename` (neuer Inode, vom laufenden Mount nicht mehr gesehen).
Host-seitige `sed`-Edits in `create_pod_finance.sh` selbst sind unkritisch, 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 weil das Skript die Container ohnehin bei jedem Lauf neu erstellt. Das Tool ist
Betrieb hinter einem Reverse-Proxy (eine Subdomain, Grafana als **produktiv über den Reverse-Proxy erreichbar** unter
`/grafana/`-Unterpfad) genügen zwei optionale `.env`-Variablen `https://fbwl.creature-go.com/` (App) + `/grafana/` (Grafana-Unterpfad);
(`FB_GRAFANA_PUBLIC_URL`, `FB_SESSION_COOKIE_SECURE`) — siehe aktiviert rein über die zwei optionalen `.env`-Variablen
`docs/reverse-proxy.md`. (`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 **Disaster Recovery = `BIND_DIR`-Backup + Repo + Skript.** Für vollständige
Wiederherstellung werden **beide** gebraucht: ein Backup von 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 Das Finanzberatungs-Tool ist öffentlich erreichbar unter
`https://fb.wolfundlaemmlein.de/`, Grafana als Unterpfad `/grafana/`. **`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 → Die Proxy-Konfiguration (sv005 Apache, sv006 Traefik) liegt außerhalb dieses
**sv006** (Traefik, ohne TLS, `10.8.0.6:8080`) → finance_pod Repos — dieses Dokument hält den realen Stand fest, damit Änderungen am Tool
(`127.0.0.1:8096` GUI/API, `127.0.0.1:8097` Grafana). die Kette nicht brechen.
Die Proxy-Konfiguration selbst (Traefik, Apache) liegt **außerhalb** dieses ## Kette (Datenfluss)
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.
## `.env`-Schalter (single-quoted!) ```
Browser ──HTTPS──► sv005 (DesTEngSsv005)
```sh Virtualmin/Apache, TLS-Terminierung, WireGuard-IP 10.8.0.1
FB_GRAFANA_PUBLIC_URL='https://fb.wolfundlaemmlein.de/grafana/' ServerName fbwl.creature-go.com ──ProxyPass──► http://10.8.0.6:8080
FB_SESSION_COOKIE_SECURE='true' ──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/`, **Warum `10.0.2.2`?** Traefik läuft in einem *rootless*-Podman-Pod mit
abschließender Slash empfohlen). Bewirkt: `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://<host>:8097`, Grafana an der Wurzel):
- `create_pod_finance.sh` startet Grafana mit `GF_SERVER_ROOT_URL` + - `create_pod_finance.sh` startet Grafana mit `GF_SERVER_ROOT_URL` +
`GF_SERVER_SERVE_FROM_SUB_PATH=true`; `GF_SERVER_SERVE_FROM_SUB_PATH=true`;
- interner Health-Check und interne Grafana-API-URL (`FB_GRAFANA_URL`, für - der **Pfadanteil** der URL (`/grafana`) wird zum Präfix für den internen
die GUI-Passwortänderung) erhalten automatisch den `/grafana`-Präfix; Grafana-Health-Check UND für die interne `FB_GRAFANA_URL`
- alle GUI-Links/iframes zeigen auf die öffentliche URL statt `<host>:8097`. (`http://localhost:3000/grafana`, für die GUI-Passwortänderung) — beides in
- **Leer lassen** = unveränderter Direktbetrieb auf `127.0.0.1`. `create_pod_finance.sh` automatisch abgeleitet;
- `FB_SESSION_COOKIE_SECURE='true'` — Session-Cookie nur über HTTPS. Vor der - alle GUI-Links/iframes bilden sich aus dieser URL (Jinja-Global
Internet-Freigabe setzen. Default (leer/`false`) für lokalen HTTP-Zugriff. `grafana_public_base`, siehe `app/routers/gui.py`).
Nach dem Editieren: `./create_pod_finance.sh` (übernimmt die `.env` **Achtung Secure-Cookie:** Mit `FB_SESSION_COOKIE_SECURE='true'` akzeptiert ein
unverändert, baut Pod/Container neu). HTTPS/HSTS und Login-Rate-Limiting sind **Browser** das Session-Cookie nur über HTTPS — ein direkter Login über
Proxy-Sache (Apache auf sv003 bzw. fail2ban) — außerhalb dieses Repos. `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).
## Traefik (sv006) — Datei-Provider, Beispiel ## Teil 2 — Traefik-Router (sv006, verwaltet als User `trf`)
Kein Prefix-Stripping für `/grafana` (Grafana serviert dank Traefik ist ein **geteilter** rootless-Podman-Pod `traefik_pod` (User `trf`,
`serve_from_sub_path` selbst unter dem Sub-Pfad): 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 ```yaml
# /etc/traefik/dynamic/finance.yml
http: http:
routers: routers:
finance-grafana: finance-grafana:
rule: "Host(`fb.wolfundlaemmlein.de`) && PathPrefix(`/grafana`)" rule: "Host(`fbwl.creature-go.com`) && PathPrefix(`/grafana`)"
priority: 20 priority: 120 # ueber dem Black Hole (priority 1)
service: finance-grafana service: finance-grafana
entryPoints: [web] entryPoints: ["wghttp"]
finance-app: finance-app:
rule: "Host(`fb.wolfundlaemmlein.de`)" rule: "Host(`fbwl.creature-go.com`)"
priority: 10 priority: 110
service: finance-app service: finance-app
entryPoints: [web] entryPoints: ["wghttp"]
services: services:
finance-grafana: finance-grafana:
loadBalancer: loadBalancer:
servers: 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: finance-app:
loadBalancer: loadBalancer:
servers: 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- Virtualmin → Server `fbwl.creature-go.com` → „Edit Directives" (bearbeitet
Forwarded-Proto https` ist wichtig, damit uvicorn (mit `--proxy-headers`) das `/etc/apache2/sites-available/fbwl.creature-go.com.conf`). Aufgebaut nach dem
öffentliche Schema erkennt: 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 ```apache
<VirtualHost *:443> SuexecUserGroup #1015 #1011
ServerName fb.wolfundlaemmlein.de ServerName fbwl.creature-go.com
ServerAlias www.fbwl.creature-go.com
SSLEngine on ServerAlias mail.fbwl.creature-go.com
SSLCertificateFile /etc/letsencrypt/live/fb.wolfundlaemmlein.de/fullchain.pem ServerAlias webmail.fbwl.creature-go.com
SSLCertificateKeyFile /etc/letsencrypt/live/fb.wolfundlaemmlein.de/privkey.pem 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
```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 ProxyPreserveHost On
RequestHeader set X-Forwarded-Proto "https" 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/ ProxyPass / http://10.8.0.6:8080/
ProxyPassReverse / http://10.8.0.6:8080/ ProxyPassReverse / http://10.8.0.6:8080/
</VirtualHost> 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
``` ```
## Zurück in den Lokalbetrieb 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.
`FB_GRAFANA_PUBLIC_URL` und `FB_SESSION_COOKIE_SECURE` aus der `.env` ## Cutover-Reihenfolge (falls die Kette neu aufgebaut wird)
entfernen (oder leeren) und `./create_pod_finance.sh` erneut ausführen.
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.