package config import ( "encoding/json" "errors" "fmt" "io/fs" "os" "github.com/sirupsen/logrus" ) type ListeningMode int64 func (lm ListeningMode) String() string { mapping := map[ListeningMode]string{ ModeNet: "net", ModeUnix: "unix", } val := mapping[lm] return val } func listeningModeFromString(rawVal string) (ListeningMode, error) { mapping := map[string]ListeningMode{ "unix": ModeUnix, "net": ModeNet, } if typedVal, ok := mapping[rawVal]; !ok { return ModeNet, fmt.Errorf("invalid listening mode %s", rawVal) } else { return typedVal, nil } } const ( ModeUnix ListeningMode = iota ModeNet ) type jsonConf struct { Log struct { Level string `json:"level"` } `json:"log"` Storage struct { Path string `json:"path"` TemplateDir string `json:"templatePath"` StaticDir string `json:"staticPath"` } `json:"storage"` Server struct { Host string `json:"host"` Port int `json:"port"` Mode string `json:"mode"` SockPath string `json:"sock"` PublicHost string `json:"public_host"` } `json:"server"` BootProvider struct { Iface string `json:"interface"` Port int `json:"port"` McastGroup string `json:"multicast_group"` SrcAddr string `json:"src_addr"` } `json:"boot_provider"` HomeAssistant struct { Enabled bool `json:"enabled"` Host string `json:"host"` APIToken string `json:"token"` } `json:"home_assistant"` } type AppConfig struct { LogLevel logrus.Level ServerMode ListeningMode DataFilepath string StaticDir string Host string PublicHost string Port int SockPath string UPDMcastGroup string UDPPort int UDPIface string UDPSrcAddr string HomeAssistantEnabled bool HomeAssistantHost string HomeAssistantToken string } func parseLevel(lvlStr string) logrus.Level { for _, lvl := range logrus.AllLevels { if lvl.String() == lvlStr { return lvl } } return logrus.InfoLevel } func (ac *AppConfig) UnmarshalJSON(data []byte) error { var jsonConf jsonConf if err := json.Unmarshal(data, &jsonConf); err != nil { return fmt.Errorf("failed to read JSON: %w", err) } ac.LogLevel = parseLevel(jsonConf.Log.Level) lm, err := listeningModeFromString(jsonConf.Server.Mode) if err != nil { return fmt.Errorf("failed to parse server listening mode: %w", err) } ac.ServerMode = lm ac.SockPath = jsonConf.Server.SockPath ac.Host = jsonConf.Server.Host ac.PublicHost = jsonConf.Server.PublicHost ac.Port = jsonConf.Server.Port ac.UPDMcastGroup = jsonConf.BootProvider.McastGroup ac.UDPIface = jsonConf.BootProvider.Iface ac.UDPPort = jsonConf.BootProvider.Port ac.UDPSrcAddr = jsonConf.BootProvider.SrcAddr ac.DataFilepath = jsonConf.Storage.Path ac.StaticDir = jsonConf.Storage.StaticDir ac.HomeAssistantEnabled = jsonConf.HomeAssistant.Enabled ac.HomeAssistantHost = jsonConf.HomeAssistant.Host ac.HomeAssistantToken = jsonConf.HomeAssistant.APIToken return nil } var defaultConfig AppConfig = AppConfig{ LogLevel: logrus.InfoLevel, ServerMode: ModeNet, DataFilepath: "boot_options.json", Host: "0.0.0.0", PublicHost: "http://127.0.0.1:5000", Port: 5000, UPDMcastGroup: "ff02::abcd:1234", UDPPort: 42, UDPIface: "eth0", StaticDir: "./static", } func New(filepath string) (*AppConfig, error) { content, err := os.ReadFile(filepath) if err != nil { if errors.Is(err, fs.ErrNotExist) { conf := defaultConfig return &conf, nil } return nil, fmt.Errorf("failed to read config file %q: %w", filepath, err) } var conf AppConfig if err := json.Unmarshal(content, &conf); err != nil { return nil, fmt.Errorf("failed to parse config file: %w", err) } return &conf, nil }