04dc3c7b72
Das Paket ist nicht pip-installiert, sondern liegt unter /opt/pdf-ocr-hotfolder
und wird nur ueber das Arbeitsverzeichnis gefunden. Der Hinweis, den update.sh
in der Zusammenfassung ausgibt, lief deshalb so wie gedruckt nicht
("No module named pdf_ocr_hotfolder"). Gleiches galt fuer die Beispiele in
README.md, docs/INSTALLATION.md, docs/UPDATE.md und docs/OS-UPGRADE.md.
Gefunden beim Update-Test v0.3.1 -> v0.6.1 auf CT 200 (Debian 12).
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
403 lines
17 KiB
Markdown
403 lines
17 KiB
Markdown
# Update
|
||
|
||
Aktualisieren des OCR-Tools mit `update.sh` — Code, venv, System-Pakete und
|
||
systemd-Unit.
|
||
|
||
Verwandte Dokumente: [README](../README.md) · [Installation](INSTALLATION.md) · [Debian-Major-Upgrade](OS-UPGRADE.md)
|
||
|
||
> Für ein **Debian-Major-Upgrade** (12 → 13) gilt ein eigener Ablauf — die venv
|
||
> muss danach neu gebaut werden. Siehe [OS-UPGRADE.md](OS-UPGRADE.md).
|
||
|
||
---
|
||
|
||
## Der Ablauf
|
||
|
||
```bash
|
||
cd /pfad/zum/repo
|
||
git pull
|
||
sudo ./update.sh
|
||
```
|
||
|
||
```
|
||
sudo ./update.sh --help # Optionen anzeigen
|
||
sudo ./update.sh --rebuild-venv # venv zwingend neu bauen (nach dist-upgrade)
|
||
sudo ./update.sh --no-smoke-test # ohne Rauchtest durchlaufen
|
||
```
|
||
|
||
`update.sh` muss aus dem Repo laufen. Findet es sich nicht selbst im Repo, liest
|
||
es den gespeicherten Pfad aus `/opt/pdf-ocr-hotfolder/.repo_path` — **das Repo
|
||
muss also liegen bleiben**, das Tool kopiert daraus.
|
||
|
||
## Was das Skript tut — in dieser Reihenfolge
|
||
|
||
| # | Schritt | Anmerkung |
|
||
|---|---------|-----------|
|
||
| 1 | **Instanzen erfassen** | aktiv / kaputt / bewusst gestoppt, siehe [unten](#instanz-erfassung) |
|
||
| 2 | **System-Pakete abgleichen** | Liste wird aus `install.sh` extrahiert, `apt-get install` ist idempotent |
|
||
| 3 | **venv prüfen** | passt sie noch zum System-Python? Läuft **vor** dem Stoppen, damit man es früh sieht |
|
||
| 4 | **Instanzen stoppen** | nur die, die vorher liefen oder kaputt waren |
|
||
| 5 | **Backup** | [Inhalt und Ort](#backup) |
|
||
| 6 | **Code kopieren** | `pdf_ocr_hotfolder/`, `requirements.txt`, `VERSION`, `config.example.toml`, `.repo_path` |
|
||
| 7 | **Dependencies** | `pip install --upgrade -r requirements.txt` — oder venv-Neubau, falls nötig. Versionssprünge der gepinnten Pakete werden [benannt](#versionssprünge-der-kernabhängigkeiten) |
|
||
| 8 | **systemd-Units** | Template-Unit aus dem Repo, LXC-Drop-in nachziehen, `daemon-reload` |
|
||
| 9 | **Berechtigungen** | Code gehört dem primären User (i.d.R. `pdfocr`) |
|
||
| 10 | **Configs prüfen** | `--check-config` je Instanz, siehe [unten](#config-prüfung-per---check-config) |
|
||
| 11 | **Instanzen starten + verifizieren** | mit Wartezeit und Crash-Loop-Erkennung |
|
||
| 12 | **Rauchtest** | eine Test-PDF durch die echte Pipeline, siehe [unten](#rauchtest) |
|
||
| 13 | **Zusammenfassung** | Soll gegen Ist |
|
||
|
||
Ab Schritt 2 gilt: **System-Pakete werden auch beim Update nachgezogen**, nicht
|
||
nur bei der Installation. Bereits installierte Tesseract-Sprachpakete bleiben
|
||
unangetastet — es gibt kein `purge` und kein `autoremove`.
|
||
|
||
Die Schritte 1–3 verändern nichts auf der Platte. Erst ab Schritt 4 wird
|
||
angefasst.
|
||
|
||
## Was das Skript NICHT anfasst
|
||
|
||
| Bleibt unverändert | Warum |
|
||
|--------------------|-------|
|
||
| `/etc/pdf-ocr-hotfolder/*.toml` | Instanz-Configs werden **nie** überschrieben — weder neu geschrieben noch gemerged. Neue Config-Keys greifen über ihre Defaults, siehe [Config-Drift](#config-drift-nach-einem-update) |
|
||
| `/var/lib/pdf-ocr-hotfolder/…` | Datenverzeichnisse (`incoming`, `working`, `outgoing`, `error`, Archiv) — nichts wird verschoben oder gelöscht |
|
||
| Instanz-Drop-ins (`…@<instanz>.service.d/user.conf`) | Service-User pro Instanz bleibt |
|
||
| Nachinstallierte Tesseract-Sprachpakete | werden nicht entfernt |
|
||
| Bewusst gestoppte Instanzen | bleiben gestoppt |
|
||
|
||
Neue Config-Optionen muss man also selbst nachtragen, wenn man sie nutzen will.
|
||
`config.example.toml` liegt nach dem Update aktuell unter
|
||
`/opt/pdf-ocr-hotfolder/config.example.toml` und ist die Vorlage dafür.
|
||
|
||
---
|
||
|
||
## Instanz-Erfassung
|
||
|
||
Eine Instanz gilt als bekannt, wenn sie **irgendwo** auftaucht: als geladene
|
||
Unit (`systemctl list-units --all`, also auch `activating` und `failed`), in
|
||
`list-unit-files` (enabled), oder als Config unter `/etc/pdf-ocr-hotfolder/`.
|
||
Damit fällt auch eine Instanz auf, die gerade in einem Crash-Loop hängt.
|
||
|
||
Aus dem Zustand vor dem Update ergeben sich drei Gruppen:
|
||
|
||
| Gruppe | Zustand vorher | Behandlung |
|
||
|--------|----------------|------------|
|
||
| **lief sauber** | `active`, nicht `failed` | wird gestoppt und muss nachher wieder laufen — sonst ist das eine **Regression** und das Update meldet Exit 1 |
|
||
| **war kaputt** | `failed`, `activating`, `reloading`, `deactivating` | wird mitgestartet; läuft sie danach, meldet das Skript "vorher kaputt, läuft jetzt". Läuft sie weiterhin nicht, Exit 1, aber ohne Regressions-Alarm |
|
||
| **bewusst gestoppt** | `inactive` und nicht `failed` | bleibt gestoppt |
|
||
|
||
### Verifikation nach dem Start
|
||
|
||
Ein `systemctl start` sagt bei `Type=simple` noch nichts. Deshalb prüft
|
||
`verify_unit()` nach einer Wartezeit (`VERIFY_WAIT`, Default 6 s) drei Dinge:
|
||
|
||
1. `is-active` muss `active` sein
|
||
2. `is-failed` darf nicht `failed` melden
|
||
3. `NRestarts` darf nicht gestiegen sein — das entlarvt den Crash-Loop, der sich
|
||
hinter einem sofortigen "active" versteckt
|
||
|
||
Vorher wird `systemctl reset-failed` gefahren, damit der alte Zustand die
|
||
Prüfung nicht verfälscht. Scheitert eine Instanz, nennt das Skript direkt den
|
||
passenden `journalctl`-Aufruf.
|
||
|
||
Die Zusammenfassung stellt am Ende **Soll gegen Ist** und liefert Exit 1, wenn
|
||
eine Instanz fehlt, eine Config einen Fehler hat oder eine vorher kaputte
|
||
Instanz immer noch kaputt ist.
|
||
|
||
---
|
||
|
||
## Backup
|
||
|
||
Vor dem ersten Eingriff auf der Platte schreibt `update.sh` ein Archiv:
|
||
|
||
```
|
||
/var/backups/pdf-ocr-hotfolder/backup-YYYYmmdd-HHMMSS.tar.gz
|
||
```
|
||
|
||
| Enthalten | Nicht enthalten |
|
||
|-----------|-----------------|
|
||
| `/opt/pdf-ocr-hotfolder/` (Code) | die **venv** (`venv/`, `venv.old-*`) |
|
||
| `/etc/pdf-ocr-hotfolder/` (alle Instanz-Configs) | die **Datenverzeichnisse** `/var/lib/pdf-ocr-hotfolder/` |
|
||
| Template-Unit `pdf-ocr-hotfolder@.service` | `__pycache__`, `*.pyc` |
|
||
| alle Drop-in-Verzeichnisse `…@*.service.d` | |
|
||
| `pip-freeze.txt` — `pip freeze` der **alten** venv plus Zeitstempel und Versionssprung | |
|
||
|
||
`pip-freeze.txt` ist die Versicherung für den Fall, dass ein neuer Pin Ärger
|
||
macht: man sieht schwarz auf weiß, welche Paketversionen vorher liefen.
|
||
|
||
**Rechte:** Das Archiv enthält die Instanz-Configs und damit **Klartext-Passwörter**
|
||
(SMTP, Nextcloud, SFTP). Es wird deshalb mit `umask 077` erzeugt und danach auf
|
||
`0600 root:root` gesetzt; das Verzeichnis selbst bekommt `700`. Backups nicht in
|
||
Tickets anhängen und nicht in allgemein lesbare Pfade kopieren.
|
||
|
||
**Rotation:** Es werden die **letzten 5** Archive behalten (`BACKUP_KEEP`),
|
||
ältere löscht das Skript nach dem Schreiben des neuen. Was entfernt wurde, steht
|
||
im Log.
|
||
|
||
Scheitert das Backup (typisch: volle Platte), bricht das Update ab, **bevor**
|
||
etwas getauscht wurde.
|
||
|
||
---
|
||
|
||
## Rollback
|
||
|
||
Das Backup-Archiv ist wurzelrelativ gepackt und lässt sich direkt zurückspielen:
|
||
|
||
```bash
|
||
sudo systemctl stop 'pdf-ocr-hotfolder@*'
|
||
sudo tar -xzf /var/backups/pdf-ocr-hotfolder/backup-YYYYmmdd-HHMMSS.tar.gz -C /
|
||
sudo systemctl daemon-reload
|
||
sudo systemctl start 'pdf-ocr-hotfolder@<instanz>'
|
||
```
|
||
|
||
Das Skript nennt diesen Befehl mit dem konkreten Archivnamen selbst — sowohl
|
||
beim Abbruch als auch bei einer erkannten Regression.
|
||
|
||
### Grenzen des Rollbacks
|
||
|
||
Ein Rollback ist ein **Overlay**, kein exaktes Zurücksetzen:
|
||
|
||
- **Die venv ist nicht im Backup.** Wurde sie beim Update neu gebaut oder
|
||
hat `pip install --upgrade` Pakete angehoben, holt das Rollback den alten
|
||
Stand der Pakete **nicht** zurück. Dafür ist `pip-freeze.txt` aus dem Archiv
|
||
da: die dort genannten Versionen lassen sich von Hand wiederherstellen
|
||
(`venv/bin/pip install -r …`).
|
||
- **Dateien, die es vorher nicht gab, bleiben liegen.** `tar -x` legt nur an und
|
||
überschreibt; es löscht nichts. Eine mit dem neuen Stand hinzugekommene Datei
|
||
im Code-Verzeichnis überlebt das Rollback. Sauberer ist deshalb
|
||
`rm -rf /opt/pdf-ocr-hotfolder/pdf_ocr_hotfolder` **vor** dem Entpacken.
|
||
- **Die Datenverzeichnisse sind nicht im Backup** — gewollt. Ein Rollback
|
||
verändert keine PDFs, weder in `incoming/` noch in `error/`.
|
||
- **System-Pakete werden nicht zurückgenommen.** Ein per apt angehobenes
|
||
Ghostscript oder ein neues Sprachpaket bleibt.
|
||
|
||
Der einfachere Weg zurück ist deshalb in den meisten Fällen: alten Stand im Repo
|
||
auschecken (`git checkout v<version>`) und `sudo ./update.sh` erneut fahren.
|
||
|
||
### Der ERR-Trap
|
||
|
||
`update.sh` läuft mit `set -Eeuo pipefail` und hat ab dem Moment, in dem
|
||
Instanzen gestoppt werden, einen Trap auf `ERR`, `INT` und `TERM`. Bricht
|
||
irgendetwas ab — Fehler, Strg-C, `kill` —, dann:
|
||
|
||
1. sagt das Skript laut, bei welchem Exit-Code und in welcher Zeile es aufhörte,
|
||
2. sagt es, **ob auf der Platte schon getauscht wurde** oder ob der alte Stand
|
||
unverändert ist,
|
||
3. **startet es die vorher laufenden Instanzen wieder** und meldet jede einzeln,
|
||
4. nennt es das Backup-Archiv und den Rollback-Befehl — oder sagt ausdrücklich,
|
||
dass noch kein Backup geschrieben wurde.
|
||
|
||
Ein abgebrochenes Update lässt also keinen Hotfolder stumm gestoppt zurück.
|
||
|
||
---
|
||
|
||
## Versionssprünge der Kernabhängigkeiten
|
||
|
||
`update.sh` misst die Versionen der in `requirements.txt` gepinnten Pakete
|
||
**vor** und **nach** `pip install` und benennt jede Änderung:
|
||
|
||
```
|
||
[WARN] DOWNGRADE: ocrmypdf: 17.4.1 -> 16.13.0
|
||
[INFO] Upgrade: watchdog: 5.0.0 -> 6.0.0
|
||
[INFO] Neu: requests 2.33.1
|
||
```
|
||
|
||
Beides steht auch noch einmal in der Abschluss-Zusammenfassung, weil es im
|
||
Fließtext zwischen den pip-Ausgaben untergeht.
|
||
|
||
**Downgrades sind der interessante Fall.** Sie entstehen, wenn ein Pin in
|
||
`requirements.txt` gesenkt wurde. Genau so ist der Totalausfall in 0.6.0
|
||
entstanden: `ocrmypdf` wurde von 17.4.1 auf 16.13.0 heruntergezogen, das Update
|
||
lief mit Exit 0 durch, der Dienst meldete `active` — und jede PDF landete in
|
||
`error/`. Sichtbar war davon nichts außer `[INFO] Dependencies ok ✓`.
|
||
|
||
War ein Downgrade nicht beabsichtigt: Pin korrigieren und
|
||
`sudo ./update.sh --rebuild-venv` erneut fahren.
|
||
|
||
---
|
||
|
||
## Rauchtest
|
||
|
||
Nach dem Start schiebt `update.sh` pro Instanz eine winzige Test-PDF durch die
|
||
**echte** Pipeline und prüft, ob sie in `outgoing/` ankommt.
|
||
|
||
Das ist der Schritt, den 0.6.0 gefehlt hat: `systemctl` sagt `active`,
|
||
`--check-config` sagt `Preflight ok` — und trotzdem scheitert jede einzelne
|
||
Datei. Ein laufender Dienst ist eben kein Beleg dafür, dass er etwas
|
||
verarbeitet.
|
||
|
||
- Die Test-PDF steckt als base64 **im Skript** (694 Bytes, eine Seite). Es
|
||
braucht also kein Pillow, kein `gs` und kein `convert` auf dem Zielsystem.
|
||
- Der Dateiname ist eindeutig (`__smoketest_update_<zeitstempel>_<pid>.pdf`) und
|
||
kann mit keiner Kundendatei kollidieren.
|
||
- Wartezeit: `SMOKE_TIMEOUT` Sekunden (Standard 90), danach gilt der Test als
|
||
durchgefallen. Das Skript hängt nicht.
|
||
|
||
### Aufräumen
|
||
|
||
Test-PDF **und** Ergebnis werden danach restlos entfernt — in jedem Ausgang,
|
||
auch bei Fehlschlag und Timeout. Angefasst werden dabei ausschließlich Dateien
|
||
mit dem Testnamen, in `incoming/`, `working/` (inkl. `__ocr_`-Zwischendatei),
|
||
`outgoing/`, `error/` und im Archivverzeichnis. In keinem dieser Verzeichnisse
|
||
bleibt etwas vom Test liegen.
|
||
|
||
### Wann der Rauchtest übersprungen wird
|
||
|
||
Hat eine Instanz ein aktives Ziel, würde die Testdatei **nach außen** gehen —
|
||
im Zweifel zum Kunden. Solche Instanzen werden mit klarer Meldung übersprungen:
|
||
|
||
| Übersprungen bei | Grund |
|
||
|------------------|-------|
|
||
| `[upload.nextcloud].enabled = true` | Testdatei landete in der Nextcloud |
|
||
| `[upload.sftp].enabled = true` | Testdatei landete auf dem SFTP-Ziel |
|
||
| `[upload.folder]` mit gesetztem `target` | Zielordner liegt außerhalb von `outgoing/`, oft eine Kundenfreigabe |
|
||
| `[notify.email].enabled = true` | löst eine Benachrichtigungs-Mail aus |
|
||
|
||
`[upload.folder]` **ohne** `target` schreibt nach `outgoing/` und ist damit
|
||
harmlos — dort läuft der Test normal.
|
||
|
||
Für diese Instanzen bleibt der manuelle Weg: eine eigene PDF in `incoming/`
|
||
legen und `journalctl -u pdf-ocr-hotfolder@<instanz> -f` mitlesen.
|
||
|
||
### Wenn der Rauchtest fehlschlägt
|
||
|
||
Der Rauchtest setzt den **Exit-Code** des Updates auf 1 und nennt den
|
||
Journal-Befehl:
|
||
|
||
```
|
||
[ERROR] RAUCHTEST FEHLGESCHLAGEN: kunde1
|
||
[ERROR] Diese Instanzen laufen, verarbeiten aber keine PDFs.
|
||
[ERROR] Es wurde NICHT zurueckgerollt. Journal ansehen:
|
||
[ERROR] journalctl -u pdf-ocr-hotfolder@kunde1.service -n 80 --no-pager
|
||
```
|
||
|
||
**Es wird nichts automatisch zurückgerollt.** Der Code ist getauscht, die
|
||
Instanzen laufen. Rollback nur von Hand und nur bewusst — siehe
|
||
[Rollback](#rollback).
|
||
|
||
Abschalten: `sudo ./update.sh --no-smoke-test`. Dann fällt ein Totalausfall
|
||
erst der ersten echten Kundendatei auf.
|
||
|
||
---
|
||
|
||
## Config-Prüfung per `--check-config`
|
||
|
||
Nach dem Code-Update und vor dem Start prüft `update.sh` jede Instanz-Config mit
|
||
dem neuen Code:
|
||
|
||
```bash
|
||
cd /opt/pdf-ocr-hotfolder && ./venv/bin/python -m pdf_ocr_hotfolder \
|
||
--check-config --config /etc/pdf-ocr-hotfolder/<instanz>.toml
|
||
```
|
||
|
||
`--check-config` verarbeitet nichts, es hat sogar Vorrang vor `--once`. Es lädt
|
||
die Config, zeigt die vier Pfade (inkl. Hinweis, falls ein Verzeichnis noch
|
||
fehlt), Sprachen, Seiten-Timeout, PDF/A-Level, `skip_text` sowie die
|
||
installierte ocrmypdf- und Ghostscript-Version, fährt den Preflight
|
||
(`tesseract`, `gs`, und die Ghostscript-Version gegen die tatsächliche
|
||
ocrmypdf-Bedingung — siehe [Rauchtest](#rauchtest) und
|
||
[INSTALLATION.md](INSTALLATION.md#ghostscript-bug-auf-debian-12)) und
|
||
validiert die `[output]`-Sektion.
|
||
|
||
| Exit | Bedeutung | Was der Admin tun soll |
|
||
|------|-----------|------------------------|
|
||
| **0** | Config sauber | nichts |
|
||
| **1** | Config nutzbar, aber mit **Warnungen** | Kein Abbruchgrund, der Dienst läuft. Die Warnungen aber **nachziehen** — sie nennen entweder einen Key, dessen Bedeutung sich geändert hat, oder einen Eintrag, der ins Leere läuft (s. [Config-Drift](#config-drift-nach-einem-update)) |
|
||
| **2** | Config **unbrauchbar** — der Dienst würde nicht starten | Sofort korrigieren. Die Instanz gilt als **nicht** erfolgreich aktualisiert, das Update endet mit Exit 1 |
|
||
|
||
Kennt der installierte Code `--check-config` noch nicht (Update von einem Stand
|
||
vor 0.6.0), erkennt `update.sh` das an der argparse-Meldung, überspringt die
|
||
Prüfung mit einer Warnung und läuft weiter.
|
||
|
||
Dieselben Warnungen schreibt der Dienst beim Start ins Journal — man sieht sie
|
||
also auch ohne Update:
|
||
|
||
```bash
|
||
journalctl -u pdf-ocr-hotfolder@<instanz> | grep 'Config-Warnung'
|
||
```
|
||
|
||
---
|
||
|
||
## Config-Drift nach einem Update
|
||
|
||
Instanz-Configs werden nie überschrieben. Das ist bequem, hat aber zwei
|
||
Konsequenzen.
|
||
|
||
**Neue Keys sind unkritisch.** Fehlt ein Key, greift der Default aus der
|
||
Dataclass — genau der Wert, der auch in `config.example.toml` steht. Eine Config
|
||
von 0.2.x läuft unter 0.6.0 weiter, ohne dass etwas nachgetragen werden muss.
|
||
Wer die neue Option nutzen will, trägt sie nach; die aktuelle Vorlage liegt
|
||
nach jedem Update unter `/opt/pdf-ocr-hotfolder/config.example.toml`.
|
||
|
||
Zwei Fälle brauchen aber Handarbeit — beide meldet `--check-config` von selbst:
|
||
|
||
### `[ocr].timeout` — Bedeutung geändert seit 0.4.0
|
||
|
||
Vor 0.4.0 war `timeout` ein **Gesamt**-Timeout pro PDF mit Default `1800` — und
|
||
wurde nirgends ausgewertet, war also wirkungslos. Seit 0.4.0 geht der Wert als
|
||
`tesseract_timeout` an ocrmypdf und ist damit das Limit **pro Seite**; ein
|
||
Dokument-Timeout kennt ocrmypdf nicht.
|
||
|
||
Wer den Altwert `1800` stehen hat, gibt Tesseract jetzt **30 Minuten je Seite**.
|
||
Ab `900` meldet `--check-config` deshalb eine Warnung. **Richtwert: 300.**
|
||
|
||
```toml
|
||
[ocr]
|
||
timeout = 300 # Sekunden pro SEITE
|
||
```
|
||
|
||
`0` heißt "kein eigenes Limit": der Wert wird dann gar nicht durchgereicht, weil
|
||
ocrmypdf `tesseract_timeout=0` als "OCR komplett überspringen" interpretiert.
|
||
|
||
### `[ocr].pdfa_level` — sollte leer sein
|
||
|
||
`pdfa_level` gehört auf `""` (reines PDF, kein PDF/A). Ist es gesetzt, warnt
|
||
`--check-config`, weil Ghostscript 10.0.0–10.02.0 — der Debian-12-Default — in
|
||
Kombination mit `skip_text` von ocrmypdf abgelehnt wird. Der Preflight bricht in
|
||
dem Fall mit Exit 2 ab; ab Ghostscript 10.02.1 ist PDF/A unproblematisch.
|
||
|
||
> **`pdfa_level = ""` allein ist kein Schutz gegen den Ghostscript-Bug.** Das
|
||
> gilt erst zusammen mit **ocrmypdf ≥ 17**. Bis ocrmypdf 16.x läuft dieselbe
|
||
> Prüfung auch ohne PDF/A, und dann scheitert mit `skip_text = true` jede
|
||
> einzelne Datei. Die Entwarnung hängt also an der ocrmypdf-Version.
|
||
> `requirements.txt` pinnt darum 17.x, und der Preflight prüft beides zusammen.
|
||
> Hintergrund: [INSTALLATION.md](INSTALLATION.md#ghostscript-bug-auf-debian-12).
|
||
|
||
### Unbekannte Keys
|
||
|
||
Einträge, die zu keiner Sektion und keinem Key gehören, werden beim Laden
|
||
ignoriert — aber **gemeldet**, mit Pfad (`[ocr].langauges`). Das deckt
|
||
Tippfehler und Optionen aus älteren Versionen ab. Die Meldung ist eine Warnung,
|
||
kein Fehler: der Dienst startet, der Eintrag tut nur nichts.
|
||
|
||
---
|
||
|
||
## Nach dem Update prüfen
|
||
|
||
```bash
|
||
systemctl status 'pdf-ocr-hotfolder@*'
|
||
journalctl -u 'pdf-ocr-hotfolder@*' --since '5 min ago'
|
||
```
|
||
|
||
Und einmal eine Test-PDF durchschieben:
|
||
|
||
```bash
|
||
cp test.pdf /var/lib/pdf-ocr-hotfolder/<instanz>/incoming/
|
||
journalctl -u pdf-ocr-hotfolder@<instanz> -f
|
||
```
|
||
|
||
Im `outgoing/` muss das OCR-PDF auftauchen.
|
||
|
||
---
|
||
|
||
## Wiederaufnahme aus `working/`
|
||
|
||
Beim Start greift der Dienst nicht nur `incoming/` auf, sondern zuerst
|
||
`working/`: Dateien, die ein harter Stopp dort liegen ließ, werden
|
||
wiederaufgenommen und das OCR läuft für sie neu. Unvollständige Zwischendateien
|
||
des abgebrochenen Laufs (Präfix `__ocr_`) werden dabei gelöscht.
|
||
|
||
Für das Update heißt das: ein `systemctl stop` mitten im OCR kostet den
|
||
angefangenen Durchlauf, aber keine Datei. Die Unit gibt einem laufenden OCR
|
||
`TimeoutStopSec=300` Zeit, sauber fertig zu werden — **ein Stop kann damit pro
|
||
Instanz bis zu 5 Minuten dauern.** Bei mehreren Instanzen entsprechend länger;
|
||
`update.sh` stoppt sie nacheinander.
|