package config import ( "fmt" "strings" "sync/atomic" "git.pcux.cn/xuans/Smail/utils/credential" ) // publishedConfig 是旧配置模型的只读兼容快照。 // 新业务应通过 CurrentConfig 读取,禁止在请求处理中直接持有并修改 Instance。 var publishedConfig atomic.Pointer[Config] var hasPublishedConfig atomic.Bool var setupPort atomic.Int64 func IsSetupReady() bool { value := CurrentConfig() if value != nil && value.SetupState.Valid() { return value.SetupState == SetupStateReady } return legacyInitReady() } // PublishConfigSnapshot 发布一份深拷贝,供运行时只读访问。 // 调用方仍可继续使用旧的 Instance 兼容路径,但不能把返回值当作可变全局状态。 func PublishConfigSnapshot(value *Config) { hasPublishedConfig.Store(true) if value == nil { publishedConfig.Store(nil) return } clone := cloneConfig(value) publishedConfig.Store(clone) } // CurrentConfig 返回当前运行时配置的独立副本。 // 返回值可以安全地在业务代码中读取或局部修改,不会改变全局配置快照。 func CurrentConfig() *Config { if value := publishedConfig.Load(); value != nil { return cloneConfig(value) } if hasPublishedConfig.Load() { return nil } if Instance == nil { return nil } return cloneConfig(Instance) } // CurrentHTTPConfig 返回当前进程实际使用的 HTTP 配置。 // 分区配置存在时优先读取分区配置,旧版配置仅作为兼容回退。 func CurrentHTTPConfig() HTTPConfig { if manager := CurrentManager(); manager != nil { runtime := manager.Snapshot() if runtime.Generation != 0 { return cloneHTTPConfig(runtime.Root.HTTP) } } cfg := CurrentConfig() if cfg == nil { return HTTPConfig{} } mode := strings.ToLower(strings.TrimSpace(cfg.TLSMode)) return HTTPConfig{ Port: cfg.HttpPort, HTTPSPort: cfg.HttpsPort, HTTPSEnabled: cfg.HttpsEnabled != 2, TLSMode: mode, TrustedProxyCIDRs: append([]string(nil), cfg.TrustedProxyCIDRs...), } } // CurrentWebDomain 返回当前进程实际使用的 Web 域名。 // 分区配置存在时优先读取分区配置,旧版配置仅作为兼容回退。 func CurrentWebDomain() string { if manager := CurrentManager(); manager != nil { runtime := manager.Snapshot() if runtime.Generation != 0 { return runtime.Root.Domains.Web } } cfg := CurrentConfig() if cfg == nil { return "" } return cfg.WebDomain } // SetRuntimeSetupPort stores the temporary setup listener port outside the persisted configuration. func SetRuntimeSetupPort(port int) { setupPort.Store(int64(port)) } func RuntimeSetupPort() int { return int(setupPort.Load()) } func cloneConfig(source *Config) *Config { if source == nil { return nil } clone := *source clone.Domains = append([]string(nil), source.Domains...) clone.AccessHosts = append([]string(nil), source.AccessHosts...) clone.TrustedProxyCIDRs = append([]string(nil), source.TrustedProxyCIDRs...) clone.PluginAllowList = append([]string(nil), source.PluginAllowList...) if source.Tables != nil { clone.Tables = make(map[string]string, len(source.Tables)) for key, value := range source.Tables { clone.Tables[key] = value } } if source.TablesInitData != nil { clone.TablesInitData = make(map[string]string, len(source.TablesInitData)) for key, value := range source.TablesInitData { clone.TablesInitData[key] = value } } return &clone } type MailPorts struct { SMTP int IMAP int POP3 int } type MailDomains struct { Primary string Accepted []string } func CurrentMailDomains() MailDomains { manager := CurrentManager() if manager != nil { runtime := manager.Snapshot() if runtime.Generation != 0 { return MailDomains{ Primary: runtime.Root.Domains.Primary, Accepted: append([]string(nil), runtime.Root.Domains.Accepted...), } } } cfg := CurrentConfig() if cfg == nil { return MailDomains{} } domains := MailDomains{ Primary: cfg.Domain, Accepted: append([]string(nil), cfg.Domains...), } if domains.Primary == "" && len(domains.Accepted) > 0 { domains.Primary = domains.Accepted[0] } if len(domains.Accepted) == 0 && domains.Primary != "" { domains.Accepted = []string{domains.Primary} } return domains } func CurrentEmailPolicy() EmailConfig { manager := CurrentManager() if manager != nil { runtime := manager.Snapshot() if runtime.Generation != 0 { return runtime.Email.Policy } } cfg := CurrentConfig() if cfg == nil { return EmailConfig{} } return EmailConfig{ SMTPPort: cfg.SMTPPort, SpamFilterLevel: cfg.SpamFilterLevel, LoginVerificationPolicy: cfg.LoginPolicy, ExternalRecoveryEnabled: cfg.ExternalRecoveryEnabled, } } type SMTPRuntimeSettings struct { Relay SMTPConfig Password string VerificationEnabled bool ExternalRecoveryEnabled bool } func CurrentSMTPRuntimeSettings() (SMTPRuntimeSettings, error) { settings := SMTPRuntimeSettings{} if manager := CurrentManager(); manager != nil { runtime := manager.Snapshot() if runtime.Generation != 0 { settings.Relay = runtime.SMTP.Relay settings.ExternalRecoveryEnabled = runtime.Email.Policy.ExternalRecoveryEnabled if settings.Relay.Username != "" { vault, err := credential.NewVault(ROOT_PATH) if err != nil { return SMTPRuntimeSettings{}, fmt.Errorf("create SMTP credential vault: %w", err) } password, err := OpenStoredSecret(vault, settings.Relay.Password) if err != nil { return SMTPRuntimeSettings{}, fmt.Errorf("open SMTP relay password: %w", err) } settings.Password = password } settings.VerificationEnabled = settings.Relay.VerificationEnabled return settings, nil } } cfg := CurrentConfig() if cfg == nil { return SMTPRuntimeSettings{}, fmt.Errorf("runtime configuration is unavailable") } settings.Relay = SMTPConfig{ RelayHost: cfg.SMTPHost, RelayPort: cfg.SMTPPort, Username: cfg.SMTPUsername, Security: cfg.SMTPSecurity, From: cfg.SMTPFrom, FromDomain: cfg.SMTPFromDomain, VerificationEnabled: cfg.VerificationEnabled, LoginPolicy: cfg.LoginPolicy, } settings.Password = cfg.SMTPPassword settings.VerificationEnabled = cfg.VerificationEnabled settings.ExternalRecoveryEnabled = cfg.ExternalRecoveryEnabled return settings, nil } func CurrentMailPorts() MailPorts { ports := MailPorts{SMTP: 25, IMAP: 143, POP3: 110} policy := CurrentEmailPolicy() if policy.SMTPPort > 0 && policy.SMTPPort <= 65535 { ports.SMTP = policy.SMTPPort } if policy.IMAPPort > 0 && policy.IMAPPort <= 65535 { ports.IMAP = policy.IMAPPort } if policy.POP3Port > 0 && policy.POP3Port <= 65535 { ports.POP3 = policy.POP3Port } return ports } func cloneHTTPConfig(source HTTPConfig) HTTPConfig { clone := source clone.TrustedProxyCIDRs = append([]string(nil), source.TrustedProxyCIDRs...) return clone }