feat: 实现配置校验与运维命令及优雅停机
This commit is contained in:
+142
-5
@@ -2,11 +2,16 @@ package config
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"net"
|
||||
"os"
|
||||
"strings"
|
||||
|
||||
"go.yaml.in/yaml/v3"
|
||||
)
|
||||
|
||||
// MaxBodyBytesCap 是 max_body_bytes 的硬上限(DEVELOPMENT 11.1)。
|
||||
const MaxBodyBytesCap = 262144
|
||||
|
||||
// Config 对应 DEVELOPMENT 第 11.1 节的配置文件。
|
||||
type Config struct {
|
||||
Listen string `yaml:"listen"`
|
||||
@@ -65,7 +70,7 @@ func Default() Config {
|
||||
TrustedProxies: nil,
|
||||
DataDir: "./data",
|
||||
Limits: LimitsConfig{
|
||||
MaxBodyBytes: 262144,
|
||||
MaxBodyBytes: MaxBodyBytesCap,
|
||||
MaxMetaBytes: 4096,
|
||||
MaxFrameBytes: 786432,
|
||||
MaxTTLSeconds: 2592000,
|
||||
@@ -101,16 +106,148 @@ func Load(path string) (Config, error) {
|
||||
if err := yaml.Unmarshal(data, &cfg); err != nil {
|
||||
return Config{}, fmt.Errorf("parse config: %w", err)
|
||||
}
|
||||
applyEmptyDefaults(&cfg)
|
||||
return cfg, nil
|
||||
}
|
||||
|
||||
func applyEmptyDefaults(cfg *Config) {
|
||||
def := Default()
|
||||
if cfg.Listen == "" {
|
||||
cfg.Listen = Default().Listen
|
||||
cfg.Listen = def.Listen
|
||||
}
|
||||
if cfg.DataDir == "" {
|
||||
cfg.DataDir = Default().DataDir
|
||||
cfg.DataDir = def.DataDir
|
||||
}
|
||||
if cfg.SQLiteSynchronous == "" {
|
||||
cfg.SQLiteSynchronous = Default().SQLiteSynchronous
|
||||
cfg.SQLiteSynchronous = def.SQLiteSynchronous
|
||||
}
|
||||
return cfg, nil
|
||||
if cfg.Log.Level == "" {
|
||||
cfg.Log.Level = def.Log.Level
|
||||
}
|
||||
if cfg.Limits.MaxBodyBytes == 0 {
|
||||
cfg.Limits.MaxBodyBytes = def.Limits.MaxBodyBytes
|
||||
}
|
||||
if cfg.Limits.MaxMetaBytes == 0 {
|
||||
cfg.Limits.MaxMetaBytes = def.Limits.MaxMetaBytes
|
||||
}
|
||||
if cfg.Limits.MaxFrameBytes == 0 {
|
||||
cfg.Limits.MaxFrameBytes = def.Limits.MaxFrameBytes
|
||||
}
|
||||
if cfg.Limits.MaxTTLSeconds == 0 {
|
||||
cfg.Limits.MaxTTLSeconds = def.Limits.MaxTTLSeconds
|
||||
}
|
||||
if cfg.Limits.MaxScheduleSeconds == 0 {
|
||||
cfg.Limits.MaxScheduleSeconds = def.Limits.MaxScheduleSeconds
|
||||
}
|
||||
if cfg.Limits.MaxGroupMembers == 0 {
|
||||
cfg.Limits.MaxGroupMembers = def.Limits.MaxGroupMembers
|
||||
}
|
||||
if cfg.Limits.GraceSeconds == 0 {
|
||||
cfg.Limits.GraceSeconds = def.Limits.GraceSeconds
|
||||
}
|
||||
if cfg.Limits.AckTimeoutSeconds == 0 {
|
||||
cfg.Limits.AckTimeoutSeconds = def.Limits.AckTimeoutSeconds
|
||||
}
|
||||
if cfg.Limits.DeliveryWindow == 0 {
|
||||
cfg.Limits.DeliveryWindow = def.Limits.DeliveryWindow
|
||||
}
|
||||
if cfg.Limits.ReceiptWindow == 0 {
|
||||
cfg.Limits.ReceiptWindow = def.Limits.ReceiptWindow
|
||||
}
|
||||
if cfg.Limits.RequestsPerSecond == 0 {
|
||||
cfg.Limits.RequestsPerSecond = def.Limits.RequestsPerSecond
|
||||
}
|
||||
}
|
||||
|
||||
// Validate 校验 DEVELOPMENT 11.1 全部字段;拒绝 max_body_bytes > 262144。
|
||||
func (c Config) Validate() error {
|
||||
var errs []string
|
||||
if strings.TrimSpace(c.Listen) == "" {
|
||||
errs = append(errs, "listen is required")
|
||||
}
|
||||
if strings.TrimSpace(c.DataDir) == "" {
|
||||
errs = append(errs, "data_dir is required")
|
||||
}
|
||||
sync := strings.ToUpper(strings.TrimSpace(c.SQLiteSynchronous))
|
||||
if sync != "FULL" && sync != "NORMAL" {
|
||||
errs = append(errs, "sqlite_synchronous must be FULL or NORMAL")
|
||||
}
|
||||
switch strings.ToLower(strings.TrimSpace(c.Log.Level)) {
|
||||
case "debug", "info", "warn", "warning", "error":
|
||||
default:
|
||||
errs = append(errs, "log.level must be debug, info, warn, or error")
|
||||
}
|
||||
if c.Limits.MaxBodyBytes <= 0 {
|
||||
errs = append(errs, "limits.max_body_bytes must be > 0")
|
||||
}
|
||||
if c.Limits.MaxBodyBytes > MaxBodyBytesCap {
|
||||
errs = append(errs, fmt.Sprintf("limits.max_body_bytes must be <= %d", MaxBodyBytesCap))
|
||||
}
|
||||
if c.Limits.MaxMetaBytes <= 0 {
|
||||
errs = append(errs, "limits.max_meta_bytes must be > 0")
|
||||
}
|
||||
if c.Limits.MaxFrameBytes <= 0 {
|
||||
errs = append(errs, "limits.max_frame_bytes must be > 0")
|
||||
}
|
||||
if c.Limits.MaxFrameBytes < c.Limits.MaxBodyBytes {
|
||||
errs = append(errs, "limits.max_frame_bytes must be >= limits.max_body_bytes")
|
||||
}
|
||||
if c.Limits.MaxTTLSeconds <= 0 {
|
||||
errs = append(errs, "limits.max_ttl_seconds must be > 0")
|
||||
}
|
||||
if c.Limits.MaxScheduleSeconds <= 0 {
|
||||
errs = append(errs, "limits.max_schedule_seconds must be > 0")
|
||||
}
|
||||
if c.Limits.MaxGroupMembers <= 0 {
|
||||
errs = append(errs, "limits.max_group_members must be > 0")
|
||||
}
|
||||
if c.Limits.GraceSeconds < 0 {
|
||||
errs = append(errs, "limits.grace_seconds must be >= 0")
|
||||
}
|
||||
if c.Limits.AckTimeoutSeconds <= 0 {
|
||||
errs = append(errs, "limits.ack_timeout_seconds must be > 0")
|
||||
}
|
||||
if c.Limits.DeliveryWindow <= 0 {
|
||||
errs = append(errs, "limits.delivery_window must be > 0")
|
||||
}
|
||||
if c.Limits.ReceiptWindow <= 0 {
|
||||
errs = append(errs, "limits.receipt_window must be > 0")
|
||||
}
|
||||
if c.Limits.RequestsPerSecond <= 0 {
|
||||
errs = append(errs, "limits.requests_per_second must be > 0")
|
||||
}
|
||||
if c.Limits.MaxPendingPerSender < 0 {
|
||||
errs = append(errs, "limits.max_pending_per_sender must be >= 0")
|
||||
}
|
||||
if c.Limits.MaxPendingPerReceiver < 0 {
|
||||
errs = append(errs, "limits.max_pending_per_receiver must be >= 0")
|
||||
}
|
||||
if c.SessionIdleDays < 0 {
|
||||
errs = append(errs, "session_idle_days must be >= 0")
|
||||
}
|
||||
if c.RecordRetentionDays < 0 {
|
||||
errs = append(errs, "record_retention_days must be >= 0")
|
||||
}
|
||||
if c.IdempotencyHours < 0 {
|
||||
errs = append(errs, "idempotency_hours must be >= 0")
|
||||
}
|
||||
if c.ReceiptRetentionDays < 0 {
|
||||
errs = append(errs, "receipt_retention_days must be >= 0")
|
||||
}
|
||||
cert := strings.TrimSpace(c.TLS.CertFile)
|
||||
key := strings.TrimSpace(c.TLS.KeyFile)
|
||||
if (cert == "") != (key == "") {
|
||||
errs = append(errs, "tls.cert_file and tls.key_file must both be set or both empty")
|
||||
}
|
||||
for _, cidr := range c.TrustedProxies {
|
||||
if _, _, err := net.ParseCIDR(strings.TrimSpace(cidr)); err != nil {
|
||||
errs = append(errs, fmt.Sprintf("trusted_proxies entry %q is not a valid CIDR", cidr))
|
||||
}
|
||||
}
|
||||
if len(errs) > 0 {
|
||||
return fmt.Errorf("invalid config: %s", strings.Join(errs, "; "))
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// PathFromEnv 返回 NIXMSG_CONFIG 或默认 ./config.yaml。
|
||||
|
||||
Reference in New Issue
Block a user