diff --git a/vps-install.sh b/vps-install.sh new file mode 100644 index 0000000..0fb0208 --- /dev/null +++ b/vps-install.sh @@ -0,0 +1,267 @@ +#!/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."