465ff8873f
Befunde aus dem ersten echten Erstinstallations-Test auf frischen Debian-12- und Debian-13-Containern. Der Weg selbst hat getragen (Basis-Install, Instanz-Anlage, zweite Instanz, Update mit Rauchtest, Rollback) — diese Stellen haben gelogen oder gefehlt: - Das Ghostscript-Backports-Angebot auf Debian 12 war ein garantierter Leerlauf, der "aktualisiert ✓" meldete: bookworm-backports enthaelt gar kein ghostscript (am Paketindex verifiziert). Die Routine sucht jetzt den echten Kandidaten, vergleicht vorher/nachher und raeumt eine nur zur Probe angelegte Quelle wieder weg. - Derselbe untaugliche Rat stand in der Preflight-Meldung, der pdfa_level-Warnung, config.example.toml und vier Doku-Dateien — ueberall ersetzt durch die echten Optionen. - Mehrzeilige Log-Hinweise waren durch "echo -e" zerrissen und nicht kopierbar; log_* nutzt jetzt printf mit %s. - pip-freeze.txt landete beim Rollback als /pip-freeze.txt im Wurzelverzeichnis, liegt jetzt unter opt/pdf-ocr-hotfolder/. - git und sudo fehlen auf dem Proxmox-Debian-Template; "sudo ./install.sh" scheitert dort. Beide Wege dokumentiert, git als Voraussetzung ergaenzt, HTTPS-Clone als Normalfall. - Rollback: systemctl start kann kein Glob. journald-Reparatur: Instanzen danach neu starten, sonst bleibt das Journal leer. 254 Tests gruen. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
474 lines
19 KiB
Bash
Executable File
474 lines
19 KiB
Bash
Executable File
#!/usr/bin/env bash
|
|
#
|
|
# PDF OCR Hotfolder — Installer / Instanz-Manager für Debian 12/13
|
|
#
|
|
# Basis-Installation erfolgt einmalig (Code, venv, systemd-Template-Unit).
|
|
# Danach werden Hotfolder-Instanzen verwaltet:
|
|
# - Beim Erstlauf: mindestens eine Instanz wird angelegt
|
|
# - Beim Folgelauf: bestehende Instanzen werden erkannt; neue können ergänzt werden
|
|
#
|
|
# Unterstützt lokale System-User und AD-User mit lokaler UID (SSSD/Winbind).
|
|
#
|
|
|
|
set -euo pipefail
|
|
|
|
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
|
|
REPO_DIR="$SCRIPT_DIR"
|
|
|
|
# ============================================================
|
|
# Gemeinsame Funktionen (Logging, Pfade, venv-Pruefung, Paketliste)
|
|
# ============================================================
|
|
# Relativ zum Skript aufgeloest, nicht zum Arbeitsverzeichnis — install.sh
|
|
# wird auch mit absolutem Pfad aufgerufen. Fehlt die Datei, ist hier Schluss;
|
|
# ein "command not found" mitten im Lauf waere die schlechtere Nachricht.
|
|
COMMON_LIB="$SCRIPT_DIR/lib/common.sh"
|
|
if [ ! -r "$COMMON_LIB" ]; then
|
|
echo "[ERROR] Gemeinsame Funktionsbibliothek nicht gefunden: $COMMON_LIB" >&2
|
|
echo " install.sh braucht lib/common.sh aus demselben Repo." >&2
|
|
echo " Repo vollstaendig auschecken und erneut ausfuehren." >&2
|
|
exit 1
|
|
fi
|
|
# shellcheck source=lib/common.sh
|
|
. "$COMMON_LIB"
|
|
|
|
require_root "sudo ./install.sh"
|
|
|
|
if [ ! -f "$REPO_DIR/pdf_ocr_hotfolder/__init__.py" ]; then
|
|
log_error "Repo-Layout nicht erkannt. install.sh aus dem Repo ausführen."
|
|
exit 1
|
|
fi
|
|
|
|
# ============================================================
|
|
# Basis-Installation (idempotent)
|
|
# ============================================================
|
|
|
|
install_base() {
|
|
log_step "System-Pakete installieren"
|
|
local -a PKGS
|
|
mapfile -t PKGS < <(pdf_ocr_apt_packages)
|
|
apt-get update -qq
|
|
apt-get install -y --no-install-recommends "${PKGS[@]}"
|
|
log_info "System-Pakete ok ✓"
|
|
|
|
# Ghostscript-Versions-Check (Issue #3 + Issue #6)
|
|
# Die eigentliche Logik steckt in lib/common.sh (check_ghostscript): sie
|
|
# prueft nur, was wirklich verfuegbar ist, meldet Erfolg ausschliesslich
|
|
# nach einem Versionsvergleich und laesst keine nutzlose apt-Quelle
|
|
# zurueck. Ein betroffenes Ghostscript ist kein Abbruchgrund.
|
|
check_ghostscript
|
|
|
|
# LXC/Container-Erkennung (Issue #4)
|
|
if systemd-detect-virt --container -q 2>/dev/null; then
|
|
VIRT_TYPE="$(systemd-detect-virt --container 2>/dev/null || echo 'container')"
|
|
log_warn "Container-Umgebung erkannt ($VIRT_TYPE)."
|
|
log_warn "systemd-Hardening kann in Containern fehlschlagen (Error 226/NAMESPACE)."
|
|
read -r -p "LXC-Kompatibilitäts-Drop-in installieren? [J/n]: " LXC_FIX
|
|
LXC_FIX="${LXC_FIX:-J}"
|
|
if [[ "$LXC_FIX" =~ ^[JjYy]$ ]]; then
|
|
mkdir -p "$LXC_DROPIN_DIR"
|
|
cp "$REPO_DIR/systemd/lxc-compat.conf" "$LXC_DROPIN"
|
|
systemctl daemon-reload
|
|
log_info "LXC-Kompatibilitäts-Drop-in installiert ✓"
|
|
fi
|
|
|
|
# Der Dienst loggt ausschliesslich nach journald. Ist journald kaputt,
|
|
# gibt es gar keine Logs — das faellt sonst erst bei der ersten
|
|
# Fehlersuche auf. Bekannter Fall: Debian 13 (systemd >= 255) in einer
|
|
# LXC auf Proxmox 8.4 — das AppArmor-Profil des Hosts blockiert den
|
|
# Credential-Mount von (sd-mkdcreds), journald scheitert mit
|
|
# 243/CREDENTIALS. Debian 12 (systemd 252) ist nicht betroffen.
|
|
if ! systemctl is-active --quiet systemd-journald 2>/dev/null; then
|
|
log_warn "ACHTUNG: systemd-journald laeuft nicht."
|
|
log_warn "Der Dienst loggt NUR nach journald — es gaebe hier keine Logs."
|
|
log_warn "Pruefen: systemctl status systemd-journald"
|
|
log_warn "Scheitert es mit 243/CREDENTIALS (Debian 13 in LXC auf"
|
|
log_warn "Proxmox), hilft ein Drop-in im Container:"
|
|
# Jede Zeile ein eigener log_warn und ohne '\n'-Escapes, damit der
|
|
# Block zeilenweise mit [WARN]-Praefix herauskommt und sich sauber
|
|
# kopieren laesst (das Schreiben der Datei bewusst als Einzeiler).
|
|
log_warn " mkdir -p /etc/systemd/system/systemd-journald.service.d"
|
|
log_warn " { echo '[Service]'; echo 'ImportCredential='; } > /etc/systemd/system/systemd-journald.service.d/no-credentials.conf"
|
|
log_warn " systemctl daemon-reload && systemctl restart systemd-journald"
|
|
log_warn "Details: docs/INSTALLATION.md, Abschnitt Troubleshooting."
|
|
read -r -p "Trotzdem fortfahren? [J/n]: " JOURNAL_GO
|
|
JOURNAL_GO="${JOURNAL_GO:-J}"
|
|
if [[ ! "$JOURNAL_GO" =~ ^[JjYy]$ ]]; then
|
|
log_error "Abbruch. Erst journald reparieren, dann erneut starten."
|
|
exit 1
|
|
fi
|
|
fi
|
|
fi
|
|
|
|
log_step "Default-User '$DEFAULT_USER' prüfen"
|
|
if id "$DEFAULT_USER" &>/dev/null; then
|
|
log_info "'$DEFAULT_USER' existiert bereits"
|
|
else
|
|
adduser --system --group --home "$DATA_ROOT" --shell /usr/sbin/nologin "$DEFAULT_USER"
|
|
log_info "System-User '$DEFAULT_USER' angelegt ✓"
|
|
fi
|
|
|
|
log_step "Verzeichnisse anlegen"
|
|
mkdir -p "$INSTALL_DIR" "$CONFIG_DIR" "$DATA_ROOT"
|
|
chown root:"$DEFAULT_USER" "$CONFIG_DIR"
|
|
chmod 750 "$CONFIG_DIR"
|
|
|
|
log_step "Code kopieren"
|
|
rm -rf "$INSTALL_DIR/pdf_ocr_hotfolder"
|
|
cp -r "$REPO_DIR/pdf_ocr_hotfolder" "$INSTALL_DIR/"
|
|
# lib/ muss mit: update.sh sucht die gemeinsamen Funktionen zuerst neben
|
|
# sich und danach in der Installation.
|
|
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/"
|
|
echo "$REPO_DIR" > "$INSTALL_DIR/.repo_path"
|
|
|
|
log_step "Python venv"
|
|
# Eine vorhandene, aber kaputte venv (z.B. nach Debian 12 -> 13) wird
|
|
# weggesichert und neu gebaut — ein reines "-d"-Vorhandensein reicht nicht.
|
|
if [ -d "$INSTALL_DIR/venv" ] && ! venv_is_healthy "$INSTALL_DIR/venv"; then
|
|
local VENV_SAVED
|
|
VENV_SAVED="$INSTALL_DIR/venv.old-$(date +%Y%m%d-%H%M%S)"
|
|
log_warn "Vorhandene venv passt nicht mehr zum System-Python (Distributions-Upgrade?)."
|
|
report_venv_issues
|
|
log_warn "Sie wird gesichert nach: $VENV_SAVED"
|
|
mv "$INSTALL_DIR/venv" "$VENV_SAVED"
|
|
fi
|
|
if [ ! -d "$INSTALL_DIR/venv" ]; then
|
|
python3 -m venv "$INSTALL_DIR/venv"
|
|
fi
|
|
"$INSTALL_DIR/venv/bin/pip" install --upgrade pip -q
|
|
if ! "$INSTALL_DIR/venv/bin/pip" install -r "$INSTALL_DIR/requirements.txt" -q; then
|
|
log_error "Requirements liessen sich nicht installieren."
|
|
log_error "Wahrscheinlich passt eine in requirements.txt gepinnte Version nicht"
|
|
log_error "zu Python $(python3 -c 'import sys; print("%d.%d" % sys.version_info[:2])' 2>/dev/null || echo '?')."
|
|
exit 1
|
|
fi
|
|
log_info "venv ok ✓"
|
|
|
|
log_step "systemd Template-Unit installieren"
|
|
cp "$REPO_DIR/systemd/$SERVICE_TEMPLATE" "$SYSTEMD_DIR/$SERVICE_TEMPLATE"
|
|
systemctl daemon-reload
|
|
log_info "Template-Unit installiert ✓"
|
|
|
|
chown -R "$DEFAULT_USER":"$DEFAULT_USER" "$INSTALL_DIR"
|
|
}
|
|
|
|
# ============================================================
|
|
# Instanz-Verwaltung
|
|
# ============================================================
|
|
|
|
list_instances() {
|
|
find "$CONFIG_DIR" -maxdepth 1 -name '*.toml' -type f 2>/dev/null \
|
|
| sed 's|.*/||; s|\.toml$||' \
|
|
| sort
|
|
}
|
|
|
|
show_existing_instances() {
|
|
local instances
|
|
mapfile -t instances < <(list_instances)
|
|
if [ "${#instances[@]}" -eq 0 ]; then
|
|
log_info "Keine bestehenden Instanzen gefunden."
|
|
return
|
|
fi
|
|
echo
|
|
log_info "Bestehende Instanzen:"
|
|
for name in "${instances[@]}"; do
|
|
local active
|
|
active=$(systemctl is-active "pdf-ocr-hotfolder@${name}.service" 2>/dev/null || echo inactive)
|
|
printf " • %-30s [%s]\n" "$name" "$active"
|
|
done
|
|
echo
|
|
}
|
|
|
|
# Liest den Wert eines Keys (erste Zuweisung am Zeilenanfang) aus einer Config
|
|
config_value() {
|
|
local file="$1" key="$2"
|
|
sed -n "s|^${key}[[:space:]]*=[[:space:]]*\"\(.*\)\"[[:space:]]*$|\1|p" "$file" | head -n1
|
|
}
|
|
|
|
# Maskiert Sonderzeichen, damit ein Pfad gefahrlos in eine sed-Ersetzung darf
|
|
# (Trennzeichen '|', Rueckverweis '&', Backslash).
|
|
sed_escape_repl() {
|
|
printf '%s' "$1" | sed -e 's/[\\&|]/\\&/g'
|
|
}
|
|
|
|
# Prueft jeden Tesseract-Sprachcode gegen die installierten Sprachdateien und
|
|
# bietet fehlende Pakete zur Installation an.
|
|
# Rueckgabe: 0 = alle Sprachen verfuegbar (oder Pruefung nicht moeglich),
|
|
# 1 = mindestens eine Sprache fehlt weiterhin.
|
|
ensure_tesseract_langs() {
|
|
local langs="$1"
|
|
local raw installed code pkg answer rc=0
|
|
local -a codes
|
|
|
|
if ! command -v tesseract >/dev/null 2>&1; then
|
|
log_warn "tesseract ist nicht aufrufbar — Sprachpruefung wird uebersprungen."
|
|
log_warn "Eingabe '$langs' wird unveraendert uebernommen."
|
|
return 0
|
|
fi
|
|
if ! raw="$(tesseract --list-langs 2>/dev/null)"; then
|
|
log_warn "'tesseract --list-langs' schlug fehl — Sprachpruefung wird uebersprungen."
|
|
log_warn "Eingabe '$langs' wird unveraendert uebernommen."
|
|
return 0
|
|
fi
|
|
installed="$(printf '%s\n' "$raw" | grep -vi '^List of available' || true)"
|
|
|
|
IFS='+' read -r -a codes <<< "$langs"
|
|
for code in "${codes[@]}"; do
|
|
[ -n "$code" ] || continue
|
|
if printf '%s\n' "$installed" | grep -qxF "$code"; then
|
|
log_info "Sprache '$code' ist installiert ✓"
|
|
continue
|
|
fi
|
|
pkg="tesseract-ocr-${code//_/-}"
|
|
log_warn "Sprache '$code' ist nicht installiert (Paket: $pkg)."
|
|
read -r -p "Paket '$pkg' jetzt installieren? [J/n]: " answer
|
|
answer="${answer:-J}"
|
|
if [[ "$answer" =~ ^[JjYy]$ ]]; then
|
|
if ! apt-get install -y --no-install-recommends "$pkg"; then
|
|
log_error "Paket '$pkg' liess sich nicht installieren."
|
|
elif tesseract --list-langs 2>/dev/null | grep -qxF "$code"; then
|
|
log_info "Paket '$pkg' installiert ✓"
|
|
continue
|
|
else
|
|
log_error "Paket '$pkg' ist da, aber tesseract kennt '$code' weiterhin nicht."
|
|
fi
|
|
fi
|
|
log_warn "Ohne die Sprachdatei '$code' scheitert das OCR bei JEDER Datei."
|
|
rc=1
|
|
done
|
|
return $rc
|
|
}
|
|
|
|
create_instance() {
|
|
echo
|
|
read -r -p "Instanz-Name (nur a-z, 0-9, -): " INST
|
|
if [[ ! "$INST" =~ ^[a-z0-9][a-z0-9-]*$ ]]; then
|
|
log_error "Ungültiger Name. Abbruch."
|
|
return 1
|
|
fi
|
|
if [ -f "$CONFIG_DIR/$INST.toml" ]; then
|
|
log_error "Instanz '$INST' existiert bereits. Abbruch."
|
|
return 1
|
|
fi
|
|
|
|
local default_base="$DATA_ROOT/$INST"
|
|
read -r -p "Basis-Pfad für Daten [$default_base]: " BASE
|
|
BASE="${BASE:-$default_base}"
|
|
|
|
read -r -p "Service-User [$DEFAULT_USER]: " SVC_USER
|
|
SVC_USER="${SVC_USER:-$DEFAULT_USER}"
|
|
|
|
local SVC_GROUP
|
|
if id "$SVC_USER" &>/dev/null; then
|
|
SVC_GROUP="$(id -gn "$SVC_USER")"
|
|
log_info "User '$SVC_USER' existiert (Gruppe: $SVC_GROUP)"
|
|
else
|
|
log_warn "User '$SVC_USER' existiert nicht."
|
|
read -r -p "Lokal als System-User anlegen? [J/n]: " CREATE_USER
|
|
CREATE_USER="${CREATE_USER:-J}"
|
|
if [[ "$CREATE_USER" =~ ^[JjYy]$ ]]; then
|
|
adduser --system --group --home "$BASE" --shell /usr/sbin/nologin "$SVC_USER"
|
|
SVC_GROUP="$SVC_USER"
|
|
log_info "User '$SVC_USER' angelegt ✓"
|
|
else
|
|
log_error "User muss existieren (z.B. via AD/SSSD). Abbruch."
|
|
return 1
|
|
fi
|
|
fi
|
|
|
|
# --- OCR-Sprachen ---
|
|
echo
|
|
log_info "Tesseract-Sprachen — gelten NUR fuer diese Instanz '$INST'."
|
|
log_info "Jede zusaetzliche Sprache kostet Laufzeit und verschlechtert zugleich"
|
|
log_info "die Erkennung — also so eng wie moeglich waehlen (z.B. nur 'deu')."
|
|
local LANGS
|
|
while true; do
|
|
read -r -p "Tesseract-Sprachen [deu+eng]: " LANGS
|
|
LANGS="${LANGS:-deu+eng}"
|
|
if [[ ! "$LANGS" =~ ^[a-z]{3}(_[A-Za-z]+)?(\+[a-z]{3}(_[A-Za-z]+)?)*$ ]]; then
|
|
log_error "Ungueltiges Format. Erwartet: Sprachcodes mit '+' verbunden,"
|
|
log_error "z.B. 'deu', 'deu+eng' oder 'chi_sim+eng'."
|
|
continue
|
|
fi
|
|
if ensure_tesseract_langs "$LANGS"; then
|
|
break
|
|
fi
|
|
log_warn "Bitte Sprachen erneut angeben (fehlende Sprache einfach weglassen)."
|
|
echo
|
|
done
|
|
|
|
# --- Original archivieren? ---
|
|
echo
|
|
local ORIG_MODE="delete"
|
|
local ARCHIVE_DIR=""
|
|
local ARCHIVE_ANS
|
|
read -r -p "Original nach erfolgreichem OCR archivieren? [j/N]: " ARCHIVE_ANS
|
|
ARCHIVE_ANS="${ARCHIVE_ANS:-N}"
|
|
if [[ "$ARCHIVE_ANS" =~ ^[JjYy]$ ]]; then
|
|
ORIG_MODE="archive"
|
|
local default_archive="$BASE/archive"
|
|
while true; do
|
|
read -r -p "Archiv-Verzeichnis [$default_archive]: " ARCHIVE_DIR
|
|
ARCHIVE_DIR="${ARCHIVE_DIR:-$default_archive}"
|
|
if [[ "$ARCHIVE_DIR" != /* ]]; then
|
|
log_error "Bitte einen absoluten Pfad angeben (beginnt mit '/')."
|
|
continue
|
|
fi
|
|
# Das Archiv darf keines der Arbeitsverzeichnisse sein: im Eingang
|
|
# wuerde das Original endlos neu aufgegriffen, in den uebrigen
|
|
# kollidiert es mit der Verarbeitung.
|
|
case "${ARCHIVE_DIR%/}" in
|
|
"$BASE/incoming"|"$BASE/outgoing"|"$BASE/working"|"$BASE/error")
|
|
log_error "Das Archiv darf nicht incoming/outgoing/working/error sein."
|
|
continue
|
|
;;
|
|
esac
|
|
break
|
|
done
|
|
else
|
|
log_info "Original wird nach erfolgreichem OCR geloescht (original_on_success = \"delete\")."
|
|
fi
|
|
|
|
log_info "Lege Datenverzeichnisse unter $BASE an..."
|
|
mkdir -p "$BASE"/{incoming,outgoing,working,error}
|
|
if [ -n "$ARCHIVE_DIR" ]; then
|
|
mkdir -p "$ARCHIVE_DIR"
|
|
fi
|
|
chown -R "$SVC_USER":"$SVC_GROUP" "$BASE"
|
|
# Innerhalb von $BASE erledigt das chown -R oben schon alles; nur ein Archiv
|
|
# ausserhalb braucht eigenes mkdir/chown.
|
|
if [ -n "$ARCHIVE_DIR" ] && [[ "$ARCHIVE_DIR" != "$BASE"/* ]] && [ "$ARCHIVE_DIR" != "$BASE" ]; then
|
|
chown -R "$SVC_USER":"$SVC_GROUP" "$ARCHIVE_DIR"
|
|
log_info "Archiv-Verzeichnis $ARCHIVE_DIR angelegt (liegt ausserhalb von $BASE)"
|
|
fi
|
|
|
|
log_info "Erstelle Config $CONFIG_DIR/$INST.toml..."
|
|
# Verankerte Ausdruecke (Zeilenanfang + Key + '='), damit die deutschen
|
|
# Kommentarzeilen ueber den Keys unangetastet bleiben.
|
|
local ESC_BASE ESC_ARCHIVE ESC_LANGS
|
|
ESC_BASE="$(sed_escape_repl "$BASE")"
|
|
ESC_ARCHIVE="$(sed_escape_repl "$ARCHIVE_DIR")"
|
|
ESC_LANGS="$(sed_escape_repl "$LANGS")"
|
|
sed \
|
|
-e "s|^incoming[[:space:]]*=.*|incoming = \"$ESC_BASE/incoming\"|" \
|
|
-e "s|^outgoing[[:space:]]*=.*|outgoing = \"$ESC_BASE/outgoing\"|" \
|
|
-e "s|^working[[:space:]]*=.*|working = \"$ESC_BASE/working\"|" \
|
|
-e "s|^error[[:space:]]*=.*|error = \"$ESC_BASE/error\"|" \
|
|
-e "s|^languages[[:space:]]*=.*|languages = \"$ESC_LANGS\"|" \
|
|
-e "s|^original_on_success[[:space:]]*=.*|original_on_success = \"$ORIG_MODE\"|" \
|
|
-e "s|^archive_dir[[:space:]]*=.*|archive_dir = \"$ESC_ARCHIVE\"|" \
|
|
"$INSTALL_DIR/config.example.toml" > "$CONFIG_DIR/$INST.toml"
|
|
chown root:"$SVC_GROUP" "$CONFIG_DIR/$INST.toml"
|
|
chmod 640 "$CONFIG_DIR/$INST.toml"
|
|
|
|
# Erzeugte Config gegenpruefen: tragen die drei Keys wirklich die Auswahl?
|
|
local CFG_OK=1 got key want
|
|
for key in languages original_on_success archive_dir; do
|
|
case "$key" in
|
|
languages) want="$LANGS" ;;
|
|
original_on_success) want="$ORIG_MODE" ;;
|
|
archive_dir) want="$ARCHIVE_DIR" ;;
|
|
esac
|
|
got="$(config_value "$CONFIG_DIR/$INST.toml" "$key")"
|
|
if [ "$got" != "$want" ]; then
|
|
log_error "Config-Pruefung: $key ist \"$got\", erwartet \"$want\""
|
|
CFG_OK=0
|
|
fi
|
|
done
|
|
if [ "$CFG_OK" -eq 1 ]; then
|
|
log_info "Config-Pruefung ok ✓ (languages / original_on_success / archive_dir)"
|
|
else
|
|
log_warn "Bitte $CONFIG_DIR/$INST.toml von Hand nachziehen."
|
|
fi
|
|
|
|
# Drop-in für abweichenden Service-User
|
|
if [ "$SVC_USER" != "$DEFAULT_USER" ]; then
|
|
local DROPIN_DIR="$SYSTEMD_DIR/pdf-ocr-hotfolder@${INST}.service.d"
|
|
mkdir -p "$DROPIN_DIR"
|
|
cat > "$DROPIN_DIR/user.conf" <<EOF
|
|
[Service]
|
|
User=$SVC_USER
|
|
Group=$SVC_GROUP
|
|
EOF
|
|
log_info "Drop-in für User '$SVC_USER' erstellt"
|
|
fi
|
|
|
|
systemctl daemon-reload
|
|
systemctl enable --now "pdf-ocr-hotfolder@${INST}.service"
|
|
sleep 1
|
|
if systemctl is-active --quiet "pdf-ocr-hotfolder@${INST}.service"; then
|
|
log_info "✅ Instanz '$INST' läuft"
|
|
else
|
|
log_warn "Instanz '$INST' läuft nicht. Logs: journalctl -u pdf-ocr-hotfolder@${INST}"
|
|
fi
|
|
|
|
echo
|
|
echo " Config: $CONFIG_DIR/$INST.toml"
|
|
echo " Eingang: $BASE/incoming"
|
|
echo " Ausgang: $BASE/outgoing"
|
|
echo " User: $SVC_USER ($SVC_GROUP)"
|
|
if [ "$CFG_OK" -eq 1 ]; then
|
|
echo " Sprachen: $LANGS"
|
|
if [ "$ORIG_MODE" = "archive" ]; then
|
|
echo " Archiv: $ARCHIVE_DIR"
|
|
fi
|
|
fi
|
|
echo
|
|
}
|
|
|
|
# ============================================================
|
|
# Main
|
|
# ============================================================
|
|
|
|
echo
|
|
echo "=========================================="
|
|
echo " PDF OCR Hotfolder — Installer"
|
|
echo "=========================================="
|
|
|
|
# Auch eine vorhandene, aber kaputte venv loest die Basis-Installation aus
|
|
# (sonst wuerde install.sh nach einem Distributions-Upgrade nichts reparieren).
|
|
if [ ! -d "$INSTALL_DIR/venv" ] || [ ! -f "$SYSTEMD_DIR/$SERVICE_TEMPLATE" ]; then
|
|
log_step "Basis-Installation"
|
|
install_base
|
|
elif ! venv_is_healthy "$INSTALL_DIR/venv"; then
|
|
log_warn "Basis-Installation vorhanden, aber die venv passt nicht mehr zum System-Python."
|
|
report_venv_issues
|
|
log_step "Basis-Installation wird zur Reparatur erneut ausgefuehrt"
|
|
install_base
|
|
else
|
|
log_info "Basis-Installation bereits vorhanden ($INSTALL_DIR)"
|
|
log_info "Überspringe Basis-Setup (nutze update.sh für Code-Updates)"
|
|
fi
|
|
|
|
show_existing_instances
|
|
|
|
# Erste Instanz ist Pflicht, wenn noch keine vorhanden
|
|
mapfile -t existing < <(list_instances)
|
|
if [ "${#existing[@]}" -eq 0 ]; then
|
|
log_info "Lege erste Hotfolder-Instanz an."
|
|
create_instance || true
|
|
fi
|
|
|
|
while true; do
|
|
read -r -p "Weitere Instanz anlegen? [j/N]: " MORE
|
|
MORE="${MORE:-N}"
|
|
if [[ "$MORE" =~ ^[JjYy]$ ]]; then
|
|
create_instance || true
|
|
else
|
|
break
|
|
fi
|
|
done
|
|
|
|
echo
|
|
echo "=========================================="
|
|
echo " Fertig"
|
|
echo "=========================================="
|
|
show_existing_instances
|
|
echo " Logs: journalctl -u pdf-ocr-hotfolder@<instanz> -f"
|
|
echo " Neustart: systemctl restart pdf-ocr-hotfolder@<instanz>"
|
|
echo " Update: sudo ./update.sh"
|
|
echo
|