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
This commit is contained in:
2026-05-24 17:03:50 +02:00
commit e2d2fd9fa2
69 changed files with 14405 additions and 0 deletions
+195
View File
@@ -0,0 +1,195 @@
# RAPPORT Server-App — Architektur
> Wie die App den Compose-Stack im Schwesterrepo [SERVER-CONTAINER](../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`](../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`](https://crates.io/crates/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.