ProtectSystem=strict + ReadOnlyPaths=.../etc/ssl/archivmail blockierte den Cert-Handler (internal/api/cert_handlers.go) beim Erstellen/Erneuern von Zertifikaten mit "read-only file system" — Fehler kam vom systemd-Namespace, nicht von einem echten ro-Mount oder Owner-Problem. SSL_DIR gehört in ReadWritePaths statt ReadOnlyPaths, da die Anwendung dort aktiv schreibt. Fix im Unit-Generator in update.sh, nicht nur manuell am Server, da die Unit bei jedem Deploy neu geschrieben wird. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_019j28kGcaJAhBnrYX34hGdt
448 lines
21 KiB
Bash
Executable File
448 lines
21 KiB
Bash
Executable File
#!/bin/bash
|
|
# archivmail Updater
|
|
# Zieht die neueste Version aus Gitea, baut Frontend + Backend und startet Dienste neu.
|
|
#
|
|
# Aufruf (auf dem Server als root):
|
|
# bash /opt/archivmail/update.sh
|
|
#
|
|
# Oder direkt von Gitea laden und ausführen:
|
|
# curl -fsSL https://gitea.perlbach24.de/scripte/archivmail/raw/branch/main/update.sh | bash
|
|
|
|
set -euo pipefail
|
|
|
|
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 update.sh"
|
|
|
|
# PROJ-68: Backfill für Bestandsinstallationen, bei denen sudo-Rechte für die
|
|
# Admin-Dienststeuerung nie provisioniert wurden (siehe install.sh für den
|
|
# ausführlichen Kommentar zum "Warum"). Whitelist MUSS mit `allowedServices`
|
|
# in internal/api/admin_services_handlers.go synchron gehalten werden.
|
|
provision_archivmail_sudoers() {
|
|
local am_user="archivmail"
|
|
command -v sudo >/dev/null 2>&1 || apt-get install -y -qq sudo
|
|
|
|
local services=("archivmail" "archivmail-web" "manticore" "postgresql@17-main" "postfix" "nginx")
|
|
local tmpfile
|
|
tmpfile="$(mktemp)"
|
|
{
|
|
echo "# Verwaltet von install.sh/update.sh (PROJ-68) — nicht manuell editieren,"
|
|
echo "# Änderungen gehen beim nächsten Deploy verloren. Whitelist muss mit"
|
|
echo "# allowedServices in internal/api/admin_services_handlers.go übereinstimmen."
|
|
for svc in "${services[@]}"; do
|
|
echo "${am_user} ALL=(root) NOPASSWD: /usr/bin/systemctl start ${svc}.service"
|
|
echo "${am_user} ALL=(root) NOPASSWD: /usr/bin/systemctl stop ${svc}.service"
|
|
echo "${am_user} ALL=(root) NOPASSWD: /usr/bin/systemctl restart ${svc}.service"
|
|
echo "${am_user} ALL=(root) NOPASSWD: /usr/bin/systemctl enable ${svc}.service"
|
|
echo "${am_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/archivmail
|
|
log "sudo-Rechte für Dienststeuerung aktuell (/etc/sudoers.d/archivmail)"
|
|
else
|
|
warn "Generierte sudoers-Regel ist ungültig — /etc/sudoers.d/archivmail NICHT verändert"
|
|
fi
|
|
rm -f "$tmpfile"
|
|
}
|
|
provision_archivmail_sudoers
|
|
|
|
REPO_URL="${REPO_URL:-https://gitea.perlbach24.de/scripte/archivmail.git}"
|
|
INSTALL_DIR="/opt/archivmail"
|
|
BUILD_DIR="/opt/archivmail/_build"
|
|
FRONTEND_DIR="/opt/archivmail/web"
|
|
BIN_DIR="/opt/archivmail/bin"
|
|
|
|
echo ""
|
|
echo " ╔══════════════════════════════════════╗"
|
|
echo " ║ archivmail Updater ║"
|
|
echo " ╚══════════════════════════════════════╝"
|
|
echo ""
|
|
|
|
# ── Script selbst aktualisieren ──────────────────────────────────────────
|
|
|
|
SCRIPT_URL="https://gitea.perlbach24.de/scripte/archivmail/raw/branch/main/update.sh"
|
|
SELF="$INSTALL_DIR/update.sh"
|
|
if [[ -f "$SELF" ]] && command -v curl >/dev/null; then
|
|
curl -fsSL "$SCRIPT_URL" -o "$SELF.new" 2>/dev/null && mv "$SELF.new" "$SELF" && chmod +x "$SELF" \
|
|
&& info "Script aktualisiert" || rm -f "$SELF.new"
|
|
fi
|
|
|
|
# ── Go: Version sicherstellen (OS-versions-agnostisch) ───────────────────
|
|
# Go wird unter /usr/local/go installiert — unabhängig von apt und Debian-Version.
|
|
# Das gilt nach einem OS-Upgrade auf Debian 14+ genauso wie auf trixie.
|
|
|
|
GO_REQUIRED="1.24.4"
|
|
export PATH="$PATH:/usr/local/go/bin"
|
|
|
|
_go_ok=0
|
|
if command -v go >/dev/null 2>&1; then
|
|
_go_installed=$(go version 2>/dev/null | grep -oP 'go\K[0-9]+\.[0-9]+' | head -1)
|
|
_go_major=${_go_installed%%.*}
|
|
_go_minor=${_go_installed#*.}
|
|
# >= 1.24 gilt als ausreichend
|
|
if [[ "$_go_major" -gt 1 ]] || [[ "$_go_major" -eq 1 && "$_go_minor" -ge 24 ]]; then
|
|
_go_ok=1
|
|
fi
|
|
fi
|
|
|
|
if [[ $_go_ok -eq 0 ]]; then
|
|
info "Go < 1.24 oder nicht gefunden — installiere Go ${GO_REQUIRED} von upstream..."
|
|
_go_arch="amd64"
|
|
[[ "$(uname -m)" == "aarch64" ]] && _go_arch="arm64"
|
|
curl -fsSL "https://dl.google.com/go/go${GO_REQUIRED}.linux-${_go_arch}.tar.gz" \
|
|
| tar -C /usr/local -xz
|
|
ln -sf /usr/local/go/bin/go /usr/local/bin/go
|
|
ln -sf /usr/local/go/bin/gofmt /usr/local/bin/gofmt
|
|
log "Go $(go version | awk '{print $3}') installiert"
|
|
else
|
|
log "Go $(go version | awk '{print $3}') OK"
|
|
fi
|
|
|
|
# ── Sonstige Voraussetzungen ──────────────────────────────────────────────
|
|
|
|
command -v git >/dev/null || die "git nicht gefunden — apt-get install git"
|
|
command -v node >/dev/null || die "node nicht gefunden — apt-get install nodejs"
|
|
command -v npm >/dev/null || die "npm nicht gefunden — apt-get install npm"
|
|
|
|
# ── Manticore Search prüfen / installieren ────────────────────────────────
|
|
# Paket ist an bookworm gebunden — funktioniert auch auf trixie und Debian 14+,
|
|
# da Manticore kein OS-spezifisches ABI nutzt (repo.manticoresearch.com hat
|
|
# kein eigenes trixie-Repo, siehe Release-Notes ab v13.6.7 "Added support for
|
|
# Debian 13 Trixie" — das bezieht sich auf Lauffähigkeit, nicht auf ein neues
|
|
# Repo). Seit ~v25 ist "manticoresearch" ein einziges "manticore"-Paket
|
|
# (vorher mehrere manticore-server/-tools/-dev-Pakete) — Paketname und
|
|
# Pfadstruktur im Repo haben sich entsprechend geändert. GitHub-Releases
|
|
# liefern seit einiger Zeit keine .deb-Assets mehr (leeres assets[]), daher
|
|
# kein Fallback mehr dorthin — repo.manticoresearch.com ist der einzige Weg.
|
|
#
|
|
# Version wird dynamisch aus der Packages-Datei des Repos ermittelt (letzter
|
|
# "Package: manticore"-Block, dort steht Version+Filename explizit drin —
|
|
# kein Rätselraten über Namensschema). _MANTICORE_VERSION_FALLBACK ist nur
|
|
# ein Sicherheitsnetz, falls die Packages-Datei mal nicht erreichbar/nicht
|
|
# parsbar ist (Netzwerkfehler, Repo down, Format ändert sich erneut) — dann
|
|
# lieber ein bekannter, getesteter Stand als ein komplett fehlgeschlagenes
|
|
# Deploy. Bei jedem Update-Lauf ohnehin neu geprüft, kein manuelles Nachpflegen
|
|
# nötig, solange das Packages-Format stabil bleibt.
|
|
_MANTICORE_VERSION_FALLBACK="27.1.5-26061911-5a1cf9399"
|
|
|
|
_mc_installed_version="$(dpkg-query -W -f='${Version}' manticore 2>/dev/null || true)"
|
|
|
|
_mc_arch="amd64"
|
|
[[ "$(uname -m)" == "aarch64" ]] && _mc_arch="arm64"
|
|
|
|
# Liefert bei Erfolg "VERSION|FILENAME" (Filename ist der repo-relative Pfad
|
|
# aus der Packages-Datei, z.B. "dists/bookworm/main/binary-amd64/manticore_
|
|
# 27.1.5-...deb") auf stdout, sonst nichts (Exit 1).
|
|
_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
|
|
# Packages-Einträge sind durch Leerzeilen getrennt; mehrere historische
|
|
# "Package: manticore"-Blöcke möglich — der letzte ist der aktuellste.
|
|
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() {
|
|
# $1 = human-readable action word for log ("installieren"/"aktualisieren")
|
|
wget -q -O /tmp/manticore.deb "$_mc_target_url" || true
|
|
if [[ ! -f /tmp/manticore.deb ]]; then
|
|
warn "Manticore Search konnte nicht heruntergeladen werden — bitte manuell $1"
|
|
warn "Siehe: https://manticoresearch.com/install/"
|
|
return 1
|
|
fi
|
|
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 "Manticore Search nicht gefunden — installiere Version ${_mc_target_version}..."
|
|
_mc_fetch_and_install "installieren" && log "Manticore Search installiert"
|
|
elif dpkg --compare-versions "$_mc_installed_version" lt "$_mc_target_version"; then
|
|
# PROJ-67: bestehende Installation ist älter als der ermittelte Repo-Stand —
|
|
# Upgrade statt Skip, damit auch Bestandsinstallationen (nicht nur
|
|
# Neuinstallationen) beim nächsten Deploy automatisch mitgezogen werden.
|
|
# Downgrade-Schutz durch "lt"-Vergleich: eine bereits neuere installierte
|
|
# Version wird nie zurückgestuft (z.B. falls die Repo-Abfrage mal einen
|
|
# älteren Stand als den Fallback liefern sollte).
|
|
info "Manticore Search ${_mc_installed_version} gefunden, aktualisiere auf ${_mc_target_version}..."
|
|
systemctl stop manticore 2>/dev/null || true
|
|
if _mc_fetch_and_install "aktualisieren"; then
|
|
log "Manticore Search aktualisiert: ${_mc_installed_version} → ${_mc_target_version}"
|
|
else
|
|
warn "Upgrade fehlgeschlagen — vorherige Version bleibt installiert, Dienst wird trotzdem neu gestartet"
|
|
fi
|
|
else
|
|
log "Manticore Search ${_mc_installed_version} bereits aktuell (>= ${_mc_target_version})"
|
|
fi
|
|
|
|
if systemctl list-unit-files manticore.service >/dev/null 2>&1; then
|
|
systemctl enable manticore 2>/dev/null || true
|
|
systemctl is-active --quiet manticore || systemctl start manticore 2>/dev/null || warn "Manticore-Dienst konnte nicht gestartet werden"
|
|
systemctl is-active --quiet manticore && log "Manticore Search läuft"
|
|
fi
|
|
|
|
# ── OCR-Tools (PROJ-35: tesseract + poppler) ──────────────────────────────
|
|
# Optional: ohne diese Tools fällt OCR auf "deaktiviert" zurück, kein Abbruch.
|
|
if ! command -v tesseract >/dev/null 2>&1 || ! command -v pdftotext >/dev/null 2>&1; then
|
|
info "Installiere OCR-Tools (tesseract, poppler-utils)..."
|
|
apt-get install -y tesseract-ocr tesseract-ocr-deu poppler-utils 2>/dev/null || \
|
|
warn "OCR-Tools konnten nicht installiert werden — OCR wird deaktiviert"
|
|
fi
|
|
if command -v tesseract >/dev/null 2>&1 && command -v pdftotext >/dev/null 2>&1; then
|
|
log "OCR-Tools verfügbar (tesseract $(tesseract --version 2>&1 | head -1 | awk '{print $2}'))"
|
|
fi
|
|
|
|
# ── Quellcode holen ───────────────────────────────────────────────────────
|
|
|
|
if [[ -d "$BUILD_DIR/.git" ]]; then
|
|
info "Aktualisiere Quellcode aus Gitea..."
|
|
git config --global --add safe.directory "$BUILD_DIR" 2>/dev/null || true
|
|
git -C "$BUILD_DIR" fetch origin
|
|
git -C "$BUILD_DIR" reset --hard origin/main
|
|
log "Quellcode aktualisiert ($(git -C "$BUILD_DIR" log -1 --format='%h %s'))"
|
|
else
|
|
info "Lade Quellcode von $REPO_URL ..."
|
|
mkdir -p "$BUILD_DIR"
|
|
git clone "$REPO_URL" "$BUILD_DIR"
|
|
log "Quellcode geladen"
|
|
fi
|
|
|
|
# ── Go Backend bauen ──────────────────────────────────────────────────────
|
|
|
|
info "Baue Go Backend..."
|
|
cd "$BUILD_DIR"
|
|
go mod tidy && go mod download
|
|
CGO_ENABLED=0 go build -buildvcs=false -o "$BUILD_DIR/archivmail-new" ./cmd/archivmail/
|
|
log "Go Backend gebaut"
|
|
|
|
# ── Next.js Frontend bauen ────────────────────────────────────────────────
|
|
|
|
info "Installiere Node-Abhängigkeiten..."
|
|
npm ci --prefer-offline 2>/dev/null || npm ci
|
|
log "Node-Abhängigkeiten installiert"
|
|
|
|
if ! npm audit --audit-level=high >/tmp/npm-audit-report.txt 2>&1; then
|
|
warn "npm audit meldet High/Critical-Vulnerabilities — Details: /tmp/npm-audit-report.txt"
|
|
warn "Fix NICHT automatisch während des Deploys (Risiko: Breaking Change live) — 'npm audit fix' lokal ausführen, testen, committen."
|
|
fi
|
|
|
|
info "Baue Next.js Frontend..."
|
|
npm run build
|
|
log "Frontend gebaut"
|
|
|
|
# ── Dienste stoppen ───────────────────────────────────────────────────────
|
|
|
|
info "Stoppe Dienste..."
|
|
systemctl stop archivmail-web 2>/dev/null || warn "archivmail-web nicht aktiv"
|
|
systemctl stop archivmail 2>/dev/null || warn "archivmail nicht aktiv"
|
|
|
|
# ── Manticore als Standard-Backend in config.yml setzen ──────────────────
|
|
CONFIG_FILE="/etc/archivmail/config.yml"
|
|
if [[ -f "$CONFIG_FILE" ]]; then
|
|
# Backend auf manticore umstellen falls noch nicht gesetzt
|
|
if grep -q 'backend:' "$CONFIG_FILE"; then
|
|
if ! grep -q 'backend: manticore' "$CONFIG_FILE"; then
|
|
sed -i 's/^\([[:space:]]*\)backend:.*/\1backend: manticore/' "$CONFIG_FILE"
|
|
info "Index-Backend auf 'manticore' gesetzt"
|
|
fi
|
|
else
|
|
# backend: Zeile unter index: einfuegen
|
|
sed -i '/^index:/a\ backend: manticore' "$CONFIG_FILE"
|
|
info "Index-Backend 'manticore' hinzugefuegt"
|
|
fi
|
|
# manticore_dsn setzen falls nicht vorhanden
|
|
if ! grep -q 'manticore_dsn' "$CONFIG_FILE"; then
|
|
sed -i '/backend: manticore/a\ manticore_dsn: "manticore@tcp(127.0.0.1:9306)/"' "$CONFIG_FILE"
|
|
info "Manticore-DSN gesetzt"
|
|
fi
|
|
log "Manticore-Konfiguration aktualisiert"
|
|
fi
|
|
|
|
# ── Dateien einspielen ────────────────────────────────────────────────────
|
|
|
|
info "Spiele Backend ein..."
|
|
mkdir -p "$BIN_DIR"
|
|
cp "$BUILD_DIR/archivmail-new" "$BIN_DIR/archivmail"
|
|
chmod +x "$BIN_DIR/archivmail"
|
|
# Service-Binary direkt überschreiben (nach systemctl stop)
|
|
cp "$BIN_DIR/archivmail" "$INSTALL_DIR/archivmail"
|
|
ln -sf "$BIN_DIR/archivmail" /usr/local/bin/archivmail
|
|
log "Backend eingespielt"
|
|
|
|
info "Spiele Cron-Jobs ein..."
|
|
cp "$BUILD_DIR/deploy/cron.d/archivmail" /etc/cron.d/archivmail
|
|
chmod 644 /etc/cron.d/archivmail
|
|
for script in "$BUILD_DIR"/deploy/cron.d/*.sh; do
|
|
cp "$script" "/usr/local/bin/$(basename "$script")"
|
|
chmod 755 "/usr/local/bin/$(basename "$script")"
|
|
done
|
|
log "Cron-Jobs eingespielt (/etc/cron.d/archivmail + Wrapper-Skripte)"
|
|
|
|
info "Spiele Frontend ein (standalone)..."
|
|
# Next.js standalone mirrors the absolute build path — find the dir that contains server.js.
|
|
# The path is typically: .next/standalone/<build-dir>/server.js
|
|
STANDALONE_SERVER=$(find "$BUILD_DIR/.next/standalone" -maxdepth 3 -name "server.js" | head -1)
|
|
[[ -n "$STANDALONE_SERVER" ]] || die "server.js nicht im standalone-Build gefunden"
|
|
STANDALONE_ROOT=$(dirname "$STANDALONE_SERVER")
|
|
# Clean destination and copy fresh standalone output
|
|
rm -rf "$FRONTEND_DIR"
|
|
mkdir -p "$FRONTEND_DIR"
|
|
rsync -a "$STANDALONE_ROOT/" "$FRONTEND_DIR/"
|
|
# Copy static assets from the full build (standalone doesn't include them)
|
|
mkdir -p "$FRONTEND_DIR/.next/static"
|
|
rsync -a --delete "$BUILD_DIR/.next/static/" "$FRONTEND_DIR/.next/static/"
|
|
if [[ -d "$BUILD_DIR/public" ]]; then
|
|
rsync -a "$BUILD_DIR/public/" "$FRONTEND_DIR/public/"
|
|
fi
|
|
log "Frontend eingespielt"
|
|
|
|
# ── systemd Units synchronisieren ─────────────────────────────────────────
|
|
# install.sh schreibt die Unit-Dateien nur beim Erst-Setup. Ohne diesen
|
|
# Schritt bleiben bereits installierte Server auf dem Stand von damals
|
|
# eingefroren, selbst wenn sich die Unit-Definition im Repo ändert (z.B.
|
|
# CAP_NET_ADMIN für die Security-Tab-Firewall-Aktion, nachträglich ergänzt —
|
|
# Server die vor dieser Änderung installiert wurden, hatten die Capability
|
|
# bis zu diesem Fix hier gefehlt). Bei jedem Deploy neu schreiben + reload,
|
|
# damit Unit-Änderungen aus install.sh automatisch nachgezogen werden.
|
|
info "Synchronisiere systemd Units..."
|
|
STORE_DIR="/var/archivmail"
|
|
LOG_DIR="/var/log/archivmail"
|
|
CONFIG_DIR="/etc/archivmail"
|
|
SSL_DIR="/etc/ssl/archivmail"
|
|
AM_USER="archivmail"
|
|
|
|
cat > /etc/systemd/system/archivmail.service << UNIT
|
|
[Unit]
|
|
Description=archivmail Mail Archive Daemon
|
|
After=network.target postgresql.service manticore.service
|
|
Requires=postgresql.service manticore.service
|
|
|
|
[Service]
|
|
Type=simple
|
|
User=${AM_USER}
|
|
Group=${AM_USER}
|
|
# CAP_NET_ADMIN: required for the admin "enable firewall" action (nft -f /etc/nftables.conf)
|
|
AmbientCapabilities=CAP_NET_BIND_SERVICE CAP_NET_ADMIN
|
|
# PROJ-68: CAP_SETUID/CAP_SETGID are NOT ambient (the Go daemon itself must
|
|
# never hold them) but must be in the bounding set so the setuid-root sudo
|
|
# child process (spawned by the admin "Dienste"-Tab for systemctl actions)
|
|
# can actually change to uid/gid 0. Without this, sudo aborts with
|
|
# "unable to change to root gid: Operation not permitted" even though the
|
|
# sudoers NOPASSWD rule is correct — verified 2026-07-05, see
|
|
# features/PROJ-68-sudo-provisionierung-dienststeuerung.md.
|
|
CapabilityBoundingSet=CAP_NET_BIND_SERVICE CAP_NET_ADMIN CAP_SETUID CAP_SETGID
|
|
ExecStart=${INSTALL_DIR}/archivmail --config ${CONFIG_DIR}/config.yml
|
|
ExecReload=/bin/kill -HUP \$MAINPID
|
|
Restart=on-failure
|
|
RestartSec=5
|
|
StandardOutput=journal
|
|
StandardError=journal
|
|
SyslogIdentifier=archivmail
|
|
NoNewPrivileges=false
|
|
ProtectSystem=strict
|
|
ReadWritePaths=${STORE_DIR} ${LOG_DIR} ${SSL_DIR}
|
|
ReadOnlyPaths=${CONFIG_DIR}
|
|
|
|
[Install]
|
|
WantedBy=multi-user.target
|
|
UNIT
|
|
|
|
cat > /etc/systemd/system/archivmail-web.service << UNIT
|
|
[Unit]
|
|
Description=archivmail Web Frontend
|
|
After=network.target archivmail.service
|
|
|
|
[Service]
|
|
Type=simple
|
|
User=${AM_USER}
|
|
Group=${AM_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=archivmail-web
|
|
|
|
[Install]
|
|
WantedBy=multi-user.target
|
|
UNIT
|
|
|
|
systemctl daemon-reload
|
|
systemctl enable archivmail archivmail-web --quiet
|
|
log "systemd Units synchronisiert"
|
|
|
|
# ── Dienste starten ───────────────────────────────────────────────────────
|
|
|
|
info "Starte Dienste..."
|
|
systemctl start archivmail
|
|
systemctl start archivmail-web
|
|
log "Dienste gestartet"
|
|
|
|
# ── Status prüfen ─────────────────────────────────────────────────────────
|
|
# Hinweis: Kein automatischer Manticore-Reindex mehr bei jedem Update.
|
|
# Neu importierte Mails (IMAP/SMTP/POP3/EML) werden synchron indiziert
|
|
# (siehe internal/imap/importer.go IndexSync) — ein Reindex ist nur nach
|
|
# Schema-Änderungen am Index nötig: manuell mit
|
|
# archivmail reindex --config /etc/archivmail/config.yml
|
|
|
|
BACKEND_OK=0
|
|
FRONTEND_OK=0
|
|
|
|
# Health-Check statt nur systemd-Status: Backend muss tatsächlich auf
|
|
# HTTP antworten (Prozess kann laufen, aber z.B. an DB-Connect hängen).
|
|
info "Prüfe Backend-Health (/api/health)..."
|
|
for i in $(seq 1 15); do
|
|
if systemctl is-active --quiet archivmail && curl -fsS -o /dev/null --max-time 2 http://127.0.0.1:8080/api/health 2>/dev/null; then
|
|
BACKEND_OK=1
|
|
break
|
|
fi
|
|
sleep 1
|
|
done
|
|
|
|
info "Prüfe Frontend-Health (Port 3000)..."
|
|
for i in $(seq 1 15); do
|
|
if systemctl is-active --quiet archivmail-web && curl -fsS -o /dev/null --max-time 2 http://127.0.0.1:3000/ 2>/dev/null; then
|
|
FRONTEND_OK=1
|
|
break
|
|
fi
|
|
sleep 1
|
|
done
|
|
|
|
echo ""
|
|
echo " ┌──────────────────────────────────────┐"
|
|
[[ $BACKEND_OK -eq 1 ]] && echo " │ Backend ✓ läuft │" \
|
|
|| echo " │ Backend ✗ nicht aktiv │"
|
|
[[ $FRONTEND_OK -eq 1 ]] && echo " │ Frontend ✓ läuft │" \
|
|
|| echo " │ Frontend ✗ nicht aktiv │"
|
|
echo " └──────────────────────────────────────┘"
|
|
echo ""
|
|
|
|
[[ $BACKEND_OK -eq 1 && $FRONTEND_OK -eq 1 ]] && log "Update abgeschlossen." \
|
|
|| warn "Ein oder mehrere Dienste sind nicht aktiv. Prüfe: journalctl -u archivmail -u archivmail-web"
|