Legt die Grundlage, auf der alle weiteren Meilensteine nur noch Fachlichkeit ergänzen: Next 16 mit App Router und TypeScript strict, Tailwind 4, Prisma 7, Vitest 5, Playwright, ESLint, Prettier und die Hooks für Lint und Commits. Zwei Dinge sind bewusst so gebaut und durch Tests festgehalten: Die Datenbank ist im Betriebsstand von außen nicht erreichbar. Sie hat kein ports:-Mapping und hängt allein im Netz "intern", das ohne Gateway läuft; nur "web" kommt zusätzlich nach draußen. tests/betriebsstand.test.ts schlägt an, falls ein Port-Mapping zurückkehrt. Geprüft, nicht behauptet: docker compose up startet den Stack, 5432 ist vom Host aus zu, aus "web" offen. pnpm pruefe:doku hält Dokumentation und Code zusammen — Version im Changelog, Issue-Dateien gegen ihre Übersicht, keine Verweise auf fehlende Dateien, keine fehlenden Bilder. Das Skript wurde gegen vier eingebaute Fehler gegengeprüft. Drei Versions-Obergrenzen sind nötig und in CLAUDE.md begründet: eslint bleibt auf 9.x, weil eslint-plugin-react keinen Nachfolger für eslint 10 hat und das Regelwerk sonst beim Laden abbricht; typescript bleibt auf 6.0.x, weil typescript-eslint TypeScript 7 noch nicht unterstützt; prisma ist exakt auf 7.10.0 gepinnt, weil sein latest-Tag auf einen 8.0-RC zeigt, der von @prisma/client aber nicht. Auth.js entfällt entgegen der ursprünglichen Vorgabe: v5 steht dauerhaft auf Beta, das Projekt ist im Wartungsmodus und sein Prisma-Adapter deckt Prisma 7 nicht ab. Stattdessen openid-client und jose, wie in ProxLedger. Dieser erste Commit geht direkt auf main, weil es noch keinen Branch gibt, von dem abzuzweigen wäre. Ab #006 läuft jedes Issue über einen eigenen Branch. Closes #5 Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
150 lines
5.2 KiB
Markdown
150 lines
5.2 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.
|
|
|
|
## 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)
|
|
- [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.
|