From 2f0ca9012459ec29c026bfb32265ef729c60bf0f Mon Sep 17 00:00:00 2001 From: root Date: Tue, 12 Sep 2023 05:03:20 +0000 Subject: [PATCH] Init --- config/dovecot-quotas.cf | 0 config/postfix-accounts.cf | 1 + config/postfix-aliases.cf | 0 docker-compose.yml | 107 +++++++++++ setup.sh | 352 +++++++++++++++++++++++++++++++++++++ 5 files changed, 460 insertions(+) create mode 100644 config/dovecot-quotas.cf create mode 100644 config/postfix-accounts.cf create mode 100644 config/postfix-aliases.cf create mode 100644 docker-compose.yml create mode 100755 setup.sh diff --git a/config/dovecot-quotas.cf b/config/dovecot-quotas.cf new file mode 100644 index 0000000..e69de29 diff --git a/config/postfix-accounts.cf b/config/postfix-accounts.cf new file mode 100644 index 0000000..c049399 --- /dev/null +++ b/config/postfix-accounts.cf @@ -0,0 +1 @@ +admin@uwu.email|{none} diff --git a/config/postfix-aliases.cf b/config/postfix-aliases.cf new file mode 100644 index 0000000..e69de29 diff --git a/docker-compose.yml b/docker-compose.yml new file mode 100644 index 0000000..18ce2bc --- /dev/null +++ b/docker-compose.yml @@ -0,0 +1,107 @@ +version: '2' + +volumes: + # SSL + certs: + vhost.d: + html: + acme: + # mail + mail_data: + mail_state: + # webmail + roundcube_app: + roundcube_sqlite: + +services: +# Mail + mail: + image: docker.io/mailserver/docker-mailserver:latest + hostname: mail + domainname: uwu.email + container_name: mail + ports: + - "25:25" + - "587:587" + - "465:465" + - "143:143" + - "993:993" + volumes: + - mail_data:/var/mail/ + - mail_state:/var/mail-state/ + - certs:/etc/letsencrypt/live/ + - ./config/:/tmp/docker-mailserver/ + environment: + - SSL_TYPE=letsencrypt + - LETSENCRYPT_HOST=mail.uwu.email + - VIRTUAL_HOST=mail.uwu.email + - ONE_DIR=1 + - DMS_DEBUG=1 + - SPOOF_PROTECTION=0 #1 + - REPORT_RECIPIENT=1 + - ENABLE_SPAMASSASSIN=0 + - ENABLE_CLAMAV=0 + - ENABLE_FAIL2BAN=1 + - ENABLE_POSTGREY=0 + cap_add: + - NET_ADMIN + - SYS_PTRACE + restart: always + webmail: + image: docker.io/roundcube/roundcubemail:latest + container_name: webmail + restart: always + expose: + - 80 + environment: + - ROUNDCUBEMAIL_DB_TYPE=sqlite + - ROUNDCUBEMAIL_SKIN=elastic + - ROUNDCUBEMAIL_DEFAULT_HOST=tls://mail.uwu.email + - ROUNDCUBEMAIL_SMTP_SERVER=tls://mail.uwu.email + - VIRTUAL_HOST=webmail.uwu.email + - LETSENCRYPT_HOST=webmail.uwu.email + - ROUNDCUBEMAIL_UPLOAD_MAX_FILESIZE=9M + volumes: + - roundcube_app:/var/www/html + # TODO Use Postgres + - roundcube_sqlite:/var/roundcube/db + networks: + - proxy-tier + - default + +# SSL + reverse_proxy: + image: docker.io/nginxproxy/nginx-proxy:alpine + restart: always + labels: + com.github.jrcs.letsencrypt_nginx_proxy_companion.nginx_proxy: "true" + volumes: + - certs:/etc/nginx/certs:ro + - vhost.d:/etc/nginx/vhost.d + - html:/usr/share/nginx/html + - /var/run/docker.sock:/tmp/docker.sock:ro + - ./config/nginx/uploadsize.conf:/etc/nginx/conf.d/uploadsize.conf:ro + ports: + - 80:80 + - 443:443 + networks: + - proxy-tier + letsencrypt-companion: + image: docker.io/nginxproxy/acme-companion + restart: always + volumes: + - certs:/etc/nginx/certs + - acme:/etc/acme.sh + - vhost.d:/etc/nginx/vhost.d + - html:/usr/share/nginx/html + - /var/run/docker.sock:/var/run/docker.sock:ro + networks: + - proxy-tier + depends_on: + - reverse_proxy + environment: + #- ACME_CA_URI=https://acme-staging-v02.api.letsencrypt.org/directory # test-server + - DEFAULT_EMAIL=admin@uwu.email + +networks: + proxy-tier: diff --git a/setup.sh b/setup.sh new file mode 100755 index 0000000..cf322e5 --- /dev/null +++ b/setup.sh @@ -0,0 +1,352 @@ +#!/bin/bash + +# Wrapper for various setup scripts +# included in the docker-mailserver + +SCRIPT='SETUP' + +set -euEo pipefail +trap '__log_err ${FUNCNAME[0]:-"?"} ${_:-"?"} ${LINENO:-"?"} ${?:-"?"}' ERR +trap '_unset_vars || :' EXIT + +function __log_err +{ + local FUNC_NAME LINE EXIT_CODE + FUNC_NAME="${1} / ${2}" + LINE="${3}" + EXIT_CODE="${4}" + + printf "\n––– \e[1m\e[31mUNCHECKED ERROR\e[0m\n%s\n%s\n%s\n%s\n\n" \ + " – script = ${SCRIPT,,}.sh" \ + " – function = ${FUNC_NAME}" \ + " – line = ${LINE}" \ + " – exit code = ${EXIT_CODE}" +} + +function _unset_vars +{ + unset CDIR CRI INFO IMAGE_NAME CONTAINER_NAME DEFAULT_CONFIG_PATH + unset USE_CONTAINER WISHED_CONFIG_PATH CONFIG_PATH VOLUME USE_TTY + unset SCRIPT +} + +function _get_current_directory +{ + if dirname "$(readlink -f "${0}")" &>/dev/null + then + CDIR="$(cd "$(dirname "$(readlink -f "${0}")")" && pwd)" + elif realpath -e -L "${0}" &>/dev/null + then + CDIR="$(realpath -e -L "${0}")" + CDIR="${CDIR%/setup.sh}" + fi +} + +CDIR="$(pwd)" +_get_current_directory + +CRI= +INFO= +IMAGE_NAME= +CONTAINER_NAME='mail' +DEFAULT_CONFIG_PATH="${CDIR}/config" +USE_CONTAINER=false +WISHED_CONFIG_PATH= +CONFIG_PATH= +VOLUME= +USE_TTY= + +function _check_root +{ + if [[ ${EUID} -ne 0 ]] + then + echo "Curently docker-mailserver doesn't support podman's rootless mode, please run this script as root user." + return 1 + fi +} + +function _update_config_path +{ + if [[ -n ${CONTAINER_NAME} ]] + then + VOLUME=$(${CRI} inspect "${CONTAINER_NAME}" \ + --format="{{range .Mounts}}{{ println .Source .Destination}}{{end}}" | \ + grep "/tmp/docker-mailserver$" 2>/dev/null) + fi + + if [[ -n ${VOLUME} ]] + then + CONFIG_PATH=$(echo "${VOLUME}" | awk '{print $1}') + fi +} + +function _inspect +{ + if _docker_image_exists "${IMAGE_NAME}" + then + echo "Image: ${IMAGE_NAME}" + else + echo "Image: '${IMAGE_NAME}' can’t be found." + fi + + if [[ -n ${CONTAINER_NAME} ]] + then + echo "Container: ${CONTAINER_NAME}" + echo "Config mount: ${CONFIG_PATH}" + else + echo "Container: Not running, please start docker-mailserver." + fi +} + +function _usage +{ + echo "${SCRIPT,,}.sh Bootstrapping Script + +Usage: ${0} [-i IMAGE_NAME] [-c CONTAINER_NAME] [args] + +OPTIONS: + + -i IMAGE_NAME The name of the docker-mailserver image, by default + 'tvial/docker-mailserver:latest' for docker, and + 'docker.io/tvial/docker-mailserver:latest' for podman. + + -c CONTAINER_NAME The name of the running container. + + -p PATH Config folder path (default: ${CDIR}/config) + + -h Show this help dialogue + +SUBCOMMANDS: + + email: + + ${0} email add [] + ${0} email update [] + ${0} email del + ${0} email restrict [] + ${0} email list + + alias: + ${0} alias add + ${0} alias del + ${0} alias list + + quota: + ${0} quota set [] + ${0} quota del + + config: + + ${0} config dkim (default: 2048) + ${0} config ssl + + relay: + + ${0} relay add-domain [] + ${0} relay add-auth [] + ${0} relay exclude-domain + + debug: + + ${0} debug fetchmail + ${0} debug fail2ban [ ] + ${0} debug show-mail-logs + ${0} debug inspect + ${0} debug login + + help: Show this help dialogue + +" +} + +function _docker_image_exists +{ + if ${CRI} history -q "${1}" >/dev/null 2>&1 + then + return 0 + else + return 1 + fi +} + +function _docker_image +{ + if ${USE_CONTAINER} + then + # reuse existing container specified on command line + ${CRI} exec "${USE_TTY}" "${CONTAINER_NAME}" "${@}" + else + # start temporary container with specified image + if ! _docker_image_exists "${IMAGE_NAME}" + then + echo "Image '${IMAGE_NAME}' not found. Pulling ..." + ${CRI} pull "${IMAGE_NAME}" + fi + + ${CRI} run --rm \ + -v "${CONFIG_PATH}":/tmp/docker-mailserver \ + "${USE_TTY}" "${IMAGE_NAME}" "${@}" + fi +} + +function _docker_container +{ + if [[ -n ${CONTAINER_NAME} ]] + then + ${CRI} exec "${USE_TTY}" "${CONTAINER_NAME}" "${@}" + else + echo "The docker-mailserver is not running!" + exit 5 + fi +} + +function _main +{ + if [[ -n $(command -v docker) ]] + then + CRI=docker + elif [[ -n $(command -v podman) ]] + then + CRI=podman + _check_root + else + echo "No supported Container Runtime Interface detected." + exit 10 + fi + + INFO=$(${CRI} ps \ + --no-trunc \ + --format "{{.Image}};{{.Names}}" \ + --filter label=org.label-schema.name="docker-mailserver" | \ + tail -1) + + IMAGE_NAME=${INFO%;*} + CONTAINER_NAME=${INFO#*;} + + if [[ -z ${IMAGE_NAME} ]] + then + if [[ ${CRI} == "docker" ]] + then + IMAGE_NAME=tvial/docker-mailserver:latest + elif [[ ${CRI} == "podman" ]] + then + IMAGE_NAME=docker.io/tvial/docker-mailserver:latest + fi + fi + + if tty -s + then + USE_TTY="-ti" + fi + + local OPTIND + while getopts ":c:i:p:h" OPT + do + case ${OPT} in + c) CONTAINER_NAME="${OPTARG}" ; USE_CONTAINER=true ;; # container specified, connect to running instance + i) IMAGE_NAME="${OPTARG}" ;; + p) + case "${OPTARG}" in + /*) WISHED_CONFIG_PATH="${OPTARG}" ;; + * ) WISHED_CONFIG_PATH="${CDIR}/${OPTARG}" ;; + esac + + if [[ ! -d ${WISHED_CONFIG_PATH} ]] + then + echo "Directory doesn't exist" + _usage + exit 40 + fi + ;; + h) _usage ; return ;; + *) echo "Invalid option: -${OPTARG}" >&2 ;; + esac + done + shift $((OPTIND-1)) + + if [[ -z ${WISHED_CONFIG_PATH} ]] + then + # no wished config path + _update_config_path + + if [[ -z ${CONFIG_PATH} ]] + then + CONFIG_PATH=${DEFAULT_CONFIG_PATH} + fi + else + CONFIG_PATH=${WISHED_CONFIG_PATH} + fi + + + case ${1:-} in + + email) + shift ; case ${1:-} in + add ) shift ; _docker_image addmailuser "${@}" ;; + update ) shift ; _docker_image updatemailuser "${@}" ;; + del ) shift ; _docker_image delmailuser "${@}" ;; + restrict ) shift ; _docker_container restrict-access "${@}" ;; + list ) _docker_image listmailuser ;; + * ) _usage ;; + esac + ;; + + alias) + shift ; case ${1:-} in + add ) shift ; _docker_image addalias "${1}" "${2}" ;; + del ) shift ; _docker_image delalias "${1}" "${2}" ;; + list ) shift ; _docker_image listalias ;; + * ) _usage ;; + esac + ;; + + quota) + shift ; case ${1:-} in + set ) shift ; _docker_image setquota "${@}" ;; + del ) shift ; _docker_image delquota "${@}" ;; + * ) _usage ;; + esac + ;; + + config) + shift ; case ${1:-} in + dkim ) _docker_image generate-dkim-config "${2:-2048}" ;; + ssl ) _docker_image generate-ssl-certificate "${2}" ;; + * ) _usage ;; + esac + ;; + + relay) + shift ; case ${1:-} in + add-domain ) shift ; _docker_image addrelayhost "${@}" ;; + add-auth ) shift ; _docker_image addsaslpassword "${@}" ;; + exclude-domain ) shift ; _docker_image excluderelaydomain "${@}" ;; + * ) _usage ;; + esac + ;; + + debug) + shift ; case ${1:-} in + fetchmail ) _docker_image debug-fetchmail ;; + fail2ban ) shift ; _docker_container fail2ban "${@}" ;; + show-mail-logs ) _docker_container cat /var/log/mail/mail.log ;; + inspect ) _inspect ;; + login ) + shift + if [[ -z ${1:-''} ]] + then + _docker_container /bin/bash + else + _docker_container /bin/bash -c "${@}" + fi + ;; + * ) _usage ; exit 1 ;; + esac + ;; + + help ) _usage ;; + * ) _usage ; exit 1 ;; + esac +} + +_main "${@}"