# Changelog ## [0.6.1] - 2026-09-22 > **Fuer Bestandsinstallationen wichtig.** Wer 0.6.0 bereits eingespielt hat, > laeuft auf Debian 12 mit hoher Wahrscheinlichkeit im Totalausfall: der Dienst > meldet `active`, `--check-config` meldet "Preflight ok" — und **jede** PDF > landet in `error/`. Nach dem Update auf 0.6.1 nachsehen, ob in `error/` > unverarbeitete Dateien liegen, und diese zurueck nach `incoming/` schieben. > Der Updater faehrt jetzt selbst einen Rauchtest, der so einen Zustand sofort > aufdeckt. ### Fixed - **ocrmypdf-Pin von 16.13.0 auf 17.4.1 korrigiert — das war ein stiller Totalausfall.** 0.6.0 pinnte `ocrmypdf==16.13.0`. Auf Bestandssystemen mit vorher `ocrmypdf>=16.0` war das ein **Downgrade** von 17.4.1, und auf Debian 12 (Ghostscript 10.0.0) bricht ocrmypdf 16.13.0 bei jeder PDF ab: ``` MissingDependencyError: Ghostscript 10.0.0 through 10.02.0 (your version: 10.0.0) contain serious regressions that corrupt PDFs with existing text ``` Ursache, verifiziert im Quelltext von `ocrmypdf/builtin_plugins/ghostscript.py::check_options()`: bis einschliesslich 16.x laeuft die Ghostscript-Pruefung **bedingungslos** — `skip_text=true` allein genuegt, `output_type` wird gar nicht geprueft, obwohl die Fehlermeldung selbst `--output-type pdf` empfiehlt. Ab **17.0.0** umschliesst denselben Block ein `if options.output_type.startswith('pdfa'):`; ohne PDF/A wird Ghostscript nicht angefasst. `run_ocr()` setzt bei leerem `pdfa_level` genau `output_type="pdf"` und hielt sich damit faelschlich fuer sicher. Betroffen war nicht nur das Update, sondern ebenso jede **Neuinstallation**: `skip_text = true` ist der Default. — 17.4.1 ist die auf Debian 12 + gs 10.0.0 real verifizierte Version; `skip_text` bleibt in 17.x als Alias fuer `mode='skip'` unterstuetzt, `run_ocr()` musste nicht angepasst werden. - **Preflight prueft jetzt die reale Bedingung.** `check_preflight()` sah die Ghostscript-Version bisher nur bei gesetztem `pdfa_level` an (`if pdfa_level:`) — genau deshalb ging der kaputte Zustand als "Preflight ok" durch. Die neue Bedingung (`_gs_block_reason()`) bildet ocrmypdf nach: ``` betroffene GS-Version UND skip_text UND (pdfa_level ODER ocrmypdf < 17) ``` Die Signatur ist jetzt `check_preflight(pdfa_level, skip_text)`; alle Aufrufstellen (`run()`, `run_once()`, `--check-config`) reichen beides durch. `redo_ocr` steht bewusst **nicht** in der Bedingung: die Config kennt keinen solchen Key, und ein erfundener waere schlimmer als ein fehlender. Ergebnis: der Dienst bricht beim **Start** mit Exit 2 ab statt bei der ersten Datei, und `--check-config` meldet den Zustand als **Fehler** (Exit 2) — also auch mitten im Update. Die Meldung nennt beide Auswege: Ghostscript >= 10.02.1 aus bookworm-backports (der Installer bietet das an) oder `[ocr].skip_text = false`. ### Added - **`update.sh` macht Versionsspruenge der Kernabhaengigkeiten sichtbar.** Die Versionen der in `requirements.txt` gepinnten Pakete werden vor und nach `pip install` gemessen; Downgrades erscheinen als `[WARN]`, Upgrades und neue Pakete als `[INFO]`, beides zusaetzlich in der Abschluss-Zusammenfassung. Das Downgrade 17.4.1 -> 16.13.0 verschwand bisher wortlos hinter `[INFO] Dependencies ok ✓`. - **Rauchtest in `update.sh`.** Nach dem Start jeder Instanz geht eine winzige Test-PDF durch die **echte** Pipeline; der Test gilt als bestanden, wenn sie in `outgoing/` ankommt. Erst das deckt einen Totalausfall auf, den systemd nicht sieht. - Die Test-PDF (694 Bytes, eine Seite) steckt als base64 im Skript — kein Pillow, kein `gs`, kein `convert` noetig. - Eindeutiger Dateiname (`__smoketest_update__.pdf`), der mit keiner Kundendatei kollidieren kann. - **Raeumt restlos auf** — Testdatei und Ergebnis, in `incoming/`, `working/` (inkl. `__ocr_`-Zwischendatei), `outgoing/`, `error/` und Archiv, auch bei Fehlschlag und Timeout. - **Uebersprungen** bei Instanzen mit aktivem `[upload.nextcloud]`, `[upload.sftp]`, `[notify.email]` oder `[upload.folder]` mit gesetztem `target`: dort wuerde die Testdatei nach aussen gehen, im Zweifel zum Kunden. `[upload.folder]` ohne `target` schreibt nach `outgoing/` und ist harmlos. - Wartezeit `SMOKE_TIMEOUT` (Standard 90 s), danach durchgefallen — das Skript haengt nicht. - Ein Fehlschlag setzt den Exit-Code auf 1 und nennt den `journalctl`-Befehl, rollt aber **nichts** zurueck. - Abschaltbar mit `--no-smoke-test`, dokumentiert in `--help`. - `--check-config` zeigt zusaetzlich `skip_text` sowie die installierte ocrmypdf- und Ghostscript-Version an. ### Changed - **Doku praezisiert.** `config.example.toml`, `config.py`, `docs/INSTALLATION.md` und `docs/UPDATE.md` behaupteten sinngemaess, `pdfa_level = ""` sei der sichere Default gegen den Ghostscript-Bug. Das stimmt so nicht: die Entwarnung haengt an der ocrmypdf-Version und gilt erst ab 17. Der Ghostscript-Abschnitt in `INSTALLATION.md` stellt die Bedingung jetzt je ocrmypdf-Major gegenueber und nennt `skip_text = false` als zweiten Weg; `UPDATE.md` beschreibt Rauchtest und Versionssprung-Meldung. - Kommentarblock in `requirements.txt` korrigiert: der Schutz gilt gegen den naechsten ungewollten Major-Sprung (18), **nicht** gegen 17 — samt Begruendung, warum 16.x fuer uns unbrauchbar ist. ### Tests - 152 statt 135 Tests. Neu: die Preflight-Matrix aus GS-Version x `skip_text` x `pdfa_level` x ocrmypdf-Major — darunter der Fall, der durchrutschte (betroffene GS-Version + `skip_text=true` + leeres `pdfa_level` + ocrmypdf 16.x muss `PreflightError` ausloesen) und die Gegenprobe, dass dieselbe Config mit ocrmypdf 17.x **nicht** ausloest (sonst startet keine Debian-12-Bestandsinstanz mehr). Dazu `ocrmypdf_checks_gs_always()`, der Abbruch in `run_once()` und Exit 2 bei `--check-config`. ## [0.6.0] - 2026-09-22 ### Added - **Wiederaufnahme aus `working/` beim Start.** `process_pdf()` verschiebt das Original vor dem OCR nach `working/`. Wurde der Dienst dort hart abgeschossen (SIGKILL nach `TimeoutStopSec`), blieb die Datei liegen und wurde **nie wieder angefasst** — stiller Datenverlust. `_scan_working()` greift sie jetzt beim Start auf (vor `incoming/`), das OCR laeuft fuer sie neu. Liegt in `incoming/` eine gleichnamige, andere Datei, bekommt die wiederaufgenommene einen Zeitstempel angehaengt, damit sich beide nicht ueberschreiben. Liegt in `working/` bereits eine andere Datei desselben Namens, bricht `process_pdf()` fuer die neue ab und laesst sie in `incoming/` liegen, statt den laufenden Vorgang stillschweigend zu ueberschreiben. - **Unvollstaendige OCR-Fragmente werden geloescht.** Die Zwischendatei, in die ocrmypdf schreibt, traegt jetzt das Praefix `__ocr_` (`OCR_TEMP_PREFIX`). Bleibt so eine Datei nach einem harten Stopp in `working/` liegen, ist sie als Eingabe unbrauchbar und als Ergebnis wertlos — sie wird beim Start mit einer Warnung entfernt, damit sie niemand fuer ein fertiges PDF haelt. - **`--check-config`**: prueft eine Instanz-Config, ohne irgendetwas zu verarbeiten (hat Vorrang vor `--once`). Zeigt die vier Pfade inkl. Hinweis auf noch fehlende Verzeichnisse, Sprachen, Seiten-Timeout und PDF/A-Level, faehrt Preflight und `[output]`-Validierung und gibt alle Warnungen aus. Exit **0** = sauber, **1** = nur Warnungen, **2** = Fehler (Dienst wuerde nicht starten). - **Legacy-Warnungen fuer `[ocr].timeout` und `[ocr].pdfa_level`.** Ein `timeout >= 900` stammt fast sicher aus einer Config vor 0.4.0, wo der Wert ein wirkungsloses Gesamt-Timeout mit Default 1800 war — seither sind es Sekunden **pro Seite** (Richtwert 300). Ein gesetztes `pdfa_level` weist auf den Ghostscript-Bug hin. Die Texte stehen nur in `config.py` (`legacy_warnings()`), weil sie sowohl beim Dienststart ins Log gehen als auch von `--check-config` ausgegeben werden. - **Unbekannte Config-Keys werden gemeldet** statt still verworfen. `_collect_unknown_keys()` sammelt Tippfehler (`[ocr].langauges`), Optionen aus aelteren Versionen, unbekannte Sektionen und unbekannte Upload-/Notify-Targets in `Config.unknown_keys`; die Meldung nennt den vollen Pfad. Warnung, kein Fehler — der Dienst startet, der Eintrag tut nur nichts. - **`TimeoutStopSec=300` in der Template-Unit**: ein laufendes OCR darf beim Stoppen zu Ende laufen. Ein `systemctl stop` kann dadurch pro Instanz bis zu 5 Minuten dauern — das ist gewollt, ein SIGKILL wuerde den Durchlauf kosten. - **Feste Pins in `requirements.txt`** (`ocrmypdf==16.13.0`, `watchdog==6.0.0`, `requests==2.33.1`, `paramiko==4.0.0`). Ohne Pins zieht ein `pip install --upgrade` beim Update ungefragt einen Major-Sprung ein; ocrmypdf 16 -> 17 wuerde alle Instanzen auf einmal reissen. Geprueft gegen Python 3.11 (Debian 12) und 3.13 (Debian 13), Wheels fuer beide vorhanden. - 40 neue Tests (Wiederaufnahme aus `working/`, `--check-config`, Config-Warnungen). Suite jetzt **135 Tests**. ### Changed - **Die Betriebsdoku ist in drei Dokumente aufgeteilt.** Der README ist wieder der Einstieg (Kurzbeschreibung, Features, Schnellstart, Verzeichnis-Layout, Config-Ueberblick) und verlinkt: - `docs/INSTALLATION.md` — Erstinstallation, Basis-Install vs. Instanz-Anlage, die Abfragen pro Instanz, Multi-Instanz-Betrieb, LXC (Error 226/NAMESPACE), Ghostscript auf Debian 12, Instanz manuell loeschen und die vollstaendige **Konfigurationsreferenz**. - `docs/UPDATE.md` — Ablauf von `update.sh`, was es nicht anfasst, Backup-Inhalt/-Rechte/-Rotation, Rollback und dessen Grenzen, `--check-config` mit den Exit-Codes und Config-Drift. - `docs/OS-UPGRADE.md` — Debian-Major-Upgrade als eigener Ablauf. `AI_AGENT_BRIEFING.md` bleibt der Agent-Kontext (Aufbau und Begruendungen) und verweist fuer Ablaeufe auf die drei Dokumente, statt sie zu wiederholen. Nichts wird doppelt gepflegt. - **`update.sh` komplett ueberarbeitet** (`--help`, `--rebuild-venv`, `set -Eeuo pipefail`): - **venv-Health-Check und Neubau.** Geprueft werden Existenz, Lauffaehigkeit des Interpreters, `major.minor` gegen das System-Python und `pyvenv.cfg`. Passt etwas nicht — typisch nach einem Debian-Major-Upgrade, systemd meldet dann `203/EXEC` —, wird die venv neu gebaut, auch ohne `--rebuild-venv`. Der Neubau ist ganz oder gar nicht: alte venv weg sichern, neu bauen, Requirements installieren, **erst bei Erfolg** die alte loeschen; scheitert etwas, wird zurueckgerollt und hart abgebrochen. Scheitert pip an einem Pin, nennt das Skript das gescheiterte Paket und den naechsten Schritt ("requirements.txt anheben"). - **apt-Sync auch beim Update.** Die Paketliste wird aus `install.sh` extrahiert (einzige Quelle, Marken `BEGIN/END apt-packages`) und installiert; nachinstallierte Tesseract-Sprachpakete bleiben unangetastet (kein purge, kein autoremove). Fehlschlaege warnen nur. - **Haertere Verifikation.** Nach dem Start prueft `verify_unit()` nicht nur `is-active`, sondern auch `is-failed` und den Restart-Zaehler — ein Crash-Loop galt bei `Type=simple` bisher als Erfolg. Die Zusammenfassung stellt Soll gegen Ist und meldet eine **Regression** namentlich. - **Vollstaendiges Backup.** Gesichert werden Code, alle Instanz-Configs, die Template-Unit, alle Drop-ins und ein `pip-freeze.txt` der alten venv — ohne venv und ohne Datenverzeichnisse. Weil die Configs Klartext-Passwoerter enthalten, wird das Archiv mit `umask 077` erzeugt und auf `0600 root:root` gesetzt, das Verzeichnis auf `700`. Rotation: die letzten 5 Archive bleiben. - **ERR-Trap.** Bricht das Update ab (Fehler, Strg-C, `kill`), sagt das Skript, ob auf der Platte schon getauscht wurde, startet die vorher laufenden Instanzen wieder und nennt Backup-Datei und Rollback-Befehl. - **Instanz-Erfassung** deckt jetzt auch `activating` und `failed` ab (ueber `list-units --all`, `list-unit-files` und die vorhandenen Configs). Vorher kaputte Instanzen werden mitgestartet, gelten aber erst als Erfolg, wenn sie danach wirklich laufen; bewusst gestoppte bleiben gestoppt. - **Config-Pruefung vor dem Start**: `--check-config` je Instanz, Exit 2 zaehlt als Fehler (Update-Exit 1), Exit 1 wird als Warnung samt Nachstell-Befehl ausgegeben. Kennt der installierte Code das Flag noch nicht, wird die Pruefung uebersprungen und das Update laeuft weiter. - **`install.sh` repariert eine kaputte venv.** Bisher reichte das blosse Vorhandensein von `venv/`, um den Basis-Install zu ueberspringen — nach einem Distributions-Upgrade hat der Installer damit gar nichts repariert. Jetzt wird die venv gegen das System-Python geprueft und bei Drift nach `venv.old-` gesichert und neu gebaut. - Die apt-Paketliste steht als **einzige Quelle** in `install.sh` in der Funktion `pdf_ocr_apt_packages()` zwischen den Marken `# --- BEGIN apt-packages` / `# --- END apt-packages`. `update.sh` schneidet den Block heraus und wertet ihn aus — Marken und Funktionsname duerfen sich nicht ohne Anpassung aendern. ### Fixed - **Dateien in `working/` gingen nach einem harten Stopp still verloren.** Siehe Wiederaufnahme oben — der Fall trat bei jedem SIGKILL waehrend eines OCR-Laufs auf, also auch bei einem Update ohne `TimeoutStopSec`. - **Tippfehler in Config-Keys fielen nicht auf.** `load_config()` filterte stumm gegen die Dataclass-Annotationen; `[ocr].langauges` lief damit wirkungslos mit. Jetzt gibt es eine Warnung mit vollem Key-Pfad. ## [0.5.0] - 2026-09-22 ### Added - Der Installer weist einen Archiv-Pfad ab, der auf `incoming/`, `outgoing/`, `working/` oder `error/` der Instanz zeigt — im Eingang wuerde das Original sonst endlos neu aufgegriffen. - **`install.sh` fragt beim Anlegen einer Instanz die OCR-Sprachen ab** (`Tesseract-Sprachen [deu+eng]:`). Die Wahl gilt bewusst **pro Instanz** — ein Hotfolder `buchhaltung` kann mit `deu` laufen, ein Hotfolder `export` mit `deu+eng+fra`. Der Installer weist vorher darauf hin, dass jede zusaetzliche Sprache Laufzeit **und** Erkennungsqualitaet kostet, die Liste also eng gehalten werden sollte. Das Eingabeformat wird geprueft (Sprachcodes mit `+` verbunden, `chi_sim` & Co. erlaubt); bei Unsinn wird erneut gefragt statt abzubrechen. - **Sprachpakete werden nachinstalliert.** Jeder eingegebene Code wird gegen `tesseract --list-langs` geprueft. Fehlt eine Sprachdatei, bietet der Installer das passende apt-Paket an (`tesseract-ocr-`, Unterstrich wird zum Bindestrich: `chi_sim` → `tesseract-ocr-chi-sim`). Lehnt der User ab oder laesst sich das Paket nicht installieren, warnt der Installer, dass OCR mit dieser Sprache **bei jeder Datei** scheitern wuerde, und fragt die Sprachen erneut ab — so kann die Sprache einfach wieder rausgeworfen werden. Ist `tesseract` nicht aufrufbar, wird die Pruefung uebersprungen und die Eingabe unveraendert uebernommen. - **Abfrage `Original nach erfolgreichem OCR archivieren? [j/N]:`** — Default nein, also weiterhin `original_on_success = "delete"`. Bei ja wird der Archiv-Pfad abgefragt (Vorschlag `/archive`), angelegt und auf den Service-User gechownt; ein Archiv ausserhalb des Instanz-Basis-Pfads bekommt ein eigenes `chown -R`. ### Changed - Die Instanz-Config wird weiterhin per `sed` aus `config.example.toml` erzeugt, substituiert jetzt aber zusaetzlich `[ocr].languages`, `[output].original_on_success` und `[output].archive_dir` — bisher waren das die Beispiel-Defaults, `archive_dir` musste von Hand nachgetragen werden. Die Ausdruecke sind am Zeilenanfang verankert (`^key[[:space:]]*=`), damit die deutschen Kommentarzeilen ueber den Keys unangetastet bleiben, und Pfad-Variablen laufen durch `sed_escape_repl()` (maskiert `\`, `&`, `|`) — Pfade mit Sonderzeichen landen damit korrekt in der Config. - Nach dem sed-Lauf liest der Installer die drei Keys aus der erzeugten Config zurueck und vergleicht sie mit der Eingabe. Erst wenn das passt, nennt die Abschluss-Zusammenfassung zusaetzlich die gewaehlten **Sprachen** und (bei Archivierung) das **Archiv-Verzeichnis**; sonst gibt es eine Warnung. ## [0.4.1] - 2026-09-22 ### Fixed - **veraPDF-FAIL hat das Original immer gelöscht.** Schlug die PDF/A-Validierung fehl, wanderte das OCR-Ergebnis nach `error/` und das Original wurde per `unlink()` entfernt — unabhängig von `[output].original_on_success`. Wer `archive` konfiguriert hatte, verlor die Datei also ausgerechnet im Fehlerfall. Der FAIL-Pfad nutzt jetzt dieselbe `_dispose_original()`-Logik wie der Erfolgsfall: `archive` legt das Original samt Timestamp-Kollisionsschutz im `archive_dir` ab, `delete` verhält sich wie bisher. Die Log-Meldung nennt jetzt beides — wohin das OCR-Ergebnis ging und was mit dem Original passiert ist. ### Removed - Das nie benutzte Logverzeichnis `/var/log/pdf-ocr-hotfolder/` wird nicht mehr vom Installer angelegt und ist aus README und Briefing entfernt. Es hat nie ein Logfile enthalten: `_setup_logging()` nutzt `logging.basicConfig()` ohne FileHandler, der Dienst loggt nach stdout → journald. **journald ist damit die einzige Log-Quelle** (`journalctl -u pdf-ocr-hotfolder@ -f`). Weder Installer noch Updater fassen das Verzeichnis an: ein vorhandenes, leeres `/var/log/pdf-ocr-hotfolder/` kann auf bestehenden Installationen gefahrlos von Hand entfernt werden (`sudo rmdir /var/log/pdf-ocr-hotfolder`). ### Added - 3 neue Tests für den veraPDF-FAIL-Pfad (`delete`, `archive`, Archiv-Namenskollision); veraPDF wird dabei gemockt. Suite jetzt 95 Tests. ## [0.4.0] - 2026-09-22 ### Added - `[ocr].timeout` ist jetzt wirksam: der Wert wird als `tesseract_timeout` (Sekunden pro Seite) an ocrmypdf durchgereicht. Bisher war der Key zwar dokumentiert, wurde aber nirgends gelesen. - `check_output_config()` validiert zusätzlich `[output].name_mode`. Ein Tippfehler führt jetzt beim Start zum Abbruch mit Exit-Code 2, statt erst pro Datei zuzuschlagen — und zwar bisher **nach** dem Verschieben nach `working/`, wo die Datei dann liegen blieb. - Neue Exception `ConfigError` in `pdf_ocr_hotfolder.config` — fehlende `[paths]`-Sektion oder ein fehlender Pfad-Eintrag liefern eine deutsche Fehlermeldung mit Datei- und Key-Nennung statt eines nackten `KeyError`-Tracebacks. Die CLI bricht damit sauber mit Exit-Code 2 ab. - `pytest.ini` mit `testpaths = tests`, damit `pytest` aus dem Repo-Root läuft. - 35 neue Tests: Fehlerzählung (Exception, Upload, Stabilitäts-Timeout), Config-Fehlermeldungen, `tesseract_timeout`-Durchreichung (ocrmypdf gemockt) und `upload_folder()`. ### Changed - **`[ocr].timeout` hat eine neue Bedeutung — für bestehende Installationen relevant!** Der Wert ist kein (nie implementiertes) Gesamt-Timeout pro PDF mehr, sondern das Limit **pro Seite** für Tesseract. Der Default sinkt entsprechend von `1800` auf `300`. Wer den alten Wert `1800` in seiner `config.toml` stehen hat, gibt Tesseract damit 30 Minuten **je Seite** — bitte auf einen Seiten-Wert anpassen (Richtwert 300). `0` bedeutet "kein eigenes Limit": der Wert wird dann gar nicht erst durchgereicht, weil ocrmypdf `tesseract_timeout=0` als "OCR komplett überspringen" interpretiert. - `_dispatch_uploads()` liefert jetzt die Namen der fehlgeschlagenen Upload-Ziele zurück; die doppelte `enabled`-Prüfung (Service + Uploader) ist entfallen — die Uploader prüfen das selbst. - `upload_folder()` kopiert mit `shutil.copyfile()` statt `read_bytes()`/`write_bytes()` — große PDFs landen nicht mehr komplett im Speicher. Die Selbst-Ziel-Erkennung bleibt unverändert. ### Fixed - `OcrConfig.pdfa_level` hatte im Code noch den Default `"2"`, obwohl `config.example.toml` seit 0.2.2 bewusst `""` setzt (Ghostscript-Bug, Issue #3). Eine Config ohne `[ocr]`-Sektion bzw. ohne den Key lief damit ungewollt in PDF/A. Default im Code jetzt ebenfalls `""`. - Eine Exception **nach** dem OCR (z.B. ein fehlgeschlagener `shutil.move()` nach `outgoing/`) wurde nur im Worker-Callback geloggt. `error_count` blieb 0 und `--once` lieferte trotz Fehlschlag Exit-Code 0. Jede Exception aus `process_pdf()` zählt jetzt als Fehler, wird geloggt, löst eine Fehler-Mail aus und die Datei wandert — soweit noch auffindbar (`incoming/` oder `working/`) — nach `error/`. - Fehlgeschlagene Uploads waren folgenlos: die Rückgabewerte der Uploader wurden verworfen, es ging sogar eine Erfolgs-Mail raus. Jetzt zählt mindestens ein fehlgeschlagenes Ziel als Fehler und die E-Mail geht als **FEHLER** raus, mit Nennung der betroffenen Ziele. Das OCR-PDF bleibt bewusst in `outgoing/` liegen (das OCR selbst war ja erfolgreich) — das steht so auch im Log. - Lief der Stabilitäts-Check einer Datei in den 60-Sekunden-Timeout, gab es nur ein `log.warning`; `--once` meldete Exit-Code 0. Jetzt `log.error` + `error_count`. Die Datei bleibt bewusst in `incoming/` liegen und wird beim nächsten Lauf erneut versucht. Eine zwischenzeitlich *verschwundene* Datei wird davon unterschieden und zählt weiterhin nicht als Fehler. ## [0.3.1] - 2026-04-10 ### Fixed - **Issue #4**: LXC/Container-Kompatibilität — systemd-Hardening (`PrivateTmp`, `ProtectSystem`, etc.) verursacht Error 226/NAMESPACE in LXC-Containern. Installer erkennt Container-Umgebung automatisch und bietet ein Drop-in an. Zusätzlich liegt `systemd/lxc-compat.conf` als Vorlage im Repo. - **Issue #5**: `WorkingDirectory=/opt/pdf-ocr-hotfolder` in der systemd Template-Unit ergänzt — ohne diesen Eintrag konnte das Python-Modul nicht gefunden werden. - **Issue #6**: Auf Debian 12 bietet der Installer bei betroffenen Ghostscript-Versionen (10.0.0–10.02.0) jetzt automatisch an, bookworm-backports zu aktivieren und GS zu upgraden (statt nur zu warnen). ## [0.3.0] - 2026-04-09 ### Added - Neue Config-Sektion `[output]` mit: - `name_mode` — Platzierung des Tags im Dateinamen: `"prefix"`, `"suffix"` (vor Extension), `"none"` - `name_tag` — verbatim einzufügender String, z.B. `"OCR_"` oder `"_OCR"` - `original_on_success` — `"delete"` (alter Default) oder `"archive"` - `archive_dir` — Zielverzeichnis für `"archive"`, mit Kollisions-Schutz (Timestamp-Suffix) - Runtime-Validierung der Output-Config in `check_output_config()` - 20 neue Tests für `build_output_name()`, `check_output_config()` und `process_pdf()` mit allen Kombinationen aus Modus + Original-Behandlung ### Changed - `process_pdf()` nimmt jetzt `output_cfg: OutputConfig` als Pflicht-Argument ## [0.2.2] - 2026-04-09 ### Fixed - **Issue #3**: Ghostscript 10.0.0–10.02.0 (Debian 12 default) zerschießen OCR mit PDF/A + `skip_text=true`. - `config.example.toml`: `pdfa_level = ""` als sicherer Default - Runtime-Preflight: Prüft `gs --version` wenn `pdfa_level` gesetzt ist, bricht mit klarer Fehlermeldung ab - `install.sh`: warnt bei betroffenen GS-Versionen mit Upgrade-Hinweis auf bookworm-backports ### Added - `is_ghostscript_broken()` / `detect_ghostscript_version()` in `pdf_ocr_hotfolder.service` - 19 weitere pytest-Tests für GS-Versions-Detection (parametrisiert) und Preflight-Kombinationen ## [0.2.1] - 2026-04-09 ### Fixed - **Issue #1**: Preflight-Check beim Start prüft jetzt `tesseract` und `gs` (Ghostscript). Fehlt eine Abhängigkeit, beendet sich der Service sofort mit Exit-Code 2 und klarer Fehlermeldung statt erst bei der ersten Datei. - **Issue #2**: `--once`-Modus liefert jetzt Exit-Code `1`, sobald **mindestens ein** PDF fehlgeschlagen ist. Exit-Code `0` nur bei vollständigem Erfolg (inkl. "keine Dateien vorhanden"). Exit-Code `2` bei Preflight-Fehler. ### Added - Public API: `HotfolderService.run_once()`, `.success_count`, `.error_count`, `.ensure_dirs()` - `check_preflight()` / `PreflightError` in `pdf_ocr_hotfolder.service` - pytest-Test-Suite (`tests/`) mit 11 Tests — deckt alle Szenarien aus Issue #1 und #2 ab - `ocrmypdf`-Import in `processor.py` ist jetzt lazy (Tests ohne ocrmypdf-Installation möglich) ## [0.2.0] - 2026-04-08 ### Added - **Multi-Instanz-Support** via systemd Template-Unit `pdf-ocr-hotfolder@.service` - Pro Instanz: eigene Config (`/etc/pdf-ocr-hotfolder/.toml`), eigene Datenverzeichnisse (`/var/lib/pdf-ocr-hotfolder//…`), optional eigener Service-User via Drop-in - **Instanz-Manager in `install.sh`**: erkennt bestehende Instanzen bei Re-Run, fragt nach weiteren, listet Namen + Status - `update.sh` stoppt/startet automatisch **alle** laufenden Instanzen ### Changed - Single-Unit `pdf-ocr-hotfolder.service` durch Template-Unit `pdf-ocr-hotfolder@.service` ersetzt - Installer fragt nicht mehr einmalig nach Service-User, sondern **pro Instanz** ### Removed - Alte Single-Config unter `/etc/pdf-ocr-hotfolder/config.toml` — wird nicht mehr erzeugt ## [0.1.0] - 2026-04-08 ### Added - Initiale Version (Komplettes Rewrite des alten Bash-Tools `pdf-tool`) - Python-Implementation auf Basis von `ocrmypdf` (Library, kein Subprozess) - Hotfolder-Watcher mit `watchdog` (created/moved/closed Events) - File-Stability-Check (wartet bis Scanner fertig geschrieben hat) - ThreadPool für parallele PDF-Verarbeitung (`max_workers`) - Upload-Targets: lokaler Ordner, Nextcloud (WebDAV via `requests`), SFTP (`paramiko`) - E-Mail-Notify (`smtplib`, immer / nur Fehler / nie) - Optional veraPDF-Validierung - TOML-Konfiguration (`tomllib` aus stdlib, Python ≥3.11) - systemd-Unit mit Hardening-Optionen - `install.sh` mit interaktivem Service-User-Prompt (lokal anlegen oder bestehenden lokalen/AD-User übernehmen) - `update.sh` mit Backup, Code-Sync und Service-Reload - README.md, AI_AGENT_BRIEFING.md