diff --git a/AI_AGENT_BRIEFING.md b/AI_AGENT_BRIEFING.md index b8aedcf..0e26eae 100644 --- a/AI_AGENT_BRIEFING.md +++ b/AI_AGENT_BRIEFING.md @@ -1,7 +1,7 @@ # AI Agent Briefing — PDF OCR Hotfolder -**Zuletzt aktualisiert:** 2026-09-22 -**Version:** 0.6.2 +**Zuletzt aktualisiert:** 2026-09-23 +**Version:** 0.6.3 **Status:** Multi-Instanz-Betrieb, Preflight-Checks, Fehlerzählung, Wiederaufnahme aus `working/` und ein abgesicherter Updater (venv-Rebuild, Backup, Verifikation, Versionssprung-Meldung, Rauchtest). Test-Suite grün (152 pytest-Tests). Ein Produktiv-Einsatz ist im Repo (README/CHANGELOG) nicht dokumentiert — die bisherigen Fixes stammen aus Issues #1–#6 und aus Vorbereitungen auf Debian 13, nicht aus einem belegten Dauerbetrieb. > **Betriebsabläufe stehen nicht hier**, sondern in: @@ -50,7 +50,7 @@ pdf-ocr-hotfolder/ ├── config.example.toml ├── install.sh # Interaktiver Installer + Instanz-Manager ├── update.sh # Updater (--help, --rebuild-venv), ~860 Zeilen -├── requirements.txt # feste Pins (ocrmypdf 16.x!) +├── requirements.txt # feste Pins (ocrmypdf 17.x — 16.x ist unbrauchbar, s. 0.6.1) ├── VERSION ├── CHANGELOG.md ├── README.md diff --git a/CHANGELOG.md b/CHANGELOG.md index 8392962..e56a83f 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,59 @@ # Changelog +## [0.6.3] - 2026-09-23 + +Reine Doku-Version — kein Code, kein Installer, kein Updater, keine Unit, keine +Tests angefasst (ausser dem Versionsstring). Beide Erkenntnisse stammen aus den +Testlaeufen auf Debian 12 und Debian 13. + +### Added +- **Abschnitt "Systemanforderungen" in `docs/INSTALLATION.md`.** Die + Dimensionierung fehlte bisher komplett. Empfohlen werden **mindestens 2 GB + RAM**; 512 MB reichen fuer 300-dpi-Scans nachweislich nicht. Gemessen auf + einem LXC-Container mit 512 MB RAM + 512 MB Swap, Debian 13, + Ghostscript 10.05.1: eine **einzelne A4-Seite in 300 dpi** mit + `deskew = true` riss das cgroup-Limit und der Dienst wurde vom OOM-Killer + beendet (`oom-kill:constraint=CONSTRAINT_MEMCG, oom_memcg=/lxc/201, + task=python`, `total-vm:1168764kB, anon-rss:489676kB`). Dieselbe + Verarbeitung mit einer kleineren Seite (850x1100 px) lief in ca. 19 s sauber + durch. + - Dokumentiert ist der Zusammenhang **RAM <-> `max_workers` x `jobs` x + Aufloesung**: `max_workers` (Default 2) laesst zwei solcher Seiten + gleichzeitig laufen, der Spitzenbedarf multipliziert sich entsprechend. + Wer knapp dimensioniert, zieht zuerst `max_workers` herunter. + - Dazu, **wie sich ein OOM-Kill aeussert** (Dienst weg bzw. von systemd neu + gestartet, `NRestarts` steigt, Abbruch mitten in der Datei ohne Traceback, + PDF bleibt in `working/` liegen) und **wie man ihn nachweist** + (`/sys/fs/cgroup/memory.events` im Container, `dmesg` auf dem LXC-Host). + Ohne diese Pruefung sieht der Fall wie ein Anwendungsfehler aus und man + sucht in ocrmypdf, Tesseract oder der Config. + - Mit dem Hinweis, dass die Wiederaufnahme aus `working/` seit v0.6.0 den + Datenverlust abfaengt — der OOM selbst bleibt aber ein Problem: bei + unveraenderter Dimensionierung laeuft dieselbe Datei nach dem Neustart + erneut hinein. + - `README.md` bekommt im Schnellstart nur einen Einzeiler mit Verweis, keine + Dublette. +- **Troubleshooting-Eintrag "Keine Logs: No journal files were found" in + `docs/INSTALLATION.md`.** Auf einem der Testcontainer war + `systemd-journald.service` kaputt (`failed`, `status=243/CREDENTIALS`), + `journalctl` lieferte `No journal files were found.` Da der Dienst seit + v0.4.1 **ausschliesslich** nach journald loggt (kein FileHandler, kein + Logverzeichnis — bewusste Entscheidung), gibt es dann gar keine Dienstlogs: + ein blinder Fleck, der vor jeder Fehlersuche per + `systemctl status systemd-journald` auszuschliessen ist. Als Notbehelf ist + der Vordergrund-Aufruf dokumentiert + (`cd /opt/pdf-ocr-hotfolder && sudo -u pdfocr ./venv/bin/python -m + pdf_ocr_hotfolder --config ...`, das `cd` ist zwingend, s. 0.6.2). + Ausdruecklich festgehalten: das ist **kein** generelles LXC-Muster — der + zweite Testcontainer war in Ordnung, es war Schaden auf genau dieser + Maschine. + +### Changed +- `AI_AGENT_BRIEFING.md`: der Kommentar zu `requirements.txt` in der + Projektstruktur nannte noch "ocrmypdf 16.x!" — genau die Version, die seit + 0.6.1 als unbrauchbar gilt und gegen die der Pin schuetzt. Korrigiert auf + 17.x. + ## [0.6.2] - 2026-09-22 ### Fixed diff --git a/README.md b/README.md index 1a7362d..6f85173 100644 --- a/README.md +++ b/README.md @@ -29,6 +29,10 @@ Weiter: [CHANGELOG.md](CHANGELOG.md) · [AI_AGENT_BRIEFING.md](AI_AGENT_BRIEFING ## 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](docs/INSTALLATION.md#systemanforderungen)). + ```bash git clone gitea@gitea.sonith.de:sonith_ug/pdf-ocr-hotfolder.git cd pdf-ocr-hotfolder @@ -158,5 +162,5 @@ MIT — © Sonith UG --- -**Version:** 0.6.2 +**Version:** 0.6.3 **Repo:** https://gitea.sonith.de/sonith_ug/pdf-ocr-hotfolder diff --git a/VERSION b/VERSION index b616048..844f6a9 100644 --- a/VERSION +++ b/VERSION @@ -1 +1 @@ -0.6.2 +0.6.3 diff --git a/docs/INSTALLATION.md b/docs/INSTALLATION.md index cb5b2e7..e8d5714 100644 --- a/docs/INSTALLATION.md +++ b/docs/INSTALLATION.md @@ -12,6 +12,7 @@ Verwandte Dokumente: [README](../README.md) · [Update](UPDATE.md) · [Debian-Ma |-------|-------------| | 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)) | @@ -29,7 +30,7 @@ ca-certificates curl ``` Weitere Tesseract-Sprachpakete installiert der Installer bei Bedarf pro Instanz -nach (siehe [OCR-Sprachen](#3-ocr-sprachen)). +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 @@ -37,6 +38,92 @@ 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 @@ -480,6 +567,55 @@ cd /opt/pdf-ocr-hotfolder && sudo ./venv/bin/python -m pdf_ocr_hotfolder \ 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) diff --git a/pdf_ocr_hotfolder/__init__.py b/pdf_ocr_hotfolder/__init__.py index 8d3da10..71f0e6f 100644 --- a/pdf_ocr_hotfolder/__init__.py +++ b/pdf_ocr_hotfolder/__init__.py @@ -1,3 +1,3 @@ """PDF OCR Hotfolder — Scanner-PDFs automatisch durchsuchbar machen.""" -__version__ = "0.6.2" +__version__ = "0.6.3"