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:
@@ -18,17 +18,40 @@
|
||||
#
|
||||
set -Eeuo pipefail
|
||||
|
||||
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} $*"; }
|
||||
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
|
||||
|
||||
# ============================================================
|
||||
# Gemeinsame Funktionen (Logging, Pfade, venv-Pruefung, Paketliste)
|
||||
# ============================================================
|
||||
# Relativ zum Skript aufgeloest, nicht zum Arbeitsverzeichnis — update.sh wird
|
||||
# auch mit absolutem Pfad aufgerufen. Zuerst neben diesem Skript suchen (Repo),
|
||||
# danach in der Installation, wohin install.sh/update.sh die Datei mitkopieren.
|
||||
# Fehlt sie ueberall, ist hier Schluss: ein "command not found" mitten im Lauf
|
||||
# waere die schlechtere Nachricht.
|
||||
COMMON_LIB=""
|
||||
for _pdf_ocr_cand in \
|
||||
"$SCRIPT_DIR/lib/common.sh" \
|
||||
"${INSTALL_DIR:-/opt/pdf-ocr-hotfolder}/lib/common.sh"
|
||||
do
|
||||
if [ -r "$_pdf_ocr_cand" ]; then
|
||||
COMMON_LIB="$_pdf_ocr_cand"
|
||||
break
|
||||
fi
|
||||
done
|
||||
unset _pdf_ocr_cand
|
||||
if [ -z "$COMMON_LIB" ]; then
|
||||
echo "[ERROR] Gemeinsame Funktionsbibliothek lib/common.sh nicht gefunden." >&2
|
||||
echo " Gesucht in: $SCRIPT_DIR/lib/ und ${INSTALL_DIR:-/opt/pdf-ocr-hotfolder}/lib/" >&2
|
||||
echo " update.sh aus dem Repo ausfuehren (dort liegt lib/common.sh)." >&2
|
||||
# shellcheck disable=SC2317 # 'exit' greift nur, wenn das Skript nicht gesourct wurde
|
||||
return 1 2>/dev/null || exit 1
|
||||
fi
|
||||
# shellcheck source=lib/common.sh
|
||||
. "$COMMON_LIB"
|
||||
|
||||
# Pfade sind ueberschreibbar, damit die Funktionen dieses Skripts isoliert
|
||||
# gegen eine Fake-Umgebung getestet werden koennen (siehe LIB_ONLY unten).
|
||||
: "${INSTALL_DIR:=/opt/pdf-ocr-hotfolder}"
|
||||
: "${CONFIG_DIR:=/etc/pdf-ocr-hotfolder}"
|
||||
: "${SYSTEMD_DIR:=/etc/systemd/system}"
|
||||
# INSTALL_DIR/CONFIG_DIR/SYSTEMD_DIR und die Unit-Namen kommen aus lib/common.sh.
|
||||
: "${BACKUP_DIR:=/var/backups/pdf-ocr-hotfolder}"
|
||||
: "${TAR_ROOT:=/}"
|
||||
: "${VERIFY_WAIT:=6}"
|
||||
@@ -37,17 +60,14 @@ log_step() { echo -e "\n${BLUE}==>${NC} $*"; }
|
||||
# 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"
|
||||
LXC_DROPIN="$LXC_DROPIN_DIR/lxc-compat.conf"
|
||||
|
||||
REBUILD_VENV=0 # per --rebuild-venv erzwungen
|
||||
VENV_REBUILT=0 # wurde tatsaechlich neu gebaut?
|
||||
APT_WARN=0 # apt-Sync hat gemeckert
|
||||
LXC_SYNCED=0
|
||||
BACKUP_FILE=""
|
||||
PRIMARY_USER="pdfocr"
|
||||
PRIMARY_USER="$DEFAULT_USER"
|
||||
TOUCHED=0 # 1, sobald auf der Platte etwas getauscht wurde
|
||||
SMOKE_TEST=1 # per --no-smoke-test abschaltbar
|
||||
|
||||
@@ -156,61 +176,9 @@ die() { log_error "$*"; abort_handler 1 "${BASH_LINENO[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.
|
||||
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
|
||||
}
|
||||
#
|
||||
# py_mm(), pyvenv_cfg_mm() und venv_is_healthy() stehen in lib/common.sh —
|
||||
# install.sh braucht dieselbe Pruefung.
|
||||
|
||||
# Installiert die Requirements und uebersetzt pip-Fehler in eine Ansage, mit
|
||||
# der man etwas anfangen kann (typisch: Pin passt nicht mehr zum Python).
|
||||
@@ -388,21 +356,26 @@ rebuild_venv() {
|
||||
# System-Pakete
|
||||
# ============================================================
|
||||
|
||||
# Holt die Paketliste aus install.sh (einzige Quelle) und installiert sie.
|
||||
# Holt die Paketliste aus lib/common.sh (einzige Quelle) und installiert sie.
|
||||
# apt-get install ist idempotent; bereits vorhandene Pakete bleiben unberuehrt.
|
||||
# Nachinstallierte Tesseract-Sprachpakete werden NICHT angefasst (kein purge,
|
||||
# kein autoremove).
|
||||
#
|
||||
# Die Liste wird bewusst noch einmal aus der REPO-Fassung herausgeschnitten:
|
||||
# gesourct wurde evtl. die aeltere Kopie aus der Installation, beim Update
|
||||
# soll aber die neue Liste aus dem Repo gelten.
|
||||
sync_system_packages() {
|
||||
local block pkgs
|
||||
local block pkgs repo_lib
|
||||
log_step "System-Pakete abgleichen"
|
||||
|
||||
block="$(sed -n '/^# --- BEGIN apt-packages/,/^# --- END apt-packages/p' "$REPO_DIR/install.sh" 2>/dev/null || true)"
|
||||
if [ -z "$block" ] || ! printf '%s' "$block" | grep -q 'pdf_ocr_apt_packages()'; then
|
||||
log_warn "Paketliste in install.sh nicht gefunden — System-Pakete werden nicht abgeglichen."
|
||||
repo_lib="$REPO_DIR/$COMMON_LIB_REL"
|
||||
block="$(sed -n '/^# --- BEGIN apt-packages/,/^# --- END apt-packages/p' "$repo_lib" 2>/dev/null || true)"
|
||||
if [ -n "$block" ] && printf '%s' "$block" | grep -q 'pdf_ocr_apt_packages()'; then
|
||||
eval "$block"
|
||||
else
|
||||
log_warn "Paketliste in $repo_lib nicht gefunden — es gilt die geladene Fassung."
|
||||
APT_WARN=1
|
||||
return 0
|
||||
fi
|
||||
eval "$block"
|
||||
if ! declare -F pdf_ocr_apt_packages >/dev/null; then
|
||||
log_warn "pdf_ocr_apt_packages() liess sich nicht laden — uebersprungen."
|
||||
APT_WARN=1
|
||||
@@ -984,7 +957,7 @@ create_backup() {
|
||||
BACKUP_FILE="$tmp"
|
||||
log_info "Backup: $tmp ($(du -h "$tmp" 2>/dev/null | awk '{print $1}'))"
|
||||
log_info " Enthalten: Code, $CONFIG_DIR, Template-Unit, Drop-ins, pip-freeze.txt"
|
||||
log_info " NICHT enthalten: venv und die Datenverzeichnisse (/var/lib/pdf-ocr-hotfolder)"
|
||||
log_info " NICHT enthalten: venv und die Datenverzeichnisse ($DATA_ROOT)"
|
||||
log_warn " Achtung: die Instanz-Configs enthalten Klartext-Passwoerter"
|
||||
log_warn " (SMTP/Nextcloud/SFTP) — Archiv ist deshalb root-only (0600)."
|
||||
rotate_backups
|
||||
@@ -1057,12 +1030,9 @@ while [ "$#" -gt 0 ]; do
|
||||
shift
|
||||
done
|
||||
|
||||
if [ "${EUID}" -ne 0 ]; then
|
||||
log_error "Bitte als root ausfuehren: sudo ./update.sh"
|
||||
exit 1
|
||||
fi
|
||||
require_root "sudo ./update.sh"
|
||||
|
||||
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
|
||||
# SCRIPT_DIR steht schon ganz oben (fuer das Sourcen von lib/common.sh).
|
||||
if [ -f "$SCRIPT_DIR/pdf_ocr_hotfolder/__init__.py" ]; then
|
||||
REPO_DIR="$SCRIPT_DIR"
|
||||
elif [ -f "$INSTALL_DIR/.repo_path" ]; then
|
||||
@@ -1099,11 +1069,11 @@ report_instances
|
||||
|
||||
# --- Eigentuemer des Codes merken (vor dem venv-Neubau) ---
|
||||
if [ -d "$INSTALL_DIR/venv" ]; then
|
||||
PRIMARY_USER="$(stat -c '%U' "$INSTALL_DIR/venv" 2>/dev/null || echo pdfocr)"
|
||||
PRIMARY_USER="$(stat -c '%U' "$INSTALL_DIR/venv" 2>/dev/null || echo "$DEFAULT_USER")"
|
||||
else
|
||||
PRIMARY_USER="$(stat -c '%U' "$INSTALL_DIR" 2>/dev/null || echo pdfocr)"
|
||||
PRIMARY_USER="$(stat -c '%U' "$INSTALL_DIR" 2>/dev/null || echo "$DEFAULT_USER")"
|
||||
fi
|
||||
[ "$PRIMARY_USER" = "root" ] && PRIMARY_USER="pdfocr"
|
||||
[ "$PRIMARY_USER" = "root" ] && PRIMARY_USER="$DEFAULT_USER"
|
||||
|
||||
# --- System-Pakete (auch beim Update, nicht nur bei install.sh) ---
|
||||
sync_system_packages
|
||||
@@ -1118,9 +1088,7 @@ elif venv_is_healthy "$INSTALL_DIR/venv"; then
|
||||
log_info "venv ok ✓ (Python $(py_mm "$INSTALL_DIR/venv/bin/python"))"
|
||||
else
|
||||
log_warn "venv passt nicht mehr:"
|
||||
for issue in "${VENV_ISSUES[@]}"; do
|
||||
log_warn " - $issue"
|
||||
done
|
||||
report_venv_issues
|
||||
log_warn "Typische Ursache: Debian-Major-Upgrade (z.B. 12 -> 13). Die venv haengt"
|
||||
log_warn "am alten Interpreter, systemd quittiert das mit 203/EXEC."
|
||||
log_warn "-> venv wird neu gebaut."
|
||||
@@ -1145,6 +1113,10 @@ log_step "Code aktualisieren"
|
||||
TOUCHED=1
|
||||
rm -rf "$INSTALL_DIR/pdf_ocr_hotfolder"
|
||||
cp -r "$REPO_DIR/pdf_ocr_hotfolder" "$INSTALL_DIR/"
|
||||
# lib/ muss mit: ein spaeterer Lauf aus dem Installationsverzeichnis heraus
|
||||
# sucht die gemeinsamen Funktionen genau dort.
|
||||
rm -rf "${INSTALL_DIR:?}/lib"
|
||||
cp -r "$REPO_DIR/lib" "$INSTALL_DIR/"
|
||||
cp "$REPO_DIR/requirements.txt" "$INSTALL_DIR/"
|
||||
cp "$REPO_DIR/VERSION" "$INSTALL_DIR/"
|
||||
cp "$REPO_DIR/config.example.toml" "$INSTALL_DIR/"
|
||||
@@ -1168,7 +1140,7 @@ rm -f "$DEP_BEFORE" "$DEP_AFTER"
|
||||
install_units
|
||||
|
||||
log_step "Berechtigungen setzen"
|
||||
# Eigentuemer des Codes bleibt der primaere User (pdfocr); Instanzen laufen
|
||||
# Eigentuemer des Codes bleibt der primaere User (Vorgabe: pdfocr); Instanzen laufen
|
||||
# ggf. als anderer User, lesen aber nur den Code.
|
||||
chown -R "$PRIMARY_USER":"$PRIMARY_USER" "$INSTALL_DIR"
|
||||
log_info "Eigentuemer: $PRIMARY_USER"
|
||||
|
||||
Reference in New Issue
Block a user