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>
7.4 KiB
Das Übergabeprotokoll als PDF
Jeder Lauf lässt sich als PDF herunterladen — ein Blatt mit Kopfdaten, allen Schritten samt Zuständen und Notizen, den Kommentaren und zwei Unterschriftsfeldern. Gedacht ist es für die Übergabe: ein Gerät wechselt den Besitzer, beide unterschreiben, das Blatt geht in die Ablage oder zum Kunden.
Das ist eine Zusatzausstattung, und sie kostet. Ohne sie fehlt nichts Wesentliches: die Druckansicht zeigt dasselbe Blatt, und jeder Browser kann daraus „in eine Datei drucken“. Das PDF vom Server nimmt diesen Schritt nur ab — für den, der zwanzig Protokolle am Stück braucht, oder für den, der sie aus dem Archiv heraus einzeln verschickt.
Was es braucht
Chromium auf dem Server. Nichts weiter — keine Schriften aus dem Netz, kein Dienst, kein zusätzlicher Port.
Der Grund: das PDF entsteht, indem der Server seine eigene Druckansicht lädt, so wie ein Browser es täte, und sie druckt. Dadurch gibt es genau eine Vorlage für Papier und PDF. Ein zweites Layout im Code wäre billiger zu betreiben und würde früher oder später von der Druckansicht abweichen, ohne dass es jemandem auffällt — bis ein Kunde zwei verschiedene Blätter nebeneinander legt.
Chromium ist der Preis dafür, und er ist hoch:
| Laufzeitbild | Größe |
|---|---|
| ohne Chromium | 370 MB |
| mit Chromium | 1,52 GB |
Gemessen am 2026-10-25. Alpines chromium kostet allein 1,13 GB — mehr als das Dreifache der
gesamten Anwendung. Deshalb ist er nicht im Standardbild, und deshalb ist diese Seite kein Anhang,
sondern eine Entscheidung.
Mit Docker Compose
Chromium kommt über ein Bau-Argument ins Bild:
docker compose build --build-arg MIT_PDF=1 web
docker compose up -d
Damit das beim nächsten docker compose build nicht verlorengeht, gehört es in die
docker-compose.yml beziehungsweise in eine Override-Datei:
services:
web:
build:
args:
MIT_PDF: "1"
Mehr ist nicht zu tun: das Paket liegt dann unter /usr/bin/chromium-browser, und dort sucht die
Anwendung von sich aus.
Nativ, im LXC oder auf einem Debian
deploy/install.sh fragt beim ersten Lauf, ob Chromium mit soll, und merkt die Antwort in
/etc/laufzettel/laufzettel.env. Nachträglich:
apt-get install -y chromium
systemctl restart laufzettel
Auf Debian 13 liegt es danach unter /usr/bin/chromium. Der Neustart ist nötig, weil die Anwendung
den Pfad beim ersten PDF sucht und sich das Ergebnis für die Laufzeit des Prozesses merkt.
Wenn Chromium fehlt
Dann passiert nichts Stilles. Der Knopf „Als PDF“ erscheint am Lauf und im Archiv gar nicht
erst — die Anwendung sieht bei jedem Seitenaufbau nach, ob ein Chromium da ist. Wer den Endpunkt
trotzdem aufruft, etwa aus einem alten Tab oder mit einem Lesezeichen, bekommt 503 und diesen
Text:
Auf diesem Server ist kein Chromium installiert, und ohne ihn entsteht kein PDF. Die Druckansicht funktioniert weiterhin: dort im Browser „in eine Datei drucken“. Wie Chromium dazukommt, steht in docs/betrieb/pdf.md.
Ein leeres oder halbes PDF gibt es in keinem Fall. Das war die Vorgabe für dieses Verhalten: ein Blatt, das aussieht wie ein Protokoll und keines ist, wäre schlimmer als eine klare Absage.
Die drei Einstellungen
Alle drei sind Vorgaben, die man kennen sollte und selten braucht.
| Variable | Vorgabe | Wofür |
|---|---|---|
CHROMIUM_PFAD |
leer | Wo Chromium liegt, wenn er woanders liegt |
PDF_MAX_GLEICHZEITIG |
2 |
Wie viele PDFs gleichzeitig entstehen dürfen |
PDF_LEERLAUF_MS |
60000 |
Nach wie viel Leerlauf der Browser wieder schließt |
CHROMIUM_PFAD leer lassen heißt „suche selbst“ — der Reihe nach
/usr/bin/chromium-browser, /usr/bin/chromium, /usr/bin/google-chrome. Ein gesetzter Pfad, der
ins Leere zeigt, ist dagegen ein Fehler und kein Rückfall auf die Suche: wer hier etwas
einträgt, hat eine Absicht, und ein Tippfehler soll auffallen und nicht dazu führen, dass
stillschweigend ein anderer Browser läuft als der gemeinte.
PDF_MAX_GLEICHZEITIG schützt den Arbeitsspeicher. Jedes PDF hält kurz einen eigenen
Browser-Kontext offen — eigen, und nicht geteilt, weil er das Sitzungscookie des Anfragenden trägt.
Ist die Grenze erreicht, bekommt der nächste die Bitte, es gleich noch einmal zu versuchen, statt
einer Maschine, die unter Last steht.
PDF_LEERLAUF_MS hält den Chromium nach dem letzten PDF noch eine Minute offen, weil ihn zu
starten rund zwei Sekunden kostet und Protokolle meist in Serie entstehen. Danach schließt er: offen
belegt er rund 100 MB, und auf einem 2-GB-LXC ist das ein Achtel des Arbeitsspeichers für etwas, das
niemand gerade braucht.
Was auf dem Blatt steht — und was nicht
Es steht dasselbe darauf wie in der Druckansicht, denn es ist die Druckansicht. Insbesondere:
- Kein generiertes Passwort. Wo ein Befehl
{{generiertes_passwort}}enthält, steht auf dem Blatt[Passwort – nicht gespeichert]. Das Passwort entsteht bei jeder Anzeige neu und wird nirgends gespeichert; ein Ausdruck dagegen liegt herum. Geprüft wird das am extrahierten Text des fertigen PDF, nicht an der Vorlage. - Keine Anhänge, nur ihre Anzahl. Ein Foto gehört nicht auf ein Übergabeprotokoll, und ein PDF mit zwanzig Bildern wäre nicht mehr zu verschicken.
- Befehle unmaskiert. Das Blatt hält fest, was zu tun war; Maskierzeichen wären auf Papier Rauschen. Wer einen Befehl ausführen will, nimmt die Laufseite — dort ist er für die gewählte Shell maskiert.
Fehlersuche
Der Knopf fehlt, obwohl Chromium installiert ist. Die Anwendung hat ihn nicht gefunden. Nachsehen,
wo er liegt, und CHROMIUM_PFAD setzen:
command -v chromium chromium-browser google-chrome
Danach den Dienst neu starten. Im Compose-Stand prüft man im Container selbst, nicht auf dem Host:
docker compose exec web command -v chromium-browser
503 mit „Die Druckansicht antwortete mit 307“ (oder 302, oder 401). Das Sitzungscookie kam
nicht mit oder gilt nicht mehr — Chromium wurde auf die Anmeldeseite geschickt. Neu anmelden und es
noch einmal versuchen. Bleibt es dabei, steht vermutlich ein Reverse Proxy im Weg, der Cookie
nicht durchlässt.
503 mit „Es entstehen gerade … PDFs gleichzeitig“. Die Grenze greift. Entweder warten oder
PDF_MAX_GLEICHZEITIG anheben — und dabei im Blick behalten, dass jeder gleichzeitige Vorgang
Arbeitsspeicher kostet.
500 mit „Einzelheiten stehen im Protokoll des Dienstes“. Hier geht die Meldung absichtlich
nicht hinaus, weil eine rohe Ausnahme Pfade und Zugangsdaten nennen kann. Nachsehen:
docker compose logs -f web | grep PDF # Compose
journalctl -u laufzettel -f | grep PDF # nativ
Leere Kästen statt Buchstaben im PDF. Dem Chromium fehlen Systemschriften. Im Bild ist
font-noto deshalb mit dabei; nativ gehört es nachinstalliert
(apt-get install -y fonts-noto-core). Die Schriften der Oberfläche selbst liefert die Anwendung
mit, aber für Zeichen, die darin nicht vorkommen, braucht Chromium einen Rückfall.
Wo sonst noch etwas steht
../../.env.example— die drei Variablen mit Kommentarinbetriebnahme.md— der Compose-Weg im Ganzenproxmox.md— der native Weg unddeploy/install.sh../anleitung/02-laeufe-abarbeiten.md— wie man das Protokoll als Anwender holt