Florian WildeandClaude Opus 5 16059e7846
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
feat(verzeichnis): LDAP-Abgleich, Zeitplan, CSV-Import und Log
Quelle anlegen, Verbindung testen, nach Zeitplan oder auf Knopfdruck
abgleichen. Gelesen wird seitenweise mit searchPaginated, objectGUID
kommt als Puffer und wird gemischt-endian zur GUID umsortiert. Wer im
Verzeichnis verschwindet, wird hier inaktiv und nie geloescht.

Drei Regeln verhindern, dass eine kaputte Eingabe wie "alle sind
gegangen" aussieht: laesst sich kein einziger Eintrag zuordnen, wird
niemand deaktiviert; uebersprungene Eintraege werden am dn erkannt und
ausgenommen; eine von Hand angelegte Person wird uebernommen statt
verdoppelt.

Der Zeitplan laeuft ueber node-cron im selben Prozess, gestartet aus
src/instrumentation.ts, und nimmt keine eigene Sperre - die liegt in der
Datenbank, damit zwei Instanzen sich einig sind.

Closes #16

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-09-26 02:21:08 +02:00

laufzettel

Checklisten für wiederkehrende IT-Arbeiten. Versionierte Vorlagen, daraus gestartete Läufe, im Team abgearbeitet, abgeschlossen und unveränderlich archiviert — mit Historie je Gerät und je Mitarbeiter. Einstieg über QR-Aufkleber: Code scannen, Geräteseite öffnen, loslegen.

Stand: in Entwicklung auf v0.1.0 (MVP). Der Fortschritt steht in docs/issues/README.md, Entscheidungen im Entwicklungstagebuch.

Zwei Betriebsmodi

Modus Für wen Kunden-Ebene Verzeichnis
INTERN (Standard) eine Firma, eigene Mitarbeiter unsichtbar eigenes AD per LDAP
DIENSTLEISTUNG IT-Dienstleister mit mehreren Kunden sichtbar manuell oder per CSV je Kunde

Der Modus liegt in der Datenbank und ist vom Admin ohne Neustart umschaltbar. Jede kundenbezogene Zeile trägt immer eine Kunden-Zuordnung, im Modus INTERN die eigene Organisation — deshalb ist der Wechsel eine Datenänderung und keine Migration.

Voraussetzungen

  • Node 24 (.nvmrc) und pnpm 12 über corepack, oder nur Docker mit Compose-Plugin
  • PostgreSQL 17 (im Compose-Stand enthalten)

Entwicklung

corepack enable pnpm
pnpm install
cp .env.example .env          # APP_SECRET und Passwörter ausfüllen
pnpm db:up                    # PostgreSQL auf 127.0.0.1:5433
pnpm db:migrate
pnpm db:seed
pnpm dev                      # http://localhost:3000

APP_SECRET erzeugen:

openssl rand -base64 48

Qualitätsschranken

pnpm lint && pnpm typecheck && pnpm test && pnpm pruefe:doku

Ein Pre-Commit-Hook erzwingt Lint und Format, ein Commit-Msg-Hook Conventional Commits. Ende-zu-Ende-Tests: pnpm test:e2e.

Betrieb mit Docker Compose

cp .env.example .env          # APP_URL, APP_SECRET, POSTGRES_PASSWORD sind Pflicht
docker compose up -d --build

Die Migrationen laufen beim Start automatisch in einem eigenen Einmal-Dienst. Der Seed läuft absichtlich nicht automatisch — ihn startest du einmal von Hand:

docker compose run --rm migrate pnpm db:seed

Die Datenbank ist von außen nicht erreichbar

Das ist bewusst so gebaut und in tests/betriebsstand.test.ts festgehalten:

  • postgres hat kein ports:-Mapping, nur expose
  • postgres hängt ausschließlich im Netz intern, das internal: true ist — ein Netz ohne Gateway, also weder vom Host noch aus dem LAN erreichbar
  • nur web hängt zusätzlich im Netz extern (Reverse Proxy, OIDC, LDAP)
  • web bindet standardmäßig auf 127.0.0.1:3000; davor gehört ein Reverse Proxy

Prüfen:

nc -z localhost 5432 && echo "FEHLER: offen" || echo "dicht"
docker compose exec web nc -z postgres 5432 && echo "von innen erreichbar"

Für die Entwicklung gibt es docker-compose.dev.yml, das PostgreSQL auf 5433 öffnet. Diese Datei ist nicht für den Betrieb gedacht.

Reverse Proxy

Die App läuft hinter einem Reverse Proxy und hat keine fest verdrahteten Adressen. Maßgeblich ist APP_URL — sie steckt in den QR-Codes und in der OIDC-Rückruf-Adresse. Der Proxy muss X-Forwarded-Proto und X-Forwarded-Host setzen.

Sicherung

Der geschlossene Port ändert nichts an der Sicherung, sie läuft über den Dienstnamen:

docker compose exec -T postgres pg_dump -U laufzettel laufzettel | gzip > sicherung-$(date +%F).sql.gz

Zurückspielen:

gunzip -c sicherung-2026-09-24.sql.gz | docker compose exec -T postgres psql -U laufzettel laufzettel

Gesichert werden muss nur die Datenbank — die App hält keinen veränderlichen Zustand auf der Platte. Das Volume heißt laufzettel_datenbank.

Anmeldung

Zwei Wege, beide gleichzeitig möglich:

  • OIDC (Ziel: authentik) für den normalen Betrieb
  • lokal mit E-Mail und Passwort, nur wenn AUTH_LOCAL_ENABLED=true — für die Entwicklung und als Notfallzugang, wenn der Identitätsanbieter ausfällt

Die Rolle kommt bei jedem OIDC-Login aus dem groups-Claim und wird über OIDC_GROUP_ADMIN, OIDC_GROUP_EDITOR, OIDC_GROUP_TECHNICIAN und OIDC_GROUP_VIEWER zugeordnet. Unbekannte Benutzer werden beim ersten Login angelegt.

Rolle Darf
ADMIN alles, inkl. Benutzer, Verzeichnis, Abteilungen, Archiv-Verwaltung
REDAKTEUR Vorlagen anlegen, bearbeiten, veröffentlichen; alles was TECHNIKER darf
TECHNIKER Läufe starten, abarbeiten, abschließen, archivieren; Geräte pflegen
LESER nur lesen

Die Anbindung an authentik ist in docs/betrieb/authentik.md Schritt für Schritt beschrieben.

Verzeichnis-Abgleich (nur Modus INTERN)

Unter Admin → Verzeichnis wird eine LDAP-Quelle angelegt: Adresse (ldaps:// empfohlen, ldap+starttls:// und ldap:// gehen auch), ein Dienstkonto mit Leserecht, der Basis-DN und optional ein eigener Filter. Ein Dienstkonto genügt — Laufzettel schreibt nie ins Verzeichnis.

  • „Verbindung testen“ liest die ersten Einträge und zeigt sie mit den gemappten Feldern, ohne etwas zu speichern. Erst daran sieht man, ob die Zuordnung stimmt.
  • Die Attributzuordnung ist einklappbar im Formular und trägt die AD-Vorgaben; ein OpenLDAP bekommt hier etwa entryUUID als Kennung.
  • Der Zeitplan läuft im selben Prozess wie die Anwendung und sieht jede Minute nach, welche Quelle fällig ist; das Intervall steht je Quelle. ZEITPLAN_AKTIV=false schaltet ihn ab — für Einmal-Container, die nur Migrationen fahren.
  • Gelöscht wird nie. Wer im Verzeichnis verschwindet oder deaktiviert wird, wird hier inaktiv. Lässt sich kein einziger Eintrag zuordnen, deaktiviert der Abgleich niemanden und meldet stattdessen einen Fehler — ein falsch gesetztes Kennungsattribut soll nicht die halbe Firma abschalten.
  • Das Bind-Passwort wird verschlüsselt gespeichert und nie wieder angezeigt; ein leeres Feld beim Bearbeiten heißt „unverändert“.

Ohne Verzeichnis geht es auch: Personen lassen sich von Hand anlegen oder aus einer CSV-Datei einlesen.

Umgebungsvariablen

Alle Variablen mit Erklärung stehen in .env.example. Pflicht sind APP_URL, APP_SECRET, POSTGRES_PASSWORD und DATABASE_URL; der Compose-Stand bricht ab, statt mit Standardwerten zu starten.

Dokumentation

Lizenz

Noch nicht festgelegt.

S
Description
No description provided
Readme
27 MiB
2026-10-02 22:17:05 +00:00
Languages
TypeScript 96.1%
Shell 2.4%
PLpgSQL 0.8%
JavaScript 0.5%
CSS 0.2%