# 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`](docs/issues/README.md), Entscheidungen im > [Entwicklungstagebuch](docs/devlog.md). ## 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 ```bash 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: ```bash openssl rand -base64 48 ``` ### Qualitätsschranken ```bash 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 ```bash 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: ```bash 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: ```bash 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: ```bash docker compose exec -T postgres pg_dump -U laufzettel laufzettel | gzip > sicherung-$(date +%F).sql.gz ``` Zurückspielen: ```bash 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`](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`](.env.example). Pflicht sind `APP_URL`, `APP_SECRET`, `POSTGRES_PASSWORD` und `DATABASE_URL`; der Compose-Stand bricht ab, statt mit Standardwerten zu starten. ## Dokumentation - Anleitung für Techniker und Admins — entsteht mit #018, inkl. Bildern und PDF - [Architektur und Entscheidungen](docs/architektur.md) - [Austauschformat für Vorlagen](docs/vorlagen-format.md) — wie eine Vorlage als JSON aussieht - [Anbindung an authentik](docs/betrieb/authentik.md) — Inbetriebnahme, Proxmox und Reverse Proxy folgen mit #019 - [Konventionen](CLAUDE.md) — Sprache, Schichtung, feste Regeln - [Änderungen](CHANGELOG.md) · [Entwicklungstagebuch](docs/devlog.md) ## Lizenz Noch nicht festgelegt.