# 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 ``` > **`sudo` oder direkt als `root`.** `update.sh` braucht root-Rechte, nicht > `sudo`. Wer als `root` arbeitet — im Proxmox-Debian-Standard-Template der > Normalfall, dort ist `sudo` gar nicht installiert —, lässt das `sudo` bei > jedem Befehl dieser Seite einfach weg: `./update.sh`. Ebenso setzt `git pull` > ein installiertes **`git`** voraus; siehe > [INSTALLATION.md](INSTALLATION.md#pakete-die-auf-einem-frischen-system-fehlen-können). `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. `install.sh` und `update.sh` teilen sich seit 0.7.0 die Datei `lib/common.sh` (Log-Funktionen, Root-Prüfung, Layout-Pfade, apt-Paketliste, venv-Prüfung). `update.sh` sourct bevorzugt die Fassung **neben sich** im Repo und fällt auf die installierte unter `/opt/pdf-ocr-hotfolder/lib/` zurück; fehlt sie überall, bricht es sofort ab statt mitten im Lauf. Die apt-Paketliste schneidet es zusätzlich noch einmal per `sed` aus der Repo-Fassung heraus — beim Update soll die neue Liste gelten, nicht die eventuell ältere installierte Kopie. ## 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 `lib/common.sh` des Repos 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/`, **`lib/`**, `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 (`…@.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 **inkl. `lib/`**) | 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` | | | `opt/pdf-ocr-hotfolder/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. Sie liegt im Archiv unter `opt/pdf-ocr-hotfolder/` und landet beim Entpacken nach `/` folglich als `/opt/pdf-ocr-hotfolder/pip-freeze.txt` — also neben der Installation statt im Wurzelverzeichnis. Ein Code-Tausch beim nächsten Update löscht sie nicht (dort fliegen nur `pdf_ocr_hotfolder/` und `lib/`). **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@kunde-a sudo systemctl start pdf-ocr-hotfolder@kunde-b # jede Instanz einzeln! ``` Das Skript nennt diesen Befehl mit dem konkreten Archivnamen selbst — sowohl beim Abbruch als auch bei einer erkannten Regression. > ⚠️ **`start` kann kein Glob.** `systemctl stop 'pdf-ocr-hotfolder@*'` trifft > alle laufenden Instanzen, weil systemd dafür die **bereits geladenen** Units > auflösen kann. Beim Starten gibt es nichts aufzulösen: > `systemctl start 'pdf-ocr-hotfolder@*'` startet eine Instanz mit dem > wörtlichen Namen `*` — und scheitert. **Bei mehreren Instanzen muss jede > einzeln gestartet werden.** Welche es sind: > `ls /etc/pdf-ocr-hotfolder/*.toml`. Am Stück: > > ```bash > for f in /etc/pdf-ocr-hotfolder/*.toml; do > n=$(basename "$f" .toml) > sudo systemctl start "pdf-ocr-hotfolder@$n" > done > systemctl status 'pdf-ocr-hotfolder@*' --no-pager > ``` ### Was ein Rollback nachweislich zurückholt Einmal real durchgespielt (Debian 12 **und** 13, Update auf den neuen Stand, danach Rollback auf das Backup). Zurück kamen korrekt: - **der Code** unter `/opt/pdf-ocr-hotfolder/` inklusive `lib/` — die alte Version lief danach wieder, - **alle Instanz-Configs** unter `/etc/pdf-ocr-hotfolder/` — **mit den `640`-Rechten und dem `root:`-Eigentum**; die Klartext-Passwörter bleiben also geschützt, `tar` stellt Modus und Eigentümer mit her, - die **Template-Unit** `pdf-ocr-hotfolder@.service` und - die **Drop-ins** unter `…@*.service.d/` (LXC-Kompat, User-Drop-in). Nach `daemon-reload` und dem Einzelstart liefen die Instanzen wieder mit dem alten Stand. Was dabei **nicht** zurückkommt, steht im nächsten Abschnitt. ### Grenzen des Rollbacks Ein Rollback ist ein **Overlay**, kein exaktes Zurücksetzen — beides im Test bestätigt: - **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 — nach dem Entpacken unter `/opt/pdf-ocr-hotfolder/pip-freeze.txt`: die dort genannten Versionen lassen sich von Hand wiederherstellen (`venv/bin/pip install -r …`). Im Test lief nach dem Rollback die **alte** Code-Version in der **neuen** venv — was hier gutging, weil sich die Pins nicht geändert hatten. Verlassen darf man sich darauf nicht: nach einem Update mit Versionssprung gehört die venv nach dem Rollback von Hand auf den alten Paketstand gebracht. - **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 — im Test nachgestellt und bestätigt. 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`) 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__.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@ -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/.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 | `--check-config` selbst kennt nur 0/1/2. Der **Dienst** kennt seit 0.7.0 zusätzlich **Exit 3** (Verzeichnis-Watch gestorben, Neustart erwünscht) — und die Unit startet bei **Exit 2** absichtlich **nicht** mehr neu (`RestartPreventExitStatus=2`), die Instanz bleibt sichtbar `failed` stehen. Für das Update heißt das: eine Instanz mit Config-Fehler verschwindet nicht mehr in einem stillen 5-Sekunden-Crash-Loop, sondern fällt in der Zusammenfassung auf. Die vollständige Tabelle: [INSTALLATION.md](INSTALLATION.md#exit-codes). 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@ | 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). ### Relative Pfade — Fehler seit 0.7.0 Bis 0.6.3 wurde ein relativer Pfad in `[paths]`, `[output].archive_dir` oder `[upload.folder].target` klaglos angenommen und gegen das `WorkingDirectory` der Unit aufgelöst, also unter `/opt/pdf-ocr-hotfolder/`. Seit 0.7.0 ist das ein **Config-Fehler mit Exit 2**: `--check-config` meldet ihn beim Update, und der Dienst startet nicht. Das ist der einzige Fall, in dem ein Update von 0.6.x eine bisher „laufende" Instanz stoppen kann. Er ist gewollt — eine solche Instanz schrieb an einer Stelle, an der niemand sie gesucht hat. Abhilfe: den Pfad absolut eintragen und, falls dort Dateien liegen, den Inhalt von `/opt/pdf-ocr-hotfolder/` vorher herüberholen. ### 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//incoming/ journalctl -u pdf-ocr-hotfolder@ -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.