feat: 管理后台一键从 Caddy 获取证书 + TLS 证书热加载
- 域名编辑页新增“从 Caddy 获取证书”按钮:一键把本机 Caddy 已签发的 证书与私钥导入该域名的 TLS 目录并自动启用 TLS,支持通配符证书 (如 *.example.com 可匹配 mail.example.com),成功/失败均回显横幅 - 新增 internal/caddycert:搜索 Caddy 证书存储(同步镜像目录优先、 caddy.data_dir 与常见位置兜底),校验密钥对/有效期/SAN,并给出 可操作的中文错误提示(未找到/证书无效/权限不足) - install.sh 新增 setup-caddy-cert:安装 root 权限的 systemd path+timer 同步任务(mailgo-caddy-sync),把 Caddy 证书树镜像到 /srv/mail_go/tls/caddy(证书续期后自动更新、每日兜底),另授予 ACL 作为直接读取兜底;install 时自动检测并配置 - 新增 [caddy] data_dir 配置节,支持自定义 Caddy 数据目录 - 新增 internal/tlsutil:TLS 证书热加载器,每次握手按需重载证书 文件(mtime 检测),重载失败继续使用旧证书兜底并节流重试; 应用于 SMTPS 465/IMAPS 993/POP3S 995 与 STARTTLS,导入或上传 新证书后无需重启服务即生效 - 证书来源动态切换:协议显式配置优先,否则取首个启用 TLS 且有证书 的域名(10 秒缓存),新域名一键导入证书后自动切换 - 更新 README 与界面文案(去掉“重启服务生效”提示)
This commit is contained in:
14 files changed
+1253
-83
No files matched your search
@@ -15,6 +15,7 @@ import (
|
||||
"mail_go/internal/outbound"
|
||||
"mail_go/internal/storage"
|
||||
"mail_go/internal/store"
|
||||
"mail_go/internal/tlsutil"
|
||||
|
||||
"github.com/emersion/go-message/mail"
|
||||
"github.com/emersion/go-sasl"
|
||||
@@ -31,27 +32,25 @@ const (
|
||||
|
||||
// SMTPServer wraps go-smtp servers and provides local mail delivery.
|
||||
type SMTPServer struct {
|
||||
stores *store.Stores
|
||||
storage *storage.AttachmentStorage
|
||||
outbound *outbound.Manager
|
||||
cfg config.SMTPConfig
|
||||
stores *store.Stores
|
||||
storage *storage.AttachmentStorage
|
||||
outbound *outbound.Manager
|
||||
cfg config.SMTPConfig
|
||||
tlsLoader *tlsutil.Loader
|
||||
}
|
||||
|
||||
// NewSMTPServer creates a new SMTP server instance.
|
||||
func NewSMTPServer(cfg config.SMTPConfig, stores *store.Stores, attStorage *storage.AttachmentStorage, ob *outbound.Manager) *SMTPServer {
|
||||
return &SMTPServer{stores: stores, storage: attStorage, outbound: ob, cfg: cfg}
|
||||
// NewSMTPServer creates a new SMTP server instance. tlsLoader may be nil
|
||||
// when TLS is not configured.
|
||||
func NewSMTPServer(cfg config.SMTPConfig, stores *store.Stores, attStorage *storage.AttachmentStorage, ob *outbound.Manager, tlsLoader *tlsutil.Loader) *SMTPServer {
|
||||
return &SMTPServer{stores: stores, storage: attStorage, outbound: ob, cfg: cfg, tlsLoader: tlsLoader}
|
||||
}
|
||||
|
||||
func (s *SMTPServer) tlsConfig() (*tls.Config, error) {
|
||||
if s.cfg.TLSCert == "" || s.cfg.TLSKey == "" {
|
||||
if s.tlsLoader == nil {
|
||||
return nil, fmt.Errorf("SMTP TLS certificate or key not configured")
|
||||
}
|
||||
|
||||
cert, err := tls.LoadX509KeyPair(s.cfg.TLSCert, s.cfg.TLSKey)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("failed to load SMTP TLS certificate: %w", err)
|
||||
}
|
||||
return &tls.Config{Certificates: []tls.Certificate{cert}}, nil
|
||||
// GetCertificate 每次握手按需重载证书,证书更新后无需重启服务
|
||||
return &tls.Config{GetCertificate: s.tlsLoader.GetCertificate}, nil
|
||||
}
|
||||
|
||||
func (s *SMTPServer) newServer(addr string, mode smtpMode, tlsConfig *tls.Config) *smtp.Server {
|
||||
|
||||
Reference in New Issue
Block a user