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:
@@ -0,0 +1,219 @@
|
||||
# Debian-Major-Upgrade
|
||||
|
||||
Wie der Hotfolder ein Distributions-Upgrade übersteht (12 → 13, später 13 → 14).
|
||||
|
||||
Verwandte Dokumente: [README](../README.md) · [Installation](INSTALLATION.md) · [Update](UPDATE.md)
|
||||
|
||||
---
|
||||
|
||||
## Warum das ein eigener Ablauf ist
|
||||
|
||||
Die venv unter `/opt/pdf-ocr-hotfolder/venv/` hängt an der **Python-Version der
|
||||
Distribution**. Ein `apt full-upgrade` von Debian 12 auf 13 tauscht Python 3.11
|
||||
gegen 3.13 aus. Danach zeigt `venv/bin/python` auf einen Interpreter, den es so
|
||||
nicht mehr gibt — systemd quittiert den Start jeder Instanz mit `203/EXEC`, und
|
||||
auch wenn der Interpreter noch existiert, passen die installierten Pakete nicht
|
||||
mehr zum System-Python.
|
||||
|
||||
**Die venv muss nach dem Sprung neu gebaut werden.** Ein normales
|
||||
`sudo ./update.sh` genügt dafür nicht sicher genug — es gibt den ausdrücklichen
|
||||
Schalter `--rebuild-venv`.
|
||||
|
||||
---
|
||||
|
||||
## Der Ablauf
|
||||
|
||||
### 1. Vorher updaten
|
||||
|
||||
```bash
|
||||
cd /pfad/zum/repo
|
||||
git pull
|
||||
sudo ./update.sh
|
||||
```
|
||||
|
||||
Das bringt die Installation auf den aktuellen Stand und erzeugt vor allem ein
|
||||
**frisches Backup** inklusive `pip-freeze.txt` — die Liste der Paketversionen,
|
||||
die auf dem alten System liefen. Inhalt und Ort des Backups:
|
||||
[UPDATE.md](UPDATE.md#backup).
|
||||
|
||||
### 2. Instanzen stoppen
|
||||
|
||||
```bash
|
||||
sudo systemctl stop 'pdf-ocr-hotfolder@*'
|
||||
```
|
||||
|
||||
Während des Upgrades darf kein OCR laufen: Ghostscript, Tesseract und die
|
||||
Python-Pakete werden mitten im Betrieb ausgetauscht.
|
||||
|
||||
> **Geduld.** Die Unit hat `TimeoutStopSec=300`, damit ein laufendes OCR sauber
|
||||
> zu Ende kommt. **Ein Stop kann pro Instanz bis zu 5 Minuten dauern** — bei
|
||||
> mehreren Instanzen entsprechend länger. Nicht mit `kill -9` nachhelfen; ein
|
||||
> harter Stopp lässt das Original in `working/` liegen (der Dienst nimmt es beim
|
||||
> nächsten Start zwar wieder auf, aber der Durchlauf ist verloren).
|
||||
|
||||
Prüfen, dass wirklich alles steht:
|
||||
|
||||
```bash
|
||||
systemctl status 'pdf-ocr-hotfolder@*'
|
||||
```
|
||||
|
||||
### 3. Distribution upgraden
|
||||
|
||||
Der übliche Debian-Weg — Sources auf das neue Release umstellen, dann:
|
||||
|
||||
```bash
|
||||
sudo apt update
|
||||
sudo apt full-upgrade
|
||||
sudo reboot
|
||||
```
|
||||
|
||||
Die Instanzen sind `enabled` und starten nach dem Reboot mit; mit der alten venv
|
||||
scheitern sie (`203/EXEC`). Das ist erwartet und wird im nächsten Schritt
|
||||
behoben.
|
||||
|
||||
### 4. venv neu bauen
|
||||
|
||||
```bash
|
||||
cd /pfad/zum/repo
|
||||
git pull
|
||||
sudo ./update.sh --rebuild-venv
|
||||
```
|
||||
|
||||
`--rebuild-venv` erzwingt den Neubau. Der Rest des Updates läuft wie gewohnt
|
||||
(siehe [UPDATE.md](UPDATE.md#was-das-skript-tut--in-dieser-reihenfolge)) — die
|
||||
System-Pakete werden dabei ebenfalls abgeglichen, auch `python3-venv` für das
|
||||
neue Python.
|
||||
|
||||
Der Neubau ist **ganz oder gar nicht**:
|
||||
|
||||
1. Die alte venv wird nach `venv.old-<timestamp>` verschoben.
|
||||
2. `python3 -m venv` baut neu.
|
||||
3. Die Requirements werden installiert.
|
||||
4. **Erst bei Erfolg** wird die alte venv gelöscht.
|
||||
|
||||
### 5. Wenn ein Pin nicht mehr passt
|
||||
|
||||
Scheitert `pip install` an einer gepinnten Version, bricht das Skript ab und
|
||||
sagt genau, was los ist:
|
||||
|
||||
- die letzten 25 Zeilen der pip-Ausgabe,
|
||||
- das **gescheiterte Paket** ("Gescheitertes Paket: …"),
|
||||
- die Diagnose: "eine in requirements.txt fest gepinnte Version gibt es für
|
||||
Python \<version\> nicht (mehr)",
|
||||
- den nächsten Schritt: **requirements.txt anheben** und
|
||||
`update.sh --rebuild-venv` erneut laufen lassen.
|
||||
|
||||
**Die alte venv ist dann zurückgerollt** — es liegt keine halb gefüllte venv
|
||||
herum. Sie hängt zwar weiterhin am alten Interpreter und die Instanzen laufen
|
||||
damit nicht (das sagt das Skript auch), aber der Zustand ist eindeutig.
|
||||
|
||||
Also:
|
||||
|
||||
```bash
|
||||
# im Repo, auf einer Testmaschine
|
||||
vim requirements.txt # Version des genannten Pakets anheben
|
||||
pytest # Suite muss grün bleiben
|
||||
git commit -am 'requirements: <paket> auf <version> anheben'
|
||||
git push
|
||||
|
||||
# auf dem Zielsystem
|
||||
git pull
|
||||
sudo ./update.sh --rebuild-venv
|
||||
```
|
||||
|
||||
### 6. `--rebuild-venv` vergessen?
|
||||
|
||||
Halb so wild: `update.sh` prüft die venv auch ohne den Schalter und baut sie bei
|
||||
Versions-Drift von selbst neu. Geprüft wird
|
||||
|
||||
- ob das Verzeichnis und `venv/bin/python` überhaupt existieren,
|
||||
- ob der Interpreter der venv noch **läuft** (toter Symlink nach dem Upgrade),
|
||||
- ob seine `major.minor` zum System-`python3` passt,
|
||||
- ob `pyvenv.cfg` dieselbe Version nennt wie der Interpreter.
|
||||
|
||||
Stimmt eines davon nicht, nennt das Skript den Grund und baut neu.
|
||||
`--rebuild-venv` ist also nicht der einzige, aber der **ausdrückliche** Weg —
|
||||
und der, den man nach einem Distributions-Upgrade nimmt, statt sich auf die
|
||||
Erkennung zu verlassen.
|
||||
|
||||
---
|
||||
|
||||
## Danach prüfen
|
||||
|
||||
**Laufen alle Instanzen?**
|
||||
|
||||
```bash
|
||||
systemctl status 'pdf-ocr-hotfolder@*'
|
||||
```
|
||||
|
||||
`update.sh` hat das schon verifiziert (Wartezeit, `is-failed`, Crash-Loop) und
|
||||
in der Zusammenfassung Soll gegen Ist gestellt — ein Exit 0 heißt, dass jede
|
||||
Instanz, die vorher lief, auch wieder läuft.
|
||||
|
||||
**Sind die Configs sauber?**
|
||||
|
||||
```bash
|
||||
for f in /etc/pdf-ocr-hotfolder/*.toml; do
|
||||
sudo /opt/pdf-ocr-hotfolder/venv/bin/python -m pdf_ocr_hotfolder \
|
||||
--check-config --config "$f"
|
||||
done
|
||||
```
|
||||
|
||||
Exit 0/1/2 und was bei Warnungen zu tun ist:
|
||||
[UPDATE.md](UPDATE.md#config-prüfung-per---check-config).
|
||||
|
||||
**Läuft eine echte PDF durch?**
|
||||
|
||||
```bash
|
||||
cp test.pdf /var/lib/pdf-ocr-hotfolder/<instanz>/incoming/
|
||||
journalctl -u pdf-ocr-hotfolder@<instanz> -f
|
||||
```
|
||||
|
||||
Im `outgoing/` muss das OCR-PDF liegen, im Journal steht `OCR done`. Das ist der
|
||||
einzige Test, der die neue venv **und** die neuen System-Binaries (Tesseract,
|
||||
Ghostscript) wirklich anfasst — `systemctl status` sagt darüber nichts.
|
||||
|
||||
**Ghostscript-Version auf dem neuen Release ansehen:**
|
||||
|
||||
```bash
|
||||
gs --version
|
||||
```
|
||||
|
||||
Auf Debian 13 ist der Ghostscript-Bug aus Debian 12 kein Thema mehr; ein
|
||||
bookworm-backports-Eintrag unter `/etc/apt/sources.list.d/` gehört nach dem
|
||||
Upgrade entfernt. Hintergrund:
|
||||
[INSTALLATION.md](INSTALLATION.md#ghostscript-bug-auf-debian-12).
|
||||
|
||||
---
|
||||
|
||||
## Pins in `requirements.txt`
|
||||
|
||||
Die Python-Abhängigkeiten sind **bewusst fest gepinnt**:
|
||||
|
||||
```
|
||||
ocrmypdf==16.13.0
|
||||
watchdog==6.0.0
|
||||
requests==2.33.1
|
||||
paramiko==4.0.0
|
||||
```
|
||||
|
||||
Ohne Pins würde ein `pip install --upgrade` bei jedem Update ungefragt eine neue
|
||||
Major-Version ziehen — ein Sprung von ocrmypdf **16 auf 17** reißt sonst alle
|
||||
Instanzen auf einmal, und zwar im Moment des Updates, nicht zu einem Zeitpunkt,
|
||||
den man sich ausgesucht hat.
|
||||
|
||||
Die aktuellen Pins sind gegen Python 3.11 (Debian 12) und 3.13 (Debian 13)
|
||||
geprüft; für beide gibt es fertige Wheels, es wird nichts kompiliert.
|
||||
|
||||
**Beim Anheben:**
|
||||
|
||||
1. **Testmaschine benutzen** — nie direkt auf dem produktiven Hotfolder.
|
||||
2. Dort `update.sh --rebuild-venv` fahren, damit die Pakete wirklich frisch
|
||||
aufgelöst werden.
|
||||
3. `pytest` muss grün bleiben (135 Tests).
|
||||
4. Eine echte PDF durchschieben — die Test-Suite mockt ocrmypdf, ein Major-Sprung
|
||||
fällt dort also nicht auf.
|
||||
5. Erst dann committen und auf die produktiven Systeme geben.
|
||||
|
||||
Der ocrmypdf-Sprung 16 → 17 ist ein **Major-Sprung** und gehört in einen eigenen
|
||||
Vorgang mit eigenem Test, nicht in ein OS-Upgrade.
|
||||
Reference in New Issue
Block a user