157 lines
3.8 KiB
Go
157 lines
3.8 KiB
Go
package config
|
|
|
|
import (
|
|
"fmt"
|
|
"os"
|
|
"path/filepath"
|
|
"runtime"
|
|
|
|
"github.com/BurntSushi/toml"
|
|
)
|
|
|
|
// DatabaseConfig holds database connection settings.
|
|
type DatabaseConfig struct {
|
|
Driver string `toml:"driver"`
|
|
DSN string `toml:"dsn"`
|
|
}
|
|
|
|
// WebConfig holds web server settings.
|
|
type WebConfig struct {
|
|
// Addr 监听地址:以 / 开头视为 unix socket,否则视为 TCP 端口
|
|
Addr string `toml:"addr"`
|
|
}
|
|
|
|
// SpeedtestConfig holds speed test limits.
|
|
type SpeedtestConfig struct {
|
|
MaxDownloadBytes int64 `toml:"max_download_bytes"` // 单次下载请求最大字节数
|
|
MaxUploadBytes int64 `toml:"max_upload_bytes"` // 单次上传请求最大字节数
|
|
}
|
|
|
|
// Config is the top-level configuration structure.
|
|
type Config struct {
|
|
Database DatabaseConfig `toml:"database"`
|
|
Web WebConfig `toml:"web"`
|
|
Speedtest SpeedtestConfig `toml:"speedtest"`
|
|
}
|
|
|
|
func isWindows() bool { return runtime.GOOS == "windows" }
|
|
|
|
func etcDir() string {
|
|
if isWindows() {
|
|
return WinEtcDir
|
|
}
|
|
return LinuxEtcDir
|
|
}
|
|
|
|
func baseDir() string {
|
|
if isWindows() {
|
|
return WinBaseDir
|
|
}
|
|
return LinuxBaseDir
|
|
}
|
|
|
|
func defaultDSN() string {
|
|
if isWindows() {
|
|
return DefaultDSNWin
|
|
}
|
|
return DefaultDSNLinux
|
|
}
|
|
|
|
// defaultConfig returns a fully populated Config with default values.
|
|
func defaultConfig() *Config {
|
|
return &Config{
|
|
Database: DatabaseConfig{
|
|
Driver: DefaultDBDriver,
|
|
DSN: defaultDSN(),
|
|
},
|
|
Web: WebConfig{
|
|
Addr: DefaultWebAddr,
|
|
},
|
|
Speedtest: SpeedtestConfig{
|
|
MaxDownloadBytes: DefaultMaxDownloadBytes,
|
|
MaxUploadBytes: DefaultMaxUploadBytes,
|
|
},
|
|
}
|
|
}
|
|
|
|
// configFilePath returns the full path to the configuration file.
|
|
// 支持通过环境变量 SPEEDTEST_CONFIG 覆盖(本地调试用)。
|
|
func configFilePath() string {
|
|
if p := os.Getenv("SPEEDTEST_CONFIG"); p != "" {
|
|
return p
|
|
}
|
|
return filepath.Join(etcDir(), ConfigFileName)
|
|
}
|
|
|
|
// mergeDefaults overlays default values onto the loaded config for any zero/empty fields.
|
|
func mergeDefaults(cfg *Config, defaults *Config) *Config {
|
|
if cfg.Database.Driver == "" {
|
|
cfg.Database.Driver = defaults.Database.Driver
|
|
}
|
|
if cfg.Database.DSN == "" {
|
|
cfg.Database.DSN = defaults.Database.DSN
|
|
}
|
|
if cfg.Web.Addr == "" {
|
|
cfg.Web.Addr = defaults.Web.Addr
|
|
}
|
|
if cfg.Speedtest.MaxDownloadBytes == 0 {
|
|
cfg.Speedtest.MaxDownloadBytes = defaults.Speedtest.MaxDownloadBytes
|
|
}
|
|
if cfg.Speedtest.MaxUploadBytes == 0 {
|
|
cfg.Speedtest.MaxUploadBytes = defaults.Speedtest.MaxUploadBytes
|
|
}
|
|
return cfg
|
|
}
|
|
|
|
// writeConfig writes the configuration to the given file path.
|
|
func writeConfig(path string, cfg *Config) error {
|
|
dir := filepath.Dir(path)
|
|
if err := os.MkdirAll(dir, 0755); err != nil {
|
|
return fmt.Errorf("创建配置目录失败 %s: %w", dir, err)
|
|
}
|
|
|
|
f, err := os.Create(path)
|
|
if err != nil {
|
|
return fmt.Errorf("创建配置文件失败 %s: %w", path, err)
|
|
}
|
|
defer f.Close()
|
|
|
|
enc := toml.NewEncoder(f)
|
|
if err := enc.Encode(cfg); err != nil {
|
|
return fmt.Errorf("写入配置文件失败: %w", err)
|
|
}
|
|
return nil
|
|
}
|
|
|
|
// LoadConfig loads the configuration from disk.
|
|
// If the configuration file does not exist, it creates one with default values.
|
|
// If the file exists but has missing fields, they are filled with defaults and the file is updated.
|
|
func LoadConfig() (*Config, error) {
|
|
path := configFilePath()
|
|
defaults := defaultConfig()
|
|
|
|
if _, err := os.Stat(path); os.IsNotExist(err) {
|
|
if mkErr := writeConfig(path, defaults); mkErr != nil {
|
|
return nil, mkErr
|
|
}
|
|
return defaults, nil
|
|
}
|
|
|
|
data, err := os.ReadFile(path)
|
|
if err != nil {
|
|
return nil, fmt.Errorf("读取配置文件失败 %s: %w", path, err)
|
|
}
|
|
|
|
cfg := &Config{}
|
|
if err := toml.Unmarshal(data, cfg); err != nil {
|
|
return nil, fmt.Errorf("解析配置文件失败: %w", err)
|
|
}
|
|
|
|
merged := mergeDefaults(cfg, defaults)
|
|
if writeErr := writeConfig(path, merged); writeErr != nil {
|
|
return nil, writeErr
|
|
}
|
|
|
|
return merged, nil
|
|
}
|