From 7b58f8215617e490a53036e7efd2b0d4a70b5546 Mon Sep 17 00:00:00 2001 From: Myve Date: Fri, 9 Aug 2024 12:50:33 +0000 Subject: [PATCH] First commit --- build/Dockerfile.alpine | 34 ++ build/Dockerfile.ubuntu | 31 ++ build/run/bin/add-user | 96 ++++ build/run/bin/install-mariadb | 24 + build/run/bin/list-dkim | 10 + build/run/bin/setup | 191 +++++++ build/run/bin/upgrade-roundcube | 17 + build/run/docker-entrypoint/entrypoint.sh | 39 ++ .../docker-entrypoint/init.d/00-mariadb.sh | 64 +++ build/run/docker-entrypoint/init.d/10-ssl.sh | 22 + .../init.d/15-opendkimmarc.sh | 45 ++ .../run/docker-entrypoint/init.d/20-nginx.sh | 57 ++ .../docker-entrypoint/init.d/25-dovecot.sh | 15 + .../docker-entrypoint/init.d/30-postfix.sh | 48 ++ build/run/docker-entrypoint/init.d/50-cron.sh | 34 ++ .../docker-entrypoint/init.d/60-postwhite.sh | 9 + build/run/installer-alpine.sh | 509 +++++++++++++++++ build/run/installer-ubuntu.sh | 511 ++++++++++++++++++ docker-compose.yaml | 51 ++ generate-env.sh | 38 ++ nginx-setup.sh | 106 ++++ 21 files changed, 1951 insertions(+) create mode 100644 build/Dockerfile.alpine create mode 100644 build/Dockerfile.ubuntu create mode 100755 build/run/bin/add-user create mode 100755 build/run/bin/install-mariadb create mode 100755 build/run/bin/list-dkim create mode 100755 build/run/bin/setup create mode 100755 build/run/bin/upgrade-roundcube create mode 100755 build/run/docker-entrypoint/entrypoint.sh create mode 100755 build/run/docker-entrypoint/init.d/00-mariadb.sh create mode 100755 build/run/docker-entrypoint/init.d/10-ssl.sh create mode 100755 build/run/docker-entrypoint/init.d/15-opendkimmarc.sh create mode 100755 build/run/docker-entrypoint/init.d/20-nginx.sh create mode 100755 build/run/docker-entrypoint/init.d/25-dovecot.sh create mode 100755 build/run/docker-entrypoint/init.d/30-postfix.sh create mode 100755 build/run/docker-entrypoint/init.d/50-cron.sh create mode 100755 build/run/docker-entrypoint/init.d/60-postwhite.sh create mode 100755 build/run/installer-alpine.sh create mode 100755 build/run/installer-ubuntu.sh create mode 100644 docker-compose.yaml create mode 100755 generate-env.sh create mode 100755 nginx-setup.sh diff --git a/build/Dockerfile.alpine b/build/Dockerfile.alpine new file mode 100644 index 0000000..a45159a --- /dev/null +++ b/build/Dockerfile.alpine @@ -0,0 +1,34 @@ +# syntax = docker/dockerfile:1 +FROM alpine:edge + +# LABEL about the custom image +LABEL description="MyveMail" + +# Env files (differs between distros) +ENV MYVEMAIL_NGINX_USERGROUP=nginx +ENV MYVEMAIL_OPENDKIM_CONF=/etc/opendkim/opendkim.conf +ENV MYVEMAIL_OPENDMARC_CONF=/etc/opendmarc/opendmarc.conf + +# Copy required files folders +ADD run/bin /usr/local/bin/ +ADD run/docker-entrypoint /docker-entrypoint/ +COPY run/installer-alpine.sh /tmp/installer.sh + +# Update Ubuntu Software repository and install requisites +RUN printf '%s\n' 'https://dl-cdn.alpinelinux.org/alpine/edge/main/' \ + 'https://dl-cdn.alpinelinux.org/alpine/edge/community/' >/etc/apk/repositories \ + && apk update \ + && apk upgrade \ + && apk add --no-cache \ + bash bash-completion ncurses \ + && /tmp/installer.sh \ + && rm /tmp/installer.sh + +# Expose ports +EXPOSE 25 +EXPOSE 80 +EXPOSE 587 +EXPOSE 143 993 + +# Entrypoint hd-wallet-derive script +CMD ["/docker-entrypoint/entrypoint.sh"] diff --git a/build/Dockerfile.ubuntu b/build/Dockerfile.ubuntu new file mode 100644 index 0000000..2b15bae --- /dev/null +++ b/build/Dockerfile.ubuntu @@ -0,0 +1,31 @@ +# syntax = docker/dockerfile:1 +FROM ubuntu:latest + +# LABEL about the custom image +LABEL description="MyveMail" + +# Disable Prompt During Packages Installation +ARG DEBIAN_FRONTEND=noninteractive + +# Env files (differs between distros) +ENV MYVEMAIL_NGINX_USERGROUP=www-data +ENV MYVEMAIL_OPENDKIM_CONF=/etc/opendkim.conf +ENV MYVEMAIL_OPENDMARC_CONF=/etc/opendmarc.conf + +# Copy required files folders +ADD run/bin /usr/local/bin/ +ADD run/docker-entrypoint /docker-entrypoint/ +COPY run/installer-ubuntu.sh /tmp/installer.sh + +# Update Ubuntu Software repository and install requisites +RUN /tmp/installer.sh \ + && rm /tmp/installer.sh + +# Expose ports +EXPOSE 25 +EXPOSE 80 +EXPOSE 587 +EXPOSE 143 993 + +# Entrypoint hd-wallet-derive script +CMD ["/docker-entrypoint/entrypoint.sh"] diff --git a/build/run/bin/add-user b/build/run/bin/add-user new file mode 100755 index 0000000..52a95ec --- /dev/null +++ b/build/run/bin/add-user @@ -0,0 +1,96 @@ +#!/usr/bin/env bash +# Exit if any errors pop up +set -e + +# Array of available domains +domains=(${MYVEMAIL_DOMAIN}) +domains+=(${MYVEMAIL_ADDMX//,/ }) + +# Check if postfixadmin db has been installed +if [ ! -d /var/lib/mysql/${MYVEMAIL_POSTFIXADMIN_DBNAME}/ ] +then + echo "Run setup script before proceeding" + exit 1 +fi + +# Check if mariadb is active +if ! ps aux | grep -q mariadb +then + mariadbd --user=root --datadir=/var/lib/mysql/ & + until mariadb --user=root --database=mysql -e "show tables;" >/dev/null + do + sleep 1 + done +fi + +# Proceed only if postfixadmin db is installed +if mariadb --user=root --database=${MYVEMAIL_POSTFIXADMIN_DBNAME} -e "select * from domain;" | grep -q ${MYVEMAIL_DOMAIN} +then + # Mail account domain + echo -e "\e[1;34mChoose an available domain from the following: ${domains[@]}\e[0m" + until [ ${MYVEMAIL_ADDUSER_DOMAIN} ] && [[ ${domains[@]} =~ ${MYVEMAIL_ADDUSER_DOMAIN} ]] + do + read -r -p 'Domain: ' MYVEMAIL_ADDUSER_DOMAIN + if [ -z "${MYVEMAIL_ADDUSER_DOMAIN}" ] + then + echo -e '\n\e[1;31mField cannot be empty, try again\e[0m' + elif ! [[ ${domains[@]} =~ ${MYVEMAIL_ADDUSER_DOMAIN} ]] + then + echo -e '\n\e[1;31mChoose a valid domain\e[0m' + fi + done + + # Mail username + if [ -z ${MYVEMAIL_MAIL_USER} ] + then + echo -e '\n\e[1;34mType in your email username\e[0m' + until [ ${MYVEMAIL_MAIL_USER} ] + do + read -r -p 'Username: ' MYVEMAIL_MAIL_USER + [ ${MYVEMAIL_MAIL_USER} ] || echo -e '\n\e[1;31mUsername cannot be empty, try again\e[0m' + done + echo -e '\n\e[1;32mMail user '${MYVEMAIL_MAIL_USER}'@'${MYVEMAIL_ADDUSER_DOMAIN}' has been saved\e[0m\n' + fi + + # Mail account password + echo -e '\e[1;34mCreate a password for your mail account\e[0m' + until [ "${MYVEMAIL_MAIL_PASS}" = "${MYVEMAIL_MAIL_PASS2}" -a "${MYVEMAIL_MAIL_PASS}" ] + do + read -s -r -p 'Mail password: ' MYVEMAIL_MAIL_PASS + read -s -r -p $'\nVerify mail password: ' MYVEMAIL_MAIL_PASS2 + if [ -z "${MYVEMAIL_MAIL_PASS}" ] + then + echo -e '\n\n\e[1;31mPassword field cannot be empty, try again\e[0m' + elif [ "${MYVEMAIL_MAIL_PASS}" != "${MYVEMAIL_MAIL_PASS2}" ] + echo -e '\n\n\e[1;31mPasswords did not match, try again\e[0m' + then + fi + done + echo -e '\n\n\e[1;32mMail password has been saved\e[0m\n' + + # Catch-all alias + until [[ ${MYVEMAIL_USER_CATCHALL} = [yYnN] ]] + do + echo -n -e '\e[1;34mWill the user use a catch-all alias?\e[0m ' + read -n 1 -r MYVEMAIL_USER_CATCHALL + echo + echo + if ! [[ ${MYVEMAIL_USER_CATCHALL} = [yYnN] ]] + then + echo -e '\e[1;31mNot a valid answer, type "y" or "n"\e[0m' + fi + done + + # Create Postfixadmin mail users + bash /usr/share/webapps/postfixadmin/scripts/postfixadmin-cli mailbox add "${MYVEMAIL_MAIL_USER}@${MYVEMAIL_ADDUSER_DOMAIN}" --active --password "${MYVEMAIL_MAIL_PASS}" --password2 "${MYVEMAIL_MAIL_PASS}" -q + + # Create Postfixadmin mail catch-all alias + if [[ ${MYVEMAIL_USER_CATCHALL} = [yY] ]] + then + bash /usr/share/webapps/postfixadmin/scripts/postfixadmin-cli alias add "*@${MYVEMAIL_ADDUSER_DOMAIN}" --goto "${MYVEMAIL_MAIL_USER}@${MYVEMAIL_ADDUSER_DOMAIN}" --active -q + fi +else + echo "Postfixadmin database does not seem to be installed" + echo "Run setup script before proceeding" + exit 1 +fi diff --git a/build/run/bin/install-mariadb b/build/run/bin/install-mariadb new file mode 100755 index 0000000..42ea547 --- /dev/null +++ b/build/run/bin/install-mariadb @@ -0,0 +1,24 @@ +#!/usr/bin/env bash +# Exit if any errors pop up +set -e + +# Install mariadb +rm -r /var/lib/mysql/* -f +mariadb-install-db --user=root --datadir=/var/lib/mysql/ + +# Wait for mariadb start +mariadbd-safe --user=root --datadir=/var/lib/mysql/ & +until mariadb --user=root --database=mysql -e "show tables;" >/dev/null +do + sleep 1 +done + +# Secure installation +printf '%s\n' "" "n" "n" "" "" "" "" | mariadb-secure-installation + +# Shutdown mariadb +mariadb --user=root -e "shutdown;" +until ! mariadb --user=root --database=mysql -e "show tables;" >/dev/null +do + sleep 1 +done diff --git a/build/run/bin/list-dkim b/build/run/bin/list-dkim new file mode 100755 index 0000000..45603db --- /dev/null +++ b/build/run/bin/list-dkim @@ -0,0 +1,10 @@ +#!/usr/bin/env bash +for domain in /etc/opendkim/keys/*/default.txt +do + # In your DNS manager, create a TXT record, enter default._domainkey in the name field + echo -e "\n\e[1;34mUpdate DKIM TXT on DNS registrar and press any key to continue\e[5m...\e[0m" + echo -e "\e[3m# Use default._domainkey in the host field" + echo -e "# Check with 'opendkim-testkey -d ${domain} -s default'" + echo -e "# Or visit https://www.dmarcanalyzer.com/dkim/dkim-checker/\e[0m" + cat ${domain} | sed 's/.*( //' | sed 's/ ).*//' | sed 's/"//g' | sed 's/^[ \t]*//g' | sed ':a;N;$!ba;s/\n//g' +done diff --git a/build/run/bin/setup b/build/run/bin/setup new file mode 100755 index 0000000..4cc789d --- /dev/null +++ b/build/run/bin/setup @@ -0,0 +1,191 @@ +#!/usr/bin/env bash +# Exit if any errors pop up +set -e + +# Exit if setup has already been run +if [ -d /var/lib/mysql/${MYVEMAIL_POSTFIXADMIN_DBNAME}/ ] +then + echo "Setup appears to have already been completed, exiting..." + exit 1 +fi + +# Function to wait for mariadb to be ready +function wait_for_mariadb_start +{ + until mariadb --user=root --database=mysql -e "show tables;" >/dev/null + do + sleep 1 + done +} +# Function to wait for mariadb to fully exit +function wait_for_mariadb_stop +{ + mariadb --user=root -e "shutdown;" + until ! mariadb --user=root --database=mysql -e "show tables;" >/dev/null + do + sleep 1 + done +} + +# Install mariadb if it isn't already installed +if [ ! -d /var/lib/mysql/mysql/ ] +then + /usr/local/bin/install-mariadb +fi + +# Start mariadb server +mariadbd --user=root --datadir=/var/lib/mysql/ & +wait_for_mariadb_start + +if [ ! -d /var/lib/mysql/${MYVEMAIL_POSTFIXADMIN_DBNAME}/ ] +then + # Postfixadmin database + mariadb --user=root <<- POSTFIXADMIN + CREATE DATABASE ${MYVEMAIL_POSTFIXADMIN_DBNAME}; + GRANT ALL PRIVILEGES ON ${MYVEMAIL_POSTFIXADMIN_DBNAME}.* to '${MYVEMAIL_POSTFIXADMIN_DBUSER}'@'localhost' IDENTIFIED BY '${MYVEMAIL_POSTFIXADMIN_DBPASS}'; + flush privileges; + POSTFIXADMIN +fi + +if ! mariadb --user=root --database=${MYVEMAIL_POSTFIXADMIN_DBNAME} -e "select * from domain;" | grep -q ${MYVEMAIL_DOMAIN} +then + +# Mail username +if [ -z ${MYVEMAIL_MAIL_USER} ] +then + echo -e '\n\e[1;34mType in your email username\e[0m' + until [ ${MYVEMAIL_MAIL_USER} ] + do + read -r -p 'Username: ' MYVEMAIL_MAIL_USER + [ ${MYVEMAIL_MAIL_USER} ] || echo -e '\n\e[1;31mUsername cannot be empty, try again\e[0m' + done + echo -e '\n\e[1;32mMail user '${MYVEMAIL_MAIL_USER}'@'${MYVEMAIL_DOMAIN}' has been saved\e[0m\n' +fi + +# Mail account password +echo -e '\e[1;34mCreate a password for your mail account\e[0m' +until [ "${MYVEMAIL_MAIL_PASS}" = "${MYVEMAIL_MAIL_PASS2}" -a "${MYVEMAIL_MAIL_PASS}" ] +do + read -s -r -p 'Mail password: ' MYVEMAIL_MAIL_PASS + read -s -r -p $'\nVerify mail password: ' MYVEMAIL_MAIL_PASS2 + if [ -z "${MYVEMAIL_MAIL_PASS}" ] + then + echo -e '\n\n\e[1;31mPassword field cannot be empty, try again\e[0m' + elif [ "${MYVEMAIL_MAIL_PASS}" != "${MYVEMAIL_MAIL_PASS2}" ] + then + echo -e '\n\n\e[1;31mPasswords did not match, try again\e[0m' + fi +done +echo -e '\n\n\e[1;32mMail password has been saved\e[0m\n' + +# Postfixadmin password +echo -e '\e[1;34mCreate a postfixadmin setup password\e[0m' +until [ "${MYVEMAIL_POSTFIXADMIN_PASS}" = "${MYVEMAIL_POSTFIXADMIN_PASS2}" -a "${MYVEMAIL_POSTFIXADMIN_PASS}" ] +do + read -s -r -p 'Postfixadmin password: ' MYVEMAIL_POSTFIXADMIN_PASS + read -s -r -p $'\nVerify Postfixadmin password: ' MYVEMAIL_POSTFIXADMIN_PASS2 + if [ -z "${MYVEMAIL_POSTFIXADMIN_PASS}" ] + then + echo -e '\n\n\e[1;31mPassword field cannot be empty, try again\e[0m' + elif [ "${MYVEMAIL_POSTFIXADMIN_PASS}" != "${MYVEMAIL_POSTFIXADMIN_PASS2}" ] + then + echo -e '\n\n\e[1;31mPasswords did not match, try again\e[0m' + fi +done +echo -e '\n\n\e[1;32mPostfixadmin password has been saved\e[0m\n' + +# Install roundcube and postfixadmin if not already installed +if [ ! -d /usr/share/webapps/roundcube ] || [ ! -d /usr/share/webapps/postfixadmin ] +then + mkdir -p /usr/share/webapps/{roundcube,postfixadmin} + wget -q4 https://github.com/postfixadmin/postfixadmin/archive/refs/tags/$(wget -q4O- https://api.github.com/repos/postfixadmin/postfixadmin/releases/latest | grep tag_name | awk '{print $2}' | tr -d '"|,').tar.gz -O postfixadmin.tar.gz + wget -q4 $(wget -q4O- https://api.github.com/repos/roundcube/roundcubemail/releases/latest | grep 'complete.tar.gz"$' | awk '{print $2}' | tr -d '"|,') -O roundcubemail.tar.gz + tar zxf roundcubemail.tar.gz -C /usr/share/webapps/roundcube --strip-components 1 + tar zxf postfixadmin.tar.gz -C /usr/share/webapps/postfixadmin --strip-components 1 + + # Postfixadmin + mkdir /usr/share/webapps/postfixadmin/templates_c/ + + # Roundcube password plugin + sed -e "/^\$config\['password_query'\]/ s/=.*/= 'UPDATE mailbox SET password=%P,modified=NOW() WHERE username=%u';/" \ + -e "/^\$config\['password_algorithm'\]/ s/=.*/= 'dovecot';/" \ + -e "/^\$config\['password_dovecotpw'\]/ s|=.*|= '/usr/bin/doveadm pw -r 5';|" \ + -e "/^\$config\['password_dovecotpw_method'\]/ s/=.*/= 'ARGON2I';/" \ + -e "/^\$config\['password_dovecotpw_with_method'\]/ s/=.*/= true;/" \ + /usr/share/webapps/roundcube/plugins/password/config.inc.php.dist >/usr/share/webapps/roundcube/plugins/password/config.inc.php + + # Cleanup + rm *.tar.gz /usr/share/webapps/roundcube/installer/ -r +fi + +# Postfixadmin setup +echo -e ' '\''postmaster@'${MYVEMAIL_DOMAIN}''\'', + '\''eff'\'' => '\''postmaster@'${MYVEMAIL_DOMAIN}''\'', + '\''dmarc'\'' => '\''postmaster@'${MYVEMAIL_DOMAIN}''\'', +); + +$CONF['\''password_validation'\''] = array( +# # '\''/regular expression/'\'' => '\''$PALANG key (optional: + parameter)'\'', +# '\''/.{5}/'\'' => '\''password_too_short 5'\'', # minimum length 5 characters +# '\''/([a-zA-Z].*){3}/'\'' => '\''password_no_characters 3'\'', # must contain at least 3 characters +# '\''/([0-9].*){2}/'\'' => '\''password_no_digits 2'\'', # must contain at least 2 digits +); + +$CONF['\''fetchmail'\''] = '\''NO'\''; +$CONF['\''show_footer_text'\''] = '\''NO'\''; + +$CONF['\''quota'\''] = '\''YES'\''; +$CONF['\''domain_quota'\''] = '\''YES'\''; +$CONF['\''quota_multiplier'\''] = '\''1024000'\''; +$CONF['\''used_quotas'\''] = '\''YES'\''; +$CONF['\''new_quota_table'\''] = '\''YES'\''; + +$CONF['\''aliases'\''] = '\''0'\''; +$CONF['\''mailboxes'\''] = '\''0'\''; +$CONF['\''maxquota'\''] = '\''0'\''; +$CONF['\''domain_quota_default'\''] = '\''0'\''; +$CONF['\''password_expiration'\''] = '\''NO'\''; + +# Postfixadmin hash +$CONF['\''setup_password'\''] = '\'$(php -r "echo password_hash('${MYVEMAIL_POSTFIXADMIN_PASS}', PASSWORD_DEFAULT);")\'';' | tee /usr/share/webapps/postfixadmin/config.local.php >/dev/null + +# Update Postfixadmin databases +# https://git.banananet.work/banananetwork/postfixadmin/raw/commit/864065cd37ef34b6dab915206eea4bd2ac4ebaed/config.inc.php +su -s /bin/bash ${MYVEMAIL_NGINX_USERGROUP} -c "php /usr/share/webapps/postfixadmin/public/upgrade.php" + +# Create Postfixadmin domain +bash /usr/share/webapps/postfixadmin/scripts/postfixadmin-cli domain add "${MYVEMAIL_DOMAIN}" --aliases 0 --mailboxes 0 --maxquota 0 --quota 0 --active --default-aliases -q + +# Create Postfixadmin admin +bash /usr/share/webapps/postfixadmin/scripts/postfixadmin-cli admin add "postmaster@${MYVEMAIL_DOMAIN}" --superadmin --active --domains "${MYVEMAIL_DOMAIN}" --password "${MYVEMAIL_POSTFIXADMIN_PASS}" --password2 "${MYVEMAIL_POSTFIXADMIN_PASS}" -q + +# Create Postfixadmin mail users +bash /usr/share/webapps/postfixadmin/scripts/postfixadmin-cli mailbox add "postmaster@${MYVEMAIL_DOMAIN}" --active --password "${MYVEMAIL_POSTFIXADMIN_PASS}" --password2 "${MYVEMAIL_POSTFIXADMIN_PASS}" -q +bash /usr/share/webapps/postfixadmin/scripts/postfixadmin-cli mailbox add "${MYVEMAIL_MAIL_USER}@${MYVEMAIL_DOMAIN}" --active --password "${MYVEMAIL_MAIL_PASS}" --password2 "${MYVEMAIL_MAIL_PASS}" -q + +# Create Postfixadmin mail catch-all alias +bash /usr/share/webapps/postfixadmin/scripts/postfixadmin-cli alias add "*@${MYVEMAIL_DOMAIN}" --goto "${MYVEMAIL_MAIL_USER}@${MYVEMAIL_DOMAIN}" --active -q + +# Add extra domains to Postfixadmin +domains+=(${MYVEMAIL_ADDMX//,/ }) +for domain in ${domains[@]} +do + bash /usr/share/webapps/postfixadmin/scripts/postfixadmin-cli domain add "${domain}" --aliases 0 --mailboxes 0 --maxquota 0 --quota 0 --active --default-aliases -q >/dev/null +done + +fi diff --git a/build/run/bin/upgrade-roundcube b/build/run/bin/upgrade-roundcube new file mode 100755 index 0000000..d1ee5a5 --- /dev/null +++ b/build/run/bin/upgrade-roundcube @@ -0,0 +1,17 @@ +#!/usr/bin/env bash +# Installer for updating Roundcube +set -e + +# Temporary work directory +workdir=$(mktemp -d) + +# Download and update +cd ${workdir} +wget -q4 $(wget -q4O- https://api.github.com/repos/roundcube/roundcubemail/releases/latest | grep 'complete.tar.gz"$' | awk '{print $2}' | tr -d '"|,') -O roundcubemail.tar.gz +mkdir ./roundcube +tar zxf roundcubemail.tar.gz -C ./roundcube --strip-components 1 +./roundcube/bin/installto.sh /usr/share/webapps/roundcube/ +cd + +# Cleanup +rm -r ${workdir} -f diff --git a/build/run/docker-entrypoint/entrypoint.sh b/build/run/docker-entrypoint/entrypoint.sh new file mode 100755 index 0000000..a2bdc26 --- /dev/null +++ b/build/run/docker-entrypoint/entrypoint.sh @@ -0,0 +1,39 @@ +#!/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 logs +echo | tee /var/log/dovecot.log /var/log/postfix.log /usr/share/webapps/roundcube/logs/errors.log >/dev/null + +# Run all scripts in init folder +for file in /docker-entrypoint/init.d/*.sh +do + bash -c ${file} || die ${file} +done + +# Reload services +dovecot reload && +postfix reload && +echo -e "\n\e[1;32mMail service is ready\e[0m\n" + +# Monitor log +tail -f /var/log/dovecot.log /var/log/postfix.log /usr/share/webapps/roundcube/logs/errors.log diff --git a/build/run/docker-entrypoint/init.d/00-mariadb.sh b/build/run/docker-entrypoint/init.d/00-mariadb.sh new file mode 100755 index 0000000..cb49315 --- /dev/null +++ b/build/run/docker-entrypoint/init.d/00-mariadb.sh @@ -0,0 +1,64 @@ +#!/usr/bin/env bash +# Exit if any errors pop up +set -e + +# Remove sock +rm -f /run/mysqld/mysqld.sock + +# Abort if setup hasn't been completed yet +if [ ! -d /var/lib/mysql/mysql/ ] +then + echo "MariaDB does not appear to be properly installed. Exiting..." + exit 1 +fi + +# Function to wait for temporary mariadb to be ready +function wait_for_mariadb_start +{ + # Start mariadb + mariadbd-safe --user=root --datadir=/var/lib/mysql/ & + until mariadb --user=root --database=mysql -e "show tables;" >/dev/null + do + sleep 1 + done +} +# Function to wait for mariadb to fully exit +function wait_for_mariadb_stop +{ + mariadb --user=root -e "shutdown;" + until ! mariadb --user=root --database=mysql -e "show tables;" >/dev/null + do + sleep 1 + done +} + +# Roundcube database +if [ ! -d /var/lib/mysql/${MYVEMAIL_ROUNDCUBE_DBNAME}/ ] +then + wait_for_mariadb_start + + mariadb -u root <<- ROUNDCUBE + CREATE DATABASE ${MYVEMAIL_ROUNDCUBE_DBNAME} CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci; + GRANT ALL PRIVILEGES ON ${MYVEMAIL_ROUNDCUBE_DBNAME}.* TO '${MYVEMAIL_ROUNDCUBE_DBUSER}'@'localhost' IDENTIFIED BY '${MYVEMAIL_ROUNDCUBE_DBPASS}'; + flush privileges; + ROUNDCUBE + mariadb ${MYVEMAIL_ROUNDCUBE_DBNAME} /dev/null + echo "default._domainkey.${domain} ${domain}:default:/etc/opendkim/keys/${domain}/default.private" | tee -a /etc/opendkim/KeyTable >/dev/null + echo "*.${domain}" | tee -a /etc/opendkim/trusted.hosts >/dev/null + + # Generate DKIM key + if [ ! -f /etc/opendkim/keys/${domain}/default.private ] + then + mkdir -p /etc/opendkim/keys/${domain} + opendkim-genkey -b 2048 -d ${domain} -D /etc/opendkim/keys/${domain} -s default + + # In your DNS manager, create a TXT record, enter default._domainkey in the name field + echo -e "\n\e[1;34mUpdate DKIM TXT on DNS registrar and press any key to continue\e[5m...\e[0m" + echo -e "\e[3m# Use default._domainkey in the host field" + echo -e "# Check with 'opendkim-testkey -d ${domain} -s default'" + echo -e "# Or visit https://www.dmarcanalyzer.com/dkim/dkim-checker/\e[0m" + cat /etc/opendkim/keys/${domain}/default.txt | sed 's/.*( //' | sed 's/ ).*//' | sed 's/"//g' | sed 's/^[ \t]*//g' | sed ':a;N;$!ba;s/\n//g' + fi +done + +# OpenDMARC +sed -i "s/{{HOSTNAME}}/${MYVEMAIL_SUBDOMAIN}.${MYVEMAIL_DOMAIN}/" ${MYVEMAIL_OPENDMARC_CONF} + +# Permissions +chown opendkim:opendkim /etc/opendkim/keys/*/default.private +chmod 600 /etc/opendkim/keys/*/default.private + +# Start services +opendkim -x ${MYVEMAIL_OPENDKIM_CONF} -p /var/spool/postfix/opendkim/opendkim.sock +opendmarc -c ${MYVEMAIL_OPENDMARC_CONF} -p /var/spool/postfix/opendmarc/opendmarc.sock diff --git a/build/run/docker-entrypoint/init.d/20-nginx.sh b/build/run/docker-entrypoint/init.d/20-nginx.sh new file mode 100755 index 0000000..cad261b --- /dev/null +++ b/build/run/docker-entrypoint/init.d/20-nginx.sh @@ -0,0 +1,57 @@ +#!/usr/bin/env bash +# Roundcube +if [ ! -f /usr/share/webapps/roundcube/config/config.inc.php ] +then + +echo "/dev/null + +# Password plugin +roundcubepass_strength_drive=$(cat /dev/urandom | tr -d -c 'a-z' | fold -w 8 | head -n 1) +sed -e "/^\$config\['password_db_dsn'\]/ s|=.*|= 'mysql://${MYVEMAIL_POSTFIXADMIN_DBUSER}:${MYVEMAIL_POSTFIXADMIN_DBPASS}@localhost/${MYVEMAIL_POSTFIXADMIN_DBNAME}';|" \ + -e "/^\$config\['password_strength_driver'\]/ s/=.*/= '${roundcubepass_strength_drive}';\\ +\$config['password_"${roundcubepass_strength_drive}"_min_score'] = 5;/" \ + -i /usr/share/webapps/roundcube/plugins/password/config.inc.php + +fi + +# Permissions +setfacl -R -m u:${MYVEMAIL_NGINX_USERGROUP}:rwx /usr/share/webapps/postfixadmin/templates_c/ +chown ${MYVEMAIL_NGINX_USERGROUP}:${MYVEMAIL_NGINX_USERGROUP} /usr/share/webapps/roundcube/{temp,logs}/ -R +chown ${MYVEMAIL_NGINX_USERGROUP}:${MYVEMAIL_NGINX_USERGROUP} /usr/share/webapps/roundcube/plugins/password/config.inc.php +chmod 0600 /usr/share/webapps/roundcube/plugins/password/config.inc.php + +# Start services +/usr/sbin/php-fpm* -D +nginx + +# <<- ##appendix +# \$config['imap_conn_options'] = array( +# 'ssl' => array( +# 'verify_peer' => true, +# 'verify_peer_name' => true, +# 'allow_self_signed' => true, +# ), +# ); +# +# \$config['smtp_conn_options'] = array( +# 'ssl' => array( +# 'verify_peer' => true, +# 'verify_peer_name' => true, +# 'allow_self_signed' => true, +# ), +# ); +# ##appendix diff --git a/build/run/docker-entrypoint/init.d/25-dovecot.sh b/build/run/docker-entrypoint/init.d/25-dovecot.sh new file mode 100755 index 0000000..0816691 --- /dev/null +++ b/build/run/docker-entrypoint/init.d/25-dovecot.sh @@ -0,0 +1,15 @@ +#!/usr/bin/env bash +# 10-auth.conf +sed -i "s/{{MYVEMAIL_DOMAIN}}/${MYVEMAIL_DOMAIN}/" /etc/dovecot/conf.d/10-auth.conf + +# dovecot-sql.conf.ext +sed -e "s/{{MYVEMAIL_POSTFIXADMIN_DBNAME}}/${MYVEMAIL_POSTFIXADMIN_DBNAME}/" \ + -e "s/{{MYVEMAIL_POSTFIXADMIN_DBUSER}}/${MYVEMAIL_POSTFIXADMIN_DBUSER}/" \ + -e "s/{{MYVEMAIL_POSTFIXADMIN_DBPASS}}/${MYVEMAIL_POSTFIXADMIN_DBPASS}/" \ + -i /etc/dovecot/dovecot-sql.conf.ext + +# Permissions +chown vmail:vmail /var/vmail/ -R + +# Start dovecot +dovecot diff --git a/build/run/docker-entrypoint/init.d/30-postfix.sh b/build/run/docker-entrypoint/init.d/30-postfix.sh new file mode 100755 index 0000000..809301b --- /dev/null +++ b/build/run/docker-entrypoint/init.d/30-postfix.sh @@ -0,0 +1,48 @@ +#!/usr/bin/env bash +# Postfix +echo ${MYVEMAIL_DOMAIN} >/etc/mailname +postconf -e "myhostname = ${MYVEMAIL_SUBDOMAIN}.${MYVEMAIL_DOMAIN}" +postconf -e "mydomain = ${MYVEMAIL_DOMAIN}" + +# resolv.conf +mkdir /var/spool/postfix/etc +cp /etc/resolv.conf /var/spool/postfix/etc/resolv.conf + +# Whitelist localhost +sed -i "s/{{LOCAL_IPADDRESS}}/$(wget -q4O- ipv4.icanhazip.com)/" /etc/postfix/postscreen_access.cidr + +# Configure backup mail servers +if [ ${MYVEMAIL_BACKUPMX} ] +then + backupmx+=(${MYVEMAIL_BACKUPMX//,/ }) + + postconf -e "$(postconf mynetworks)$(printf ' %s/32' ${backupmx[@]})" + postconf -e "smtp_fallback_relay =$(printf ' [%s]:25' ${backupmx[@]})" + + # Whitelist + for domain in ${backupmx[@]} + do + echo "${domain}/32 permit" >>/etc/postfix/postscreen_access.cidr + done +fi + +# Whitelist +addmx=(${MYVEMAIL_DOMAIN}) +addmx+=(${MYVEMAIL_ADDMX//,/ }) +for domain in ${addmx[@]} +do + echo "${domain} OK" | tee -a /etc/postfix/{helo_access,rbl_override} >/dev/null +done + +# Virtual mailboxes +sed -e "s/{{MYVEMAIL_POSTFIXADMIN_DBNAME}}/${MYVEMAIL_POSTFIXADMIN_DBNAME}/" \ + -e "s/{{MYVEMAIL_POSTFIXADMIN_DBUSER}}/${MYVEMAIL_POSTFIXADMIN_DBUSER}/" \ + -e "s/{{MYVEMAIL_POSTFIXADMIN_DBPASS}}/${MYVEMAIL_POSTFIXADMIN_DBPASS}/" \ + -i /etc/postfix/sql/*.cf + +# Permissions +setfacl -R -m u:postfix:rx /etc/postfix/sql/ + +# Start postfix +postfix start +postmap /etc/postfix/helo_access /etc/postfix/rbl_override /etc/postfix/smtp_header_checks /etc/postfix/header_checks /etc/postfix/body_checks /etc/postfix/postscreen_access.cidr diff --git a/build/run/docker-entrypoint/init.d/50-cron.sh b/build/run/docker-entrypoint/init.d/50-cron.sh new file mode 100755 index 0000000..0ed133e --- /dev/null +++ b/build/run/docker-entrypoint/init.d/50-cron.sh @@ -0,0 +1,34 @@ +#!/usr/bin/env bash + +# Update Postscreen Whitelists (daily) +while true +do + sleep 1d + /usr/local/bin/postwhite/postwhite +done & + +# Update Yahoo! IPs for Postscreen Whitelists (weekly) +while true +do + sleep 7d + /usr/local/bin/postwhite/scrape_yahoo +done & + +# Roundcube cleanup (daily) +while true +do + sleep 1d + /usr/share/webapps/roundcube/bin/cleandb.sh +done & + +# # Refresh ssl keys daily +# # https://www.golinuxcloud.com/renew-self-signed-certificate-openssl/ +# while true +# do +# sleep 1d +# openssl x509 -x509toreq -in /etc/ssl/dovecot/tls.pem -signkey /etc/ssl/dovecot/tls.key -out /tmp/new-certificate-sign-request.csr +# openssl x509 -req -days 3650 -in /tmp/new-certificate-sign-request.csr -signkey /etc/ssl/dovecot/tls.key -out /etc/ssl/dovecot/tls.pem +# rm /tmp/new-certificate-sign-request.csr +# dovecot reload +# postfix reload +# done & diff --git a/build/run/docker-entrypoint/init.d/60-postwhite.sh b/build/run/docker-entrypoint/init.d/60-postwhite.sh new file mode 100755 index 0000000..f1c6eea --- /dev/null +++ b/build/run/docker-entrypoint/init.d/60-postwhite.sh @@ -0,0 +1,9 @@ +#!/usr/bin/env bash +# Install postwhite +if [ ! -s /etc/postfix/postscreen_spf_whitelist.cidr ] +then + /usr/local/bin/postwhite/postwhite +fi + +# Permissions +chown root:root /etc/postfix/postscreen_spf_whitelist.cidr diff --git a/build/run/installer-alpine.sh b/build/run/installer-alpine.sh new file mode 100755 index 0000000..276a854 --- /dev/null +++ b/build/run/installer-alpine.sh @@ -0,0 +1,509 @@ +#!/usr/bin/env bash + +############### +#// +#// Alpine specific +#// +############### +# Pre-create vmail user +addgroup -g 600 vmail +adduser -S -D -h /var/vmail -u 600 -G vmail vmail + +# Update and install +apk add --no-cache \ + nginx \ + mariadb mariadb-client \ + ca-certificates acl \ + git wget bind-tools \ + postfix postfix-mysql postfix-pcre \ + dovecot dovecot-mysql dovecot-lmtpd \ + postfix-policyd-spf-perl opendkim opendkim-utils \ + opendmarc \ + php php-fpm php-imap php-mbstring php-mysqli php-curl php-zip php-xml php-bz2 php-intl php-gmp php-ldap php-common php-gd php-sqlite3 \ + php-session php-pdo_mysql php-dom php-ctype + +# Nginx +sed '/^http {/a\ \ + types_hash_max_size 4096;\ \ + server_names_hash_bucket_size 128;\n' -i /etc/nginx/nginx.conf + +############### +#// +#// Users and directories +#// +############### +# Add users to groups +adduser dovecot mail >/dev/null +adduser ${MYVEMAIL_NGINX_USERGROUP} dovecot >/dev/null +adduser postfix opendkim >/dev/null + +# Virtual mailboxes +mkdir /etc/postfix/sql + +# MariaDB +install -m 1777 -d /run/mysqld + +# DKIM/DMARC +mkdir /{run,etc}/{opendkim,opendmarc} +install -o opendkim -g postfix -d /var/spool/postfix/opendkim/ +install -m 750 -o opendmarc -g postfix -d /var/spool/postfix/opendmarc/ + +############### +#// +#// PHP +#// +############### +sed -e '/upload_max_filesize =/c upload_max_filesize = 0' \ + -e '/post_max_size =/c post_max_size = 0' \ + -e '/memory_limit =/c memory_limit = 4096M' \ + -i $(find /etc/php*/ -name 'php.ini') + +############### +#// +#// Postfix +#// +############### +postconf -e 'relay_domains = $mydestination' +postconf -e 'smtp_tls_security_level = may' +postconf -e 'smtp_tls_loglevel = 1' +postconf -e 'smtpd_tls_security_level = may' +postconf -e 'smtpd_tls_loglevel = 1' +postconf -e 'mailbox_size_limit = 0' +postconf -e 'message_size_limit = 0' +postconf -e "authorized_submit_users = root,${MYVEMAIL_NGINX_USERGROUP}" + +postconf -e 'smtpd_tls_session_cache_database = lmdb:${data_directory}/smtpd_scache' +postconf -e 'smtp_tls_session_cache_database = lmdb:${data_directory}/smtp_scache' + +postconf -e 'smtp_tls_note_starttls_offer = yes' +postconf -e 'smtpd_tls_received_header = yes' + +postconf -e 'inet_interfaces = all' +postconf -e 'inet_protocols = ipv4' +postconf -e 'smtp_address_preference = ipv4' +postconf -e 'myorigin = $mydomain' +postconf -e 'mydestination = $myhostname, localhost.$mydomain, localhost' + +postconf -e "smtp_tls_CAfile = /etc/ssl/certs/ca-certificates.crt" +postconf -e "smtpd_tls_key_file = /etc/ssl/dovecot/tls.key" +postconf -e "smtpd_tls_cert_file = /etc/ssl/dovecot/tls.pem" + +# Enforce TLSv1.2 or TLSv1.2 +postconf -e "smtpd_tls_mandatory_protocols = >=TLSv1.2" +postconf -e "smtp_tls_mandatory_protocols = >=TLSv1.2" +postconf -e "smtpd_tls_protocols = >=TLSv1.2" +postconf -e "smtp_tls_protocols = >=TLSv1.2" + +# Logging +postconf -e "maillog_file = /var/log/postfix.log" + +# Virtual mailboxes +postconf -e "mailbox_transport = lmtp:unix:private/dovecot-lmtp" +postconf -e "smtputf8_enable = no" + +postconf -e "virtual_mailbox_domains = proxy:mysql:/etc/postfix/sql/virtual_domains_maps.cf" +postconf -e "virtual_mailbox_maps = proxy:mysql:/etc/postfix/sql/virtual_mailbox_maps.cf, proxy:mysql:/etc/postfix/sql/virtual_alias_domain_mailbox_maps.cf" +postconf -e "virtual_alias_maps = proxy:mysql:/etc/postfix/sql/virtual_alias_maps.cf, proxy:mysql:/etc/postfix/sql/virtual_alias_domain_maps.cf, proxy:mysql:/etc/postfix/sql/virtual_alias_domain_catchall_maps.cf" + +postconf -e "virtual_transport = lmtp:unix:private/dovecot-lmtp" + +postconf -e "virtual_mailbox_base = /var/vmail" +postconf -e "virtual_uid_maps = static:600" +postconf -e "virtual_gid_maps = static:600" +postconf -e "virtual_minimum_uid = 600" +postconf -e "virtual_mailbox_limit = 0" + +postconf -e "smtp_header_checks = pcre:/etc/postfix/smtp_header_checks" + +# SPF and DKIM checks +postconf -e "policy_time_limit = 3600" +postconf -e "smtpd_recipient_restrictions = permit_mynetworks, permit_sasl_authenticated, reject_unauth_destination, check_policy_service unix:private/policy, check_client_access lmdb:/etc/postfix/rbl_override" + +# Milter configuration +postconf -e "milter_default_action = accept" +postconf -e "milter_protocol = 6" +postconf -e "smtpd_milters = local:opendkim/opendkim.sock, local:opendmarc/opendmarc.sock" +postconf -e 'non_smtpd_milters = $smtpd_milters' + +# Blocking spam +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" + +# Postscreen spambots +postconf -e "postscreen_access_list = permit_mynetworks cidr:/etc/postfix/postscreen_access.cidr cidr:/etc/postfix/postscreen_spf_whitelist.cidr" +postconf -e "postscreen_blacklist_action = drop" + +postconf -e "postscreen_greet_action = enforce" + +postconf -e "postscreen_dnsbl_threshold = 3" +postconf -e "postscreen_dnsbl_action = enforce" +postconf -e "postscreen_dnsbl_sites = zen.spamhaus.org*3, bl.spameatingmonkey.net*2, bl.spamcop.net, dnsbl.sorbs.net" + +postconf -e "postscreen_dnsbl_whitelist_threshold = -2" + +postconf -e "header_checks = pcre:/etc/postfix/header_checks" +postconf -e "body_checks = pcre:/etc/postfix/body_checks" + +# master.cf +sed -e 's/^smtp .*smtpd$/# &/' \ + -e '/#smtp\|#smtpd\|#dnsblog\|#tlsproxy/ s/^#//' -i /etc/postfix/master.cf +tee -a /etc/postfix/master.cf >/dev/null <<- 'master.cf' + +# Enable submission +submission inet n - y - - smtpd + -o syslog_name=postfix/submission + -o smtpd_tls_security_level=encrypt + -o smtpd_tls_wrappermode=no + -o smtpd_sasl_auth_enable=yes + -o smtpd_relay_restrictions=permit_sasl_authenticated,reject + -o smtpd_recipient_restrictions=permit_mynetworks,permit_sasl_authenticated,reject + -o smtpd_sasl_type=dovecot + -o smtpd_sasl_path=private/auth + +# SPF Policy +policy unix - n n - 0 spawn + user=nobody argv=/usr/bin/postfix-policyd-spf-perl +master.cf + +# Deleting Email Headers For Outgoing Emails +echo -e '/^X-Spam-Status:/ IGNORE +/^X-Spam-Checker-Version:/ IGNORE +/^Received:.*/ IGNORE +/^User-Agent:.*/ IGNORE' | tee /etc/postfix/smtp_header_checks >/dev/null + +# Header checks +echo "/free mortgage quote/ DISCARD +/repair your credit/ DISCARD +/lose weight/ DISCARD +/To:.*<>/ DISCARD +/From:.*<>/ DISCARD" | tee -a /etc/postfix/header_checks >/dev/null + +# Body checks +echo "/free mortgage quote/ DISCARD +/repair your credit/ DISCARD +/lose weight/ DISCARD" | tee -a /etc/postfix/body_checks >/dev/null + +# Whitelist localhost +tee /etc/postfix/postscreen_access.cidr >/dev/null <<- postscreen_access.cidr +# Permit my own IP addresses +{{LOCAL_IPADDRESS}}/32 permit +postscreen_access.cidr + +# Touch aliases db +newaliases + +############### +#// +#// Dovecot +#// +############### +# dovecot.conf +tee -a /etc/dovecot/dovecot.conf >/dev/null <<- dovecot.conf + +!include_try ssl-keys.conf +protocols = imap lmtp +dovecot.conf + +# Logging +echo "log_path = /var/log/dovecot.log" >/etc/dovecot/conf.d/10-logging.conf + +# 10-auth.conf +sed '/include auth-sql.conf.ext\|disable_plaintext_auth =\|auth_username_format =\|auth_mechanisms =\|auth_default_realm =\|include auth-system.conf.ext/d' \ + -i /etc/dovecot/conf.d/10-auth.conf +tee -a /etc/dovecot/conf.d/10-auth.conf >/dev/null <<- '10-auth.conf' + +!include auth-sql.conf.ext +disable_plaintext_auth = yes +auth_username_format = %Lu +auth_mechanisms = plain login +auth_default_realm = {{MYVEMAIL_DOMAIN}} + +auth_debug = yes +auth_debug_passwords = yes +10-auth.conf + +# dovecot-sql.conf.ext +tee -a /etc/dovecot/dovecot-sql.conf.ext >/dev/null <<- 'dovecot' + +# Virtual mailboxes +driver = mysql +connect = host=localhost dbname={{MYVEMAIL_POSTFIXADMIN_DBNAME}} user={{MYVEMAIL_POSTFIXADMIN_DBUSER}} password={{MYVEMAIL_POSTFIXADMIN_DBPASS}} +default_pass_scheme = ARGON2I +password_query = SELECT username AS user,password FROM mailbox WHERE username = '%u' AND active='1' +user_query = SELECT maildir, 600 AS uid, 600 AS gid FROM mailbox WHERE username = '%u' AND active='1' +iterate_query = SELECT username AS user FROM mailbox +dovecot + +# 10-mail.conf +sed '/mail_location =\|mail_home =\|mail_privileged_group =/d' \ + -i /etc/dovecot/conf.d/10-mail.conf +tee -a /etc/dovecot/conf.d/10-mail.conf >/dev/null <<- '10-mail.conf' + +mail_privileged_group = mail +mail_location = maildir:~/Maildir +mail_home = /var/vmail/%d/%n/ +10-mail.conf + +# 10-master.conf +sed -e 's|unix_listener lmtp {|unix_listener /var/spool/postfix/private/dovecot-lmtp {\ + mode = 0600\ + user = postfix\ + group = postfix|' \ + -e 's|unix_listener auth-userdb {|unix_listener /var/spool/postfix/private/auth {\ + mode = 0660\ + user = postfix\ + group = postfix|' \ + -i /etc/dovecot/conf.d/10-master.conf + +# 10-ssl.conf +rm -f /etc/ssl/dovecot/* +install -m 0600 /dev/stdin /etc/dovecot/ssl-keys.conf <<- ssl-keys.conf +ssl_dh = /dev/null <<- '10-ssl.conf' + +ssl = required +ssl_prefer_server_ciphers = yes +ssl_min_protocol = TLSv1.2 +10-ssl.conf + +# Stats service +tee -a /etc/dovecot/conf.d/10-master.conf >/dev/null <<- 10-master.conf + +service stats { + unix_listener stats-reader { + user = ${MYVEMAIL_NGINX_USERGROUP} + group = ${MYVEMAIL_NGINX_USERGROUP} + mode = 0660 +} + +unix_listener stats-writer { + user = ${MYVEMAIL_NGINX_USERGROUP} + group = ${MYVEMAIL_NGINX_USERGROUP} + mode = 0660 + } +} +10-master.conf + +# Mailboxes +sed -i 's/namespace inbox {/&\ + # Archive folder\ + mailbox Archive {\ + special_use = \\Archive\ + }/' /etc/dovecot/conf.d/15-mailboxes.conf +sed -i '/Sent Messages/! s/^ mailbox.*{/&\ + auto = subscribe/' /etc/dovecot/conf.d/15-mailboxes.conf + +# Virtual mailboxes +install -m 0640 /dev/stdin /etc/postfix/sql/virtual_domains_maps.cf <<- eof +hosts = localhost +dbname = {{MYVEMAIL_POSTFIXADMIN_DBNAME}} +user = {{MYVEMAIL_POSTFIXADMIN_DBUSER}} +password = {{MYVEMAIL_POSTFIXADMIN_DBPASS}} +query = SELECT domain FROM domain WHERE domain='%s' AND active = '1' +#query = SELECT domain FROM domain WHERE domain='%s' +#optional query to use when relaying for backup MX +#query = SELECT domain FROM domain WHERE domain='%s' AND backupmx = '0' AND active = '1' +#expansion_limit = 100 +eof + +install -m 0640 /dev/stdin /etc/postfix/sql/virtual_mailbox_maps.cf <<- eof +hosts = localhost +dbname = {{MYVEMAIL_POSTFIXADMIN_DBNAME}} +user = {{MYVEMAIL_POSTFIXADMIN_DBUSER}} +password = {{MYVEMAIL_POSTFIXADMIN_DBPASS}} +query = SELECT maildir FROM mailbox WHERE username='%s' AND active = '1' +#expansion_limit = 100 +eof + +install -m 0640 /dev/stdin /etc/postfix/sql/virtual_alias_domain_mailbox_maps.cf <<- eof +hosts = localhost +dbname = {{MYVEMAIL_POSTFIXADMIN_DBNAME}} +user = {{MYVEMAIL_POSTFIXADMIN_DBUSER}} +password = {{MYVEMAIL_POSTFIXADMIN_DBPASS}} +query = SELECT maildir FROM mailbox,alias_domain WHERE alias_domain.alias_domain = '%d' and mailbox.username = CONCAT('%u', '@', alias_domain.target_domain) AND mailbox.active = 1 AND alias_domain.active='1' +eof + +install -m 0640 /dev/stdin /etc/postfix/sql/virtual_alias_maps.cf <<- eof +hosts = localhost +dbname = {{MYVEMAIL_POSTFIXADMIN_DBNAME}} +user = {{MYVEMAIL_POSTFIXADMIN_DBUSER}} +password = {{MYVEMAIL_POSTFIXADMIN_DBPASS}} +query = SELECT goto FROM alias WHERE address='%s' AND active = '1' +#expansion_limit = 100 +eof + +install -m 0640 /dev/stdin /etc/postfix/sql/virtual_alias_domain_maps.cf <<- eof +hosts = localhost +dbname = {{MYVEMAIL_POSTFIXADMIN_DBNAME}} +user = {{MYVEMAIL_POSTFIXADMIN_DBUSER}} +password = {{MYVEMAIL_POSTFIXADMIN_DBPASS}} +query = SELECT goto FROM alias,alias_domain WHERE alias_domain.alias_domain = '%d' and alias.address = CONCAT('%u', '@', alias_domain.target_domain) AND alias.active = 1 AND alias_domain.active='1' +eof + +install -m 0640 /dev/stdin /etc/postfix/sql/virtual_alias_domain_catchall_maps.cf <<- eof +# handles catch-all settings of target-domain +hosts = localhost +dbname = {{MYVEMAIL_POSTFIXADMIN_DBNAME}} +user = {{MYVEMAIL_POSTFIXADMIN_DBUSER}} +password = {{MYVEMAIL_POSTFIXADMIN_DBPASS}} +query = SELECT goto FROM alias,alias_domain WHERE alias_domain.alias_domain = '%d' and alias.address = CONCAT('@', alias_domain.target_domain) AND alias.active = 1 AND alias_domain.active='1' +eof + +############### +#// +#// OpenDKIM +#// +############### +# sed -i 's\^SOCKET=local:$RUNDIR/opendkim.sock\SOCKET="local:/var/spool/postfix/opendkim/opendkim.sock"\' /etc/default/opendkim +sed -i "/Canonicalization\|Mode\|SubDomains\|Socket\|SoftwareHeader\|UserID\|UMask/d" ${MYVEMAIL_OPENDKIM_CONF} +tee -a ${MYVEMAIL_OPENDKIM_CONF} >/dev/null <<- 'opendkim.conf' + +Canonicalization relaxed/simple +Mode sv +SubDomains no +Socket local:/var/spool/postfix/opendkim/opendkim.sock +SoftwareHeader yes + +UserID opendkim +UMask 007 + +# Map domains in "from" addresses to keys used to sign messages +KeyTable refile:/etc/opendkim/KeyTable +SigningTable refile:/etc/opendkim/SigningTable + +# Hosts to ignore when verifying signatures +ExternalIgnoreList /etc/opendkim/trusted.hosts + +# A set of internal hosts whose mail should be signed +InternalHosts /etc/opendkim/trusted.hosts +opendkim.conf + +# trusted.hosts +cat >/etc/opendkim/trusted.hosts <<- trusted.hosts +127.0.0.1 +localhost + +trusted.hosts + +############### +#// +#// OpenDMARC +#// +############### +sed '/TrustedAuthservIDs\|AuthservID\|RejectFailures\|IgnoreAuthenticatedClients\|RequiredHeaders\|SPFSelfValidate\|Socket\|IgnoreHosts\|UMask\|UserID/d' \ + -i ${MYVEMAIL_OPENDMARC_CONF} + +tee -a ${MYVEMAIL_OPENDMARC_CONF} >/dev/null <<- 'opendmarc.conf' + +TrustedAuthservIDs {{HOSTNAME}} +AuthservID OpenDMARC +RejectFailures true +IgnoreAuthenticatedClients true +RequiredHeaders true +SPFSelfValidate true + +Socket local:/var/spool/postfix/opendmarc/opendmarc.sock +IgnoreHosts /etc/opendmarc/ignore.hosts + +UserID opendmarc +UMask 0002 +opendmarc.conf + +echo '127.0.0.1' | tee -a /etc/opendmarc/ignore.hosts >/dev/null + +############### +#// +#// Nginx +#// +############### +# php-fpm sock +cat >$(find /etc/php* -type d \( -name "pool.d" -o -name "php-fpm.d" \))/zz-listen.conf <<- php-fpm.sock +listen = 127.0.0.1:9000 +user = ${MYVEMAIL_NGINX_USERGROUP} +group = ${MYVEMAIL_NGINX_USERGROUP} +listen.owner = ${MYVEMAIL_NGINX_USERGROUP} +listen.group = ${MYVEMAIL_NGINX_USERGROUP} +listen.mode = 0660 +php-fpm.sock + +# proxy_params +tee /etc/nginx/proxy_params >/dev/null <<'PROXY' +proxy_set_header Host $http_host; +proxy_set_header X-Real-IP $remote_addr; +proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; +proxy_set_header X-Forwarded-Proto $scheme; +PROXY + +# Roundcube/Postfixadmin virtual proxy +tee $(find /etc/nginx -type f -name "default*") >/dev/null <<- 'nginx' +upstream php-handler { + server 127.0.0.1:9000; +} + +server { + listen 80 default_server; + server_name _; + + index index.php index.html; + + error_log /var/log/nginx/roundcube_error.log; + access_log /var/log/nginx/roundcube_access.log; + + root /usr/share/webapps/roundcube; + + # Postfixadmin + location ^~ /admin/ { + proxy_pass http://127.0.0.1:12000/; + include proxy_params; + } + + # Roundcube + location ~ \.php$ { + client_max_body_size 0; + + try_files $uri =404; + fastcgi_split_path_info ^(.+\.php)(/.+)$; + fastcgi_pass php-handler; + fastcgi_index index.php; + include fastcgi_params; + fastcgi_param SCRIPT_FILENAME $document_root$fastcgi_script_name; + fastcgi_param PATH_INFO $fastcgi_path_info; + } +} + +server { + listen 12000; + root /usr/share/webapps/postfixadmin/public/; + index index.php index.html; + + error_log /var/log/nginx/postfixadmin_error.log; + access_log /var/log/nginx/postfixadmin_access.log; + + location / { + try_files $uri $uri/ /index.php; + } + + location ~ ^/(.+\.php)$ { + try_files $uri =404; + fastcgi_pass php-handler; + fastcgi_index index.php; + fastcgi_param SCRIPT_FILENAME $document_root$fastcgi_script_name; + include /etc/nginx/fastcgi_params; + } +} +nginx + +############### +#// +#// Postwhite +#// +############### +cd /usr/local/bin +git clone --quiet https://github.com/spf-tools/spf-tools.git +git clone --quiet https://github.com/stevejenkins/postwhite.git +cp /usr/local/bin/postwhite/postwhite.conf /etc diff --git a/build/run/installer-ubuntu.sh b/build/run/installer-ubuntu.sh new file mode 100755 index 0000000..523560e --- /dev/null +++ b/build/run/installer-ubuntu.sh @@ -0,0 +1,511 @@ +#!/usr/bin/env bash + +############### +#// +#// Ubuntu specific +#// +############### +# Update and install +apt remove -y nano* exim* +apt update +apt upgrade -y +apt dist-upgrade -y +debconf-set-selections <<< "postfix postfix/main_mailer_type string 'Internet Site'" +apt install -y \ + ca-certificates \ + git wget bind9-host acl dbconfig-no-thanks \ + rsyslog \ + nginx \ + mariadb-server mariadb-client postfix-mysql dovecot-mysql \ + php php-fpm php-imap php-mbstring php-mysql php-json php-curl php-zip php-xml php-bz2 php-intl php-gmp php-net-ldap3 php-imagick php-common php-gd php-sqlite3 php-cli \ + postfix postfix-pcre \ + dovecot-core dovecot-imapd dovecot-lmtpd \ + postfix-policyd-spf-python opendkim opendkim-tools \ + opendmarc +apt autoremove -y +apt clean +rm -r -f /var/lib/apt/lists + +# Create vmail user +adduser vmail --system --group --uid 600 --disabled-login --home /var/vmail/ --quiet + +############### +#// +#// Users and directories +#// +############### +# Add users to groups +adduser dovecot mail >/dev/null +adduser ${MYVEMAIL_NGINX_USERGROUP} dovecot >/dev/null +adduser postfix opendkim >/dev/null + +# Virtual mailboxes +mkdir /etc/postfix/sql + +# MariaDB +install -m 1777 -d /run/mysqld + +# DKIM/DMARC +mkdir /{run,etc}/{opendkim,opendmarc} +install -o opendkim -g postfix -d /var/spool/postfix/opendkim/ +install -m 750 -o opendmarc -g postfix -d /var/spool/postfix/opendmarc/ + +############### +#// +#// PHP +#// +############### +sed -e '/upload_max_filesize =/c upload_max_filesize = 0' \ + -e '/post_max_size =/c post_max_size = 0' \ + -e '/memory_limit =/c memory_limit = 4096M' \ + -i $(find /etc/php*/ -name 'php.ini') + +############### +#// +#// Postfix +#// +############### +postconf -e 'relay_domains = $mydestination' +postconf -e 'smtp_tls_security_level = may' +postconf -e 'smtp_tls_loglevel = 1' +postconf -e 'smtpd_tls_security_level = may' +postconf -e 'smtpd_tls_loglevel = 1' +postconf -e 'mailbox_size_limit = 0' +postconf -e 'message_size_limit = 0' +postconf -e "authorized_submit_users = root,${MYVEMAIL_NGINX_USERGROUP}" + +postconf -e 'smtpd_tls_session_cache_database = lmdb:${data_directory}/smtpd_scache' +postconf -e 'smtp_tls_session_cache_database = lmdb:${data_directory}/smtp_scache' + +postconf -e 'smtp_tls_note_starttls_offer = yes' +postconf -e 'smtpd_tls_received_header = yes' + +postconf -e 'inet_interfaces = all' +postconf -e 'inet_protocols = ipv4' +postconf -e 'smtp_address_preference = ipv4' +postconf -e 'myorigin = $mydomain' +postconf -e 'mydestination = $myhostname, localhost.$mydomain, localhost' + +postconf -e "smtp_tls_CAfile = /etc/ssl/certs/ca-certificates.crt" +postconf -e "smtpd_tls_key_file = /etc/ssl/dovecot/tls.key" +postconf -e "smtpd_tls_cert_file = /etc/ssl/dovecot/tls.pem" + +# Enforce TLSv1.2 or TLSv1.2 +postconf -e "smtpd_tls_mandatory_protocols = >=TLSv1.2" +postconf -e "smtp_tls_mandatory_protocols = >=TLSv1.2" +postconf -e "smtpd_tls_protocols = >=TLSv1.2" +postconf -e "smtp_tls_protocols = >=TLSv1.2" + +# Logging +postconf -e "maillog_file = /var/log/postfix.log" + +# Virtual mailboxes +postconf -e "mailbox_transport = lmtp:unix:private/dovecot-lmtp" +postconf -e "smtputf8_enable = no" + +postconf -e "virtual_mailbox_domains = proxy:mysql:/etc/postfix/sql/virtual_domains_maps.cf" +postconf -e "virtual_mailbox_maps = proxy:mysql:/etc/postfix/sql/virtual_mailbox_maps.cf, proxy:mysql:/etc/postfix/sql/virtual_alias_domain_mailbox_maps.cf" +postconf -e "virtual_alias_maps = proxy:mysql:/etc/postfix/sql/virtual_alias_maps.cf, proxy:mysql:/etc/postfix/sql/virtual_alias_domain_maps.cf, proxy:mysql:/etc/postfix/sql/virtual_alias_domain_catchall_maps.cf" + +postconf -e "virtual_transport = lmtp:unix:private/dovecot-lmtp" + +postconf -e "virtual_mailbox_base = /var/vmail" +postconf -e "virtual_uid_maps = static:600" +postconf -e "virtual_gid_maps = static:600" +postconf -e "virtual_minimum_uid = 600" +postconf -e "virtual_mailbox_limit = 0" + +postconf -e "smtp_header_checks = pcre:/etc/postfix/smtp_header_checks" + +# SPF and DKIM checks +postconf -e "policy_time_limit = 3600" +postconf -e "smtpd_recipient_restrictions = permit_mynetworks, permit_sasl_authenticated, reject_unauth_destination, check_policy_service unix:private/policy, check_client_access lmdb:/etc/postfix/rbl_override" + +# Milter configuration +postconf -e "milter_default_action = accept" +postconf -e "milter_protocol = 6" +postconf -e "smtpd_milters = local:opendkim/opendkim.sock, local:opendmarc/opendmarc.sock" +postconf -e 'non_smtpd_milters = $smtpd_milters' + +# Blocking spam +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" + +# Postscreen spambots +postconf -e "postscreen_access_list = permit_mynetworks cidr:/etc/postfix/postscreen_access.cidr cidr:/etc/postfix/postscreen_spf_whitelist.cidr" +postconf -e "postscreen_blacklist_action = drop" + +postconf -e "postscreen_greet_action = enforce" + +postconf -e "postscreen_dnsbl_threshold = 3" +postconf -e "postscreen_dnsbl_action = enforce" +postconf -e "postscreen_dnsbl_sites = zen.spamhaus.org*3, bl.spameatingmonkey.net*2, bl.spamcop.net, dnsbl.sorbs.net" + +postconf -e "postscreen_dnsbl_whitelist_threshold = -2" + +postconf -e "header_checks = pcre:/etc/postfix/header_checks" +postconf -e "body_checks = pcre:/etc/postfix/body_checks" + +# master.cf +sed -e 's/^smtp .*smtpd$/# &/' \ + -e '/#smtp\|#smtpd\|#dnsblog\|#tlsproxy/ s/^#//' -i /etc/postfix/master.cf +tee -a /etc/postfix/master.cf >/dev/null <<- 'master.cf' + +# Enable submission +submission inet n - y - - smtpd + -o syslog_name=postfix/submission + -o smtpd_tls_security_level=encrypt + -o smtpd_tls_wrappermode=no + -o smtpd_sasl_auth_enable=yes + -o smtpd_relay_restrictions=permit_sasl_authenticated,reject + -o smtpd_recipient_restrictions=permit_mynetworks,permit_sasl_authenticated,reject + -o smtpd_sasl_type=dovecot + -o smtpd_sasl_path=private/auth + +# SPF Policy +policy unix - n n - 0 spawn + user=nobody argv=/usr/bin/postfix-policyd-spf-perl +master.cf + +# Deleting Email Headers For Outgoing Emails +echo -e '/^X-Spam-Status:/ IGNORE +/^X-Spam-Checker-Version:/ IGNORE +/^Received:.*/ IGNORE +/^User-Agent:.*/ IGNORE' | tee /etc/postfix/smtp_header_checks >/dev/null + +# Header checks +echo "/free mortgage quote/ DISCARD +/repair your credit/ DISCARD +/lose weight/ DISCARD +/To:.*<>/ DISCARD +/From:.*<>/ DISCARD" | tee -a /etc/postfix/header_checks >/dev/null + +# Body checks +echo "/free mortgage quote/ DISCARD +/repair your credit/ DISCARD +/lose weight/ DISCARD" | tee -a /etc/postfix/body_checks >/dev/null + +# Whitelist localhost +tee /etc/postfix/postscreen_access.cidr >/dev/null <<- postscreen_access.cidr +# Permit my own IP addresses +{{LOCAL_IPADDRESS}}/32 permit +postscreen_access.cidr + +# Touch aliases db +newaliases + +############### +#// +#// Dovecot +#// +############### +# dovecot.conf +tee -a /etc/dovecot/dovecot.conf >/dev/null <<- dovecot.conf + +!include_try ssl-keys.conf +protocols = imap lmtp +dovecot.conf + +# Logging +echo "log_path = /var/log/dovecot.log" >/etc/dovecot/conf.d/10-logging.conf + +# 10-auth.conf +sed '/include auth-sql.conf.ext\|disable_plaintext_auth =\|auth_username_format =\|auth_mechanisms =\|auth_default_realm =\|include auth-system.conf.ext/d' \ + -i /etc/dovecot/conf.d/10-auth.conf +tee -a /etc/dovecot/conf.d/10-auth.conf >/dev/null <<- '10-auth.conf' + +!include auth-sql.conf.ext +disable_plaintext_auth = yes +auth_username_format = %Lu +auth_mechanisms = plain login +auth_default_realm = {{MYVEMAIL_DOMAIN}} + +auth_debug = yes +auth_debug_passwords = yes +10-auth.conf + +# dovecot-sql.conf.ext +tee -a /etc/dovecot/dovecot-sql.conf.ext >/dev/null <<- 'dovecot' + +# Virtual mailboxes +driver = mysql +connect = host=localhost dbname={{MYVEMAIL_POSTFIXADMIN_DBNAME}} user={{MYVEMAIL_POSTFIXADMIN_DBUSER}} password={{MYVEMAIL_POSTFIXADMIN_DBPASS}} +default_pass_scheme = ARGON2I +password_query = SELECT username AS user,password FROM mailbox WHERE username = '%u' AND active='1' +user_query = SELECT maildir, 600 AS uid, 600 AS gid FROM mailbox WHERE username = '%u' AND active='1' +iterate_query = SELECT username AS user FROM mailbox +dovecot + +# 10-mail.conf +sed '/mail_location =\|mail_home =\|mail_privileged_group =/d' \ + -i /etc/dovecot/conf.d/10-mail.conf +tee -a /etc/dovecot/conf.d/10-mail.conf >/dev/null <<- '10-mail.conf' + +mail_privileged_group = mail +mail_location = maildir:~/Maildir +mail_home = /var/vmail/%d/%n/ +10-mail.conf + +# 10-master.conf +sed -e 's|unix_listener lmtp {|unix_listener /var/spool/postfix/private/dovecot-lmtp {\ + mode = 0600\ + user = postfix\ + group = postfix|' \ + -e 's|unix_listener auth-userdb {|unix_listener /var/spool/postfix/private/auth {\ + mode = 0660\ + user = postfix\ + group = postfix|' \ + -i /etc/dovecot/conf.d/10-master.conf + +# 10-ssl.conf +rm -f /etc/ssl/dovecot/* +install -m 0600 /dev/stdin /etc/dovecot/ssl-keys.conf <<- ssl-keys.conf +ssl_dh = /dev/null <<- '10-ssl.conf' + +ssl = required +ssl_prefer_server_ciphers = yes +ssl_min_protocol = TLSv1.2 +10-ssl.conf + +# Stats service +tee -a /etc/dovecot/conf.d/10-master.conf >/dev/null <<- 10-master.conf + +service stats { + unix_listener stats-reader { + user = ${MYVEMAIL_NGINX_USERGROUP} + group = ${MYVEMAIL_NGINX_USERGROUP} + mode = 0660 +} + +unix_listener stats-writer { + user = ${MYVEMAIL_NGINX_USERGROUP} + group = ${MYVEMAIL_NGINX_USERGROUP} + mode = 0660 + } +} +10-master.conf + +# Mailboxes +sed -i 's/namespace inbox {/&\ + # Archive folder\ + mailbox Archive {\ + special_use = \\Archive\ + }/' /etc/dovecot/conf.d/15-mailboxes.conf +sed -i '/Sent Messages/! s/^ mailbox.*{/&\ + auto = subscribe/' /etc/dovecot/conf.d/15-mailboxes.conf + +# Virtual mailboxes +install -m 0640 /dev/stdin /etc/postfix/sql/virtual_domains_maps.cf <<- eof +hosts = localhost +dbname = {{MYVEMAIL_POSTFIXADMIN_DBNAME}} +user = {{MYVEMAIL_POSTFIXADMIN_DBUSER}} +password = {{MYVEMAIL_POSTFIXADMIN_DBPASS}} +query = SELECT domain FROM domain WHERE domain='%s' AND active = '1' +#query = SELECT domain FROM domain WHERE domain='%s' +#optional query to use when relaying for backup MX +#query = SELECT domain FROM domain WHERE domain='%s' AND backupmx = '0' AND active = '1' +#expansion_limit = 100 +eof + +install -m 0640 /dev/stdin /etc/postfix/sql/virtual_mailbox_maps.cf <<- eof +hosts = localhost +dbname = {{MYVEMAIL_POSTFIXADMIN_DBNAME}} +user = {{MYVEMAIL_POSTFIXADMIN_DBUSER}} +password = {{MYVEMAIL_POSTFIXADMIN_DBPASS}} +query = SELECT maildir FROM mailbox WHERE username='%s' AND active = '1' +#expansion_limit = 100 +eof + +install -m 0640 /dev/stdin /etc/postfix/sql/virtual_alias_domain_mailbox_maps.cf <<- eof +hosts = localhost +dbname = {{MYVEMAIL_POSTFIXADMIN_DBNAME}} +user = {{MYVEMAIL_POSTFIXADMIN_DBUSER}} +password = {{MYVEMAIL_POSTFIXADMIN_DBPASS}} +query = SELECT maildir FROM mailbox,alias_domain WHERE alias_domain.alias_domain = '%d' and mailbox.username = CONCAT('%u', '@', alias_domain.target_domain) AND mailbox.active = 1 AND alias_domain.active='1' +eof + +install -m 0640 /dev/stdin /etc/postfix/sql/virtual_alias_maps.cf <<- eof +hosts = localhost +dbname = {{MYVEMAIL_POSTFIXADMIN_DBNAME}} +user = {{MYVEMAIL_POSTFIXADMIN_DBUSER}} +password = {{MYVEMAIL_POSTFIXADMIN_DBPASS}} +query = SELECT goto FROM alias WHERE address='%s' AND active = '1' +#expansion_limit = 100 +eof + +install -m 0640 /dev/stdin /etc/postfix/sql/virtual_alias_domain_maps.cf <<- eof +hosts = localhost +dbname = {{MYVEMAIL_POSTFIXADMIN_DBNAME}} +user = {{MYVEMAIL_POSTFIXADMIN_DBUSER}} +password = {{MYVEMAIL_POSTFIXADMIN_DBPASS}} +query = SELECT goto FROM alias,alias_domain WHERE alias_domain.alias_domain = '%d' and alias.address = CONCAT('%u', '@', alias_domain.target_domain) AND alias.active = 1 AND alias_domain.active='1' +eof + +install -m 0640 /dev/stdin /etc/postfix/sql/virtual_alias_domain_catchall_maps.cf <<- eof +# handles catch-all settings of target-domain +hosts = localhost +dbname = {{MYVEMAIL_POSTFIXADMIN_DBNAME}} +user = {{MYVEMAIL_POSTFIXADMIN_DBUSER}} +password = {{MYVEMAIL_POSTFIXADMIN_DBPASS}} +query = SELECT goto FROM alias,alias_domain WHERE alias_domain.alias_domain = '%d' and alias.address = CONCAT('@', alias_domain.target_domain) AND alias.active = 1 AND alias_domain.active='1' +eof + +############### +#// +#// OpenDKIM +#// +############### +# sed -i 's\^SOCKET=local:$RUNDIR/opendkim.sock\SOCKET="local:/var/spool/postfix/opendkim/opendkim.sock"\' /etc/default/opendkim +sed -i "/Canonicalization\|Mode\|SubDomains\|Socket\|SoftwareHeader\|UserID\|UMask/d" ${MYVEMAIL_OPENDKIM_CONF} +tee -a ${MYVEMAIL_OPENDKIM_CONF} >/dev/null <<- 'opendkim.conf' + +Canonicalization relaxed/simple +Mode sv +SubDomains no +Socket local:/var/spool/postfix/opendkim/opendkim.sock +SoftwareHeader yes + +UserID opendkim +UMask 007 + +# Map domains in "from" addresses to keys used to sign messages +KeyTable refile:/etc/opendkim/KeyTable +SigningTable refile:/etc/opendkim/SigningTable + +# Hosts to ignore when verifying signatures +ExternalIgnoreList /etc/opendkim/trusted.hosts + +# A set of internal hosts whose mail should be signed +InternalHosts /etc/opendkim/trusted.hosts +opendkim.conf + +# trusted.hosts +cat >/etc/opendkim/trusted.hosts <<- trusted.hosts +127.0.0.1 +localhost + +trusted.hosts + +############### +#// +#// OpenDMARC +#// +############### +sed '/TrustedAuthservIDs\|AuthservID\|RejectFailures\|IgnoreAuthenticatedClients\|RequiredHeaders\|SPFSelfValidate\|Socket\|IgnoreHosts\|UMask\|UserID/d' \ + -i ${MYVEMAIL_OPENDMARC_CONF} + +tee -a ${MYVEMAIL_OPENDMARC_CONF} >/dev/null <<- 'opendmarc.conf' + +TrustedAuthservIDs {{HOSTNAME}} +AuthservID OpenDMARC +RejectFailures true +IgnoreAuthenticatedClients true +RequiredHeaders true +SPFSelfValidate true + +Socket local:/var/spool/postfix/opendmarc/opendmarc.sock +IgnoreHosts /etc/opendmarc/ignore.hosts + +UserID opendmarc +UMask 0002 +opendmarc.conf + +echo '127.0.0.1' | tee -a /etc/opendmarc/ignore.hosts >/dev/null + +############### +#// +#// Nginx +#// +############### +# php-fpm sock +cat >$(find /etc/php* -type d \( -name "pool.d" -o -name "php-fpm.d" \))/zz-listen.conf <<- php-fpm.sock +listen = 127.0.0.1:9000 +user = ${MYVEMAIL_NGINX_USERGROUP} +group = ${MYVEMAIL_NGINX_USERGROUP} +listen.owner = ${MYVEMAIL_NGINX_USERGROUP} +listen.group = ${MYVEMAIL_NGINX_USERGROUP} +listen.mode = 0660 +php-fpm.sock + +# proxy_params +tee /etc/nginx/proxy_params >/dev/null <<'PROXY' +proxy_set_header Host $http_host; +proxy_set_header X-Real-IP $remote_addr; +proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; +proxy_set_header X-Forwarded-Proto $scheme; +PROXY + +# Roundcube/Postfixadmin virtual proxy +tee $(find /etc/nginx -type f -name "default*") >/dev/null <<- 'nginx' +upstream php-handler { + server 127.0.0.1:9000; +} + +server { + listen 80 default_server; + server_name _; + + index index.php index.html; + + error_log /var/log/nginx/roundcube_error.log; + access_log /var/log/nginx/roundcube_access.log; + + root /usr/share/webapps/roundcube; + + # Postfixadmin + location ^~ /admin/ { + proxy_pass http://127.0.0.1:12000/; + include proxy_params; + } + + # Roundcube + location ~ \.php$ { + client_max_body_size 0; + + try_files $uri =404; + fastcgi_split_path_info ^(.+\.php)(/.+)$; + fastcgi_pass php-handler; + fastcgi_index index.php; + include fastcgi_params; + fastcgi_param SCRIPT_FILENAME $document_root$fastcgi_script_name; + fastcgi_param PATH_INFO $fastcgi_path_info; + } +} + +server { + listen 12000; + root /usr/share/webapps/postfixadmin/public/; + index index.php index.html; + + error_log /var/log/nginx/postfixadmin_error.log; + access_log /var/log/nginx/postfixadmin_access.log; + + location / { + try_files $uri $uri/ /index.php; + } + + location ~ ^/(.+\.php)$ { + try_files $uri =404; + fastcgi_pass php-handler; + fastcgi_index index.php; + fastcgi_param SCRIPT_FILENAME $document_root$fastcgi_script_name; + include /etc/nginx/fastcgi_params; + } +} +nginx + +############### +#// +#// Postwhite +#// +############### +cd /usr/local/bin +git clone --quiet https://github.com/spf-tools/spf-tools.git +git clone --quiet https://github.com/stevejenkins/postwhite.git +cp /usr/local/bin/postwhite/postwhite.conf /etc diff --git a/docker-compose.yaml b/docker-compose.yaml new file mode 100644 index 0000000..648a46d --- /dev/null +++ b/docker-compose.yaml @@ -0,0 +1,51 @@ +services: + + myvemail: + image: git.myvelabs.com/lab/myvemail:${MYVEMAIL_VERSION:-edge} + container_name: myvemail + restart: unless-stopped + + # build: . + + ports: + - ${MYVEMAIL_PORT:-80}:80/tcp + - 25:25/tcp + - 587:587/tcp + - 143:143/tcp + - 993:993/tcp + + environment: + # Mail domain details + MYVEMAIL_SUBDOMAIN: ${MYVEMAIL_SUBDOMAIN} + MYVEMAIL_DOMAIN: ${MYVEMAIL_DOMAIN} + + # Additional mail domains separated by commas + MYVEMAIL_ADDMX: ${MYVEMAIL_ADDMX} + + # Backup mail servers separated by commas + MYVEMAIL_BACKUPMX: ${MYVEMAIL_BACKUPMX} + + # Roundcube + MYVEMAIL_ROUNDCUBE_DBNAME: ${MYVEMAIL_ROUNDCUBE_DBNAME:-roundcube} + MYVEMAIL_ROUNDCUBE_DBUSER: ${MYVEMAIL_ROUNDCUBE_DBUSER:-roundcube} + MYVEMAIL_ROUNDCUBE_DBPASS: ${MYVEMAIL_ROUNDCUBE_DBPASS:-roundcube} + + # Postfixadmin + MYVEMAIL_POSTFIXADMIN_DBNAME: ${MYVEMAIL_POSTFIXADMIN_DBNAME:-postfixadmin} + MYVEMAIL_POSTFIXADMIN_DBUSER: ${MYVEMAIL_POSTFIXADMIN_DBUSER:-postfixadmin} + MYVEMAIL_POSTFIXADMIN_DBPASS: ${MYVEMAIL_POSTFIXADMIN_DBPASS:-postfixadmin} + + volumes: + # Required + - ${MYVEMAIL_VOLUME_MARIADB:-./data/sql}:/var/lib/mysql:Z + - ${MYVEMAIL_VOLUME_DATA:-./data/webapps}:/usr/share/webapps + - ${MYVEMAIL_VOLUME_MAIL:-./data/mail}:/var/vmail + + # SSL (point to individual files in case symlinks are being used) + - ${MYVEMAIL_VOLUME_SSL:-./data/ssl}/tls.key:/etc/ssl/dovecot/tls.key + - ${MYVEMAIL_VOLUME_SSL:-./data/ssl}/tls.pem:/etc/ssl/dovecot/tls.pem + - ${MYVEMAIL_VOLUME_SSL:-./data/ssl}/dh.pem:/etc/ssl/dovecot/dh.pem + + # Optional + - ${MYVEMAIL_VOLUME_DKIM:-./data/dkim}:/etc/opendkim/keys + - ${MYVEMAIL_VOLUME_POSTWHITE:-./data/postwhite}:/etc/postfix/postscreen_spf_whitelist.cidr diff --git a/generate-env.sh b/generate-env.sh new file mode 100755 index 0000000..f8db568 --- /dev/null +++ b/generate-env.sh @@ -0,0 +1,38 @@ +#!/usr/bin/env bash +cat >./.env <<- gen-env +# Required +# Mail domain +MYVEMAIL_SUBDOMAIN=${SUBDOMAIN} +MYVEMAIL_DOMAIN=${DOMAIN} + +# Optional +# Version: edge or stable +MYVEMAIL_VERSION=edge + +# Webmail port +MYVEMAIL_PORT=${PORT} + +# Additional mail domains separated by commas +MYVEMAIL_ADDMX= + +# Backup mail servers separated by commas +MYVEMAIL_BACKUPMX= + +# Volumes +MYVEMAIL_VOLUME_MARIADB= +MYVEMAIL_VOLUME_SSL= +MYVEMAIL_VOLUME_DATA= +MYVEMAIL_VOLUME_MAIL= +MYVEMAIL_VOLUME_DKIM= +MYVEMAIL_VOLUME_POSTWHITE= + +# MariaDB +# Roundcube +MYVEMAIL_ROUNDCUBE_DBNAME=roundcube +MYVEMAIL_ROUNDCUBE_DBUSER=roundcube +MYVEMAIL_ROUNDCUBE_DBPASS=$(openssl rand -hex 32) +# Postfixadmin +MYVEMAIL_POSTFIXADMIN_DBNAME=postfixadmin +MYVEMAIL_POSTFIXADMIN_DBUSER=postfixadmin +MYVEMAIL_POSTFIXADMIN_DBPASS=$(openssl rand -hex 32) +gen-env diff --git a/nginx-setup.sh b/nginx-setup.sh new file mode 100755 index 0000000..2e31756 --- /dev/null +++ b/nginx-setup.sh @@ -0,0 +1,106 @@ +#!/usr/bin/env bash +# Fill in the following variables +appname= #google +proxyurl= #http://webapps.kvm:4001 +domain= #www.google.com +eff_email_address= #eff@eff.com + +# Check privilege +if [ $(id -u) -ne 0 ] +then + echo "This script must be run by root" >&2 + exit 1 +fi + +# Variable check +if [ -z ${appname} ] || [ -z ${proxyurl} ] || [ -z ${domain} ] || [ -z ${eff_email_address} ] +then + echo "Missing variable, exiting..." + exit 1 +fi + +# Figure out nginx conf directory +if grep -q 'include.*conf.d' /etc/nginx/nginx.conf +then + nginxdir=/etc/nginx/conf.d +elif grep -q 'include.*sites-available' /etc/nginx/nginx.conf +then + nginxdir=/etc/nginx/sites-available + ln -s -f /etc/nginx/sites-available/${appname}.conf /etc/nginx/sites-enabled/ +else + echo "Missing nginx directory, exiting..." + exit 1 +fi + +# Virtual proxy +cat <<- 'proxy' | \ +sed -e "s|{{domain}}|${domain}|" \ + -e "s|{{proxyurl}}|${proxyurl}|" \ + -e "s|{{appname}}|${appname}|" | tee ${nginxdir}/${appname}.conf >/dev/null +server { + server_name {{domain}}; + + location / { + proxy_pass {{proxyurl}}; + error_log /var/log/nginx/{{appname}}_error.log; + access_log /var/log/nginx/{{appname}}_access.log; + + # proxy_params; + proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; + proxy_set_header X-Forwarded-Host $host; + proxy_set_header X-Forwarded-Port $server_port; + proxy_set_header X-Forwarded-Scheme $scheme; + proxy_set_header X-Forwarded-Proto $scheme; + proxy_set_header X-Real-IP $remote_addr; + proxy_set_header Accept-Encoding ""; + proxy_set_header Host $host; + + client_body_buffer_size 512k; + proxy_read_timeout 86400s; + client_max_body_size 0; + + # Websocket + proxy_http_version 1.1; + proxy_cache_bypass $http_upgrade; + proxy_set_header Upgrade $http_upgrade; + proxy_set_header Connection "upgrade"; + } + + # http_upgrade + # Security + server_tokens off; + add_header X-Frame-Options "SAMEORIGIN" always; + add_header X-Content-Type-Options "nosniff" always; + add_header X-XSS-Protection "1; mode=block" always; + add_header Referrer-Policy "strict-origin" always; + add_header X-Permitted-Cross-Domain-Policies "none" always; + add_header X-Robots-Tag "noindex, nofollow" always; + # add_header Content-Security-Policy "default-src 'self';" always; + + # http2 + http2 on; + + # http3 + listen 443 quic; + add_header Alt-Svc 'h3=":443"; ma=86400'; + quic_retry on; + http3 on; + + # Certbot defaults + listen 443 ssl; + include /etc/letsencrypt/options-ssl-nginx.conf; + ssl_dhparam /etc/letsencrypt/ssl-dhparams.pem; + add_header Strict-Transport-Security "max-age=31536000" always; +} +proxy + +# Run certbot +if nginx -t +then + certbot --nginx --non-interactive --agree-tos --no-eff-email -m ${eff_email_address} -d ${domain} \ + --staple-ocsp --hsts --no-redirect --renew-hook 'docker exec --interactive --tty myvemail /bin/ash -c "dovecot reload; postfix reload"' + + # Link certificates + ln -s /etc/letsencrypt/live/${domain}/fullchain.pem ./data/ssl/tls.pem + ln -s /etc/letsencrypt/live/${domain}/privkey.pem ./data/ssl/tls.key +fi