fix: Ghostscript-Angebot log nicht mehr, fehlende Voraussetzungen dokumentiert (v0.7.1)

Befunde aus dem ersten echten Erstinstallations-Test auf frischen Debian-12-
und Debian-13-Containern. Der Weg selbst hat getragen (Basis-Install,
Instanz-Anlage, zweite Instanz, Update mit Rauchtest, Rollback) — diese
Stellen haben gelogen oder gefehlt:

- Das Ghostscript-Backports-Angebot auf Debian 12 war ein garantierter
  Leerlauf, der "aktualisiert ✓" meldete: bookworm-backports enthaelt gar
  kein ghostscript (am Paketindex verifiziert). Die Routine sucht jetzt den
  echten Kandidaten, vergleicht vorher/nachher und raeumt eine nur zur Probe
  angelegte Quelle wieder weg.
- Derselbe untaugliche Rat stand in der Preflight-Meldung, der
  pdfa_level-Warnung, config.example.toml und vier Doku-Dateien — ueberall
  ersetzt durch die echten Optionen.
- Mehrzeilige Log-Hinweise waren durch "echo -e" zerrissen und nicht
  kopierbar; log_* nutzt jetzt printf mit %s.
- pip-freeze.txt landete beim Rollback als /pip-freeze.txt im
  Wurzelverzeichnis, liegt jetzt unter opt/pdf-ocr-hotfolder/.
- git und sudo fehlen auf dem Proxmox-Debian-Template; "sudo ./install.sh"
  scheitert dort. Beide Wege dokumentiert, git als Voraussetzung ergaenzt,
  HTTPS-Clone als Normalfall.
- Rollback: systemctl start kann kein Glob. journald-Reparatur: Instanzen
  danach neu starten, sonst bleibt das Journal leer.

254 Tests gruen.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
This commit is contained in:
2026-09-23 01:36:42 +02:00
parent cd803a3dfe
commit 465ff8873f
15 changed files with 611 additions and 101 deletions
+134 -18
View File
@@ -14,10 +14,46 @@ Verwandte Dokumente: [README](../README.md) · [Update](UPDATE.md) · [Debian-Ma
| Python | 3.11+ (wegen `tomllib` aus der stdlib); kommt aus der Distribution |
| Arbeitsspeicher | **mindestens 2 GB** für den produktiven Betrieb — siehe [Systemanforderungen](#systemanforderungen) |
| Dateisystem | **ext4, xfs oder zfs**; `incoming/` **lokal**, kein CIFS/NFS — siehe [Dateisystem](#dateisystem-ext4-xfs-oder-zfs) |
| Rechte | `root` (`sudo ./install.sh`) |
| Rechte | `root` — als root direkt `./install.sh`, sonst `sudo ./install.sh` (s. [root oder sudo](#root-oder-sudo)) |
| `git` | zum Klonen des Repos — **nicht** vorinstalliert, s. [Pakete, die fehlen können](#pakete-die-auf-einem-frischen-system-fehlen-können) |
| Netz | apt-Zugriff für die System-Pakete, PyPI-Zugriff für die venv |
| Repo | muss dauerhaft liegen bleiben — `update.sh` kopiert daraus (s. [UPDATE.md](UPDATE.md)) |
### Pakete, die auf einem frischen System fehlen können
Auf dem **Proxmox-Debian-Standard-Template** (12 **und** 13) fehlen zwei Dinge,
die jede Anleitung stillschweigend voraussetzt — am frisch angelegten Container
verifiziert:
| Fehlt | Folge | Abhilfe |
|-------|-------|---------|
| **`git`** | `git clone …` schlägt mit `git: command not found` fehl — und ohne Clone gibt es kein Repo, aus dem `install.sh` läuft | `apt install git` |
| **`sudo`** | der überall dokumentierte Aufruf `sudo ./install.sh` schlägt mit `sudo: command not found` fehl | entweder `apt install sudo`, oder **einfach als `root` ohne `sudo` arbeiten** |
```bash
apt update
apt install -y git # zwingend
apt install -y sudo # nur, wenn nicht als root gearbeitet wird
```
### root oder sudo
Installer und Updater brauchen **root-Rechte** — *wie* man dahin kommt, ist
ihnen gleich. Beide Wege sind gleichwertig:
```bash
# (a) man ist bereits root — im frischen Container der Normalfall
./install.sh
# (b) man arbeitet als normaler Benutzer und hat sudo
sudo ./install.sh
```
In dieser Doku steht durchgehend die Variante mit `sudo`, weil die meisten
Systeme es haben. **Wer als `root` arbeitet, lässt das `sudo` bei jedem Befehl
einfach weg** — das gilt für alle Kommandos in diesem und den übrigen
Dokumenten. Ein fehlendes `sudo` ist kein Grund, es nachzuinstallieren.
Die System-Pakete installiert der Installer selbst. Die Liste steht als
einzige Quelle in `lib/common.sh` (Funktion `pdf_ocr_apt_packages()`, zwischen
den Marken `# --- BEGIN apt-packages` / `# --- END apt-packages`); `install.sh`
@@ -174,14 +210,51 @@ System mehr RAM bekommt.
## Installation
```bash
git clone gitea@gitea.sonith.de:sonith_ug/pdf-ocr-hotfolder.git
apt update && apt install -y git # fehlt im Proxmox-Standard-Template
git clone https://gitea.sonith.de/sonith_ug/pdf-ocr-hotfolder.git
cd pdf-ocr-hotfolder
sudo ./install.sh
sudo ./install.sh # als root: ./install.sh
```
#### Welche Clone-URL?
| Variante | URL | Wann |
|----------|-----|------|
| **HTTPS** (Normalfall) | `https://gitea.sonith.de/sonith_ug/pdf-ocr-hotfolder.git` | braucht **keine** Credentials und funktioniert auf einem frischen Server sofort — der Weg, der im Installations-Test benutzt wurde |
| **SSH** | `gitea@gitea.sonith.de:sonith_ug/pdf-ocr-hotfolder.git` | nur sinnvoll, wenn auf dem Zielsystem ein **Deploy-Key** hinterlegt ist (z.B. weil auch gepusht werden soll) |
> ⚠️ **Der SSH-User bei `gitea.sonith.de` heißt `gitea`, nicht `git`.**
> `git@gitea.sonith.de:…` ist der Default anderer Git-Hoster und hier **falsch**
> — der Clone scheitert dann mit `Permission denied (publickey)`.
`install.sh` ist **Installer und Instanz-Manager in einem** und idempotent —
jeder weitere Aufruf überspringt, was schon steht.
### So sieht ein Erstlauf aus
Verifiziert auf frischen Debian-12- und Debian-13-Containern. Die Reihenfolge
der Abfragen:
1. **LXC-Drop-in** — nur im Container: „systemd-Hardening-Drop-in für LXC
installieren?" → **ja**, sonst scheitert der Start mit `226/NAMESPACE`
([warum](#lxccontainer-error-226namespace)).
2. **Ghostscript-Hinweis** — nur auf Debian 12 mit betroffener Version. Kein
Abbruch: mit dem Default `pdfa_level = ""` ist die Installation
unproblematisch ([Hintergrund](#ghostscript-bug-auf-debian-12)).
3. **journald-Warnung** — nur, wenn `systemd-journald` nicht läuft. Typisch für
Debian 13 in LXC auf Proxmox. Hier **abbrechen**, journald reparieren und neu
anfangen — sonst hat der Dienst kein Log
([Abhilfe](#debian-13-in-lxc-auf-proxmox-journald-scheitert-243credentials)).
4. Dann die **fünf Fragen pro Instanz**: Instanz-Name → Basis-Pfad →
Service-User → OCR-Sprachen → Original-Behandlung (Archiv/löschen/liegen
lassen). Im Detail: [Die Abfragen pro Instanz](#die-abfragen-pro-instanz).
5. Zum Schluss `Weitere Instanz anlegen? [j/N]:` — hier entsteht die zweite
Instanz, ohne dass irgendetwas am Basis-Install noch einmal angefasst wird.
Danach läuft `pdf-ocr-hotfolder@<instanz>.service`. Gegenprobe: eine PDF nach
`incoming/` kopieren und `journalctl -u pdf-ocr-hotfolder@<instanz> -f`
mitlesen.
### Basis-Install vs. Instanz-Anlage
Der Installer unterscheidet zwei Ebenen:
@@ -424,21 +497,40 @@ ocrmypdf-Version:
### Abhilfe
**Weg 1 — Ghostscript anheben** (empfohlen). Der Installer erkennt betroffene
Versionen und bietet auf Debian 12 bookworm-backports an. Manuell:
> 🚫 **Es gibt auf Debian 12 kein neueres Ghostscript.** `bookworm-backports`
> führt **kein** Ghostscript-Paket — am Paketindex geprüft
> (`bookworm-backports/main/binary-amd64`: 2606 Pakete, `ghostscript` nicht
> darunter, `systemd`/`golang-go`/`linux-image-amd64` schon). Ältere Fassungen
> dieser Anleitung und frühere Versionen des Installers haben genau das
> empfohlen — der Rat konnte nie funktionieren.
>
> **Planungsaussage:** Wer auf Debian 12 **PDF/A in der schnellen Betriebsart**
> (`skip_text = true`) braucht, hat dort **keinen Weg** — weder über Backports
> noch sonst. Es bleibt: PDF/A aufgeben, `skip_text` aufgeben (Weg 2, kostet
> Laufzeit) oder Debian 13. **Das gehört vor die Installation**, nicht in den
> Moment, in dem der Preflight abbricht.
```bash
echo 'deb http://deb.debian.org/debian bookworm-backports main' | \
sudo tee /etc/apt/sources.list.d/bookworm-backports.list
sudo apt update && sudo apt install -t bookworm-backports ghostscript
```
Ab Ghostscript 10.02.1 ist alles in Ordnung; PDF/A kann dann eingeschaltet
werden.
**Weg 1 — `pdfa_level = ""` lassen** (Default, empfohlen). Ohne PDF/A-Ausgabe
fasst ocrmypdf ≥ 17 Ghostscript gar nicht an; die betroffene Version ist dann
völlig unproblematisch. Der Preis ist PDF/A — das Ergebnis ist eine normale
durchsuchbare PDF. Für die übliche Anwendung (Scan wird durchsuchbar) reicht
das.
**Weg 2 — `skip_text = false` setzen.** Dann wird vorhandener Text neu erkannt
statt übersprungen, und die Bedingung greift nicht mehr. Das kostet Laufzeit bei
PDFs, die bereits eine Textebene haben.
statt übersprungen, und die Bedingung greift nicht mehr — PDF/A ist damit auch
auf Debian 12 möglich. Das kostet Laufzeit bei PDFs, die bereits eine Textebene
haben, und OCRt sie ein zweites Mal.
**Weg 3 — Distribution mit neuerem Ghostscript.** **Debian 13 liefert
Ghostscript 10.05.1** und ist vom Bug nicht betroffen; dort ist PDF/A zusammen
mit `skip_text = true` ohne Einschränkung nutzbar. Wer PDF/A verbindlich
braucht, installiert von vornherein auf Debian 13.
| Ich brauche … | Debian 12 | Debian 13 |
|---------------|-----------|-----------|
| durchsuchbare PDF, kein PDF/A | ✅ Default (`pdfa_level = ""`) | ✅ |
| PDF/A **und** `skip_text = true` | ❌ kein Weg | ✅ |
| PDF/A mit `skip_text = false` | ✅ (langsamer) | ✅ |
---
@@ -762,6 +854,13 @@ Ist `systemd-journald.service` selbst `failed` (beobachtet mit
`status=243/CREDENTIALS`), gibt es schlicht kein journal, in das geschrieben
werden könnte.
Läuft journald dagegen `active (running)` und das Journal der Instanz ist
trotzdem leer (`-- No entries --`), ist meist journald **erst nach dem Dienst**
wieder hochgekommen: die Startmeldungen hatten in der Zwischenzeit kein Ziel und
sind weg. `sudo systemctl restart 'pdf-ocr-hotfolder@*'` schreibt sie neu —
siehe die Warnung im
[nächsten Abschnitt](#debian-13-in-lxc-auf-proxmox-journald-scheitert-243credentials).
**Einordnung.** Der Dienst loggt seit v0.4.1 **ausschließlich** nach journald —
es gibt bewusst kein eigenes Logfile und kein Logverzeichnis. Ein kaputtes
journald ist damit ein blinder Fleck: jede Fehlersuche läuft ins Leere, und der
@@ -843,9 +942,26 @@ sudo systemctl restart systemd-journald
sudo journalctl --flush
```
Danach `systemctl status systemd-journald` (muss `active (running)` sein) und
`journalctl -u pdf-ocr-hotfolder@<instanz>` gegenprüfen. Für die anderen
betroffenen Units gilt dasselbe Muster mit deren Unit-Namen.
Danach `systemctl status systemd-journald` gegenprüfen — muss `active
(running)` sein. Für die anderen betroffenen Units gilt dasselbe Muster mit
deren Unit-Namen.
> ⚠️ **Jetzt die Instanzen einmal neu starten — sonst steht man vor einem
> leeren Journal.** Wurde der Dienst gestartet, **während** journald tot war,
> sind seine Startmeldungen unwiederbringlich weg: sie hatten kein Ziel. Nach
> der Reparatur meldet `journalctl -u pdf-ocr-hotfolder@<instanz>` dann
> `-- No entries --` — und das sieht exakt so aus wie ein gescheiterter
> Reparaturversuch, obwohl journald längst wieder läuft. Der Neustart schreibt
> die Startmeldungen neu ins frische Journal:
>
> ```bash
> sudo systemctl restart 'pdf-ocr-hotfolder@*'
> journalctl -u pdf-ocr-hotfolder@<instanz> -n 20
> ```
>
> Erst wenn hier die Startmeldungen stehen, ist die Reparatur belegt. (`restart`
> kann das Glob, weil die Units geladen sind — `start` nicht, siehe
> [UPDATE.md](UPDATE.md#rollback).)
> **Der saubere Weg liegt host-seitig.** Das Drop-in kuriert das Symptom im
> Container. Richtig behoben wird es auf dem Proxmox-Host: Update von
+23 -3
View File
@@ -6,6 +6,14 @@ Verwandte Dokumente: [README](../README.md) · [Installation](INSTALLATION.md)
---
> **`sudo` oder direkt als `root`.** Alle Befehle dieser Seite brauchen
> 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` einfach weg. Siehe
> [INSTALLATION.md](INSTALLATION.md#root-oder-sudo).
---
## Warum das ein eigener Ablauf ist
Die venv unter `/opt/pdf-ocr-hotfolder/venv/` hängt an der **Python-Version der
@@ -179,11 +187,23 @@ Ghostscript) wirklich anfasst — `systemctl status` sagt darüber nichts.
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:
Auf Debian 13 ist der Ghostscript-Bug aus Debian 12 kein Thema mehr — **Debian
13 liefert Ghostscript 10.05.1**. Damit ist PDF/A (`[ocr].pdfa_level = "1"`,
`"2"` oder `"3"`) zusammen mit `skip_text = true` erstmals ohne Einschränkung
nutzbar; auf Debian 12 gab es dafür **keinen** Weg (ein Upgrade aus
`bookworm-backports` existiert nicht, dort liegt kein Ghostscript-Paket). Genau
das ist oft der Grund für das Upgrade. Hintergrund:
[INSTALLATION.md](INSTALLATION.md#ghostscript-bug-auf-debian-12).
Hat jemand auf dem alten System nach der früheren, falschen Empfehlung einen
`bookworm-backports`-Eintrag unter `/etc/apt/sources.list.d/` angelegt, gehört
er nach dem Upgrade entfernt — Ghostscript kam ohnehin nie daher:
```bash
sudo rm -f /etc/apt/sources.list.d/bookworm-backports.list
sudo apt update
```
---
## Pins in `requirements.txt`
+58 -6
View File
@@ -24,6 +24,13 @@ 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.
@@ -126,10 +133,14 @@ Vor dem ersten Eingriff auf der Platte schreibt `update.sh` ein Archiv:
| `/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 | |
| `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.
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
@@ -153,24 +164,65 @@ Das Backup-Archiv ist wurzelrelativ gepackt und lässt sich direkt zurückspiele
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>'
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:<service-gruppe>`-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:
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: die dort genannten Versionen lassen sich von Hand wiederherstellen
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. Sauberer ist deshalb
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/`.