Gleiche Haertung wie im healthjournal-Stack: DATABASE_URL zeigt auf den expliziten Netz-Alias materialschein-db statt auf den container_name. Reine DNS-Aenderung, Datenverzeichnis und Env bleiben unangetastet. Co-Authored-By: Claude <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01GTRprKS68jGmJCSZURn99Y
BergVOLT Materialschein
Materialschein & Kabelrechner für BergVOLT Elektrotechnik. React + Vite + TypeScript (PWA) im Client, Express + TypeScript + Prisma im Server, Postgres als Datenbank. Anmeldung über Microsoft Entra ID (gleiche App-Registrierung wie das Zeiterfassungs-Tool).
Lokale Entwicklung
# 1. Postgres lokal starten
docker compose -f docker-compose.dev.yml up -d
# 2. Server
cd server
npm install
echo 'DATABASE_URL="postgresql://materialschein:dev@localhost:5433/materialschein"' > .env
echo 'MSAL_CLIENT_ID="b1cd974c-77ce-4d93-b6e8-00e41a31adc8"' >> .env
echo 'MSAL_TENANT_ID="d9adeaaf-853b-4c4e-a822-8f1bedbb84f6"' >> .env
echo 'PORT=8787' >> .env
npx prisma migrate deploy
npm run dev # läuft auf :8787
# 3. Client (neues Terminal)
cd client
npm install
npm run dev # läuft auf :5173, proxied /api auf :8787
Architektur
client/– React 18 + Vite + TypeScript,vite-plugin-pwafür Manifest/Service Worker. MSAL.js (@azure/msal-browser+@azure/msal-react) für den Microsoft-Login.server/– Express + TypeScript, validiert das MSAL-ID-Token serverseitig (JWKS vonlogin.microsoftonline.com) und liefert zusätzlich die gebaute Client-App aus (public/=client/dist, SPA-Fallback für React-Router).server/prisma/schema.prisma– zwei Tabellen:projekte(Baustellen) undmaterial(Positionen je Projekt, perprojektIdverknüpft).- Ein Docker-Image (
Dockerfile, Multi-Stage) enthält Client-Build + Server, Port 80.
Deploy (git.prox.ws → Portainer)
Folgt deploy-standard 1:1 – gitops/stack.yml
und .gitea/workflows/build.yml sind bereits fertig im Repo. Zum Scharfschalten:
1. Azure AD – zweite Redirect-URI eintragen
Bestehende App-Registrierung "BergVolt Zeiterfassung" im Azure Portal öffnen →
Authentifizierung → bei "Single-Page-Anwendung" ergänzen:
https://materialschein.bergvolt.at (oder die tatsächlich vorgesehene Domain/Pfad).
Redirect-URI in client/src/lib/authConfig.ts steht auf window.location.origin –
d.h. die exakte Domain, unter der die App später erreichbar ist, muss hier eingetragen werden.
2. Gitea – Repo-Secrets setzen
Repo → Settings → Actions → Secrets:
| Secret | Wert |
|---|---|
GITEAUSER |
Gitea-Username des Package-Namespace-Owners (siehe deploy-standard: Package-Push geht nur als Namespace-Owner) |
GITEAPASSWORD |
PAT dieses Users, Scope nur write:package (Settings → Applications → Generate New Token) |
PORTAINER_WEBHOOK_URL |
kommt aus Schritt 4 |
Repo → Settings → Actions: Default-Permission auf Read and Write, damit der
Commit-back (gitops/stack.yml auf :<version> pinnen) funktioniert.
3. Erster Push
git push origin main
build wird grün, deploy schlägt fehl (Webhook-Secret fehlt noch) – erwartet.
4. Portainer – Stack anlegen
Auf https://10.1.10.111:9443 (Zugang laut deploy-standard bei "dominic"):
- Stacks → Add stack → Repository
- Repository URL:
https://git.prox.ws/BERGVOLT/Materialschein - Reference:
refs/heads/main - Compose path:
gitops/stack.yml - Authentication:
portainer-deploy+ dessen PAT (Org-Repos sind über dasdeploy-read-Team bereits abgedeckt, nichts zusätzlich einzutragen) - Environment-Variablen setzen:
POSTGRES_PASSWORD= ein zufälliges, sicheres PasswortJWT_SECRET= ein zufälliger, langer String (signiert die lokalen Login-Tokens – niemals ins Repo, nur hier als Stack-Env). Beispiel zum Generieren:openssl rand -base64 48SEED_ADMIN_USERNAME/SEED_ADMIN_PASSWORD= legt beim ersten Start automatisch ein lokales Fallback-Konto an (siehe "Lokales Konto" unten)
- Webhook aktivieren, ForceUpdate an,
ForcePullImagebleibt aus
- Repository URL:
- Die angezeigte Webhook-URL (
.../api/stacks/webhooks/<uuid>) als Gitea-SecretPORTAINER_WEBHOOK_URLeintragen (Schritt 2). - nginx-proxy-manager: Proxy Host auf Container
materialschein, Port80.
5. Verifizieren
- Webhook von Hand feuern (
curl -sk -X POST <url>) →204, Container laufen (db+web). - Push oder „Run workflow" auslösen → beide Jobs grün,
gitops/stack.ymlzeigt danachimage: …:1.0.<N>(kein:latestmehr), Commitdeploy: v1.0.<N> [skip ci]im Log. - Cleanup-Rule für den Package-Owner einmalig setzen (Org/User → Settings → Packages →
Cleanup Rules): keep most recent 10 + keep matching
^latest$.
Lokales Konto (Fallback ohne Microsoft-Login)
Auf dem Login-Screen gibt es unter dem Microsoft-Button den Link "Alternativ mit
Benutzername anmelden". Damit das funktioniert, muss im Portainer-Stack JWT_SECRET
gesetzt sein (Schritt 4).
Ein erstes Konto legt der Server automatisch beim Start an, wenn
SEED_ADMIN_USERNAME und SEED_ADMIN_PASSWORD als Stack-Env gesetzt sind (Upsert –
läuft bei jedem Neustart mit und synchronisiert das Passwort neu, solange die Variable
gesetzt ist – ein falsch übernommenes Passwort lässt sich also einfach per Neustart
korrigieren). Das Klartext-Passwort steht dabei nur in der Portainer-Stack-Konfiguration,
nie im Git-Repo – der Server speichert ausschließlich einen bcrypt-Hash in der Datenbank.
Vorsicht bei $ im Passwort: Docker-Compose/Portainer interpretieren $ in
Env-Werten als Beginn einer Variablen-Referenz ($xyz wird als ${xyz} gelesen und bei
fehlender Definition oft still zu leer aufgelöst) – ein Passwort mit $ kann dadurch
unbemerkt anders ankommen, als eingetippt. Entweder jedes $ im Passwortwert als $$
escapen, oder – einfacher – ein Passwort ohne $ verwenden.
Optional danach SEED_ADMIN_PASSWORD aus dem Stack wieder entfernen (Härtung), sobald
das Konto wie gewünscht funktioniert – SEED_ADMIN_USERNAME kann stehen bleiben, ohne
Passwort-Variable wird nichts mehr verändert. Weitere lokale Konten aktuell nur direkt in der
Datenbank anlegbar (INSERT INTO users … mit einem bcrypt-Hash) – ein Verwaltungs-UI
dafür gibt es noch nicht.
Danach: zum Homescreen hinzufügen
Seite im Handy-Browser öffnen, anmelden, "Zum Home-Bildschirm hinzufügen" (iOS Safari)
bzw. "App installieren" (Android Chrome) – die App ist als PWA installierbar
(vite-plugin-pwa generiert Manifest + Service Worker automatisch beim Build).