Dennis Nemec bfdd4d2307 feat(sync): Anschrift je Kontaktquelle mitführen
Beleg-, Liefer-, Rechnungsadresse, Ansprechpartner und Kundenstamm tragen
jetzt neben Namensblock und Kanälen auch ihre Anschrift (Straße, Nr., PLZ,
Ort, Land aus dem Länderstamm, Adresszusatz), sofern im ERP gepflegt.
Migration 0035 ergänzt die Spalten an delivery_contact_sources;
ContactSource.address in der API.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-09-25 16:26:00 +02:00

Holzleitner-Backend

Rust-Backend für die Holzleitner-Lieferservice-App. Workspace mit Clean Architecture: domain → application → infrastructure → api.

Schnellstart (lokale Entwicklung)

# 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:

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):

# 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

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:

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:

# 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.

# 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.

Description
No description provided
Readme 1.1 MiB
Languages
Rust 92.5%
Shell 5.7%
HTML 1.1%
PowerShell 0.5%
Batchfile 0.2%