package config import ( "log" "os" "sync" "gopkg.in/yaml.v3" ) const DefaultConfigPath = "/etc/meshgo/config.yaml" // Config 主配置结构 type Config struct { Server ServerConfig `yaml:"server"` Auth AuthConfig `yaml:"auth"` Logging LoggingConfig `yaml:"logging"` Database DatabaseConfig `yaml:"database"` Admin AdminConfig `yaml:"admin"` } // ServerConfig MQTT 监听相关配置 type ServerConfig struct { // TCP 监听地址,如 :1883 TCPAddr string `yaml:"tcp_addr"` // WebSocket 监听地址,如 :8883(留空则不启动) WSAddr string `yaml:"ws_addr"` // 最大并发连接数,0 表示不限 MaxConnections int `yaml:"max_connections"` // 客户端消息写入超时(秒) WriteTimeout int `yaml:"write_timeout"` } // AuthConfig 认证配置 type AuthConfig struct { // 是否开启用户名/密码认证 Enabled bool `yaml:"enabled"` // 允许匿名连接 AllowAnonymous bool `yaml:"allow_anonymous"` // 内置用户列表(简单场景使用) Users []UserEntry `yaml:"users"` } // UserEntry 单条用户凭证 type UserEntry struct { Username string `yaml:"username"` Password string `yaml:"password"` } // LoggingConfig 日志配置 type LoggingConfig struct { // debug / info / warn / error Level string `yaml:"level"` // 日志文件路径,留空则输出到 stdout File string `yaml:"file"` } // DatabaseConfig 数据库配置 type DatabaseConfig struct { // 是否启用数据库,默认 false Enabled bool `yaml:"enabled"` // 数据库类型:sqlite3(默认) / mysql Type string `yaml:"type"` // SQLite 数据库文件路径(相对于 dataDir) // MySQL 留空,DSN 填 DSN 字段 File string `yaml:"file"` // MySQL DSN,如 user:password@tcp(127.0.0.1:3306)/meshgo // SQLite 模式下此字段被忽略 DSN string `yaml:"dsn"` } // AdminConfig HTTP 管理界面配置 type AdminConfig struct { // 是否启用管理界面,默认 false Enabled bool `yaml:"enabled"` // HTTP 监听端口,留空则默认 :8080 Port string `yaml:"port"` // 认证类型:none(默认) / basic / token(预留) AuthType string `yaml:"auth_type"` // Basic 认证用户名 Username string `yaml:"username"` // Basic 认证密码 Password string `yaml:"password"` } // --------------------------------------------------------------------------- // 全局单例 // --------------------------------------------------------------------------- var ( current *Config mu sync.RWMutex ) // Get 返回当前配置的只读快照 func Get() *Config { mu.RLock() defer mu.RUnlock() return current } // Load 从磁盘加载配置,替换全局单例 func Load(path string) error { data, err := os.ReadFile(path) if err != nil { return err } cfg := defaultConfig() if err = yaml.Unmarshal(data, cfg); err != nil { return err } // 补充缺失字段的默认值 applyDefaults(cfg) mu.Lock() current = cfg mu.Unlock() log.Printf("[config] 已加载配置文件: %s", path) return nil } // Reload 重新读取配置文件(SIGHUP 时调用) func Reload(path string) { log.Printf("[config] 收到重载信号,重新读取: %s", path) if err := Load(path); err != nil { log.Printf("[config] 重载失败,继续使用旧配置: %v", err) } } // EnsureConfigComplete 确保配置文件存在且字段完整 // - 文件不存在:创建带注释的默认配置 // - 文件存在但缺失字段:补充缺失字段并写回文件 func EnsureConfigComplete(path string) error { data, err := os.ReadFile(path) if err != nil { // 文件不存在,创建默认配置 cfg := defaultConfig() return writeDefaultConfig(path, cfg) } // 文件存在,解析并补充缺失字段 cfg := defaultConfig() if err = yaml.Unmarshal(data, cfg); err != nil { return err } if changed := applyDefaults(cfg); changed { log.Printf("[config] 检测到配置文件缺失字段,已自动补充: %s", path) if err = writeDefaultConfig(path, cfg); err != nil { return err } } return nil } // writeDefaultConfig 写入带注释头的默认配置 func writeDefaultConfig(path string, cfg *Config) error { data, err := yaml.Marshal(cfg) if err != nil { return err } header := `# meshgo MQTT 服务配置文件 # 修改后执行: systemctl reload meshgo 即可热重载(无需重启服务) # # 热重载支持的字段:auth / logging # 需要重启才能生效的字段:server.tcp_addr / server.ws_addr / database ` return os.WriteFile(path, append([]byte(header), data...), 0640) } // applyDefaults 补充零值字段的默认值,返回是否发生了修改 func applyDefaults(cfg *Config) bool { changed := false // Server if cfg.Server.TCPAddr == "" { cfg.Server.TCPAddr = ":1883" changed = true } if cfg.Server.WriteTimeout == 0 { cfg.Server.WriteTimeout = 3 changed = true } // Auth if !cfg.Auth.Enabled { cfg.Auth.Enabled = false cfg.Auth.AllowAnonymous = true changed = true } // Logging if cfg.Logging.Level == "" { cfg.Logging.Level = "info" changed = true } // Database if cfg.Database.Type == "" { cfg.Database.Type = "sqlite3" changed = true } if cfg.Database.File == "" { cfg.Database.File = "meshgo.db" changed = true } // Admin if cfg.Admin.Port == "" { cfg.Admin.Port = ":8080" changed = true } if cfg.Admin.AuthType == "" { cfg.Admin.AuthType = "none" changed = true } return changed } // defaultConfig 返回一份合理的默认配置 func defaultConfig() *Config { return &Config{ Server: ServerConfig{ TCPAddr: ":1883", WSAddr: "", MaxConnections: 0, WriteTimeout: 3, }, Auth: AuthConfig{ Enabled: false, AllowAnonymous: true, Users: []UserEntry{}, }, Logging: LoggingConfig{ Level: "info", File: "", }, Database: DatabaseConfig{ Enabled: false, Type: "sqlite3", File: "meshgo.db", DSN: "", }, Admin: AdminConfig{ Enabled: false, Port: ":8080", AuthType: "none", Username: "", Password: "", }, } }