#!/bin/bash # archivdms Installer — native Installation (kein Docker) # Zielumgebung: Debian 13 (trixie), typischerweise ein unprivilegierter # Proxmox-LXC-Container (funktioniert genauso auf VM/Bare-Metal — es gibt # hier keinen LXC-spezifischen Zweig, siehe Abschnitt "LXC-Hinweise" unten). # # WICHTIG — kein Git-Remote: archivdms hat (Stand jetzt) kein Gitea/GitHub- # Repository und soll dort auch nicht hochgeladen werden. Dieses Skript zieht # den Quellcode daher NICHT per `git clone`/`git pull`, sondern erwartet, dass # der Code bereits lokal auf dem Zielserver liegt (z.B. per rsync/scp vom # Entwicklungsrechner kopiert). Quelle wird über die Umgebungsvariable # ARCHIVDMS_SRC festgelegt; Default ist das Verzeichnis, in dem dieses Skript # selbst liegt (dirname "$0") — das deckt den Normalfall ab, dass man # install.sh direkt aus dem mitkopierten Projektverzeichnis heraus aufruft. # # Aufruf: # sudo bash install.sh # ARCHIVDMS_SRC=/opt/archivdms-src sudo bash install.sh set -euo pipefail export DEBIAN_FRONTEND=noninteractive RED='\033[0;31m'; GREEN='\033[0;32m'; YELLOW='\033[1;33m'; BLUE='\033[0;34m'; NC='\033[0m' log() { echo -e "${GREEN}[OK]${NC} $*"; } info() { echo -e "${BLUE}[..]${NC} $*"; } warn() { echo -e "${YELLOW}[!!]${NC} $*"; } die() { echo -e "${RED}[ERR]${NC} $*" >&2; exit 1; } [[ $EUID -eq 0 ]] || die "Bitte als root ausführen: sudo bash install.sh" # ── OS-Prüfung: Debian 13 (trixie) ist der getestete Zielstand ────────────── # Anders als beim Hard-Fail in archivmail/install.sh nur eine Warnung: Ein DMS # in einem LXC-Container wird öfter mal auf einem leicht abweichenden Stand # betrieben (z.B. Debian 12 während einer Migrationsphase) — ein Hard-Block # hilft hier niemandem, wichtig ist nur, dass der Betreiber es weiß. _os_id=$(. /etc/os-release 2>/dev/null && echo "$ID" || echo "") _os_ver=$(. /etc/os-release 2>/dev/null && echo "$VERSION_ID" || echo "") if [[ "$_os_id" == "debian" && "$_os_ver" == "13" ]]; then log "Debian 13 (trixie) erkannt" else warn "Getestet ist Debian 13 (trixie) — erkannt: ${_os_id:-unbekannt} ${_os_ver:-?}. Fahre trotzdem fort." fi # ── LXC-Erkennung — rein informativ ───────────────────────────────────────── # Ein reiner systemd-Service + PostgreSQL + Go-Binary + Next.js-Standalone- # Setup braucht in einem unprivilegierten LXC-Container keinerlei # Sonderbehandlung (kein Nested-Docker, kein FUSE, keine Kernel-Module nötig). # Der einzige Punkt, an dem unprivilegierte LXC-Container gelegentlich # anecken, ist wenn Software eigene Netzwerk-Namespaces oder rohe Sockets # braucht — das ist hier nicht der Fall (der Go-Server bindet normale TCP- # Ports, das reicht auch unprivilegiert). if command -v systemd-detect-virt >/dev/null 2>&1; then _virt="$(systemd-detect-virt 2>/dev/null || echo unknown)" if [[ "$_virt" == "lxc" ]]; then info "LXC-Container erkannt (systemd-detect-virt: lxc) — kein Sonderhandling nötig für dieses Setup" else info "Virtualisierung: ${_virt} (kein LXC — dieses Skript funktioniert unabhängig davon identisch)" fi fi # ── Gemeinsame Variablen ───────────────────────────────────────────────────── DB_PASSWORD="${DB_PASSWORD:-$(openssl rand -base64 24 | tr -dc 'a-zA-Z0-9' | head -c 32)}" API_SECRET="${API_SECRET:-$(openssl rand -base64 48 | tr -dc 'a-zA-Z0-9' | head -c 64)}" INSTALL_DIR="/opt/archivdms" STORE_BASE="/var/lib/archivdms" LOG_DIR="/var/log/archivdms" CONFIG_DIR="/etc/archivdms" SSL_DIR="/etc/ssl/archivdms" DMS_USER="archivdms" FQDN="$(hostname -f 2>/dev/null || hostname)" # Quellverzeichnis: entweder explizit über ARCHIVDMS_SRC vorgegeben, sonst das # Verzeichnis, in dem install.sh selbst liegt (funktioniert, wenn der Code # per rsync/scp komplett auf den Server kopiert und von dort ausgeführt wird). SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)" ARCHIVDMS_SRC="${ARCHIVDMS_SRC:-$SCRIPT_DIR}" [[ -f "$ARCHIVDMS_SRC/go.mod" ]] || die "Kein Go-Quellcode unter ARCHIVDMS_SRC gefunden ($ARCHIVDMS_SRC/go.mod fehlt) — ARCHIVDMS_SRC korrekt setzen oder Quellcode dorthin kopieren." echo "" echo " ╔══════════════════════════════════════════╗" echo " ║ archivdms Installer (nativ) ║" echo " ╚══════════════════════════════════════════╝" echo "" info "Hostname: $FQDN" info "Quellverzeichnis: $ARCHIVDMS_SRC" echo "" # ── 1. Basispakete ─────────────────────────────────────────────────────────── # ca-certificates/gnupg/curl explizit zuerst: schlanke LXC-Templates (z.B. # Proxmox' debian-13-standard) liefern die teils NICHT vorinstalliert, das # Manticore-Repo (HTTPS + GPG-Key) würde sonst weiter unten fehlschlagen. info "Installiere Basispakete..." apt-get update -qq apt-get install -y -qq \ ca-certificates gnupg curl wget \ postgresql nginx \ rsync logrotate openssl sudo cron \ tesseract-ocr tesseract-ocr-deu tesseract-ocr-eng poppler-utils log "Basispakete installiert" # ── 1b. Node.js ≥20 (empfohlen 24.x LTS) ──────────────────────────────────── # Debians eigenes nodejs-Paket ist für ein 2026er Next.js 16 i.d.R. zu alt. # NodeSource liefert aktuelle LTS-Zeilen für Debian/trixie. NODE_MAJOR_REQUIRED=20 _node_ok=0 if command -v node >/dev/null 2>&1; then _node_major="$(node -v | sed -E 's/^v([0-9]+).*/\1/')" [[ "$_node_major" =~ ^[0-9]+$ && "$_node_major" -ge "$NODE_MAJOR_REQUIRED" ]] && _node_ok=1 fi if [[ $_node_ok -eq 1 ]]; then log "Node.js $(node -v) OK (>= v${NODE_MAJOR_REQUIRED})" else info "Installiere Node.js 24.x LTS von NodeSource..." curl -fsSL https://deb.nodesource.com/setup_24.x | bash - >/dev/null 2>&1 \ || warn "NodeSource-Setup-Skript fehlgeschlagen — versuche apt trotzdem" apt-get install -y -qq nodejs command -v node >/dev/null 2>&1 && log "Node.js $(node -v) installiert" \ || die "Node.js-Installation fehlgeschlagen" fi # ── 1c. Go ≥1.26 von upstream (apt-Paket ist zu alt) ──────────────────────── # Analog archivmail: /usr/local/go statt apt, damit die Version unabhängig # von der Debian-Paketierung und OS-Version aktuell gehalten werden kann. GO_VERSION="1.26.5" info "Installiere Go ${GO_VERSION} von upstream..." if /usr/local/go/bin/go version 2>/dev/null | grep -q "go${GO_VERSION}"; then log "Go ${GO_VERSION} bereits installiert" else _go_arch="amd64" [[ "$(dpkg --print-architecture)" == "arm64" ]] && _go_arch="arm64" curl -fsSL "https://dl.google.com/go/go${GO_VERSION}.linux-${_go_arch}.tar.gz" \ | tar -C /usr/local -xz log "Go $(/usr/local/go/bin/go version | awk '{print $3}') installiert" fi export PATH="$PATH:/usr/local/go/bin" ln -sf /usr/local/go/bin/go /usr/local/bin/go ln -sf /usr/local/go/bin/gofmt /usr/local/bin/gofmt # ── 1d. Manticore Search ≥27.x ─────────────────────────────────────────────── # Kein natives trixie-Repo — das bookworm-Paket läuft auch auf trixie/Debian # 14+, da Manticore kein OS-spezifisches ABI verwendet (siehe archivmail # update.sh für den ausführlichen Hintergrund). Version wird dynamisch aus der # Packages-Datei des Repos gelesen; Fallback nur falls das Repo mal nicht # erreichbar/parsbar ist. _MANTICORE_VERSION_FALLBACK="27.1.5-26061911-5a1cf9399" _mc_installed_version="$(dpkg-query -W -f='${Version}' manticore 2>/dev/null || true)" _mc_arch="$(dpkg --print-architecture)" _mc_latest_from_repo() { local base="https://repo.manticoresearch.com/repository/manticoresearch_bookworm" local packages block version filename packages="$(curl -fsSL --max-time 15 "${base}/dists/bookworm/main/binary-${_mc_arch}/Packages" 2>/dev/null)" || return 1 [[ -n "$packages" ]] || return 1 block="$(printf '%s\n' "$packages" | awk 'BEGIN{RS="\n\n"} {n=split($0,l,"\n"); if (l[1]=="Package: manticore") b=$0} END{print b}')" [[ -n "$block" ]] || return 1 version="$(printf '%s\n' "$block" | awk -F': ' '/^Version:/{print $2; exit}')" filename="$(printf '%s\n' "$block" | awk -F': ' '/^Filename:/{print $2; exit}')" [[ -n "$version" && -n "$filename" ]] || return 1 printf '%s|%s\n' "$version" "$filename" } _mc_target_version="$_MANTICORE_VERSION_FALLBACK" _mc_target_url="https://repo.manticoresearch.com/repository/manticoresearch_bookworm/dists/bookworm/main/binary-${_mc_arch}/manticore_${_MANTICORE_VERSION_FALLBACK}_${_mc_arch}.deb" if _mc_latest="$(_mc_latest_from_repo)"; then _mc_target_version="${_mc_latest%%|*}" _mc_target_url="https://repo.manticoresearch.com/repository/manticoresearch_bookworm/${_mc_latest#*|}" info "Manticore Search: aktuelle Repo-Version ${_mc_target_version} ermittelt" else warn "Manticore-Repo-Metadaten nicht abrufbar — falle auf bekannten Stand ${_MANTICORE_VERSION_FALLBACK} zurück" fi _mc_fetch_and_install() { wget -q -O /tmp/manticore.deb "$_mc_target_url" || true [[ -f /tmp/manticore.deb ]] || return 1 dpkg -i /tmp/manticore.deb 2>/dev/null || apt-get install -f -y -qq 2>/dev/null || true rm -f /tmp/manticore.deb return 0 } if [[ -z "$_mc_installed_version" ]]; then info "Installiere Manticore Search ${_mc_target_version}..." _mc_fetch_and_install && log "Manticore Search installiert" \ || warn "Manticore Search konnte nicht installiert werden — siehe: https://manticoresearch.com/install/ (Suche ist im Code laut README noch nicht integriert, kein Hard-Fail)" elif dpkg --compare-versions "$_mc_installed_version" lt "$_mc_target_version"; then info "Manticore Search ${_mc_installed_version} gefunden, aktualisiere auf ${_mc_target_version}..." systemctl stop manticore 2>/dev/null || true _mc_fetch_and_install \ && log "Manticore Search aktualisiert: ${_mc_installed_version} → ${_mc_target_version}" \ || warn "Upgrade fehlgeschlagen — vorherige Version bleibt installiert" else log "Manticore Search ${_mc_installed_version} bereits aktuell (>= ${_mc_target_version})" fi systemctl enable --now manticore 2>/dev/null || warn "Manticore-Dienst konnte nicht gestartet werden" systemctl is-active --quiet manticore && log "Manticore Search läuft" # ── 2. Systembenutzer ──────────────────────────────────────────────────────── # Dedizierter, nicht-interaktiver Service-User — der archivdms-Prozess läuft # nie als root, im Unterschied etwa zu klassischen root-Cron-Setups. info "Lege Systembenutzer '$DMS_USER' an..." id "$DMS_USER" &>/dev/null \ && log "Benutzer '$DMS_USER' existiert bereits" \ || { useradd --system --shell /bin/false --home "$STORE_BASE" --create-home "$DMS_USER"; log "Benutzer angelegt"; } # ── 2b. sudo-NOPASSWD-Whitelist für Dienststeuerung ───────────────────────── # Analog archivmail PROJ-68: falls ein späteres Admin-UI Dienst-Restarts aus # der Anwendung heraus auslösen soll, ist die Whitelist bereits vorbereitet. # Validierung per `visudo -c` VOR dem Einspielen — eine kaputte sudoers-Datei # würde sonst den gesamten sudo-Mechanismus auf dem Server lahmlegen. # Idempotent: deterministischer Inhalt, überschreibt bei jedem Lauf. provision_archivdms_sudoers() { local dms_user="${1:-$DMS_USER}" local services=("archivdms" "manticore" "postgresql" "nginx") local tmpfile tmpfile="$(mktemp)" { echo "# Verwaltet von install.sh/update.sh — nicht manuell editieren," echo "# Änderungen gehen beim nächsten Deploy verloren." for svc in "${services[@]}"; do echo "${dms_user} ALL=(root) NOPASSWD: /usr/bin/systemctl start ${svc}.service" echo "${dms_user} ALL=(root) NOPASSWD: /usr/bin/systemctl stop ${svc}.service" echo "${dms_user} ALL=(root) NOPASSWD: /usr/bin/systemctl restart ${svc}.service" echo "${dms_user} ALL=(root) NOPASSWD: /usr/bin/systemctl enable ${svc}.service" echo "${dms_user} ALL=(root) NOPASSWD: /usr/bin/systemctl disable ${svc}.service" done } > "$tmpfile" if visudo -c -f "$tmpfile" >/dev/null 2>&1; then install -m 0440 -o root -g root "$tmpfile" /etc/sudoers.d/archivdms log "sudo-Rechte für Dienststeuerung eingerichtet (/etc/sudoers.d/archivdms)" else warn "Generierte sudoers-Regel ist ungültig — /etc/sudoers.d/archivdms NICHT verändert" fi rm -f "$tmpfile" } provision_archivdms_sudoers # ── 3. Verzeichnisstruktur ─────────────────────────────────────────────────── # Layout gemäß README.md "Storage-Struktur & OCR" / config/config.go # (StorageConfig.BasePath + InboxPath/StorePath/OCRTmpPath). config.Load() # legt diese Unterverzeichnisse beim Serverstart selbst per os.MkdirAll an # (0750) — install.sh legt die Wurzel bereits mit korrektem Owner an, damit # der archivdms-User (nicht root) sie beim ersten Start auch beschreiben kann. info "Erstelle Verzeichnisstruktur..." mkdir -p "$STORE_BASE/inbox" "$STORE_BASE/store" "$STORE_BASE/ocr-tmp" mkdir -p "$CONFIG_DIR" "$LOG_DIR" "$INSTALL_DIR" "$SSL_DIR" chown -R "$DMS_USER:$DMS_USER" "$STORE_BASE" "$LOG_DIR" chmod 750 "$STORE_BASE" "$STORE_BASE/inbox" "$STORE_BASE/store" "$STORE_BASE/ocr-tmp" log "Verzeichnisse erstellt ($STORE_BASE/{inbox,store,ocr-tmp})" # ── 4. TLS-Zertifikat (selbstsigniert, Let's-Encrypt-Option siehe unten) ──── info "Erstelle selbstsigniertes TLS-Zertifikat..." if [[ ! -f "$SSL_DIR/archivdms.crt" ]]; then SERVER_IP="$(hostname -I | awk '{print $1}')" openssl req -x509 -nodes -days 3650 -newkey rsa:4096 \ -keyout "$SSL_DIR/archivdms.key" \ -out "$SSL_DIR/archivdms.crt" \ -subj "/CN=${FQDN}/O=archivdms/C=DE" \ -addext "subjectAltName=DNS:${FQDN},DNS:$(hostname -s),IP:${SERVER_IP}" \ 2>/dev/null chmod 640 "$SSL_DIR/archivdms.key" chmod 644 "$SSL_DIR/archivdms.crt" chown "root:$DMS_USER" "$SSL_DIR/archivdms.key" log "TLS-Zertifikat erstellt: $SSL_DIR/archivdms.crt" else log "TLS-Zertifikat existiert bereits – wird nicht überschrieben" fi # ── 5. PostgreSQL ───────────────────────────────────────────────────────── info "Richte PostgreSQL ein..." systemctl enable postgresql --quiet systemctl start postgresql # Falls config.yml schon existiert, DB-Passwort daraus übernehmen — verhindert # Passwort-Mismatch zwischen laufender DB-Rolle und Config bei Re-Install. if [[ -f "$CONFIG_DIR/config.yml" ]]; then EXISTING_PW=$(grep -A6 '^database:' "$CONFIG_DIR/config.yml" | awk '/password:/{print $2}' | head -1 | tr -d '"') [[ -n "$EXISTING_PW" ]] && DB_PASSWORD="$EXISTING_PW" && info "DB-Passwort aus vorhandener config.yml übernommen" fi su -c "psql -tc \"SELECT 1 FROM pg_roles WHERE rolname='archivdms'\" | grep -q 1 \ && psql -c \"ALTER USER archivdms WITH PASSWORD '$DB_PASSWORD'\" \ || psql -c \"CREATE USER archivdms WITH PASSWORD '$DB_PASSWORD'\"" postgres su -c "psql -tc \"SELECT 1 FROM pg_database WHERE datname='archivdms'\" | grep -q 1 || \ psql -c \"CREATE DATABASE archivdms OWNER archivdms\"" postgres su -c "psql archivdms -c \"GRANT ALL PRIVILEGES ON ALL TABLES IN SCHEMA public TO archivdms;\"" postgres su -c "psql archivdms -c \"GRANT ALL PRIVILEGES ON ALL SEQUENCES IN SCHEMA public TO archivdms;\"" postgres su -c "psql archivdms -c \"ALTER DEFAULT PRIVILEGES IN SCHEMA public GRANT ALL ON TABLES TO archivdms;\"" postgres su -c "psql archivdms -c \"ALTER DEFAULT PRIVILEGES IN SCHEMA public GRANT ALL ON SEQUENCES TO archivdms;\"" postgres log "PostgreSQL eingerichtet" # ── 6. Konfiguration (config.yml aus config.yml.example) ──────────────────── info "Erstelle Konfigurationsdatei..." if [[ ! -f "$CONFIG_DIR/config.yml" ]]; then [[ -f "$ARCHIVDMS_SRC/config/config.yml.example" ]] \ || die "config/config.yml.example nicht gefunden unter $ARCHIVDMS_SRC" sed \ -e "s/CHANGE_ME_TO_A_LONG_RANDOM_SECRET/$API_SECRET/g" \ -e "s/password: \"CHANGE_ME\"/password: \"$DB_PASSWORD\"/" \ -e "s/dms\.example\.com/$FQDN/" \ "$ARCHIVDMS_SRC/config/config.yml.example" \ > "$CONFIG_DIR/config.yml" # storage.base_path und Log-Pfade auf die tatsächlich angelegten # Verzeichnisse zeigen lassen (das Beispiel verwendet dieselben Defaults, # sed hier trotzdem defensiv falls sich das Example mal ändert). sed -i \ -e "s#base_path: \".*\"#base_path: \"$STORE_BASE\"#" \ -e "s#log_path: \".*\"#log_path: \"$LOG_DIR/audit.log\"#" \ -e "s#path: \"/var/log/archivdms/app.log\"#path: \"$LOG_DIR/app.log\"#" \ "$CONFIG_DIR/config.yml" chmod 640 "$CONFIG_DIR/config.yml" chown "root:$DMS_USER" "$CONFIG_DIR/config.yml" log "config.yml erstellt: $CONFIG_DIR/config.yml" else log "config.yml existiert bereits – wird nicht überschrieben" fi # ── 7. nginx Reverse-Proxy (mit Let's-Encrypt-Option) ─────────────────────── info "Konfiguriere nginx (HTTP → HTTPS + TLS)..." cat > /etc/nginx/sites-available/archivdms << NGINX # HTTP → HTTPS Redirect server { listen 80; server_name ${FQDN} _; return 301 https://\$host\$request_uri; } # HTTPS server { listen 443 ssl; http2 on; server_name ${FQDN} _; ssl_certificate ${SSL_DIR}/archivdms.crt; ssl_certificate_key ${SSL_DIR}/archivdms.key; ssl_protocols TLSv1.2 TLSv1.3; ssl_ciphers HIGH:!aNULL:!MD5; ssl_prefer_server_ciphers on; ssl_session_cache shared:SSL:10m; ssl_session_timeout 10m; add_header Strict-Transport-Security "max-age=31536000" always; add_header X-Content-Type-Options nosniff always; add_header X-Frame-Options SAMEORIGIN always; location /api/ { proxy_pass http://127.0.0.1:8080; proxy_http_version 1.1; proxy_set_header Host \$host; proxy_set_header X-Real-IP \$remote_addr; proxy_set_header X-Forwarded-For \$proxy_add_x_forwarded_for; proxy_set_header X-Forwarded-Proto \$scheme; client_max_body_size 512M; } location / { proxy_pass http://127.0.0.1:3000; proxy_http_version 1.1; proxy_set_header Upgrade \$http_upgrade; proxy_set_header Connection 'upgrade'; proxy_set_header Host \$host; proxy_set_header X-Real-IP \$remote_addr; proxy_set_header X-Forwarded-For \$proxy_add_x_forwarded_for; proxy_set_header X-Forwarded-Proto \$scheme; proxy_cache_bypass \$http_upgrade; } access_log /var/log/nginx/archivdms.access.log; error_log /var/log/nginx/archivdms.error.log; } NGINX ln -sf /etc/nginx/sites-available/archivdms /etc/nginx/sites-enabled/archivdms rm -f /etc/nginx/sites-enabled/default nginx -t systemctl enable nginx --quiet systemctl restart nginx log "nginx konfiguriert" # Let's-Encrypt-Option: nur ein Hinweis, kein automatischer Zwang zu einer # öffentlichen Domain — viele DMS-Installationen laufen intern ohne # öffentlich auflösbaren FQDN, wo certbot ohnehin fehlschlagen würde. if command -v certbot >/dev/null 2>&1; then info "certbot bereits installiert — Let's-Encrypt-Zertifikat manuell holen mit:" echo " certbot --nginx -d ${FQDN}" else info "Let's-Encrypt-Zertifikat gewünscht? Falls ${FQDN} öffentlich auflösbar ist:" echo " apt-get install -y certbot python3-certbot-nginx && certbot --nginx -d ${FQDN}" fi # ── 8. logrotate ────────────────────────────────────────────────────────── cat > /etc/logrotate.d/archivdms << LOGROTATE ${LOG_DIR}/*.log { daily rotate 365 compress delaycompress missingok notifempty create 640 ${DMS_USER} ${DMS_USER} } LOGROTATE log "logrotate konfiguriert" # ── 9. Cron-Job für Wiedervorlage-Benachrichtigung ───────────────────────── # deploy/cron.d/archivdms-reminders liegt bereits fertig im Projekt (siehe # README "Struktur") — hier nur nach /etc/cron.d/ kopieren. info "Installiere Cron-Job für Wiedervorlage-Benachrichtigung..." if [[ -f "$ARCHIVDMS_SRC/deploy/cron.d/archivdms-reminders" ]]; then cp "$ARCHIVDMS_SRC/deploy/cron.d/archivdms-reminders" /etc/cron.d/archivdms-reminders chmod 644 /etc/cron.d/archivdms-reminders systemctl enable --now cron --quiet 2>/dev/null || true log "Cron-Job installiert: /etc/cron.d/archivdms-reminders" else warn "deploy/cron.d/archivdms-reminders nicht gefunden — Wiedervorlage-Mails laufen nicht automatisch" fi # ── 9b. Cron-Job für Klassifizierer-Retraining ───────────────────────────── info "Installiere Cron-Job für Klassifizierer-Retraining..." if [[ -f "$ARCHIVDMS_SRC/deploy/cron.d/archivdms-classify-retrain" ]]; then cp "$ARCHIVDMS_SRC/deploy/cron.d/archivdms-classify-retrain" /etc/cron.d/archivdms-classify-retrain chmod 644 /etc/cron.d/archivdms-classify-retrain systemctl enable --now cron --quiet 2>/dev/null || true log "Cron-Job installiert: /etc/cron.d/archivdms-classify-retrain" else warn "deploy/cron.d/archivdms-classify-retrain nicht gefunden — Klassifizierer-Retraining läuft nicht automatisch" fi # ── 10. systemd Unit für 'archivdms serve' ────────────────────────────────── # Der SFTP-Server läuft im selben Prozess mit (kein eigener systemd-Dienst) — # er startet nur, wenn cfg.SFTP.Enabled in config.yml gesetzt ist # (siehe cmd/archivdms/main.go). info "Erstelle systemd Unit..." cat > /etc/systemd/system/archivdms.service << UNIT [Unit] Description=archivdms GoBD-DMS Daemon (API + eingebetteter SFTP-Server) After=network.target postgresql.service manticore.service Wants=manticore.service Requires=postgresql.service [Service] Type=simple User=${DMS_USER} Group=${DMS_USER} ExecStart=${INSTALL_DIR}/bin/archivdms serve -config ${CONFIG_DIR}/config.yml Restart=on-failure RestartSec=5 StandardOutput=journal StandardError=journal SyslogIdentifier=archivdms NoNewPrivileges=true ProtectSystem=strict ReadWritePaths=${STORE_BASE} ${LOG_DIR} ReadOnlyPaths=${CONFIG_DIR} ${SSL_DIR} [Install] WantedBy=multi-user.target UNIT cat > /etc/systemd/system/archivdms-web.service << UNIT [Unit] Description=archivdms Next.js Frontend After=network.target archivdms.service [Service] Type=simple User=${DMS_USER} Group=${DMS_USER} WorkingDirectory=${INSTALL_DIR}/web ExecStart=/usr/bin/node server.js Environment=NODE_ENV=production Environment=PORT=3000 Restart=on-failure RestartSec=5 StandardOutput=journal StandardError=journal SyslogIdentifier=archivdms-web [Install] WantedBy=multi-user.target UNIT systemctl daemon-reload systemctl enable archivdms archivdms-web --quiet log "systemd Units erstellt und aktiviert" # ── 11. Erstes Build + Deployment via update.sh ───────────────────────────── info "Führe update.sh für den ersten Build/Deploy aus..." if [[ -f "$SCRIPT_DIR/update.sh" ]]; then chmod +x "$SCRIPT_DIR/update.sh" ARCHIVDMS_SRC="$ARCHIVDMS_SRC" bash "$SCRIPT_DIR/update.sh" else warn "update.sh nicht gefunden neben install.sh — Build/Start manuell nötig: bash update.sh" fi # ── Superadmin-Passwort aus dem Log ziehen (siehe main.go seedDefaultUsers) ─ PW_SUPERADMIN="(nicht gefunden — journalctl -u archivdms | grep superadmin)" for _i in $(seq 1 15); do _pw=$(journalctl -u archivdms --no-pager -n 200 2>/dev/null | grep -A1 'superadmin :' | grep -oP 'superadmin\s*:\s*\K\S+' | tail -1) if [[ -n "$_pw" ]]; then PW_SUPERADMIN="$_pw" break fi sleep 1 done # ── Zusammenfassung ─────────────────────────────────────────────────────── SUMMARY_FILE="$CONFIG_DIR/install-summary.txt" cat > "$SUMMARY_FILE" << SUMMARY archivdms Native-Installation — $(date '+%d.%m.%Y %H:%M:%S') Server: $FQDN === ZUGANGSDATEN === Datenbank: archivdms / $DB_PASSWORD API-Secret: $API_SECRET Web-Login (UNBEDINGT ÄNDERN!): superadmin / $PW_SUPERADMIN === DIENSTE === Web (HTTPS): https://$FQDN API intern: 127.0.0.1:8080 Frontend intern: 127.0.0.1:3000 === DATEIPFADE === Konfiguration: $CONFIG_DIR/config.yml Storage: $STORE_BASE/{inbox,store,ocr-tmp} TLS-Zertifikat: $SSL_DIR/archivdms.crt Logs: $LOG_DIR/ Updater: $SCRIPT_DIR/update.sh Quellcode: $ARCHIVDMS_SRC SUMMARY chmod 600 "$SUMMARY_FILE" log "Zusammenfassung: $SUMMARY_FILE" # ── Abschluss ──────────────────────────────────────────────────────────── echo "" echo " ╔══════════════════════════════════════════════════════════╗" echo " ║ Installation abgeschlossen! ║" echo " ╚══════════════════════════════════════════════════════════╝" echo "" echo " Web (HTTPS): https://$FQDN" echo "" echo " ┌─────────────────────────────────────────────────────────┐" printf " │ DB archivdms: %-40s │\n" "$DB_PASSWORD" printf " │ superadmin / %-40s │\n" "$PW_SUPERADMIN" echo " └─────────────────────────────────────────────────────────┘" echo "" warn "Zusammenfassung mit Passwörtern: $SUMMARY_FILE" warn "Standardpasswort unbedingt nach dem ersten Login ändern!" echo ""