package config import ( "encoding/json" "fmt" "os" "path/filepath" ) type Config struct { BackendURL string `json:"backendUrl"` Identifier string `json:"identifier"` APIKey string `json:"apiKey"` ReportIntervalSec int `json:"reportIntervalSec"` LogLevel string `json:"logLevel"` RootFS string `json:"rootFs"` } func Load(path string) (Config, error) { cfg := Config{ BackendURL: getenvAny([]string{"HF_MONITER_BACKEND_URL", "HF_MONITOR_BACKEND_URL"}, "https://monitor.hangman-lab.top"), Identifier: getenvAny([]string{"HF_MONITER_IDENTIFIER", "HF_MONITOR_IDENTIFIER"}, hostnameOr("unknown-host")), APIKey: getenvAny([]string{"HF_MONITER_API_KEY", "HF_MONITOR_API_KEY"}, ""), ReportIntervalSec: getenvIntAny([]string{"HF_MONITER_REPORT_INTERVAL", "HF_MONITOR_REPORT_INTERVAL"}, 30), LogLevel: getenvAny([]string{"HF_MONITER_LOG_LEVEL", "HF_MONITOR_LOG_LEVEL"}, "info"), RootFS: getenvAny([]string{"HF_MONITER_ROOTFS", "HF_MONITOR_ROOTFS"}, ""), } if path != "" { if fileCfg, err := loadFile(path); err == nil { merge(&cfg, fileCfg) } else if !os.IsNotExist(err) { return cfg, fmt.Errorf("load config file: %w", err) } } // env always wins over file cfg.BackendURL = getenvAny([]string{"HF_MONITER_BACKEND_URL", "HF_MONITOR_BACKEND_URL"}, cfg.BackendURL) cfg.Identifier = getenvAny([]string{"HF_MONITER_IDENTIFIER", "HF_MONITOR_IDENTIFIER"}, cfg.Identifier) if v := getenvAny([]string{"HF_MONITER_API_KEY", "HF_MONITOR_API_KEY"}, ""); v != "" { cfg.APIKey = v } cfg.ReportIntervalSec = getenvIntAny([]string{"HF_MONITER_REPORT_INTERVAL", "HF_MONITOR_REPORT_INTERVAL"}, cfg.ReportIntervalSec) cfg.LogLevel = getenvAny([]string{"HF_MONITER_LOG_LEVEL", "HF_MONITOR_LOG_LEVEL"}, cfg.LogLevel) cfg.RootFS = getenvAny([]string{"HF_MONITER_ROOTFS", "HF_MONITOR_ROOTFS"}, cfg.RootFS) if cfg.BackendURL == "" { return cfg, fmt.Errorf("backendUrl is required") } if cfg.Identifier == "" { return cfg, fmt.Errorf("identifier is required") } if cfg.ReportIntervalSec <= 0 { cfg.ReportIntervalSec = 30 } applyHostFSEnv(cfg.RootFS) return cfg, nil } func loadFile(path string) (Config, error) { var cfg Config buf, err := os.ReadFile(path) if err != nil { return cfg, err } if err := json.Unmarshal(buf, &cfg); err != nil { return cfg, err } return cfg, nil } func merge(dst *Config, src Config) { if src.BackendURL != "" { dst.BackendURL = src.BackendURL } if src.Identifier != "" { dst.Identifier = src.Identifier } if src.APIKey != "" { dst.APIKey = src.APIKey } if src.ReportIntervalSec > 0 { dst.ReportIntervalSec = src.ReportIntervalSec } if src.LogLevel != "" { dst.LogLevel = src.LogLevel } if src.RootFS != "" { dst.RootFS = src.RootFS } } func getenvAny(keys []string, fallback string) string { for _, key := range keys { if v := os.Getenv(key); v != "" { return v } } return fallback } func getenvIntAny(keys []string, fallback int) int { for _, key := range keys { if v := os.Getenv(key); v != "" { var out int if _, err := fmt.Sscanf(v, "%d", &out); err == nil && out > 0 { return out } } } return fallback } func hostnameOr(fallback string) string { name, err := os.Hostname() if err != nil || name == "" { return fallback } return name } func applyHostFSEnv(rootFS string) { if rootFS == "" { return } setIfEmpty("HOST_PROC", filepath.Join(rootFS, "proc")) setIfEmpty("HOST_SYS", filepath.Join(rootFS, "sys")) setIfEmpty("HOST_ETC", filepath.Join(rootFS, "etc")) setIfEmpty("HOST_VAR", filepath.Join(rootFS, "var")) setIfEmpty("HOST_RUN", filepath.Join(rootFS, "run")) } func setIfEmpty(key, value string) { if os.Getenv(key) == "" { _ = os.Setenv(key, value) } }