feat(deploy): 支持配置邮件客户端 TLS 证书
- 新增 `LANQIN_TLS_CERT_FILE` 和 `LANQIN_TLS_KEY_FILE`,让 Postfix 与 Dovecot 使用外部证书。 - 在单容器与分服务启动脚本中统一回退到 `localhost` 自签证书,并在证书不可读时给出告警。 - 补充部署文档与示例配置,说明第三方客户端校验证书的挂载方式。
This commit is contained in:
@@ -26,6 +26,12 @@ LANQIN_PUBLIC_HOSTNAME=mail.example.com
|
||||
# Webmail 对外访问地址。生产环境建议使用 https。
|
||||
LANQIN_PUBLIC_BASE_URL=https://mail.example.com
|
||||
|
||||
# 邮件客户端 TLS 证书。用于 SMTP 465/587、IMAP 993、POP3 995。
|
||||
# 生产环境请挂载真实证书,并确保域名包含 LANQIN_PUBLIC_HOSTNAME。
|
||||
# 留空时会使用容器自带 localhost 自签证书,第三方客户端会提示证书不匹配。
|
||||
LANQIN_TLS_CERT_FILE=
|
||||
LANQIN_TLS_KEY_FILE=
|
||||
|
||||
# =========================
|
||||
# 初始管理员
|
||||
# =========================
|
||||
|
||||
@@ -129,6 +129,36 @@ docker compose -f docker-compose.stack.yml -f docker-compose.stack.build.yml up
|
||||
- Go API 是 Webmail 和管理后台入口;浏览器不直接连接 SMTP/IMAP/POP3。
|
||||
- Go API 会读取 `LANQIN_MAILDIR_ROOT=/var/mail/vhosts`,周期扫描 Maildir,把 Postfix/Dovecot 入站邮件同步成 Webmail 索引。
|
||||
|
||||
## 邮件客户端 TLS 证书
|
||||
|
||||
Web 站点可以由宿主机 Nginx / 宝塔反代到容器 `80`,但 SMTP/IMAP/POP3 端口不会使用 Web 反代的证书。
|
||||
如果第三方客户端连接 `465/587/993/995` 时提示证书是 `localhost`,说明 Postfix/Dovecot 仍在使用容器自带的测试证书。
|
||||
|
||||
生产环境请把域名证书挂载进容器,并在 `.env` 指向证书文件:
|
||||
|
||||
```env
|
||||
LANQIN_TLS_CERT_FILE=/certs/fullchain.pem
|
||||
LANQIN_TLS_KEY_FILE=/certs/privkey.pem
|
||||
```
|
||||
|
||||
单容器示例:
|
||||
|
||||
```yaml
|
||||
services:
|
||||
lanqin-email:
|
||||
volumes:
|
||||
- ./data:/data
|
||||
- ./mail:/var/mail/vhosts
|
||||
- ./dkim:/var/lib/rspamd/dkim
|
||||
- /etc/letsencrypt:/etc/letsencrypt:ro
|
||||
```
|
||||
|
||||
证书域名必须覆盖 `LANQIN_PUBLIC_HOSTNAME`。更新后执行:
|
||||
|
||||
```bash
|
||||
docker compose up -d --force-recreate
|
||||
```
|
||||
|
||||
## SMTP 发信排查
|
||||
|
||||
单容器部署时,Webmail 发信默认提交给同容器内的 Postfix:
|
||||
|
||||
@@ -8,6 +8,8 @@ set -eu
|
||||
: "${LANQIN_SMTP_HOST:=127.0.0.1}"
|
||||
: "${LANQIN_SMTP_PORT:=25}"
|
||||
: "${LANQIN_MAILDIR_ROOT:=/var/mail/vhosts}"
|
||||
: "${LANQIN_TLS_CERT_FILE:=}"
|
||||
: "${LANQIN_TLS_KEY_FILE:=}"
|
||||
|
||||
export LANQIN_DATA_DIR LANQIN_DB_PATH LANQIN_ADDR LANQIN_SMTP_HOST LANQIN_SMTP_PORT LANQIN_MAILDIR_ROOT
|
||||
|
||||
@@ -21,13 +23,28 @@ elif id rspamd >/dev/null 2>&1; then
|
||||
chown -R rspamd:rspamd /run/rspamd /var/lib/rspamd 2>/dev/null || true
|
||||
fi
|
||||
|
||||
TLS_CERT=/etc/ssl/certs/ssl-cert-snakeoil.pem
|
||||
TLS_KEY=/etc/ssl/private/ssl-cert-snakeoil.key
|
||||
if [ -n "$LANQIN_TLS_CERT_FILE" ] || [ -n "$LANQIN_TLS_KEY_FILE" ]; then
|
||||
if [ -f "$LANQIN_TLS_CERT_FILE" ] && [ -f "$LANQIN_TLS_KEY_FILE" ]; then
|
||||
TLS_CERT="$LANQIN_TLS_CERT_FILE"
|
||||
TLS_KEY="$LANQIN_TLS_KEY_FILE"
|
||||
else
|
||||
echo "warning: LANQIN_TLS_CERT_FILE/LANQIN_TLS_KEY_FILE not readable; using snakeoil localhost certificate" >&2
|
||||
fi
|
||||
fi
|
||||
|
||||
postconf -e "myhostname = ${LANQIN_PUBLIC_HOSTNAME}"
|
||||
postconf -e "myorigin = ${LANQIN_PUBLIC_HOSTNAME}"
|
||||
postconf -e "smtpd_tls_cert_file = ${TLS_CERT}"
|
||||
postconf -e "smtpd_tls_key_file = ${TLS_KEY}"
|
||||
postconf -e "virtual_transport = lmtp:inet:127.0.0.1:24"
|
||||
postconf -e "smtpd_sasl_path = inet:127.0.0.1:12345"
|
||||
postconf -e "milter_mail_macros = i {mail_addr} {client_addr} {client_name} {auth_authen}"
|
||||
postconf -e "smtpd_milters = inet:127.0.0.1:11332"
|
||||
postconf -e "non_smtpd_milters = inet:127.0.0.1:11332"
|
||||
sed -i "s#^ssl_cert = <.*#ssl_cert = <${TLS_CERT}#" /etc/dovecot/dovecot.conf
|
||||
sed -i "s#^ssl_key = <.*#ssl_key = <${TLS_KEY}#" /etc/dovecot/dovecot.conf
|
||||
|
||||
# Rspamd DKIM keys are exported after API seed/migrations create the SQLite DB.
|
||||
/usr/local/bin/lanqin-api >/tmp/lanqin-api-bootstrap.log 2>&1 &
|
||||
|
||||
@@ -32,6 +32,10 @@ services:
|
||||
volumes:
|
||||
- ./data:/data
|
||||
- ./mail:/var/mail/vhosts
|
||||
# 生产环境如需第三方客户端校验证书,请取消下面挂载,并在 .env 配置:
|
||||
# LANQIN_TLS_CERT_FILE=/etc/letsencrypt/live/mail.example.com/fullchain.pem
|
||||
# LANQIN_TLS_KEY_FILE=/etc/letsencrypt/live/mail.example.com/privkey.pem
|
||||
# - /etc/letsencrypt:/etc/letsencrypt:ro
|
||||
ports:
|
||||
- "25:25"
|
||||
- "465:465"
|
||||
@@ -47,6 +51,10 @@ services:
|
||||
volumes:
|
||||
- ./data:/data
|
||||
- ./mail:/var/mail/vhosts
|
||||
# 生产环境如需第三方客户端校验证书,请取消下面挂载,并在 .env 配置:
|
||||
# LANQIN_TLS_CERT_FILE=/etc/letsencrypt/live/mail.example.com/fullchain.pem
|
||||
# LANQIN_TLS_KEY_FILE=/etc/letsencrypt/live/mail.example.com/privkey.pem
|
||||
# - /etc/letsencrypt:/etc/letsencrypt:ro
|
||||
ports:
|
||||
- "993:993"
|
||||
- "995:995"
|
||||
|
||||
@@ -14,4 +14,8 @@ services:
|
||||
- ./data:/data
|
||||
- ./mail:/var/mail/vhosts
|
||||
- ./dkim:/var/lib/rspamd/dkim
|
||||
# 生产环境如需第三方客户端校验证书,请取消下面挂载,并在 .env 配置:
|
||||
# LANQIN_TLS_CERT_FILE=/etc/letsencrypt/live/mail.example.com/fullchain.pem
|
||||
# LANQIN_TLS_KEY_FILE=/etc/letsencrypt/live/mail.example.com/privkey.pem
|
||||
# - /etc/letsencrypt:/etc/letsencrypt:ro
|
||||
restart: unless-stopped
|
||||
|
||||
@@ -1,7 +1,21 @@
|
||||
#!/bin/sh
|
||||
set -eu
|
||||
: "${LANQIN_TLS_CERT_FILE:=}"
|
||||
: "${LANQIN_TLS_KEY_FILE:=}"
|
||||
addgroup --system --gid 5000 vmail 2>/dev/null || true
|
||||
adduser --system --uid 5000 --gid 5000 --home /var/mail/vhosts --no-create-home vmail 2>/dev/null || true
|
||||
mkdir -p /var/mail/vhosts
|
||||
chown -R 5000:5000 /var/mail/vhosts
|
||||
TLS_CERT=/etc/ssl/certs/ssl-cert-snakeoil.pem
|
||||
TLS_KEY=/etc/ssl/private/ssl-cert-snakeoil.key
|
||||
if [ -n "$LANQIN_TLS_CERT_FILE" ] || [ -n "$LANQIN_TLS_KEY_FILE" ]; then
|
||||
if [ -f "$LANQIN_TLS_CERT_FILE" ] && [ -f "$LANQIN_TLS_KEY_FILE" ]; then
|
||||
TLS_CERT="$LANQIN_TLS_CERT_FILE"
|
||||
TLS_KEY="$LANQIN_TLS_KEY_FILE"
|
||||
else
|
||||
echo "warning: LANQIN_TLS_CERT_FILE/LANQIN_TLS_KEY_FILE not readable; using snakeoil localhost certificate" >&2
|
||||
fi
|
||||
fi
|
||||
sed -i "s#^ssl_cert = <.*#ssl_cert = <${TLS_CERT}#" /etc/dovecot/dovecot.conf
|
||||
sed -i "s#^ssl_key = <.*#ssl_key = <${TLS_KEY}#" /etc/dovecot/dovecot.conf
|
||||
exec dovecot -F
|
||||
|
||||
@@ -1,8 +1,22 @@
|
||||
#!/bin/sh
|
||||
set -eu
|
||||
: "${LANQIN_PUBLIC_HOSTNAME:=mail.example.com}"
|
||||
: "${LANQIN_TLS_CERT_FILE:=}"
|
||||
: "${LANQIN_TLS_KEY_FILE:=}"
|
||||
TLS_CERT=/etc/ssl/certs/ssl-cert-snakeoil.pem
|
||||
TLS_KEY=/etc/ssl/private/ssl-cert-snakeoil.key
|
||||
if [ -n "$LANQIN_TLS_CERT_FILE" ] || [ -n "$LANQIN_TLS_KEY_FILE" ]; then
|
||||
if [ -f "$LANQIN_TLS_CERT_FILE" ] && [ -f "$LANQIN_TLS_KEY_FILE" ]; then
|
||||
TLS_CERT="$LANQIN_TLS_CERT_FILE"
|
||||
TLS_KEY="$LANQIN_TLS_KEY_FILE"
|
||||
else
|
||||
echo "warning: LANQIN_TLS_CERT_FILE/LANQIN_TLS_KEY_FILE not readable; using snakeoil localhost certificate" >&2
|
||||
fi
|
||||
fi
|
||||
postconf -e "myhostname = ${LANQIN_PUBLIC_HOSTNAME}"
|
||||
postconf -e "myorigin = ${LANQIN_PUBLIC_HOSTNAME}"
|
||||
postconf -e "smtpd_tls_cert_file = ${TLS_CERT}"
|
||||
postconf -e "smtpd_tls_key_file = ${TLS_KEY}"
|
||||
postconf -e "milter_mail_macros = i {mail_addr} {client_addr} {client_name} {auth_authen}"
|
||||
postconf -e "smtpd_milters = inet:rspamd:11332"
|
||||
postconf -e "non_smtpd_milters = inet:rspamd:11332"
|
||||
|
||||
Reference in New Issue
Block a user