174 lines
6.7 KiB
Markdown
174 lines
6.7 KiB
Markdown
# 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.
|