// Package config handles application configuration. package config import ( "os" "path/filepath" "runtime" "strconv" "git.eeqj.de/sneak/smartconfig" ) // Config holds the application configuration. type Config struct { Port int StateDir string LogLevel string } // New creates a new configuration instance. func New(configFile string) (*Config, error) { // Check if config file exists first if _, err := os.Stat(configFile); os.IsNotExist(err) { return newDefaultConfig(), nil } // Load smartconfig sc, err := smartconfig.NewFromConfigPath(configFile) if err != nil { return nil, err } cfg := &Config{} // Get port from smartconfig or environment or default if port, err := sc.GetInt("port"); err == nil { cfg.Port = port } else { cfg.Port = getPortFromEnv() } // Get state directory if stateDir, err := sc.GetString("state_dir"); err == nil { cfg.StateDir = stateDir } else { cfg.StateDir = getDefaultStateDir() } // Get log level if logLevel, err := sc.GetString("log_level"); err == nil { cfg.LogLevel = logLevel } else { cfg.LogLevel = "info" } return cfg, nil } func newDefaultConfig() *Config { return &Config{ Port: getPortFromEnv(), StateDir: getDefaultStateDir(), LogLevel: "info", } } func getPortFromEnv() int { const defaultPort = 8080 portStr := os.Getenv("PORT") if portStr == "" { return defaultPort } port, err := strconv.Atoi(portStr) if err != nil { return defaultPort } return port } func getDefaultStateDir() string { switch runtime.GOOS { case "darwin": // macOS: use ~/Library/Application Support/berlin.sneak.app.ipapi/ home, err := os.UserHomeDir() if err != nil { return "/var/lib/ipapi" } return filepath.Join(home, "Library", "Application Support", "berlin.sneak.app.ipapi") case "windows": // Windows: use %APPDATA%\berlin.sneak.app.ipapi appData := os.Getenv("APPDATA") if appData == "" { return "/var/lib/ipapi" } return filepath.Join(appData, "berlin.sneak.app.ipapi") default: // Linux and other Unix-like systems return "/var/lib/ipapi" } }