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>
This commit is contained in:
co-authored by
Claude Opus 5
parent
d30bb6d235
commit
f3950847f3
@@ -62,6 +62,9 @@ Sitzungen ungültig *und* die verschlüsselt gespeicherten LDAP-Bind-Passwörter
|
||||
Signatur nachrechnen, und wo man nachsieht, wenn keine Mail mehr ankommt
|
||||
- **[SCIM-Endpunkt](scim.md)** — die einzige Stelle, an der ein fremder Dienst von außen schreibend
|
||||
zugreift: das Token, was nicht unterstützt wird, und was der Reverse Proxy durchlassen muss
|
||||
- **[Das Übergabeprotokoll als PDF](pdf.md)** — eine Zusatzausstattung, die Chromium verlangt und
|
||||
das Bild von 370 MB auf 1,52 GB bringt: wann sie sich lohnt, wie sie dazukommt, und was passiert,
|
||||
wenn man sie weglässt
|
||||
|
||||
## Wo sonst noch etwas steht
|
||||
|
||||
|
||||
@@ -91,6 +91,7 @@ Jede steht mit Kommentar in [`../../.env.example`](../../.env.example). **Pflich
|
||||
| `SEED_ADMIN_EMAIL`, `SEED_ADMIN_PASSWORD` | Der Administrator, den der Seed anlegt. Das Passwort wird nur als argon2-Hash gespeichert. Fehlt eines, überspringt der Seed ihn — und dann kommst du nicht hinein. |
|
||||
| `SEED_MODE` | `INTERN` (Standard) oder `DIENSTLEISTUNG`, `SERVICE` als Alias. Betrifft nur den **ersten** Seed-Lauf; danach steht der Modus in der Datenbank und wird in der Oberfläche umgeschaltet. |
|
||||
| `SEED_ORG_NAME`, `SEED_ORG_KUERZEL` | Name und Kürzel der eigenen Organisation. Das Kürzel (2 bis 6 Großbuchstaben oder Ziffern) steht auf Etiketten und in der Hostname-Regel. |
|
||||
| `CHROMIUM_PFAD`, `PDF_MAX_GLEICHZEITIG`, `PDF_LEERLAUF_MS` | Nur wenn das Übergabeprotokoll als PDF entstehen soll. Das verlangt Chromium im Bild (`--build-arg MIT_PDF=1`) und bringt es von 370 MB auf 1,52 GB; ohne ihn erscheint der Knopf nicht und die Druckansicht bleibt der Weg. Alles dazu in [`pdf.md`](pdf.md). |
|
||||
| `TEST_DATABASE_URL` | Nur für die Tests. Im Betrieb ohne Bedeutung. |
|
||||
|
||||
## 4. Starten
|
||||
|
||||
@@ -0,0 +1,167 @@
|
||||
# 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:
|
||||
|
||||
```bash
|
||||
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:
|
||||
|
||||
```yaml
|
||||
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:
|
||||
|
||||
```bash
|
||||
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:
|
||||
|
||||
```bash
|
||||
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:
|
||||
|
||||
```bash
|
||||
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:
|
||||
|
||||
```bash
|
||||
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`](../../.env.example) — die drei Variablen mit Kommentar
|
||||
- [`inbetriebnahme.md`](inbetriebnahme.md) — der Compose-Weg im Ganzen
|
||||
- [`proxmox.md`](proxmox.md) — der native Weg und `deploy/install.sh`
|
||||
- [`../anleitung/02-laeufe-abarbeiten.md`](../anleitung/02-laeufe-abarbeiten.md) — wie man das Protokoll als Anwender holt
|
||||
Reference in New Issue
Block a user