Grund: $ in SEED_ADMIN_PASSWORD wird von Docker-Compose/Portainer als Variablen-Referenz interpretiert und kann dadurch verstümmelt beim Container ankommen. Seed synct das Passwort jetzt bei jedem Start neu, solange die Variable gesetzt ist, statt nur einmalig anzulegen. README um Warnhinweis ergänzt. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
6.2 KiB
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).