305454eeb5
Aus den Testlaeufen auf Debian 12 (CT 200) und Debian 13 (CT 201): - Systemanforderungen: mindestens 2 GB RAM. Eine einzelne A4-Seite in 300 dpi mit deskew=true hat auf einem 512-MB-Container den OOM-Killer ausgeloest (anon-rss:489676kB). Dazu der Zusammenhang RAM <-> max_workers x jobs x Aufloesung, wie sich ein OOM-Kill aeussert und wie man ihn nachweist (/sys/fs/cgroup/memory.events, dmesg auf dem Host). - Troubleshooting: kaputtes systemd-journald ist ein blinder Fleck, weil der Dienst seit v0.4.1 nur dorthin loggt. Symptom, erste Pruefung und ein Vordergrund-Notbehelf. Auf einem von zwei Testcontainern aufgetreten — kein generelles LXC-Muster. Nebenbei korrigiert: toter Anker in docs/INSTALLATION.md (#3-ocr-sprachen -> #4-ocr-sprachen) und eine veraltete Stelle im Briefing, die requirements.txt noch als "ocrmypdf 16.x" beschrieb. Reine Doku-Version, 152 Tests unveraendert gruen. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
633 lines
24 KiB
Markdown
633 lines
24 KiB
Markdown
# Installation
|
||
|
||
Erstinstallation und Anlage von Hotfolder-Instanzen mit `install.sh`.
|
||
|
||
Verwandte Dokumente: [README](../README.md) · [Update](UPDATE.md) · [Debian-Major-Upgrade](OS-UPGRADE.md)
|
||
|
||
---
|
||
|
||
## 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 |
|
||
| Arbeitsspeicher | **mindestens 2 GB** für den produktiven Betrieb — siehe [Systemanforderungen](#systemanforderungen) |
|
||
| 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](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](#4-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](OS-UPGRADE.md#pins-in-requirementstxt).
|
||
|
||
---
|
||
|
||
## Systemanforderungen
|
||
|
||
CPU und Platte sind unkritisch — **der Arbeitsspeicher ist es nicht.** OCR
|
||
rastert jede Seite in voller Auflösung ins RAM; der Spitzenbedarf hängt an der
|
||
Seitengröße, nicht an der Dateigröße der PDF.
|
||
|
||
**Empfehlung: mindestens 2 GB RAM.** Für Mehr-Instanz-Betrieb oder
|
||
`max_workers > 2` entsprechend mehr.
|
||
|
||
### Warum 512 MB nachweislich nicht reichen
|
||
|
||
Gemessen auf einem LXC-Container mit **512 MB RAM + 512 MB Swap**, Debian 13,
|
||
Ghostscript 10.05.1:
|
||
|
||
| Vorgang | Ergebnis |
|
||
|---------|----------|
|
||
| eine einzelne **A4-Seite in 300 dpi**, `deskew = true` | cgroup-Limit gerissen, Dienst vom **OOM-Killer** beendet |
|
||
| dieselbe Verarbeitung mit einer kleineren Seite (850 × 1100 px) | läuft sauber durch, ca. 19 s |
|
||
|
||
Beleg aus dem `dmesg` des LXC-Hosts:
|
||
|
||
```
|
||
oom-kill:constraint=CONSTRAINT_MEMCG, oom_memcg=/lxc/201, task=python
|
||
total-vm:1168764kB, anon-rss:489676kB
|
||
```
|
||
|
||
Eine Seite, ein Worker — und schon knapp 500 MB anonymer Speicher. 512 MB sind
|
||
damit für 300-dpi-Scans keine knappe, sondern eine unzureichende Dimensionierung.
|
||
|
||
### RAM ↔ `max_workers` × `jobs` × Auflösung
|
||
|
||
Der Spitzenbedarf multipliziert sich über drei Config-Werte aus
|
||
[`[ocr]`](#ocr):
|
||
|
||
| Key | Default | Wirkung auf den Speicher |
|
||
|-----|---------|--------------------------|
|
||
| `max_workers` | `2` | **so viele PDFs gleichzeitig** — jede mit eigenem Seitenpuffer. Der direkte Multiplikator |
|
||
| `jobs` | `4` | Threads **innerhalb** einer PDF; mehrere Seiten gleichzeitig im Speicher |
|
||
| `oversample` | `300` | Auflösung gerasterter Seiten — der Bedarf wächst quadratisch mit der dpi |
|
||
|
||
Der Default `max_workers = 2` erlaubt also, dass **zwei** solcher Seiten
|
||
parallel verarbeitet werden. Wer knapp dimensioniert, zieht zuerst
|
||
`max_workers` auf `1` herunter, danach `jobs`. `oversample` unter 300 zu
|
||
drücken, spart zwar Speicher, kostet aber Erkennungsqualität — das ist der
|
||
letzte Hebel, nicht der erste.
|
||
|
||
### Wie sich ein OOM-Kill äußert
|
||
|
||
Von außen sieht ein OOM-Kill wie ein Anwendungsfehler aus — er ist keiner:
|
||
|
||
- Der Dienst ist **weg** bzw. wurde von systemd neu gestartet
|
||
(`Restart=on-failure`); `systemctl show -p NRestarts` steigt.
|
||
- Im journal bricht die Verarbeitung **mitten in der Datei** ab, ohne
|
||
Python-Traceback und ohne `ERROR`-Zeile aus dem Tool.
|
||
- Die betroffene PDF bleibt in `working/` liegen.
|
||
|
||
### Wie man ihn nachweist
|
||
|
||
**Im Container:**
|
||
|
||
```bash
|
||
cat /sys/fs/cgroup/memory.events
|
||
# oom_kill 1 <- alles über 0 ist ein Treffer
|
||
```
|
||
|
||
**Auf dem LXC-Host** (im Container zeigt `dmesg` diese Zeilen nicht):
|
||
|
||
```bash
|
||
dmesg -T | grep -i oom-kill
|
||
```
|
||
|
||
Diese Prüfung gehört an den **Anfang** der Fehlersuche, wenn Dateien
|
||
unerklärlich in `working/` liegen bleiben: ohne sie sucht man den Fehler in
|
||
ocrmypdf, Tesseract oder der Config, wo keiner ist.
|
||
|
||
### Datenverlust ist abgefangen, der OOM bleibt
|
||
|
||
Seit **v0.6.0** greift der Dienst beim nächsten Start auf, was in `working/`
|
||
liegen geblieben ist (siehe [UPDATE.md](UPDATE.md#wiederaufnahme-aus-working)).
|
||
Eine vom OOM-Killer unterbrochene Datei geht also nicht verloren. Behoben ist
|
||
damit aber nur die Folge: bei unveränderter Dimensionierung läuft dieselbe Datei
|
||
nach dem Neustart erneut in denselben OOM — bis `max_workers` sinkt oder das
|
||
System mehr RAM bekommt.
|
||
|
||
---
|
||
|
||
## Installation
|
||
|
||
```bash
|
||
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](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.
|
||
|
||
```toml
|
||
# /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
|
||
|
||
```bash
|
||
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.
|
||
|
||
```bash
|
||
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](UPDATE.md).
|
||
|
||
Das vollständige Verzeichnis-Layout steht im
|
||
[README](../README.md#verzeichnisse).
|
||
|
||
---
|
||
|
||
## 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:
|
||
|
||
```bash
|
||
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** —
|
||
enthält Regressionen, die PDFs mit vorhandenem Text beschädigen. ocrmypdf
|
||
verweigert deshalb den Dienst, statt ein kaputtes Ergebnis zu liefern.
|
||
|
||
### Wann genau ocrmypdf abbricht
|
||
|
||
Die Bedingung steht in `ocrmypdf/builtin_plugins/ghostscript.py`
|
||
(`check_options()`) und hängt an **zwei** Dingen — an der Config *und* an der
|
||
ocrmypdf-Version:
|
||
|
||
| ocrmypdf | Die Prüfung greift bei | Heißt für uns |
|
||
|----------|------------------------|---------------|
|
||
| **≤ 16.x** | `skip_text` oder `redo_ocr` — **unabhängig vom `output_type`** | Auch ohne PDF/A scheitert **jede** Datei, denn `skip_text = true` ist unser Default |
|
||
| **≥ 17.0** | dasselbe, aber nur innerhalb von `if options.output_type.startswith('pdfa')` | Ohne PDF/A wird Ghostscript gar nicht angefasst — unkritisch |
|
||
|
||
> ⚠️ **`pdfa_level = ""` allein ist damit kein Schutz.** Die Entwarnung gilt nur
|
||
> zusammen mit **ocrmypdf ≥ 17**. Das war der Fehler in 0.6.0: der Pin stand auf
|
||
> `ocrmypdf==16.13.0`, und auf Debian 12 landete daraufhin jede PDF in `error/` —
|
||
> bei grünem `systemctl status` und „Preflight ok".
|
||
> `requirements.txt` pinnt deshalb 17.x.
|
||
|
||
### Was das Tool dagegen tut
|
||
|
||
- `pdfa_level = ""` ist der Default (kein PDF/A-Output).
|
||
- `requirements.txt` pinnt **ocrmypdf 17.x**. Ein Downgrade auf 16.x macht jede
|
||
Debian-12-Instanz unbrauchbar; `update.sh` weist Versionssprünge der
|
||
gepinnten Pakete deshalb ausdrücklich aus.
|
||
- Der Preflight bricht beim **Dienststart** mit **Exit 2** ab, wenn die
|
||
Ghostscript-Version betroffen ist **und** die Kombination aus `skip_text`,
|
||
`pdfa_level` und installierter ocrmypdf-Version tatsächlich zum Abbruch
|
||
führen würde. Der Dienst startet dann gar nicht erst, statt jede Datei
|
||
einzeln scheitern zu lassen.
|
||
- `--check-config` meldet denselben Zustand als **Fehler (Exit 2)** und zeigt
|
||
ocrmypdf- und Ghostscript-Version an (siehe
|
||
[UPDATE.md](UPDATE.md#config-prüfung-per---check-config)).
|
||
- Der [Rauchtest](UPDATE.md#rauchtest) in `update.sh` schiebt nach dem Update
|
||
eine Test-PDF durch die echte Pipeline — er hätte den Ausfall sofort gezeigt.
|
||
|
||
### Abhilfe
|
||
|
||
**Weg 1 — Ghostscript anheben** (empfohlen). Der Installer erkennt betroffene
|
||
Versionen und bietet auf Debian 12 bookworm-backports an. Manuell:
|
||
|
||
```bash
|
||
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.
|
||
|
||
**Weg 2 — `skip_text = false` setzen.** Dann wird vorhandener Text neu erkannt
|
||
statt übersprungen, und die Bedingung greift nicht mehr. Das kostet Laufzeit bei
|
||
PDFs, die bereits eine Textebene haben.
|
||
|
||
---
|
||
|
||
## Instanz manuell löschen
|
||
|
||
Der Installer legt Instanzen an, löscht aber keine. Von Hand:
|
||
|
||
```bash
|
||
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`](../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](#ghostscript-bug-auf-debian-12). Achtung: leer allein schützt nur zusammen mit ocrmypdf ≥ 17 |
|
||
| `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](UPDATE.md#config-drift-nach-einem-update).
|
||
|
||
### `[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
|
||
|
||
```bash
|
||
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):
|
||
|
||
```bash
|
||
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:
|
||
|
||
```bash
|
||
cd /opt/pdf-ocr-hotfolder && sudo ./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](OS-UPGRADE.md).
|
||
|
||
### Keine Logs: "No journal files were found"
|
||
|
||
**Symptom:** `journalctl -u pdf-ocr-hotfolder@<instanz>` bleibt leer oder meldet
|
||
`No journal files were found.` — auch dann, wenn der Dienst nachweislich läuft
|
||
und Dateien verarbeitet.
|
||
|
||
**Erste Prüfung:**
|
||
|
||
```bash
|
||
systemctl status systemd-journald
|
||
```
|
||
|
||
Ist `systemd-journald.service` selbst `failed` (beobachtet mit
|
||
`status=243/CREDENTIALS`), gibt es schlicht kein journal, in das geschrieben
|
||
werden könnte.
|
||
|
||
**Einordnung.** Der Dienst loggt seit v0.4.1 **ausschließlich** nach journald —
|
||
es gibt bewusst kein eigenes Logfile und kein Logverzeichnis. Ein kaputtes
|
||
journald ist damit ein blinder Fleck: jede Fehlersuche läuft ins Leere, und der
|
||
Ausfall sieht aus wie ein stummer Dienst. **Deshalb vor jeder Fehlersuche
|
||
zuerst journald prüfen**, nicht erst, wenn nichts anderes mehr passt.
|
||
|
||
Das ist **kein** generelles LXC-Muster. Von zwei Testcontainern war genau einer
|
||
betroffen; auf dem zweiten lief journald einwandfrei. Es handelt sich um einen
|
||
Schaden auf dieser einen Maschine, nicht um eine Eigenschaft von Containern.
|
||
|
||
**Notbehelf, solange journald nicht zu retten ist:** die Instanz einmal im
|
||
Vordergrund laufen lassen — dann geht die Ausgabe direkt ins Terminal, am
|
||
journal vorbei.
|
||
|
||
```bash
|
||
sudo systemctl stop pdf-ocr-hotfolder@<instanz>
|
||
cd /opt/pdf-ocr-hotfolder && sudo -u pdfocr ./venv/bin/python -m pdf_ocr_hotfolder \
|
||
--config /etc/pdf-ocr-hotfolder/<instanz>.toml
|
||
```
|
||
|
||
Das `cd` ist zwingend: das Paket wird nicht pip-installiert, sondern nach
|
||
`/opt/pdf-ocr-hotfolder` kopiert und nur über das Arbeitsverzeichnis gefunden
|
||
(ohne `cd` gibt es `No module named pdf_ocr_hotfolder`, v0.6.2). Beenden mit
|
||
`Strg+C`, danach `sudo systemctl start pdf-ocr-hotfolder@<instanz>`. Wer nur den
|
||
Bestand abarbeiten und dann aussteigen will, hängt `--once` an (siehe
|
||
[Manueller Lauf](#manueller-lauf-one-shot)).
|
||
|
||
### Dienst bricht mitten in der Verarbeitung weg
|
||
|
||
Datei bleibt in `working/`, kein Traceback, `NRestarts` steigt: das ist fast
|
||
immer der OOM-Killer, kein Anwendungsfehler. Nachweis und Dimensionierung unter
|
||
[Systemanforderungen](#wie-sich-ein-oom-kill-äußert).
|
||
|
||
---
|
||
|
||
## Manueller Lauf (One-Shot)
|
||
|
||
Bestehende PDFs einer Instanz einmalig verarbeiten und beenden — greift auch
|
||
Dateien auf, die in `working/` liegen geblieben sind:
|
||
|
||
```bash
|
||
cd /opt/pdf-ocr-hotfolder && sudo -u pdfocr ./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.
|