Files
archivmail/install.sh
T
sysops b73ef55a65 fix: archivmail.service benötigt CAP_NET_ADMIN für Firewall-Aktivierung
Der Admin-Endpoint "Firewall aktivieren" (POST /api/admin/security/fix,
enable_firewall) ruft "nft -f /etc/nftables.conf" auf. flush ruleset
benötigt CAP_NET_ADMIN, das fehlte bisher in der systemd-Unit, wodurch
der Aufruf mit "Operation not permitted" fehlschlug.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-06-13 22:01:29 +02:00

772 lines
34 KiB
Bash
Executable File
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
#!/bin/bash
# archivmail Installer
# Unterstützte Systeme: Debian 12 / 13
#
# Aufruf:
# bash install.sh # Interaktiv (Modus-Auswahl)
# INSTALL_MODE=native bash install.sh # Nativ (systemd, direkter Build)
# INSTALL_MODE=docker bash install.sh # Docker (alle Abhängigkeiten im Container)
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"
# ── 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)}"
WEBHOOK_SECRET="${WEBHOOK_SECRET:-$(openssl rand -base64 32 | tr -dc 'a-zA-Z0-9' | head -c 40)}"
INSTALL_DIR="/opt/archivmail"
STORE_DIR="/var/archivmail"
LOG_DIR="/var/log/archivmail"
CONFIG_DIR="/etc/archivmail"
SSL_DIR="/etc/ssl/archivmail"
AM_USER="archivmail"
FQDN="$(hostname -f 2>/dev/null || hostname)"
REPO_URL="${REPO_URL:-https://gitea.perlbach24.de/scripte/archivmail.git}"
# ── Banner ────────────────────────────────────────────────────────────────────
echo ""
echo " ╔══════════════════════════════════════════╗"
echo " ║ archivmail Installer v2.0 ║"
echo " ╚══════════════════════════════════════════╝"
echo ""
info "Hostname: $FQDN"
echo ""
# ── Modus-Auswahl ─────────────────────────────────────────────────────────────
INSTALL_MODE="${INSTALL_MODE:-}"
if [[ -z "$INSTALL_MODE" ]]; then
echo " Installations-Modus wählen:"
echo ""
echo " 1) Nativ — Go/Node.js direkt auf dem Server kompiliert und gebaut"
echo " (systemd-Dienste, PostgreSQL lokal, kein Docker nötig)"
echo ""
echo " 2) Docker — Alle Abhängigkeiten (Go, Node.js, PostgreSQL, Manticore)"
echo " laufen in Containern. Kein Build-Toolchain auf dem Host."
echo " Deployment via GitHub Webhook (git pull → docker compose up)"
echo ""
read -rp " Auswahl [1/2]: " _choice
case "$_choice" in
1) INSTALL_MODE="native" ;;
2) INSTALL_MODE="docker" ;;
*) die "Ungültige Auswahl: $_choice" ;;
esac
fi
[[ "$INSTALL_MODE" == "native" || "$INSTALL_MODE" == "docker" ]] \
|| die "INSTALL_MODE muss 'native' oder 'docker' sein (aktuell: $INSTALL_MODE)"
echo ""
info "Modus: $INSTALL_MODE"
echo ""
# ══════════════════════════════════════════════════════════════════════════════
# DOCKER-INSTALLATION
# ══════════════════════════════════════════════════════════════════════════════
install_docker() {
# ── Docker installieren ───────────────────────────────────────────────────
info "Installiere Docker..."
if command -v docker &>/dev/null; then
log "Docker bereits installiert: $(docker --version)"
else
apt-get update -qq
apt-get install -y -qq ca-certificates curl gnupg
install -m 0755 -d /etc/apt/keyrings
curl -fsSL https://download.docker.com/linux/debian/gpg \
| gpg --dearmor -o /etc/apt/keyrings/docker.gpg
chmod a+r /etc/apt/keyrings/docker.gpg
echo "deb [arch=$(dpkg --print-architecture) signed-by=/etc/apt/keyrings/docker.gpg] \
https://download.docker.com/linux/debian $(. /etc/os-release && echo "$VERSION_CODENAME") stable" \
> /etc/apt/sources.list.d/docker.list
apt-get update -qq
apt-get install -y -qq docker-ce docker-ce-cli containerd.io docker-compose-plugin
systemctl enable --now docker
log "Docker $(docker --version) installiert"
fi
# ── Hilfspakete ───────────────────────────────────────────────────────────
info "Installiere Hilfspakete..."
apt-get install -y -qq git curl nginx openssl logrotate
log "Pakete installiert"
# ── webhook-Binary (adnanh/webhook) ──────────────────────────────────────
info "Installiere webhook-Empfänger..."
if ! command -v webhook &>/dev/null; then
WEBHOOK_BIN_VERSION="2.8.2"
_arch="$(dpkg --print-architecture)"
[[ "$_arch" == "amd64" ]] && _arch="linux_amd64"
[[ "$_arch" == "arm64" ]] && _arch="linux_arm64"
curl -fsSL \
"https://github.com/adnanh/webhook/releases/download/${WEBHOOK_BIN_VERSION}/webhook-${_arch}.tar.gz" \
| tar -xz -C /usr/local/bin --strip-components=1 "webhook-${_arch}/webhook"
chmod +x /usr/local/bin/webhook
log "webhook ${WEBHOOK_BIN_VERSION} installiert"
else
log "webhook bereits installiert"
fi
# ── Verzeichnisse ─────────────────────────────────────────────────────────
info "Erstelle Verzeichnisse..."
mkdir -p "$CONFIG_DIR" "$LOG_DIR"
log "Verzeichnisse erstellt"
# ── Repository klonen ─────────────────────────────────────────────────────
info "Klone Repository..."
if [[ -d "$INSTALL_DIR/.git" ]]; then
log "Repository existiert bereits — git pull"
git -C "$INSTALL_DIR" pull origin main
else
git clone "$REPO_URL" "$INSTALL_DIR"
log "Repository geklont: $INSTALL_DIR"
fi
# ── Keyfile ───────────────────────────────────────────────────────────────
info "Generiere Verschlüsselungs-Keyfile..."
if [[ ! -f "$CONFIG_DIR/keyfile" ]]; then
openssl rand -base64 32 > "$CONFIG_DIR/keyfile"
chmod 400 "$CONFIG_DIR/keyfile"
log "Keyfile generiert: $CONFIG_DIR/keyfile"
else
log "Keyfile existiert bereits wird nicht überschrieben"
fi
# ── TLS-Zertifikat ────────────────────────────────────────────────────────
info "Erstelle selbstsigniertes TLS-Zertifikat..."
mkdir -p "$SSL_DIR"
if [[ ! -f "$SSL_DIR/archivmail.crt" ]]; then
SERVER_IP="$(hostname -I | awk '{print $1}')"
openssl req -x509 -nodes -days 3650 -newkey rsa:4096 \
-keyout "$SSL_DIR/archivmail.key" \
-out "$SSL_DIR/archivmail.crt" \
-subj "/CN=${FQDN}/O=archivmail/C=DE" \
-addext "subjectAltName=DNS:${FQDN},DNS:$(hostname -s),IP:${SERVER_IP}" \
2>/dev/null
chmod 640 "$SSL_DIR/archivmail.key"
chmod 644 "$SSL_DIR/archivmail.crt"
log "TLS-Zertifikat erstellt"
else
log "TLS-Zertifikat existiert bereits wird nicht überschrieben"
fi
# ── config.yml aus Vorlage ────────────────────────────────────────────────
info "Erstelle Konfigurationsdatei..."
if [[ ! -f "$CONFIG_DIR/config.yml" ]]; then
sed \
-e "s/CHANGE_ME_DB_PASSWORD/$DB_PASSWORD/g" \
-e "s/CHANGE_ME_64_CHAR_SECRET/$API_SECRET/g" \
-e "s/mail\.example\.com/$FQDN/g" \
"$INSTALL_DIR/config/config.docker.yml.example" \
> "$CONFIG_DIR/config.yml"
chmod 640 "$CONFIG_DIR/config.yml"
log "config.yml erstellt: $CONFIG_DIR/config.yml"
else
log "config.yml existiert bereits wird nicht überschrieben"
fi
# ── .env für docker-compose ───────────────────────────────────────────────
info "Erstelle .env..."
if [[ ! -f "$INSTALL_DIR/.env" ]]; then
printf 'DB_PASSWORD=%s\n' "$DB_PASSWORD" > "$INSTALL_DIR/.env"
chmod 600 "$INSTALL_DIR/.env"
log ".env erstellt"
else
log ".env existiert bereits wird nicht überschrieben"
fi
# ── deploy.sh ausführbar ──────────────────────────────────────────────────
chmod +x "$INSTALL_DIR/scripts/deploy.sh"
# ── logrotate ─────────────────────────────────────────────────────────────
cat > /etc/logrotate.d/archivmail <<EOF
${LOG_DIR}/deploy.log {
weekly
rotate 12
compress
delaycompress
missingok
notifempty
}
EOF
log "logrotate konfiguriert"
# ── webhook systemd-Dienst ────────────────────────────────────────────────
info "Richte webhook-Dienst ein..."
cat > /etc/systemd/system/archivmail-webhook.service <<EOF
[Unit]
Description=archivmail GitHub Webhook Receiver
After=network.target
[Service]
Type=simple
User=root
WorkingDirectory=$INSTALL_DIR
Environment="WEBHOOK_SECRET=$WEBHOOK_SECRET"
Environment="INSTALL_DIR=$INSTALL_DIR"
ExecStart=/usr/local/bin/webhook \\
-hooks $INSTALL_DIR/scripts/webhook-hooks.json \\
-port 9000 \\
-ip 127.0.0.1 \\
-verbose
Restart=always
RestartSec=5
[Install]
WantedBy=multi-user.target
EOF
systemctl daemon-reload
systemctl enable --now archivmail-webhook
log "archivmail-webhook Dienst gestartet"
# ── nginx ─────────────────────────────────────────────────────────────────
info "Konfiguriere nginx..."
cat > /etc/nginx/sites-available/archivmail <<EOF
# 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}/archivmail.crt;
ssl_certificate_key ${SSL_DIR}/archivmail.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;
# GitHub Webhook
location /hooks/ {
proxy_pass http://127.0.0.1:9000/hooks/;
proxy_set_header Host \$host;
proxy_set_header X-Real-IP \$remote_addr;
proxy_read_timeout 30s;
}
# Go API
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;
}
# Next.js Frontend
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;
}
access_log /var/log/nginx/archivmail.access.log;
error_log /var/log/nginx/archivmail.error.log;
}
EOF
ln -sf /etc/nginx/sites-available/archivmail /etc/nginx/sites-enabled/archivmail
rm -f /etc/nginx/sites-enabled/default
nginx -t && systemctl enable --now nginx && systemctl reload nginx
log "nginx konfiguriert"
# ── Docker Compose Stack starten ──────────────────────────────────────────
info "Baue und starte Docker Compose Stack..."
cd "$INSTALL_DIR"
docker compose up -d --build
log "Stack gestartet"
# ── Zusammenfassung speichern ─────────────────────────────────────────────
local summary_file="$CONFIG_DIR/install-summary.txt"
cat > "$summary_file" <<EOF
archivmail Docker-Installation — $(date '+%d.%m.%Y %H:%M:%S')
Server: $FQDN
=== ZUGANGSDATEN ===
Datenbank (PostgreSQL im Container):
Passwort: $DB_PASSWORD
API-Secret (JWT + AES):
Secret: $API_SECRET
GitHub Webhook:
Secret: $WEBHOOK_SECRET
Web-Logins werden beim ersten Container-Start angelegt.
Passwörter: docker logs archivmail | grep -E 'admin|auditor'
=== DIENSTE ===
Web: https://$FQDN
SMTP: $FQDN:2525
IMAP: $FQDN:1143 (nginx → TLS)
=== GITHUB WEBHOOK EINRICHTEN ===
URL: https://$FQDN/hooks/deploy
Content-Type: application/json
Secret: $WEBHOOK_SECRET
Events: Just the push event (refs/heads/main)
=== DATEIPFADE ===
Konfiguration: $CONFIG_DIR/config.yml
Keyfile: $CONFIG_DIR/keyfile (NIEMALS löschen!)
TLS-Cert: $SSL_DIR/archivmail.crt
Repository: $INSTALL_DIR
Deploy-Log: $LOG_DIR/deploy.log
=== NÜTZLICHE BEFEHLE ===
docker compose -f $INSTALL_DIR/docker-compose.yml logs -f
docker compose -f $INSTALL_DIR/docker-compose.yml ps
bash $INSTALL_DIR/scripts/deploy.sh # Manueller Deploy
EOF
chmod 600 "$summary_file"
log "Zusammenfassung: $summary_file"
# ── Abschluss ─────────────────────────────────────────────────────────────
echo ""
echo " ╔══════════════════════════════════════════════════════════════╗"
echo " ║ Docker-Installation abgeschlossen! ║"
echo " ╚══════════════════════════════════════════════════════════════╝"
echo ""
log "archivmail läuft unter: https://$FQDN"
echo ""
warn "GitHub Webhook jetzt einrichten:"
echo " URL: https://$FQDN/hooks/deploy"
echo " Content-Type: application/json"
echo " Secret: $WEBHOOK_SECRET"
echo " Events: Push (nur main-Branch)"
echo ""
warn "Initial-Passwörter: docker logs archivmail | grep -E 'admin|auditor'"
warn "Zusammenfassung: $CONFIG_DIR/install-summary.txt"
echo ""
}
# ══════════════════════════════════════════════════════════════════════════════
# NATIVE INSTALLATION (systemd, direkter Build)
# ══════════════════════════════════════════════════════════════════════════════
install_native() {
# ── 1. Pakete ─────────────────────────────────────────────────────────────
info "Installiere Systempakete..."
apt-get update -qq
apt-get install -y -qq \
golang-go nodejs npm postgresql nginx \
curl git rsync logrotate openssl
log "Pakete installiert"
# ── 1b. Manticore Search ──────────────────────────────────────────────────
info "Installiere Manticore Search..."
if ! command -v searchd >/dev/null 2>&1 && ! systemctl is-active --quiet manticore 2>/dev/null; then
apt-get install -y -qq wget gnupg2 lsb-release 2>/dev/null || true
MANTICORE_CODENAME=$(lsb_release -cs 2>/dev/null || echo "bookworm")
wget -q -O /tmp/manticore.deb \
"https://repo.manticoresearch.com/repository/manticoresearch_${MANTICORE_CODENAME}/pool/main/m/manticoresearch/manticoresearch_6.3.6_amd64.deb" 2>/dev/null \
|| wget -q -O /tmp/manticore.deb \
"https://github.com/manticoresoftware/manticoresearch/releases/download/6.3.6/manticoresearch_6.3.6.202408011246.4c39781ba-1+${MANTICORE_CODENAME}_amd64.deb" 2>/dev/null \
|| true
if [[ -f /tmp/manticore.deb ]]; then
dpkg -i /tmp/manticore.deb 2>/dev/null || apt-get install -f -y 2>/dev/null || true
rm -f /tmp/manticore.deb
log "Manticore Search installiert"
else
die "Manticore Search konnte nicht installiert werden — siehe: https://manticoresearch.com/install/"
fi
else
log "Manticore Search bereits installiert"
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"
# go im PATH sicherstellen (Debian legt binary nicht immer in /usr/bin)
if ! command -v go >/dev/null 2>&1; then
GO_BIN=$(find /usr/lib/go-*/bin /usr/local/go/bin -name "go" 2>/dev/null | head -1)
[[ -n "$GO_BIN" ]] || die "go binary nicht gefunden nach Installation"
ln -sf "$GO_BIN" /usr/local/bin/go
log "go binary verlinkt: $GO_BIN → /usr/local/bin/go"
fi
log "go $(go version | awk '{print $3}')"
# ── 2. Systembenutzer ─────────────────────────────────────────────────────
info "Lege Systembenutzer '$AM_USER' an..."
id "$AM_USER" &>/dev/null \
&& log "Benutzer '$AM_USER' existiert bereits" \
|| { useradd --system --shell /bin/false --home "$STORE_DIR" --create-home "$AM_USER"; log "Benutzer angelegt"; }
# ── 3. Verzeichnisstruktur ────────────────────────────────────────────────
info "Erstelle Verzeichnisstruktur..."
mkdir -p "$STORE_DIR/store" "$STORE_DIR/astore"
mkdir -p "$CONFIG_DIR" "$LOG_DIR" "$INSTALL_DIR" "$INSTALL_DIR/web" "$SSL_DIR"
chown -R "$AM_USER:$AM_USER" "$STORE_DIR" "$LOG_DIR"
chmod 755 "$STORE_DIR"
chmod 700 "$STORE_DIR/store" "$STORE_DIR/astore"
log "Verzeichnisse erstellt"
# ── 4. Keyfile ────────────────────────────────────────────────────────────
info "Generiere Verschlüsselungs-Keyfile..."
if [ ! -f "$CONFIG_DIR/keyfile" ]; then
openssl rand -base64 32 > "$CONFIG_DIR/keyfile"
chmod 400 "$CONFIG_DIR/keyfile"
chown "$AM_USER:$AM_USER" "$CONFIG_DIR/keyfile"
log "Keyfile generiert: $CONFIG_DIR/keyfile"
else
log "Keyfile existiert bereits wird nicht überschrieben"
fi
# ── 5. TLS-Zertifikat ─────────────────────────────────────────────────────
info "Erstelle selbstsigniertes TLS-Zertifikat..."
if [ ! -f "$SSL_DIR/archivmail.crt" ]; then
SERVER_IP="$(hostname -I | awk '{print $1}')"
openssl req -x509 -nodes -days 3650 -newkey rsa:4096 \
-keyout "$SSL_DIR/archivmail.key" \
-out "$SSL_DIR/archivmail.crt" \
-subj "/CN=${FQDN}/O=archivmail/C=DE" \
-addext "subjectAltName=DNS:${FQDN},DNS:$(hostname -s),IP:${SERVER_IP}" \
2>/dev/null
chmod 640 "$SSL_DIR/archivmail.key"
chmod 644 "$SSL_DIR/archivmail.crt"
chown "root:$AM_USER" "$SSL_DIR/archivmail.key"
log "TLS-Zertifikat erstellt: $SSL_DIR/archivmail.crt (FQDN: $FQDN, IP: $SERVER_IP)"
else
log "TLS-Zertifikat existiert bereits wird nicht überschrieben"
fi
# ── 6. PostgreSQL ─────────────────────────────────────────────────────────
info "Richte PostgreSQL ein..."
systemctl enable postgresql --quiet
systemctl start postgresql
# Falls config.yml schon existiert, DB-Passwort daraus lesen (verhindert Passwort-Mismatch bei Re-Install)
if [ -f "$CONFIG_DIR/config.yml" ]; then
EXISTING_PW=$(grep -A5 '^database:' "$CONFIG_DIR/config.yml" | awk '/password:/{print $2}' | head -1)
[[ -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='archivmail'\" | grep -q 1 \
&& psql -c \"ALTER USER archivmail WITH PASSWORD '$DB_PASSWORD'\" \
|| psql -c \"CREATE USER archivmail WITH PASSWORD '$DB_PASSWORD'\"" postgres
su -c "psql -tc \"SELECT 1 FROM pg_database WHERE datname='archivmail'\" | grep -q 1 || \
psql -c \"CREATE DATABASE archivmail OWNER archivmail\"" postgres
su -c "psql archivmail -c \"GRANT ALL PRIVILEGES ON ALL TABLES IN SCHEMA public TO archivmail;\"" postgres
su -c "psql archivmail -c \"GRANT ALL PRIVILEGES ON ALL SEQUENCES IN SCHEMA public TO archivmail;\"" postgres
su -c "psql archivmail -c \"ALTER DEFAULT PRIVILEGES IN SCHEMA public GRANT ALL ON TABLES TO archivmail;\"" postgres
su -c "psql archivmail -c \"ALTER DEFAULT PRIVILEGES IN SCHEMA public GRANT ALL ON SEQUENCES TO archivmail;\"" postgres
log "PostgreSQL eingerichtet"
# ── 7. Konfiguration ──────────────────────────────────────────────────────
info "Erstelle Konfigurationsdatei..."
if [ ! -f "$CONFIG_DIR/config.yml" ]; then
cat > "$CONFIG_DIR/config.yml" << CONFIG
# archivmail Konfiguration generiert am $(date -u +%Y-%m-%dT%H:%M:%SZ)
server:
api_port: 8080
smtp_port: 2525
database:
host: 127.0.0.1
port: 5432
name: archivmail
user: archivmail
password: ${DB_PASSWORD}
sslmode: disable
storage:
store_path: ${STORE_DIR}/store
astore_path: ${STORE_DIR}/astore
keyfile: ${CONFIG_DIR}/keyfile
api:
bind: ":8080"
secret: ${API_SECRET}
trusted_proxies:
- 127.0.0.1
secure_cookies: true
index:
backend: manticore
manticore_dsn: "manticore@tcp(127.0.0.1:9306)/"
batch_size: 100
audit:
log_path: ${LOG_DIR}/audit.log
retention_days: 0
smtp:
enabled: true
bind: ":2525"
domain: "${FQDN}"
tls_cert: ${SSL_DIR}/archivmail.crt
tls_key: ${SSL_DIR}/archivmail.key
allowed_ips:
- 127.0.0.1
imap_server:
enabled: true
bind: ":993"
tls_cert: ${SSL_DIR}/archivmail.crt
tls_key: ${SSL_DIR}/archivmail.key
CONFIG
chmod 640 "$CONFIG_DIR/config.yml"
chown "root:$AM_USER" "$CONFIG_DIR/config.yml"
log "Konfiguration erstellt: $CONFIG_DIR/config.yml"
else
log "config.yml existiert bereits wird nicht überschrieben"
fi
# ── 8. Nginx ──────────────────────────────────────────────────────────────
info "Konfiguriere nginx (HTTP → HTTPS + TLS)..."
cat > /etc/nginx/sites-available/archivmail << 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}/archivmail.crt;
ssl_certificate_key ${SSL_DIR}/archivmail.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/archivmail.access.log;
error_log /var/log/nginx/archivmail.error.log;
}
NGINX
ln -sf /etc/nginx/sites-available/archivmail /etc/nginx/sites-enabled/archivmail
rm -f /etc/nginx/sites-enabled/default
nginx -t
systemctl enable nginx --quiet
systemctl restart nginx
log "nginx konfiguriert"
# ── 9. logrotate ──────────────────────────────────────────────────────────
cat > /etc/logrotate.d/archivmail << LOGROTATE
${LOG_DIR}/audit.log {
daily
rotate 365
compress
delaycompress
missingok
notifempty
create 640 ${AM_USER} ${AM_USER}
}
LOGROTATE
log "logrotate konfiguriert"
# ── 10. systemd Units ─────────────────────────────────────────────────────
info "Erstelle systemd Units..."
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
CapabilityBoundingSet=CAP_NET_BIND_SERVICE CAP_NET_ADMIN
ExecStart=${INSTALL_DIR}/archivmail --config ${CONFIG_DIR}/config.yml
Restart=on-failure
RestartSec=5
StandardOutput=journal
StandardError=journal
SyslogIdentifier=archivmail
NoNewPrivileges=false
ProtectSystem=strict
ReadWritePaths=${STORE_DIR} ${LOG_DIR}
ReadOnlyPaths=${CONFIG_DIR} ${SSL_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
Environment=NEXT_PUBLIC_API_URL=http://127.0.0.1:8080
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 erstellt und aktiviert"
# ── 11. Erstes Deployment via update.sh ───────────────────────────────────
info "Installiere update.sh und führe erstes Deployment durch..."
_update_url="${REPO_URL/\.git/}/raw/branch/main/update.sh"
curl -fsSL "$_update_url" -o "$INSTALL_DIR/update.sh" 2>/dev/null \
|| { warn "update.sh konnte nicht geladen werden überspringe Deployment"; }
if [ -f "$INSTALL_DIR/update.sh" ]; then
chmod +x "$INSTALL_DIR/update.sh"
log "update.sh installiert"
info "Führe erstes Deployment durch..."
bash "$INSTALL_DIR/update.sh"
for _i in $(seq 1 15); do
_pw=$(journalctl -u archivmail --no-pager -n 100 | grep 'admin' | grep -oP ':\s+\K[0-9a-f]{16,}' | tail -1)
[[ -n "$_pw" ]] && break
sleep 1
done
PW_SUPERADMIN=$(journalctl -u archivmail --no-pager -n 100 | grep 'superadmin' | grep -oP ':\s+\K\S+' | tail -1)
PW_ADMIN=$(journalctl -u archivmail --no-pager -n 100 | grep ' admin ' | grep -v superadmin | grep -oP ':\s+\K\S+' | tail -1)
PW_AUDITOR=$(journalctl -u archivmail --no-pager -n 100 | grep 'auditor' | grep -oP ':\s+\K\S+' | tail -1)
[[ -n "$PW_ADMIN" ]] || PW_ADMIN="(nicht gefunden — journalctl -u archivmail | grep admin)"
[[ -n "$PW_AUDITOR" ]] || PW_AUDITOR="(nicht gefunden — journalctl -u archivmail | grep auditor)"
[[ -n "$PW_SUPERADMIN" ]] || PW_SUPERADMIN="(nicht gefunden — journalctl -u archivmail | grep superadmin)"
else
warn "update.sh fehlt — manuell: bash $INSTALL_DIR/update.sh"
PW_SUPERADMIN="(noch nicht generiert)"; PW_ADMIN="(noch nicht generiert)"; PW_AUDITOR="(noch nicht generiert)"
fi
# ── Zusammenfassung ───────────────────────────────────────────────────────
SUMMARY_FILE="$CONFIG_DIR/install-summary.txt"
cat > "$SUMMARY_FILE" << SUMMARY
archivmail Native-Installation — $(date '+%d.%m.%Y %H:%M:%S')
Server: $FQDN
=== ZUGANGSDATEN ===
Datenbank: archivmail / $DB_PASSWORD
API-Secret: $API_SECRET
Web-Logins (UNBEDINGT ÄNDERN!):
superadmin@archivmail / $PW_SUPERADMIN
admin@archivmail / $PW_ADMIN
auditor@archivmail / $PW_AUDITOR
=== DIENSTE ===
Web (HTTPS): https://$FQDN
IMAP (TLS): $FQDN:993
SMTP: $FQDN:2525
=== DATEIPFADE ===
Konfiguration: $CONFIG_DIR/config.yml
Keyfile: $CONFIG_DIR/keyfile
TLS-Zertifikat: $SSL_DIR/archivmail.crt
Mail-Speicher: $STORE_DIR/
Logs: $LOG_DIR/
Updater: $INSTALL_DIR/update.sh
SUMMARY
chmod 600 "$SUMMARY_FILE"
log "Zusammenfassung: $SUMMARY_FILE"
# ── Abschluss ─────────────────────────────────────────────────────────────
echo ""
echo " ╔══════════════════════════════════════════════════════════╗"
echo " ║ Installation abgeschlossen! ║"
echo " ╚══════════════════════════════════════════════════════════╝"
echo ""
echo " Web (HTTPS): https://$FQDN"
echo " IMAP (TLS): $FQDN:993"
echo " SMTP: $FQDN:2525"
echo ""
echo " ┌─────────────────────────────────────────────────────────┐"
echo " │ ANGELEGTE BENUTZER & PASSWÖRTER │"
echo " ├─────────────────────────────────────────────────────────┤"
printf " │ DB archivmail: %-38s │\n" "$DB_PASSWORD"
echo " ├─────────────────────────────────────────────────────────┤"
echo " │ Web-Logins (UNBEDINGT ÄNDERN!): │"
printf " │ superadmin / %-38s│\n" "$PW_SUPERADMIN"
printf " │ admin / %-38s│\n" "$PW_ADMIN"
printf " │ auditor / %-38s│\n" "$PW_AUDITOR"
echo " └─────────────────────────────────────────────────────────┘"
echo ""
warn "Zusammenfassung mit Passwörtern: $SUMMARY_FILE"
warn "Standardpasswörter unbedingt nach dem ersten Login ändern!"
echo ""
}
# ══════════════════════════════════════════════════════════════════════════════
# MODUS AUSFÜHREN
# ══════════════════════════════════════════════════════════════════════════════
case "$INSTALL_MODE" in
docker) install_docker ;;
native) install_native ;;
esac