fix: ocrmypdf-Pin auf 17.4.1, Preflight erkennt den GS-Fall, Rauchtest (v0.6.1)

v0.6.0 hat ocrmypdf auf 16.13.0 gepinnt, um einen ungewollten Major-Sprung
zu verhindern. Auf Bestandsinstallationen war das ein DOWNGRADE (dort lief
via ">=16.0" bereits 17.x) — und 16.13.0 bricht auf Debian 12 mit dem
Bord-Ghostscript 10.0.0 bei JEDER PDF ab, sobald skip_text gesetzt ist.
Im Test auf CT 200 lief das Update mit Exit 0 durch, der Dienst blieb
"active", --check-config meldete "Preflight ok" — und jede Datei landete
in error/. Stiller Totalausfall.

- requirements.txt: ocrmypdf==17.4.1 (real auf Debian 12 + gs 10.0.0
  verifiziert). Ab 17.0.0 steht die GS-Pruefung in ocrmypdf unter einem
  `if options.output_type.startswith('pdfa')`; bis 16.x lief sie ohne
  diesen Guard und schlug auch bei output_type="pdf" zu.
- check_preflight() prueft Ghostscript nicht mehr nur bei gesetztem
  pdfa_level, sondern bildet die reale Bedingung ab:
  betroffene GS-Version UND skip_text UND (PDF/A ODER ocrmypdf < 17).
  Der Dienst bricht damit beim Start ab statt bei der ersten Datei.
- update.sh zeigt Versionsspruenge der gepinnten Pakete; Downgrades als
  WARN, auch in der Abschluss-Zusammenfassung.
- update.sh faehrt nach dem Start einen Rauchtest (eingebettete Mini-PDF
  durch die echte Pipeline) und raeumt restlos auf. Uebersprungen, wenn
  Upload-Ziele oder E-Mail-Notify aktiv sind, damit kein Testmuell zum
  Kunden geht. Abschaltbar mit --no-smoke-test.
- Doku korrigiert: pdfa_level = "" allein ist keine Entwarnung, die haengt
  an der ocrmypdf-Version.

152 Tests gruen.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
This commit is contained in:
2026-09-22 22:55:47 +02:00
parent 3e24aa2ecd
commit aa9918adba
15 changed files with 1044 additions and 86 deletions
+436 -1
View File
@@ -14,6 +14,7 @@
# Aufruf:
# sudo ./update.sh # normales Update
# sudo ./update.sh --rebuild-venv # venv-Neubau erzwingen (nach dist-upgrade)
# sudo ./update.sh --no-smoke-test # ohne Rauchtest durchlaufen
#
set -Eeuo pipefail
@@ -32,6 +33,10 @@ log_step() { echo -e "\n${BLUE}==>${NC} $*"; }
: "${TAR_ROOT:=/}"
: "${VERIFY_WAIT:=6}"
: "${BACKUP_KEEP:=5}"
# Max. Sekunden, die der Rauchtest auf das fertige OCR-PDF wartet. Eine
# Mini-PDF braucht ein paar Sekunden (Datei-Stabilisierung + OCR), unter Last
# auch mal laenger.
: "${SMOKE_TIMEOUT:=90}"
SERVICE_TEMPLATE="pdf-ocr-hotfolder@.service"
UNIT_GLOB='pdf-ocr-hotfolder@*.service'
LXC_DROPIN_DIR="$SYSTEMD_DIR/${SERVICE_TEMPLATE}.d"
@@ -44,6 +49,7 @@ LXC_SYNCED=0
BACKUP_FILE=""
PRIMARY_USER="pdfocr"
TOUCHED=0 # 1, sobald auf der Platte etwas getauscht wurde
SMOKE_TEST=1 # per --no-smoke-test abschaltbar
UNITS_ALL=() # alle bekannten Instanz-Units
PREV_OK=() # liefen vorher sauber -> muessen nachher laufen
@@ -53,6 +59,12 @@ CFG_WARN=() # Instanzen mit Config-Warnungen (Exit 1)
CFG_ERR=() # Instanzen mit Config-Fehlern (Exit 2)
STARTED_OK=()
STARTED_FAIL=()
DEP_UPGRADES=() # Paket: alt -> neu (hoeher)
DEP_DOWNGRADES=() # Paket: alt -> neu (niedriger) <- das Gefaehrliche
DEP_NEW=() # Paket war vorher nicht installiert
SMOKE_OK=() # Rauchtest bestanden
SMOKE_FAIL=() # Rauchtest fehlgeschlagen
SMOKE_SKIP=() # Rauchtest uebersprungen (mit Begruendung)
usage() {
cat <<EOF
@@ -65,10 +77,25 @@ Optionen:
installieren. Das ist der Aufruf nach einem Debian-
Major-Upgrade (apt full-upgrade, z.B. 12 -> 13), wenn das
System-Python eine neue Version bekommen hat.
--no-smoke-test Den Rauchtest nach dem Start auslassen.
-h, --help Diese Hilfe.
Ohne Option prueft das Skript selbst, ob die venv noch zum System-Python
passt, und baut sie bei Bedarf neu.
Rauchtest (Standard: an)
Nach dem Start schiebt das Skript pro Instanz eine winzige Test-PDF durch
die echte Pipeline und prueft, ob sie in outgoing/ ankommt. Erst das zeigt
einen Totalausfall, den systemd nicht sieht (Dienst active, aber jede PDF
landet in error/). Test-PDF und Ergebnis werden danach restlos wieder
entfernt, auch im Fehlerfall.
Uebersprungen wird der Test bei Instanzen mit aktivem Upload-Ziel
(Nextcloud, SFTP, Ordner-Ziel ausserhalb von outgoing) oder aktivem
E-Mail-Notify: dort wuerde die Testdatei nach aussen gehen.
Ein fehlgeschlagener Rauchtest setzt den Exit-Code, rollt aber NICHTS
zurueck. Wartezeit pro Instanz: ${SMOKE_TIMEOUT}s (SMOKE_TIMEOUT=...).
EOF
}
@@ -223,6 +250,97 @@ pip_install_requirements() {
return 1
}
# ============================================================
# Versionen der Kernabhaengigkeiten
# ============================================================
#
# Ein Downgrade (z.B. ocrmypdf 17.4.1 -> 16.13.0, weil requirements.txt neu
# gepinnt wurde) verschwand bis v0.6.0 wortlos hinter "[INFO] Dependencies ok".
# Genau so ist der Totalausfall aus Issue #7 entstanden. Deshalb: vorher und
# nachher messen und jede Aenderung benennen.
# Paketnamen aus einer requirements.txt (normalisiert: klein, Bindestriche).
pinned_package_names() {
local req
for req in "$@"; do
[ -f "$req" ] || continue
sed -n 's/^[[:space:]]*\([A-Za-z0-9._-]\{1,\}\)[[:space:]]*[<>=!~].*/\1/p' "$req"
done | tr '[:upper:]_' '[:lower:]-' | sort -u
}
# "paket version" je Zeile — nur fuer die in $2.. gepinnten Pakete.
snapshot_pinned_versions() {
local venv="$1"; shift
local line name ver n
local -a names=()
mapfile -t names < <(pinned_package_names "$@")
[ "${#names[@]}" -gt 0 ] || return 0
[ -x "$venv/bin/pip" ] || return 0
while IFS= read -r line; do
case "$line" in (*==*) ;; (*) continue ;; esac
name="$(printf '%s' "${line%%==*}" | tr '[:upper:]_' '[:lower:]-')"
ver="${line#*==}"
for n in "${names[@]}"; do
[ "$n" = "$name" ] && printf '%s %s\n' "$name" "$ver"
done
done < <("$venv/bin/pip" list --format=freeze 2>/dev/null || true)
}
# 0, wenn $1 eine aeltere Version als $2 ist.
version_is_older() {
[ "$1" = "$2" ] && return 1
[ "$(printf '%s\n%s\n' "$1" "$2" | sort -V | head -n1)" = "$1" ]
}
# Vergleicht zwei Snapshots und fuellt DEP_UPGRADES/DEP_DOWNGRADES/DEP_NEW.
diff_pinned_versions() {
local before="$1" after="$2"
local name new old
DEP_UPGRADES=(); DEP_DOWNGRADES=(); DEP_NEW=()
while read -r name new; do
[ -n "$name" ] || continue
old="$(awk -v p="$name" '$1==p {print $2; exit}' "$before" 2>/dev/null || true)"
if [ -z "$old" ]; then
DEP_NEW+=("$name $new")
elif [ "$old" = "$new" ]; then
continue
elif version_is_older "$new" "$old"; then
DEP_DOWNGRADES+=("$name: $old -> $new")
else
DEP_UPGRADES+=("$name: $old -> $new")
fi
done < "$after"
}
# Gibt die Aenderungen aus. Downgrades als WARN — die sind das Gefaehrliche.
report_dependency_changes() {
local entry
if [ "${#DEP_DOWNGRADES[@]}" -eq 0 ] && [ "${#DEP_UPGRADES[@]}" -eq 0 ] \
&& [ "${#DEP_NEW[@]}" -eq 0 ]; then
log_info "Kernabhaengigkeiten unveraendert."
return 0
fi
for entry in "${DEP_DOWNGRADES[@]:-}"; do
[ -n "$entry" ] || continue
log_warn "DOWNGRADE: $entry"
done
if [ "${#DEP_DOWNGRADES[@]}" -gt 0 ]; then
log_warn " Ein Downgrade kommt aus einem gesenkten Pin in requirements.txt."
log_warn " Pruefen, ob das beabsichtigt ist — die aeltere Version kann auf"
log_warn " diesem System unbrauchbar sein, ohne dass systemd es merkt."
fi
for entry in "${DEP_UPGRADES[@]:-}"; do
[ -n "$entry" ] || continue
log_info "Upgrade: $entry"
done
for entry in "${DEP_NEW[@]:-}"; do
[ -n "$entry" ] || continue
log_info "Neu: $entry"
done
}
# Baut die venv neu: alte wegsichern, neue bauen, Requirements installieren,
# und erst bei Erfolg die alte entfernen. Scheitert etwas, wird die alte
# zurueckgerollt und hart abgebrochen.
@@ -534,6 +652,266 @@ check_all_configs() {
done
}
# ============================================================
# Rauchtest
# ============================================================
#
# systemd sagt "active", --check-config sagt "ok" — und trotzdem landet jede
# PDF in error/ (Issue #7). Das sieht man nur, wenn man wirklich eine Datei
# durch die Pipeline schiebt. Deshalb: eine Mini-PDF nach incoming/, warten,
# bis sie in outgoing/ ankommt, danach restlos aufraeumen.
#
# Die Test-PDF steckt als base64 hier drin — kein Pillow, kein gs, kein
# convert, damit der Test keine Abhaengigkeit mitbringt, die auf dem Zielsystem
# fehlen kann. 694 Bytes, eine Seite, eine Textzeile.
smoke_pdf_base64() {
cat <<'SMOKEPDF'
JVBERi0xLjQKJeLjz9MKMSAwIG9iago8PCAvVHlwZSAvQ2F0YWxvZyAvUGFnZXMgMiAwIFIgPj4K
ZW5kb2JqCjIgMCBvYmoKPDwgL1R5cGUgL1BhZ2VzIC9LaWRzIFszIDAgUl0gL0NvdW50IDEgPj4K
ZW5kb2JqCjMgMCBvYmoKPDwgL1R5cGUgL1BhZ2UgL1BhcmVudCAyIDAgUiAvTWVkaWFCb3ggWzAg
MCA1OTUgODQyXSAvUmVzb3VyY2VzIDw8IC9Gb250IDw8IC9GMSA0IDAgUiA+PiA+PiAvQ29udGVu
dHMgNSAwIFIgPj4KZW5kb2JqCjQgMCBvYmoKPDwgL1R5cGUgL0ZvbnQgL1N1YnR5cGUgL1R5cGUx
IC9CYXNlRm9udCAvSGVsdmV0aWNhID4+CmVuZG9iago1IDAgb2JqCjw8IC9MZW5ndGggMTQ0ID4+
CnN0cmVhbQpCVCAvRjEgMjQgVGYgNzIgNzAwIFRkIChQREYgT0NSIEhPVEZPTERFUiBTTU9LRVRF
U1QpIFRqIEVUCkJUIC9GMSAxMSBUZiA3MiA2NzAgVGQgKEF1dG9tYXRpc2NoIGVyemV1Z3Qgdm9u
IHVwZGF0ZS5zaCAtIGJpdHRlIGlnbm9yaWVyZW4uKSBUaiBFVAplbmRzdHJlYW0KZW5kb2JqCnhy
ZWYKMCA2CjAwMDAwMDAwMDAgNjU1MzUgZiAKMDAwMDAwMDAxNSAwMDAwMCBuIAowMDAwMDAwMDY0
IDAwMDAwIG4gCjAwMDAwMDAxMjEgMDAwMDAgbiAKMDAwMDAwMDI0NyAwMDAwMCBuIAowMDAwMDAw
MzE3IDAwMDAwIG4gCnRyYWlsZXIKPDwgL1NpemUgNiAvUm9vdCAxIDAgUiA+PgpzdGFydHhyZWYK
NTExCiUlRU9GCg==
SMOKEPDF
}
# Liest die fuer den Rauchtest noetigen Werte aus einer Instanz-Config.
# Ausgabe: "SCHLUESSEL<TAB>Wert" je Zeile.
smoke_read_config() {
local py="$1" cfg="$2"
"$py" - "$cfg" <<'SMOKECFG'
import sys
import tomllib
with open(sys.argv[1], "rb") as fh:
data = tomllib.load(fh)
def sec(*keys):
cur = data
for key in keys:
cur = cur.get(key, {}) if isinstance(cur, dict) else {}
return cur if isinstance(cur, dict) else {}
paths = sec("paths")
out = sec("output")
folder = sec("upload", "folder")
nextcloud = sec("upload", "nextcloud")
sftp = sec("upload", "sftp")
mail = sec("notify", "email")
# Alles, was die Testdatei nach aussen tragen wuerde. Defaults wie in config.py.
blockers = []
if nextcloud.get("enabled", False):
blockers.append("upload.nextcloud")
if sftp.get("enabled", False):
blockers.append("upload.sftp")
if mail.get("enabled", False):
blockers.append("notify.email")
# Ordner-Upload ohne target schreibt nach outgoing/ (harmlos); mit target
# geht die Datei irgendwo anders hin - oft eine Kundenfreigabe.
if folder.get("enabled", True) and str(folder.get("target", "")).strip():
blockers.append("upload.folder (target gesetzt)")
mode = str(out.get("original_on_success", "delete"))
archive = str(out.get("archive_dir", "")) if mode == "archive" else ""
for key, value in (
("INCOMING", paths.get("incoming", "")),
("OUTGOING", paths.get("outgoing", "")),
("WORKING", paths.get("working", "")),
("ERROR", paths.get("error", "")),
("ARCHIVE", archive),
("NAME_MODE", out.get("name_mode", "prefix")),
("NAME_TAG", out.get("name_tag", "OCR_")),
("BLOCKERS", ", ".join(blockers)),
):
print(f"{key}\t{value}")
SMOKECFG
}
# Bildet build_output_name() aus processor.py nach.
smoke_output_name() {
local src="$1" mode="$2" tag="$3"
if [ "$mode" = "none" ] || [ -z "$tag" ]; then
printf '%s' "$src"; return 0
fi
case "$mode" in
prefix) printf '%s%s' "$tag" "$src" ;;
suffix) printf '%s%s%s' "${src%.*}" "$tag" ".${src##*.}" ;;
*) printf '%s' "$src" ;;
esac
}
# Raeumt alle Spuren des Rauchtests weg — in JEDEM Ausgang. Nach dieser
# Funktion darf in keinem der Verzeichnisse etwas vom Test liegen bleiben.
smoke_cleanup() {
local in_dir="$1" out_dir="$2" work_dir="$3" err_dir="$4" arch_dir="$5"
local src_name="$6" out_name="$7"
local f
for f in "$in_dir/$src_name" \
"$work_dir/$src_name" \
"$work_dir/__ocr_$out_name" \
"$out_dir/$out_name" \
"$err_dir/$src_name" \
"$err_dir/$out_name" \
"${arch_dir:+$arch_dir/$src_name}"; do
[ -n "$f" ] || continue
[ -e "$f" ] && rm -f "$f" 2>/dev/null
done
return 0
}
# Schiebt eine Test-PDF durch eine laufende Instanz.
# 0 = bestanden, 1 = durchgefallen, 2 = uebersprungen
smoke_test_instance() {
local name="$1"
local py="$INSTALL_DIR/venv/bin/python"
local cfg="$CONFIG_DIR/$name.toml"
local key value
local INCOMING="" OUTGOING="" WORKING="" ERROR="" ARCHIVE=""
local NAME_MODE="prefix" NAME_TAG="OCR_" BLOCKERS=""
[ -f "$cfg" ] || { SMOKE_SKIP+=("$name (keine Config)"); return 2; }
[ -x "$py" ] || { SMOKE_SKIP+=("$name (kein venv-Python)"); return 2; }
local cfg_out
if ! cfg_out="$(smoke_read_config "$py" "$cfg" 2>&1)"; then
log_warn " ⚠ $name — Config liess sich fuer den Rauchtest nicht lesen:"
printf '%s\n' "$cfg_out" | tail -n 5
SMOKE_SKIP+=("$name (Config nicht lesbar)")
return 2
fi
while IFS="$(printf '\t')" read -r key value; do
case "$key" in
INCOMING) INCOMING="$value" ;;
OUTGOING) OUTGOING="$value" ;;
WORKING) WORKING="$value" ;;
ERROR) ERROR="$value" ;;
ARCHIVE) ARCHIVE="$value" ;;
NAME_MODE) NAME_MODE="$value" ;;
NAME_TAG) NAME_TAG="$value" ;;
BLOCKERS) BLOCKERS="$value" ;;
esac
done <<< "$cfg_out"
if [ -n "$BLOCKERS" ]; then
log_info " ⏭ $name — Rauchtest uebersprungen: aktive Ziele ($BLOCKERS)."
log_info " Eine Testdatei wuerde sonst beim Empfaenger landen."
SMOKE_SKIP+=("$name (aktive Ziele: $BLOCKERS)")
return 2
fi
if [ -z "$INCOMING" ] || [ -z "$OUTGOING" ]; then
SMOKE_SKIP+=("$name ([paths] unvollstaendig)")
return 2
fi
if [ ! -d "$INCOMING" ]; then
log_warn " ⚠ $name — $INCOMING existiert nicht; Rauchtest uebersprungen."
SMOKE_SKIP+=("$name (incoming fehlt)")
return 2
fi
# Eindeutiger Name, der mit keiner Kundendatei kollidieren kann.
local src_name out_name
src_name="__smoketest_update_$(date +%Y%m%d-%H%M%S)_$$.pdf"
out_name="$(smoke_output_name "$src_name" "$NAME_MODE" "$NAME_TAG")"
# Als Instanz-User anlegen, damit Rechte und Eigentuemer denen einer
# echten Scan-Datei entsprechen.
local unit_user
unit_user="$(systemctl show -p User --value "pdf-ocr-hotfolder@${name}.service" 2>/dev/null || true)"
[ -n "$unit_user" ] || unit_user="$PRIMARY_USER"
local tmp_pdf
tmp_pdf="$(mktemp)"
if ! smoke_pdf_base64 | base64 -d > "$tmp_pdf" 2>/dev/null; then
rm -f "$tmp_pdf"
log_warn " ⚠ $name — Test-PDF liess sich nicht erzeugen (base64 fehlt?)."
SMOKE_SKIP+=("$name (base64 fehlt)")
return 2
fi
# Erst fertig schreiben, dann hineinbewegen: der Watcher soll keine
# halb geschriebene Datei sehen.
local staged="$INCOMING/.$src_name.part"
cp "$tmp_pdf" "$staged" 2>/dev/null || {
rm -f "$tmp_pdf" "$staged"
log_warn " ⚠ $name — nach $INCOMING liess sich nicht schreiben."
SMOKE_SKIP+=("$name (incoming nicht beschreibbar)")
return 2
}
rm -f "$tmp_pdf"
chown "$unit_user":"$unit_user" "$staged" 2>/dev/null || true
chmod 644 "$staged" 2>/dev/null || true
if ! mv "$staged" "$INCOMING/$src_name" 2>/dev/null; then
rm -f "$staged"
log_warn " ⚠ $name — Testdatei liess sich nicht in $INCOMING einstellen."
SMOKE_SKIP+=("$name (incoming nicht beschreibbar)")
return 2
fi
# Warten: fertiges PDF in outgoing/ oder Abbruch in error/.
local waited=0 result=1
while [ "$waited" -lt "$SMOKE_TIMEOUT" ]; do
if [ -f "$OUTGOING/$out_name" ]; then result=0; break; fi
if [ -n "$ERROR" ] && { [ -f "$ERROR/$src_name" ] || [ -f "$ERROR/$out_name" ]; }; then
result=1; break
fi
sleep 1
waited=$((waited + 1))
done
smoke_cleanup "$INCOMING" "$OUTGOING" "$WORKING" "$ERROR" "$ARCHIVE" \
"$src_name" "$out_name"
if [ "$result" -eq 0 ]; then
log_info " ✅ $name — Test-PDF kam nach ${waited}s in outgoing/ an"
SMOKE_OK+=("$name")
return 0
fi
if [ "$waited" -ge "$SMOKE_TIMEOUT" ]; then
log_error " ❌ $name — Test-PDF war nach ${SMOKE_TIMEOUT}s nicht in $OUTGOING"
else
log_error " ❌ $name — Test-PDF landete in $ERROR statt in $OUTGOING"
fi
log_error " Der Dienst laeuft, verarbeitet aber nichts. Ursache im Journal:"
log_error " journalctl -u pdf-ocr-hotfolder@${name}.service -n 80 --no-pager"
SMOKE_FAIL+=("$name")
return 1
}
# Faehrt den Rauchtest fuer alle Instanzen, die nach dem Update laufen.
run_smoke_tests() {
SMOKE_OK=(); SMOKE_FAIL=(); SMOKE_SKIP=()
if [ "$SMOKE_TEST" -ne 1 ]; then
log_step "Rauchtest"
log_warn "Uebersprungen (--no-smoke-test). Ein Totalausfall bei der"
log_warn "Verarbeitung faellt damit erst der ersten echten Datei auf."
return 0
fi
if [ "${#STARTED_OK[@]}" -eq 0 ]; then
log_step "Rauchtest"
log_info "Keine laufende Instanz — nichts zu testen."
return 0
fi
log_step "Rauchtest (Test-PDF durch die echte Pipeline)"
local unit name
for unit in "${STARTED_OK[@]}"; do
name="${unit#pdf-ocr-hotfolder@}"
name="${name%.service}"
smoke_test_instance "$name" || true
done
}
# ============================================================
# Backup
# ============================================================
@@ -672,6 +1050,7 @@ fi
while [ "$#" -gt 0 ]; do
case "$1" in
--rebuild-venv) REBUILD_VENV=1 ;;
--no-smoke-test) SMOKE_TEST=0 ;;
-h|--help) usage; exit 0 ;;
*) log_error "Unbekannte Option: $1"; echo; usage; exit 1 ;;
esac
@@ -754,6 +1133,14 @@ stop_instances
create_backup
# Versionen der gepinnten Pakete festhalten, SOLANGE die alte
# requirements.txt noch liegt — sonst verschwindet ein Downgrade
# (z.B. ocrmypdf 17.4.1 -> 16.13.0) wortlos hinter "Dependencies ok ✓".
DEP_BEFORE="$(mktemp)"
DEP_AFTER="$(mktemp)"
snapshot_pinned_versions "$INSTALL_DIR/venv" \
"$INSTALL_DIR/requirements.txt" "$REPO_DIR/requirements.txt" > "$DEP_BEFORE" || true
log_step "Code aktualisieren"
TOUCHED=1
rm -rf "$INSTALL_DIR/pdf_ocr_hotfolder"
@@ -772,6 +1159,12 @@ else
log_info "Dependencies ok ✓"
fi
snapshot_pinned_versions "$INSTALL_DIR/venv" \
"$INSTALL_DIR/requirements.txt" "$REPO_DIR/requirements.txt" > "$DEP_AFTER" || true
diff_pinned_versions "$DEP_BEFORE" "$DEP_AFTER"
report_dependency_changes
rm -f "$DEP_BEFORE" "$DEP_AFTER"
install_units
log_step "Berechtigungen setzen"
@@ -785,10 +1178,16 @@ check_all_configs
log_step "Instanzen starten"
start_instances
# Ab hier wird nichts mehr getauscht. Der Rauchtest laeuft bewusst OHNE
# ERR-Trap: ein durchgefallener Test setzt den Exit-Code, loest aber keinen
# Abbruch und kein Zurueckrollen aus.
trap - ERR INT TERM
run_smoke_tests
# ============================================================
# Zusammenfassung: Ist gegen Soll
# ============================================================
trap - ERR INT TERM
RC=0
echo
@@ -851,6 +1250,42 @@ if [ "$APT_WARN" -eq 1 ]; then
log_warn "System-Pakete konnten nicht vollstaendig abgeglichen werden (siehe oben)."
fi
# Versionsspruenge der Kernabhaengigkeiten hier noch einmal — im Log weiter
# oben gehen sie zwischen pip-Ausgaben unter.
if [ "${#DEP_DOWNGRADES[@]}" -gt 0 ]; then
log_warn "Abhaengigkeiten DOWNGEGRADED:"
for entry in "${DEP_DOWNGRADES[@]}"; do
log_warn " $entry"
done
log_warn " Das kommt aus requirements.txt. War es nicht beabsichtigt: Pin"
log_warn " korrigieren und update.sh --rebuild-venv erneut fahren."
fi
if [ "${#DEP_UPGRADES[@]}" -gt 0 ]; then
log_info "Abhaengigkeiten angehoben:"
for entry in "${DEP_UPGRADES[@]}"; do
log_info " $entry"
done
fi
if [ "${#DEP_NEW[@]}" -gt 0 ]; then
log_info "Abhaengigkeiten neu dazugekommen: ${DEP_NEW[*]}"
fi
if [ "$SMOKE_TEST" -ne 1 ]; then
log_warn "Rauchtest: uebersprungen (--no-smoke-test)"
else
[ "${#SMOKE_OK[@]}" -gt 0 ] && log_info "Rauchtest bestanden: ${SMOKE_OK[*]}"
[ "${#SMOKE_SKIP[@]}" -gt 0 ] && log_info "Rauchtest uebersprungen: ${SMOKE_SKIP[*]}"
if [ "${#SMOKE_FAIL[@]}" -gt 0 ]; then
log_error "RAUCHTEST FEHLGESCHLAGEN: ${SMOKE_FAIL[*]}"
log_error " Diese Instanzen laufen, verarbeiten aber keine PDFs."
log_error " Es wurde NICHT zurueckgerollt. Journal ansehen:"
for name in "${SMOKE_FAIL[@]}"; do
log_error " journalctl -u pdf-ocr-hotfolder@${name}.service -n 80 --no-pager"
done
RC=1
fi
fi
echo
if [ "$RC" -eq 0 ]; then
log_info "Update auf $NEW_VERSION abgeschlossen ✓"