# 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, `.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-` 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@.service`) und zum Config-Dateinamen (`/etc/pdf-ocr-hotfolder/.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/]: ``` 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@.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-`, 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 [/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:`, 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@.service`. ### Test ```bash cp irgendein-scan.pdf /var/lib/pdf-ocr-hotfolder//incoming/ journalctl -u pdf-ocr-hotfolder@ -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@.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@ sudo rm /etc/pdf-ocr-hotfolder/.toml sudo rm -rf /etc/systemd/system/pdf-ocr-hotfolder@.service.d sudo systemctl daemon-reload # Datenverzeichnis /var/lib/pdf-ocr-hotfolder/ 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/.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/ ``` ### 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/.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@` 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@ cd /opt/pdf-ocr-hotfolder && sudo -u pdfocr ./venv/bin/python -m pdf_ocr_hotfolder \ --config /etc/pdf-ocr-hotfolder/.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@`. 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.