accounts/scripts/install_postfix_sendonly.sh
2025-11-01 18:10:46 +08:00

265 lines
8.6 KiB
Bash
Raw Permalink Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

#!/usr/bin/env bash
#
# install_postfix_sendonly.sh v1.1
# Postfix + OpenDKIM + SPF + DMARCSend-Only 模式)
# --------------------------------------------------------
# ✅ 自动部署轻量级 Postfix 发信服务,仅启用 submission(587) STARTTLS
# ✅ 集成 DKIM 签名、SPF/DMARC/rDNS/HELO 校验模板
# ✅ 兼容阿里云 / Cloudflare DNS 输出格式
# ✅ 适配 Ubuntu / Debian / RHEL 系列系统
# --------------------------------------------------------
# Author: Pan Haitao @ svc.plus
#
set -euo pipefail
DOMAIN="svc.plus"
HOSTNAME="smtp.${DOMAIN}"
SERVER_IP="52.196.108.28"
EMAIL="no-reply@${DOMAIN}"
CERT="/etc/ssl/${DOMAIN}.pem"
KEY="/etc/ssl/${DOMAIN}.key"
DKIM_SELECTOR="mail"
DKIM_KEY_DIR="/etc/opendkim/keys/${DOMAIN}"
TMP_PASS="$(openssl rand -base64 12)"
ACTION="${1:-help}"
log(){ echo -e "\033[1;36m$*\033[0m"; }
die(){ echo "$*"; exit 1; }
check_root(){ [ "$EUID" -eq 0 ] || die "请用 root 运行"; }
# ------------------ 应用端配置 ------------------
show_app_config(){
cat <<EOF
📦 应用端 SMTP 配置:
----------------------------------------------------------
smtp:
host: "${HOSTNAME}"
port: 587
username: "${EMAIL}"
password: "${TMP_PASS}"
from: "XControl Account <${EMAIL}>"
tls:
mode: "auto"
insecureSkipVerify: false
auth: "login"
----------------------------------------------------------
EOF
echo "首发密码(仅本次显示):${TMP_PASS}"
}
check_send_email(){
local SMTP_HOST="${HOSTNAME}"
local SMTP_PORT=587
local SMTP_USER="${EMAIL}"
local SMTP_PASS="${TMP_PASS}"
local TEST_TO="${1:-${EMAIL}}"
echo "🔍 Testing outbound mail via ${SMTP_HOST}:${SMTP_PORT}"
echo "-------------------------------------------------------------"
swaks --server "${SMTP_HOST}:${SMTP_PORT}" \
--tls --protocol ESMTP \
--auth LOGIN \
--auth-user "${SMTP_USER}" \
--auth-password "${SMTP_PASS}" \
--from "${SMTP_USER}" \
--to "${TEST_TO}" \
--header "From: XControl Mail System <${SMTP_USER}>" \
--header "Subject: ✅ Postfix 587-only 测试 $(date '+%F %T')" \
--body "测试发信 $(date '+%F %T')" \
--timeout 20
echo "-------------------------------------------------------------"
}
# ------------------ 安装依赖 ------------------
ensure_packages(){
log "📦 安装 Postfix + OpenDKIM..."
export DEBIAN_FRONTEND=noninteractive
apt update -qq
apt install -y postfix opendkim opendkim-tools mailutils swaks dnsutils openssl curl
}
# ------------------ 证书 ------------------
verify_cert(){
if [[ -f "$CERT" && -f "$KEY" ]]; then
log "🔐 使用自有 SSL 证书:$CERT"
openssl x509 -noout -subject -dates -in "$CERT" || true
else
log "⚠️ 未检测到 ${CERT}/${KEY},生成自签证书..."
mkdir -p /etc/ssl
openssl req -x509 -nodes -newkey rsa:2048 -days 365 \
-subj "/CN=${HOSTNAME}" -keyout "$KEY" -out "$CERT"
fi
}
# ------------------ DKIM ------------------
deploy_dkim(){
log "🔏 配置 OpenDKIM..."
mkdir -p "${DKIM_KEY_DIR}"
cd "${DKIM_KEY_DIR}"
if [ ! -f "${DKIM_SELECTOR}.private" ]; then
opendkim-genkey -s "${DKIM_SELECTOR}" -d "${DOMAIN}"
chown opendkim:opendkim "${DKIM_SELECTOR}.private" "${DKIM_SELECTOR}.txt"
chmod 600 "${DKIM_SELECTOR}.private"
fi
cat >/etc/opendkim.conf <<EOF
Syslog yes
UMask 002
Mode sv
Canonicalization relaxed/simple
SubDomains no
KeyTable /etc/opendkim/key.table
SigningTable /etc/opendkim/signing.table
ExternalIgnoreList refile:/etc/opendkim/trusted.hosts
InternalHosts refile:/etc/opendkim/trusted.hosts
Socket inet:8891@localhost
UserID opendkim
EOF
cat >/etc/opendkim/key.table <<EOF
${DKIM_SELECTOR}._domainkey.${DOMAIN} ${DOMAIN}:${DKIM_SELECTOR}:${DKIM_KEY_DIR}/${DKIM_SELECTOR}.private
EOF
cat >/etc/opendkim/signing.table <<EOF
*@${DOMAIN} ${DKIM_SELECTOR}._domainkey.${DOMAIN}
EOF
cat >/etc/opendkim/trusted.hosts <<EOF
127.0.0.1
localhost
${DOMAIN}
EOF
chown -R opendkim:opendkim /etc/opendkim
systemctl enable --now opendkim
}
# ------------------ Postfix ------------------
deploy_postfix(){
verify_cert
log "🚀 配置 Postfix Send-only (587 STARTTLS)..."
postconf -e "myhostname=${HOSTNAME}"
postconf -e "myorigin=${DOMAIN}"
postconf -e "inet_interfaces=all"
postconf -e "inet_protocols=all"
postconf -e "mydestination="
postconf -e "relayhost="
postconf -e "smtpd_banner=${HOSTNAME} ESMTP"
postconf -e "mynetworks=127.0.0.0/8 [::1]/128"
postconf -e "relay_domains=${DOMAIN}"
postconf -e "smtpd_tls_cert_file=${CERT}"
postconf -e "smtpd_tls_key_file=${KEY}"
postconf -e "smtpd_tls_security_level=encrypt"
postconf -e "smtp_tls_security_level=may"
postconf -e "smtp_tls_note_starttls_offer=yes"
postconf -e "smtpd_tls_auth_only=yes"
postconf -e "milter_default_action=accept"
postconf -e "milter_protocol=6"
postconf -e "smtpd_milters=inet:localhost:8891"
postconf -e "non_smtpd_milters=inet:localhost:8891"
cat >/etc/postfix/master.cf <<'EOF'
# ==========================================================
# Postfix master.cf (Send-only, STARTTLS on submission/587)
# ==========================================================
pickup unix n - y 60 1 pickup
cleanup unix n - y - 0 cleanup
qmgr unix n - n 300 1 qmgr
proxymap unix - - n - - proxymap
submission inet n - y - - smtpd
-o syslog_name=postfix/submission
-o smtpd_tls_security_level=encrypt
-o smtpd_sasl_auth_enable=no
-o smtpd_relay_restrictions=permit_mynetworks,reject_unauth_destination
-o milter_macro_daemon_name=ORIGINATING
EOF
systemctl enable --now postfix
systemctl restart postfix
sleep 1
ss -tlnp | grep -qE ':587\s' \
&& log "✅ Postfix 已启用 submission(587) (STARTTLS Send-Only 模式)" \
|| die "❌ 端口 587 未监听,请执行 journalctl -xeu postfix"
}
# ------------------ DNS 模板 ------------------
show_dns_record(){
log "🌐 生成 DNS 模板SPF / DKIM / DMARC / rDNS / HELO..."
local DKIM_FILE="${DKIM_KEY_DIR}/${DKIM_SELECTOR}.txt"
local DKIM_ONE_LINE="<DKIM 公钥未生成>"
if [[ -f "$DKIM_FILE" ]]; then
DKIM_ONE_LINE=$(grep -v '^;' "$DKIM_FILE" | tr -d '\n' \
| sed -E 's/[()]//g; s/"//g; s/\s+/ /g; s/IN TXT//; s/mail._domainkey.*v=/v=/; s/\s*v=DKIM1/v=DKIM1/')
fi
echo "----------------------------------------------------------"
echo "A smtp.${DOMAIN} ${SERVER_IP}"
echo "MX ${DOMAIN} smtp.${DOMAIN}."
echo "SPF @ \"v=spf1 a:smtp.${DOMAIN} -all\""
echo "DKIM ${DKIM_SELECTOR}._domainkey \"${DKIM_ONE_LINE}\""
echo "DMARC _dmarc \"v=DMARC1; p=none; rua=mailto:postmaster@${DOMAIN}\""
echo "rDNS (请让 ${SERVER_IP} 反查为 ${HOSTNAME})"
echo "HELO (EHLO 输出应为 ${HOSTNAME})"
echo "----------------------------------------------------------"
}
# ------------------ 自检 ------------------
check_self(){
log "🔍 自检 SPF / DKIM / DMARC / rDNS / 端口 ..."
echo
echo "SPF:"; dig +short TXT ${DOMAIN} | grep -i spf || echo "⚠️ 未配置 SPF"; echo
echo "DKIM:"; dig +short TXT ${DKIM_SELECTOR}._domainkey.${DOMAIN} || echo "⚠️ 未配置 DKIM"; echo
echo "DMARC:"; dig +short TXT _dmarc.${DOMAIN} || echo "⚠️ 未配置 DMARC"; echo
echo "rDNS:"; dig +short -x ${SERVER_IP} || echo "⚠️ 未配置反向解析"; echo
echo "端口监听:"; ss -tlnp | grep -E '(:25|:587|:8891)\s' || echo "⚠️ 端口未监听"; echo
echo "OpenDKIM testkey"; opendkim-testkey -d "${DOMAIN}" -s "${DKIM_SELECTOR}" -vvv || true
}
# ------------------ 卸载 ------------------
uninstall_reset(){
log "🧹 停止并清理..."
systemctl stop postfix || true
systemctl stop opendkim || true
apt purge -y postfix opendkim opendkim-tools || true
apt autoremove -y || true
rm -rf /etc/postfix /etc/opendkim /var/log/mail*
log "✅ 已清理完成(证书未动)。"
}
# ------------------ 主流程 ------------------
check_root
case "${ACTION}" in
deploy)
ensure_packages
deploy_dkim
deploy_postfix
show_dns_record ;;
upgrade)
log "⬆️ 更新配置并重启..."
deploy_dkim
deploy_postfix
show_dns_record ;;
show)
case "${2:-}" in
dns_record) show_dns_record ;;
app_config) show_app_config ;;
*) echo "用法: $0 show {dns_record|app_config}" ;;
esac ;;
check)
case "${2:-}" in
self) check_self ;;
send_email) check_send_email ;;
*) echo "用法: $0 check {self|send_email}" ;;
esac ;;
uninstall|reset)
uninstall_reset ;;
help|--help|-h|*)
echo "用法: $0 {deploy|upgrade|show {dns_record|app_config}|check {self|send_email}|uninstall}" ;;
esac