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>
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.confmitUser=/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:
- Format prüfen — Sprachcodes mit
+verbunden (^[a-z]{3}(_[A-Za-z]+)?(\+…)*$), alsodeu,deu+eng,chi_sim+eng. Bei Unsinn wird erneut gefragt, nicht abgebrochen. - Jeden Code gegen
tesseract --list-langsprüfen. Fehlt eine Sprachdatei, bietet er das passende apt-Paket an:tesseract-ocr-<code>, Unterstrich wird zum Bindestrich (chi_sim→tesseract-ocr-chi-sim). - 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.
- Ist
tesseractgar 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_levelgesetzt und die installierte Ghostscript-Version betroffen ist. --check-configmeldet ein gesetztespdfa_levelals 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.