feat(betrieb): native Installation, Proxmox-Einzeiler und Abnahme
Prüfung / pruefung (push) Canceled after 0s
Prüfung / pruefung (pull_request) Canceled after 0s
Prüfung / e2e (push) Canceled after 0s
Prüfung / e2e (pull_request) Canceled after 0s

Zwei Wege in den Betrieb, beide so weit gefahren, wie es hier moeglich war.

deploy/install.sh richtet Debian 13 in neun Schritten ein: PostgreSQL 17 hoert
nur am Unix-Socket, der Dienstnutzer hat kein Login, die Geheimnisse entstehen
im Zielsystem und liegen 0600 bei root. Die Umgebungsdatei wird nie
ueberschrieben -- auch nicht beim Aktualisieren, sonst enden alle Sitzungen und
die verschluesselten Bind-Passwoerter waeren unlesbar.

deploy/proxmox/laufzettel.sh legt den Container an und ruft install.sh darin
auf. Der pct-Teil ist ungetestet, weil kein PVE-Host zur Verfuegung stand; die
Gegenprobe steht als Pruefliste in docs/betrieb/proxmox.md, das Folge-Issue ist
#024.

Die Abnahme gegen alle 61 Kriterien hat drei Zusagen ohne Deckung gefunden, alle
behoben: der Healthcheck des Webdienstes konnte nie bestehen (busybox loest
localhost nach ::1 auf, Next lauscht nur auf IPv4), die CI-Pipeline konnte in
keinem Job gruen werden (kein LDAP-Dienst, kein gestarteter Server, kein
shellcheck), und der README nannte fuer die Entwicklungsdatenbank den falschen
Port.

Closes #19
This commit is contained in:
Florian Wilde
2026-09-26 15:53:01 +02:00
parent fbbb634e56
commit bf1e52cf73
20 changed files with 3663 additions and 103 deletions
+68 -59
View File
@@ -5,91 +5,100 @@ Punkt ist entweder abgehakt oder hat ein Folge-Issue — nichts bleibt stillschw
## Betrieb
- [ ] `docker compose up` startet App und Datenbank, Migrationen laufen automatisch
- [ ] Seed läuft per dokumentiertem Befehl, nicht automatisch
- [ ] Die Datenbank ist vom Host aus **nicht** erreichbar (`nc -z localhost 5432` schlägt fehl), aus `web` schon
- [x] `docker compose up` startet App und Datenbank, Migrationen laufen automatisch
- [x] Seed läuft per dokumentiertem Befehl, nicht automatisch
- [x] Die Datenbank ist vom Host aus **nicht** erreichbar (`nc -z localhost 5432` schlägt fehl), aus `web` schon
- [ ] Der Proxmox-One-Liner rollt einen LXC aus, in dem die App läuft und PostgreSQL nur am Unix-Socket hört
- [ ] Erneuter Aufruf des Skripts aktualisiert, ohne Daten oder Geheimnisse zu verlieren
- [ ] `shellcheck --severity=warning` über alle Skripte ohne Befund
- [ ] Läuft hinter einem Reverse Proxy ohne fest verdrahtete Adressen
- [x] Erneuter Aufruf des Skripts aktualisiert, ohne Daten oder Geheimnisse zu verlieren
- [x] `shellcheck --severity=warning` über alle Skripte ohne Befund
- [x] Läuft hinter einem Reverse Proxy ohne fest verdrahtete Adressen
> **Zeile 4 bleibt offen, mit Ansage:** hier stand kein Proxmox-Host zur Verfügung. Belegt ist beides
> drumherum — `laufzettel.sh --dry-run` und `--dry-run --aktualisieren` laufen vollständig durch, und
> der Inhalt des Containers, `deploy/install.sh`, lief zweimal in `debian:13` und einmal unter echtem
> systemd durch, mit `ss -ltn` ohne einen einzigen lauschenden TCP-Port. Offen ist allein die
> `pct`-Schicht. Die Gegenprobe steht in [docs/betrieb/proxmox.md](betrieb/proxmox.md) („Was du beim
> ersten Lauf selbst nachprüfen musst“), das Folge-Issue ist
> [#024](issues/024-proxmox-echtlauf.md). Bedingung dafür: der Einzeiler lädt von `main` — er wird
> erst nach dem Merge prüfbar.
## Anmeldung und Rechte
- [ ] Login per lokalem Konto funktioniert
- [ ] OIDC ist konfigurierbar und für authentik dokumentiert, inkl. Gruppen für Rollen
- [ ] Bei `AUTH_LOCAL_ENABLED=false` ist der lokale Login weg und der Endpunkt lehnt ab
- [ ] Rollenrechte: jede Rolle gegen jede Aktion getestet, Prüfung ausschließlich serverseitig
- [ ] Eine Rollenänderung greift ohne neue Anmeldung; ein deaktivierter Benutzer wird abgewiesen
- [ ] Passwörter stehen nur als argon2-Hash in der Datenbank, nie in einer API-Antwort
- [x] Login per lokalem Konto funktioniert
- [x] OIDC ist konfigurierbar und für authentik dokumentiert, inkl. Gruppen für Rollen
- [x] Bei `AUTH_LOCAL_ENABLED=false` ist der lokale Login weg und der Endpunkt lehnt ab
- [x] Rollenrechte: jede Rolle gegen jede Aktion getestet, Prüfung ausschließlich serverseitig
- [x] Eine Rollenänderung greift ohne neue Anmeldung; ein deaktivierter Benutzer wird abgewiesen
- [x] Passwörter stehen nur als argon2-Hash in der Datenbank, nie in einer API-Antwort
## Betriebsmodus
- [ ] Hauptablauf läuft in `INTERN` ohne jede Kundenanzeige (Ausnahme: die Einstellungsseite und der Wechsel-Dialog zeigen die eigene Organisation — dort ist sie zu pflegen)
- [ ] Hauptablauf läuft in `DIENSTLEISTUNG` mit Kundenwahl
- [ ] Wechsel `INTERN → DIENSTLEISTUNG` übernimmt alle Daten auf den ersten Kunden
- [ ] Wechsel zurück wird bei zwei nicht archivierten Kunden abgelehnt
- [ ] Im Modus `INTERN` ignoriert die Service-Schicht eine mitgeschickte `kundeId`
- [ ] Der Modus ist ohne Neustart umschaltbar, jeder Wechsel steht im `Protokoll`
- [x] Hauptablauf läuft in `INTERN` ohne jede Kundenanzeige (Ausnahme: die Einstellungsseite und der Wechsel-Dialog zeigen die eigene Organisation — dort ist sie zu pflegen)
- [x] Hauptablauf läuft in `DIENSTLEISTUNG` mit Kundenwahl
- [x] Wechsel `INTERN → DIENSTLEISTUNG` übernimmt alle Daten auf den ersten Kunden
- [x] Wechsel zurück wird bei zwei nicht archivierten Kunden abgelehnt
- [x] Im Modus `INTERN` ignoriert die Service-Schicht eine mitgeschickte `kundeId`
- [x] Der Modus ist ohne Neustart umschaltbar, jeder Wechsel steht im `Protokoll`
## Vorlagen
- [ ] Veröffentlichte Version ist unveränderlich; „Bearbeiten“ erzeugt eine tiefe Kopie als Entwurf
- [ ] Höchstens ein Entwurf je Vorlage
- [ ] Veröffentlichen einer neuen Version verändert bestehende Läufe nicht
- [ ] Export und anschließender Import ergeben eine inhaltsgleiche Vorlage
- [ ] Unbekannte Platzhalter werden im Editor als Warnung angezeigt
- [x] Veröffentlichte Version ist unveränderlich; „Bearbeiten“ erzeugt eine tiefe Kopie als Entwurf
- [x] Höchstens ein Entwurf je Vorlage
- [x] Veröffentlichen einer neuen Version verändert bestehende Läufe nicht
- [x] Export und anschließender Import ergeben eine inhaltsgleiche Vorlage
- [x] Unbekannte Platzhalter werden im Editor als Warnung angezeigt
## Läufe und Archiv
- [ ] Lauf ohne Mitarbeiter und ohne Gerät lässt sich starten, abschließen und archivieren
- [ ] Abschließen scheitert, solange ein Pflichtschritt offen ist
- [ ] `NICHT_ZUTREFFEND` und `FEHLGESCHLAGEN` verlangen eine Notiz
- [ ] „Abschnitt als nicht zutreffend markieren“ setzt alle Schritte mit einer Notiz
- [ ] Archiv-Schreibschutz greift für Kopfdaten, Schrittergebnisse und Kommentare
- [ ] Nur `ADMIN` holt einen archivierten Lauf zurück, mit `Protokoll`-Eintrag
- [ ] Im Archiv wird über Titel, Notizen, Werte und Kommentare gefunden
- [ ] Druckansicht eines Laufs ist sauber und hell
- [x] Lauf ohne Mitarbeiter und ohne Gerät lässt sich starten, abschließen und archivieren
- [x] Abschließen scheitert, solange ein Pflichtschritt offen ist
- [x] `NICHT_ZUTREFFEND` und `FEHLGESCHLAGEN` verlangen eine Notiz
- [x] „Abschnitt als nicht zutreffend markieren“ setzt alle Schritte mit einer Notiz
- [x] Archiv-Schreibschutz greift für Kopfdaten, Schrittergebnisse und Kommentare
- [x] Nur `ADMIN` holt einen archivierten Lauf zurück, mit `Protokoll`-Eintrag
- [x] Im Archiv wird über Titel, Notizen, Werte und Kommentare gefunden
- [x] Druckansicht eines Laufs ist sauber und hell
## Mitarbeiter, Geräte, QR
- [ ] Mitarbeitersuche liefert standardmäßig nur aktive, Umschalter zeigt auch deaktivierte
- [ ] Ein von der Geräteseite gestarteter Lauf hat den Besitzer vorbelegt
- [ ] `/t/LZ-7K3F9Q` ohne Anmeldung führt nach dem Login auf die Geräteseite
- [ ] „Vorlage zuweisen“ erzeugt einen `OFFEN`-Lauf, der unter „Zugewiesen“ erscheint; „Starten“ setzt `IN_ARBEIT`
- [ ] Unregistrierter Tag → Registrierformular → Geräteseite; unbekannter Tag → Hinweis
- [ ] `tagCode`: richtiges Alphabet, eindeutig, unveränderlich, nie wiederverwendet
- [ ] Etikettendruck in beiden Layouts sauber; QR maschinell zurücklesbar
- [x] Mitarbeitersuche liefert standardmäßig nur aktive, Umschalter zeigt auch deaktivierte
- [x] Ein von der Geräteseite gestarteter Lauf hat den Besitzer vorbelegt
- [x] `/t/LZ-7K3F9Q` ohne Anmeldung führt nach dem Login auf die Geräteseite
- [x] „Vorlage zuweisen“ erzeugt einen `OFFEN`-Lauf, der unter „Zugewiesen“ erscheint; „Starten“ setzt `IN_ARBEIT`
- [x] Unregistrierter Tag → Registrierformular → Geräteseite; unbekannter Tag → Hinweis
- [x] `tagCode`: richtiges Alphabet, eindeutig, unveränderlich, nie wiederverwendet
- [x] Etikettendruck in beiden Layouts sauber; QR maschinell zurücklesbar
## Platzhalter und Befehle
- [ ] Onboarding: Vorname und Nachname eingeben → UPN, `sam` und Mail werden vorgeschlagen
- [ ] Abteilung „Buchhaltung“ wählen → `-Path "OU=Buchhaltung,…"` und Gruppen `G-Buchhaltung, G-Alle`
- [ ] Kopieren liefert exakt diesen Text, zeichengenau
- [ ] „Jörg Müller-Lüdenscheid“ ergibt gültige Vorschläge (`joerg.mueller-luedenscheid@…`, `jmuellerluedensch`)
- [ ] `{{generiertes_passwort}}` erscheint im Befehl, steht aber nicht in Datenbank, Druckansicht oder `Protokoll`
- [ ] Werte mit Anführungszeichen werden für die jeweilige Shell korrekt maskiert
- [ ] „Alle Befehle als Skript kopieren“ liefert die Schritte in richtiger Reihenfolge
- [ ] Unbekannte Platzhalter bleiben stehen; ersetzte Werte sind HTML-escaped
- [x] Onboarding: Vorname und Nachname eingeben → UPN, `sam` und Mail werden vorgeschlagen
- [x] Abteilung „Buchhaltung“ wählen → `-Path "OU=Buchhaltung,…"` und Gruppen `G-Buchhaltung, G-Alle`
- [x] Kopieren liefert exakt diesen Text, zeichengenau
- [x] „Jörg Müller-Lüdenscheid“ ergibt gültige Vorschläge (`joerg.mueller-luedenscheid@…`, `jmuellerluedensch`)
- [x] `{{generiertes_passwort}}` erscheint im Befehl, steht aber nicht in Datenbank, Druckansicht oder `Protokoll`
- [x] Werte mit Anführungszeichen werden für die jeweilige Shell korrekt maskiert
- [x] „Alle Befehle als Skript kopieren“ liefert die Schritte in richtiger Reihenfolge
- [x] Unbekannte Platzhalter bleiben stehen; ersetzte Werte sind HTML-escaped
## Verzeichnis (LDAP)
- [ ] Erster Abgleich legt Mitarbeiter an, zweiter aktualisiert sie ohne Duplikate
- [ ] Entfernte oder deaktivierte Konten werden `aktiv = false`, nie gelöscht
- [ ] `{{ou}}` wird korrekt aus dem `dn` abgeleitet
- [ ] Das Bind-Passwort erscheint in keiner API-Antwort und keinem Log
- [ ] Zwei gleichzeitige Abgleiche derselben Quelle: der zweite wird von der Sperre abgewiesen
- [x] Erster Abgleich legt Mitarbeiter an, zweiter aktualisiert sie ohne Duplikate
- [x] Entfernte oder deaktivierte Konten werden `aktiv = false`, nie gelöscht
- [x] `{{ou}}` wird korrekt aus dem `dn` abgeleitet
- [x] Das Bind-Passwort erscheint in keiner API-Antwort und keinem Log
- [x] Zwei gleichzeitige Abgleiche derselben Quelle: der zweite wird von der Sperre abgewiesen
## Oberfläche
- [ ] Am Handy kein horizontales Scrollen — bei 390 px auf allen Hauptseiten geprüft
- [ ] Gestaltung nach Vorgabe: dunkel, ein Akzent, keine Emojis, keine Schatten, keine Verläufe
- [ ] Version mit Git-SHA unten links
- [x] Am Handy kein horizontales Scrollen — bei 390 px auf allen Hauptseiten geprüft
- [x] Gestaltung nach Vorgabe: dunkel, ein Akzent, keine Emojis, keine Schatten, keine Verläufe
- [x] Version mit Git-SHA unten links
## Projektorganisation
- [ ] Issues für alle Phasen und MVP-Meilensteine existieren
- [ ] `CHANGELOG.md` und `docs/devlog.md` sind gepflegt
- [x] Issues für alle Phasen und MVP-Meilensteine existieren
- [x] `CHANGELOG.md` und `docs/devlog.md` sind gepflegt
- [ ] `v0.1.0` ist getaggt, Gitea-Release mit Changelog-Auszug
- [ ] `docs/anleitung/` vollständig mit Screenshots und als PDF
- [ ] `docs/screenshots/` enthält die Meilenstein-Bilder
- [ ] `pnpm lint && pnpm typecheck && pnpm test && pnpm pruefe:doku` grün
- [x] `docs/anleitung/` vollständig mit Screenshots und als PDF
- [x] `docs/screenshots/` enthält die Meilenstein-Bilder
- [x] `pnpm lint && pnpm typecheck && pnpm test && pnpm pruefe:doku` grün