Files
pdf-ocr-hotfolder/README.md
T
techadmin 1b67c846a2 fix: Updater meldet den LXC-Drop-in nur noch, wenn sich wirklich etwas aendert (v0.7.2)
Nachlese aus der Verifikation von v0.7.1 auf einem frischen Debian 12.

- update.sh meldete "LXC-Drop-in nachgezogen ✓" auch bei unveraenderter
  Datei — dieselbe Klasse Unwahrheit wie das Ghostscript-Haekchen, das in
  v0.7.1 behoben wurde. Jetzt wird verglichen und ehrlich gemeldet.
- "Quelle wieder entfernt" ist Teil der schlechten Nachricht und kommt als
  WARN statt INFO.

254 Tests gruen.

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

201 lines
10 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# PDF OCR Hotfolder
Verwandelt eingehende gescannte PDFs automatisch in **durchsuchbare PDFs** (PDF/A optional) per OCR. Hauptanwendung: Kunden-Scanner schiebt PDF in einen Ordner — Sekunden später liegt die OCR-Version im Ausgang oder wird in Nextcloud / per SFTP weitergeleitet.
## Dokumentation
| Dokument | Inhalt |
|----------|--------|
| **[docs/INSTALLATION.md](docs/INSTALLATION.md)** | Erstinstallation, Instanzen anlegen, LXC, Ghostscript, **Konfigurationsreferenz**, Troubleshooting |
| **[docs/UPDATE.md](docs/UPDATE.md)** | Update mit `update.sh`: Ablauf, Backup, Rollback, `--check-config`, Config-Drift |
| **[docs/OS-UPGRADE.md](docs/OS-UPGRADE.md)** | Debian-Major-Upgrade (12 → 13): venv neu bauen, Pins anheben |
Weiter: [CHANGELOG.md](CHANGELOG.md) · [AI_AGENT_BRIEFING.md](AI_AGENT_BRIEFING.md) · [config.example.toml](config.example.toml)
## Features
- 🔍 **OCR via ocrmypdf + Tesseract** (Library-Call, kein Subprozess-Overhead)
- 📂 **Hotfolder via watchdog** — reagiert auf `created`, `moved`, `closed` Events
- 🧠 **Stabilitäts-Erkennung**: wartet bis Scanner fertig geschrieben hat
- 🔁 **Parallelverarbeitung** mehrerer PDFs (ThreadPool, konfigurierbar)
- ♻️ **Wiederaufnahme aus `working/`** nach einem harten Stopp — keine Datei bleibt liegen
- ✅ **PDF/A-Output** (1, 2 oder 3) optional — **aber nicht auf Debian 12**: dessen Ghostscript (10.0.0–10.02.0) hat einen Bug, ocrmypdf lehnt PDF/A zusammen mit `skip_text = true` ab, und ein neueres Ghostscript gibt es dort nicht (`bookworm-backports` enthält kein Ghostscript-Paket). Wer PDF/A braucht, installiert auf **Debian 13** (Ghostscript 10.05.1) oder setzt `skip_text = false` ([Details](docs/INSTALLATION.md#ghostscript-bug-auf-debian-12))
- 🛡️ **veraPDF-Validierung** optional — Binary wird im Preflight geprüft, eine Störung gilt nicht als FAIL
- 🚫 **Überschreibt nie eine gleichnamige Datei** — in `outgoing/`, `error/`, Archiv und Ordner-Upload weicht sie mit Zeitstempel aus
- ☁️ **Upload-Ziele**: lokaler Ordner, Nextcloud (WebDAV via Python), SFTP
- 📧 **E-Mail-Notify** (immer / nur Fehler / nie)
- 🔐 **Service-User-Support** für lokale **und AD-User mit lokaler UID** (SSSD/Winbind)
- ⚙️ Saubere systemd-Integration mit auto-Restart, **Multi-Instanz** über eine Template-Unit
- 👁️ **Toter Verzeichnis-Watch wird erkannt** — der Dienst beendet sich (Exit 3), systemd setzt den Watch neu auf
- 🩺 **`--check-config`** prüft eine Instanz-Config ohne etwas zu verarbeiten
## 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)).
Dateisystem **ext4, xfs oder zfs**; `incoming/` muss **lokal** liegen — auf
einem CIFS/NFS-Mount liefert inotify keine Events und der Hotfolder bemerkt
neue Dateien nur noch beim Start
([warum](docs/INSTALLATION.md#dateisystem-ext4-xfs-oder-zfs)).
Außerdem **`git`** (für den Clone) und, falls nicht als `root` gearbeitet wird,
**`sudo`** — beides fehlt im Proxmox-Debian-Standard-Template
([Details](docs/INSTALLATION.md#voraussetzungen)):
```bash
apt update && apt install -y git # als root; ggf. zusätzlich: sudo
```
```bash
# HTTPS — funktioniert ohne Credentials, das ist der Normalfall
git clone https://gitea.sonith.de/sonith_ug/pdf-ocr-hotfolder.git
cd pdf-ocr-hotfolder
./install.sh # als root; mit sudo: sudo ./install.sh
```
Wer einen Deploy-Key hinterlegt hat, klont per SSH — **der SSH-User heißt
`gitea`, nicht `git`**:
```bash
git clone gitea@gitea.sonith.de:sonith_ug/pdf-ocr-hotfolder.git
```
Der Installer legt einmalig Code, venv und die systemd-Template-Unit an und
fragt danach **pro Instanz** Name, Basis-Pfad, Service-User, OCR-Sprachen und
die Original-Behandlung ab. Bei jedem erneuten Aufruf erkennt er bestehende
Instanzen und fragt nur nach neuen.
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/`-Ordner der Instanz.
Alle Details zu den Abfragen, zum Multi-Instanz-Betrieb und zu den Fallstricken
(LXC, Ghostscript): **[docs/INSTALLATION.md](docs/INSTALLATION.md)**.
Update: `git pull && ./update.sh` (als root; mit `sudo`:
`sudo ./update.sh`) — siehe **[docs/UPDATE.md](docs/UPDATE.md)**.
Nach einem Debian-Major-Upgrade: **[docs/OS-UPGRADE.md](docs/OS-UPGRADE.md)**.
## Verzeichnisse
| Pfad | Zweck |
|------|-------|
| `/opt/pdf-ocr-hotfolder/` | Code + venv (für alle Instanzen gemeinsam) |
| `/opt/pdf-ocr-hotfolder/lib/common.sh` | gemeinsame Shell-Funktionen für `install.sh`/`update.sh` (mitkopiert) |
| `/etc/pdf-ocr-hotfolder/<instanz>.toml` | Config pro Instanz (640, root:\<service-gruppe\>) |
| `/etc/systemd/system/pdf-ocr-hotfolder@.service` | systemd Template-Unit |
| `/var/lib/pdf-ocr-hotfolder/<instanz>/incoming` | Eingang (Scanner schreibt hier rein) |
| `/var/lib/pdf-ocr-hotfolder/<instanz>/working` | Arbeitsverzeichnis während OCR |
| `/var/lib/pdf-ocr-hotfolder/<instanz>/outgoing` | Ausgang (fertige PDFs) |
| `/var/lib/pdf-ocr-hotfolder/<instanz>/error` | Fehlgeschlagene PDFs |
| `/var/backups/pdf-ocr-hotfolder/` | Update-Backups (0600, letzte 5) |
Ein eigenes Logverzeichnis gibt es nicht — der Dienst loggt nach stdout und
damit ins journal.
## Konfiguration im Überblick
Jede Instanz hat ihre eigene TOML unter `/etc/pdf-ocr-hotfolder/<instanz>.toml`.
Vollständiges, kommentiertes Beispiel: [`config.example.toml`](config.example.toml).
Key-für-Key-Referenz: [docs/INSTALLATION.md](docs/INSTALLATION.md#konfigurationsreferenz).
| Sektion | Zweck |
|---------|-------|
| `[paths]` | `incoming`, `outgoing`, `working`, `error` — **Pflicht**, alle **absolut** |
| `[ocr]` | Sprachen, `jobs`, `skip_text`, `pdfa_level`, `deskew`, `max_workers`, `timeout` (Sekunden **pro Seite**) |
| `[output]` | Dateibenennung (`name_mode`/`name_tag`) und Original-Behandlung (`delete`/`archive`) |
| `[verapdf]` | optionale PDF/A-Validierung per CLI |
| `[upload.folder]` / `[upload.nextcloud]` / `[upload.sftp]` | Upload-Ziele, beliebig viele gleichzeitig |
| `[notify.email]` | SMTP-Benachrichtigung: `always` \| `errors` \| `never` |
| `[logging]` | `level` = DEBUG/INFO/WARNING/ERROR |
Die Instanz-Configs enthalten **Klartext-Passwörter** (SMTP, Nextcloud, SFTP) —
deshalb `640 root:<service-gruppe>` und beim Debuggen nicht in Tickets kopieren.
Config prüfen, ohne etwas zu verarbeiten:
```bash
cd /opt/pdf-ocr-hotfolder && sudo ./venv/bin/python -m pdf_ocr_hotfolder \
--check-config --config /etc/pdf-ocr-hotfolder/<instanz>.toml
```
Exit 0 = sauber, 1 = Warnungen, 2 = Fehler. Details:
[docs/UPDATE.md](docs/UPDATE.md#config-prüfung-per---check-config).
### Exit-Codes des Dienstes
| Exit | Bedeutung | Neustart durch systemd |
|------|-----------|------------------------|
| `0` | regulärer Stopp | — |
| `1` | nur bei `--once`: mindestens eine PDF fehlgeschlagen | — |
| `2` | Config- oder Preflight-Fehler | **nein** (`RestartPreventExitStatus=2`) — die Instanz bleibt sichtbar `failed` |
| `3` | Verzeichnis-Watch gestorben, es würden keine Dateien mehr erkannt | **ja**, genau dafür |
Vollständig: [docs/INSTALLATION.md](docs/INSTALLATION.md#exit-codes).
## Service-Verwaltung
```bash
# Eine bestimmte Instanz
sudo systemctl status pdf-ocr-hotfolder@kunde-a
sudo systemctl restart pdf-ocr-hotfolder@kunde-a
journalctl -u pdf-ocr-hotfolder@kunde-a -f
# Alle Instanzen
sudo systemctl status 'pdf-ocr-hotfolder@*'
sudo systemctl restart 'pdf-ocr-hotfolder@*'
journalctl -u 'pdf-ocr-hotfolder@*' --since today
```
Ein laufendes OCR darf beim Stoppen zu Ende laufen (`TimeoutStopSec=300`) — ein
`stop` kann deshalb pro Instanz bis zu 5 Minuten dauern.
## Architektur
```
┌──────────┐ watchdog ┌──────────────┐ ocrmypdf ┌──────────┐
│ Scanner │ ──────────────▶ │ incoming/ │ ─────────────▶ │ working/ │
└──────────┘ PDF-Datei └──────────────┘ (Library) └────┬─────┘
│
optional veraPDF
│
▼
┌──────────────┐
│ outgoing/ │
└──────┬───────┘
│
┌──────────────────────┼──────────────────────┐
▼ ▼ ▼
┌────────────┐ ┌────────────┐ ┌────────────┐
│ Nextcloud │ │ SFTP │ │ E-Mail │
│ (WebDAV) │ │ (paramiko) │ │ Notify │
└────────────┘ └────────────┘ └────────────┘
```
Beim Start wird `working/` zuerst durchsucht: was ein harter Stopp dort liegen
ließ, wird wiederaufgenommen; unvollständige OCR-Fragmente (`__ocr_*`) werden
gelöscht.
## Tests
```bash
pytest # 254 Tests
```
`ocrmypdf` muss dafür nicht installiert sein — der Import ist lazy und wird in
den Tests gemockt.
## Lizenz
MIT — © Sonith UG
---
**Version:** 0.7.2
**Repo:** https://gitea.sonith.de/sonith_ug/pdf-ocr-hotfolder