From efc9a8a0f4e6a7c41ef3a8f97150f84cf5d370e0 Mon Sep 17 00:00:00 2001 From: furyhawk Date: Sat, 11 Apr 2026 23:59:27 +0800 Subject: [PATCH] fix: update mailserver configuration to sync Traefik certificates and adjust SSL settings --- scripts/mailserver-sync-traefik-cert.sh | 65 +++++++++++++++++++++++++ swarm/mailserver.yml | 2 +- swarm/mailserver/README.md | 50 ++++++++++--------- swarm/mailserver/docker-mailserver.env | 5 +- 4 files changed, 96 insertions(+), 26 deletions(-) create mode 100755 scripts/mailserver-sync-traefik-cert.sh diff --git a/scripts/mailserver-sync-traefik-cert.sh b/scripts/mailserver-sync-traefik-cert.sh new file mode 100755 index 0000000..5b495f8 --- /dev/null +++ b/scripts/mailserver-sync-traefik-cert.sh @@ -0,0 +1,65 @@ +#!/bin/bash +set -euo pipefail + +ACME_JSON_PATH="${1:-/var/data/config/traefik/acme.json}" +MAIL_DOMAIN="${2:-mail.furyhawk.lol}" +TARGET_DIR="/var/data/docker-mailserver/ssl" + +if [ ! -f "$ACME_JSON_PATH" ]; then + echo "ACME file not found: $ACME_JSON_PATH" + exit 1 +fi + +mkdir -p "$TARGET_DIR" + +python3 - "$ACME_JSON_PATH" "$MAIL_DOMAIN" "$TARGET_DIR" <<'PY' +import base64 +import json +import os +import sys + +acme_path, mail_domain, target_dir = sys.argv[1], sys.argv[2], sys.argv[3] + +with open(acme_path, "r", encoding="utf-8") as f: + data = json.load(f) + +cert_entries = data.get("letsencrypt", {}).get("Certificates", []) +if not cert_entries: + raise SystemExit("No certificates found in acme.json") + +selected = None +for entry in cert_entries: + domain = entry.get("domain", {}) + main = domain.get("main", "") + sans = domain.get("sans", []) or [] + if main == mail_domain or mail_domain in sans: + selected = entry + break + +if selected is None: + raise SystemExit(f"No certificate found for domain: {mail_domain}") + +cert_b64 = selected.get("certificate") +key_b64 = selected.get("key") +if not cert_b64 or not key_b64: + raise SystemExit("Selected certificate entry is missing certificate/key data") + +cert_pem = base64.b64decode(cert_b64) +key_pem = base64.b64decode(key_b64) + +cert_path = os.path.join(target_dir, "fullchain.pem") +key_path = os.path.join(target_dir, "privkey.pem") + +with open(cert_path, "wb") as f: + f.write(cert_pem) +with open(key_path, "wb") as f: + f.write(key_pem) + +os.chmod(cert_path, 0o600) +os.chmod(key_path, 0o600) + +print(f"Wrote {cert_path}") +print(f"Wrote {key_path}") +PY + +echo "Certificate sync complete for $MAIL_DOMAIN" diff --git a/swarm/mailserver.yml b/swarm/mailserver.yml index b52c2f4..d68a2c3 100644 --- a/swarm/mailserver.yml +++ b/swarm/mailserver.yml @@ -9,7 +9,7 @@ services: - mail_state:/var/mail-state - mail_logs:/var/log/mail - mail_config:/tmp/docker-mailserver - - /var/data/config/acme.json:/etc/letsencrypt/acme.json:ro + - /var/data/docker-mailserver/ssl:/tmp/docker-mailserver/ssl:ro networks: - internal - traefik-public diff --git a/swarm/mailserver/README.md b/swarm/mailserver/README.md index e66cd79..4465a92 100644 --- a/swarm/mailserver/README.md +++ b/swarm/mailserver/README.md @@ -1,19 +1,30 @@ # Self-hosted Mail Server (Docker Swarm) -This stack uses Docker Mailserver and is deployed with: - -make deploy-mailserver +This stack uses Docker Mailserver and can reuse the certificate issued by Traefik. ## 1) Configure mail domain -Edit swarm/mailserver/docker-mailserver.env: +Edit [swarm/mailserver/docker-mailserver.env](swarm/mailserver/docker-mailserver.env): - OVERRIDE_HOSTNAME: mail host FQDN (example: mail.example.com) - OVERRIDE_DOMAINNAME: root mail domain (example: example.com) - POSTMASTER_ADDRESS: postmaster mailbox -- SSL_DOMAIN: certificate domain for mail TLS -## 2) DNS records (required) +## 2) Sync Traefik cert for mailserver + +Traefik stores certificates in `/var/data/config/traefik/acme.json`. +Mailserver needs cert files (`fullchain.pem` and `privkey.pem`). + +Run: + +`./scripts/mailserver-sync-traefik-cert.sh /var/data/config/traefik/acme.json mail.example.com` + +This writes: + +- `/var/data/docker-mailserver/ssl/fullchain.pem` +- `/var/data/docker-mailserver/ssl/privkey.pem` + +## 3) DNS records (required) Create these DNS records for your mail domain: @@ -23,7 +34,7 @@ Create these DNS records for your mail domain: - DKIM TXT: mail._domainkey.example.com -> generated DKIM key - DMARC TXT: _dmarc.example.com -> v=DMARC1; p=quarantine; rua=mailto:postmaster@example.com -## 3) Open ports +## 4) Open ports Ensure inbound TCP is open to your Traefik manager node for: @@ -32,27 +43,20 @@ Ensure inbound TCP is open to your Traefik manager node for: - 993 (IMAPS) - 995 (POP3S) -Mail ports are exposed by the Traefik service in [swarm/core.yml](swarm/core.yml), then routed to the mail service using TCP labels in [swarm/mailserver.yml](swarm/mailserver.yml). +Mail ports are exposed by [swarm/core.yml](swarm/core.yml) and routed to [swarm/mailserver.yml](swarm/mailserver.yml) via TCP labels. -## 4) Deploy +## 5) Deploy -make deploy-mailserver +`make deploy-mailserver` -## 5) Create mailbox accounts +## 6) Create mailbox accounts -Use the Docker Mailserver setup helper from a manager node: +`docker exec -it $(docker ps --filter name=mailserver_mail --format '{{.ID}}' | head -n 1) setup email add user@example.com 'StrongPasswordHere'` -docker exec -it $(docker ps --filter name=mailserver_mail --format '{{.ID}}' | head -n 1) setup email add user@example.com 'StrongPasswordHere' +`docker exec -it $(docker ps --filter name=mailserver_mail --format '{{.ID}}' | head -n 1) setup alias add postmaster@example.com user@example.com` -docker exec -it $(docker ps --filter name=mailserver_mail --format '{{.ID}}' | head -n 1) setup alias add postmaster@example.com user@example.com +## 7) Verify -## 6) Verify +- Check service status: `docker service ls | grep mailserver` +- Check logs: `docker service logs -f mailserver_mail` -- Check service status: docker service ls | grep mailserver -- Check logs: docker service logs -f mailserver_mail -- Send a test from an external mailbox and confirm delivery. - -## Notes - -- Mail data is persisted in named volumes: mail_data, mail_state, mail_logs, mail_config. -- For high deliverability, add PTR/rDNS with your server provider and keep SPF/DKIM/DMARC aligned. diff --git a/swarm/mailserver/docker-mailserver.env b/swarm/mailserver/docker-mailserver.env index 4269a51..3cb2bb8 100644 --- a/swarm/mailserver/docker-mailserver.env +++ b/swarm/mailserver/docker-mailserver.env @@ -6,5 +6,6 @@ OVERRIDE_HOSTNAME=mail.furyhawk.lol OVERRIDE_DOMAINNAME=furyhawk.lol POSTMASTER_ADDRESS=postmaster@furyhawk.lol PERMIT_DOCKER=network -SSL_TYPE=letsencrypt -SSL_DOMAIN=mail.furyhawk.lol \ No newline at end of file +SSL_TYPE=manual +SSL_CERT_PATH=/tmp/docker-mailserver/ssl/fullchain.pem +SSL_KEY_PATH=/tmp/docker-mailserver/ssl/privkey.pem \ No newline at end of file