前提需要安装完成docker

  • 安装Docker
curl -fsSL https://get.docker.com -o get-docker.sh  # 使用脚本安装
sudo sh get-docker.sh

cat > /etc/docker/daemon.json << EOF `# 配置优化`
{
    "oom-score-adjust": -1000,
    "log-driver": "json-file",
    "log-opts": {
    "max-size": "100m",
    "max-file": "3"
    },
    "max-concurrent-downloads": 10,
    "exec-opts": ["native.cgroupdriver=systemd"],
    "max-concurrent-uploads": 10,
    "storage-driver": "overlay2",
    "storage-opts": [
    "overlay2.override_kernel_check=true"
    ]
}
EOF

systemctl start docker \  `# 启动 并设置开机自启`
&& systemctl enable docker \
&& systemctl status docker
  • 安装Docker-compose
curl -L "https://github.com/docker/compose/releases/download/1.29.2/docker-compose-$(uname -s)-$(uname -m)" -o /usr/local/bin/docker-compose \
&& chmod +x /usr/local/bin/docker-compose \
&& ln -s /usr/local/bin/docker-compose /usr/bin/docker-compose \
&& docker-compose --version
  • 配置对应的DNS记录
主机记录 记录类型 记录值 优先级
mail A 82.157.43.5 -
@ MX mail.wmhxx.cn -
@ TXT v=spf1 mx ~all -

wmhxx.cn表示的为主域名

  • 使用 certbot 生成 letsencrypt 证书
yum install -y certbot

certbot certonly --manual --preferred-challenge dns -d  mail.wmhxx.cn
# `此 命令会生成 一条 TXT 记录记录用于验证`

DNS添加记录
image

  • 安装dig命令

dig 进行检查解析是否生效

# 安装 dig 命令
yum install bind-utils

# 检查 解析
dig TXT _acme-challenge.mail.treesir.pub
  • 配置定时续签证书

注意添加 crontab 定时任务时,检查一下 crond 服务器 是否有在正常运行。

# 打开 crontab 配置文件
crontab -e

0 5 * * 1 /usr/bin/certbot renew --quiet

# 检查服务是否正常运行
service crond status

部署 docker-mailserver 容器镜像

新建docker-compose-mailserver.yml

version: '3.8'

services:
  mailserver:
    image: docker.io/mailserver/docker-mailserver:latest
    hostname: mail
    domainname: wmhxx.cn
    container_name: mailserver
    env_file: mailserver.env
    dns: 223.5.5.5
    ports:
      - "25:25"
      - "143:143"
      - "587:587"
      - "993:993"
      - "110:110"
      - "995:995"
    volumes:
      - ./data/maildata:/var/mail
      - ./data/mailstate:/var/mail-state
      - ./data/maillogs:/var/log/mail
      - /etc/localtime:/etc/localtime:ro
      - ./config/:/tmp/docker-mailserver
      - /etc/letsencrypt/archive/mail.wmhxx.cn:/tmp/ssl:ro
    restart: always
    stop_grace_period: 1m
    cap_add: [ "NET_ADMIN", "SYS_PTRACE" ]

新建mailserver.env文件

OVERRIDE_HOSTNAME=
DMS_DEBUG=1
SUPERVISOR_LOGLEVEL=
ONE_DIR=1
POSTMASTER_ADDRESS=wangmenghe@sumervart.com
ENABLE_UPDATE_CHECK=1
UPDATE_CHECK_INTERVAL=1d
PERMIT_DOCKER=network
NETWORK_INTERFACE=
TLS_LEVEL=
SPOOF_PROTECTION=
ENABLE_SRS=0
ENABLE_POP3=1
ENABLE_CLAMAV=0
ENABLE_AMAVIS=1
AMAVIS_LOGLEVEL=0
ENABLE_FAIL2BAN=0
FAIL2BAN_BLOCKTYPE=drop
ENABLE_MANAGESIEVE=
POSTSCREEN_ACTION=enforce
SMTP_ONLY=
SSL_TYPE=manual
SSL_CERT_PATH=/tmp/ssl/fullchain1.pem
SSL_KEY_PATH=/tmp/ssl/privkey1.pem
SSL_ALT_CERT_PATH=
SSL_ALT_KEY_PATH=
VIRUSMAILS_DELETE_DELAY=
ENABLE_POSTFIX_VIRTUAL_TRANSPORT=
POSTFIX_DAGENT=
POSTFIX_MAILBOX_SIZE_LIMIT=102400000
ENABLE_QUOTAS=1
POSTFIX_MESSAGE_SIZE_LIMIT=102400000
PFLOGSUMM_TRIGGER=
PFLOGSUMM_RECIPIENT=
PFLOGSUMM_SENDER=
LOGWATCH_INTERVAL=
LOGWATCH_RECIPIENT=
REPORT_RECIPIENT=0
REPORT_SENDER=
REPORT_INTERVAL=daily
POSTFIX_INET_PROTOCOLS=all
ENABLE_SPAMASSASSIN=0
SPAMASSASSIN_SPAM_TO_INBOX=1
MOVE_SPAM_TO_JUNK=1
SA_TAG=2.0
SA_TAG2=6.31
SA_KILL=6.31
SA_SPAM_SUBJECT=***SPAM*****
ENABLE_FETCHMAIL=0
FETCHMAIL_POLL=300
ENABLE_LDAP=
LDAP_START_TLS=
LDAP_SERVER_HOST=
LDAP_SEARCH_BASE=
LDAP_BIND_DN=
LDAP_BIND_PW=
LDAP_QUERY_FILTER_USER=
LDAP_QUERY_FILTER_GROUP=
LDAP_QUERY_FILTER_ALIAS=
LDAP_QUERY_FILTER_DOMAIN=
DOVECOT_TLS=
DOVECOT_USER_FILTER=
DOVECOT_PASS_FILTER=
DOVECOT_MAILBOX_FORMAT=maildir
DOVECOT_AUTH_BIND=
ENABLE_POSTGREY=0
POSTGREY_DELAY=300
POSTGREY_MAX_AGE=35
POSTGREY_TEXT=Delayed by Postgrey
POSTGREY_AUTO_WHITELIST_CLIENTS=5
ENABLE_SASLAUTHD=0
SASLAUTHD_MECHANISMS=
SASLAUTHD_MECH_OPTIONS=
SASLAUTHD_LDAP_SERVER=
SASLAUTHD_LDAP_BIND_DN=
SASLAUTHD_LDAP_PASSWORD=
SASLAUTHD_LDAP_SEARCH_BASE=
SASLAUTHD_LDAP_FILTER=
SASLAUTHD_LDAP_START_TLS=
SASLAUTHD_LDAP_TLS_CHECK_PEER=
SASLAUTHD_LDAP_TLS_CACERT_FILE=
SASLAUTHD_LDAP_TLS_CACERT_DIR=
SASLAUTHD_LDAP_PASSWORD_ATTR=
SASL_PASSWD=
SASLAUTHD_LDAP_AUTH_METHOD=
SASLAUTHD_LDAP_MECH=
SRS_SENDER_CLASSES=envelope_sender
SRS_EXCLUDE_DOMAINS=
SRS_SECRET=
DEFAULT_RELAY_HOST=
RELAY_HOST=
RELAY_PORT=25
RELAY_USER=
RELAY_PASSWORD=
  • 启动docker-compose
docker-compose -f docker-compose-mailserver.yml up --build -d
  • 新增setup.sh文件
#!/bin/bash

# version   v1.0.0
# executed  manually / via Make
# task      wrapper for various setup scripts

CONFIG_PATH=
CONTAINER_NAME=
CRI=
DEFAULT_CONFIG_PATH=
DESIRED_CONFIG_PATH=
DIR=$(pwd)
DMS_CONFIG='/tmp/docker-mailserver'
IMAGE_NAME=
DEFAULT_IMAGE_NAME='docker.io/mailserver/docker-mailserver:latest'
INFO=
PODMAN_ROOTLESS=false
USE_SELINUX=
USE_TTY=
VOLUME=

WHITE=$(echo -ne '\e[37m')
ORANGE=$(echo -ne '\e[38;5;214m')
LBLUE=$(echo -ne '\e[94m')
RESET=$(echo -ne '\e[0m')

set -euEo pipefail
shopt -s inherit_errexit 2>/dev/null || true

function _show_local_usage
{
  # shellcheck disable=SC2059
  printf '%s' "${ORANGE}OPTIONS${RESET}
    ${LBLUE}Config path, container or image adjustments${RESET}
        -i IMAGE_NAME
            Provides the name of the 'docker-mailserver' image. The default value is
            '${WHITE}${DEFAULT_IMAGE_NAME}${RESET}'

        -c CONTAINER_NAME
            Provides the name of the running container.

        -p PATH
            Provides the local path of the config folder to the temporary container instance.
            Does not work if an existing a 'docker-mailserver' container is already running.

    ${LBLUE}SELinux${RESET}
        -z
            Allows container access to the bind mount content that is shared among
            multiple containers on a SELinux-enabled host.

        -Z
            Allows container access to the bind mount content that is private and
            unshared with other containers on a SELinux-enabled host.

    ${LBLUE}Podman${RESET}
        -R
            Accept running in Podman rootless mode. Ignored when using Docker / Docker Compose.

"

  [[ ${1:-} == 'no-exit' ]] && return 0

  # shellcheck disable=SC2059
  printf '%s' "${ORANGE}EXIT STATUS${RESET}
    Exit status is 0 if the command was successful. If there was an unexpected error, an error
    message is shown describing the error. In case of an error, the script will exit with exit
    status 1.

"
}

function _get_absolute_script_directory
{
  if dirname "$(readlink -f "${0}")" &>/dev/null
  then
    DIR=$(dirname "$(readlink -f "${0}")")
  elif realpath -e -L "${0}" &>/dev/null
  then
    DIR=$(realpath -e -L "${0}")
    DIR="${DIR%/setup.sh}"
  fi
}

function _set_default_config_path
{
  if [[ -d "${DIR}/config" ]]
  then
    # legacy path (pre v10.2.0)
    DEFAULT_CONFIG_PATH="${DIR}/config"
  else
    DEFAULT_CONFIG_PATH="${DIR}/docker-data/dms/config"
  fi
}

function _handle_config_path
{
  if [[ -z ${DESIRED_CONFIG_PATH} ]]
  then
    # no desired config path
    if [[ -n ${CONTAINER_NAME} ]]
    then
      VOLUME=$(${CRI} inspect "${CONTAINER_NAME}" \
        --format="{{range .Mounts}}{{ println .Source .Destination}}{{end}}" | \
        grep "${DMS_CONFIG}$" 2>/dev/null || :)
    fi

    if [[ -n ${VOLUME} ]]
    then
      CONFIG_PATH=$(echo "${VOLUME}" | awk '{print $1}')
    fi

    if [[ -z ${CONFIG_PATH} ]]
    then
      CONFIG_PATH=${DEFAULT_CONFIG_PATH}
    fi
  else
    CONFIG_PATH=${DESIRED_CONFIG_PATH}
  fi
}

function _run_in_new_container
{
  # start temporary container with specified image
  if ! ${CRI} history -q "${IMAGE_NAME}" &>/dev/null
  then
    echo "Image '${IMAGE_NAME}' not found. Pulling ..."
    ${CRI} pull "${IMAGE_NAME}"
  fi

  ${CRI} run --rm "${USE_TTY}" \
    -v "${CONFIG_PATH}:${DMS_CONFIG}${USE_SELINUX}" \
    "${IMAGE_NAME}" "${@}"
}

function _main
{
  _get_absolute_script_directory
  _set_default_config_path

  local OPTIND
  while getopts ":c:i:p:zZR" OPT
  do
    case ${OPT} in
      ( i )     IMAGE_NAME="${OPTARG}"     ;;
      ( z | Z ) USE_SELINUX=":${OPT}"      ;;
      ( c )     CONTAINER_NAME="${OPTARG}" ;;
      ( R )     PODMAN_ROOTLESS=true       ;;
      ( p )
        case "${OPTARG}" in
          ( /* ) DESIRED_CONFIG_PATH="${OPTARG}"        ;;
          ( *  ) DESIRED_CONFIG_PATH="${DIR}/${OPTARG}" ;;
        esac

        if [[ ! -d ${DESIRED_CONFIG_PATH} ]]
        then
          echo "Specified directory '${DESIRED_CONFIG_PATH}' doesn't exist" >&2
          exit 1
        fi
        ;;

      ( * )
        echo "Invalid option: '-${OPTARG}'" >&2
        echo -e "Use './setup.sh help' to get a complete overview.\n" >&2
        _show_local_usage 'no-exit'
        exit 1
        ;;

    esac
  done
  shift $(( OPTIND - 1 ))

  if command -v docker &>/dev/null
  then
    CRI=docker
  elif command -v podman &>/dev/null
  then
    CRI=podman
    if ! ${PODMAN_ROOTLESS} && [[ ${EUID} -ne 0 ]]
    then
      read -r -p "You are running Podman in rootless mode. Continue? [Y/n] "
      [[ -n ${REPLY} ]] && [[ ${REPLY} =~ (n|N) ]] && exit 0
    fi
  else
    echo 'No supported Container Runtime Interface detected.'
    exit 1
  fi

  INFO=$(${CRI} ps --no-trunc --format "{{.Image}};{{.Names}}" --filter \
    label=org.opencontainers.image.title="docker-mailserver" | tail -1)

  [[ -z ${CONTAINER_NAME} ]] && CONTAINER_NAME=${INFO#*;}
  [[ -z ${IMAGE_NAME} ]] && IMAGE_NAME=${INFO%;*}
  if [[ -z ${IMAGE_NAME} ]]
  then
    IMAGE_NAME=${NAME:-${DEFAULT_IMAGE_NAME}}
  fi

  if test -t 0
  then
    USE_TTY="-it"
  else
    # GitHub Actions will fail (or really anything else
    #   lacking an interactive tty) if we don't set a
    #   value here; "-t" alone works for these cases.
    USE_TTY="-t"
  fi

  _handle_config_path

  if [[ -n ${CONTAINER_NAME} ]]
  then
    ${CRI} exec "${USE_TTY}" "${CONTAINER_NAME}" setup "${@}"
  else
    _run_in_new_container setup "${@}"
  fi

  [[ ${1:-} == 'help' ]] && _show_local_usage

  return 0
}

[[ -z ${1:-} ]] && set 'help'
_main "${@}"
  • 新增用户
# 添加 邮件账号及密码
./setup.sh email add xxxx@wmhxx.cn "xxx"

# 更新 邮件账号及密码
./setup.sh email update xxxx@wmhxx.cn "xxx"
  • 生成 DKIM 签名记录

注意下面的配置中,设置了 keysize 参数。原因是默认使用的 keysize 大小为 4096 ,在 aliyun中 无法进行正常的添加

./setup.sh config dkim keysize 2048

# 查看 DKIM 签名记录
cat config/opendkim/keys/wmhxx.cn/mail.txt

注意生成的记录,需要进行合并起来 才能使用。
image-1675739053610

  • 添加 DKIM 记录值至DNS
    image-1675739083209

  • 验证测试
    在进行邮件测试前,https://www.checktls.com 测试 是否支持 tls 验证。
    image-1675739181921
    image-1675739325178

  • 搭建完毕 登录测试 (em client软件登录)
    image-1675739409739
    image-1675739427654

文章作者: 烦恼的夏洛克
版权声明: 本站所有文章除特别声明外,均采用 CC BY-NC-SA 4.0 许可协议。转载请注明来自 烦恼的夏洛克
linux
喜欢就支持一下吧