Vor dem Rollout durchgesehen und die verbliebenen Stellen geschlossen, an denen etwas schiefgehen konnte, ohne dass es irgendwo sichtbar wurde. Datenverlust: - veraPDF: das in [verapdf].binary konfigurierte Programm wird im Preflight geprueft. Bisher galt bei falschem Pfad JEDE Datei als "nicht konform" — Ergebnis nach error/, Original geloescht (Default delete). run_verapdf() trennt jetzt ausserdem ein echtes FAIL-Urteil von einer Stoerung (VeraPdfUnavailable: nicht startbar, abgestuerzt, kein PASS/FAIL in der Ausgabe). Bei Stoerung wandern Original UND Ergebnis nach error/, das Original wird nicht entsorgt. - Gleichnamige Dateien wurden in outgoing/, error/ und beim Ordner-Upload mit abweichendem target kommentarlos ueberschrieben. Jetzt Zeitstempel daneben, mit Warnung; ProcessResult.output traegt den echten Pfad. Robustheit: - Kaputtes oder nicht lesbares TOML beim Start: Exit 2 statt Traceback. - RestartPreventExitStatus=2 in der Unit — Exit 2 (Config/Preflight) laeuft nicht mehr endlos neu, die Instanz bleibt sichtbar failed stehen. - Toter watchdog-Observer wird erkannt: Exit 3, systemd setzt den Watch neu auf. Vorher blieb die Unit "active" und verarbeitete nichts mehr. - Relative Pfade in [paths]/archive_dir/target sind ein Config-Fehler statt still unter /opt zu landen. - Fehler beim Archivieren entwertet den Durchlauf nicht mehr: Upload und Mail laufen, Sichtbarkeit ueber log.error + "OK mit Warnung"-Mail. - Nicht-PDFs in incoming/ werden beim Start-Scan gesammelt gemeldet. - Logging explizit nach stdout (die Doku versprach das schon). Struktur: - Neue lib/common.sh, von install.sh und update.sh gesourct. Die doppelte venv_is_healthy() gibt es nur noch einmal, in der gruendlichen Fassung — die schlanke in install.sh haette eine nach einem Distro-Sprung kaputte venv als gesund durchgewunken (nachgewiesen). - install.sh warnt in Containern, wenn systemd-journald nicht laeuft. Doku: Dateisystem-Festlegung (ext4/xfs/zfs, kein CIFS/NFS wegen inotify), Debian 13 in LXC auf Proxmox scheitert an journald (243/CREDENTIALS, AppArmor blockiert sd-mkdcreds) inkl. Abhilfe, echte Speicher-Messwerte, Exit-Code-Tabelle. 254 Tests gruen (vorher 152). Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
PDF OCR Hotfolder
Verwandelt eingehende gescannte PDFs automatisch in durchsuchbare PDFs (PDF/A optional) per OCR. Hauptanwendung: Kunden-Scanner schiebt PDF in einen Ordner — Sekunden später liegt die OCR-Version im Ausgang oder wird in Nextcloud / per SFTP weitergeleitet.
Dokumentation
| Dokument | Inhalt |
|---|---|
| docs/INSTALLATION.md | Erstinstallation, Instanzen anlegen, LXC, Ghostscript, Konfigurationsreferenz, Troubleshooting |
| docs/UPDATE.md | Update mit update.sh: Ablauf, Backup, Rollback, --check-config, Config-Drift |
| docs/OS-UPGRADE.md | Debian-Major-Upgrade (12 → 13): venv neu bauen, Pins anheben |
Weiter: CHANGELOG.md · AI_AGENT_BRIEFING.md · config.example.toml
Features
- 🔍 OCR via ocrmypdf + Tesseract (Library-Call, kein Subprozess-Overhead)
- 📂 Hotfolder via watchdog — reagiert auf
created,moved,closedEvents - 🧠 Stabilitäts-Erkennung: wartet bis Scanner fertig geschrieben hat
- 🔁 Parallelverarbeitung mehrerer PDFs (ThreadPool, konfigurierbar)
- ♻️ Wiederaufnahme aus
working/nach einem harten Stopp — keine Datei bleibt liegen - ✅ PDF/A-Output (1, 2 oder 3) optional
- 🛡️ veraPDF-Validierung optional — Binary wird im Preflight geprüft, eine Störung gilt nicht als FAIL
- 🚫 Überschreibt nie eine gleichnamige Datei — in
outgoing/,error/, Archiv und Ordner-Upload weicht sie mit Zeitstempel aus - ☁️ Upload-Ziele: lokaler Ordner, Nextcloud (WebDAV via Python), SFTP
- 📧 E-Mail-Notify (immer / nur Fehler / nie)
- 🔐 Service-User-Support für lokale und AD-User mit lokaler UID (SSSD/Winbind)
- ⚙️ Saubere systemd-Integration mit auto-Restart, Multi-Instanz über eine Template-Unit
- 👁️ Toter Verzeichnis-Watch wird erkannt — der Dienst beendet sich (Exit 3), systemd setzt den Watch neu auf
- 🩺
--check-configprüft eine Instanz-Config ohne etwas zu verarbeiten
Schnellstart
Voraussetzungen: Debian 12 oder 13, Python 3.11+, root — und mindestens
2 GB RAM (512 MB reichen für 300-dpi-Scans nachweislich nicht, siehe
Systemanforderungen).
Dateisystem ext4, xfs oder zfs; incoming/ muss lokal liegen — auf
einem CIFS/NFS-Mount liefert inotify keine Events und der Hotfolder bemerkt
neue Dateien nur noch beim Start
(warum).
git clone gitea@gitea.sonith.de:sonith_ug/pdf-ocr-hotfolder.git
cd pdf-ocr-hotfolder
sudo ./install.sh
Der Installer legt einmalig Code, venv und die systemd-Template-Unit an und fragt danach pro Instanz Name, Basis-Pfad, Service-User, OCR-Sprachen und die Original-Behandlung ab. Bei jedem erneuten Aufruf erkennt er bestehende Instanzen und fragt nur nach neuen.
Test:
cp irgendein-scan.pdf /var/lib/pdf-ocr-hotfolder/<instanz>/incoming/
journalctl -u pdf-ocr-hotfolder@<instanz> -f
Nach wenigen Sekunden liegt das OCR-PDF im outgoing/-Ordner der Instanz.
Alle Details zu den Abfragen, zum Multi-Instanz-Betrieb und zu den Fallstricken (LXC, Ghostscript): docs/INSTALLATION.md.
Update: git pull && sudo ./update.sh — siehe docs/UPDATE.md.
Nach einem Debian-Major-Upgrade: docs/OS-UPGRADE.md.
Verzeichnisse
| Pfad | Zweck |
|---|---|
/opt/pdf-ocr-hotfolder/ |
Code + venv (für alle Instanzen gemeinsam) |
/opt/pdf-ocr-hotfolder/lib/common.sh |
gemeinsame Shell-Funktionen für install.sh/update.sh (mitkopiert) |
/etc/pdf-ocr-hotfolder/<instanz>.toml |
Config pro Instanz (640, root:<service-gruppe>) |
/etc/systemd/system/pdf-ocr-hotfolder@.service |
systemd Template-Unit |
/var/lib/pdf-ocr-hotfolder/<instanz>/incoming |
Eingang (Scanner schreibt hier rein) |
/var/lib/pdf-ocr-hotfolder/<instanz>/working |
Arbeitsverzeichnis während OCR |
/var/lib/pdf-ocr-hotfolder/<instanz>/outgoing |
Ausgang (fertige PDFs) |
/var/lib/pdf-ocr-hotfolder/<instanz>/error |
Fehlgeschlagene PDFs |
/var/backups/pdf-ocr-hotfolder/ |
Update-Backups (0600, letzte 5) |
Ein eigenes Logverzeichnis gibt es nicht — der Dienst loggt nach stdout und damit ins journal.
Konfiguration im Überblick
Jede Instanz hat ihre eigene TOML unter /etc/pdf-ocr-hotfolder/<instanz>.toml.
Vollständiges, kommentiertes Beispiel: config.example.toml.
Key-für-Key-Referenz: docs/INSTALLATION.md.
| Sektion | Zweck |
|---|---|
[paths] |
incoming, outgoing, working, error — Pflicht, alle absolut |
[ocr] |
Sprachen, jobs, skip_text, pdfa_level, deskew, max_workers, timeout (Sekunden pro Seite) |
[output] |
Dateibenennung (name_mode/name_tag) und Original-Behandlung (delete/archive) |
[verapdf] |
optionale PDF/A-Validierung per CLI |
[upload.folder] / [upload.nextcloud] / [upload.sftp] |
Upload-Ziele, beliebig viele gleichzeitig |
[notify.email] |
SMTP-Benachrichtigung: always | errors | never |
[logging] |
level = DEBUG/INFO/WARNING/ERROR |
Die Instanz-Configs enthalten Klartext-Passwörter (SMTP, Nextcloud, SFTP) —
deshalb 640 root:<service-gruppe> und beim Debuggen nicht in Tickets kopieren.
Config prüfen, ohne etwas zu verarbeiten:
cd /opt/pdf-ocr-hotfolder && sudo ./venv/bin/python -m pdf_ocr_hotfolder \
--check-config --config /etc/pdf-ocr-hotfolder/<instanz>.toml
Exit 0 = sauber, 1 = Warnungen, 2 = Fehler. Details: docs/UPDATE.md.
Exit-Codes des Dienstes
| Exit | Bedeutung | Neustart durch systemd |
|---|---|---|
0 |
regulärer Stopp | — |
1 |
nur bei --once: mindestens eine PDF fehlgeschlagen |
— |
2 |
Config- oder Preflight-Fehler | nein (RestartPreventExitStatus=2) — die Instanz bleibt sichtbar failed |
3 |
Verzeichnis-Watch gestorben, es würden keine Dateien mehr erkannt | ja, genau dafür |
Vollständig: docs/INSTALLATION.md.
Service-Verwaltung
# Eine bestimmte Instanz
sudo systemctl status pdf-ocr-hotfolder@kunde-a
sudo systemctl restart pdf-ocr-hotfolder@kunde-a
journalctl -u pdf-ocr-hotfolder@kunde-a -f
# Alle Instanzen
sudo systemctl status 'pdf-ocr-hotfolder@*'
sudo systemctl restart 'pdf-ocr-hotfolder@*'
journalctl -u 'pdf-ocr-hotfolder@*' --since today
Ein laufendes OCR darf beim Stoppen zu Ende laufen (TimeoutStopSec=300) — ein
stop kann deshalb pro Instanz bis zu 5 Minuten dauern.
Architektur
┌──────────┐ watchdog ┌──────────────┐ ocrmypdf ┌──────────┐
│ Scanner │ ──────────────▶ │ incoming/ │ ─────────────▶ │ working/ │
└──────────┘ PDF-Datei └──────────────┘ (Library) └────┬─────┘
│
optional veraPDF
│
▼
┌──────────────┐
│ outgoing/ │
└──────┬───────┘
│
┌──────────────────────┼──────────────────────┐
▼ ▼ ▼
┌────────────┐ ┌────────────┐ ┌────────────┐
│ Nextcloud │ │ SFTP │ │ E-Mail │
│ (WebDAV) │ │ (paramiko) │ │ Notify │
└────────────┘ └────────────┘ └────────────┘
Beim Start wird working/ zuerst durchsucht: was ein harter Stopp dort liegen
ließ, wird wiederaufgenommen; unvollständige OCR-Fragmente (__ocr_*) werden
gelöscht.
Tests
pytest # 254 Tests
ocrmypdf muss dafür nicht installiert sein — der Import ist lazy und wird in
den Tests gemockt.
Lizenz
MIT — © Sonith UG
Version: 0.7.0 Repo: https://gitea.sonith.de/sonith_ug/pdf-ocr-hotfolder