# 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 liegt in `keycloak/import/realm-holzleitner.json` und ist bereits passend zum Backend konfiguriert (Client `holzleitner-app` mit PKCE + Audience-Mapper `holzleitner-api` + `personalnummer`-Claim, Rolle `driver`, Service-Account-Client `holzleitner-provisioner`, Test-User). Wer in der Admin-UI Änderungen macht, sollte sie **in die JSON zurückspielen**. **Wichtig:** Das compose-`--import-realm` greift nur beim **ersten** Start (solange das `keycloak-data`-Volume leer ist) — spätere JSON-Änderungen landen NICHT automatisch in Keycloak. Realm anwenden/bootstrappen geht daher über `tool/keycloak_bootstrap.sh` (spricht nur die Admin-REST-API an, funktioniert gegen **Dev-Docker wie baremetal-Prod**): ```bash # Dev (lokales Docker mit hochfahren, Default-Realm "holzleitner") ./tool/keycloak_bootstrap.sh --up # Production (baremetal Keycloak, eigener Realm, frisches Secret, kein Test-User) ./tool/keycloak_bootstrap.sh \ --url https://auth.holzleitner.de \ --realm holzleitner-prod \ --admin kc-admin --admin-password "$KC_PW" \ --provisioner-secret "$(openssl rand -hex 24)" \ --no-test-user # Sauberer Neu-Import (LÖSCHT den Realm inkl. provisionierter Fahrer-Konten): ./tool/keycloak_bootstrap.sh --url ... --realm ... --reset ``` Wichtigste Optionen: `--url` (Keycloak-Basis inkl. Port), `--realm` (Realm-Name), `--admin`/`--admin-password`, `--provisioner-secret`, `--no-test-user`, `--reset`, `--up`. Alles auch via Env (`KC_URL`, `REALM`, `KC_ADMIN`, `KC_ADMIN_PASSWORD`, `PROVISIONER_SECRET`, `REALM_FILE`). Default ohne `--up` wird **kein** Docker angefasst. Das Skript gibt am Ende die passenden `[keycloak]`-Werte für die `config.toml` aus. ### Wie die Authentifizierung funktioniert Es gibt zwei getrennte Auth-Kontexte: **a) Bootstrap-Skript → Keycloak (einmalig/selten).** Das Skript meldet sich am `master`-Realm über den eingebauten Client `admin-cli` per Passwort-Grant an (`--admin`/`--admin-password`) und nutzt das Admin-Token nur, um den Realm anzulegen. In Prod ein echtes Admin-Konto + HTTPS verwenden; das Passwort besser über die Env-Variable `KC_ADMIN_PASSWORD` setzen (nicht in der History). **b) Laufzeit — App ↔ Backend ↔ Keycloak.** 1. Die Flutter-App loggt sich per **OIDC Authorization Code + PKCE** gegen `{KC_URL}/realms/{realm}` ein und erhält ein **Access-Token (JWT, RS256)** mit `aud=holzleitner-api`, Claim `personalnummer` und Realm-Rolle `driver`. 2. Das Backend **validiert das JWT public-key-basiert**: es holt die Realm-JWKS von `{issuer}/protocol/openid-connect/certs`, prüft Signatur, `iss` (== `config.toml [keycloak] issuer_url`), `aud` (enthält `holzleitner-api`) und Ablauf. Kein gemeinsames Secret nötig — nur der öffentliche Schlüssel. 3. Die `/admin`-Maschinen-Endpunkte des Backends laufen NICHT über Keycloak, sondern über den statischen `X-Admin-Api-Key` (siehe `[admin] api_key`). 4. Der **Provisioner** (`holzleitner-provisioner`, confidential, Client- Credentials-Grant mit `manage-users`) wird vom ERP-Sync genutzt, um neue Fahrer-Konten im Realm anzulegen — dafür gilt das `provisioner_client_secret`. Damit b) klappt, müssen `issuer_url`/`realm`/`admin_url` in der `config.toml` **exakt** auf denselben Server + Realm zeigen, den das Skript angelegt hat — und `issuer_url` muss dem von Keycloak ausgestellten `iss`-Claim entsprechen (in Prod also Keycloaks Frontend-/Hostname-URL korrekt setzen, sonst `invalid issuer`). ### 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: _.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.