v0.6.0 hat ocrmypdf auf 16.13.0 gepinnt, um einen ungewollten Major-Sprung
zu verhindern. Auf Bestandsinstallationen war das ein DOWNGRADE (dort lief
via ">=16.0" bereits 17.x) — und 16.13.0 bricht auf Debian 12 mit dem
Bord-Ghostscript 10.0.0 bei JEDER PDF ab, sobald skip_text gesetzt ist.
Im Test auf CT 200 lief das Update mit Exit 0 durch, der Dienst blieb
"active", --check-config meldete "Preflight ok" — und jede Datei landete
in error/. Stiller Totalausfall.
- requirements.txt: ocrmypdf==17.4.1 (real auf Debian 12 + gs 10.0.0
verifiziert). Ab 17.0.0 steht die GS-Pruefung in ocrmypdf unter einem
`if options.output_type.startswith('pdfa')`; bis 16.x lief sie ohne
diesen Guard und schlug auch bei output_type="pdf" zu.
- check_preflight() prueft Ghostscript nicht mehr nur bei gesetztem
pdfa_level, sondern bildet die reale Bedingung ab:
betroffene GS-Version UND skip_text UND (PDF/A ODER ocrmypdf < 17).
Der Dienst bricht damit beim Start ab statt bei der ersten Datei.
- update.sh zeigt Versionsspruenge der gepinnten Pakete; Downgrades als
WARN, auch in der Abschluss-Zusammenfassung.
- update.sh faehrt nach dem Start einen Rauchtest (eingebettete Mini-PDF
durch die echte Pipeline) und raeumt restlos auf. Uebersprungen, wenn
Upload-Ziele oder E-Mail-Notify aktiv sind, damit kein Testmuell zum
Kunden geht. Abschaltbar mit --no-smoke-test.
- Doku korrigiert: pdfa_level = "" allein ist keine Entwarnung, die haengt
an der ocrmypdf-Version.
152 Tests gruen.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
25 KiB
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-configmeldet "Preflight ok" — und jede PDF landet inerror/. Nach dem Update auf 0.6.1 nachsehen, ob inerror/unverarbeitete Dateien liegen, und diese zurueck nachincoming/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 vorherocrmypdf>=16.0war 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 textUrsache, verifiziert im Quelltext von
ocrmypdf/builtin_plugins/ghostscript.py::check_options(): bis einschliesslich 16.x laeuft die Ghostscript-Pruefung bedingungslos —skip_text=trueallein genuegt,output_typewird gar nicht geprueft, obwohl die Fehlermeldung selbst--output-type pdfempfiehlt. Ab 17.0.0 umschliesst denselben Block einif options.output_type.startswith('pdfa'):; ohne PDF/A wird Ghostscript nicht angefasst.run_ocr()setzt bei leerempdfa_levelgenauoutput_type="pdf"und hielt sich damit faelschlich fuer sicher.Betroffen war nicht nur das Update, sondern ebenso jede Neuinstallation:
skip_text = trueist der Default. — 17.4.1 ist die auf Debian 12 + gs 10.0.0 real verifizierte Version;skip_textbleibt in 17.x als Alias fuermode='skip'unterstuetzt,run_ocr()musste nicht angepasst werden. -
Preflight prueft jetzt die reale Bedingung.
check_preflight()sah die Ghostscript-Version bisher nur bei gesetztempdfa_levelan (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_ocrsteht 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-configmeldet 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.shmacht Versionsspruenge der Kernabhaengigkeiten sichtbar. Die Versionen der inrequirements.txtgepinnten Pakete werden vor und nachpip installgemessen; 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 inoutgoing/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, keinconvertnoetig. - Eindeutiger Dateiname (
__smoketest_update_<zeitstempel>_<pid>.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 gesetztemtarget: dort wuerde die Testdatei nach aussen gehen, im Zweifel zum Kunden.[upload.folder]ohnetargetschreibt nachoutgoing/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.
- Die Test-PDF (694 Bytes, eine Seite) steckt als base64 im Skript — kein
Pillow, kein
--check-configzeigt zusaetzlichskip_textsowie die installierte ocrmypdf- und Ghostscript-Version an.
Changed
- Doku praezisiert.
config.example.toml,config.py,docs/INSTALLATION.mdunddocs/UPDATE.mdbehaupteten 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 inINSTALLATION.mdstellt die Bedingung jetzt je ocrmypdf-Major gegenueber und nenntskip_text = falseals zweiten Weg;UPDATE.mdbeschreibt Rauchtest und Versionssprung-Meldung. - Kommentarblock in
requirements.txtkorrigiert: 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_textxpdfa_levelx ocrmypdf-Major — darunter der Fall, der durchrutschte (betroffene GS-Version +skip_text=true+ leerespdfa_level+ ocrmypdf 16.x mussPreflightErrorausloesen) und die Gegenprobe, dass dieselbe Config mit ocrmypdf 17.x nicht ausloest (sonst startet keine Debian-12-Bestandsinstanz mehr). Dazuocrmypdf_checks_gs_always(), der Abbruch inrun_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 nachworking/. Wurde der Dienst dort hart abgeschossen (SIGKILL nachTimeoutStopSec), blieb die Datei liegen und wurde nie wieder angefasst — stiller Datenverlust._scan_working()greift sie jetzt beim Start auf (vorincoming/), das OCR laeuft fuer sie neu. Liegt inincoming/eine gleichnamige, andere Datei, bekommt die wiederaufgenommene einen Zeitstempel angehaengt, damit sich beide nicht ueberschreiben. Liegt inworking/bereits eine andere Datei desselben Namens, brichtprocess_pdf()fuer die neue ab und laesst sie inincoming/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 inworking/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].timeoutund[ocr].pdfa_level. Eintimeout >= 900stammt 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 gesetztespdfa_levelweist auf den Ghostscript-Bug hin. Die Texte stehen nur inconfig.py(legacy_warnings()), weil sie sowohl beim Dienststart ins Log gehen als auch von--check-configausgegeben 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 inConfig.unknown_keys; die Meldung nennt den vollen Pfad. Warnung, kein Fehler — der Dienst startet, der Eintrag tut nur nichts. TimeoutStopSec=300in der Template-Unit: ein laufendes OCR darf beim Stoppen zu Ende laufen. Einsystemctl stopkann 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 einpip install --upgradebeim 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 vonupdate.sh, was es nicht anfasst, Backup-Inhalt/-Rechte/-Rotation, Rollback und dessen Grenzen,--check-configmit den Exit-Codes und Config-Drift.docs/OS-UPGRADE.md— Debian-Major-Upgrade als eigener Ablauf.AI_AGENT_BRIEFING.mdbleibt der Agent-Kontext (Aufbau und Begruendungen) und verweist fuer Ablaeufe auf die drei Dokumente, statt sie zu wiederholen. Nichts wird doppelt gepflegt.
update.shkomplett ueberarbeitet (--help,--rebuild-venv,set -Eeuo pipefail):- venv-Health-Check und Neubau. Geprueft werden Existenz, Lauffaehigkeit
des Interpreters,
major.minorgegen das System-Python undpyvenv.cfg. Passt etwas nicht — typisch nach einem Debian-Major-Upgrade, systemd meldet dann203/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.shextrahiert (einzige Quelle, MarkenBEGIN/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 nuris-active, sondern auchis-failedund den Restart-Zaehler — ein Crash-Loop galt beiType=simplebisher 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.txtder alten venv — ohne venv und ohne Datenverzeichnisse. Weil die Configs Klartext-Passwoerter enthalten, wird das Archiv mitumask 077erzeugt und auf0600 root:rootgesetzt, das Verzeichnis auf700. 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
activatingundfailedab (ueberlist-units --all,list-unit-filesund 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-configje 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.
- venv-Health-Check und Neubau. Geprueft werden Existenz, Lauffaehigkeit
des Interpreters,
install.shrepariert eine kaputte venv. Bisher reichte das blosse Vorhandensein vonvenv/, 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 nachvenv.old-<timestamp>gesichert und neu gebaut.- Die apt-Paketliste steht als einzige Quelle in
install.shin der Funktionpdf_ocr_apt_packages()zwischen den Marken# --- BEGIN apt-packages/# --- END apt-packages.update.shschneidet 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 ohneTimeoutStopSec. - Tippfehler in Config-Keys fielen nicht auf.
load_config()filterte stumm gegen die Dataclass-Annotationen;[ocr].langaugeslief 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/odererror/der Instanz zeigt — im Eingang wuerde das Original sonst endlos neu aufgegriffen. install.shfragt beim Anlegen einer Instanz die OCR-Sprachen ab (Tesseract-Sprachen [deu+eng]:). Die Wahl gilt bewusst pro Instanz — ein Hotfolderbuchhaltungkann mitdeulaufen, ein Hotfolderexportmitdeu+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-langsgeprueft. Fehlt eine Sprachdatei, bietet der Installer das passende apt-Paket an (tesseract-ocr-<code>, 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. Isttesseractnicht aufrufbar, wird die Pruefung uebersprungen und die Eingabe unveraendert uebernommen. - Abfrage
Original nach erfolgreichem OCR archivieren? [j/N]:— Default nein, also weiterhinoriginal_on_success = "delete". Bei ja wird der Archiv-Pfad abgefragt (Vorschlag<basis>/archive), angelegt und auf den Service-User gechownt; ein Archiv ausserhalb des Instanz-Basis-Pfads bekommt ein eigeneschown -R.
Changed
- Die Instanz-Config wird weiterhin per
sedausconfig.example.tomlerzeugt, substituiert jetzt aber zusaetzlich[ocr].languages,[output].original_on_successund[output].archive_dir— bisher waren das die Beispiel-Defaults,archive_dirmusste 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 durchsed_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 perunlink()entfernt — unabhängig von[output].original_on_success. Werarchivekonfiguriert hatte, verlor die Datei also ausgerechnet im Fehlerfall. Der FAIL-Pfad nutzt jetzt dieselbe_dispose_original()-Logik wie der Erfolgsfall:archivelegt das Original samt Timestamp-Kollisionsschutz imarchive_dirab,deleteverhä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()nutztlogging.basicConfig()ohne FileHandler, der Dienst loggt nach stdout → journald. journald ist damit die einzige Log-Quelle (journalctl -u pdf-ocr-hotfolder@<instanz> -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].timeoutist jetzt wirksam: der Wert wird alstesseract_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 nachworking/, wo die Datei dann liegen blieb.- Neue Exception
ConfigErrorinpdf_ocr_hotfolder.config— fehlende[paths]-Sektion oder ein fehlender Pfad-Eintrag liefern eine deutsche Fehlermeldung mit Datei- und Key-Nennung statt eines nacktenKeyError-Tracebacks. Die CLI bricht damit sauber mit Exit-Code 2 ab. pytest.inimittestpaths = tests, damitpytestaus dem Repo-Root läuft.- 35 neue Tests: Fehlerzählung (Exception, Upload, Stabilitäts-Timeout),
Config-Fehlermeldungen,
tesseract_timeout-Durchreichung (ocrmypdf gemockt) undupload_folder().
Changed
[ocr].timeouthat 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 von1800auf300. Wer den alten Wert1800in seinerconfig.tomlstehen hat, gibt Tesseract damit 30 Minuten je Seite — bitte auf einen Seiten-Wert anpassen (Richtwert 300).0bedeutet "kein eigenes Limit": der Wert wird dann gar nicht erst durchgereicht, weil ocrmypdftesseract_timeout=0als "OCR komplett überspringen" interpretiert._dispatch_uploads()liefert jetzt die Namen der fehlgeschlagenen Upload-Ziele zurück; die doppelteenabled-Prüfung (Service + Uploader) ist entfallen — die Uploader prüfen das selbst.upload_folder()kopiert mitshutil.copyfile()stattread_bytes()/write_bytes()— große PDFs landen nicht mehr komplett im Speicher. Die Selbst-Ziel-Erkennung bleibt unverändert.
Fixed
OcrConfig.pdfa_levelhatte im Code noch den Default"2", obwohlconfig.example.tomlseit 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()nachoutgoing/) wurde nur im Worker-Callback geloggt.error_countblieb 0 und--oncelieferte trotz Fehlschlag Exit-Code 0. Jede Exception ausprocess_pdf()zählt jetzt als Fehler, wird geloggt, löst eine Fehler-Mail aus und die Datei wandert — soweit noch auffindbar (incoming/oderworking/) — nacherror/. - 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;--oncemeldete Exit-Code 0. Jetztlog.error+error_count. Die Datei bleibt bewusst inincoming/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 liegtsystemd/lxc-compat.confals Vorlage im Repo. - Issue #5:
WorkingDirectory=/opt/pdf-ocr-hotfolderin 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()undprocess_pdf()mit allen Kombinationen aus Modus + Original-Behandlung
Changed
process_pdf()nimmt jetztoutput_cfg: OutputConfigals 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 --versionwennpdfa_levelgesetzt 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()inpdf_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
tesseractundgs(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-Code1, sobald mindestens ein PDF fehlgeschlagen ist. Exit-Code0nur bei vollständigem Erfolg (inkl. "keine Dateien vorhanden"). Exit-Code2bei Preflight-Fehler.
Added
- Public API:
HotfolderService.run_once(),.success_count,.error_count,.ensure_dirs() check_preflight()/PreflightErrorinpdf_ocr_hotfolder.service- pytest-Test-Suite (
tests/) mit 11 Tests — deckt alle Szenarien aus Issue #1 und #2 ab ocrmypdf-Import inprocessor.pyist 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@<name>.service - Pro Instanz: eigene Config (
/etc/pdf-ocr-hotfolder/<name>.toml), eigene Datenverzeichnisse (/var/lib/pdf-ocr-hotfolder/<name>/…), 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.shstoppt/startet automatisch alle laufenden Instanzen
Changed
- Single-Unit
pdf-ocr-hotfolder.servicedurch Template-Unitpdf-ocr-hotfolder@.serviceersetzt - 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 (
tomllibaus stdlib, Python ≥3.11) - systemd-Unit mit Hardening-Optionen
install.shmit interaktivem Service-User-Prompt (lokal anlegen oder bestehenden lokalen/AD-User übernehmen)update.shmit Backup, Code-Sync und Service-Reload- README.md, AI_AGENT_BRIEFING.md