#!/bin/bash # archivmail Server-Installer # Unterstützte Systeme: Debian 12 / 13 # Aufruf: bash install.sh # Mit eigenem DB-Passwort: DB_PASSWORD=geheim 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" 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/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}" echo "" echo " ╔══════════════════════════════════════╗" echo " ║ archivmail Installer v1.1 ║" echo " ╚══════════════════════════════════════╝" echo "" info "Hostname: $FQDN" echo "" # ── 1. Pakete ───────────────────────────────────────────────────────────────── info "Installiere Systempakete..." apt-get update -qq apt-get install -y -qq \ golang-go nodejs npm postgresql nginx \ libxapian-dev pkg-config build-essential \ curl git rsync logrotate openssl log "Pakete installiert" # 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" "$STORE_DIR/xapian" 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" "$STORE_DIR/xapian" 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" log "Standard-Benutzer werden beim ersten Daemon-Start angelegt" # ── 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 xapian_path: ${STORE_DIR}/xapian keyfile: ${CONFIG_DIR}/keyfile api: bind: ":8080" secret: ${API_SECRET} trusted_proxies: - 127.0.0.1 secure_cookies: true index: path: ${STORE_DIR}/xapian backend: xapian 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 (HTTP + HTTPS) ──────────────────────────────────────────────────── info "Konfiguriere Nginx (HTTP → HTTPS Redirect + 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 (HTTP→HTTPS, TLS)" # ── 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 Requires=postgresql.service [Service] Type=simple User=${AM_USER} Group=${AM_USER} AmbientCapabilities=CAP_NET_BIND_SERVICE CapabilityBoundingSet=CAP_NET_BIND_SERVICE 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. update.sh installieren und erstes Deployment ────────────────────────── info "Installiere update.sh und führe erstes Deployment durch..." curl -fsSL "${REPO_URL/\.git/}/raw/branch/main/update.sh" -o "$INSTALL_DIR/update.sh" 2>/dev/null \ || { warn "update.sh konnte nicht von Gitea geladen werden – überspringe Deployment"; } if [ -f "$INSTALL_DIR/update.sh" ]; then chmod +x "$INSTALL_DIR/update.sh" log "update.sh installiert: $INSTALL_DIR/update.sh" info "Führe erstes Deployment durch (Build + Start)..." bash "$INSTALL_DIR/update.sh" # Echte Passwörter aus Journal lesen (Backend gibt sie beim ersten Start aus) sleep 2 PW_SUPERADMIN=$(journalctl -u archivmail --no-pager -n 50 | grep -oP '(?<=superadmin\s+: )\S+' | tail -1) PW_ADMIN=$(journalctl -u archivmail --no-pager -n 50 | grep -oP '(?<=admin\s+: )\S+' | tail -1) PW_AUDITOR=$(journalctl -u archivmail --no-pager -n 50 | grep -oP '(?<=auditor\s+: )\S+' | tail -1) [[ -n "$PW_ADMIN" ]] || PW_ADMIN="(nicht gefunden — siehe: journalctl -u archivmail | grep admin)" [[ -n "$PW_AUDITOR" ]] || PW_AUDITOR="(nicht gefunden — siehe: journalctl -u archivmail | grep auditor)" [[ -n "$PW_SUPERADMIN" ]] || PW_SUPERADMIN="(nicht gefunden — siehe: journalctl -u archivmail | grep superadmin)" else warn "update.sh fehlt — manuell ausführen: bash $INSTALL_DIR/update.sh" PW_SUPERADMIN="(noch nicht generiert)" PW_ADMIN="(noch nicht generiert)" PW_AUDITOR="(noch nicht generiert)" fi # ── Zusammenfassung in Datei speichern ──────────────────────────────────────── SUMMARY_FILE="$CONFIG_DIR/install-summary.txt" cat > "$SUMMARY_FILE" << SUMMARY archivmail Installation — $(date '+%d.%m.%Y %H:%M:%S') Server: $FQDN === ANGELEGTE BENUTZER & PASSWÖRTER === Systembenutzer (Linux): Benutzer: $AM_USER Shell: /bin/false (kein Login) Home: $STORE_DIR Datenbank (PostgreSQL): Datenbank: archivmail Benutzer: archivmail Passwort: $DB_PASSWORD Verbindung: 127.0.0.1:5432 API-Secret (JWT + AES-Schlüsselableitung): Secret: $API_SECRET Web-Anwendung (UNBEDINGT ÄNDERN!): Superadmin: superadmin@archivmail / $PW_SUPERADMIN Admin: admin@archivmail / $PW_ADMIN Auditor: 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 TLS-Schlüssel: $SSL_DIR/archivmail.key Mail-Speicher: $STORE_DIR/ Logs: $LOG_DIR/ Updater: $INSTALL_DIR/update.sh === HINWEISE === - Das TLS-Zertifikat ist selbstsigniert (10 Jahre gültig). Im Browser: Ausnahme hinzufügen oder Zertifikat importieren. Für vertrauenswürdiges Zertifikat: Admin → Zertifikat → Let's Encrypt - DB-Passwort und API-Secret stehen in $CONFIG_DIR/config.yml (chmod 640). - Keyfile NIEMALS löschen oder überschreiben — alle Mails werden damit entschlüsselt. - Diese Datei nach dem ersten Login löschen oder sicher aufbewahren! SUMMARY chmod 600 "$SUMMARY_FILE" log "Zusammenfassung gespeichert: $SUMMARY_FILE" # ── Abschlussbericht ────────────────────────────────────────────────────────── echo "" echo " ╔══════════════════════════════════════════════════════════╗" echo " ║ Installation abgeschlossen! ║" echo " ╚══════════════════════════════════════════════════════════╝" echo "" echo " Hostname (FQDN): $FQDN" echo "" echo " Pfade:" echo " Binaries: $INSTALL_DIR/" echo " Mail-Speicher: $STORE_DIR/" echo " Konfiguration: $CONFIG_DIR/config.yml" echo " Keyfile: $CONFIG_DIR/keyfile (chmod 400 – sicher aufbewahren!)" echo " TLS-Zertifikat: $SSL_DIR/archivmail.crt" echo " Logs: $LOG_DIR/" 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 " ├─────────────────────────────────────────────────────────┤" printf " │ API-Secret: %-38s │\n" "${API_SECRET:0:38}" echo " └─────────────────────────────────────────────────────────┘" echo "" echo " Vollständige Zusammenfassung: $SUMMARY_FILE" echo "" echo " Dienste:" echo " Web (HTTPS): https://$FQDN" echo " IMAP (TLS): $FQDN:993" echo " SMTP: $FQDN:2525" echo "" echo " Hinweis: Das TLS-Zertifikat ist selbstsigniert." echo " Für ein vertrauenswürdiges Zertifikat: Admin → Zertifikat → Let's Encrypt" echo "" warn "Zusammenfassung mit Passwörtern: $SUMMARY_FILE (chmod 600)" warn "Standardpasswörter unbedingt nach dem ersten Login ändern!" echo ""