搭建邮箱服务器
前提需要安装完成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记录
主机记录 | 记录类型 | 记录值 | 优先级 |
---|---|---|---|
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添加记录
- 安装
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
注意生成的记录,需要进行合并起来 才能使用。
-
添加 DKIM 记录值至DNS
-
验证测试
在进行邮件测试前,https://www.checktls.com 测试 是否支持 tls 验证。
-
搭建完毕 登录测试 (em client软件登录)
版权声明:
本站所有文章除特别声明外,均采用 CC BY-NC-SA 4.0 许可协议。转载请注明来自
烦恼的夏洛克!
喜欢就支持一下吧