Das Backend kann jetzt — analog zum Mail-Client — als Windows-Dienst laufen. - main() refaktoriert: App-Logik in run_app(shutdown, service_mode); eigene tokio-Runtime statt #[tokio::main]. Windows startet zuerst den SCM-Dispatcher, fällt bei interaktivem Start auf Konsolenmodus zurück (--console erzwingt ihn). - src/service.rs (windows-only): SCM-Integration via windows-service-Crate, Stop/Shutdown-Handler, Running/Stopped-Status. Setzt das Arbeitsverzeichnis aufs EXE-Verzeichnis (Dienst startet sonst in System32), damit config.toml/ data/logs daneben liegen. Fallback-Log bei Boot-Fehler. - Graceful Shutdown: GSD-Lizenz-Freigabe in den Serve-Wrapper gezogen (greift in beiden Modi); Stop-Trigger ist das übergebene shutdown-Future. - Logging: Konsolenmodus → stderr (wie bisher); Dienst-Modus → rollende Tagesdatei (tracing-appender) unter [logging] dir (Default logs/). - install-service.ps1 / uninstall-service.ps1 (Dienst "Holzleitner Backend"). - README: Windows-Dienst-Abschnitt; .gitignore: /logs + Fatal-Log. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
150 lines
5.3 KiB
Markdown
150 lines
5.3 KiB
Markdown
# Holzleitner-Backend
|
||
|
||
Rust-Backend für die Holzleitner-Lieferservice-App. Workspace mit Clean
|
||
Architecture: `domain` → `application` → `infrastructure` → `api`.
|
||
|
||
## Schnellstart (lokale Entwicklung)
|
||
|
||
```bash
|
||
# 1) Postgres + Keycloak hochfahren
|
||
docker compose up -d
|
||
# Keycloak braucht ~30s bis "Listening on http://0.0.0.0:8080" im Log steht.
|
||
|
||
# 2) Konfiguration vorbereiten
|
||
cp config.example.toml config.toml
|
||
# Werte in config.toml anpassen (DB-URL, Keycloak-Issuer, ERP-Zugang, …).
|
||
|
||
# 3) Backend starten
|
||
cargo run -p holzleitner-api
|
||
```
|
||
|
||
Migrations laufen beim Start automatisch über `sqlx::migrate!`.
|
||
Smoke-Test danach:
|
||
|
||
```bash
|
||
curl http://127.0.0.1:3000/health
|
||
# → ok
|
||
|
||
curl http://127.0.0.1:3000/accounts/1001
|
||
# → {"personalnummer":1001,"name":"Müller Logistik GmbH","active":true}
|
||
```
|
||
|
||
## Keycloak (Dev)
|
||
|
||
| Wo | URL / Credentials |
|
||
|---|---|
|
||
| Admin-Console | http://localhost:8080/admin/ (admin / admin) |
|
||
| Realm | `holzleitner` |
|
||
| Client | `holzleitner-app` (public, PKCE) |
|
||
| Test-User | `testfahrer` / `test` · Personalnummer 1001 · Rolle `driver` |
|
||
| Audience im Access-Token | `holzleitner-api` |
|
||
|
||
Der Realm wird bei jedem `docker compose up` aus
|
||
`keycloak/import/realm-holzleitner.json` frisch importiert. Wer in der
|
||
Admin-UI Änderungen macht, sollte sie **in die JSON zurückspielen**,
|
||
sonst sind sie beim nächsten `docker compose down` weg.
|
||
|
||
### Token für Dev-Tests holen
|
||
|
||
```bash
|
||
curl -s -X POST \
|
||
http://localhost:8080/realms/holzleitner/protocol/openid-connect/token \
|
||
-d 'grant_type=password' \
|
||
-d 'client_id=holzleitner-app' \
|
||
-d 'username=testfahrer' \
|
||
-d 'password=test' | jq -r .access_token
|
||
```
|
||
|
||
Den Token in den `Authorization`-Header packen, sobald die JWT-Middleware
|
||
in der API-Schicht aktiv ist:
|
||
|
||
```bash
|
||
TOKEN=$(curl -s -X POST .../token -d ... | jq -r .access_token)
|
||
curl -H "Authorization: Bearer $TOKEN" http://127.0.0.1:3000/accounts/1001
|
||
```
|
||
|
||
## Crate-Layout
|
||
|
||
| Crate | Inhalt | Abhängigkeiten |
|
||
|---|---|---|
|
||
| `holzleitner-domain` | Reines Domänenmodell (serde + UUID + chrono) | — |
|
||
| `holzleitner-application` | Use Cases und Ports (Trait-Definitionen für Repository, AuthService) | `domain` |
|
||
| `holzleitner-infrastructure` | Konkrete Adapter (sqlx-Postgres, später Keycloak) | `domain`, `application` |
|
||
| `holzleitner-api` | Axum HTTP-Layer + Composition Root | alle |
|
||
|
||
## Konfiguration
|
||
|
||
Werte werden aus `config.toml` gelesen (Vorlage: `config.example.toml`),
|
||
gruppiert in TOML-Sections. Der Dateipfad ist über die Env-Variable
|
||
`HOLZLEITNER_CONFIG` überschreibbar (z. B. im Deployment); Default ist
|
||
`config.toml` im Arbeitsverzeichnis. Die echte `config.toml` enthält
|
||
Secrets und ist `.gitignore`-t.
|
||
|
||
| Section | Bereich | Pflicht? |
|
||
|---|---|---|
|
||
| `[server]` | Bind-Host/Port | ja |
|
||
| `[database]` | Postgres-URL, Pool-Größe | ja |
|
||
| `[keycloak]` | OIDC-Issuer, Audience, JWKS-Cache, Provisioning | ja |
|
||
| `[gsd]` | DOCUframe-REST (Datei-Upload) | ja |
|
||
| `[erp]` | ERPframe-MSSQL (Touren-Pull) | optional |
|
||
| `[import]` | Import-Scheduler (Cron, Offset) | optional |
|
||
| `[report]` / `[signature]` / `[attachment]` | Lokale Speicher-Pfade | optional |
|
||
| `[dev]` | `today_override`, `sync_enabled` (DEV-ONLY) | optional |
|
||
| `[admin]` | `api_key` für das `/admin`-Gate | optional |
|
||
| `[logging]` | Log-Filter (Default; `RUST_LOG`-Env hat Vorrang) | optional |
|
||
|
||
Unbekannte Schlüssel werden beim Laden abgewiesen (`deny_unknown_fields`),
|
||
sodass Tippfehler sofort als Startfehler auffallen.
|
||
|
||
## Migrations
|
||
|
||
Migrations liegen im Workspace-Root `migrations/`. Eingebettet via
|
||
`sqlx::migrate!()` — kein zusätzlicher Laufzeit-Dateizugriff nötig.
|
||
Neue Migration anlegen:
|
||
|
||
```bash
|
||
# Format: <epoch>_<beschreibung>.sql, z.B.:
|
||
touch migrations/0002_tour.sql
|
||
```
|
||
|
||
## Logging
|
||
|
||
`tracing` + `tracing-subscriber` mit `EnvFilter`. Der Default-Filter steht
|
||
in `config.toml` unter `[logging] filter`; die Env-Variable `RUST_LOG` hat
|
||
Vorrang (Ad-hoc-Debugging ohne Datei-Edit, z. B. `RUST_LOG=debug cargo run`).
|
||
|
||
Im **Konsolenmodus** geht alles auf `stderr`. Im **Windows-Dienst-Modus**
|
||
(keine Konsole) wird stattdessen in rollende Tages-Logdateien unter
|
||
`[logging] dir` (Default `logs/`, relativ zur EXE) geschrieben.
|
||
|
||
## Windows-Dienst
|
||
|
||
Das Backend kann als Windows-Dienst laufen (gleiches Muster wie der
|
||
Mail-Client). Beim Start versucht die EXE zuerst, sich beim Service Control
|
||
Manager zu registrieren; gelingt das nicht (interaktiver Start), fällt sie in
|
||
den Konsolenmodus zurück. `--console` (bzw. `-c`) erzwingt den Konsolenmodus.
|
||
|
||
Der Dienst setzt sein Arbeitsverzeichnis auf das EXE-Verzeichnis — `config.toml`,
|
||
`data/` und `logs/` müssen also **neben der EXE** liegen.
|
||
|
||
```powershell
|
||
# 1) Release-Build (auf dem Zielsystem oder per Cross-Build)
|
||
cargo build --release -p holzleitner-api
|
||
|
||
# 2) target\release\holzleitner-server.exe + config.toml + die *.ps1-Skripte
|
||
# in ein Installationsverzeichnis kopieren, z. B. C:\HolzleitnerBackend\
|
||
|
||
# 3) Als Administrator registrieren (Dienst "Holzleitner Backend")
|
||
.\install-service.ps1
|
||
# Optional mit Dienstkonto (DB-/Netzwerkzugriff):
|
||
.\install-service.ps1 -Credential (Get-Credential)
|
||
|
||
# Entfernen
|
||
.\uninstall-service.ps1
|
||
```
|
||
|
||
Der Dienst startet verzögert-automatisch (nach den System-/DB-Diensten) und
|
||
wird bei Absturz 3× im Abstand von 60 s neu gestartet. Interner Dienst-Name:
|
||
`HolzleitnerBackend`. Boot-Fehler **vor** der Logger-Initialisierung (z. B.
|
||
fehlende `config.toml`) landen in `holzleitner-backend-fatal.log` neben der EXE.
|