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:
@@ -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 ✓"
|
||||
|
||||
Reference in New Issue
Block a user