#!/usr/bin/env bash # ============================================================================= # docker-mailserver Stack auf einem NACKTEN VPS (Debian) — ohne Proxmox/LXC # ----------------------------------------------------------------------------- # Auf dem VPS als root ausführen: # bash <(curl -fsSL https://git.kgva.ch/karim/DOCKERMAILSERVER-LXC/raw/branch/main/vps-install.sh) # # Installiert Docker, deployt mailserver + Admin-API + Admin-UI + SnappyMail # + Rspamd, legt erstes Postfach + DKIM an. (Kein pct, keine LXC-Erstellung.) # ============================================================================= set -Eeuo pipefail # --------------------------------------------------------------------------- # Einstellungen (per ENV oder interaktiv) # --------------------------------------------------------------------------- MAIL_FQDN="${MAIL_FQDN:-}" MAIL_DOMAIN="${MAIL_DOMAIN:-}" MAIL_DOMAINS="${MAIL_DOMAINS:-}" FIRST_EMAIL="${FIRST_EMAIL:-}" FIRST_PASSWORD="${FIRST_PASSWORD:-}" BRAND="${BRAND:-}" WEBMAIL_FQDN="${WEBMAIL_FQDN:-}" ADMIN_FQDN="${ADMIN_FQDN:-}" ADMIN_ALLOWED_EMAILS="${ADMIN_ALLOWED_EMAILS:-}" SUPABASE_URL="${SUPABASE_URL:-}" SUPABASE_ANON_KEY="${SUPABASE_ANON_KEY:-}" RSPAMD_PASSWORD="${RSPAMD_PASSWORD:-}" ADMIN_PORT="${ADMIN_PORT:-8080}" WEBMAIL_PORT="${WEBMAIL_PORT:-8888}" RSPAMD_PORT="${RSPAMD_PORT:-11334}" TIMEZONE="${TZ:-Europe/Zurich}" ENABLE_CLAMAV="${ENABLE_CLAMAV:-0}" ENABLE_FAIL2BAN="${ENABLE_FAIL2BAN:-1}" DMS_IMAGE="${DMS_IMAGE:-ghcr.io/docker-mailserver/docker-mailserver:latest}" DEPLOY_DIR="${DEPLOY_DIR:-/opt/dms-stack}" REPO_ARCHIVE="${REPO_ARCHIVE:-https://git.kgva.ch/karim/DOCKERMAILSERVER-LXC/archive/main.tar.gz}" RD=$'\033[01;31m'; GN=$'\033[1;92m'; YW=$'\033[33m'; BL=$'\033[1;34m'; CL=$'\033[m' msg_info() { echo -e " ${YW}•${CL} $1"; } msg_ok() { echo -e " ${GN}✔${CL} $1"; } msg_err() { echo -e " ${RD}✖${CL} $1" >&2; } die() { msg_err "$1"; exit 1; } trap 'msg_err "Fehler in Zeile $LINENO. Abbruch."' ERR [[ $EUID -eq 0 ]] || die "Bitte als root ausführen." command -v apt-get >/dev/null 2>&1 || die "Kein Debian/Ubuntu (apt fehlt)." # --- Grundpakete --- export DEBIAN_FRONTEND=noninteractive msg_info "Installiere Grundpakete ..." apt-get update -qq apt-get install -y -qq ca-certificates curl gnupg openssl jq tar >/dev/null # --- Stack-Ordner: neben dem Skript ODER selbst herunterladen --- SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]:-$0}")" 2>/dev/null && pwd || echo /tmp)" STACK_DIR="${STACK_DIR:-$SCRIPT_DIR/stack}" if [[ ! -f "$STACK_DIR/docker-compose.yml" ]]; then msg_info "Lade Repo von $REPO_ARCHIVE ..." DL="$(mktemp -d)" AUTH=(); [[ -n "${GIT_TOKEN:-}" ]] && AUTH=(-H "Authorization: token ${GIT_TOKEN}") curl -fsSL "${AUTH[@]}" "$REPO_ARCHIVE" -o "$DL/repo.tar.gz" || die "Download fehlgeschlagen." tar -xzf "$DL/repo.tar.gz" -C "$DL" STACK_DIR="$(dirname "$(find "$DL" -maxdepth 4 -type f -name docker-compose.yml -path '*/stack/*' | head -1)")" fi [[ -f "$STACK_DIR/docker-compose.yml" ]] || die "stack/ nicht gefunden." msg_ok "stack/ unter $STACK_DIR" # --------------------------------------------------------------------------- # Dialog # --------------------------------------------------------------------------- ask() { local p="$1" d="${2:-}" v; read -rp "$(echo -e " ${BL}?${CL} ${p}${d:+ ${YW}[$d]${CL}}: ")" v; echo "${v:-$d}"; } asks() { local p="$1" v; read -rsp "$(echo -e " ${BL}?${CL} ${p}: ")" v; echo >&2; echo "$v"; } section() { echo -e "\n${GN}┌──${CL} ${BL}$1${CL}"; } cat <} ${GN}────────────────────────────────────────────────${CL} EOF read -rp "$(echo -e " ${BL}?${CL} Fortfahren? [J/n] ")" go; [[ "${go:-J}" =~ ^[JjYy]?$ ]] || die "Abgebrochen." # --------------------------------------------------------------------------- # System-MTA entfernen (Postfix/Exim belegen Port 25) + Docker installieren # --------------------------------------------------------------------------- msg_info "Entferne System-MTA (Postfix/Exim) ..." systemctl disable --now postfix exim4 >/dev/null 2>&1 || true apt-get purge -y -qq postfix "exim4*" >/dev/null 2>&1 || true if ! command -v docker >/dev/null 2>&1; then msg_info "Installiere Docker ..." curl -fsSL https://get.docker.com | sh >/dev/null 2>&1 systemctl enable --now docker >/dev/null 2>&1 fi msg_ok "Docker bereit." # --------------------------------------------------------------------------- # Stack vorbereiten # --------------------------------------------------------------------------- msg_info "Bereite Stack vor ..." mkdir -p "$DEPLOY_DIR" cp -r "$STACK_DIR"/. "$DEPLOY_DIR"/ DMS_TAG="${DMS_IMAGE##*:}" sed -i "s|^TZ=.*|TZ=${TIMEZONE}|" "$DEPLOY_DIR/mailserver.env" sed -i "s|^ENABLE_CLAMAV=.*|ENABLE_CLAMAV=${ENABLE_CLAMAV}|" "$DEPLOY_DIR/mailserver.env" sed -i "s|^ENABLE_FAIL2BAN=.*|ENABLE_FAIL2BAN=${ENABLE_FAIL2BAN}|" "$DEPLOY_DIR/mailserver.env" cat > "$DEPLOY_DIR/.env" < "$DEPLOY_DIR/docker-data/dms/config/rspamd/override.d/worker-controller.inc" </dev/null 2>&1 fi PW_HASH="$(printf '%s' "$FIRST_PASSWORD" | openssl passwd -6 -stdin)" grep -q "^${FIRST_EMAIL}|" "$DEPLOY_DIR/docker-data/dms/config/postfix-accounts.cf" 2>/dev/null || \ printf '%s\n' "${FIRST_EMAIL}|{SHA512-CRYPT}${PW_HASH}" >> "$DEPLOY_DIR/docker-data/dms/config/postfix-accounts.cf" # --------------------------------------------------------------------------- # Stack starten + DKIM + SnappyMail # --------------------------------------------------------------------------- msg_info "Baue & starte Stack (kann ein paar Minuten dauern) ..." ( cd "$DEPLOY_DIR" && docker compose up -d --build ) msg_info "Warte auf Mailserver ..." for i in $(seq 1 60); do docker exec mailserver ss -lnt 2>/dev/null | grep -q ":25 " && break sleep 3 done msg_info "Erzeuge DKIM pro Domain ..." for d in $MAIL_DOMAINS; do docker exec mailserver setup config dkim keysize 2048 domain "$d" >/dev/null 2>&1 \ && msg_ok "DKIM $d" || msg_err "DKIM $d fehlgeschlagen" done DKIM_TXT="$(for f in "$DEPLOY_DIR"/docker-data/dms/config/rspamd/dkim/*.dns.txt; do [ -f "$f" ] && echo "; --- $(basename "$f" .dns.txt) ---" && cat "$f" && echo; done 2>/dev/null || true)" # SnappyMail: Wildcard-Domain -> mailserver, Shibui-Theme SM_DEF="$DEPLOY_DIR/docker-data/snappymail/_data_/_default_" for i in $(seq 1 30); do [ -f "$SM_DEF/domains/default.json" ] && break; sleep 2; done if [ -f "$SM_DEF/domains/default.json" ]; then jq '.IMAP.host="mailserver"|.IMAP.port=143|.IMAP.type=2|.IMAP.ssl.allow_self_signed=true |.SMTP.host="mailserver"|.SMTP.port=587|.SMTP.type=2|.SMTP.useAuth=true|.SMTP.ssl.allow_self_signed=true |.Sieve.host="mailserver"' "$SM_DEF/domains/default.json" > "$SM_DEF/domains/default.json.tmp" \ && mv "$SM_DEF/domains/default.json.tmp" "$SM_DEF/domains/default.json" sed -i 's/^theme = .*/theme = "Shibui@custom"/' "$SM_DEF/configs/application.ini" 2>/dev/null || true ( cd "$DEPLOY_DIR" && docker compose restart snappymail >/dev/null 2>&1 ) || true fi PUB_IP="$(curl -fsSL https://ipv4.icanhazip.com 2>/dev/null || hostname -I | awk '{print $1}')" # --------------------------------------------------------------------------- # Abschluss # --------------------------------------------------------------------------- cat <}:${WEBMAIL_PORT} Admin-UI ........... http://${PUB_IP:-}:${ADMIN_PORT} (Supabase-Login) Rspamd-UI .......... http://${PUB_IP:-}:${RSPAMD_PORT} ${RD}Rspamd-Passwort:${CL} ${RSPAMD_PASSWORD} Verwaltung ......... cd ${DEPLOY_DIR} · docker compose ps ${YW}── DNS: Mailhost (einmalig) ──${CL} A ${MAIL_FQDN}. IN A ${PUB_IP:-} PTR ${PUB_IP:-} -> ${MAIL_FQDN} (in der Hetzner-Console: Server → rDNS) EOF for d in $MAIL_DOMAINS; do cat < docker-data/certs/cert.pem|key.pem, dann 'docker compose restart mailserver' (ersetzt self-signed). [ ] Backups (Hetzner-Backups oder docker-data/ sichern). Test: mail-tester.com · Details: README.md EOF msg_ok "Fertig."