Nachlese aus der Verifikation von v0.7.1 auf einem frischen Debian 12. - update.sh meldete "LXC-Drop-in nachgezogen ✓" auch bei unveraenderter Datei — dieselbe Klasse Unwahrheit wie das Ghostscript-Haekchen, das in v0.7.1 behoben wurde. Jetzt wird verglichen und ehrlich gemeldet. - "Quelle wieder entfernt" ist Teil der schlechten Nachricht und kommt als WARN statt INFO. 254 Tests gruen. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
44 KiB
Changelog
[0.7.2] - 2026-09-23
Nachlese aus der Verifikation von 0.7.1 auf einem frischen Debian-12-Container.
Fixed
update.shmeldete "LXC-Drop-in nachgezogen ✓" auch dann, wenn die Datei bereits identisch war — dieselbe Sorte Unwahrheit wie das Ghostscript-Haekchen in 0.7.0. Es wird jetzt verglichen und nur gemeldet, was tatsaechlich passiert ist ("bereits aktuell" vs. "nachgezogen").- Die Zeile "Quelle wieder entfernt" gehoert zur schlechten Nachricht und kommt jetzt als WARN statt als INFO.
[0.7.1] - 2026-09-23
Gefunden beim ersten echten Erstinstallations-Test auf frischen Debian-12- und Debian-13-Containern. Der Installationsweg selbst hat getragen; diese drei Stellen haben gelogen oder gefehlt.
Fixed
- Das Ghostscript-Angebot auf Debian 12 war ein No-Op, der sich als Erfolg
meldete (
Ghostscript aktualisiert: 10.00.0 -> 10.00.0 ✓). Grund:bookworm-backportsenthaelt ueberhaupt keinghostscript— am echten Paketindex verifiziert (2606 Pakete, ghostscript nicht dabei, Kontroll- pakete wie systemd/golang-go sehr wohl). Zurueck blieb eine nutzlosesources.list.d-Quelle. Die Routine (jetztcheck_ghostscriptinlib/common.sh) sucht nun erst den tatsaechlichen Kandidaten, vergleicht die Version vorher/nachher, meldet "unveraendert" als Warnung statt als Erfolg und entfernt eine nur zur Probe angelegte Quelle wieder. Eine bereits vorhandene Backports-Quelle bleibt unangetastet. - Der Rat "Ghostscript aus bookworm-backports" ist ueberall raus — er stand auch in der Preflight-Fehlermeldung, der pdfa_level-Warnung, config.example.toml und vier Doku-Dateien. Ersetzt durch die echten Optionen: pdfa_level leer lassen (Default), skip_text = false, oder Debian 13 (gs 10.05.1). Ein Test prueft negativ, dass der Rat nicht zurueckkommt.
- Mehrzeilige Log-Hinweise waren zerrissen und nicht kopierbar: die
log-Funktionen nutzten
echo -e, wodurch\nin Hinweistexten zu echten Zeilenumbruechen ohne[WARN]-Praefix wurden. Jetztprintfmit%sfuer den Text — das Problem kann strukturell nicht wiederkommen. pip-freeze.txtlandete beim Rollback im Wurzelverzeichnis. Sie liegt im Backup jetzt unteropt/pdf-ocr-hotfolder/und damit nach dem Entpacken neben der Installation.
Added (Doku)
- Voraussetzungen, die auf einem frischen Proxmox-Debian-Template fehlen:
gitundsudosind dort nicht installiert — der dokumentierte Aufrufsudo ./install.shscheitert mitsudo: command not found. Beide Wege (root direkt / sudo) sind jetzt beschrieben. - HTTPS- statt SSH-Clone als Normalfall (funktioniert ohne Credentials);
SSH-Variante mit dem Hinweis, dass der User
giteaheisst, nichtgit. - Entscheidungstabelle zu PDF/A auf Debian 12 vs. 13. Praezisierung: die
Sackgasse ist PDF/A zusammen mit
skip_text = true; mitskip_text = falsegeht PDF/A auch auf Debian 12, nur langsamer. - Rollback:
systemctl startkann kein Glob (anders alsstop), bei mehreren Instanzen jede einzeln starten. Dazu, was ein Rollback nachweislich zurueckholt und was nicht. - journald-Reparatur: die Instanzen danach einmal neu starten, sonst bleibt das Journal leer und die Reparatur sieht gescheitert aus.
- "So sieht ein Erstlauf aus" — die Reihenfolge der Abfragen.
[0.7.0] - 2026-09-23
Schliesst die stillen Datenverlust-Pfade: gleichnamige Dateien werden nirgends mehr ueberschrieben, und ein nicht aufrufbares veraPDF wird nicht mehr als "PDF ist ungueltig" missverstanden. Dazu Robustheit (Exit 2 statt Traceback, toter Verzeichnis-Watch faellt auf, relative Pfade sind ein Config-Fehler) und eine gemeinsame Shell-Bibliothek fuer install.sh und update.sh. Test-Suite: 254 pytest-Tests gruen.
Added
lib/common.sh— gemeinsame Shell-Bibliothek.install.shundupdate.shsourcen sie und teilen sich darueber Log-Funktionen (log_info/log_warn/log_error/log_step),require_root, die Layout-Konstanten (INSTALL_DIR,CONFIG_DIR,DATA_ROOT,SYSTEMD_DIR,DEFAULT_USER,SERVICE_TEMPLATE,LXC_DROPIN*), die apt-Paketliste (pdf_ocr_apt_packages()zwischen den BEGIN/END-Marken) und die venv-Pruefung (py_mm,pyvenv_cfg_mm,venv_is_healthy,report_venv_issues).- Die Paketliste steht damit nicht mehr in
install.sh, sondern inlib/common.sh;update.shschneidet sie weiterhin persedaus der Repo-Fassung heraus, damit beim Update die neue Liste gilt und nicht die vielleicht aeltere, bereits gesourcte aus der Installation. venv_is_healthy()gab es bisher zweimal — die schlanke Variante ininstall.shhaette den Distro-Upgrade-Fall (pyvenv.cfggegen System-Python) nicht erkannt. Jetzt existiert nur noch die gruendliche Fassung, undinstall.shnennt die Befunde ueberreport_venv_issues.lib/wird voninstall.shundupdate.shnach/opt/pdf-ocr-hotfolder/lib/mitkopiert und liegt damit im Update-Backup.update.shsourct bevorzugt die Repo-Fassung neben sich und faellt auf die installierte zurueck; fehlt sie ueberall, bricht es sofort ab statt mitten im Lauf mit "command not found".
- Die Paketliste steht damit nicht mehr in
- veraPDF wird im Preflight geprueft (
check_verapdf_binary(),resolve_verapdf_binary()). Mit[verapdf].enabled = truemuss[verapdf].binaryauf ein vorhandenes, ausfuehrbares Programm zeigen — sonst startet der Dienst gar nicht erst (Exit 2), und--check-configmeldet den Fehler. Ein Pfad mit/wird direkt geprueft, ein nackter Name imPATHgesucht.--check-configzeigt jetzt ausserdem Binary und Flavour bzw.(aus)an. Hintergrund: ein Tippfehler im Pfad war der gefaehrlichste Fehler des ganzen Dienstes.run_verapdf()fand das Programm fuer JEDE Datei nicht, wertete das als FAIL, schob das OCR-Ergebnis nacherror/— und_dispose_original()entsorgte das Original laut[output].original_on_success, bei dessen Defaultdeletealso Scan fuer Scan die Vorlage. Die Unit stand dabei alsactive (running)da. - Exit 3: toter Verzeichnis-Watch (
EXIT_OBSERVER_DEADinservice.py). Stirbt der watchdog-Observer im Betrieb (erschoepftesfs.inotify.max_user_watches, ersetztes oder neu gemountetes Verzeichnis), blieb die Unit bisheractive (running)und verarbeitete nichts mehr — kein Log, keine Mail, niemand merkt es. Die Hauptschleife prueft den Observer jetzt sekuendlich mit, loggt im Ernstfall die moeglichen Ursachen und beendet sich mit Exit 3;Restart=on-failurestartet den Dienst neu und der Watch wird neu aufgesetzt. Bewusst nicht Exit 2 — den unterdrueckt die Unit jetzt beim Neustart (s.u.). ProcessResult.warning— erfolgreicher Durchlauf mit Nebenbefund. Bisher gab es nur Erfolg oder Fehler; ein liegengebliebenes Original passte in keine der beiden Schubladen.- Sammelmeldung fuer Nicht-PDF-Dateien in
incoming/(_report_non_pdf()). Alles ohne.pdf-Endung wurde ignoriert und sammelte sich stumm an (Scanner-Fehlablagen, abgebrochene Uploads, Thumbnails). Beim Start-Scan gibt es jetzt eine Warnung mit Anzahl und bis zu drei Beispielnamen — keine Zeile pro Datei und nichts im laufenden Betrieb. install.shwarnt beim Erstinstall in Containern, wennsystemd-journaldnicht laeuft. Der Dienst loggt ausschliesslich nach journald; ist journald kaputt, gibt es gar keine Logs. Die Warnung nennt den Drop-in-Befehl fuer den Debian-13-Fall und fragt, ob fortgefahren werden soll.- Dokumentation (siehe unten unter Docs): Dateisystem-Festlegung, Debian-13-journald-Befund, konkrete Speicher-Messwerte, Exit-Code-Tabelle.
Fixed
outgoing/: gleichnamige Datei wird nicht mehr ueberschrieben. Liefert der Scanner denselben Dateinamen ein zweites Mal (oder wurde das Vorgaengerergebnis noch nicht abgeholt), legte dershutil.movedie aeltere Datei kommentarlos um. Neu:_collision_free_path()haengt einen Zeitstempel an (scan.pdf->scan_20260923-081500.pdf), bei Kollision innerhalb derselben Sekunde zusaetzlich einen Zaehler. Es gibt einelog.warning, undProcessResult.outputtraegt den tatsaechlich geschriebenen Pfad — die Upload-Ziele und die Mail nennen damit die richtige Datei.- Dieselbe Klasse in
error/und beim Ordner-Upload._move_to_error()(und damit auch_rescue_to_error()) sowieupload_folder()mit abweichendem[upload.folder].targetersetzten bisher still eine gleichnamige Datei im Ziel. Beide nutzen jetzt denselben Zeitstempel-Ausweg. Scheitert dieselbescan.pdfzweimal, liegen jetzt beide Fassungen inerror/. - veraPDF: Stoerung wird nicht mehr als FAIL gewertet.
run_verapdf()unterscheidet jetzt zwischen einem echten Urteil (PASS/FAIL) und "Programm nicht aufrufbar, nicht startbar, Timeout oder kein PASS/FAIL in der Ausgabe" — Letzteres wirftVeraPdfUnavailable. Im Stoerungsfall wandern Original UND OCR-Ergebnis nacherror/, und das Original wird weder geloescht noch archiviert, unabhaengig von[output].original_on_success. Vorher lieferte jeder dieser Faelle schlichtFalseund damit ein Fehlurteil ueber die Datei. - Kaputtes TOML und nicht lesbare Config beenden den Dienststart mit
Exit 2 statt mit einem nackten Traceback.
main()faengt jetzttomllib.TOMLDecodeErrorundOSErrorgenauso ab wie--check-config; die Meldung nennt Zeile und Spalte, sofern der Interpreter sie liefert (TOMLDecodeError.lineno/.colnogibt es erst ab Python 3.14 — auf Debian 12 steht die Position nur im Meldungstext, deshalbgetattr). - Relative Pfade in der Config sind jetzt ein Fehler.
[paths].incoming,outgoing,working,errorsowie[output].archive_dirund[upload.folder].targetmuessen absolut sein (_require_absolute(),ConfigError+ Exit 2). Ein relativer Pfad wurde gegen dasWorkingDirectoryder Unit aufgeloest und landete still unter/opt/pdf-ocr-hotfolder/— der Scanner schrieb dann woanders hin als der Dienst schaute, ohne dass irgendwo ein Fehler auftauchte. Leere Werte bleiben erlaubt (archive_dir/targetsind optional). - Ein Fehler beim Entsorgen des Originals entwertet den Durchlauf nicht
mehr.
_dispose_original()wirft nicht mehr, sondern liefert eine Meldung zurueck: zum Aufrufzeitpunkt liegt das fertige PDF schon inoutgoing/, eine Exception von dort haette den gelungenen Durchlauf im Catch-all des Service in einen Fehler verwandelt — mitsamt ausgefallenem Upload. Jetzt gilt der Lauf als Erfolg, Upload und Benachrichtigung laufen, es gibt aber einelog.error(Original liegt noch inworking/und wird beim naechsten Start erneut durch das OCR geschickt), und die Mail geht als "OK mit Warnung" auch bei[notify.email].on = "errors"raus — sonst waere genau das wieder ein stiller Fehlerpfad. - Log geht explizit nach stdout.
logging.basicConfig()schreibt per Default nach stderr; README unddocs/INSTALLATION.mdversprachen aber stdout. Fuer journald egal, fuer den dort beschriebenen Vordergrund-Notbehelf und fuer jede Weiterleitung nicht.
Changed
systemd/pdf-ocr-hotfolder@.service:RestartPreventExitStatus=2. Exit 2 = Config- oder Preflight-Fehler, den behebt kein Neustart. Bisher starteteRestart=on-failuredie Instanz endlos im 5-Sekunden-Takt neu (das Start-Rate-Limit greift beiRestartSec=5nie). Jetzt bleibt die Instanz sichtbarfailedstehen.HotfolderService.run()gibt einen Exit-Code zurueck (0 oderEXIT_OBSERVER_DEAD) stattNone;main()reicht ihn durch. Preflight undcheck_output_config()stehen jetzt gebuendelt in_preflight(), dasrun()undrun_once()gemeinsam nutzen.check_preflight()nimmt zwei weitere Parameter (verapdf_enabled,verapdf_binary) — beide mit Default, bestehende Aufrufe bleiben gueltig.config.example.toml: Warnhinweise zu absoluten Pfaden ([paths],[output].archive_dir,[upload.folder].target), zur veraPDF-Preflight- Pruefung samt Begruendung und zum Kollisionsverhalten im Upload-Ziel.install.shist von ~73 Zeilen Kopf auf das Sourcen vonlib/common.shgeschrumpft und nutzt durchgehendSYSTEMD_DIR/LXC_DROPINstatt hartkodierter Pfade.
Docs
docs/INSTALLATION.md, Systemanforderungen: Dateisystem festgelegt. Der Dienst laeuft ausschliesslich auf ext4, xfs oder zfs.incoming/gehoert auf ein lokales, inotify-faehiges Dateisystem — kein CIFS/NFS-Mount: dort liefert inotify grundsaetzlich keine Events, weil Schreibzugriffe anderer Rechner am lokalen Kernel vorbeigehen. Der Dienst wuerde dann nur noch beim Start etwas verarbeiten und im laufenden Betrieb nichts mehr bemerken.- Speicher-Messwerte konkretisiert. Zur bestehenden 512-MB-Messung kommen
Zahlen von einem 2-GB-Container (Debian 13, 300-dpi-A4-Seite mit
deskew,oversample = 300,jobs = 4): Laufzeit 19 s,MemoryPeakdes Dienstes 380 MB,memory.peakdes ganzen Containers 503 MB,oom_kill 0. Auf derselben Maschine mit 512 MB war genau das der OOM-Kill. Die Empfehlung "mindestens 2 GB" ist damit belegt statt geschaetzt. - Debian 13 in LXC auf Proxmox: journald scheitert — verifizierter Befund,
betrifft jede Debian-13-LXC auf Proxmox 8.4. In 0.6.3 stand noch, das sei
ein Schaden auf genau einer Maschine; das war falsch. Ursache: systemd >= 255
(Debian 13 hat 257) setzt
ImportCredential=journal.*in der journald-Unit, der Hilfsprozess(sd-mkdcreds)mountet dafuer, und das AppArmor-Profil des Proxmox-Hosts blockiert das (apparmor="DENIED" operation="mount" profile="lxc-<id>_</var/lib/lxc>" name="/dev/" comm="(sd-mkdcreds)"im Host-Log). Debian 12 (systemd 252) kenntImportCredentialnicht und ist nicht betroffen. Dokumentiert sind die reboot-feste Abhilfe im Container (Drop-insystemd-journald.service.d/no-credentials.confmit leeremImportCredential=), der Hinweis auf die weiteren betroffenen Units (logind, networkd, console-getty, tmpfiles-setup) und der saubere Weg host-seitig. - Exit-Code-Tabelle 0/1/2/3 in
docs/INSTALLATION.md, verlinkt aus README,docs/UPDATE.mdund dem Troubleshooting — mit dem Hinweis, dass die Unit bei 2 nicht neu startet und bei 3 gerade doch. - Die fuenf Ueberschriften der Instanz-Abfragen in
docs/INSTALLATION.mdsind entnummeriert (### 4. OCR-Sprachen->### OCR-Sprachen). Die Anker hiessen vorher#4-ocr-sprachenund waeren bei jeder Umsortierung gebrochen; die Reihenfolge steht weiterhin im Text. AI_AGENT_BRIEFING.mdauf den heutigen Stand gezogen: Dateibaum mitlib/und allen 20 Test-Dateien, nur noch einvenv_is_healthy(), Paketliste inlib/common.sh, veraPDF-Preflight, Kollisionsschutz, Exit-Codes, Observer-Bewachung, Testzahl 254.- Testzahl ueberall von 152 auf 254 korrigiert (README,
AI_AGENT_BRIEFING.md,docs/OS-UPGRADE.md).
[0.6.3] - 2026-09-23
Reine Doku-Version — kein Code, kein Installer, kein Updater, keine Unit, keine Tests angefasst (ausser dem Versionsstring). Beide Erkenntnisse stammen aus den Testlaeufen auf Debian 12 und Debian 13.
Added
- Abschnitt "Systemanforderungen" in
docs/INSTALLATION.md. Die Dimensionierung fehlte bisher komplett. Empfohlen werden mindestens 2 GB RAM; 512 MB reichen fuer 300-dpi-Scans nachweislich nicht. Gemessen auf einem LXC-Container mit 512 MB RAM + 512 MB Swap, Debian 13, Ghostscript 10.05.1: eine einzelne A4-Seite in 300 dpi mitdeskew = trueriss das cgroup-Limit und der Dienst wurde vom OOM-Killer beendet (oom-kill:constraint=CONSTRAINT_MEMCG, oom_memcg=/lxc/201, task=python,total-vm:1168764kB, anon-rss:489676kB). Dieselbe Verarbeitung mit einer kleineren Seite (850x1100 px) lief in ca. 19 s sauber durch.- Dokumentiert ist der Zusammenhang RAM <->
max_workersxjobsx Aufloesung:max_workers(Default 2) laesst zwei solcher Seiten gleichzeitig laufen, der Spitzenbedarf multipliziert sich entsprechend. Wer knapp dimensioniert, zieht zuerstmax_workersherunter. - Dazu, wie sich ein OOM-Kill aeussert (Dienst weg bzw. von systemd neu
gestartet,
NRestartssteigt, Abbruch mitten in der Datei ohne Traceback, PDF bleibt inworking/liegen) und wie man ihn nachweist (/sys/fs/cgroup/memory.eventsim Container,dmesgauf dem LXC-Host). Ohne diese Pruefung sieht der Fall wie ein Anwendungsfehler aus und man sucht in ocrmypdf, Tesseract oder der Config. - Mit dem Hinweis, dass die Wiederaufnahme aus
working/seit v0.6.0 den Datenverlust abfaengt — der OOM selbst bleibt aber ein Problem: bei unveraenderter Dimensionierung laeuft dieselbe Datei nach dem Neustart erneut hinein. README.mdbekommt im Schnellstart nur einen Einzeiler mit Verweis, keine Dublette.
- Dokumentiert ist der Zusammenhang RAM <->
- Troubleshooting-Eintrag "Keine Logs: No journal files were found" in
docs/INSTALLATION.md. Auf einem der Testcontainer warsystemd-journald.servicekaputt (failed,status=243/CREDENTIALS),journalctllieferteNo journal files were found.Da der Dienst seit v0.4.1 ausschliesslich nach journald loggt (kein FileHandler, kein Logverzeichnis — bewusste Entscheidung), gibt es dann gar keine Dienstlogs: ein blinder Fleck, der vor jeder Fehlersuche persystemctl status systemd-journaldauszuschliessen ist. Als Notbehelf ist der Vordergrund-Aufruf dokumentiert (cd /opt/pdf-ocr-hotfolder && sudo -u pdfocr ./venv/bin/python -m pdf_ocr_hotfolder --config ..., dascdist zwingend, s. 0.6.2). Ausdruecklich festgehalten: das ist kein generelles LXC-Muster — der zweite Testcontainer war in Ordnung, es war Schaden auf genau dieser Maschine.
Changed
AI_AGENT_BRIEFING.md: der Kommentar zurequirements.txtin der Projektstruktur nannte noch "ocrmypdf 16.x!" — genau die Version, die seit 0.6.1 als unbrauchbar gilt und gegen die der Pin schuetzt. Korrigiert auf 17.x.
[0.6.2] - 2026-09-22
Fixed
- Der
--check-config-Befehl, denupdate.shin der Zusammenfassung ausgibt, lief so wie gedruckt nicht (No module named pdf_ocr_hotfolder). Das Paket wird nicht pip-installiert, sondern nach/opt/pdf-ocr-hotfolderkopiert und nur ueber das Arbeitsverzeichnis gefunden — dem Hinweis fehlte das vorangestelltecd. Betraf auch die Beispiele in README.md, docs/INSTALLATION.md, docs/UPDATE.md und docs/OS-UPGRADE.md. Gefunden beim Update-Test v0.3.1 -> v0.6.1 auf Debian 12.
[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