This commit is contained in:
Andreas Knuth 2026-02-22 13:00:24 -06:00
parent ee19b5b659
commit 8808d81113
3 changed files with 232 additions and 213 deletions

View File

@ -1,23 +1,26 @@
services: services:
mailserver: mailserver:
# image: docker.io/mailserver/docker-mailserver:latest # AUSKOMMENTIERT
build: build:
context: . context: .
dockerfile: Dockerfile dockerfile: Dockerfile
image: dms-custom:latest image: dms-custom:latest
container_name: mailserver container_name: mailserver
hostname: mail.email-srvr.com
domainname: email-srvr.com # Node-spezifischer Hostname - A-Record zeigt auf DIESEN Server.
# email-srvr.com selbst zeigt auf einen anderen Server und wird hier NICHT verwendet.
hostname: node1.email-srvr.com
ports: ports:
- "25:25" # SMTP (parallel zu MailCow auf Port 25) - "25:25"
- "587:587" # SMTP Submission - "587:587"
- "465:465" # SMTP SSL - "465:465"
- "143:143" # IMAP - "143:143"
- "993:993" # IMAP SSL - "993:993"
- "110:110" # POP3 - "110:110"
- "995:995" # POP3 SSL - "995:995"
- "127.0.0.1:11334:11334" # Bindet nur an Localhost! - "127.0.0.1:11334:11334"
volumes: volumes:
- ./docker-data/dms/mail-data/:/var/mail/ - ./docker-data/dms/mail-data/:/var/mail/
- ./docker-data/dms/mail-state/:/var/mail-state/ - ./docker-data/dms/mail-state/:/var/mail-state/
@ -27,67 +30,88 @@ services:
- /etc/localtime:/etc/localtime:ro - /etc/localtime:/etc/localtime:ro
- ./sync_dynamodb_to_sieve.py:/scripts/sync.py:ro - ./sync_dynamodb_to_sieve.py:/scripts/sync.py:ro
- ./sieve-cron:/etc/cron.d/sieve-sync:ro - ./sieve-cron:/etc/cron.d/sieve-sync:ro
- /var/lib/docker/volumes/caddy_data/_data/caddy/certificates/acme-v02.api.letsencrypt.org-directory/mail.email-srvr.com:/etc/mail/certs:ro
# -------------------------------------------------------
# Caddy Zertifikate: gesamtes Cert-Verzeichnis mounten.
#
# Caddy legt Wildcard-Certs so ab:
# *.andreasknuth.de/
# *.andreasknuth.de.crt
# *.andreasknuth.de.key
# node1.email-srvr.com/
# node1.email-srvr.com.crt
# node1.email-srvr.com.key
#
# setup-dms-tls.sh referenziert per:
# /etc/mail/certs/*.domain/*.domain.crt|.key
# -------------------------------------------------------
- /var/lib/docker/volumes/caddy_data/_data/caddy/certificates/acme-v02.api.letsencrypt.org-directory:/etc/mail/certs:ro
# -------------------------------------------------------
# Dovecot SNI Konfiguration (generiert von setup-dms-tls.sh)
# DMS lädt /tmp/docker-mailserver/dovecot-sni.cf automatisch.
# -------------------------------------------------------
- ./docker-data/dms/config/dovecot-sni.cf:/tmp/docker-mailserver/dovecot-sni.cf:ro
environment: environment:
# Wichtig: Rspamd und andere Services deaktivieren für ersten Test # -------------------------------------------------------
# SSL Default-Cert: node1.email-srvr.com
# Das ist das Fallback-Cert wenn kein SNI-Match gefunden wird
# (z.B. bei direktem IP-Connect ohne Hostname).
# Kundendomain-SNI wird über postfix-main.cf + dovecot-sni.cf gesteuert.
# -------------------------------------------------------
- SSL_TYPE=manual - SSL_TYPE=manual
# Diese Pfade beziehen sich auf das INNERE des Containers (wo wir hin mounten) - SSL_CERT_PATH=/etc/mail/certs/node1.email-srvr.com/node1.email-srvr.com.crt
- SSL_CERT_PATH=/etc/mail/certs/mail.email-srvr.com.crt - SSL_KEY_PATH=/etc/mail/certs/node1.email-srvr.com/node1.email-srvr.com.key
- SSL_KEY_PATH=/etc/mail/certs/mail.email-srvr.com.key
# SPAM / Rspamd
- ENABLE_OPENDKIM=1 - ENABLE_OPENDKIM=1
- ENABLE_OPENDMARC=0 - ENABLE_OPENDMARC=0
- ENABLE_POLICYD_SPF=0 - ENABLE_POLICYD_SPF=0
# #### SPAM SECTION #####
# SPAM Rspamd aktivieren
- ENABLE_RSPAMD=1 - ENABLE_RSPAMD=1
# Greylisting AUS (vermeidet Verzögerungen)
- RSPAMD_GREYLISTING=0 - RSPAMD_GREYLISTING=0
# Eigene Mails NICHT scannen (vermeidet Probleme beim Senden)
- RSPAMD_CHECK_AUTHENTICATED=0 - RSPAMD_CHECK_AUTHENTICATED=0
# Hostname Check AN (filtert Botnets, sehr sicher)
- RSPAMD_HFILTER=1 - RSPAMD_HFILTER=1
# Spam sortieren statt löschen (Sieve Magic)
- MOVE_SPAM_TO_JUNK=1 - MOVE_SPAM_TO_JUNK=1
# Alte Dienste aus
- ENABLE_AMAVIS=0 - ENABLE_AMAVIS=0
- ENABLE_SPAMASSASSIN=0 - ENABLE_SPAMASSASSIN=0
- ENABLE_POSTGREY=0 - ENABLE_POSTGREY=0
# 2. ClamAV deaktivieren (Anti-Virus)
- ENABLE_CLAMAV=0 - ENABLE_CLAMAV=0
# HACKERSCHUTZ (Pflicht!)
# Sicherheit
- ENABLE_FAIL2BAN=1 - ENABLE_FAIL2BAN=1
# DNS Resolver (verhindert Spamhaus-Probleme)
- ENABLE_UNBOUND=1 - ENABLE_UNBOUND=1
# #### END SPAM SECTION #####
# END SPAM SECTION # Sonstige
- ENABLE_MANAGESIEVE=0 - ENABLE_MANAGESIEVE=0
- ENABLE_POP3=1 - ENABLE_POP3=1
- RSPAMD_LEARN=1 - RSPAMD_LEARN=1
- ONE_DIR=1 - ONE_DIR=1
- ENABLE_UPDATE_CHECK=0 - ENABLE_UPDATE_CHECK=0
- PERMIT_DOCKER=network - PERMIT_DOCKER=network
# - PERMIT_DOCKER=empty - SPOOF_PROTECTION=0
- SSL_TYPE=manual - ENABLE_SRS=0
- SSL_CERT_PATH=/tmp/docker-mailserver/ssl/cert.pem - LOG_LEVEL=info
- SSL_KEY_PATH=/tmp/docker-mailserver/ssl/key.pem
# Amazon SES SMTP Relay # Amazon SES Relay
- RELAY_HOST=email-smtp.us-east-2.amazonaws.com - RELAY_HOST=email-smtp.us-east-2.amazonaws.com
- RELAY_PORT=587 - RELAY_PORT=587
- RELAY_USER=${SES_SMTP_USER} - RELAY_USER=${SES_SMTP_USER}
- RELAY_PASSWORD=${SES_SMTP_PASSWORD} - RELAY_PASSWORD=${SES_SMTP_PASSWORD}
# Content Filter AWS Credentials
# AWS Credentials
- AWS_ACCESS_KEY_ID=${AWS_ACCESS_KEY_ID} - AWS_ACCESS_KEY_ID=${AWS_ACCESS_KEY_ID}
- AWS_SECRET_ACCESS_KEY=${AWS_SECRET_ACCESS_KEY} - AWS_SECRET_ACCESS_KEY=${AWS_SECRET_ACCESS_KEY}
- AWS_REGION=us-east-2 - AWS_REGION=us-east-2
# Weitere Einstellungen
- POSTFIX_OVERRIDE_HOSTNAME=email-srvr.com # Postfix
# POSTFIX_OVERRIDE_HOSTNAME: Was Postfix im EHLO/HELO Banner sendet.
# node1.email-srvr.com passt zum TLS-Cert und ist der echte Hostname.
- POSTFIX_OVERRIDE_HOSTNAME=node1.email-srvr.com
- POSTFIX_MYNETWORKS=172.16.0.0/12 172.17.0.0/12 172.18.0.0/12 [::1]/128 [fe80::]/64 - POSTFIX_MYNETWORKS=172.16.0.0/12 172.17.0.0/12 172.18.0.0/12 [::1]/128 [fe80::]/64
- POSTFIX_MAILBOX_SIZE_LIMIT=0 - POSTFIX_MAILBOX_SIZE_LIMIT=0
- POSTFIX_MESSAGE_SIZE_LIMIT=0 - POSTFIX_MESSAGE_SIZE_LIMIT=0
- SPOOF_PROTECTION=0
- ENABLE_SRS=0
# Debug-Einstellungen
- LOG_LEVEL=info
cap_add: cap_add:
- NET_ADMIN - NET_ADMIN
- SYS_PTRACE - SYS_PTRACE
@ -95,7 +119,6 @@ services:
networks: networks:
mail_network: mail_network:
aliases: aliases:
- mail.email-srvr.com
- mailserver - mailserver
roundcube: roundcube:
@ -111,16 +134,14 @@ services:
- ROUNDCUBEMAIL_DB_NAME=roundcube - ROUNDCUBEMAIL_DB_NAME=roundcube
- ROUNDCUBEMAIL_DB_USER=roundcube - ROUNDCUBEMAIL_DB_USER=roundcube
- ROUNDCUBEMAIL_DB_PASSWORD=${ROUNDCUBE_DB_PASSWORD} - ROUNDCUBEMAIL_DB_PASSWORD=${ROUNDCUBE_DB_PASSWORD}
# Einfache Konfiguration ohne SSL-Probleme (für ersten Test) # Roundcube verbindet intern über den Docker-Alias
- ROUNDCUBEMAIL_DEFAULT_HOST=ssl://mail.email-srvr.com - ROUNDCUBEMAIL_DEFAULT_HOST=ssl://mailserver
- ROUNDCUBEMAIL_DEFAULT_PORT=993 - ROUNDCUBEMAIL_DEFAULT_PORT=993
- ROUNDCUBEMAIL_SMTP_SERVER=tls://mail.email-srvr.com - ROUNDCUBEMAIL_SMTP_SERVER=tls://mailserver
- ROUNDCUBEMAIL_SMTP_PORT=587 - ROUNDCUBEMAIL_SMTP_PORT=587
#- ROUNDCUBEMAIL_PLUGINS=password,email_config,managesieve
- ROUNDCUBEMAIL_PLUGINS=password,email_config - ROUNDCUBEMAIL_PLUGINS=password,email_config
# In docker-compose.yml bei roundcube hinzufügen:
ports: ports:
- "8888:80" # Host:Container - "8888:80"
volumes: volumes:
- ./docker-data/roundcube/config:/var/roundcube/config - ./docker-data/roundcube/config:/var/roundcube/config
- ./docker-data/roundcube/plugins/email_config:/var/www/html/plugins/email_config:ro - ./docker-data/roundcube/plugins/email_config:/var/www/html/plugins/email_config:ro

View File

@ -1,37 +1,40 @@
#!/bin/bash #!/bin/bash
# setup-dms-tls.sh # setup-dms-tls.sh
# Generiert Dovecot und Postfix SNI-Konfigurationen für Multi-Domain TLS. # Gehört ins Root-Verzeichnis des DMS (neben docker-compose.yml).
# Liest die vorhandenen Domains aus den DMS Accounts und erstellt:
# - docker-data/dms/config/dovecot-sni.cf (Dovecot SNI pro Domain)
# - docker-data/dms/config/postfix-main.cf (Postfix SNI Map + TLS Chain)
# #
# Voraussetzung: # Generiert Dovecot- und Postfix-SNI-Konfigurationen für Multi-Domain TLS.
# - Caddy hat Wildcard-Certs gezogen (z.B. *.andreasknuth.de) # Liest Domains aus dem laufenden DMS und erstellt:
# - Cert-Verzeichnis ist gemountet unter /etc/mail/certs im Container # - docker-data/dms/config/dovecot-sni.cf
# - Konvention Cert-Pfad: /etc/mail/certs/DOMAIN_NAME/*.DOMAIN_NAME.crt|.key # - docker-data/dms/config/postfix-main.cf
#
# Cert-Konvention (Caddy Wildcard):
# /etc/mail/certs/*.domain.tld/*.domain.tld.crt
# /etc/mail/certs/*.domain.tld/*.domain.tld.key
# #
# Usage: # Usage:
# DMS_CONTAINER=mailserver ./setup-dms-tls.sh # ./setup-dms-tls.sh
# DMS_CONTAINER=mailserver DEFAULT_DOMAIN=email-srvr.com ./setup-dms-tls.sh # DMS_CONTAINER=mailserver NODE_HOSTNAME=node1.email-srvr.com ./setup-dms-tls.sh
set -e set -e
DMS_CONTAINER=${DMS_CONTAINER:-"mailserver"} DMS_CONTAINER=${DMS_CONTAINER:-"mailserver"}
CONFIG_DIR=${CONFIG_DIR:-"./docker-data/dms/config"} SCRIPT_DIR="$(cd "$(dirname "$0")" && pwd)"
CONFIG_DIR="$SCRIPT_DIR/docker-data/dms/config"
CERTS_BASE_PATH=${CERTS_BASE_PATH:-"/etc/mail/certs"} CERTS_BASE_PATH=${CERTS_BASE_PATH:-"/etc/mail/certs"}
# Die Default-Domain für DMS hostname/domainname (bleibt email-srvr.com) # Node-Hostname: Fallback-Cert für DMS (kein Wildcard, direktes Cert)
DEFAULT_DOMAIN=${DEFAULT_DOMAIN:-"email-srvr.com"} # Muss mit dem 'hostname' in docker-compose.yml übereinstimmen.
NODE_HOSTNAME=${NODE_HOSTNAME:-"node1.email-srvr.com"}
echo "============================================================" echo "============================================================"
echo " 🔐 DMS TLS SNI Setup (Multi-Domain)" echo " 🔐 DMS TLS SNI Setup (Multi-Domain)"
echo " Container: $DMS_CONTAINER" echo " DMS Container: $DMS_CONTAINER"
echo " Config Dir: $CONFIG_DIR" echo " Config Dir: $CONFIG_DIR"
echo " Certs Base: $CERTS_BASE_PATH" echo " Certs Base: $CERTS_BASE_PATH"
echo " Default Domain: $DEFAULT_DOMAIN" echo " Node Hostname: $NODE_HOSTNAME"
echo "============================================================" echo "============================================================"
# --- Alle Domains aus DMS Accounts lesen --- # --- Domains aus DMS lesen ---
echo "" echo ""
echo "📋 Lese Domains aus DMS..." echo "📋 Lese Domains aus DMS..."
DOMAINS=$(docker exec "$DMS_CONTAINER" setup email list 2>/dev/null \ DOMAINS=$(docker exec "$DMS_CONTAINER" setup email list 2>/dev/null \
@ -40,74 +43,80 @@ DOMAINS=$(docker exec "$DMS_CONTAINER" setup email list 2>/dev/null \
if [ -z "$DOMAINS" ]; then if [ -z "$DOMAINS" ]; then
echo "❌ Keine Accounts im DMS gefunden!" echo "❌ Keine Accounts im DMS gefunden!"
echo " Bitte zuerst Accounts anlegen: ./manage_mail_user.sh add user@domain.com PW" echo " Bitte zuerst anlegen: ./manage_mail_user.sh add user@domain.com PW"
exit 1 exit 1
fi fi
echo " Gefundene Domains:" echo " Gefundene Domains:"
for d in $DOMAINS; do echo " - $d"; done for d in $DOMAINS; do echo " - $d"; done
# --- Cert-Verfügbarkeit prüfen --- # --- Cert-Verfügbarkeit im Container prüfen ---
echo "" echo ""
echo "🔍 Prüfe Zertifikat-Verfügbarkeit (im Container)..." echo "🔍 Prüfe Zertifikat-Verfügbarkeit..."
DOMAINS_WITH_CERTS="" DOMAINS_OK=""
DOMAINS_WITHOUT_CERTS="" DOMAINS_MISSING=""
for domain in $DOMAINS; do for domain in $DOMAINS; do
# Caddy speichert Wildcard-Certs als: *.domain.tld/
# Pfad im Container (über den Volume-Mount): /etc/mail/certs/*.domain.tld/
CERT_PATH="$CERTS_BASE_PATH/*.$domain/*.$domain.crt" CERT_PATH="$CERTS_BASE_PATH/*.$domain/*.$domain.crt"
KEY_PATH="$CERTS_BASE_PATH/*.$domain/*.$domain.key" KEY_PATH="$CERTS_BASE_PATH/*.$domain/*.$domain.key"
# Prüfe ob die Datei im Container existiert
if docker exec "$DMS_CONTAINER" test -f "$CERT_PATH" 2>/dev/null; then if docker exec "$DMS_CONTAINER" test -f "$CERT_PATH" 2>/dev/null; then
echo "$domain → Cert gefunden" echo "$domain → Cert vorhanden"
DOMAINS_WITH_CERTS="$DOMAINS_WITH_CERTS $domain" DOMAINS_OK="$DOMAINS_OK $domain"
else else
echo " ⚠️ $domain → KEIN Cert unter $CERT_PATH" echo " ⚠️ $domain → KEIN Cert unter $CERT_PATH"
echo " Caddy-Block '*.${domain}' eintragen und Caddy neu starten!" echo " → update-caddy-certs.sh ausführen + caddy reload!"
DOMAINS_WITHOUT_CERTS="$DOMAINS_WITHOUT_CERTS $domain" DOMAINS_MISSING="$DOMAINS_MISSING $domain"
fi fi
done done
if [ -n "$DOMAINS_WITHOUT_CERTS" ]; then # Node-Hostname Cert prüfen (direktes Cert, kein Wildcard)
echo "" NODE_CERT_PATH="$CERTS_BASE_PATH/$NODE_HOSTNAME/$NODE_HOSTNAME.crt"
echo "⚠️ WARNUNG: Fehlende Certs für:$DOMAINS_WITHOUT_CERTS" NODE_KEY_PATH="$CERTS_BASE_PATH/$NODE_HOSTNAME/$NODE_HOSTNAME.key"
echo " Diese Domains werden NICHT in die SNI-Configs eingetragen." if docker exec "$DMS_CONTAINER" test -f "$NODE_CERT_PATH" 2>/dev/null; then
echo " Bitte Certs erzeugen und Script erneut ausführen." echo "$NODE_HOSTNAME → Cert vorhanden (Node Default)"
echo "" NODE_CERT_OK=true
else
echo " ⚠️ $NODE_HOSTNAME → KEIN Cert! Caddy-Block im Caddyfile prüfen."
NODE_CERT_OK=false
fi fi
if [ -z "$DOMAINS_WITH_CERTS" ]; then if [ -n "$DOMAINS_MISSING" ]; then
echo "❌ Kein einziges Zertifikat gefunden! Abbruch." echo ""
echo " ⚠️ Fehlende Certs:$DOMAINS_MISSING"
echo " Diese Domains werden NICHT in SNI-Config eingetragen."
fi
if [ -z "$DOMAINS_OK" ]; then
echo "❌ Kein einziges Kundendomain-Cert gefunden!"
echo " Bitte zuerst update-caddy-certs.sh ausführen + caddy reload abwarten."
exit 1 exit 1
fi fi
# ================================================================ # ================================================================
# DOVECOT SNI Konfiguration generieren # DOVECOT SNI Konfiguration
# ================================================================ # ================================================================
DOVECOT_CFG="$CONFIG_DIR/dovecot-sni.cf" DOVECOT_CFG="$CONFIG_DIR/dovecot-sni.cf"
echo "" echo ""
echo "📝 Generiere Dovecot SNI Konfiguration: $DOVECOT_CFG" echo "📝 Generiere: $DOVECOT_CFG"
cat > "$DOVECOT_CFG" << 'HEADER' cat > "$DOVECOT_CFG" << 'HEADER'
# dovecot-sni.cf - Automatisch generiert von setup-dms-tls.sh # dovecot-sni.cf - Automatisch generiert von setup-dms-tls.sh
# SNI-basierte TLS-Konfiguration für mehrere Domains. # SNI-basierte Zertifikat-Auswahl für Dovecot (IMAP/POP3).
# Dovecot wählt das Zertifikat anhand des SNI-Hostnamens des Clients. # Dovecot liest dieses File über den Volume-Mount in /tmp/docker-mailserver/
# Dieses File wird via Volume-Mount in den Container eingebunden. # und wendet es automatisch an.
# #
# Gemounteter Pfad: /tmp/docker-mailserver/dovecot-sni.cf # Volume-Mount in docker-compose.yml:
# In DMS docker-compose.yml volumes Sektion:
# - ./docker-data/dms/config/dovecot-sni.cf:/tmp/docker-mailserver/dovecot-sni.cf:ro # - ./docker-data/dms/config/dovecot-sni.cf:/tmp/docker-mailserver/dovecot-sni.cf:ro
HEADER HEADER
for domain in $DOMAINS_WITH_CERTS; do for domain in $DOMAINS_OK; do
CERT_PATH="$CERTS_BASE_PATH/*.$domain/*.$domain.crt" CERT_PATH="$CERTS_BASE_PATH/*.$domain/*.$domain.crt"
KEY_PATH="$CERTS_BASE_PATH/*.$domain/*.$domain.key" KEY_PATH="$CERTS_BASE_PATH/*.$domain/*.$domain.key"
cat >> "$DOVECOT_CFG" << EOF cat >> "$DOVECOT_CFG" << EOF
# Domain: $domain # $domain
local_name mail.$domain { local_name mail.$domain {
ssl_cert = <$CERT_PATH ssl_cert = <$CERT_PATH
ssl_key = <$KEY_PATH ssl_key = <$KEY_PATH
@ -128,75 +137,69 @@ local_name pop.$domain {
EOF EOF
done done
echo "$DOVECOT_CFG erstellt ($(echo $DOMAINS_WITH_CERTS | wc -w) Domains)" echo "Dovecot SNI: $(echo $DOMAINS_OK | wc -w) Domain(s)"
# ================================================================ # ================================================================
# POSTFIX SNI Konfiguration generieren # POSTFIX SNI Konfiguration
# ================================================================ # ================================================================
POSTFIX_CFG="$CONFIG_DIR/postfix-main.cf" POSTFIX_CFG="$CONFIG_DIR/postfix-main.cf"
echo "" echo ""
echo "📝 Generiere Postfix SNI Konfiguration: $POSTFIX_CFG" echo "📝 Generiere: $POSTFIX_CFG"
# Prüfe ob postfix-main.cf schon existiert und sichere sie # Backup falls vorhanden
if [ -f "$POSTFIX_CFG" ]; then if [ -f "$POSTFIX_CFG" ]; then
cp "$POSTFIX_CFG" "${POSTFIX_CFG}.bak.$(date +%Y%m%d%H%M%S)" cp "$POSTFIX_CFG" "${POSTFIX_CFG}.bak.$(date +%Y%m%d%H%M%S)"
echo " Backup erstellt: ${POSTFIX_CFG}.bak.*" echo " Backup: ${POSTFIX_CFG}.bak.*"
fi fi
# TLS Chain Files für Postfix aufbauen # smtpd_tls_chain_files aufbauen: Key + Cert Paar pro Domain
# Postfix unterstützt smtpd_tls_chain_files mit mehreren Key/Cert Paaren # Postfix wählt automatisch per SNI das passende Paar
CHAIN_FILES="" CHAIN_LINES=""
for domain in $DOMAINS_WITH_CERTS; do for domain in $DOMAINS_OK; do
KEY_PATH="$CERTS_BASE_PATH/*.$domain/*.$domain.key" KEY_PATH="$CERTS_BASE_PATH/*.$domain/*.$domain.key"
CERT_PATH="$CERTS_BASE_PATH/*.$domain/*.$domain.crt" CERT_PATH="$CERTS_BASE_PATH/*.$domain/*.$domain.crt"
if [ -z "$CHAIN_FILES" ]; then if [ -z "$CHAIN_LINES" ]; then
CHAIN_FILES=" $KEY_PATH, $CERT_PATH" CHAIN_LINES=" $KEY_PATH, $CERT_PATH"
else else
CHAIN_FILES="$CHAIN_FILES,\n $KEY_PATH, $CERT_PATH" CHAIN_LINES="$CHAIN_LINES,\n $KEY_PATH, $CERT_PATH"
fi fi
done done
cat > "$POSTFIX_CFG" << POSTFIX_CONF cat > "$POSTFIX_CFG" << POSTFIX_EOF
# postfix-main.cf - Automatisch generiert von setup-dms-tls.sh # postfix-main.cf - Automatisch generiert von setup-dms-tls.sh
# Postfix SNI-Konfiguration für mehrere Domains. # Postfix SNI-Konfiguration: pro Kundendomain ein Key/Cert-Paar.
# DMS lädt dieses File automatisch beim Start via /tmp/docker-mailserver/ # Postfix wählt beim TLS-Handshake das passende Paar per SNI.
# DMS lädt dieses File automatisch beim Start.
# ------------------------------------------------------------------ # TLS Chain: Key + Cert Paare (Postfix >= 3.4)
# TLS Chain Files (Key + Cert pro Domain)
# Postfix wählt das passende Paar automatisch per SNI
# ------------------------------------------------------------------
smtpd_tls_chain_files = smtpd_tls_chain_files =
$(printf '%b' "$CHAIN_FILES") $(printf '%b' "$CHAIN_LINES")
POSTFIX_CONF POSTFIX_EOF
echo "$POSTFIX_CFG erstellt" echo "Postfix SNI: $(echo $DOMAINS_OK | wc -w) Domain(s)"
# ================================================================ # ================================================================
# Hinweise für docker-compose.yml # Zusammenfassung
# ================================================================ # ================================================================
echo "" echo ""
echo "============================================================" echo "============================================================"
echo "✅ Konfigurationen generiert."
echo ""
echo "📋 Nächste Schritte:" echo "📋 Nächste Schritte:"
echo "" echo ""
echo "1. Volume-Mounts in DMS docker-compose.yml hinzufügen:" echo "1. DMS neu starten:"
echo ""
echo " volumes:"
echo " # Bestehend (Caddy Certs - gesamtes Verzeichnis):"
echo " - /var/lib/docker/volumes/caddy_data/_data/caddy/certificates/"
echo " acme-v02.api.letsencrypt.org-directory:/etc/mail/certs:ro"
echo ""
echo " # NEU - Dovecot SNI:"
echo " - ./docker-data/dms/config/dovecot-sni.cf:/tmp/docker-mailserver/dovecot-sni.cf:ro"
echo ""
echo " # Postfix-main.cf wird von DMS automatisch geladen wenn sie liegt unter:"
echo " - ./docker-data/dms/config/postfix-main.cf:/tmp/docker-mailserver/postfix-main.cf:ro"
echo ""
echo "2. DMS neu starten:"
echo " docker compose restart mailserver" echo " docker compose restart mailserver"
echo "" echo ""
echo "3. TLS testen:" echo "2. TLS testen (SNI):"
for domain in $DOMAINS_WITH_CERTS; do for domain in $DOMAINS_OK; do
echo " openssl s_client -connect mail.$domain:993 -servername mail.$domain" echo " openssl s_client -connect mail.$domain:993 -servername mail.$domain 2>/dev/null | grep 'subject\|issuer'"
done done
echo ""
echo "3. Bei neuen Domains:"
echo " a) Accounts anlegen: ./manage_mail_user.sh add user@newdomain.com PW"
echo " b) Im Caddy-Dir: ./update-caddy-certs.sh && docker exec caddy caddy reload ..."
echo " c) Warten bis Cert generiert (~30s)"
echo " d) Dieses Script erneut ausführen"
echo " e) docker compose restart mailserver"
echo "============================================================" echo "============================================================"

View File

@ -1,28 +1,35 @@
#!/bin/bash #!/bin/bash
# update-caddy-certs.sh # update-caddy-certs.sh
# Liest alle Domains aus dem DMS und generiert die notwendigen # Gehört ins Caddy-Verzeichnis (neben dem Caddyfile).
# Caddyfile-Blöcke für Wildcard-Zertifikate.
# #
# Die generierten Blöcke werden NICHT automatisch in das Caddyfile geschrieben, # Liest alle Domains aus dem DMS und generiert die Wildcard-Cert-Blöcke
# sondern in eine separate Datei (caddy_mail_certs.conf) ausgegeben, # für Caddy in die Datei "mail_certs" (per "import mail_certs" im Caddyfile).
# die per "import" in das Hauptcaddyfile eingebunden werden kann. #
# Bei neuen Domains: Script erneut laufen lassen + caddy reload.
# #
# Usage: # Usage:
# DMS_CONTAINER=mailserver ./update-caddy-certs.sh # ./update-caddy-certs.sh
# DMS_CONTAINER=mailserver CADDY_DIR=/pfad/zu/caddy ./update-caddy-certs.sh # DRY_RUN=true ./update-caddy-certs.sh
# DMS_CONTAINER=mailserver DRY_RUN=true ./update-caddy-certs.sh # DMS_CONTAINER=mailserver CADDY_CONTAINER=caddy ./update-caddy-certs.sh
set -e set -e
DMS_CONTAINER=${DMS_CONTAINER:-"mailserver"} DMS_CONTAINER=${DMS_CONTAINER:-"mailserver"}
CADDY_DIR=${CADDY_DIR:-"."} # Verzeichnis wo das Caddyfile liegt CADDY_CONTAINER=${CADDY_CONTAINER:-"caddy"}
OUTPUT_FILE=${OUTPUT_FILE:-"$CADDY_DIR/mail_certs"} # Ohne Extension - Caddy importiert ohne .conf SCRIPT_DIR="$(cd "$(dirname "$0")" && pwd)"
OUTPUT_FILE="$SCRIPT_DIR/mail_certs"
DRY_RUN=${DRY_RUN:-"false"} DRY_RUN=${DRY_RUN:-"false"}
# Node-Hostname des Mailservers (für Default-Cert Block)
# Wird immer mit eingetragen, auch wenn keine DMS-Accounts existieren.
NODE_HOSTNAME=${NODE_HOSTNAME:-"node1.email-srvr.com"}
echo "============================================================" echo "============================================================"
echo " 📜 Caddy Wildcard-Cert Konfig Generator" echo " 📜 Caddy Wildcard-Cert Konfig Generator"
echo " DMS Container: $DMS_CONTAINER" echo " DMS Container: $DMS_CONTAINER"
echo " Caddy Container: $CADDY_CONTAINER"
echo " Output: $OUTPUT_FILE" echo " Output: $OUTPUT_FILE"
echo " Node Hostname: $NODE_HOSTNAME"
[ "$DRY_RUN" = "true" ] && echo " ⚠️ DRY RUN - Keine Dateien werden geschrieben" [ "$DRY_RUN" = "true" ] && echo " ⚠️ DRY RUN - Keine Dateien werden geschrieben"
echo "============================================================" echo "============================================================"
@ -34,104 +41,92 @@ DOMAINS=$(docker exec "$DMS_CONTAINER" setup email list 2>/dev/null \
| sort -u) | sort -u)
if [ -z "$DOMAINS" ]; then if [ -z "$DOMAINS" ]; then
echo "❌ Keine Accounts gefunden!" echo "⚠️ Keine DMS-Accounts gefunden. Nur Node-Hostname wird eingetragen."
exit 1
fi fi
if [ -n "$DOMAINS" ]; then
echo " Gefundene Domains:" echo " Gefundene Domains:"
for d in $DOMAINS; do echo " - $d"; done for d in $DOMAINS; do echo " - $d"; done
# --- email-srvr.com immer einschließen (Default-Domain des DMS) ---
EXTRA_DOMAINS="email-srvr.com"
for extra in $EXTRA_DOMAINS; do
if ! echo "$DOMAINS" | grep -q "^${extra}$"; then
DOMAINS="$DOMAINS $extra"
echo " + $extra (Default DMS Domain - immer dabei)"
fi fi
done
# --- Konfig generieren --- # --- Konfig generieren ---
echo "" echo ""
echo "📝 Generiere Caddy-Konfiguration..." echo "📝 Generiere Caddy-Konfiguration..."
CONTENT="" OUTPUT=""
CONTENT="${CONTENT}# mail_certs - Automatisch generiert von update-caddy-certs.sh\n" OUTPUT="${OUTPUT}# mail_certs - Automatisch generiert von update-caddy-certs.sh\n"
CONTENT="${CONTENT}# Wildcard-Zertifikate für alle DMS-Domains.\n" OUTPUT="${OUTPUT}# Wildcard-Zertifikate für DMS-Domains + Node-Hostname.\n"
CONTENT="${CONTENT}# Einbinden im Hauptcaddyfile: import mail_certs\n" OUTPUT="${OUTPUT}# Einbinden im Caddyfile: import mail_certs\n"
CONTENT="${CONTENT}# Generiert: $(date)\n" OUTPUT="${OUTPUT}# Generiert: $(date)\n"
CONTENT="${CONTENT}\n" OUTPUT="${OUTPUT}\n"
# Node-Hostname immer als erstes (Default-Cert des DMS)
echo " → Node-Hostname Block: $NODE_HOSTNAME"
OUTPUT="${OUTPUT}# Node-Hostname (Default-Cert für DMS Fallback)\n"
OUTPUT="${OUTPUT}${NODE_HOSTNAME} {\n"
OUTPUT="${OUTPUT} tls {\n"
OUTPUT="${OUTPUT} dns cloudflare {env.CLOUDFLARE_API_TOKEN}\n"
OUTPUT="${OUTPUT} }\n"
OUTPUT="${OUTPUT} respond \"OK\" 200\n"
OUTPUT="${OUTPUT}}\n\n"
# Wildcard-Blocks pro Kundendomain
for domain in $DOMAINS; do for domain in $DOMAINS; do
echo " → Block für: $domain" echo " → Wildcard Block: *.${domain}"
CONTENT="${CONTENT}# Wildcard-Cert für $domain\n" OUTPUT="${OUTPUT}# Wildcard-Cert für $domain\n"
CONTENT="${CONTENT}*.${domain}, ${domain} {\n" OUTPUT="${OUTPUT}*.${domain}, ${domain} {\n"
CONTENT="${CONTENT} tls {\n" OUTPUT="${OUTPUT} tls {\n"
CONTENT="${CONTENT} dns cloudflare {env.CLOUDFLARE_API_TOKEN}\n" OUTPUT="${OUTPUT} dns cloudflare {env.CLOUDFLARE_API_TOKEN}\n"
CONTENT="${CONTENT} }\n" OUTPUT="${OUTPUT} }\n"
CONTENT="${CONTENT} respond \"OK\" 200\n" OUTPUT="${OUTPUT} respond \"OK\" 200\n"
CONTENT="${CONTENT}}\n" OUTPUT="${OUTPUT}}\n\n"
CONTENT="${CONTENT}\n"
done done
# --- Ausgabe --- # --- Ausgabe ---
if [ "$DRY_RUN" = "true" ]; then if [ "$DRY_RUN" = "true" ]; then
echo "" echo ""
echo "--- VORSCHAU (DRY RUN) ---" echo "--- VORSCHAU ---"
printf '%b' "$CONTENT" printf '%b' "$OUTPUT"
echo "--- ENDE VORSCHAU ---" echo "--- ENDE ---"
else else
printf '%b' "$CONTENT" > "$OUTPUT_FILE" printf '%b' "$OUTPUT" > "$OUTPUT_FILE"
echo ""
echo " ✅ Geschrieben: $OUTPUT_FILE" echo " ✅ Geschrieben: $OUTPUT_FILE"
fi fi
# --- Prüfen ob Import im Caddyfile vorhanden --- # --- Import im Caddyfile prüfen ---
CADDYFILE="$CADDY_DIR/Caddyfile" CADDYFILE="$SCRIPT_DIR/Caddyfile"
if [ -f "$CADDYFILE" ]; then if [ -f "$CADDYFILE" ]; then
if grep -q "import mail_certs" "$CADDYFILE"; then if grep -q "import mail_certs" "$CADDYFILE"; then
echo " ✅ 'import mail_certs' bereits im Caddyfile vorhanden." echo " ✅ 'import mail_certs' bereits im Caddyfile vorhanden."
else else
echo "" echo ""
echo "⚠️ AKTION ERFORDERLICH:" echo "⚠️ AKTION: 'import mail_certs' fehlt noch im Caddyfile!"
echo " 'import mail_certs' fehlt noch im Caddyfile!" echo " Bitte nach dem globalen {} Block eintragen:"
echo " Bitte folgende Zeile am Anfang (nach dem globalen Block) eintragen:"
echo "" echo ""
echo " import mail_certs" echo " { ← globaler Block"
echo "" echo " email {env.CLOUDFLARE_EMAIL}"
echo " Oder automatisch einfügen? (y/N)" echo " ..."
read -r answer echo " }"
if [ "$answer" = "y" ] || [ "$answer" = "Y" ]; then echo " import mail_certs ← hier einfügen"
# Import nach der ersten Zeile mit "import " einfügen (falls schon welche da sind) echo " import email_autodiscover"
# oder nach dem globalen {} Block echo " ..."
if grep -q "^import " "$CADDYFILE"; then
# Schreibe nach der letzten import-Zeile
sed -i "/^import /a import mail_certs" "$CADDYFILE"
else
# Schreibe nach dem schließenden } des globalen Blocks
sed -i "/^}/a \\\nimport mail_certs" "$CADDYFILE"
fi
echo " ✅ Import eingefügt."
fi
fi fi
fi fi
# --- Caddy reload ---
echo "" echo ""
echo "============================================================" echo "============================================================"
echo "🔄 Nächste Schritte:" echo "🔄 Nächste Schritte:"
echo "" echo ""
echo "1. Caddyfile prüfen - 'import mail_certs' muss vorhanden sein" echo "1. Caddy Konfiguration validieren:"
echo " docker exec $CADDY_CONTAINER caddy validate --config /etc/caddy/Caddyfile"
echo "" echo ""
echo "2. Caddy Konfiguration validieren:" echo "2. Caddy neu laden (kein Downtime):"
echo " docker exec caddy caddy validate --config /etc/caddy/Caddyfile" echo " docker exec $CADDY_CONTAINER caddy reload --config /etc/caddy/Caddyfile"
echo "" echo ""
echo "3. Caddy neu laden (kein Downtime):" echo "3. Cert-Generierung verfolgen (~30s pro Domain):"
echo " docker exec caddy caddy reload --config /etc/caddy/Caddyfile" echo " docker logs -f $CADDY_CONTAINER 2>&1 | grep -i 'certificate\|acme\|tls\|error'"
echo "" echo ""
echo "4. Cert-Generierung verfolgen (dauert ~30s pro Domain):" echo "4. Cert-Pfade kontrollieren:"
echo " docker logs -f caddy 2>&1 | grep -i 'certificate\|acme\|tls'"
echo ""
echo "5. Cert-Pfade prüfen:"
echo " ls /var/lib/docker/volumes/caddy_data/_data/caddy/certificates/" echo " ls /var/lib/docker/volumes/caddy_data/_data/caddy/certificates/"
echo " acme-v02.api.letsencrypt.org-directory/" echo " acme-v02.api.letsencrypt.org-directory/"
echo "============================================================" echo "============================================================"