diff options
author | Elizabeth Hunt <me@liz.coffee> | 2025-04-27 21:15:30 -0700 |
---|---|---|
committer | Elizabeth Hunt <me@liz.coffee> | 2025-04-27 21:25:52 -0700 |
commit | daef0cf448af17357b552245f39067a9d340ce3d (patch) | |
tree | f65a660f7232f057b0c14e477c166006bfb83f87 /playbooks/roles/mail/templates | |
parent | 1dcdfe34a74708f88aad68af965f4bb5c79adff1 (diff) | |
download | infra-daef0cf448af17357b552245f39067a9d340ce3d.tar.gz infra-daef0cf448af17357b552245f39067a9d340ce3d.zip |
Waow
Diffstat (limited to 'playbooks/roles/mail/templates')
7 files changed, 157 insertions, 58 deletions
diff --git a/playbooks/roles/mail/templates/stacks/docker-compose.yml b/playbooks/roles/mail/templates/stacks/docker-compose.yml index 50108c1..b4cc3e0 100644 --- a/playbooks/roles/mail/templates/stacks/docker-compose.yml +++ b/playbooks/roles/mail/templates/stacks/docker-compose.yml @@ -4,11 +4,13 @@ services: restart: always volumes: - {{ mail_base }}/volumes/data/roundcube/db:/var/roundcube/db - - {{ mail_base }}/volumes/data/roundcube/config:/var/roundcube/config + - {{ mail_base }}/volumes/data/roundcube/config:/var/roundcube/config/ environment: + - DEPLOYMENT_TIME={{ now() }} - ROUNDCUBEMAIL_DB_TYPE=sqlite - - ROUNDCUBEMAIL_SKIN=elastic + - ROUNDCUBEMAIL_SKIN={{ roundcube_skin | default('elastic') }} - ROUNDCUBEMAIL_PLUGINS={{ roundcube_plugins }} + - ROUNDCUBEMAIL_COMPOSER_PLUGINS={{ roundcube_composer_plugins }} - ROUNDCUBEMAIL_DEFAULT_HOST={{ roundcube_default_host }} - ROUNDCUBEMAIL_DEFAULT_PORT={{ roundcube_default_port }} - ROUNDCUBEMAIL_SMTP_SERVER={{ roundcube_smtp_host }} @@ -16,6 +18,11 @@ services: networks: - proxy - roundcube + healthcheck: + test: ["CMD", "curl", "--fail", "http://localhost:8000"] + timeout: 3s + interval: 30s + retries: 2 deploy: mode: replicated replicas: 1 @@ -31,70 +38,62 @@ services: mailserver: image: ghcr.io/docker-mailserver/docker-mailserver:latest hostname: {{ mail_domain }} + command: + - /scripts/wait-for-cert.sh {% if homelab_build %} - command: - - /bin/sh - - -c - - | - [ ! -f "/etc/letsencrypt/live/{{ mail_domain }}" ] && sleep 60 # Sleep until certificate requested from traefik - supervisord -c /etc/supervisor/supervisord.conf healthcheck: disable: true +{% else %} + healthcheck: + test: ["CMD-SHELL", "ss --listening --tcp | grep -P :smtp"] + interval: 3s + timeout: 2s + retries: 3 {% endif %} + ports: + - '25:25' + - '587:587' + - '465:465' + - '143:143' + - '993:993' + - '4190:4190' + - '110:110' + - '995:995' + stop_grace_period: 30s deploy: mode: replicated replicas: 1 - labels: - - traefik.enable=true - - traefik.swarm.network=proxy - # ManageSieve - - traefik.tcp.routers.sieve.tls.passthrough=true - - traefik.tcp.routers.sieve.rule=HostSNI(`*`) - - traefik.tcp.routers.sieve.entrypoints=sieve - - traefik.tcp.routers.sieve.service=sieve - - traefik.tcp.services.sieve.loadbalancer.server.port=4190 - # IMAP - - traefik.tcp.routers.imap.tls.passthrough=true - - traefik.tcp.routers.imap.rule=HostSNI(`*`) - - traefik.tcp.routers.imap.entrypoints=imap - - traefik.tcp.routers.imap.service=imap - - traefik.tcp.services.imap.loadbalancer.server.port=993 - # SMTPS - - traefik.tcp.routers.smtps.tls.passthrough=true - - traefik.tcp.routers.smtps.rule=HostSNI(`*`) - - traefik.tcp.routers.smtps.entrypoints=smtps - - traefik.tcp.routers.smtps.service=smtps - - traefik.tcp.services.smtps.loadbalancer.server.port=465 - # SMTP (StartTLS) - - traefik.tcp.routers.smtptls.tls.passthrough=true - - traefik.tcp.routers.smtptls.rule=HostSNI(`*`) - - traefik.tcp.routers.smtptls.entrypoints=smtptls - - traefik.tcp.routers.smtptls.service=smtptls - - traefik.tcp.services.smtptls.loadbalancer.server.port=587 - # SMTP ("ye' old") - - traefik.tcp.routers.smtp.tls.passthrough=true - - traefik.tcp.routers.smtp.rule=HostSNI(`*`) - - traefik.tcp.routers.smtp.entrypoints=smtp - - traefik.tcp.routers.smtp.service=smtp - - traefik.tcp.services.smtp.loadbalancer.server.port=25 + update_config: + parallelism: 1 + failure_action: rollback + # order: start-first + # We need to stop the old container first because it holds a lock on the + # Postfix mail queue. I don't believe there is a feasible way to solve + # this without either a tiny bit of downtime waiting for the lock to clear, + # or lost mail since we'd have to ignore the lock and thus two competing mailservers + # are accepting mail. + # One of these is more acceptable than the other haha. + # See stuff in scripts/ for the last attempt if interested. + order: stop-first volumes: - - {{ mail_base }}/volumes/data/dms/vmail:/var/mail/ - - {{ mail_base }}/volumes/data/dms/mail-state:/var/mail-state/ - - {{ mail_base }}/volumes/data/dms/mail-logs:/var/log/mail/ - - {{ mail_base }}/volumes/data/dms/config:/tmp/docker-mailserver/ + - {{ mail_base }}/volumes/scripts/:/scripts/ + - {{ mail_base }}/volumes/data/dms/vmail/:/var/mail/ + - {{ mail_base }}/volumes/data/dms/mail-state/:/var/mail-state/ + - {{ mail_base }}/volumes/data/dms/mail-logs/:/var/log/mail/ + - {{ mail_base }}/volumes/data/dms/config/:/tmp/docker-mailserver/ - {{ mail_base }}/volumes/data/dms/config/dovecot-ldap.conf:/etc/dovecot/dovecot-ldap.conf.ext - {{ letsencrypt_certs }}:/certs/:ro - /etc/localtime:/etc/localtime:ro environment: + - DEPLOYMENT_TIME={{ now() }} - SSL_TYPE=manual - SSL_CERT_PATH=/certs/{{ mail_domain }}.pem - SSL_KEY_PATH=/certs/{{ mail_domain }}.key - ENABLE_CLAMAV=0 - ENABLE_AMAVIS=0 - - ENABLE_FAIL2BAN=1 - ENABLE_SASLAUTHD=1 - ENABLE_MANAGESIEVE=1 - - ENABLE_POSTGREY=0 + - ENABLE_POSTGREY=1 - SPOOF_PROTECTION=1 - ACCOUNT_PROVISIONER=LDAP @@ -121,12 +120,7 @@ services: - RELAY_USER={{ relay_user }} - RELAY_PASSWORD={{ relay_password }} - networks: - - mailserver - - proxy - networks: - mailserver: roundcube: proxy: external: true diff --git a/playbooks/roles/mail/templates/volumes/data/dms/config/dovecot.cf b/playbooks/roles/mail/templates/volumes/data/dms/config/dovecot.cf new file mode 100644 index 0000000..62d0550 --- /dev/null +++ b/playbooks/roles/mail/templates/volumes/data/dms/config/dovecot.cf @@ -0,0 +1,27 @@ +haproxy_trusted_networks = {{ homelab_network }} + +service imap-login { + inet_listener imap { + haproxy = yes + } + + inet_listener imaps { + haproxy = yes + } +} + +service pop3-login { + inet_listener pop3 { + haproxy = yes + } + + inet_listener pop3s { + haproxy = yes + } +} + +service managesieve-login { + inet_listener sieve { + haproxy = yes + } +} diff --git a/playbooks/roles/mail/templates/volumes/data/dms/config/postfix-master.cf b/playbooks/roles/mail/templates/volumes/data/dms/config/postfix-master.cf new file mode 100644 index 0000000..1885f4d --- /dev/null +++ b/playbooks/roles/mail/templates/volumes/data/dms/config/postfix-master.cf @@ -0,0 +1,3 @@ +smtp/inet/postscreen_upstream_proxy_protocol=haproxy +submission/inet/smtpd_upstream_proxy_protocol=haproxy +submissions/inet/smtpd_upstream_proxy_protocol=haproxy diff --git a/playbooks/roles/mail/templates/volumes/data/dms/config/user-patches.sh b/playbooks/roles/mail/templates/volumes/data/dms/config/user-patches.sh index c62753f..1749499 100755 --- a/playbooks/roles/mail/templates/volumes/data/dms/config/user-patches.sh +++ b/playbooks/roles/mail/templates/volumes/data/dms/config/user-patches.sh @@ -3,7 +3,13 @@ postconf -e 'smtpd_sasl_type = dovecot' postconf -e 'smtpd_sasl_path = /dev/shm/sasl-auth.sock' postconf -e 'smtpd_sasl_auth_enable = yes' -postconf -e 'broken_sasl_auth_clients = yes' +postconf -e 'broken_sasl_auth_clients = no' +postconf -e 'smtpd_tls_auth_only = yes' +postconf -e 'smtpd_tls_security_level = encrypt' + +postconf -e 'postscreen_bare_newline_enable = no' +postconf -e 'postscreen_non_smtp_command_enable = no' +postconf -e 'postscreen_pipelining_enable = no' postconf -e 'smtp_tls_wrappermode = yes' # for relay @@ -34,8 +40,3 @@ userdb { args = username_format=%u uid=docker gid=docker home=/var/mail/%d/%u default_fields = uid=docker gid=docker home=/var/mail/%d/%u }" > /etc/dovecot/conf.d/auth-ldap.conf.ext - -#userdb { -# driver = static -# args = home=/var/mail/%u -#}" diff --git a/playbooks/roles/mail/templates/volumes/scripts/check-postfix-health.unused.sh b/playbooks/roles/mail/templates/volumes/scripts/check-postfix-health.unused.sh new file mode 100644 index 0000000..198221a --- /dev/null +++ b/playbooks/roles/mail/templates/volumes/scripts/check-postfix-health.unused.sh @@ -0,0 +1,24 @@ +#!/bin/bash + +LOCKFILE="/var/mail-state/lib-postfix/master.lock" + +function log() { + echo "[health] $@" +} + +if [ -f "$LOCKFILE" ]; then + PID=$(cat "$LOCKFILE") + log "pid $PID" + if kill -0 "$PID" 2>/dev/null; then + if ss --listening --tcp | grep -P 'LISTEN.+:smtp' > /dev/null; then + log "successfully listening to smtp" + exit 0 + fi + else + # Not our postfix lock. + exit 0 + fi +fi + +log "bad health state" +exit 1 diff --git a/playbooks/roles/mail/templates/volumes/scripts/wait-for-cert.sh b/playbooks/roles/mail/templates/volumes/scripts/wait-for-cert.sh new file mode 100644 index 0000000..0f8018c --- /dev/null +++ b/playbooks/roles/mail/templates/volumes/scripts/wait-for-cert.sh @@ -0,0 +1,23 @@ +#!/bin/bash +set -e + +function log() { + echo "[cert] $@" +} + +CERT="/certs/{{ mail_domain }}.pem" +MAX_TRIES=30 +TRY=0 + +while [ ! -f "$CERT" ]; do + if [ "$TRY" -eq "$MAX_TRIES" ]; then + log "[$TRY/$MAX_TRIES] Max tries, failing." + exit 1 + fi + log "[$TRY/$MAX_TRIES] Certificate nonexistant. Waiting..." + sleep 2 + TRY=$((TRY + 1)) +done + +log "Cert check done. Starting DMS." +supervisord -c /etc/supervisor/supervisord.conf diff --git a/playbooks/roles/mail/templates/volumes/scripts/wait-for-postfix.unused.sh b/playbooks/roles/mail/templates/volumes/scripts/wait-for-postfix.unused.sh new file mode 100644 index 0000000..3e8a8c5 --- /dev/null +++ b/playbooks/roles/mail/templates/volumes/scripts/wait-for-postfix.unused.sh @@ -0,0 +1,27 @@ +# This was an attempt to keep rolling updates with very little downtime. +# I don't think it's worth it, and the nature of update_config provides +# little flexibility to use here. + +#!/bin/bash +set -e + +function log() { + echo "[startup] $@" +} + +LOCKFILE="/var/mail-state/lib-postfix/master.lock" +MAX_TRIES=30 +TRY=0 + +while [ -f "$LOCKFILE" ]; do + if [ "$TRY" -eq "$MAX_TRIES" ]; then + log "[$TRY/$MAX_TRIES] Max tries, failing." + exit 1 + fi + log "[$TRY/$MAX_TRIES] Lockfile exists, waiting for it to be cleaned up by previous container..." + sleep 2 + TRY=$((TRY + 1)) +done + +log "Lock check done. Starting DMS." +supervisord -c /etc/supervisor/supervisord.conf |