cd803a3dfe
Vor dem Rollout durchgesehen und die verbliebenen Stellen geschlossen, an denen etwas schiefgehen konnte, ohne dass es irgendwo sichtbar wurde. Datenverlust: - veraPDF: das in [verapdf].binary konfigurierte Programm wird im Preflight geprueft. Bisher galt bei falschem Pfad JEDE Datei als "nicht konform" — Ergebnis nach error/, Original geloescht (Default delete). run_verapdf() trennt jetzt ausserdem ein echtes FAIL-Urteil von einer Stoerung (VeraPdfUnavailable: nicht startbar, abgestuerzt, kein PASS/FAIL in der Ausgabe). Bei Stoerung wandern Original UND Ergebnis nach error/, das Original wird nicht entsorgt. - Gleichnamige Dateien wurden in outgoing/, error/ und beim Ordner-Upload mit abweichendem target kommentarlos ueberschrieben. Jetzt Zeitstempel daneben, mit Warnung; ProcessResult.output traegt den echten Pfad. Robustheit: - Kaputtes oder nicht lesbares TOML beim Start: Exit 2 statt Traceback. - RestartPreventExitStatus=2 in der Unit — Exit 2 (Config/Preflight) laeuft nicht mehr endlos neu, die Instanz bleibt sichtbar failed stehen. - Toter watchdog-Observer wird erkannt: Exit 3, systemd setzt den Watch neu auf. Vorher blieb die Unit "active" und verarbeitete nichts mehr. - Relative Pfade in [paths]/archive_dir/target sind ein Config-Fehler statt still unter /opt zu landen. - Fehler beim Archivieren entwertet den Durchlauf nicht mehr: Upload und Mail laufen, Sichtbarkeit ueber log.error + "OK mit Warnung"-Mail. - Nicht-PDFs in incoming/ werden beim Start-Scan gesammelt gemeldet. - Logging explizit nach stdout (die Doku versprach das schon). Struktur: - Neue lib/common.sh, von install.sh und update.sh gesourct. Die doppelte venv_is_healthy() gibt es nur noch einmal, in der gruendlichen Fassung — die schlanke in install.sh haette eine nach einem Distro-Sprung kaputte venv als gesund durchgewunken (nachgewiesen). - install.sh warnt in Containern, wenn systemd-journald nicht laeuft. Doku: Dateisystem-Festlegung (ext4/xfs/zfs, kein CIFS/NFS wegen inotify), Debian 13 in LXC auf Proxmox scheitert an journald (243/CREDENTIALS, AppArmor blockiert sd-mkdcreds) inkl. Abhilfe, echte Speicher-Messwerte, Exit-Code-Tabelle. 254 Tests gruen (vorher 152). Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
237 lines
7.8 KiB
Markdown
237 lines
7.8 KiB
Markdown
# 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
|
|
cd /opt/pdf-ocr-hotfolder
|
|
for f in /etc/pdf-ocr-hotfolder/*.toml; do
|
|
sudo ./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==17.4.1
|
|
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 — der nächste Sprung wäre ocrmypdf **17 auf 18**, und der
|
|
reißt sonst alle Instanzen auf einmal, und zwar im Moment des Updates, nicht zu
|
|
einem Zeitpunkt, den man sich ausgesucht hat.
|
|
|
|
> ⚠️ **ocrmypdf darf nicht unter 17 fallen.** Bis einschließlich 16.x prüft
|
|
> ocrmypdf die Ghostscript-Version auch dann, wenn gar kein PDF/A erzeugt wird —
|
|
> auf Debian 12 (Ghostscript 10.0.0) scheitert damit **jede** PDF, weil
|
|
> `skip_text = true` unser Default ist. Genau das war der Ausfall in 0.6.0.
|
|
> Hintergrund: [INSTALLATION.md](INSTALLATION.md#ghostscript-bug-auf-debian-12).
|
|
|
|
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. Das gilt
|
|
auch für die Abhängigkeiten, die ocrmypdf 17 zusätzlich mitbringt (`pydantic`,
|
|
`pypdfium2`, `fpdf2`, `uharfbuzz`).
|
|
|
|
**Beim Anheben:**
|
|
|
|
1. **Testmaschine benutzen** — nie direkt auf dem produktiven Hotfolder.
|
|
2. Prüfen, dass es für die neue Version auf **beiden** Python-Versionen fertige
|
|
Wheels gibt, sonst wird auf dem Zielsystem kompiliert:
|
|
```bash
|
|
pip install --dry-run --only-binary=:all: --python-version 3.11 \
|
|
--target /tmp/wheelcheck ocrmypdf==<version>
|
|
pip install --dry-run --only-binary=:all: --python-version 3.13 \
|
|
--target /tmp/wheelcheck ocrmypdf==<version>
|
|
```
|
|
3. Dort `update.sh --rebuild-venv` fahren, damit die Pakete wirklich frisch
|
|
aufgelöst werden.
|
|
4. `pytest` muss grün bleiben (254 Tests).
|
|
5. Eine echte PDF durchschieben — die Test-Suite mockt ocrmypdf, ein Major-Sprung
|
|
fällt dort also nicht auf. Der [Rauchtest](UPDATE.md#rauchtest) in `update.sh`
|
|
macht genau das automatisch.
|
|
6. 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.
|