node:20-alpine ships OpenSSL 3.x and has no libssl.so.1.1. Without the
openssl CLI, Prisma's version detection failed and fell back to the
openssl-1.1.x engine, which then failed to load its shared library and
emitted plain text instead of JSON ("Unexpected token 'E'..."), crashing
`prisma migrate deploy` at container startup.
- Install openssl in the build and runtime stages so detection succeeds
- Pin binaryTargets to linux-musl-openssl-3.0.x so the correct engine is bundled
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
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-Variable setzen:
POSTGRES_PASSWORD= ein zufälliges, sicheres Passwort - 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$.
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).