This commit is contained in:
parent
e5931dbf71
commit
99919778a4
@ -6,7 +6,6 @@ import (
|
||||
|
||||
"git.pcux.cn/xuans/Smail/config"
|
||||
"git.pcux.cn/xuans/Smail/internal/buildinfo"
|
||||
"git.pcux.cn/xuans/Smail/listen/cron_server"
|
||||
"git.pcux.cn/xuans/Smail/res_init"
|
||||
"git.pcux.cn/xuans/Smail/services/runtimelog"
|
||||
"git.pcux.cn/xuans/Smail/utils/runtimepath"
|
||||
@ -37,7 +36,6 @@ func Run() (err error) {
|
||||
return fmt.Errorf("启动运行日志失败: %w", err)
|
||||
}
|
||||
defer finishRuntimeLogs(runtimeLogs, &err)
|
||||
config.MaintainManagedSSLHistory()
|
||||
|
||||
build := buildinfo.Current()
|
||||
if build.Version == "dev" {
|
||||
@ -46,9 +44,6 @@ func Run() (err error) {
|
||||
|
||||
log.WithFields(log.Fields{"action": "启动主服务", "object": "Smail", "result": "started"}).Infof("服务启动,版本:%s", build.Version)
|
||||
|
||||
// 启动定时任务。
|
||||
go cron_server.Start()
|
||||
|
||||
// 启动核心服务。
|
||||
if err := res_init.Init(build.Version); err != nil {
|
||||
return err
|
||||
|
||||
@ -13,20 +13,35 @@ import (
|
||||
"strconv"
|
||||
)
|
||||
|
||||
func Start() {
|
||||
// Runtime 将证书维护绑定到核心服务,避免恢复配置时并发争用状态锁。
|
||||
type Runtime struct {
|
||||
cancel context.CancelFunc
|
||||
done chan struct{}
|
||||
}
|
||||
|
||||
// 第一次启动,等待到初始化完成
|
||||
if cfg := config.CurrentConfig(); cfg == nil || !cfg.IsSetupReady() {
|
||||
for {
|
||||
time.Sleep(1 * time.Minute)
|
||||
if cfg := config.CurrentConfig(); cfg != nil && cfg.IsSetupReady() {
|
||||
break
|
||||
}
|
||||
}
|
||||
// Start 只能在配置恢复及核心服务初始化完成后调用。
|
||||
func Start() *Runtime {
|
||||
ctx, cancel := context.WithCancel(context.Background())
|
||||
runtime := &Runtime{cancel: cancel, done: make(chan struct{})}
|
||||
go func() {
|
||||
defer close(runtime.done)
|
||||
sslUpdateLoop(ctx)
|
||||
}()
|
||||
return runtime
|
||||
}
|
||||
|
||||
// Stop 取消后续维护并等待当前操作结束;超时不能继续关闭配置及数据库。
|
||||
func (runtime *Runtime) Stop(ctx context.Context) error {
|
||||
if runtime == nil {
|
||||
return nil
|
||||
}
|
||||
runtime.cancel()
|
||||
select {
|
||||
case <-runtime.done:
|
||||
return nil
|
||||
case <-ctx.Done():
|
||||
return ctx.Err()
|
||||
}
|
||||
|
||||
go sslUpdateLoop()
|
||||
|
||||
}
|
||||
|
||||
// 只检查外部维护的本地邮件证书是否更新,不触发 ACME。
|
||||
@ -37,6 +52,13 @@ func sslCheck() {
|
||||
|
||||
// 每轮读取最新策略,切换反代模式后不再等待 HTTP 挑战或执行自动续期。
|
||||
func maintainSSL() {
|
||||
maintainSSLContext(context.Background())
|
||||
}
|
||||
|
||||
func maintainSSLContext(ctx context.Context) {
|
||||
if ctx.Err() != nil {
|
||||
return
|
||||
}
|
||||
config.MaintainManagedSSLHistory()
|
||||
cfg := config.CurrentCertificateConfig()
|
||||
if cfg == nil || !cfg.IsSetupReady() {
|
||||
@ -46,30 +68,43 @@ func maintainSSL() {
|
||||
sslCheck()
|
||||
return
|
||||
}
|
||||
if cfg.EffectiveSSLType() == config.SSLTypeAutoHTTP && !waitHTTPReady() {
|
||||
if cfg.EffectiveSSLType() == config.SSLTypeAutoHTTP && !waitHTTPReadyContext(ctx) {
|
||||
return
|
||||
}
|
||||
if ctx.Err() != nil {
|
||||
return
|
||||
}
|
||||
ssl.Update(true)
|
||||
}
|
||||
|
||||
// 每天检查证书,外部管理模式仅保留本地证书变更检查。
|
||||
func sslUpdateLoop() {
|
||||
func sslUpdateLoop(ctx context.Context) {
|
||||
lastRenewal := time.Time{}
|
||||
for {
|
||||
for ctx.Err() == nil {
|
||||
if time.Since(lastRenewal) >= 24*time.Hour {
|
||||
maintainSSL()
|
||||
maintainSSLContext(ctx)
|
||||
lastRenewal = time.Now()
|
||||
} else {
|
||||
config.MaintainManagedSSLHistory()
|
||||
sslCheck()
|
||||
}
|
||||
// 每分钟清理已到期的上一套证书;自动签发仍最多每天检查一次。
|
||||
time.Sleep(time.Minute)
|
||||
timer := time.NewTimer(time.Minute)
|
||||
select {
|
||||
case <-ctx.Done():
|
||||
timer.Stop()
|
||||
return
|
||||
case <-timer.C:
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// 新增:HTTP 就绪探测(最多等待 ~90 秒)
|
||||
func waitHTTPReady() bool {
|
||||
return waitHTTPReadyContext(context.Background())
|
||||
}
|
||||
|
||||
func waitHTTPReadyContext(ctx context.Context) bool {
|
||||
port := config.CurrentHTTPConfig().Port
|
||||
if port < 1 || port > 65535 {
|
||||
log.Warnf("invalid HTTP port %d, skipping SSL update this round", port)
|
||||
@ -84,10 +119,13 @@ func waitHTTPReady() bool {
|
||||
client := &http.Client{Timeout: 2 * time.Second}
|
||||
|
||||
for i := 0; i < 90; i++ {
|
||||
if ctx.Err() != nil {
|
||||
return false
|
||||
}
|
||||
if !config.CurrentCertificateConfig().AutomaticSSLEnabled() {
|
||||
return false
|
||||
}
|
||||
requestContext, cancel := context.WithTimeout(context.Background(), 2*time.Second)
|
||||
requestContext, cancel := context.WithTimeout(ctx, 2*time.Second)
|
||||
request := (&http.Request{
|
||||
Method: http.MethodGet,
|
||||
URL: target,
|
||||
@ -105,7 +143,13 @@ func waitHTTPReady() bool {
|
||||
log.Infof("HTTP ready: %s", target.String())
|
||||
return true
|
||||
}
|
||||
time.Sleep(1 * time.Second)
|
||||
timer := time.NewTimer(time.Second)
|
||||
select {
|
||||
case <-ctx.Done():
|
||||
timer.Stop()
|
||||
return false
|
||||
case <-timer.C:
|
||||
}
|
||||
}
|
||||
log.Warnf("HTTP not ready after 90s, skipping SSL update this round")
|
||||
return false
|
||||
|
||||
@ -16,6 +16,7 @@ import (
|
||||
"git.pcux.cn/xuans/Smail/db/migration"
|
||||
"git.pcux.cn/xuans/Smail/dto/parsemail"
|
||||
"git.pcux.cn/xuans/Smail/hooks"
|
||||
"git.pcux.cn/xuans/Smail/listen/cron_server"
|
||||
"git.pcux.cn/xuans/Smail/listen/http_server"
|
||||
"git.pcux.cn/xuans/Smail/listen/imap_server"
|
||||
"git.pcux.cn/xuans/Smail/listen/pop3_server"
|
||||
@ -52,6 +53,7 @@ var credentialService *credentials.Service
|
||||
var webAuthnRuntimeService *webauthnservice.Service
|
||||
var logRuntime *logstore.Runtime
|
||||
var outboxRuntime *outbox.Runtime
|
||||
var certificateRuntime *cron_server.Runtime
|
||||
|
||||
func startConfigManager() error {
|
||||
root := filepath.Join(config.ROOT_PATH, "config")
|
||||
@ -261,6 +263,7 @@ func Init(serverVersion string) error {
|
||||
}
|
||||
|
||||
log.WithFields(log.Fields{"action": "启动邮箱监听", "object": "SMTP / IMAP / POP3", "result": "started"}).Info("邮箱监听任务已提交,具体监听结果由各服务报告")
|
||||
certificateRuntime = cron_server.Start()
|
||||
restart.SetCurrent(restart.NewSignalTrigger(signal.RestartChan))
|
||||
|
||||
select {
|
||||
@ -293,6 +296,14 @@ func startService(serviceErrors chan<- error, name string, service func() error)
|
||||
|
||||
func stopServices() error {
|
||||
restart.SetCurrent(nil)
|
||||
// 先结束证书维护,再关闭其依赖;超时直接上报,禁止进入下一轮配置恢复。
|
||||
certificateCtx, certificateCancel := context.WithTimeout(context.Background(), 25*time.Second)
|
||||
certificateErr := certificateRuntime.Stop(certificateCtx)
|
||||
certificateCancel()
|
||||
if certificateErr != nil {
|
||||
return fmt.Errorf("certificate maintenance did not stop; dependencies retained: %w", certificateErr)
|
||||
}
|
||||
certificateRuntime = nil
|
||||
stopAdminResetControl()
|
||||
smtp_server.Stop()
|
||||
http_server.HttpsStop()
|
||||
|
||||
Loading…
Reference in New Issue
Block a user