From 985a33d3f917c72c7bac708972bdb2557f0d7f62 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Dominik=20H=C3=B6fling?= Date: Wed, 8 Apr 2026 14:31:58 +0200 Subject: [PATCH] feat: Multi-Instanz-Support via systemd Template-Unit (v0.2.0) - pdf-ocr-hotfolder@.service mit Config pro Instanz - install.sh als Instanz-Manager: erkennt bestehende, fragt nach weiteren - Optional eigener Service-User pro Instanz (systemd drop-in) - update.sh stoppt/startet alle aktiven Instanzen automatisch Co-Authored-By: Claude Opus 4.6 --- AI_AGENT_BRIEFING.md | 61 +++- CHANGELOG.md | 15 + README.md | 79 +++-- VERSION | 2 +- install.sh | 304 +++++++++++------- ...der.service => pdf-ocr-hotfolder@.service} | 8 +- update.sh | 58 ++-- 7 files changed, 358 insertions(+), 169 deletions(-) rename systemd/{pdf-ocr-hotfolder.service => pdf-ocr-hotfolder@.service} (77%) diff --git a/AI_AGENT_BRIEFING.md b/AI_AGENT_BRIEFING.md index d4fce93..84f6fd6 100644 --- a/AI_AGENT_BRIEFING.md +++ b/AI_AGENT_BRIEFING.md @@ -1,8 +1,8 @@ # AI Agent Briefing — PDF OCR Hotfolder **Zuletzt aktualisiert:** 2026-04-08 -**Version:** 0.1.0 -**Status:** Initiale Implementation, nicht produktiv getestet +**Version:** 0.2.0 +**Status:** Multi-Instanz-Support, nicht produktiv getestet ## 🎯 Projektziel @@ -20,7 +20,7 @@ pdf-ocr-hotfolder/ │ ├── processor.py # ocrmypdf + veraPDF │ └── uploaders.py # folder, nextcloud (WebDAV), sftp, email ├── systemd/ -│ └── pdf-ocr-hotfolder.service # Template (Platzhalter __SERVICE_USER__/__SERVICE_GROUP__) +│ └── pdf-ocr-hotfolder@.service # systemd Template-Unit (Instanz = %i) ├── config.example.toml ├── install.sh # Interaktiver Installer ├── update.sh # Update aus Repo @@ -43,25 +43,58 @@ pdf-ocr-hotfolder/ | Email | `smtplib` (stdlib) | | Service | systemd | -## 🖥️ Installations-Layout +## 🖥️ Installations-Layout (Multi-Instanz) | Pfad | Inhalt | |------|--------| -| `/opt/pdf-ocr-hotfolder/` | Code + venv (`venv/bin/python`) | -| `/etc/pdf-ocr-hotfolder/config.toml` | Konfiguration (mode 640, root:) | -| `/var/lib/pdf-ocr-hotfolder/{incoming,working,outgoing,error}/` | Datenverzeichnisse | -| `/var/log/pdf-ocr-hotfolder/` | Logs (zusätzlich zu journald) | -| `/etc/systemd/system/pdf-ocr-hotfolder.service` | systemd-Unit | +| `/opt/pdf-ocr-hotfolder/` | Code + venv (für alle Instanzen gemeinsam) | +| `/etc/pdf-ocr-hotfolder/.toml` | Config pro Instanz (mode 640, root:) | +| `/etc/systemd/system/pdf-ocr-hotfolder@.service` | Template-Unit | +| `/etc/systemd/system/pdf-ocr-hotfolder@.service.d/user.conf` | Drop-in für abweichenden User (optional) | +| `/var/lib/pdf-ocr-hotfolder//{incoming,working,outgoing,error}/` | Daten pro Instanz | +| `/var/log/pdf-ocr-hotfolder/` | Logs | | `/var/backups/pdf-ocr-hotfolder/` | Update-Backups | ## 👤 Service-User -Der Installer fragt interaktiv: -1. Username (default `pdfocr`) -2. Falls User existiert (lokal oder AD via SSSD/Winbind): wird übernommen, primäre Gruppe automatisch erkannt -3. Falls nicht: Frage nach lokaler Anlage als System-User +- Basis-Install legt Default-User `pdfocr` an (als System-User, falls nicht schon vorhanden) +- Beim Anlegen einer Instanz fragt der Installer nach dem Service-User (default `pdfocr`) +- Wird ein **abweichender** User gewählt, wird ein systemd-Drop-in erstellt (`pdf-ocr-hotfolder@.service.d/user.conf`) mit `User=/Group=` Override +- Existierende User (lokal oder AD via SSSD/Winbind) werden übernommen, primäre Gruppe via `id -gn` ermittelt +- Bei AD-Usern mit lokaler UID werden Datei-Berechtigungen über die UID gesetzt — transparent -**Wichtig:** Bei AD-Usern mit lokaler UID werden Datei-Berechtigungen über die UID gesetzt — funktioniert transparent. +## 🗂️ Instanz-Management + +`install.sh` ist gleichzeitig **Installer und Instanz-Manager**: + +- Erster Lauf: Basis-Install + erste Instanz anlegen (Pflicht) +- Folgender Lauf: Basis-Install wird übersprungen, bestehende Instanzen werden gelistet, weitere Instanzen können ergänzt werden +- Eingaben pro Instanz: Name (`[a-z0-9-]+`), Basis-Pfad (default `/var/lib/pdf-ocr-hotfolder/`), Service-User +- `config.toml` wird aus `config.example.toml` mit sed-substituierten Pfaden generiert +- Instanz wird sofort `enable --now` gestartet + +Manuelles Löschen einer Instanz: +```bash +systemctl disable --now pdf-ocr-hotfolder@ +rm /etc/pdf-ocr-hotfolder/.toml +rm -rf /etc/systemd/system/pdf-ocr-hotfolder@.service.d +systemctl daemon-reload +# Datenverzeichnis /var/lib/pdf-ocr-hotfolder/ manuell aufräumen +``` + +## 🔄 Update-Verhalten + +`update.sh`: +1. Ermittelt alle aktiven `pdf-ocr-hotfolder@*.service` Units +2. Stoppt diese +3. Backup nach `/var/backups/pdf-ocr-hotfolder/` +4. Kopiert Code + requirements + VERSION + config.example aus dem Repo +5. `pip install --upgrade` im venv +6. Aktualisiert Template-Unit + `daemon-reload` +7. Startet alle zuvor aktiven Instanzen wieder +8. Exit 1 wenn eine Instanz nicht mehr hochkommt + +Config-Dateien werden **nie** überschrieben. ## 🔄 Verarbeitungs-Flow diff --git a/CHANGELOG.md b/CHANGELOG.md index 20f895e..c04cb41 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,20 @@ # Changelog +## [0.2.0] - 2026-04-08 + +### Added +- **Multi-Instanz-Support** via systemd Template-Unit `pdf-ocr-hotfolder@.service` +- Pro Instanz: eigene Config (`/etc/pdf-ocr-hotfolder/.toml`), eigene Datenverzeichnisse (`/var/lib/pdf-ocr-hotfolder//…`), optional eigener Service-User via Drop-in +- **Instanz-Manager in `install.sh`**: erkennt bestehende Instanzen bei Re-Run, fragt nach weiteren, listet Namen + Status +- `update.sh` stoppt/startet automatisch **alle** laufenden Instanzen + +### Changed +- Single-Unit `pdf-ocr-hotfolder.service` durch Template-Unit `pdf-ocr-hotfolder@.service` ersetzt +- Installer fragt nicht mehr einmalig nach Service-User, sondern **pro Instanz** + +### Removed +- Alte Single-Config unter `/etc/pdf-ocr-hotfolder/config.toml` — wird nicht mehr erzeugt + ## [0.1.0] - 2026-04-08 ### Added diff --git a/README.md b/README.md index a8c2659..580df88 100644 --- a/README.md +++ b/README.md @@ -23,35 +23,56 @@ cd pdf-ocr-hotfolder sudo ./install.sh ``` -Der Installer fragt nach dem Service-User. Standardmäßig wird ein lokaler System-User `pdfocr` angelegt. Wenn der User bereits existiert (z.B. AD via SSSD), wird er einfach übernommen. +Der Installer: +1. Installiert einmalig Code + venv + systemd-Template-Unit +2. Fragt nach Instanz-Name, Basis-Pfad, Service-User +3. Legt so viele Hotfolder-Instanzen an, wie du willst (`Weitere Instanz anlegen? [j/N]`) -Danach Konfiguration anpassen: - -```bash -sudo nano /etc/pdf-ocr-hotfolder/config.toml -sudo systemctl restart pdf-ocr-hotfolder -``` +Bei jedem erneuten Aufruf erkennt der Installer bestehende Instanzen und fragt nur nach neuen. Test: ```bash -cp irgendein-scan.pdf /var/lib/pdf-ocr-hotfolder/incoming/ -journalctl -u pdf-ocr-hotfolder -f +cp irgendein-scan.pdf /var/lib/pdf-ocr-hotfolder//incoming/ +journalctl -u pdf-ocr-hotfolder@ -f ``` -Nach wenigen Sekunden liegt das OCR-PDF unter `/var/lib/pdf-ocr-hotfolder/outgoing/OCR_irgendein-scan.pdf`. +Nach wenigen Sekunden liegt das OCR-PDF im `outgoing/`-Ordner der Instanz. + +## Multi-Instanz-Betrieb + +Das Tool arbeitet komplett **instanzbasiert** über eine systemd Template-Unit `pdf-ocr-hotfolder@.service`. Jede Instanz hat: + +- eigene Config-Datei: `/etc/pdf-ocr-hotfolder/.toml` +- eigene Datenverzeichnisse: `/var/lib/pdf-ocr-hotfolder//{incoming,working,outgoing,error}/` +- eigene systemd-Unit: `pdf-ocr-hotfolder@.service` +- optional eigenen Service-User (via Drop-in `/etc/systemd/system/pdf-ocr-hotfolder@.service.d/user.conf`) + +Beispiel für 3 Hotfolder: + +```bash +sudo ./install.sh +# → legt z.B. kunde-a, kunde-b, buchhaltung an + +systemctl status 'pdf-ocr-hotfolder@*' +journalctl -u pdf-ocr-hotfolder@kunde-a -f +``` + +Manuell eine weitere Instanz anlegen geht auch — einfach `install.sh` erneut starten, er fragt wieder nach. ## Verzeichnisse | Pfad | Zweck | |------|-------| -| `/etc/pdf-ocr-hotfolder/config.toml` | Konfiguration | -| `/var/lib/pdf-ocr-hotfolder/incoming` | Eingang (Scanner schreibt hier rein) | -| `/var/lib/pdf-ocr-hotfolder/working` | Arbeitsverzeichnis während OCR | -| `/var/lib/pdf-ocr-hotfolder/outgoing` | Ausgang (fertige PDFs) | -| `/var/lib/pdf-ocr-hotfolder/error` | PDFs, die nicht verarbeitet werden konnten | -| `/opt/pdf-ocr-hotfolder/` | Code + venv | -| `/var/log/pdf-ocr-hotfolder/` | Logs (zusätzlich zu journald) | +| `/opt/pdf-ocr-hotfolder/` | Code + venv (für alle Instanzen gemeinsam) | +| `/etc/pdf-ocr-hotfolder/.toml` | Config pro Instanz | +| `/etc/systemd/system/pdf-ocr-hotfolder@.service` | systemd Template-Unit | +| `/var/lib/pdf-ocr-hotfolder//incoming` | Eingang (Scanner schreibt hier rein) | +| `/var/lib/pdf-ocr-hotfolder//working` | Arbeitsverzeichnis während OCR | +| `/var/lib/pdf-ocr-hotfolder//outgoing` | Ausgang (fertige PDFs) | +| `/var/lib/pdf-ocr-hotfolder//error` | Fehlgeschlagene PDFs | +| `/var/log/pdf-ocr-hotfolder/` | Logs (zusätzlich zu journald) | +| `/var/backups/pdf-ocr-hotfolder/` | Update-Backups | ## Konfiguration @@ -101,9 +122,14 @@ on = "errors" # always | errors | never ## Service-Verwaltung ```bash -sudo systemctl status pdf-ocr-hotfolder -sudo systemctl restart pdf-ocr-hotfolder -journalctl -u pdf-ocr-hotfolder -f +# Eine bestimmte Instanz +sudo systemctl status pdf-ocr-hotfolder@kunde-a +sudo systemctl restart pdf-ocr-hotfolder@kunde-a +journalctl -u pdf-ocr-hotfolder@kunde-a -f + +# Alle Instanzen +sudo systemctl status 'pdf-ocr-hotfolder@*' +sudo systemctl restart 'pdf-ocr-hotfolder@*' ``` ## Update @@ -114,15 +140,22 @@ git pull sudo ./update.sh ``` +`update.sh`: +1. Stoppt alle laufenden Instanzen +2. Sichert den alten Code nach `/var/backups/pdf-ocr-hotfolder/` +3. Aktualisiert Code + venv + systemd-Template-Unit in `/opt/pdf-ocr-hotfolder/` +4. Startet alle zuvor laufenden Instanzen neu + +Config-Dateien unter `/etc/pdf-ocr-hotfolder/` werden **nie** überschrieben. Das Repo muss bestehen bleiben — `update.sh` kopiert daraus. ## Manueller Lauf (One-Shot) -Bestehende PDFs im Eingang einmalig verarbeiten und beenden: +Bestehende PDFs einer Instanz einmalig verarbeiten und beenden: ```bash sudo -u pdfocr /opt/pdf-ocr-hotfolder/venv/bin/python -m pdf_ocr_hotfolder \ - --config /etc/pdf-ocr-hotfolder/config.toml --once + --config /etc/pdf-ocr-hotfolder/kunde-a.toml --once ``` ## Troubleshooting @@ -172,5 +205,5 @@ MIT — © Sonith UG --- -**Version:** 0.1.0 +**Version:** 0.2.0 **Repo:** https://gitea.sonith.de/sonith_ug/pdf-ocr-hotfolder diff --git a/VERSION b/VERSION index 6e8bf73..0ea3a94 100644 --- a/VERSION +++ b/VERSION @@ -1 +1 @@ -0.1.0 +0.2.0 diff --git a/install.sh b/install.sh index e4dd871..d7aff7a 100755 --- a/install.sh +++ b/install.sh @@ -1,11 +1,13 @@ #!/usr/bin/env bash # -# PDF OCR Hotfolder — Installer für Debian 12/13 +# PDF OCR Hotfolder — Installer / Instanz-Manager für Debian 12/13 # -# Fragt interaktiv nach dem Service-User. Unterstützt: -# - Lokal anlegen (neuer System-User) -# - Bereits existierender lokaler User -# - AD-User mit lokaler UID (z.B. via SSSD/Winbind) +# 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 @@ -14,7 +16,7 @@ RED='\033[0;31m'; GREEN='\033[0;32m'; YELLOW='\033[1;33m'; BLUE='\033[0;34m'; NC 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 "${BLUE}==>${NC} $*"; } +log_step() { echo -e "\n${BLUE}==>${NC} $*"; } if [ "${EUID}" -ne 0 ]; then log_error "Bitte als root ausführen: sudo ./install.sh" @@ -23,9 +25,10 @@ fi INSTALL_DIR="/opt/pdf-ocr-hotfolder" CONFIG_DIR="/etc/pdf-ocr-hotfolder" -DATA_DIR="/var/lib/pdf-ocr-hotfolder" +DATA_ROOT="/var/lib/pdf-ocr-hotfolder" LOG_DIR="/var/log/pdf-ocr-hotfolder" -SERVICE_NAME="pdf-ocr-hotfolder" +SERVICE_TEMPLATE="pdf-ocr-hotfolder@.service" +DEFAULT_USER="pdfocr" SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)" REPO_DIR="$SCRIPT_DIR" @@ -35,123 +38,206 @@ if [ ! -f "$REPO_DIR/pdf_ocr_hotfolder/__init__.py" ]; then exit 1 fi -echo -echo "==========================================" -echo " PDF OCR Hotfolder — Installation" -echo "==========================================" -echo +# ============================================================ +# Basis-Installation (idempotent) +# ============================================================ -# ============ 1. System-Dependencies ============ -log_step "Installiere System-Pakete" +install_base() { + log_step "System-Pakete installieren" + apt-get update -qq + apt-get install -y --no-install-recommends \ + python3 python3-venv python3-pip \ + tesseract-ocr tesseract-ocr-deu tesseract-ocr-eng \ + ghostscript qpdf unpaper pngquant \ + icc-profiles-free ca-certificates curl + log_info "System-Pakete ok ✓" -apt-get update -qq -apt-get install -y --no-install-recommends \ - python3 python3-venv python3-pip \ - tesseract-ocr tesseract-ocr-deu tesseract-ocr-eng \ - ghostscript qpdf unpaper pngquant \ - icc-profiles-free \ - ca-certificates curl - -log_info "System-Pakete installiert ✓" - -# ============ 2. Service-User ============ -log_step "Service-User konfigurieren" - -read -r -p "Service-User-Name [pdfocr]: " SERVICE_USER -SERVICE_USER="${SERVICE_USER:-pdfocr}" - -if id "$SERVICE_USER" &>/dev/null; then - log_info "User '$SERVICE_USER' existiert bereits (lokal oder via AD)." - SERVICE_GROUP="$(id -gn "$SERVICE_USER")" - log_info "Verwende bestehende primäre Gruppe: $SERVICE_GROUP" -else - log_warn "User '$SERVICE_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 "$DATA_DIR" --shell /usr/sbin/nologin "$SERVICE_USER" - SERVICE_GROUP="$SERVICE_USER" - log_info "Lokaler System-User '$SERVICE_USER' angelegt ✓" + log_step "Default-User '$DEFAULT_USER' prüfen" + if id "$DEFAULT_USER" &>/dev/null; then + log_info "'$DEFAULT_USER' existiert bereits" else - log_error "User '$SERVICE_USER' muss vor der Installation existieren (z.B. via AD/SSSD)." - log_error "Lege ihn an oder wähle einen existierenden Namen." - exit 1 + adduser --system --group --home "$DATA_ROOT" --shell /usr/sbin/nologin "$DEFAULT_USER" + log_info "System-User '$DEFAULT_USER' angelegt ✓" fi -fi -# ============ 3. Verzeichnisse ============ -log_step "Verzeichnisse erstellen" + log_step "Verzeichnisse anlegen" + mkdir -p "$INSTALL_DIR" "$CONFIG_DIR" "$DATA_ROOT" "$LOG_DIR" + chown root:"$DEFAULT_USER" "$CONFIG_DIR" + chmod 750 "$CONFIG_DIR" -mkdir -p "$INSTALL_DIR" "$CONFIG_DIR" "$LOG_DIR" -mkdir -p "$DATA_DIR"/{incoming,outgoing,working,error} + log_step "Code kopieren" + rm -rf "$INSTALL_DIR/pdf_ocr_hotfolder" + cp -r "$REPO_DIR/pdf_ocr_hotfolder" "$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" -cp -r "$REPO_DIR/pdf_ocr_hotfolder" "$INSTALL_DIR/" -cp "$REPO_DIR/requirements.txt" "$INSTALL_DIR/" -cp "$REPO_DIR/VERSION" "$INSTALL_DIR/" -echo "$REPO_DIR" > "$INSTALL_DIR/.repo_path" + log_step "Python venv" + if [ ! -d "$INSTALL_DIR/venv" ]; then + python3 -m venv "$INSTALL_DIR/venv" + fi + "$INSTALL_DIR/venv/bin/pip" install --upgrade pip -q + "$INSTALL_DIR/venv/bin/pip" install -r "$INSTALL_DIR/requirements.txt" -q + log_info "venv ok ✓" -if [ ! -f "$CONFIG_DIR/config.toml" ]; then - cp "$REPO_DIR/config.example.toml" "$CONFIG_DIR/config.toml" - log_info "Beispiel-Konfig nach $CONFIG_DIR/config.toml kopiert" + log_step "systemd Template-Unit installieren" + cp "$REPO_DIR/systemd/$SERVICE_TEMPLATE" "/etc/systemd/system/$SERVICE_TEMPLATE" + systemctl daemon-reload + log_info "Template-Unit installiert ✓" + + chown -R "$DEFAULT_USER":"$DEFAULT_USER" "$INSTALL_DIR" "$LOG_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 +} + +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 + + log_info "Lege Datenverzeichnisse unter $BASE an..." + mkdir -p "$BASE"/{incoming,outgoing,working,error} + chown -R "$SVC_USER":"$SVC_GROUP" "$BASE" + + log_info "Erstelle Config $CONFIG_DIR/$INST.toml..." + sed \ + -e "s|/var/lib/pdf-ocr-hotfolder/incoming|$BASE/incoming|" \ + -e "s|/var/lib/pdf-ocr-hotfolder/outgoing|$BASE/outgoing|" \ + -e "s|/var/lib/pdf-ocr-hotfolder/working|$BASE/working|" \ + -e "s|/var/lib/pdf-ocr-hotfolder/error|$BASE/error|" \ + "$INSTALL_DIR/config.example.toml" > "$CONFIG_DIR/$INST.toml" + chown root:"$SVC_GROUP" "$CONFIG_DIR/$INST.toml" + chmod 640 "$CONFIG_DIR/$INST.toml" + + # Drop-in für abweichenden Service-User + if [ "$SVC_USER" != "$DEFAULT_USER" ]; then + local DROPIN_DIR="/etc/systemd/system/pdf-ocr-hotfolder@${INST}.service.d" + mkdir -p "$DROPIN_DIR" + cat > "$DROPIN_DIR/user.conf" < "/etc/systemd/system/${SERVICE_NAME}.service" - -systemctl daemon-reload -systemctl enable "${SERVICE_NAME}.service" - -log_info "systemd-Unit installiert & enabled ✓" - -# ============ 7. Start ============ -log_step "Service starten" -systemctl restart "${SERVICE_NAME}.service" -sleep 2 -systemctl --no-pager --lines=10 status "${SERVICE_NAME}.service" || true +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 " Installation abgeschlossen" +echo " Fertig" echo "==========================================" -echo -echo " Konfiguration: $CONFIG_DIR/config.toml" -echo " Eingang: $DATA_DIR/incoming" -echo " Ausgang: $DATA_DIR/outgoing" -echo " Service-User: $SERVICE_USER ($SERVICE_GROUP)" -echo -echo " Logs: journalctl -u $SERVICE_NAME -f" -echo " Update: sudo ./update.sh" +show_existing_instances +echo " Logs: journalctl -u pdf-ocr-hotfolder@ -f" +echo " Neustart: systemctl restart pdf-ocr-hotfolder@" +echo " Update: sudo ./update.sh" echo diff --git a/systemd/pdf-ocr-hotfolder.service b/systemd/pdf-ocr-hotfolder@.service similarity index 77% rename from systemd/pdf-ocr-hotfolder.service rename to systemd/pdf-ocr-hotfolder@.service index 5ae1963..7d43a8c 100644 --- a/systemd/pdf-ocr-hotfolder.service +++ b/systemd/pdf-ocr-hotfolder@.service @@ -1,13 +1,13 @@ [Unit] -Description=PDF OCR Hotfolder +Description=PDF OCR Hotfolder (Instance: %i) After=network-online.target Wants=network-online.target [Service] Type=simple -User=__SERVICE_USER__ -Group=__SERVICE_GROUP__ -ExecStart=/opt/pdf-ocr-hotfolder/venv/bin/python -m pdf_ocr_hotfolder --config /etc/pdf-ocr-hotfolder/config.toml +User=pdfocr +Group=pdfocr +ExecStart=/opt/pdf-ocr-hotfolder/venv/bin/python -m pdf_ocr_hotfolder --config /etc/pdf-ocr-hotfolder/%i.toml Restart=on-failure RestartSec=5 KillMode=mixed diff --git a/update.sh b/update.sh index cc40581..cf83be6 100755 --- a/update.sh +++ b/update.sh @@ -2,6 +2,10 @@ # # PDF OCR Hotfolder — Update-Script # +# Aktualisiert Code und venv unter /opt/pdf-ocr-hotfolder/ sowie die +# systemd Template-Unit. Danach werden alle laufenden Instanzen neu gestartet. +# Config-Dateien unter /etc/pdf-ocr-hotfolder/ bleiben unverändert. +# set -euo pipefail RED='\033[0;31m'; GREEN='\033[0;32m'; YELLOW='\033[1;33m'; NC='\033[0m' @@ -15,7 +19,8 @@ if [ "${EUID}" -ne 0 ]; then fi INSTALL_DIR="/opt/pdf-ocr-hotfolder" -SERVICE_NAME="pdf-ocr-hotfolder" +CONFIG_DIR="/etc/pdf-ocr-hotfolder" +SERVICE_TEMPLATE="pdf-ocr-hotfolder@.service" SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)" if [ -f "$SCRIPT_DIR/pdf_ocr_hotfolder/__init__.py" ]; then @@ -42,12 +47,18 @@ log_info "Install: $INSTALL_DIR" log_info "Version: $OLD_VERSION → $NEW_VERSION" echo -# Service-User aus systemd-Unit lesen -SERVICE_USER="$(awk -F= '/^User=/{print $2}' /etc/systemd/system/${SERVICE_NAME}.service 2>/dev/null || echo pdfocr)" -SERVICE_GROUP="$(awk -F= '/^Group=/{print $2}' /etc/systemd/system/${SERVICE_NAME}.service 2>/dev/null || echo pdfocr)" +# Laufende Instanzen ermitteln +mapfile -t RUNNING < <(systemctl list-units --no-legend --state=active 'pdf-ocr-hotfolder@*.service' 2>/dev/null | awk '{print $1}') +if [ "${#RUNNING[@]}" -gt 0 ]; then + log_info "Laufende Instanzen: ${RUNNING[*]}" +else + log_info "Keine laufenden Instanzen." +fi -log_info "Stoppe Service..." -systemctl stop "${SERVICE_NAME}.service" 2>/dev/null || true +log_info "Stoppe laufende Instanzen..." +for unit in "${RUNNING[@]}"; do + systemctl stop "$unit" || true +done log_info "Backup erstellen..." BACKUP_DIR="/var/backups/pdf-ocr-hotfolder" @@ -60,29 +71,40 @@ rm -rf "$INSTALL_DIR/pdf_ocr_hotfolder" cp -r "$REPO_DIR/pdf_ocr_hotfolder" "$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_info "Dependencies aktualisieren..." "$INSTALL_DIR/venv/bin/pip" install --upgrade pip -q "$INSTALL_DIR/venv/bin/pip" install --upgrade -r "$INSTALL_DIR/requirements.txt" -q -log_info "systemd-Unit aktualisieren..." -sed -e "s|__SERVICE_USER__|$SERVICE_USER|g" \ - -e "s|__SERVICE_GROUP__|$SERVICE_GROUP|g" \ - "$REPO_DIR/systemd/pdf-ocr-hotfolder.service" \ - > "/etc/systemd/system/${SERVICE_NAME}.service" +log_info "systemd Template-Unit aktualisieren..." +cp "$REPO_DIR/systemd/$SERVICE_TEMPLATE" "/etc/systemd/system/$SERVICE_TEMPLATE" systemctl daemon-reload log_info "Berechtigungen setzen..." -chown -R "$SERVICE_USER:$SERVICE_GROUP" "$INSTALL_DIR" +# Eigentümer des Codes bleibt der primäre User (pdfocr); Instanzen laufen +# ggf. als anderer User, lesen aber nur den Code. +PRIMARY_USER="$(stat -c '%U' "$INSTALL_DIR/venv" 2>/dev/null || echo pdfocr)" +chown -R "$PRIMARY_USER":"$PRIMARY_USER" "$INSTALL_DIR" -log_info "Service starten..." -systemctl start "${SERVICE_NAME}.service" -sleep 2 +log_info "Starte Instanzen wieder..." +FAIL=0 +for unit in "${RUNNING[@]}"; do + systemctl start "$unit" || true + sleep 1 + if systemctl is-active --quiet "$unit"; then + log_info " ✅ $unit" + else + log_error " ❌ $unit — journalctl -u $unit -n 30" + FAIL=1 + fi +done -if systemctl is-active --quiet "${SERVICE_NAME}.service"; then - log_info "✅ Service läuft (Version $NEW_VERSION)" +echo +if [ "$FAIL" -eq 0 ]; then + log_info "Update auf $NEW_VERSION abgeschlossen ✓" else - log_error "Service läuft nicht. journalctl -u $SERVICE_NAME -n 30" + log_warn "Update abgeschlossen, aber mindestens eine Instanz läuft nicht." exit 1 fi