feat: Multi-Instanz-Support via systemd Template-Unit (v0.2.0)

- pdf-ocr-hotfolder@<name>.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 <noreply@anthropic.com>
This commit is contained in:
2026-04-08 14:31:58 +02:00
parent 76c3a991df
commit 985a33d3f9
7 changed files with 358 additions and 169 deletions
+47 -14
View File
@@ -1,8 +1,8 @@
# AI Agent Briefing — PDF OCR Hotfolder # AI Agent Briefing — PDF OCR Hotfolder
**Zuletzt aktualisiert:** 2026-04-08 **Zuletzt aktualisiert:** 2026-04-08
**Version:** 0.1.0 **Version:** 0.2.0
**Status:** Initiale Implementation, nicht produktiv getestet **Status:** Multi-Instanz-Support, nicht produktiv getestet
## 🎯 Projektziel ## 🎯 Projektziel
@@ -20,7 +20,7 @@ pdf-ocr-hotfolder/
│ ├── processor.py # ocrmypdf + veraPDF │ ├── processor.py # ocrmypdf + veraPDF
│ └── uploaders.py # folder, nextcloud (WebDAV), sftp, email │ └── uploaders.py # folder, nextcloud (WebDAV), sftp, email
├── systemd/ ├── systemd/
│ └── pdf-ocr-hotfolder.service # Template (Platzhalter __SERVICE_USER__/__SERVICE_GROUP__) │ └── pdf-ocr-hotfolder@.service # systemd Template-Unit (Instanz = %i)
├── config.example.toml ├── config.example.toml
├── install.sh # Interaktiver Installer ├── install.sh # Interaktiver Installer
├── update.sh # Update aus Repo ├── update.sh # Update aus Repo
@@ -43,25 +43,58 @@ pdf-ocr-hotfolder/
| Email | `smtplib` (stdlib) | | Email | `smtplib` (stdlib) |
| Service | systemd | | Service | systemd |
## 🖥️ Installations-Layout ## 🖥️ Installations-Layout (Multi-Instanz)
| Pfad | Inhalt | | Pfad | Inhalt |
|------|--------| |------|--------|
| `/opt/pdf-ocr-hotfolder/` | Code + venv (`venv/bin/python`) | | `/opt/pdf-ocr-hotfolder/` | Code + venv (für alle Instanzen gemeinsam) |
| `/etc/pdf-ocr-hotfolder/config.toml` | Konfiguration (mode 640, root:<service-group>) | | `/etc/pdf-ocr-hotfolder/<instanz>.toml` | Config pro Instanz (mode 640, root:<service-group>) |
| `/var/lib/pdf-ocr-hotfolder/{incoming,working,outgoing,error}/` | Datenverzeichnisse | | `/etc/systemd/system/pdf-ocr-hotfolder@.service` | Template-Unit |
| `/var/log/pdf-ocr-hotfolder/` | Logs (zusätzlich zu journald) | | `/etc/systemd/system/pdf-ocr-hotfolder@<instanz>.service.d/user.conf` | Drop-in für abweichenden User (optional) |
| `/etc/systemd/system/pdf-ocr-hotfolder.service` | systemd-Unit | | `/var/lib/pdf-ocr-hotfolder/<instanz>/{incoming,working,outgoing,error}/` | Daten pro Instanz |
| `/var/log/pdf-ocr-hotfolder/` | Logs |
| `/var/backups/pdf-ocr-hotfolder/` | Update-Backups | | `/var/backups/pdf-ocr-hotfolder/` | Update-Backups |
## 👤 Service-User ## 👤 Service-User
Der Installer fragt interaktiv: - Basis-Install legt Default-User `pdfocr` an (als System-User, falls nicht schon vorhanden)
1. Username (default `pdfocr`) - Beim Anlegen einer Instanz fragt der Installer nach dem Service-User (default `pdfocr`)
2. Falls User existiert (lokal oder AD via SSSD/Winbind): wird übernommen, primäre Gruppe automatisch erkannt - Wird ein **abweichender** User gewählt, wird ein systemd-Drop-in erstellt (`pdf-ocr-hotfolder@<instanz>.service.d/user.conf`) mit `User=/Group=` Override
3. Falls nicht: Frage nach lokaler Anlage als System-User - 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/<name>`), 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@<name>
rm /etc/pdf-ocr-hotfolder/<name>.toml
rm -rf /etc/systemd/system/pdf-ocr-hotfolder@<name>.service.d
systemctl daemon-reload
# Datenverzeichnis /var/lib/pdf-ocr-hotfolder/<name> 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 ## 🔄 Verarbeitungs-Flow
+15
View File
@@ -1,5 +1,20 @@
# Changelog # Changelog
## [0.2.0] - 2026-04-08
### Added
- **Multi-Instanz-Support** via systemd Template-Unit `pdf-ocr-hotfolder@<name>.service`
- Pro Instanz: eigene Config (`/etc/pdf-ocr-hotfolder/<name>.toml`), eigene Datenverzeichnisse (`/var/lib/pdf-ocr-hotfolder/<name>/…`), 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 ## [0.1.0] - 2026-04-08
### Added ### Added
+56 -23
View File
@@ -23,35 +23,56 @@ cd pdf-ocr-hotfolder
sudo ./install.sh 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: Bei jedem erneuten Aufruf erkennt der Installer bestehende Instanzen und fragt nur nach neuen.
```bash
sudo nano /etc/pdf-ocr-hotfolder/config.toml
sudo systemctl restart pdf-ocr-hotfolder
```
Test: Test:
```bash ```bash
cp irgendein-scan.pdf /var/lib/pdf-ocr-hotfolder/incoming/ cp irgendein-scan.pdf /var/lib/pdf-ocr-hotfolder/<instanz>/incoming/
journalctl -u pdf-ocr-hotfolder -f journalctl -u pdf-ocr-hotfolder@<instanz> -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@<name>.service`. Jede Instanz hat:
- eigene Config-Datei: `/etc/pdf-ocr-hotfolder/<name>.toml`
- eigene Datenverzeichnisse: `/var/lib/pdf-ocr-hotfolder/<name>/{incoming,working,outgoing,error}/`
- eigene systemd-Unit: `pdf-ocr-hotfolder@<name>.service`
- optional eigenen Service-User (via Drop-in `/etc/systemd/system/pdf-ocr-hotfolder@<name>.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 ## Verzeichnisse
| Pfad | Zweck | | Pfad | Zweck |
|------|-------| |------|-------|
| `/etc/pdf-ocr-hotfolder/config.toml` | Konfiguration | | `/opt/pdf-ocr-hotfolder/` | Code + venv (für alle Instanzen gemeinsam) |
| `/var/lib/pdf-ocr-hotfolder/incoming` | Eingang (Scanner schreibt hier rein) | | `/etc/pdf-ocr-hotfolder/<instanz>.toml` | Config pro Instanz |
| `/var/lib/pdf-ocr-hotfolder/working` | Arbeitsverzeichnis während OCR | | `/etc/systemd/system/pdf-ocr-hotfolder@.service` | systemd Template-Unit |
| `/var/lib/pdf-ocr-hotfolder/outgoing` | Ausgang (fertige PDFs) | | `/var/lib/pdf-ocr-hotfolder/<instanz>/incoming` | Eingang (Scanner schreibt hier rein) |
| `/var/lib/pdf-ocr-hotfolder/error` | PDFs, die nicht verarbeitet werden konnten | | `/var/lib/pdf-ocr-hotfolder/<instanz>/working` | Arbeitsverzeichnis während OCR |
| `/opt/pdf-ocr-hotfolder/` | Code + venv | | `/var/lib/pdf-ocr-hotfolder/<instanz>/outgoing` | Ausgang (fertige PDFs) |
| `/var/log/pdf-ocr-hotfolder/` | Logs (zusätzlich zu journald) | | `/var/lib/pdf-ocr-hotfolder/<instanz>/error` | Fehlgeschlagene PDFs |
| `/var/log/pdf-ocr-hotfolder/` | Logs (zusätzlich zu journald) |
| `/var/backups/pdf-ocr-hotfolder/` | Update-Backups |
## Konfiguration ## Konfiguration
@@ -101,9 +122,14 @@ on = "errors" # always | errors | never
## Service-Verwaltung ## Service-Verwaltung
```bash ```bash
sudo systemctl status pdf-ocr-hotfolder # Eine bestimmte Instanz
sudo systemctl restart pdf-ocr-hotfolder sudo systemctl status pdf-ocr-hotfolder@kunde-a
journalctl -u pdf-ocr-hotfolder -f 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 ## Update
@@ -114,15 +140,22 @@ git pull
sudo ./update.sh 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. Das Repo muss bestehen bleiben — `update.sh` kopiert daraus.
## Manueller Lauf (One-Shot) ## Manueller Lauf (One-Shot)
Bestehende PDFs im Eingang einmalig verarbeiten und beenden: Bestehende PDFs einer Instanz einmalig verarbeiten und beenden:
```bash ```bash
sudo -u pdfocr /opt/pdf-ocr-hotfolder/venv/bin/python -m pdf_ocr_hotfolder \ 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 ## 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 **Repo:** https://gitea.sonith.de/sonith_ug/pdf-ocr-hotfolder
+1 -1
View File
@@ -1 +1 @@
0.1.0 0.2.0
+195 -109
View File
@@ -1,11 +1,13 @@
#!/usr/bin/env bash #!/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: # Basis-Installation erfolgt einmalig (Code, venv, systemd-Template-Unit).
# - Lokal anlegen (neuer System-User) # Danach werden Hotfolder-Instanzen verwaltet:
# - Bereits existierender lokaler User # - Beim Erstlauf: mindestens eine Instanz wird angelegt
# - AD-User mit lokaler UID (z.B. via SSSD/Winbind) # - 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 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_info() { echo -e "${GREEN}[INFO]${NC} $*"; }
log_warn() { echo -e "${YELLOW}[WARN]${NC} $*"; } log_warn() { echo -e "${YELLOW}[WARN]${NC} $*"; }
log_error() { echo -e "${RED}[ERROR]${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 if [ "${EUID}" -ne 0 ]; then
log_error "Bitte als root ausführen: sudo ./install.sh" log_error "Bitte als root ausführen: sudo ./install.sh"
@@ -23,9 +25,10 @@ fi
INSTALL_DIR="/opt/pdf-ocr-hotfolder" INSTALL_DIR="/opt/pdf-ocr-hotfolder"
CONFIG_DIR="/etc/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" 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)" SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
REPO_DIR="$SCRIPT_DIR" REPO_DIR="$SCRIPT_DIR"
@@ -35,123 +38,206 @@ if [ ! -f "$REPO_DIR/pdf_ocr_hotfolder/__init__.py" ]; then
exit 1 exit 1
fi fi
echo # ============================================================
echo "==========================================" # Basis-Installation (idempotent)
echo " PDF OCR Hotfolder — Installation" # ============================================================
echo "=========================================="
echo
# ============ 1. System-Dependencies ============ install_base() {
log_step "Installiere System-Pakete" 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 log_step "Default-User '$DEFAULT_USER' prüfen"
apt-get install -y --no-install-recommends \ if id "$DEFAULT_USER" &>/dev/null; then
python3 python3-venv python3-pip \ log_info "'$DEFAULT_USER' existiert bereits"
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 ✓"
else else
log_error "User '$SERVICE_USER' muss vor der Installation existieren (z.B. via AD/SSSD)." adduser --system --group --home "$DATA_ROOT" --shell /usr/sbin/nologin "$DEFAULT_USER"
log_error "Lege ihn an oder wähle einen existierenden Namen." log_info "System-User '$DEFAULT_USER' angelegt ✓"
exit 1
fi fi
fi
# ============ 3. Verzeichnisse ============ log_step "Verzeichnisse anlegen"
log_step "Verzeichnisse erstellen" 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" log_step "Code kopieren"
mkdir -p "$DATA_DIR"/{incoming,outgoing,working,error} 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/" log_step "Python venv"
cp "$REPO_DIR/requirements.txt" "$INSTALL_DIR/" if [ ! -d "$INSTALL_DIR/venv" ]; then
cp "$REPO_DIR/VERSION" "$INSTALL_DIR/" python3 -m venv "$INSTALL_DIR/venv"
echo "$REPO_DIR" > "$INSTALL_DIR/.repo_path" 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 log_step "systemd Template-Unit installieren"
cp "$REPO_DIR/config.example.toml" "$CONFIG_DIR/config.toml" cp "$REPO_DIR/systemd/$SERVICE_TEMPLATE" "/etc/systemd/system/$SERVICE_TEMPLATE"
log_info "Beispiel-Konfig nach $CONFIG_DIR/config.toml kopiert" 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" <<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)"
echo
}
# ============================================================
# Main
# ============================================================
echo
echo "=========================================="
echo " PDF OCR Hotfolder — Installer"
echo "=========================================="
if [ ! -d "$INSTALL_DIR/venv" ] || [ ! -f "/etc/systemd/system/$SERVICE_TEMPLATE" ]; then
log_step "Basis-Installation"
install_base
else else
log_info "Bestehende Konfig $CONFIG_DIR/config.toml bleibt unverändert" log_info "Basis-Installation bereits vorhanden ($INSTALL_DIR)"
log_info "Überspringe Basis-Setup (nutze update.sh für Code-Updates)"
fi fi
log_info "Verzeichnisse erstellt ✓" show_existing_instances
# ============ 4. Python venv ============ # Erste Instanz ist Pflicht, wenn noch keine vorhanden
log_step "Python venv anlegen" mapfile -t existing < <(list_instances)
if [ "${#existing[@]}" -eq 0 ]; then
if [ ! -d "$INSTALL_DIR/venv" ]; then log_info "Lege erste Hotfolder-Instanz an."
python3 -m venv "$INSTALL_DIR/venv" create_instance || true
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 bereit ✓"
# ============ 5. Berechtigungen ============
log_step "Berechtigungen setzen"
chown -R "$SERVICE_USER:$SERVICE_GROUP" "$INSTALL_DIR" "$DATA_DIR" "$LOG_DIR"
chown root:"$SERVICE_GROUP" "$CONFIG_DIR"
chmod 750 "$CONFIG_DIR"
if [ -f "$CONFIG_DIR/config.toml" ]; then
chown root:"$SERVICE_GROUP" "$CONFIG_DIR/config.toml"
chmod 640 "$CONFIG_DIR/config.toml"
fi fi
log_info "Berechtigungen gesetzt ✓" while true; do
read -r -p "Weitere Instanz anlegen? [j/N]: " MORE
# ============ 6. systemd-Unit ============ MORE="${MORE:-N}"
log_step "systemd-Unit installieren" if [[ "$MORE" =~ ^[JjYy]$ ]]; then
create_instance || true
sed -e "s|__SERVICE_USER__|$SERVICE_USER|g" \ else
-e "s|__SERVICE_GROUP__|$SERVICE_GROUP|g" \ break
"$REPO_DIR/systemd/pdf-ocr-hotfolder.service" \ fi
> "/etc/systemd/system/${SERVICE_NAME}.service" done
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
echo echo
echo "==========================================" echo "=========================================="
echo " Installation abgeschlossen" echo " Fertig"
echo "==========================================" echo "=========================================="
echo show_existing_instances
echo " Konfiguration: $CONFIG_DIR/config.toml" echo " Logs: journalctl -u pdf-ocr-hotfolder@<instanz> -f"
echo " Eingang: $DATA_DIR/incoming" echo " Neustart: systemctl restart pdf-ocr-hotfolder@<instanz>"
echo " Ausgang: $DATA_DIR/outgoing" echo " Update: sudo ./update.sh"
echo " Service-User: $SERVICE_USER ($SERVICE_GROUP)"
echo
echo " Logs: journalctl -u $SERVICE_NAME -f"
echo " Update: sudo ./update.sh"
echo echo
@@ -1,13 +1,13 @@
[Unit] [Unit]
Description=PDF OCR Hotfolder Description=PDF OCR Hotfolder (Instance: %i)
After=network-online.target After=network-online.target
Wants=network-online.target Wants=network-online.target
[Service] [Service]
Type=simple Type=simple
User=__SERVICE_USER__ User=pdfocr
Group=__SERVICE_GROUP__ Group=pdfocr
ExecStart=/opt/pdf-ocr-hotfolder/venv/bin/python -m pdf_ocr_hotfolder --config /etc/pdf-ocr-hotfolder/config.toml ExecStart=/opt/pdf-ocr-hotfolder/venv/bin/python -m pdf_ocr_hotfolder --config /etc/pdf-ocr-hotfolder/%i.toml
Restart=on-failure Restart=on-failure
RestartSec=5 RestartSec=5
KillMode=mixed KillMode=mixed
+40 -18
View File
@@ -2,6 +2,10 @@
# #
# PDF OCR Hotfolder — Update-Script # 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 set -euo pipefail
RED='\033[0;31m'; GREEN='\033[0;32m'; YELLOW='\033[1;33m'; NC='\033[0m' 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 fi
INSTALL_DIR="/opt/pdf-ocr-hotfolder" 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)" SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
if [ -f "$SCRIPT_DIR/pdf_ocr_hotfolder/__init__.py" ]; then 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" log_info "Version: $OLD_VERSION$NEW_VERSION"
echo echo
# Service-User aus systemd-Unit lesen # Laufende Instanzen ermitteln
SERVICE_USER="$(awk -F= '/^User=/{print $2}' /etc/systemd/system/${SERVICE_NAME}.service 2>/dev/null || echo pdfocr)" mapfile -t RUNNING < <(systemctl list-units --no-legend --state=active 'pdf-ocr-hotfolder@*.service' 2>/dev/null | awk '{print $1}')
SERVICE_GROUP="$(awk -F= '/^Group=/{print $2}' /etc/systemd/system/${SERVICE_NAME}.service 2>/dev/null || echo pdfocr)" if [ "${#RUNNING[@]}" -gt 0 ]; then
log_info "Laufende Instanzen: ${RUNNING[*]}"
else
log_info "Keine laufenden Instanzen."
fi
log_info "Stoppe Service..." log_info "Stoppe laufende Instanzen..."
systemctl stop "${SERVICE_NAME}.service" 2>/dev/null || true for unit in "${RUNNING[@]}"; do
systemctl stop "$unit" || true
done
log_info "Backup erstellen..." log_info "Backup erstellen..."
BACKUP_DIR="/var/backups/pdf-ocr-hotfolder" 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 -r "$REPO_DIR/pdf_ocr_hotfolder" "$INSTALL_DIR/"
cp "$REPO_DIR/requirements.txt" "$INSTALL_DIR/" cp "$REPO_DIR/requirements.txt" "$INSTALL_DIR/"
cp "$REPO_DIR/VERSION" "$INSTALL_DIR/" cp "$REPO_DIR/VERSION" "$INSTALL_DIR/"
cp "$REPO_DIR/config.example.toml" "$INSTALL_DIR/"
echo "$REPO_DIR" > "$INSTALL_DIR/.repo_path" echo "$REPO_DIR" > "$INSTALL_DIR/.repo_path"
log_info "Dependencies aktualisieren..." log_info "Dependencies aktualisieren..."
"$INSTALL_DIR/venv/bin/pip" install --upgrade pip -q "$INSTALL_DIR/venv/bin/pip" install --upgrade pip -q
"$INSTALL_DIR/venv/bin/pip" install --upgrade -r "$INSTALL_DIR/requirements.txt" -q "$INSTALL_DIR/venv/bin/pip" install --upgrade -r "$INSTALL_DIR/requirements.txt" -q
log_info "systemd-Unit aktualisieren..." log_info "systemd Template-Unit aktualisieren..."
sed -e "s|__SERVICE_USER__|$SERVICE_USER|g" \ cp "$REPO_DIR/systemd/$SERVICE_TEMPLATE" "/etc/systemd/system/$SERVICE_TEMPLATE"
-e "s|__SERVICE_GROUP__|$SERVICE_GROUP|g" \
"$REPO_DIR/systemd/pdf-ocr-hotfolder.service" \
> "/etc/systemd/system/${SERVICE_NAME}.service"
systemctl daemon-reload systemctl daemon-reload
log_info "Berechtigungen setzen..." 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..." log_info "Starte Instanzen wieder..."
systemctl start "${SERVICE_NAME}.service" FAIL=0
sleep 2 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 echo
log_info "✅ Service läuft (Version $NEW_VERSION)" if [ "$FAIL" -eq 0 ]; then
log_info "Update auf $NEW_VERSION abgeschlossen ✓"
else 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 exit 1
fi fi