1b67c846a2
Nachlese aus der Verifikation von v0.7.1 auf einem frischen Debian 12. - update.sh meldete "LXC-Drop-in nachgezogen ✓" auch bei unveraenderter Datei — dieselbe Klasse Unwahrheit wie das Ghostscript-Haekchen, das in v0.7.1 behoben wurde. Jetzt wird verglichen und ehrlich gemeldet. - "Quelle wieder entfernt" ist Teil der schlechten Nachricht und kommt als WARN statt INFO. 254 Tests gruen. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
370 lines
14 KiB
Bash
370 lines
14 KiB
Bash
#!/usr/bin/env bash
|
|
#
|
|
# PDF OCR Hotfolder — gemeinsame Shell-Funktionen
|
|
#
|
|
# Wird von install.sh und update.sh gesourct:
|
|
# source "$SCRIPT_DIR/lib/common.sh"
|
|
#
|
|
# Diese Datei fuehrt beim Sourcen NICHTS aus, was das System anfasst — sie
|
|
# enthaelt nur Definitionen und Vorgabewerte. Alle Pfade sind ueber die
|
|
# Umgebung ueberschreibbar, damit sich die Funktionen gegen eine Fake-Umgebung
|
|
# testen lassen (siehe PDF_OCR_UPDATE_LIB_ONLY in update.sh).
|
|
#
|
|
# Die Datei wird bei Installation und Update nach $INSTALL_DIR/lib/ kopiert,
|
|
# damit auch ein Lauf ausserhalb des Repos sie findet.
|
|
#
|
|
# shellcheck shell=bash
|
|
|
|
# Mehrfaches Sourcen ist harmlos, aber unnoetig.
|
|
if [ -n "${PDF_OCR_COMMON_LOADED:-}" ]; then
|
|
# shellcheck disable=SC2317 # 'exit' greift nur, wenn die Datei nicht gesourct wurde
|
|
return 0 2>/dev/null || exit 0
|
|
fi
|
|
PDF_OCR_COMMON_LOADED=1
|
|
|
|
# ============================================================
|
|
# Ausgabe
|
|
# ============================================================
|
|
|
|
RED='\033[0;31m'; GREEN='\033[0;32m'; YELLOW='\033[1;33m'; BLUE='\033[0;34m'; NC='\033[0m'
|
|
|
|
# Die Meldung selbst geht als %s durch, NICHT als %b (frueher: echo -e). Sonst
|
|
# wird ein '\n' im Text — etwa in einem Befehl, den wir zum Kopieren anzeigen —
|
|
# zu einem echten Zeilenumbruch: die Folgezeilen haetten dann kein [WARN] davor
|
|
# und wer den Block kopiert, schleppt das Praefix mit. Nur die Farbcodes
|
|
# brauchen %b. Eine Meldung pro Zeile — mehrzeilige Hinweise bitte als mehrere
|
|
# Aufrufe schreiben.
|
|
log_info() { printf '%b[INFO]%b %s\n' "$GREEN" "$NC" "$*"; }
|
|
log_warn() { printf '%b[WARN]%b %s\n' "$YELLOW" "$NC" "$*"; }
|
|
log_error() { printf '%b[ERROR]%b %s\n' "$RED" "$NC" "$*"; }
|
|
log_step() { printf '\n%b==>%b %s\n' "$BLUE" "$NC" "$*"; }
|
|
|
|
# Bricht ab, wenn nicht root. $1 = der Aufruf, der gemeint ist.
|
|
require_root() {
|
|
if [ "${EUID:-$(id -u)}" -ne 0 ]; then
|
|
log_error "Bitte als root ausfuehren: ${1:-sudo $0}"
|
|
exit 1
|
|
fi
|
|
}
|
|
|
|
# ============================================================
|
|
# Installations-Layout — einzige Quelle der Wahrheit
|
|
# ============================================================
|
|
|
|
: "${INSTALL_DIR:=/opt/pdf-ocr-hotfolder}"
|
|
: "${CONFIG_DIR:=/etc/pdf-ocr-hotfolder}"
|
|
: "${DATA_ROOT:=/var/lib/pdf-ocr-hotfolder}"
|
|
: "${SYSTEMD_DIR:=/etc/systemd/system}"
|
|
: "${DEFAULT_USER:=pdfocr}"
|
|
|
|
# Systempfade, die nur die Ghostscript-Pruefung braucht. Ueberschreibbar,
|
|
# damit sich die Pruefung gegen eine Fake-Umgebung testen laesst.
|
|
: "${OS_RELEASE_FILE:=/etc/os-release}"
|
|
: "${APT_SOURCES_LIST:=/etc/apt/sources.list}"
|
|
: "${APT_SOURCES_DIR:=/etc/apt/sources.list.d}"
|
|
|
|
SERVICE_TEMPLATE="pdf-ocr-hotfolder@.service"
|
|
LXC_DROPIN_DIR="$SYSTEMD_DIR/${SERVICE_TEMPLATE}.d"
|
|
# shellcheck disable=SC2034 # wird von install.sh und update.sh genutzt
|
|
LXC_DROPIN="$LXC_DROPIN_DIR/lxc-compat.conf"
|
|
|
|
# Pfad dieser Datei relativ zum Repo- bzw. Installationsverzeichnis. update.sh
|
|
# schneidet damit die Paketliste aus der Repo-Fassung heraus (siehe unten).
|
|
# shellcheck disable=SC2034 # wird von update.sh genutzt
|
|
COMMON_LIB_REL="lib/common.sh"
|
|
|
|
# ============================================================
|
|
# System-Pakete
|
|
# ============================================================
|
|
# Der folgende Block wird von update.sh aus DIESER Datei herausgeschnitten
|
|
# (sed auf die BEGIN/END-Marken) und dort ausgewertet: beim Update soll die
|
|
# Liste aus dem Repo gelten, nicht die vielleicht aeltere, bereits gesourcte
|
|
# aus dem Installationsverzeichnis. Die Marken und der Funktionsname duerfen
|
|
# sich deshalb nicht aendern, ohne update.sh anzupassen.
|
|
# --- BEGIN apt-packages (wird von update.sh extrahiert) ---
|
|
pdf_ocr_apt_packages() {
|
|
cat <<'PKGLIST'
|
|
python3
|
|
python3-venv
|
|
python3-pip
|
|
tesseract-ocr
|
|
tesseract-ocr-deu
|
|
tesseract-ocr-eng
|
|
ghostscript
|
|
qpdf
|
|
unpaper
|
|
pngquant
|
|
icc-profiles-free
|
|
ca-certificates
|
|
curl
|
|
PKGLIST
|
|
}
|
|
# --- END apt-packages ---
|
|
|
|
# ============================================================
|
|
# Ghostscript
|
|
# ============================================================
|
|
# Ghostscript 10.0.0 bis 10.02.0 erzeugt fehlerhafte PDF/A-Ausgaben: zusammen
|
|
# mit [ocr].pdfa_level UND skip_text = true scheitert ocrmypdf an jeder Datei.
|
|
# Mit leerem pdfa_level (der Default) ist so ein Ghostscript voellig
|
|
# unproblematisch — die Pruefung ist deshalb ein Hinweis, kein Abbruchgrund.
|
|
|
|
# Betroffen? $1 = Ausgabe von 'gs --version'.
|
|
gs_version_affected() {
|
|
case "$1" in
|
|
10.0.0|10.00.0|10.01.*|10.02.0) return 0 ;;
|
|
*) return 1 ;;
|
|
esac
|
|
}
|
|
|
|
# Version, die gs meldet; leer, wenn gs nicht aufrufbar ist.
|
|
gs_version() {
|
|
command -v gs >/dev/null 2>&1 || return 0
|
|
gs --version 2>/dev/null || true
|
|
}
|
|
|
|
# Codename der Distribution (VERSION_CODENAME aus os-release); leer, wenn
|
|
# unbekannt.
|
|
os_codename() {
|
|
[ -r "$OS_RELEASE_FILE" ] || return 0
|
|
sed -n 's/^VERSION_CODENAME=//p' "$OS_RELEASE_FILE" | tr -d '"' | head -n1
|
|
}
|
|
|
|
# Ist $1 (z.B. bookworm-backports) schon irgendwo als apt-Quelle eingetragen?
|
|
apt_release_configured() {
|
|
grep -RqsF -- "$1" "$APT_SOURCES_LIST" "$APT_SOURCES_DIR" 2>/dev/null
|
|
}
|
|
|
|
# Installierte Paketversion laut dpkg; leer, wenn das Paket nicht da ist.
|
|
apt_installed_version() {
|
|
command -v dpkg-query >/dev/null 2>&1 || return 0
|
|
dpkg-query -W -f '${Version}' "$1" 2>/dev/null || true
|
|
}
|
|
|
|
# Hoechste Version von Paket $1, die aus Release $2 (z.B. bookworm-backports)
|
|
# zu haben ist — gelesen aus der Versionstabelle von 'apt-cache policy'.
|
|
# Leer, wenn das Release das Paket nicht fuehrt. Bewusst nicht hart verdrahtet:
|
|
# liefert Debian spaeter doch ein Backports-Ghostscript, greift der Pfad.
|
|
apt_version_in_release() {
|
|
local pkg="$1" release="$2"
|
|
command -v apt-cache >/dev/null 2>&1 || return 0
|
|
LC_ALL=C apt-cache policy "$pkg" 2>/dev/null | awk -v rel="$release" '
|
|
$1 == "***" { ver = $2; next }
|
|
NF == 2 && $1 ~ /^[0-9]/ { ver = $1; next }
|
|
ver != "" && index($0, rel) { print ver; exit }
|
|
'
|
|
}
|
|
|
|
# Entfernt eine Quelle, die NUR fuer die Pruefung angelegt wurde ($2 = 1).
|
|
# War sie vorher schon da, bleibt sie unangetastet — sie kann von etwas
|
|
# anderem stammen.
|
|
gs_drop_probe_source() {
|
|
local list_file="$1" added="$2"
|
|
[ "$added" = "1" ] || return 0
|
|
rm -f "$list_file" 2>/dev/null || true
|
|
log_warn "Quelle wieder entfernt: $list_file (sie hat nichts gebracht)"
|
|
apt-get update -qq || log_warn "'apt-get update' nach dem Aufraeumen schlug fehl."
|
|
}
|
|
|
|
# Versucht, Ghostscript aus Release $1 zu aktualisieren. $2 = bisherige
|
|
# gs-Version. Rueckgabe 0 NUR, wenn hinterher nachweislich eine andere,
|
|
# nicht mehr betroffene Version laeuft.
|
|
gs_try_release_upgrade() {
|
|
local release="$1" old_ver="$2"
|
|
local list_file="$APT_SOURCES_DIR/${release}.list"
|
|
local added=0 cand installed new_ver new_installed
|
|
|
|
# Ohne apt-cache/dpkg laesst sich nicht feststellen, was dort ueberhaupt
|
|
# liegt — dann wird auch nichts eingetragen.
|
|
if ! command -v apt-cache >/dev/null 2>&1 || ! command -v dpkg >/dev/null 2>&1; then
|
|
log_warn "apt-cache oder dpkg fehlt — der Paketstand von $release ist nicht pruefbar."
|
|
return 1
|
|
fi
|
|
|
|
if apt_release_configured "$release"; then
|
|
log_info "$release ist bereits eingetragen — die Quelle bleibt, wie sie ist."
|
|
else
|
|
log_info "Trage $release voruebergehend ein, um den Paketstand zu pruefen..."
|
|
mkdir -p "$APT_SOURCES_DIR" 2>/dev/null || true
|
|
if ! printf 'deb http://deb.debian.org/debian %s main\n' "$release" > "$list_file" 2>/dev/null; then
|
|
log_warn "$list_file liess sich nicht schreiben — Pruefung nicht moeglich."
|
|
return 1
|
|
fi
|
|
added=1
|
|
if ! apt-get update -qq; then
|
|
log_warn "'apt-get update' schlug fehl — $release nicht auswertbar."
|
|
gs_drop_probe_source "$list_file" "$added"
|
|
return 1
|
|
fi
|
|
fi
|
|
|
|
installed="$(apt_installed_version ghostscript)"
|
|
cand="$(apt_version_in_release ghostscript "$release")"
|
|
|
|
if [ -z "$cand" ]; then
|
|
log_warn "$release fuehrt gar kein Paket 'ghostscript' — von dort kommt nichts."
|
|
gs_drop_probe_source "$list_file" "$added"
|
|
return 1
|
|
fi
|
|
if [ -n "$installed" ] && ! dpkg --compare-versions "$cand" gt "$installed"; then
|
|
log_warn "$release hat ghostscript $cand — nicht neuer als das installierte $installed."
|
|
gs_drop_probe_source "$list_file" "$added"
|
|
return 1
|
|
fi
|
|
|
|
log_info "$release bietet ghostscript $cand (installiert: ${installed:-unbekannt}) — installiere..."
|
|
if ! apt-get install -y -t "$release" ghostscript; then
|
|
log_warn "Installation aus $release schlug fehl — Ghostscript bleibt, wie es war."
|
|
gs_drop_probe_source "$list_file" "$added"
|
|
return 1
|
|
fi
|
|
|
|
# Erfolg wird NUR gemeldet, wenn sich die Version wirklich geaendert hat.
|
|
new_ver="$(gs_version)"
|
|
new_installed="$(apt_installed_version ghostscript)"
|
|
if [ "$new_ver" = "$old_ver" ] && [ "$new_installed" = "$installed" ]; then
|
|
log_warn "Ghostscript unveraendert: $old_ver (Paket ${installed:-unbekannt})."
|
|
log_warn "Das Upgrade aus $release hat nichts bewirkt."
|
|
gs_drop_probe_source "$list_file" "$added"
|
|
return 1
|
|
fi
|
|
|
|
log_info "Ghostscript: $old_ver -> $new_ver (Paket ${installed:-unbekannt} -> ${new_installed:-unbekannt})"
|
|
if gs_version_affected "$new_ver"; then
|
|
log_warn "Auch $new_ver liegt noch im betroffenen Bereich (10.0.0-10.02.0)."
|
|
return 1
|
|
fi
|
|
log_info "Ghostscript ist jetzt frei vom PDF/A-Fehler ✓"
|
|
return 0
|
|
}
|
|
|
|
# Sagt dem Admin, was wirklich zur Auswahl steht. Kein Verweis auf Wege, die
|
|
# es nicht gibt.
|
|
gs_report_options() {
|
|
log_warn "Es bleibt bei der betroffenen Ghostscript-Version. Echte Optionen:"
|
|
log_warn " 1. Nichts tun: [ocr].pdfa_level leer lassen (Default). Dann ist der"
|
|
log_warn " Fehler folgenlos — die Ausgabe ist nur kein PDF/A."
|
|
log_warn " 2. PDF/A wirklich noetig? Dann [ocr].skip_text = false setzen."
|
|
log_warn " 3. Neuere Distribution: Debian 13 (trixie) liefert Ghostscript 10.05.1."
|
|
log_warn "Die Installation laeuft normal weiter."
|
|
}
|
|
|
|
# Vollstaendige Pruefung fuer den Installer. Gibt immer 0 zurueck: ein
|
|
# betroffenes Ghostscript ist ein Hinweis, kein Abbruch.
|
|
check_ghostscript() {
|
|
local ver codename release answer
|
|
|
|
ver="$(gs_version)"
|
|
if [ -z "$ver" ]; then
|
|
log_warn "Ghostscript ist nicht aufrufbar — Versionspruefung uebersprungen."
|
|
return 0
|
|
fi
|
|
log_info "Ghostscript: $ver"
|
|
gs_version_affected "$ver" || return 0
|
|
|
|
echo
|
|
log_warn "═══════════════════════════════════════════════════════════════"
|
|
log_warn "Ghostscript $ver ist vom PDF/A-Fehler betroffen (10.0.0-10.02.0)."
|
|
log_warn "Betroffen sind nur Instanzen mit [ocr].pdfa_level UND skip_text = true;"
|
|
log_warn "mit leerem pdfa_level (Default) ist die Version unauffaellig."
|
|
log_warn "═══════════════════════════════════════════════════════════════"
|
|
echo
|
|
|
|
codename="$(os_codename)"
|
|
if [ "$codename" = "bookworm" ]; then
|
|
release="bookworm-backports"
|
|
# Bewusst als Frage nach dem PRUEFEN formuliert: ob dort ueberhaupt ein
|
|
# neueres Ghostscript liegt, steht erst nach 'apt-cache policy' fest.
|
|
read -r -p "In $release nach einem neueren Ghostscript suchen? [J/n]: " answer || answer=""
|
|
answer="${answer:-J}"
|
|
if [[ "$answer" =~ ^[JjYy]$ ]]; then
|
|
if gs_try_release_upgrade "$release" "$ver"; then
|
|
echo
|
|
return 0
|
|
fi
|
|
else
|
|
log_info "Uebersprungen."
|
|
fi
|
|
else
|
|
log_warn "Kein Debian 12 (bookworm) erkannt${codename:+ (Codename: $codename)} — es gibt hier keinen Backports-Weg."
|
|
fi
|
|
|
|
gs_report_options
|
|
echo
|
|
return 0
|
|
}
|
|
|
|
# ============================================================
|
|
# Python / venv
|
|
# ============================================================
|
|
|
|
# major.minor des uebergebenen Interpreters; leer, wenn er nicht laeuft.
|
|
py_mm() {
|
|
local py="$1"
|
|
"$py" -c 'import sys; print("%d.%d" % sys.version_info[:2])' 2>/dev/null || true
|
|
}
|
|
|
|
# major.minor aus pyvenv.cfg (version = / version_info =); leer, wenn unlesbar.
|
|
pyvenv_cfg_mm() {
|
|
local cfg="$1/pyvenv.cfg"
|
|
[ -f "$cfg" ] || return 0
|
|
sed -n 's/^[[:space:]]*version\(_info\)\?[[:space:]]*=[[:space:]]*\([0-9]\+\.[0-9]\+\).*/\2/p' "$cfg" | head -n1
|
|
}
|
|
|
|
# Prueft die venv gegen das aktuelle System-Python.
|
|
# Setzt VENV_ISSUES (Array) und gibt 0 zurueck, wenn alles passt.
|
|
#
|
|
# Drei Faelle fuehren zum Neubau: (1) der Interpreter der venv laeuft gar nicht
|
|
# mehr (toter Symlink nach einem Distributions-Upgrade, systemd: 203/EXEC),
|
|
# (2) er laeuft noch, ist aber eine andere Version als das System-Python
|
|
# (Debian 12 -> 13), (3) pyvenv.cfg und Interpreter widersprechen sich.
|
|
# Keine Versionsnummer ist hier hartcodiert.
|
|
VENV_ISSUES=()
|
|
venv_is_healthy() {
|
|
local venv="$1"
|
|
local sys_mm venv_mm cfg_mm
|
|
VENV_ISSUES=()
|
|
|
|
if [ ! -d "$venv" ]; then
|
|
VENV_ISSUES+=("venv-Verzeichnis fehlt: $venv")
|
|
return 1
|
|
fi
|
|
if [ ! -x "$venv/bin/python" ]; then
|
|
VENV_ISSUES+=("$venv/bin/python fehlt oder ist nicht ausfuehrbar")
|
|
return 1
|
|
fi
|
|
|
|
venv_mm="$(py_mm "$venv/bin/python")"
|
|
if [ -z "$venv_mm" ]; then
|
|
VENV_ISSUES+=("$venv/bin/python laeuft nicht (toter Symlink nach einem Distributions-Upgrade?)")
|
|
return 1
|
|
fi
|
|
|
|
sys_mm="$(py_mm "$(command -v python3 || echo /usr/bin/python3)")"
|
|
if [ -z "$sys_mm" ]; then
|
|
VENV_ISSUES+=("System-python3 laeuft nicht — venv-Pruefung nicht moeglich")
|
|
return 1
|
|
fi
|
|
|
|
if [ "$venv_mm" != "$sys_mm" ]; then
|
|
VENV_ISSUES+=("venv haengt an Python $venv_mm, das System liefert Python $sys_mm")
|
|
return 1
|
|
fi
|
|
|
|
cfg_mm="$(pyvenv_cfg_mm "$venv")"
|
|
if [ -n "$cfg_mm" ] && [ "$cfg_mm" != "$venv_mm" ]; then
|
|
VENV_ISSUES+=("pyvenv.cfg nennt Python $cfg_mm, der Interpreter meldet $venv_mm")
|
|
return 1
|
|
fi
|
|
return 0
|
|
}
|
|
|
|
# Gibt die von venv_is_healthy gesammelten Befunde als Warnungen aus.
|
|
report_venv_issues() {
|
|
local issue
|
|
for issue in "${VENV_ISSUES[@]:-}"; do
|
|
[ -n "$issue" ] || continue
|
|
log_warn " - $issue"
|
|
done
|
|
}
|