e2d2fd9fa2
- 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
196 lines
9.6 KiB
Markdown
196 lines
9.6 KiB
Markdown
# 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.
|