commit cd1be5abe2e8cee98c8ac8cf0ee58d1d7057857d Author: Myve Date: Thu Dec 19 20:27:46 2024 +0000 First commit diff --git a/01-setup.sh b/01-setup.sh new file mode 100755 index 0000000..5ce6e35 --- /dev/null +++ b/01-setup.sh @@ -0,0 +1,181 @@ +#!/usr/bin/env bash +# Fill in the following variables +domain= #www.google.com +mailver= #latest/stable + +# Exit on any error +set -e + +# Check for subdomain +if [ $(echo ${domain} | awk -F . '{print $3}') ] +then + _subdomain=$(echo ${domain} | awk -F . '{print $1}') + _domain="$(echo ${domain} | awk -F . '{print $2}').$(echo ${domain} | awk -F . '{print $3}')" +else + echo "Invalid \${domain} variable, exiting" + exit 1 +fi + +# Variable check +if [ -z ${domain} ] +then + echo "Missing variable, exiting..." + exit 1 +fi + +# Certbot +sudo certbot certonly --nginx --non-interactive --agree-tos --no-eff-email \ + --staple-ocsp --hsts --no-redirect --renew-hook 'docker exec myvemailbackup postfix reload' \ + -m eff@${_domain} -d ${domain} + +# Log +[ -d ./data/log/ ] || install --directory ./data/log/ +echo | tee ./data/log/{mail,downtime} + +# SSL +[ -d ./data/ssl/ ] || install --directory ./data/ssl/ +sudo ln -s -f /etc/letsencrypt/live/${domain}/fullchain.pem ./data/ssl/tls.pem +sudo ln -s -f /etc/letsencrypt/live/${domain}/privkey.pem ./data/ssl/tls.key + +# Environment file +[ -f ./.env ] || \ +cat >./.env <<- gen-env +# Required +# Mail domain +MYVEMAIL_SUBDOMAIN=${_subdomain} +MYVEMAIL_DOMAIN=${_domain} + +# Webmail port +MYVEMAIL_PORT=${proxyport} + +# Optional +# Version: latest or stable (defaults to latest) +MYVEMAIL_VERSION=${mailver} + +# Additional mail domains separated by commas +MYVEMAIL_ADDMX=${_domain} + +# Backup mail servers separated by commas +MYVEMAIL_PRIMARYMX= + +# Volumes +MYVEMAIL_VOLUME_SSL= +MYVEMAIL_VOLUME_LOG= +gen-env + +# Cleanup +rm -r ${0} ./build/ ./README.md -f + +# Create a downtime log +echo >./data/log/downtime + +# Add postqueue check systemd service +sudo tee /etc/systemd/system/postqueue-check.service >/dev/null <<'POSTQ-SERVICE' +[Unit] +Description=Check postfix mail queue + +[Service] +ExecStart=docker exec myvemailbackup postqueue-check +Type=oneshot + +[Install] +WantedBy=basic.target +POSTQ-SERVICE +sudo tee /etc/systemd/system/postqueue-check.timer >/dev/null <<'POSTQ-TIMER' +[Unit] +Description=Run postqueue-check every 5 seconds + +[Timer] +OnCalendar=*:*:0/5 +Persistent=true + +[Install] +WantedBy=timers.target +POSTQ-TIMER +sudo systemctl enable --now postqueue-check.timer + +# Log downtimes +sudo install /dev/stdin /usr/local/bin/downtime-check >/dev/null </dev/null && ping -q -c 1 -W 15 google.com >/dev/null +then + if [[ \$(ssh ${domain} docker container inspect -f '{{.State.Running}}' myvemail) == true ]] + then + exit 0 + else + echo "${domain} was inaccessible on \$(date)" >>$(pwd)/data/log/downtime + exit 1 + fi +else + exit 1 +fi +MAILSERVER +sudo tee /etc/systemd/system/downtime-check.service >/dev/null <<'MAILSERVER-SERVICE' +[Unit] +Description=Log downtimes + +[Service] +ExecStart=/usr/local/bin/downtime-check +Type=oneshot + +[Install] +WantedBy=basic.target +MAILSERVER-SERVICE +sudo tee /etc/systemd/system/downtime-check.timer >/dev/null <<'MAILSERVER-TIMER' +[Unit] +Description=Run primary mail server check every minute + +[Timer] +OnCalendar=*:0/1 +Persistent=true + +[Install] +WantedBy=timers.target +MAILSERVER-TIMER + +sudo tee /etc/systemd/system/downtime-send.service >/dev/null <<'POSTQ-SERVICE' +[Unit] +Description=Send downtime log + +[Service] +ExecStart=docker exec myvemailbackup downtime-send +Type=oneshot + +[Install] +WantedBy=basic.target +POSTQ-SERVICE +sudo tee /etc/systemd/system/downtime-send.timer >/dev/null <<'POSTQ-TIMER' +[Unit] +Description=Send downtime log monthly + +[Timer] +OnCalendar=monthly +AccuracySec=1h +Persistent=true + +[Install] +WantedBy=timers.target +POSTQ-TIMER +sudo systemctl enable --now downtime-check.timer downtime-send.timer + +# fail2ban postfix +sudo tee /etc/fail2ban/jail.d/postfix.local >/dev/null </dev/null <<'POSTFIX-FLOOD-ATTACK' +[Definition] +failregex = lost connection after AUTH from (.*)\[\] +ignoreregex = +POSTFIX-FLOOD-ATTACK diff --git a/README.md b/README.md new file mode 100644 index 0000000..de7ff5a --- /dev/null +++ b/README.md @@ -0,0 +1,43 @@ +# MyveMail Backup + +All-in-one docker container to host your own personal backup mail server, powered by Postfix mail transfer agent. If your primary mail server goes down, emails will be temporarily sent to this backup server, which then returns these emails to the primary as soon as it comes back online. + +*Note: ISP must have SMTP Port 25 open.* + +## :: Pre-installation +Update your DNS registry to reflect the following records +``` +# MX Record +@ 300 IN MX 10 ${subdomain}.${domain}. + +# A Record +${subdomain} 300 IN A ${server-ip-address} +``` +Example entries: +``` +server-ip-address= # Host IPv4 address +subdomain=mail +domain=website.com +``` + +## :: Installation +Clone this repo and build it locally or pull it on the registry specified in docker-compose.yaml: + +``` +git clone https://git.myvelabs.com/docker/myvemailbackup.git +``` + +Supply the variables asked for in 01-setup.sh. + +Run **01-setup.sh** to install the Letsencrypt certificates to be used by Postfix. It also generates a functional docker-compose env file. + +*Note: The container will fail if this step is skipped.* + +Once completed, the container may be brought up: + +``` +docker compose up --detach +``` + +## :: Post-installation +Add the backup server's IP address to the primary mail server's Postfix **mynetworks** configuration to properly receive the bounced emails. diff --git a/build/Dockerfile b/build/Dockerfile new file mode 100644 index 0000000..deb150d --- /dev/null +++ b/build/Dockerfile @@ -0,0 +1,28 @@ +# syntax = docker/dockerfile:1 +FROM alpine:edge + +# LABEL about the custom image +LABEL description="MyveMail Backup" + +# Copy required files folders +ADD run/docker-entrypoint /docker-entrypoint/ +ADD run/installer.sh /tmp/ + +# Update Ubuntu Software repository and install requisites +RUN printf '%s\n' 'https://dl-cdn.alpinelinux.org/alpine/latest-stable/main/' \ + 'https://dl-cdn.alpinelinux.org/alpine/latest-stable/community/' >/etc/apk/repositories \ + && apk update \ + && apk upgrade \ + && apk add --no-cache \ + bash bash-completion ncurses \ + ca-certificates openssl \ + postfix \ + # Installer + && /tmp/installer.sh \ + && rm /tmp/installer.sh + +# Expose ports +EXPOSE 25 + +# Entrypoint hd-wallet-derive script +CMD ["/docker-entrypoint/entrypoint.sh"] diff --git a/build/run/docker-entrypoint/entrypoint.sh b/build/run/docker-entrypoint/entrypoint.sh new file mode 100755 index 0000000..5ba8f59 --- /dev/null +++ b/build/run/docker-entrypoint/entrypoint.sh @@ -0,0 +1,75 @@ +#!/usr/bin/env bash +# Abort if an error is encountered +set -e + +# Exit function +trap '[ "${?}" -ne 77 ] || exit 77' ERR +function die +{ + local reset="\e[0m" + local red="\e[0m\e[0;31m" + local yellow="\e[0m\e[0;33m" + + echo -e "${red} +Error encountered in the following init script: +${yellow} + ${@} +${red} +Aborting... +${reset}" + + exit 77 +} + +# Reset +echo | tee /var/log/maillog /etc/postfix/{relaydomains,transportmaps,helo_access,rbl_override} + +# Postfix +echo ${MYVEMAIL_DOMAIN} >/etc/mailname +postconf -e "myhostname = ${MYVEMAIL_SUBDOMAIN}.${MYVEMAIL_DOMAIN}" +postconf -e "mydestination = \$myhostname, ${MYVEMAIL_SUBDOMAIN}.${MYVEMAIL_DOMAIN}, localhost, localhost.localdomain, localhost" +postconf -e "mydomain = ${MYVEMAIL_DOMAIN}" + +# resolv.conf +[ -d /var/spool/postfix/etc/ ] || mkdir /var/spool/postfix/etc/ +cp /etc/resolv.conf /var/spool/postfix/etc/resolv.conf + +# Add primary mail servers to mynetworks +if [ ${MYVEMAIL_PRIMARYMX} ] +then + primarymx+=(${MYVEMAIL_PRIMARYMX//,/ }) + postconf -e "$(postconf mynetworks)$(printf ' %s/32' ${primarymx[@]})" +fi + +# Relay setup +addmx=(${MYVEMAIL_DOMAIN}) +addmx+=(${MYVEMAIL_ADDMX//,/ }) +printf '%s OK\n' ${addmx[@]} >/etc/postfix/relaydomains +for domain in ${addmx[@]} +do + echo "${domain} smtp:mail.${domain}:25" | tee -a /etc/postfix/transportmaps >/dev/null + echo "${domain} OK" | tee -a /etc/postfix/{helo_access,rbl_override} >/dev/null +done + +# Start postfix +postfix start +postmap /etc/postfix/relaydomains /etc/postfix/transportmaps /etc/postfix/helo_access /etc/postfix/rbl_override +postfix reload + +# Downtime log +install /dev/stdin /usr/local/bin/downtime <<- downtime +#!/usr/bin/env bash +# Send downtime log to downtime email address +echo "From: ${MYVEMAIL_SUBDOMAIN}@${MYVEMAIL_DOMAIN} +To: downtime@${MYVEMAIL_DOMAIN} +Subject: Monthly downtime log + +\$(cat /var/log/downtime)" | sendmail downtime@${MYVEMAIL_DOMAIN} + +# Delete log to start anew +echo >/var/log/downtime +downtime + +# Monitor log +echo -e "\n\e[1;32mMail service is ready\e[0m\n" +tail -f /var/log/maillog diff --git a/build/run/installer.sh b/build/run/installer.sh new file mode 100755 index 0000000..8c51e60 --- /dev/null +++ b/build/run/installer.sh @@ -0,0 +1,62 @@ +#!/usr/bin/env bash + +############### +#// +#// Postfix +#// +############### +# Postfix +postconf -e 'myorigin = $mydomain' +postconf -e 'inet_interfaces = all' +postconf -e 'inet_protocols = ipv4' +postconf -e 'smtp_address_preference = ipv4' +postconf -e 'message_size_limit = 0' +postconf -e 'mailbox_size_limit = 0' + +# Touch aliases db +newaliases + +# Logging +postconf -e "maillog_file = /var/log/maillog" + +# Backup mail server specific settings +postconf -e 'maximal_queue_lifetime = 30d' +postconf -e 'minimal_backoff_time = 60s' +postconf -e 'relay_recipient_maps = ' +postconf -e "relay_domains = lmdb:/etc/postfix/relaydomains" +postconf -e "transport_maps = lmdb:/etc/postfix/transportmaps" + +# Security +postconf -e 'smtpd_tls_security_level = may' +postconf -e 'smtp_tls_security_level = may' + +postconf -e 'smtpd_tls_loglevel = 1' +postconf -e 'smtp_tls_verify_cert_match = hostname, nexthop, dot-nexthop' +postconf -e 'smtp_tls_CApath = /etc/ssl/certs' +postconf -e "smtp_tls_CAfile = /etc/ssl/certs/ca-certificates.crt" +postconf -e 'smtp_tls_loglevel = 1' +openssl rehash /etc/ssl/certs || c_rehash /etc/ssl/certs + +[ -d /etc/postfix/ssl/ ] || mkdir -p /etc/postfix/ssl/ +postconf -e "smtpd_tls_key_file = /etc/postfix/ssl/tls.key" +postconf -e "smtpd_tls_cert_file = /etc/postfix/ssl/tls.pem" + +# # Enforce TLSv1.2 or TLSv1.2 +postconf -e "smtpd_tls_protocols = >=TLSv1.2" + +# Spam filters (https://www.linuxbabe.com/mail-server/block-email-spam-postfix) +postconf -e "smtpd_sender_restrictions = permit_mynetworks, permit_sasl_authenticated, reject_unknown_sender_domain, reject_unknown_reverse_client_hostname, reject_unknown_client_hostname" +postconf -e "smtpd_helo_required = yes" +postconf -e "smtpd_helo_restrictions = permit_mynetworks, permit_sasl_authenticated, check_helo_access lmdb:/etc/postfix/helo_access, reject_invalid_helo_hostname, reject_non_fqdn_helo_hostname, reject_unknown_helo_hostname" +postconf -e "smtpd_recipient_restrictions = permit_mynetworks, permit_sasl_authenticated, reject_unauth_destination" + +# Check postqueue every 5 seconds +install /dev/stdin /usr/local/bin/postqueue-check >/dev/null <<'postqueue' +#!/usr/bin/env bash +if postqueue -p | grep -q 'Mail queue is empty' +then + exit 0 +else + postqueue -f +fi +postqueue diff --git a/docker-compose.yaml b/docker-compose.yaml new file mode 100644 index 0000000..fc6f1ac --- /dev/null +++ b/docker-compose.yaml @@ -0,0 +1,34 @@ +services: + myvemailbackup: + image: hub.myvelabs.com/lab/myvemailbackup:${MYVEMAIL_VERSION:-latest} + container_name: myvemailbackup + restart: unless-stopped + + # build: . + + ports: + - 25:25/tcp + + environment: + # Backup mmail domain details + MYVEMAIL_SUBDOMAIN: ${MYVEMAIL_SUBDOMAIN} + MYVEMAIL_DOMAIN: ${MYVEMAIL_DOMAIN} + + # Primary mail domain names separated by commas + MYVEMAIL_ADDMX: ${MYVEMAIL_ADDMX} + + # Primary mail servers IP addresses separated by commas + MYVEMAIL_PRIMARYMX: ${MYVEMAIL_PRIMARYMX} + volumes: + # Logs + - ${MYVEMAIL_VOLUME_LOG:-./data/log/mail}:/var/log/maillog + - ${MYVEMAIL_VOLUME_LOG:-./data/log/downtime}:/var/log/downtime + # SSL (point to individual files in case symlinks are being used) + - ${MYVEMAIL_VOLUME_SSL:-./data/ssl}/tls.key:/etc/postfix/ssl/tls.key + - ${MYVEMAIL_VOLUME_SSL:-./data/ssl}/tls.pem:/etc/postfix/ssl/tls.pem + networks: + - myvemailbackup + +networks: + myvemailbackup: + external: false