feat: stille Datenverlust-Pfade geschlossen, gemeinsame Shell-Lib (v0.7.0)
Vor dem Rollout durchgesehen und die verbliebenen Stellen geschlossen, an denen etwas schiefgehen konnte, ohne dass es irgendwo sichtbar wurde. Datenverlust: - veraPDF: das in [verapdf].binary konfigurierte Programm wird im Preflight geprueft. Bisher galt bei falschem Pfad JEDE Datei als "nicht konform" — Ergebnis nach error/, Original geloescht (Default delete). run_verapdf() trennt jetzt ausserdem ein echtes FAIL-Urteil von einer Stoerung (VeraPdfUnavailable: nicht startbar, abgestuerzt, kein PASS/FAIL in der Ausgabe). Bei Stoerung wandern Original UND Ergebnis nach error/, das Original wird nicht entsorgt. - Gleichnamige Dateien wurden in outgoing/, error/ und beim Ordner-Upload mit abweichendem target kommentarlos ueberschrieben. Jetzt Zeitstempel daneben, mit Warnung; ProcessResult.output traegt den echten Pfad. Robustheit: - Kaputtes oder nicht lesbares TOML beim Start: Exit 2 statt Traceback. - RestartPreventExitStatus=2 in der Unit — Exit 2 (Config/Preflight) laeuft nicht mehr endlos neu, die Instanz bleibt sichtbar failed stehen. - Toter watchdog-Observer wird erkannt: Exit 3, systemd setzt den Watch neu auf. Vorher blieb die Unit "active" und verarbeitete nichts mehr. - Relative Pfade in [paths]/archive_dir/target sind ein Config-Fehler statt still unter /opt zu landen. - Fehler beim Archivieren entwertet den Durchlauf nicht mehr: Upload und Mail laufen, Sichtbarkeit ueber log.error + "OK mit Warnung"-Mail. - Nicht-PDFs in incoming/ werden beim Start-Scan gesammelt gemeldet. - Logging explizit nach stdout (die Doku versprach das schon). Struktur: - Neue lib/common.sh, von install.sh und update.sh gesourct. Die doppelte venv_is_healthy() gibt es nur noch einmal, in der gruendlichen Fassung — die schlanke in install.sh haette eine nach einem Distro-Sprung kaputte venv als gesund durchgewunken (nachgewiesen). - install.sh warnt in Containern, wenn systemd-journald nicht laeuft. Doku: Dateisystem-Festlegung (ext4/xfs/zfs, kein CIFS/NFS wegen inotify), Debian 13 in LXC auf Proxmox scheitert an journald (243/CREDENTIALS, AppArmor blockiert sd-mkdcreds) inkl. Abhilfe, echte Speicher-Messwerte, Exit-Code-Tabelle. 254 Tests gruen (vorher 152). Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
This commit is contained in:
+163
@@ -0,0 +1,163 @@
|
||||
#!/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'
|
||||
log_info() { echo -e "${GREEN}[INFO]${NC} $*"; }
|
||||
log_warn() { echo -e "${YELLOW}[WARN]${NC} $*"; }
|
||||
log_error() { echo -e "${RED}[ERROR]${NC} $*"; }
|
||||
log_step() { echo -e "\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}"
|
||||
|
||||
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 ---
|
||||
|
||||
# ============================================================
|
||||
# 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
|
||||
}
|
||||
Reference in New Issue
Block a user