228 lines
12 KiB
Markdown
228 lines
12 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:5434
|
|
pnpm db:migrate
|
|
pnpm db:seed
|
|
pnpm dev # http://localhost:3000
|
|
```
|
|
|
|
`APP_SECRET` erzeugen:
|
|
|
|
```bash
|
|
openssl rand -base64 48
|
|
```
|
|
|
|
### Qualitätsschranken
|
|
|
|
```bash
|
|
docker compose -f docker-compose.test.yml up -d --wait ldap # sonst fehlen 22 Tests
|
|
pnpm lint && pnpm typecheck && pnpm format:pruefen && pnpm test && pnpm pruefe:doku
|
|
```
|
|
|
|
Der LDAP-Container ist keine Zierde: ohne ihn überspringt die Suite den ganzen Verzeichnis-Abgleich
|
|
— sie sagt das auf stderr, aber der Lauf sieht grün aus. In CI bricht sie stattdessen ab.
|
|
|
|
Ein Pre-Commit-Hook erzwingt Lint und Format, ein Commit-Msg-Hook Conventional Commits.
|
|
Ende-zu-Ende-Tests: `pnpm test:e2e` (verlangt einen gebauten, laufenden Stand — siehe
|
|
`docs/anleitung/`).
|
|
|
|
## 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
|
|
```
|
|
|
|
### Was der Seed anlegt
|
|
|
|
| | Inhalt |
|
|
|---|---|
|
|
| Grundbestand | Betriebsmodus, eigene Organisation, Administrator aus `SEED_ADMIN_EMAIL` und `SEED_ADMIN_PASSWORD` |
|
|
| Vorlagen | die sieben Abläufe des Hauses als **veröffentlichte Version 1** — PC Neuinstallation, Onboarding, Offboarding, Server-Inbetriebnahme, Firewall-Rollout, Patchday, Restore-Test |
|
|
| Beispielbestand | 3 Abteilungen, 5 Personen, `PC-001` und `NB-001` mit Tag-Code, 5 vorgedruckte Tags, Schnellstart-Vorgaben je Gerätetyp |
|
|
| Verzeichnis | eine **deaktivierte** Beispielquelle zum Abschauen — Adresse und Bind-Passwort ersetzen, dann einschalten |
|
|
|
|
Mit `SEED_MODE=DIENSTLEISTUNG` (Alias `SEED_MODE=SERVICE`) kommen zwei Demo-Kunden dazu,
|
|
„Musterfirma GmbH" (`MUS`) und „Bäckerei Reiter" (`REI`), mit je einer Abteilung, einer Person und
|
|
einem Gerät nach der Hostname-Regel (`MUS-PC-001`).
|
|
|
|
**Der Seed richtet ein, er räumt nicht auf.** Was schon da ist, bleibt: eine bearbeitete Vorlage,
|
|
eine geänderte Ziel-OU oder ein umgeschalteter Betriebsmodus überstehen jeden weiteren Lauf. Zweimal
|
|
hintereinander aufgerufen ändert der zweite Lauf nichts.
|
|
|
|
Die sieben Vorlagen liegen als JSON im [Austauschformat](docs/vorlagen-format.md) unter
|
|
`prisma/seed-daten/vorlagen/`. Wer eine anpassen will, ändert die Datei — oder besser: die Vorlage
|
|
in der Oberfläche, denn eine veröffentlichte Version wird nie verändert, und der Seed fasst eine
|
|
vorhandene Vorlage nicht mehr an.
|
|
|
|
### 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 `5434` öffnet — umstellbar
|
|
über `DEV_DB_PORT`. 5432 ist oft lokal belegt und 5433 von einem anderen Projekt, daher dieser Port.
|
|
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 müssen **zwei** Dinge: die Datenbank (das Volume heißt `laufzettel_datenbank`; die
|
|
App selbst hält keinen veränderlichen Zustand auf der Platte) **und die Geheimnisse aus der `.env`**.
|
|
Ohne das alte `APP_SECRET` sind nach dem Zurückspielen alle gespeicherten LDAP-Bind-Passwörter
|
|
unlesbar — und das merkt man erst beim nächsten Abgleich. Einzelheiten in
|
|
[`docs/betrieb/sicherung.md`](docs/betrieb/sicherung.md).
|
|
|
|
## 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
|
|
|
|
Jede Variable steht mit Erklärung in [`.env.example`](.env.example) — hier die Übersicht. **Pflicht**
|
|
sind `APP_URL`, `APP_SECRET`, `POSTGRES_PASSWORD` und `DATABASE_URL`; fehlt eine davon, bricht der
|
|
Compose-Stand ab, statt mit Standardwerten zu starten.
|
|
|
|
| Variable | Bedeutung |
|
|
|---|---|
|
|
| `APP_URL` | Die **öffentliche** Adresse, unter der die App erreichbar ist. Steht sie hinter einem Reverse Proxy, ist es dessen Adresse — nicht die interne. Aus ihr entstehen die QR-Adressen auf den Aufklebern und die OIDC-Rücksprungadresse. |
|
|
| `APP_SECRET` | Schlüssel für Sitzungs-Cookies und die Verschlüsselung des LDAP-Bind-Passworts. Mindestens 32 Zeichen, erzeugen mit `openssl rand -base64 48`. **Ändert man ihn, sind alle Sitzungen ungültig und gespeicherte Bind-Passwörter unlesbar.** |
|
|
| `DATABASE_URL` | Verbindung zur PostgreSQL-Datenbank. |
|
|
| `POSTGRES_USER`, `POSTGRES_PASSWORD`, `POSTGRES_DB` | Zugangsdaten des Datenbank-Containers im Compose-Stand. |
|
|
| `AUTH_LOCAL_ENABLED` | `true` schaltet die lokale Anmeldung mit E-Mail und Passwort frei — Notfallzugang und Entwicklung. Auf `false` verschwindet das Formular **und** der Endpunkt lehnt ab. |
|
|
| `OIDC_ISSUER`, `OIDC_CLIENT_ID`, `OIDC_CLIENT_SECRET` | Der Identitätsanbieter. Discovery über `<issuer>/.well-known/openid-configuration`. |
|
|
| `OIDC_NAME` | Beschriftung des Anmeldeknopfs, etwa „Firmenanmeldung“. |
|
|
| `OIDC_SCOPE` | Angefragte Scopes; muss einen enthalten, der `groups` liefert. |
|
|
| `OIDC_GROUP_ADMIN`, `OIDC_GROUP_EDITOR`, `OIDC_GROUP_TECHNICIAN`, `OIDC_GROUP_VIEWER` | Welche Gruppe welche Rolle bekommt. Die Rolle wird bei **jeder** Anmeldung neu gesetzt. |
|
|
| `OIDC_CUSTOMER_GROUP_PREFIX` | Nur im Modus `DIENSTLEISTUNG`: Gruppen mit diesem Präfix weisen einen Benutzer einem Kunden zu. |
|
|
| `BIND_ADDR`, `APP_PORT` | Wohin der Compose-Stand die App bindet. Standard `127.0.0.1:3000` — davor gehört ein Reverse Proxy. |
|
|
| `APP_VERSION`, `GIT_SHA` | Erscheinen unten links in der Oberfläche. Im Compose-Stand Build-Argumente. |
|
|
| `SEED_ADMIN_EMAIL`, `SEED_ADMIN_PASSWORD` | Der Administrator, den `pnpm db:seed` anlegt. Das Passwort wird nur als argon2-Hash gespeichert. Fehlt eines, überspringt der Seed ihn. |
|
|
| `SEED_MODE` | `INTERN` (Standard) oder `DIENSTLEISTUNG` — der Alias `SERVICE` tut dasselbe. Betrifft nur den **ersten** Seed-Lauf; danach steht der Modus in der Datenbank. |
|
|
| `SEED_ORG_NAME`, `SEED_ORG_KUERZEL` | Name und Kürzel der eigenen Organisation. Das Kürzel steht auf Etiketten und in der Hostname-Regel. |
|
|
| `ZEITPLAN_AKTIV` | `false` schaltet den Verzeichnis-Abgleich nach Zeitplan ab. |
|
|
| `TEST_DATABASE_URL` | Datenbank für die Tests. Ohne sie überspringen sich alle Datenbanktests. |
|
|
|
|
## Dokumentation
|
|
|
|
- [Anleitung für Technik und Verwaltung](docs/anleitung/) — acht Kapitel mit Bildern. `pnpm docs:pdf`
|
|
macht daraus ein PDF; jedem Release liegt es bei
|
|
- [Architektur und Entscheidungen](docs/architektur.md)
|
|
- [Austauschformat für Vorlagen](docs/vorlagen-format.md) — wie eine Vorlage als JSON aussieht
|
|
- [Betrieb](docs/betrieb/) — [Inbetriebnahme mit Compose](docs/betrieb/inbetriebnahme.md),
|
|
[nativ im Proxmox-LXC](docs/betrieb/proxmox.md), [Reverse Proxy](docs/betrieb/reverse-proxy.md),
|
|
[Anbindung an authentik](docs/betrieb/authentik.md), [Sichern und Zurückspielen](docs/betrieb/sicherung.md)
|
|
- [Konventionen](CLAUDE.md) — Sprache, Schichtung, feste Regeln
|
|
- [Änderungen](CHANGELOG.md) · [Entwicklungstagebuch](docs/devlog.md)
|
|
|
|
## Lizenz
|
|
|
|
Noch nicht festgelegt.
|