install.sh gibt das frisch generierte Keyfile nach der Erzeugung einmalig aus (beide Installationspfade), mit Aufforderung es sofort in einen Passwort-Safe (Bitwarden) zu verschieben. Backup ist ohne dieses Keyfile wertlos — der Hinweis darf nicht vom "mach ich später" verschluckt werden, da der Wert danach nirgends mehr sichtbar abrufbar ist.
809 lines
36 KiB
Bash
Executable File
809 lines
36 KiB
Bash
Executable File
#!/bin/bash
|
||
# archivmail Installer
|
||
# Unterstütztes System: Debian 13 (trixie)
|
||
#
|
||
# 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; }
|
||
|
||
# PROJ-66: Ein Backup (App-Backup oder PBS/Snapshot) ist ohne dieses Keyfile
|
||
# wertlos — der Store bleibt Chiffretext. Der Inhalt wird EINMALIG bei der
|
||
# Generierung ausgegeben, damit er sofort in einen Passwort-Safe (z.B.
|
||
# Bitwarden Secure Note, siehe features/PROJ-66-backup-strategie.md) verschoben
|
||
# werden kann, statt sich später darauf zu verlassen, dass irgendein Backup
|
||
# das Keyfile schon mitgesichert hat.
|
||
print_keyfile_escrow_warning() {
|
||
local keyfile_path="$1"
|
||
echo
|
||
warn "WICHTIG — Keyfile jetzt sichern (Restore ist ohne dieses Keyfile unmöglich):"
|
||
echo -e "${YELLOW}$(cat "$keyfile_path")${NC}"
|
||
warn "Diesen Wert in einen separaten Passwort-Safe kopieren (z.B. Bitwarden Secure"
|
||
warn "Note, eigene Collection mit eingeschränktem Zugriff) — unabhängig von jedem"
|
||
warn "Server-Backup. Ohne Keyfile sind gesicherte Mail-Dateien nicht entschlüsselbar."
|
||
echo
|
||
}
|
||
|
||
[[ $EUID -eq 0 ]] || die "Bitte als root ausführen: sudo bash install.sh"
|
||
|
||
# ── OS-Prüfung: nur Debian 13 (trixie) ──────────────────────────────────────
|
||
_os_id=$(. /etc/os-release 2>/dev/null && echo "$ID" || echo "")
|
||
_os_ver=$(. /etc/os-release 2>/dev/null && echo "$VERSION_ID" || echo "")
|
||
[[ "$_os_id" == "debian" && "$_os_ver" == "13" ]] \
|
||
|| die "Nur Debian 13 (trixie) wird unterstützt (erkannt: ${_os_id} ${_os_ver})"
|
||
log "Debian 13 erkannt"
|
||
|
||
# ── 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"
|
||
print_keyfile_escrow_warning "$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. Basispakete (kein golang-go — zu alt in Debian-Repos) ────────────
|
||
info "Installiere Systempakete..."
|
||
apt-get update -qq
|
||
apt-get install -y -qq \
|
||
nodejs npm postgresql nginx \
|
||
curl wget git rsync logrotate openssl ca-certificates
|
||
log "Pakete installiert"
|
||
|
||
# ── 1b. Go 1.24 von upstream (apt-Paket ist zu alt) ──────────────────────
|
||
GO_VERSION="1.24.4"
|
||
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
|
||
|
||
# ── 1c. Manticore Search ──────────────────────────────────────────────────
|
||
info "Installiere Manticore Search..."
|
||
if ! command -v searchd >/dev/null 2>&1 && ! systemctl is-active --quiet manticore 2>/dev/null; then
|
||
# Manticore hat kein natives trixie-Paket — bookworm-Paket ist kompatibel
|
||
_mc_arch="$(dpkg --print-architecture)"
|
||
wget -q -O /tmp/manticore.deb \
|
||
"https://repo.manticoresearch.com/repository/manticoresearch_bookworm/pool/main/m/manticoresearch/manticoresearch_6.3.6_${_mc_arch}.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+bookworm_${_mc_arch}.deb" 2>/dev/null \
|
||
|| true
|
||
if [[ -f /tmp/manticore.deb ]]; then
|
||
dpkg -i /tmp/manticore.deb 2>/dev/null || apt-get install -f -y -qq 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"
|
||
|
||
# ── 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"
|
||
print_keyfile_escrow_warning "$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
|
||
batch_mode: true
|
||
|
||
ocr:
|
||
batch_mode: true
|
||
|
||
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
|
||
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}
|
||
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
|
||
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
|