Files
pdf-ocr-hotfolder/docs/INSTALLATION.md
T
techadmin 3e24aa2ecd feat: Wiederaufnahme aus working/, --check-config, feldtauglicher Updater (v0.6.0)
Datenverlust behoben:
- Nach hartem Stopp blieb das Original in working/ liegen und wurde nie
  wieder angefasst (_scan_existing sah nur incoming/). Es wird jetzt beim
  Start an Ort und Stelle wieder aufgegriffen, mit Kollisionsschutz gegen
  gleichnamige neue Scans; angefangene __ocr_-Fragmente werden geloescht.
- TimeoutStopSec 30 -> 300, damit laufendes OCR zu Ende laufen darf.

Config-Drift sichtbar gemacht:
- Neues --check-config (Exit 0 sauber / 1 Warnungen / 2 Fehler), das
  update.sh vor dem Neustart ueber alle Instanz-Configs laufen laesst.
- Warnungen fuer [ocr].timeout >= 900 (seit 0.4.0 pro SEITE) und gesetztes
  pdfa_level, beim Dienststart wie im Check.
- Unbekannte Config-Keys werden nicht mehr still verworfen, sondern genannt.

Updater feldtauglich:
- venv-Health-Check erkennt toten Symlink UND Versions-Drift gegen das
  System-Python; --rebuild-venv als ausdruecklicher Weg nach einem Debian-
  Major-Upgrade. Neubau ist ganz-oder-gar-nicht mit Rollback.
- apt-Pakete werden auch beim Update synchronisiert (Quelle: install.sh).
- Instanz-Erfassung inkl. activating/failed, Verifikation prueft is-failed
  und NRestarts statt sleep 1 + is-active.
- Backup enthaelt Configs, Unit, Drop-ins und pip-freeze.txt, liegt auf
  0600 und rotiert auf 5; schlaegt es fehl, bricht das Update vorher ab.
- ERR-Trap faehrt die vorher laufenden Instanzen wieder hoch.
- lxc-compat.conf wird beim Update nachgezogen.
- requirements.txt gepinnt (ocrmypdf 16.13.0, geprueft fuer Python 3.11+3.13).

Doku in Installation / Update / OS-Upgrade aufgeteilt (docs/).
135 Tests gruen.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-09-22 22:04:04 +02:00

17 KiB

Installation

Erstinstallation und Anlage von Hotfolder-Instanzen mit install.sh.

Verwandte Dokumente: README · Update · Debian-Major-Upgrade


Voraussetzungen

Punkt Anforderung
Betriebssystem Debian 12 (bookworm) oder Debian 13 — systemd wird vorausgesetzt
Python 3.11+ (wegen tomllib aus der stdlib); kommt aus der Distribution
Rechte root (sudo ./install.sh)
Netz apt-Zugriff für die System-Pakete, PyPI-Zugriff für die venv
Repo muss dauerhaft liegen bleiben — update.sh kopiert daraus (s. UPDATE.md)

Die System-Pakete installiert der Installer selbst. Die Liste steht als einzige Quelle in install.sh (Funktion pdf_ocr_apt_packages(), zwischen den Marken # --- BEGIN apt-packages / # --- END apt-packages) und wird von update.sh von dort ausgelesen:

python3  python3-venv  python3-pip
tesseract-ocr  tesseract-ocr-deu  tesseract-ocr-eng
ghostscript  qpdf  unpaper  pngquant  icc-profiles-free
ca-certificates  curl

Weitere Tesseract-Sprachpakete installiert der Installer bei Bedarf pro Instanz nach (siehe OCR-Sprachen).

Die Python-Abhängigkeiten stehen fest gepinnt in requirements.txt (ocrmypdf, watchdog, requests, paramiko). Warum das so ist und wie man die Pins anhebt: OS-UPGRADE.md.


Installation

git clone gitea@gitea.sonith.de:sonith_ug/pdf-ocr-hotfolder.git
cd pdf-ocr-hotfolder
sudo ./install.sh

install.sh ist Installer und Instanz-Manager in einem und idempotent — jeder weitere Aufruf überspringt, was schon steht.

Basis-Install vs. Instanz-Anlage

Der Installer unterscheidet zwei Ebenen:

Ebene Wann Was passiert
Basis-Install einmalig; erkannt an venv + Template-Unit System-Pakete, Ghostscript-Check, Container-Erkennung, Default-User pdfocr, Code nach /opt/pdf-ocr-hotfolder/, venv, systemd-Template-Unit
Instanz-Anlage bei jedem Lauf, beliebig oft Abfragen pro Instanz, Datenverzeichnisse, <instanz>.toml, optionales User-Drop-in, enable --now

Ist die Basis-Installation vorhanden, aber die venv passt nicht mehr zum System-Python (typisch nach einem Distributions-Upgrade), läuft der Basis-Install zur Reparatur erneut: die alte venv wird nach venv.old-<timestamp> weggesichert und neu gebaut. Für den geplanten Weg über ein Debian-Major-Upgrade ist aber update.sh --rebuild-venv gedacht, siehe OS-UPGRADE.md.

Beim Erstlauf ist mindestens eine Instanz Pflicht. Danach fragt der Installer in der Schleife Weitere Instanz anlegen? [j/N]:.


Die Abfragen pro Instanz

create_instance() stellt fünf Fragen. Alle Antworten gelten nur für diese Instanz — nichts davon ist global.

1. Instanz-Name

Instanz-Name (nur a-z, 0-9, -):

Muster ^[a-z0-9][a-z0-9-]*$. Der Name wird zum Unit-Suffix (pdf-ocr-hotfolder@<name>.service) und zum Config-Dateinamen (/etc/pdf-ocr-hotfolder/<name>.toml). Existiert die Config schon, bricht die Anlage ab — ein versehentliches Überschreiben gibt es nicht.

2. Basis-Pfad für die Daten

Basis-Pfad für Daten [/var/lib/pdf-ocr-hotfolder/<name>]:

Darunter entstehen incoming/, outgoing/, working/, error/ und werden auf den Service-User gechownt.

3. Service-User

Service-User [pdfocr]:
  • Existiert der User (lokal oder als AD-User via SSSD/Winbind), wird er übernommen; die primäre Gruppe ermittelt der Installer per id -gn.
  • Existiert er nicht, bietet der Installer an, ihn lokal als System-User anzulegen. Wird das abgelehnt, bricht die Instanz-Anlage ab — der User muss dann erst über AD/SSSD bereitstehen.
  • Ist der gewählte User nicht pdfocr, legt der Installer das Drop-in /etc/systemd/system/pdf-ocr-hotfolder@<instanz>.service.d/user.conf mit User=/Group= an.

Bei AD-Usern mit lokaler UID werden die Datei-Berechtigungen über die UID gesetzt — das läuft transparent.

4. OCR-Sprachen

Tesseract-Sprachen [deu+eng]:

Die Sprachen werden bewusst je Instanz abgefragt: ein Hotfolder buchhaltung sieht nur deutsche Belege, ein Hotfolder export internationale Korrespondenz.

# /etc/pdf-ocr-hotfolder/buchhaltung.toml
languages = "deu"

# /etc/pdf-ocr-hotfolder/export.toml
languages = "deu+eng+fra"

Die Liste so eng wie möglich halten. Jede zusätzliche Sprache kostet Laufzeit und Erkennungsqualität: Tesseract wägt mehr Modelle gegeneinander ab und verwechselt dabei Wörter, die in einer Sprache eindeutig wären. deu+eng+fra auf reinen Deutsch-Scans ist kein Sicherheitsnetz, sondern ein Rückschritt.

Was der Installer damit macht:

  1. Format prüfen — Sprachcodes mit + verbunden (^[a-z]{3}(_[A-Za-z]+)?(\+…)*$), also deu, deu+eng, chi_sim+eng. Bei Unsinn wird erneut gefragt, nicht abgebrochen.
  2. Jeden Code gegen tesseract --list-langs prüfen. Fehlt eine Sprachdatei, bietet er das passende apt-Paket an: tesseract-ocr-<code>, Unterstrich wird zum Bindestrich (chi_sim → tesseract-ocr-chi-sim).
  3. Lehnt man ab oder scheitert die Installation, warnt er, dass OCR mit dieser Sprache bei jeder Datei scheitern würde, und fragt die Sprachen erneut ab — die fehlende Sprache kann man dann einfach weglassen.
  4. Ist tesseract gar nicht aufrufbar, wird die Prüfung übersprungen und die Eingabe unverändert übernommen.

5. Original archivieren?

Original nach erfolgreichem OCR archivieren? [j/N]:
  • Nein (Default) → original_on_success = "delete", das Original wird nach erfolgreichem OCR gelöscht.
  • Ja → original_on_success = "archive", danach:
Archiv-Verzeichnis [<basis>/archive]:

Der Pfad muss absolut sein und darf nicht incoming/, outgoing/, working/ oder error/ der Instanz sein — im Eingang würde das Original sonst endlos neu aufgegriffen, in den übrigen kollidiert es mit der Verarbeitung. Das Verzeichnis wird angelegt und auf den Service-User gechownt; innerhalb des Basis-Pfads erledigt das bestehende chown -R das mit, ein Archiv außerhalb bekommt ein eigenes.

Was danach passiert

Die Instanz-Config entsteht per sed aus config.example.toml. Substituiert werden die vier [paths]-Zeilen sowie [ocr].languages, [output].original_on_success und [output].archive_dir. Anschließend liest der Installer diese drei Keys aus der erzeugten Datei zurück und vergleicht sie mit der Eingabe; erst wenn das passt, nennt die Zusammenfassung Sprachen und Archiv-Verzeichnis. Sonst gibt es eine Warnung mit der Bitte, die Config von Hand nachzuziehen.

Die Config bekommt chmod 640 und chown root:<service-gruppe>, das Verzeichnis /etc/pdf-ocr-hotfolder selbst 750 root:pdfocr — in den Instanz-Configs stehen Klartext-Passwörter für SMTP, Nextcloud und SFTP.

Zum Schluss: daemon-reload und systemctl enable --now pdf-ocr-hotfolder@<instanz>.service.

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/ der Instanz.


Multi-Instanz-Betrieb

Das Tool arbeitet komplett instanzbasiert über die systemd-Template-Unit pdf-ocr-hotfolder@<name>.service. Jede Instanz hat eigene Config, eigene Datenverzeichnisse, eigene Unit, optional eigenen Service-User — und eigene OCR-Sprachen und Original-Behandlung.

sudo ./install.sh          # legt z.B. kunde-a, kunde-b, buchhaltung an

systemctl status 'pdf-ocr-hotfolder@*'
journalctl -u pdf-ocr-hotfolder@kunde-a -f

Der Code unter /opt/pdf-ocr-hotfolder/ (inkl. venv) ist für alle Instanzen gemeinsam. Ein Update trifft damit immer alle Instanzen auf einmal — siehe UPDATE.md.

Das vollständige Verzeichnis-Layout steht im README.


LXC/Container: Error 226/NAMESPACE

In LXC-Containern schlagen die systemd-Hardening-Optionen der Unit (PrivateTmp, ProtectSystem, ProtectKernelTunables, …) fehl; systemd quittiert das mit Error 226/NAMESPACE.

Der Installer erkennt Container über systemd-detect-virt --container und bietet das Drop-in automatisch an. Manuell:

sudo mkdir -p /etc/systemd/system/pdf-ocr-hotfolder@.service.d/
sudo cp /opt/pdf-ocr-hotfolder/systemd/lxc-compat.conf \
    /etc/systemd/system/pdf-ocr-hotfolder@.service.d/
sudo systemctl daemon-reload
sudo systemctl restart 'pdf-ocr-hotfolder@*'

Das Drop-in setzt alle betroffenen Hardening-Schalter auf false. Es liegt auf Template-Ebene (pdf-ocr-hotfolder@.service.d/) und gilt damit für alle Instanzen. Ist es installiert, zieht update.sh es bei jedem Update aus dem Repo nach, damit ein neu ergänzter Hardening-Schalter nicht alle Container-Instanzen reißt.


Ghostscript-Bug auf Debian 12

Ghostscript 10.0.0 bis einschließlich 10.02.0 — der Debian-12-Default — zerschießt OCR in der Kombination [ocr].pdfa_level + skip_text = true: ocrmypdf blockiert komplett.

Deshalb:

  • pdfa_level = "" ist der sichere Default (kein PDF/A-Output).
  • Der Preflight beim Dienststart bricht mit Exit 2 ab, wenn pdfa_level gesetzt und die installierte Ghostscript-Version betroffen ist.
  • --check-config meldet ein gesetztes pdfa_level als Warnung (siehe UPDATE.md).

Der Installer erkennt betroffene Versionen und bietet auf Debian 12 bookworm-backports an. Manuell:

echo 'deb http://deb.debian.org/debian bookworm-backports main' | \
    sudo tee /etc/apt/sources.list.d/bookworm-backports.list
sudo apt update && sudo apt install -t bookworm-backports ghostscript

Ab Ghostscript 10.02.1 ist alles in Ordnung; PDF/A kann dann eingeschaltet werden.


Instanz manuell löschen

Der Installer legt Instanzen an, löscht aber keine. Von Hand:

sudo systemctl disable --now pdf-ocr-hotfolder@<name>
sudo rm /etc/pdf-ocr-hotfolder/<name>.toml
sudo rm -rf /etc/systemd/system/pdf-ocr-hotfolder@<name>.service.d
sudo systemctl daemon-reload
# Datenverzeichnis /var/lib/pdf-ocr-hotfolder/<name> manuell aufräumen

Das Datenverzeichnis bleibt bewusst liegen: dort können noch unverarbeitete PDFs in incoming/, Fehlerfälle in error/ oder Originale im Archiv liegen. Erst hineinsehen, dann löschen.

Solange die Config unter /etc/pdf-ocr-hotfolder/ liegt, zählt update.sh die Instanz weiter mit — auch wenn sie gestoppt ist.


Konfigurationsreferenz

Vollständiges, kommentiertes Beispiel: config.example.toml. Jede Instanz hat ihre eigene Kopie unter /etc/pdf-ocr-hotfolder/<instanz>.toml.

Unbekannte Keys werden beim Laden ignoriert, aber gemeldet — beim Dienststart im Log und von --check-config. Ein Tippfehler wie [ocr].langauges fällt damit auf.

[paths] — Pflicht

Key Bedeutung
incoming Eingang, hier schreibt der Scanner hinein
outgoing Ausgang, fertige OCR-PDFs
working Arbeitsverzeichnis während der Verarbeitung
error fehlgeschlagene PDFs

Fehlt die Sektion oder einer der vier Einträge, gibt es eine deutsche ConfigError-Meldung mit Datei- und Key-Nennung und Exit 2 — der Dienst startet nicht.

[ocr]

Key Default Bedeutung
languages "deu+eng" Tesseract-Sprachen; der Installer fragt sie pro Instanz ab
jobs 4 Threads, die ocrmypdf innerhalb einer PDF nutzt
skip_text true Seiten, die schon Text haben, nicht neu OCRen
oversample 300 Auflösung für gerasterte Seiten
pdfa_level "" "1", "2", "3" oder leer für reines PDF — leer wegen des Ghostscript-Bugs
deskew true schiefe Scans begradigen
clean false Hintergrund säubern (unpaper)
max_workers 2 wie viele PDFs parallel verarbeitet werden
timeout 300 max. Sekunden, die Tesseract pro Seite laufen darf; 0 = kein eigenes Limit

timeout ist ein Seiten-Timeout, kein Gesamt-Timeout. Der Wert geht als tesseract_timeout an ocrmypdf; ein Dokument-Timeout kennt ocrmypdf nicht. Wer noch den alten Default 1800 aus einer Config vor 0.4.0 stehen hat, gibt Tesseract 30 Minuten je Seite — Richtwert ist 300. Läuft eine Seite in den Timeout, landet sie ohne Textebene im Ergebnis, die übrigen Seiten laufen weiter. Ein durchgereichtes 0 würde ocrmypdf dazu bringen, OCR still zu überspringen, deshalb wird 0 (oder negativ) gar nicht erst übergeben und der ocrmypdf-Default greift. Siehe auch Config-Drift.

[output]

Key Default Bedeutung
name_mode "prefix" prefix → OCR_scan.pdf, suffix → scan_OCR.pdf (vor der Extension), none → unverändert
name_tag "OCR_" verbatim eingefügter String; leer wirkt wie none
original_on_success "delete" delete oder archive — Installer fragt das ab
archive_dir "" absoluter Pfad, Pflicht bei archive; Namenskollision → Zeitstempel-Suffix

Ein Tippfehler in name_mode oder original_on_success führt beim Start zum Abbruch mit Exit 2, nicht erst bei der ersten Datei.

[verapdf]

Key Default Bedeutung
enabled false PDF/A-Validierung per veraPDF-CLI
binary /opt/verapdf/verapdf Pfad zum veraPDF-Binary
flavour "1b" PDF/A-Flavour

veraPDF startet eine JVM und ist entsprechend teuer — nur einschalten, wenn die Validierung wirklich gebraucht wird. Bei FAIL wandert das OCR-Ergebnis nach error/; das Original folgt original_on_success (bei archive bleibt es also erhalten).

[upload.folder] / [upload.nextcloud] / [upload.sftp]

Beliebig viele Ziele gleichzeitig aktivierbar. Sind alle aus, bleibt das fertige PDF einfach in outgoing/ liegen.

Sektion Keys
[upload.folder] enabled, target — leer heißt [paths].outgoing, dann No-op
[upload.nextcloud] enabled, url, username, password, remote_path, verify_ssl
[upload.sftp] enabled, host, port, username, key_file, password, remote_path

Schlägt mindestens ein Ziel fehl, zählt das als Fehler und die Mail geht als FEHLER raus — das PDF bleibt aber bewusst in outgoing/ liegen, das OCR selbst war ja erfolgreich.

[notify.email]

Key Default Bedeutung
enabled false E-Mail-Benachrichtigung an/aus
smtp_host, smtp_port, smtp_user, smtp_password, use_starttls — SMTP-Zugang
from_addr — Absender
to_addrs [] Empfängerliste
on "errors" always | errors | never

[logging]

Key Default Bedeutung
level "INFO" DEBUG | INFO | WARNING | ERROR

Der Dienst schreibt kein eigenes Logfile — alles geht nach stdout und damit ins journal.


Troubleshooting

Tesseract findet die Sprache nicht

sudo apt install tesseract-ocr-deu tesseract-ocr-eng

Danach [ocr].languages prüfen. Der Installer nimmt einem das beim Anlegen einer Instanz ab, ein nachträglich in die Config geschriebener Sprachcode wird aber nicht geprüft — --check-config zeigt die eingestellten Sprachen an.

"PriorOcrFoundError"

ocrmypdf erkennt bereits vorhandenen OCR-Text. skip_text = true in der Config setzen (Default).

Berechtigungsprobleme bei AD-User

Der Service-User braucht rw auf alle vier Verzeichnisse der Instanz (und auf das Archiv, falls konfiguriert):

sudo chown -R DOMAIN\\scanuser:DOMAIN\\scangroup /var/lib/pdf-ocr-hotfolder/<instanz>

veraPDF-Validierung schlägt immer fehl

[verapdf].binary prüfen. Wenn die Validierung nicht zwingend gebraucht wird: enabled = false.

Dienst startet nicht (Exit 2)

Exit 2 heißt immer: Config oder Preflight. Die Ursache steht im journal und ausführlicher in:

sudo /opt/pdf-ocr-hotfolder/venv/bin/python -m pdf_ocr_hotfolder \
    --check-config --config /etc/pdf-ocr-hotfolder/<instanz>.toml

Dienst startet nicht (203/EXEC)

Der Interpreter der venv ist weg — fast immer nach einem Distributions-Upgrade. Siehe OS-UPGRADE.md.


Manueller Lauf (One-Shot)

Bestehende PDFs einer Instanz einmalig verarbeiten und beenden — greift auch Dateien auf, die in working/ liegen geblieben sind:

sudo -u pdfocr /opt/pdf-ocr-hotfolder/venv/bin/python -m pdf_ocr_hotfolder \
    --config /etc/pdf-ocr-hotfolder/kunde-a.toml --once

Exit-Code: 0 = alles verarbeitet (auch "nichts da"), 1 = mindestens eine Datei fehlgeschlagen, 2 = Config- oder Preflight-Fehler.