feat: Wiederaufnahme aus working/, --check-config, feldtauglicher Updater (v0.6.0)

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>
This commit is contained in:
2026-09-22 22:04:04 +02:00
parent 2062476252
commit 3e24aa2ecd
20 changed files with 3095 additions and 347 deletions
+115
View File
@@ -1,5 +1,120 @@
# Changelog
## [0.6.0] - 2026-09-22
### Added
- **Wiederaufnahme aus `working/` beim Start.** `process_pdf()` verschiebt das
Original vor dem OCR nach `working/`. Wurde der Dienst dort hart abgeschossen
(SIGKILL nach `TimeoutStopSec`), blieb die Datei liegen und wurde **nie wieder
angefasst** — stiller Datenverlust. `_scan_working()` greift sie jetzt beim
Start auf (vor `incoming/`), das OCR laeuft fuer sie neu. Liegt in `incoming/`
eine gleichnamige, andere Datei, bekommt die wiederaufgenommene einen
Zeitstempel angehaengt, damit sich beide nicht ueberschreiben. Liegt in
`working/` bereits eine andere Datei desselben Namens, bricht `process_pdf()`
fuer die neue ab und laesst sie in `incoming/` liegen, statt den laufenden
Vorgang stillschweigend zu ueberschreiben.
- **Unvollstaendige OCR-Fragmente werden geloescht.** Die Zwischendatei, in die
ocrmypdf schreibt, traegt jetzt das Praefix `__ocr_` (`OCR_TEMP_PREFIX`).
Bleibt so eine Datei nach einem harten Stopp in `working/` liegen, ist sie als
Eingabe unbrauchbar und als Ergebnis wertlos — sie wird beim Start mit einer
Warnung entfernt, damit sie niemand fuer ein fertiges PDF haelt.
- **`--check-config`**: prueft eine Instanz-Config, ohne irgendetwas zu
verarbeiten (hat Vorrang vor `--once`). Zeigt die vier Pfade inkl. Hinweis auf
noch fehlende Verzeichnisse, Sprachen, Seiten-Timeout und PDF/A-Level, faehrt
Preflight und `[output]`-Validierung und gibt alle Warnungen aus.
Exit **0** = sauber, **1** = nur Warnungen, **2** = Fehler (Dienst wuerde nicht
starten).
- **Legacy-Warnungen fuer `[ocr].timeout` und `[ocr].pdfa_level`.** Ein
`timeout >= 900` stammt fast sicher aus einer Config vor 0.4.0, wo der Wert ein
wirkungsloses Gesamt-Timeout mit Default 1800 war — seither sind es Sekunden
**pro Seite** (Richtwert 300). Ein gesetztes `pdfa_level` weist auf den
Ghostscript-Bug hin. Die Texte stehen nur in `config.py`
(`legacy_warnings()`), weil sie sowohl beim Dienststart ins Log gehen als auch
von `--check-config` ausgegeben werden.
- **Unbekannte Config-Keys werden gemeldet** statt still verworfen.
`_collect_unknown_keys()` sammelt Tippfehler (`[ocr].langauges`), Optionen aus
aelteren Versionen, unbekannte Sektionen und unbekannte Upload-/Notify-Targets
in `Config.unknown_keys`; die Meldung nennt den vollen Pfad. Warnung, kein
Fehler — der Dienst startet, der Eintrag tut nur nichts.
- **`TimeoutStopSec=300` in der Template-Unit**: ein laufendes OCR darf beim
Stoppen zu Ende laufen. Ein `systemctl stop` kann dadurch pro Instanz bis zu
5 Minuten dauern — das ist gewollt, ein SIGKILL wuerde den Durchlauf kosten.
- **Feste Pins in `requirements.txt`** (`ocrmypdf==16.13.0`, `watchdog==6.0.0`,
`requests==2.33.1`, `paramiko==4.0.0`). Ohne Pins zieht ein
`pip install --upgrade` beim Update ungefragt einen Major-Sprung ein; ocrmypdf
16 -> 17 wuerde alle Instanzen auf einmal reissen. Geprueft gegen Python 3.11
(Debian 12) und 3.13 (Debian 13), Wheels fuer beide vorhanden.
- 40 neue Tests (Wiederaufnahme aus `working/`, `--check-config`,
Config-Warnungen). Suite jetzt **135 Tests**.
### Changed
- **Die Betriebsdoku ist in drei Dokumente aufgeteilt.** Der README ist wieder
der Einstieg (Kurzbeschreibung, Features, Schnellstart, Verzeichnis-Layout,
Config-Ueberblick) und verlinkt:
- `docs/INSTALLATION.md` — Erstinstallation, Basis-Install vs. Instanz-Anlage,
die Abfragen pro Instanz, Multi-Instanz-Betrieb, LXC (Error 226/NAMESPACE),
Ghostscript auf Debian 12, Instanz manuell loeschen und die vollstaendige
**Konfigurationsreferenz**.
- `docs/UPDATE.md` — Ablauf von `update.sh`, was es nicht anfasst,
Backup-Inhalt/-Rechte/-Rotation, Rollback und dessen Grenzen,
`--check-config` mit den Exit-Codes und Config-Drift.
- `docs/OS-UPGRADE.md` — Debian-Major-Upgrade als eigener Ablauf.
`AI_AGENT_BRIEFING.md` bleibt der Agent-Kontext (Aufbau und Begruendungen) und
verweist fuer Ablaeufe auf die drei Dokumente, statt sie zu wiederholen.
Nichts wird doppelt gepflegt.
- **`update.sh` komplett ueberarbeitet** (`--help`, `--rebuild-venv`,
`set -Eeuo pipefail`):
- **venv-Health-Check und Neubau.** Geprueft werden Existenz, Lauffaehigkeit
des Interpreters, `major.minor` gegen das System-Python und `pyvenv.cfg`.
Passt etwas nicht — typisch nach einem Debian-Major-Upgrade, systemd meldet
dann `203/EXEC` —, wird die venv neu gebaut, auch ohne `--rebuild-venv`. Der
Neubau ist ganz oder gar nicht: alte venv weg sichern, neu bauen,
Requirements installieren, **erst bei Erfolg** die alte loeschen; scheitert
etwas, wird zurueckgerollt und hart abgebrochen. Scheitert pip an einem Pin,
nennt das Skript das gescheiterte Paket und den naechsten Schritt
("requirements.txt anheben").
- **apt-Sync auch beim Update.** Die Paketliste wird aus `install.sh`
extrahiert (einzige Quelle, Marken `BEGIN/END apt-packages`) und
installiert; nachinstallierte Tesseract-Sprachpakete bleiben unangetastet
(kein purge, kein autoremove). Fehlschlaege warnen nur.
- **Haertere Verifikation.** Nach dem Start prueft `verify_unit()` nicht nur
`is-active`, sondern auch `is-failed` und den Restart-Zaehler — ein
Crash-Loop galt bei `Type=simple` bisher als Erfolg. Die Zusammenfassung
stellt Soll gegen Ist und meldet eine **Regression** namentlich.
- **Vollstaendiges Backup.** Gesichert werden Code, alle Instanz-Configs, die
Template-Unit, alle Drop-ins und ein `pip-freeze.txt` der alten venv — ohne
venv und ohne Datenverzeichnisse. Weil die Configs Klartext-Passwoerter
enthalten, wird das Archiv mit `umask 077` erzeugt und auf `0600 root:root`
gesetzt, das Verzeichnis auf `700`. Rotation: die letzten 5 Archive bleiben.
- **ERR-Trap.** Bricht das Update ab (Fehler, Strg-C, `kill`), sagt das Skript,
ob auf der Platte schon getauscht wurde, startet die vorher laufenden
Instanzen wieder und nennt Backup-Datei und Rollback-Befehl.
- **Instanz-Erfassung** deckt jetzt auch `activating` und `failed` ab (ueber
`list-units --all`, `list-unit-files` und die vorhandenen Configs). Vorher
kaputte Instanzen werden mitgestartet, gelten aber erst als Erfolg, wenn sie
danach wirklich laufen; bewusst gestoppte bleiben gestoppt.
- **Config-Pruefung vor dem Start**: `--check-config` je Instanz, Exit 2 zaehlt
als Fehler (Update-Exit 1), Exit 1 wird als Warnung samt Nachstell-Befehl
ausgegeben. Kennt der installierte Code das Flag noch nicht, wird die
Pruefung uebersprungen und das Update laeuft weiter.
- **`install.sh` repariert eine kaputte venv.** Bisher reichte das blosse
Vorhandensein von `venv/`, um den Basis-Install zu ueberspringen — nach einem
Distributions-Upgrade hat der Installer damit gar nichts repariert. Jetzt wird
die venv gegen das System-Python geprueft und bei Drift nach
`venv.old-<timestamp>` gesichert und neu gebaut.
- Die apt-Paketliste steht als **einzige Quelle** in `install.sh` in der Funktion
`pdf_ocr_apt_packages()` zwischen den Marken `# --- BEGIN apt-packages` /
`# --- END apt-packages`. `update.sh` schneidet den Block heraus und wertet ihn
aus — Marken und Funktionsname duerfen sich nicht ohne Anpassung aendern.
### Fixed
- **Dateien in `working/` gingen nach einem harten Stopp still verloren.** Siehe
Wiederaufnahme oben — der Fall trat bei jedem SIGKILL waehrend eines OCR-Laufs
auf, also auch bei einem Update ohne `TimeoutStopSec`.
- **Tippfehler in Config-Keys fielen nicht auf.** `load_config()` filterte
stumm gegen die Dataclass-Annotationen; `[ocr].langauges` lief damit
wirkungslos mit. Jetzt gibt es eine Warnung mit vollem Key-Pfad.
## [0.5.0] - 2026-09-22
### Added