Files
Laufzettel/.env.example
T
Florian WildeandClaude Opus 5 f3950847f3
Prüfung / pruefung (push) Waiting to run
Prüfung / e2e (push) Blocked by required conditions
feat(laeufe): Uebergabeprotokoll als PDF (#041)
Neben "Drucken" steht am Lauf "Als PDF", im Archiv an jedem Treffer "Protokoll
als PDF" — dasselbe Blatt als fertige Datei, zum Verschicken oder Abheften.
Unten darauf jetzt zwei Unterschriftsfelder mit Zeile fuer Ort, Datum und Namen;
wo eine Vier-Augen-Freigabe erteilt wurde, steht sie mit im Kopf.

Erzeugt wird es, indem der Server seine eigene Druckansicht ueber 127.0.0.1
laedt und druckt — mit dem Sitzungscookie des Anfragenden. Damit gibt es genau
eine Vorlage fuer Papier und Datei, und fuer das PDF gilt dieselbe Rechte- und
Mandantenpruefung wie fuer die Seite. Ein zweites Layout waere billiger und
wuerde frueher oder spaeter abweichen, ohne dass es jemandem auffaellt.

Ein eigener Browser-Kontext je PDF, kein geteilter: er traegt die fremde
Sitzung, und ein geteilter gaebe sie an das naechste PDF weiter. Gespeichert
wird nichts — ein abgelegtes PDF waere eine zweite Wahrheit neben dem Lauf.
Ein erzeugtes Passwort steht auch hier nie darauf, geprueft am extrahierten
Text des fertigen PDF und nicht an den Bytes: der Inhalt ist komprimiert, dort
nichts zu finden bewiese nichts.

Chromium bleibt optional, und die gemessene Zahl ist der Grund: das
Laufzeitbild waechst von 370 MB auf 1,52 GB, Alpines chromium kostet allein
1,13 GB. Er kommt nur mit `--build-arg MIT_PDF=1` beziehungsweise
`deploy/install.sh --mit-pdf` mit. Fehlt er, erscheint der Knopf gar nicht
erst — ein Knopf, der zuverlaessig in eine Fehlermeldung fuehrt, ist schlimmer
als keiner. Ein direkter Aufruf bekommt 503 mit einer Meldung, die sagt, was
fehlt und wo es steht; ein leeres PDF gibt es in keinem Fall.

Meine Schaetzung lag um das Dreieinhalbfache daneben: "+328 MB" war das
.apk-Paket, nicht das Bild, und stand schon im Dockerfile-Kommentar, bevor ich
gebaut hatte. Jetzt steht die gemessene Zahl an drei Stellen.

1545 Vitest-Tests (vorher 1526): 15 in tests/pdf.test.ts, die den Handler echt
fahren, 3 in tests/pdf-grenze.test.ts fuer die Obergrenze mit gestelltem
Browser, einer in tests/archiv.test.ts. Dazu drei E2E-Ablaeufe; volle Suite
gruen mit 118 bestanden. Ein Blatt wurde wirklich erzeugt und angesehen.

Sechs Gegenproben, alle angeschlagen — darunter ein echtes Passwort auf dem
Blatt und die Freigabe des Zaehlers aus dem finally genommen, der Fall, in dem
ein Fehlschlag die PDF-Erzeugung bis zum Neustart leise stilllegt.

Closes #41

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-10-25 11:14:25 +01:00

141 lines
6.1 KiB
Bash
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# Laufzettel – Umgebungsvariablen.
# Kopieren nach .env und ausfuellen. Die .env gehoert nie ins Repo.
# ---------- Pflicht ----------
# Oeffentliche Adresse der Anwendung. Steckt in den QR-Codes und in der
# OIDC-Rueckruf-Adresse, muss also stimmen, wenn ein Reverse Proxy davor haengt.
APP_URL=https://laufzettel.example.de
# Geheimnis fuer Sitzungs-Cookies und die Verschluesselung des LDAP-Bind-Passworts.
# Mindestens 32 Zeichen. Erzeugen mit: openssl rand -base64 48
APP_SECRET=
# Zugang zur Datenbank.
#
# Im Compose-Stand baut sich die Adresse aus den drei POSTGRES_*-Werten zusammen:
# die Datenbank heisst dort `postgres` (Dienstname im internen Netz) und ist von
# aussen nicht erreichbar. DATABASE_URL wird dafuer **nicht** gelesen.
#
# DATABASE_URL gilt fuer alles, was ausserhalb der Container laeuft: `pnpm dev`,
# `pnpm prisma …`, `pnpm db:seed` von der Werkbank aus. Standard ist die
# ---- Anhaenge (#032) -------------------------------------------------------
# Wo die Dateien der Anhaenge liegen. Im Compose-Stand setzt docker-compose.yml
# das selbst auf /daten/anhaenge im Volume `anhaenge`; hier gilt es fuer die
# Entwicklung und die native Installation.
#
# **Gehoert in jede Sicherung.** In der Datenbank stehen nur die Metadaten.
ANHAENGE_PFAD=./daten/anhaenge
# Hoechstgroesse je Datei in MB und Hoechstzahl Anhaenge je Lauf.
ANHANG_MAX_MB=10
ANHAENGE_JE_LAUF=20
#
# **Wer das anhebt, hebt `client_max_body_size` im Reverse Proxy mit an.** Sonst
# weist nginx die Datei mit 413 ab, bevor die Anwendung ihre eigene, freundliche
# Meldung zeigen kann — siehe docs/betrieb/reverse-proxy.md.
# ---- Live-Aktualisierung (#033) --------------------------------------------
# Die Abarbeitungsansicht haelt einen Ereignisstrom offen, statt alle zehn
# Sekunden zu fragen. Beide Werte brauchen im Normalfall niemand anzufassen.
#
# Abstand der Herzschlaege in Millisekunden. Muss **unter** der Leerlaufzeit des
# Reverse Proxy liegen (nginx und Caddy: 60 s), sonst schliesst der die stille
# Verbindung.
SSE_HERZSCHLAG_MS=25000
# Hoechstzahl gleichzeitig offener Stroeme je Instanz. Ist sie erreicht, fallen
# weitere Laschen von sich aus auf das Polling zurueck — die Seite bleibt
# bedienbar, nur nicht mehr sofort aktuell.
SSE_MAX_STROEME=200
# Entwicklungsdatenbank aus docker-compose.dev.yml (Port 5434, umstellbar ueber DEV_DB_PORT).
POSTGRES_USER=laufzettel
POSTGRES_PASSWORD=
POSTGRES_DB=laufzettel
DATABASE_URL=postgresql://laufzettel:laufzettel@localhost:5434/laufzettel?schema=public
# ---------- Anmeldung ----------
# Lokaler Login mit E-Mail und Passwort. Fuer die Entwicklung und als Notfallzugang.
# Im Betrieb mit OIDC auf false setzen.
AUTH_LOCAL_ENABLED=false
# Generischer OIDC-Anbieter, Ziel ist authentik. Leer lassen, um OIDC abzuschalten.
OIDC_ISSUER=
OIDC_CLIENT_ID=
OIDC_CLIENT_SECRET=
OIDC_NAME=authentik
# Angeforderte Bereiche. Der Standard reicht fuer authentik mit dem
# Gruppen-Mapping aus docs/betrieb/authentik.md. Nur aendern, wenn der Anbieter
# die Gruppen unter einem anderen Bereich liefert.
OIDC_SCOPE=openid profile email groups
# Zuordnung der Rollen ueber den groups-Claim. Wird bei jedem Login neu ausgewertet.
OIDC_GROUP_ADMIN=lz-admin
OIDC_GROUP_EDITOR=lz-redakteur
OIDC_GROUP_TECHNICIAN=lz-techniker
OIDC_GROUP_VIEWER=lz-leser
# Optional, nur im Betriebsmodus Dienstleistung: Gruppen wie lz-kunde-MUS binden
# Benutzer an Kunden. TECHNIKER und LESER sehen dann nur ihre Kunden.
# Leer lassen heisst: keine Einschraenkung.
OIDC_CUSTOMER_GROUP_PREFIX=
# ---------- Betrieb ----------
# Adresse, auf die der Webdienst gebunden wird. Standard ist nur localhost,
# davor gehoert ein Reverse Proxy. Fuer direkten Zugriff im LAN: 0.0.0.0
BIND_ADDR=127.0.0.1
APP_PORT=3000
# Wird beim Bauen in die Anzeige unten links uebernommen.
APP_VERSION=0.1.0
GIT_SHA=
# ---------- Seed ----------
SEED_ADMIN_EMAIL=admin@firma.example
SEED_ADMIN_PASSWORD=
# INTERN (Standard) oder DIENSTLEISTUNG. SERVICE wird als Alias akzeptiert.
SEED_MODE=INTERN
# Name der eigenen Organisation. Im Modus INTERN taucht er nirgends in der
# Oberflaeche auf, wird aber beim Wechsel zu DIENSTLEISTUNG zum ersten Kunden.
SEED_ORG_NAME=Eigene Organisation
# 2 bis 6 Grossbuchstaben oder Ziffern. Im Modus INTERN unsichtbar; im Modus
# DIENSTLEISTUNG steht das Kuerzel auf Etiketten und in der Hostname-Regel.
SEED_ORG_KUERZEL=EIGEN
# ---------- Verzeichnis-Abgleich ----------
# Der Zeitplan laeuft im selben Prozess wie die Anwendung und sieht jede Minute
# nach, welche Verzeichnisquelle faellig ist; das Intervall steht je Quelle in
# der Datenbank. `false` schaltet ihn ab — fuer Einmal-Container, die nur
# Migrationen fahren, und fuer Testlaeufe.
ZEITPLAN_AKTIV=true
# ---- PDF-Uebergabeprotokoll (#041) -----------------------------------------
# Das Protokoll eines Laufs als PDF entsteht, indem der Server seine **eigene**
# Druckansicht mit Chromium laedt. Dafuer muss Chromium da sein – und das ist er
# absichtlich nicht ueberall: im Bild kostet er 1,13 GB, mehr als die dreifache
# Anwendung. Ohne ihn zeigt die Oberflaeche den Knopf gar nicht erst, und die
# Druckansicht bleibt der Weg. Alles dazu in docs/betrieb/pdf.md.
#
# Leer lassen heisst „suche selbst“: /usr/bin/chromium-browser (Alpine),
# /usr/bin/chromium, /usr/bin/google-chrome. Nur setzen, wo er woanders liegt –
# ein Pfad, der ins Leere zeigt, ist ein Fehler und kein Rueckfall auf die Suche.
CHROMIUM_PFAD=
# Wie viele PDFs gleichzeitig entstehen duerfen. Jedes haelt kurz einen
# Browser-Kontext offen; mehr als eine Handvoll bringt auf kleinen Maschinen nur
# Arbeitsspeicherdruck. Darueber hinaus bekommt der Anwender die Bitte, es gleich
# noch einmal zu versuchen.
PDF_MAX_GLEICHZEITIG=2
# Nach wie vielen Millisekunden Leerlauf der Chromium wieder schliesst. Er haelt
# offen rund 100 MB belegt – auf einem 2-GB-LXC ist das spuerbar.
PDF_LEERLAUF_MS=60000
# ---------- Tests ----------
# Datenbank fuer Vitest. Ohne diese Variable ueberspringen sich die Datenbanktests.
# Passend zu docker-compose.dev.yml (Port 5434, umstellbar ueber DEV_DB_PORT):
TEST_DATABASE_URL=postgresql://laufzettel:laufzettel@localhost:5434/laufzettel_test?schema=public