Files
RAPPORT-SERVER-APP/ARCHITECTURE.md
T
karim e2d2fd9fa2 Initial source: RAPPORT Server-App v0.1.0
- 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
2026-05-24 17:03:50 +02:00

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:

  1. COMPOSE_DIR=... aus config.env
  2. Env-Var RAPPORT_COMPOSE_DIR
  3. ~/RAPPORT/SERVER-CONTAINER/
  4. <exe-parent>/../SERVER-CONTAINER/ (bundled Layout)
  5. <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 statisches dist/ als Fallback (gleiche React-App wie in der Tauri-WebView).
  • TLS: self-signed Cert via rcgen, SANs für localhost, 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 aus ADMIN_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)

  • 🔒 db exposed nur auf 127.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-exit einschränken)
  • Compose-Dir + .env ins 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.