- Tauri-2-Admin-UI fuer den Rapport-Compose-Stack - React-Frontend (JSX, kein TS) mit Material-Symbols-Icons - Service-Cards mit Live-Stats (CPU/RAM), Logs, Restart/Stop - Backup-/Restore-System mit pg_dumpall + Retention - Container-Auto-Updates mit Pre-Backup - App-Auto-Updater (Tauri signiert) gegen latest.json im Repo-Root - HTTPS-WebUI (axum/rustls) mit Basic-Auth, CSRF, Rate-Limit, Security-Headers - Setup-Wizard: lädt Docker+Colima+Lima direct von GitHub/docker.com nach ~/.rapport/bin/ - Tray-Modus + macOS-Notifications + Auto-Recovery - Login-Item via tauri-plugin-autostart
9.6 KiB
RAPPORT Server-App — Architektur
Wie die App den Compose-Stack im Schwesterrepo SERVER-CONTAINER verwaltet, lokal anzeigt und im LAN per HTTPS erreichbar macht.
1 · Mentales Modell in einem Absatz
Die Server-App ist eine Tauri-2-App, deren Rust-Backend keine Container selbst startet, sondern docker compose gegen den existierenden Compose-Stack aufruft. Die Compose-Datei und ihr .env im SERVER-CONTAINER-Repo sind die einzige Konfigurations-Wahrheit für die Services — die App ist eine dünne Steuerschicht obendrauf. Das Tauri-Backend exponiert die Steuerung auf zwei Wegen: über Tauri-IPC an die eingebettete React-WebView (Admin-UI auf dem Server-Mac selbst) und über einen eingebauten HTTPS-Server (axum + rustls, self-signed) an Browser im LAN (für headless Mac-Mini-Deployments). Beide Endpoints sprechen die identische API, beide rendern dieselbe React-App.
Konsequenz: Schliessen des Tauri-Fensters reduziert nur in den System-Tray — Container und HTTPS-Server laufen weiter. Echtes Beenden nur über das Tray-Menü.
2 · Verzeichnis-Karte
SERVER-APP/
├── src/ React-Admin-UI (JSX, kein TS)
│ ├── main.jsx Entry
│ ├── App.jsx Root: Tabs (Status / Logs / Backup / Settings)
│ ├── api.js invoke() in Tauri, fetch() im Browser — gleicher Shape
│ └── components/
│ ├── ServiceCard.jsx eine Karte pro Service mit State-Dot
│ ├── LogViewer.jsx live tail über docker compose logs
│ ├── BackupPanel.jsx (Placeholder)
│ └── SettingsPanel.jsx Admin-WebUI-Sektion + Roh-Editor für config.env
│
├── src-tauri/ Rust-Backend
│ ├── src/
│ │ ├── main.rs 6 Zeilen
│ │ ├── lib.rs Tauri-Setup, Tray, Tick-Loop, HTTP-Server-Spawn
│ │ ├── supervisor.rs Wrapper um `docker compose` (up/down/stop/ps/logs)
│ │ ├── services.rs ServiceDef-Liste, init_compose_dir() Auto-Detect
│ │ ├── http_server.rs axum + rustls + Basic-Auth + Rate-Limit
│ │ ├── config.rs config.env Lesen/Schreiben + Default-Generation
│ │ ├── paths.rs ~/Library/.../com.rapport.server-app/...
│ │ ├── health.rs (alt: HTTP-Probes — nicht mehr aktiv genutzt,
│ │ │ wir lesen Container-State aus `docker compose ps`)
│ │ └── commands.rs #[tauri::command]-Wrapper für die UI
│ ├── Cargo.toml
│ └── tauri.conf.json
│
├── README.md
└── ARCHITECTURE.md (diese Datei)
3 · Service-Inventar
Image-Tags und Konfiguration kommen aus SERVER-CONTAINER/docker-compose.yml — siehe dort. Aktueller Stand (Mai 2026):
| Compose-Service | Image | Host-Port | Erreichbar |
|---|---|---|---|
db |
supabase/postgres:15.8.1.020 |
15432 | 127.0.0.1 (kein LAN) |
auth (GoTrue) |
supabase/gotrue:v2.158.1 |
intern | nur über kong |
rest (PostgREST) |
postgrest/postgrest:v12.2.0 |
intern | nur über kong |
realtime |
supabase/realtime:v2.30.34 |
intern | nur über kong |
storage |
supabase/storage-api:v1.11.13 |
intern | nur über kong |
kong |
kong:2.8.1 |
18000 | LAN (0.0.0.0:18000) |
app (Frontend) |
rapport-app:main (lokal gebaut) |
18080 | LAN (0.0.0.0:18080) |
db ist bewusst nur Loopback — direkter LAN-Zugriff auf die DB wäre Privesc-Surface. Alle App-Clients gehen über Kong.
4 · Supervisor
Der Rust-Supervisor (supervisor.rs) ist im Wesentlichen ein Wrapper um die docker compose-CLI:
| Funktion | Compose-Aufruf |
|---|---|
start(id) |
docker compose up -d <id> |
stop(id) |
docker compose stop <id> |
start_all() |
docker compose up -d (Compose erzwingt depends_on) |
stop_all() |
docker compose down |
logs(id) |
Hintergrund-Spawn von docker compose logs -f <id>, stdout → Ring-Buffer (1000 Zeilen) |
tick_health() |
alle 2s: docker compose ps -a --format json parsen, Container-State + Health-Status auf die UI-State-Machine mappen |
Pro Service wird die State-Machine Stopped → Starting → Running (mit Error-Zweig) aus Compose-Output abgeleitet. Healthchecks definiert Compose in der yml; unsere App liest nur den Status, probt nicht selber.
State-Mapping
compose.state → ServiceState
─────────────────────────────────────────
nicht in ps → Stopped
running + health=healthy → Running
running + health=starting → Starting
running + (kein health) → Running
restarting / dead → Error
exited / stopped / removing → Stopped
created / paused → Starting
5 · Compose-Dir-Auflösung
services::init_compose_dir() sucht beim App-Start in dieser Reihenfolge:
COMPOSE_DIR=...ausconfig.env- Env-Var
RAPPORT_COMPOSE_DIR ~/RAPPORT/SERVER-CONTAINER/<exe-parent>/../SERVER-CONTAINER/(bundled Layout)<crate>/../../SERVER-CONTAINER/(Dev-Layout)
Der erste Pfad mit einer docker-compose.yml wird genommen. Hat keiner eine, loggt die App einen Fehler und alle compose-Aufrufe scheitern hart (mit klarer Fehlermeldung).
6 · HTTP-Server (LAN-WebUI)
http_server.rs startet axum 0.7 mit axum-server-rustls-Adapter. Im Detail:
- Routen:
GET /api/services,POST /api/services/start-all|stop-all|:id/start|:id/stop,GET /api/services/:id/logs,GET /api/activity. Plus statischesdist/als Fallback (gleiche React-App wie in der Tauri-WebView). - TLS: self-signed Cert via
rcgen, SANs fürlocalhost,127.0.0.1,<hostname>,<hostname>.local. Cert + Key landen in~/Library/.../admin-ui-cert.pem(chmod 600 fürs Key). Browser warnt einmal pro Gerät. - Auth: HTTP-Basic, User
admin, Passwort ausADMIN_UI_PASSWORD. Pro IP wird ein Fehler-Counter geführt — nach 5 Fehlversuchen in 60 s ist die IP für 5 Min geblockt. - Bind: Default
127.0.0.1:9090. LAN-Freigabe ist Opt-In via Settings-Toggle (mit Confirm-Dialog).
7 · Daten- und Konfigurationspfade
| OS | App-Data-Dir |
|---|---|
| macOS | ~/Library/Application Support/com.rapport.server-app/ |
| Linux | ~/.local/share/rapport-server-app/ |
| Windows | %APPDATA%/rapport/server-app/ |
<app-data>/
├── config.env Settings (chmod 600)
├── admin-ui-cert.pem TLS-Cert für WebUI
├── admin-ui-key.pem TLS-Private-Key (chmod 600)
├── postgres/ (nicht genutzt — Compose mountet sein eigenes Volume)
├── storage/ (analog)
├── logs/ (App-eigene Logs, separat von Compose-Logs)
└── backups/ pg_dumpall-Targets (TODO Phase 7)
Container-Volumes verwaltet Compose über Docker selbst (postgres-data, storage-data-Named-Volumes). Wenn die App stoppt, bleiben die Daten erhalten.
8 · Tray-Modus
Verhalten:
| Aktion | Effekt |
|---|---|
| Fenster X klicken | Fenster wird versteckt, App läuft im Tray weiter |
| Tray-Click (Links) | Fenster wieder zeigen |
| Tray-Rechtsklick → "Show Dashboard" | gleiches Verhalten |
| Tray-Rechtsklick → "Quit RAPPORT Server" | App wirklich beenden (Container laufen weiter — das ist Compose-Sache) |
Effekt: ein headless Mac Mini kann die App im Boot-Login starten, sie verschwindet ins Tray, die Container laufen, und vom Laptop aus erreicht man die WebUI per https://hostname.local:9090.
9 · Sicherheit (Stand Hardening Pass 1)
- 🔒
dbexposed nur auf127.0.0.1— kein direkter LAN-DB-Zugriff - 🔒 WebUI Default
127.0.0.1; LAN ist bewusste Opt-In-Aktion - 🔒 WebUI über HTTPS (rustls + self-signed)
- 🔒 Basic-Auth mit IP-basiertem Rate-Limit
- 🔒
config.env+ TLS-Key chmod 600 - ⚠️ Updater-Plugin:
active: false, Pubkey-Placeholder noch drin - ⚠️ Backup-Knopf legt nur Placeholder-Datei an
- ⚠️ Container-Crash-Notification: nur via UI-Status, kein Alert-Push
10 · Bekannte Lücken / nächste Schritte
- Setup-Wizard: Docker + Colima auto-install beim Erst-Start, ohne Brew (Direct-Downloads von GitHub-Releases)
- Echter
pg_dumpall-Backup + Restore-UI - Audit-Log (wer hat wann was gestartet/gestoppt)
- Updater-Pubkey generieren oder Plugin ganz raus
- Capabilities tighten (
process:allow-exiteinschränken) - Compose-Dir +
.envins Bundle inkludieren statt Pfad-Sucherei - Linux/Windows getestet (aktuell nur Mac aarch64 verifiziert)
11 · Bezug zur ursprünglichen "kein Docker"-Vision
Die erste Version dieses Dokuments behauptete Postgres + GoTrue + ... als plattform-native Subprozesse spawnen, kein Container nötig. Diese Vision ist bewusst verworfen:
- Realtime (Erlang/BEAM) und Storage (Node-Bundle) haben keine offiziellen Mac-aarch64-Binaries
- GoTrue ships zwar offiziell
darwin-arm64, aber das Asset enthält fälschlicherweise ein Linux-ELF - Wir müssten eine eigene Build-Pipeline pro Service pro Plattform unterhalten → exponentieller Wartungsaufwand
Pragmatik: Docker-CLI (Apache 2.0, ~30 MB) ist universell und open-source, Colima liefert den Daemon Mac-nativ ohne Docker Desktop, und unsere App polished das Erlebnis darüber so weit, dass Endnutzer die Container-Schicht nicht mehr sehen.