// Package config provides application configuration via Viper. package config import ( "errors" "fmt" "log/slog" "github.com/spf13/viper" "go.uber.org/fx" "git.eeqj.de/sneak/upaas/internal/globals" "git.eeqj.de/sneak/upaas/internal/logger" ) // defaultPort is the default HTTP server port. const defaultPort = 8080 // Params contains dependencies for Config. type Params struct { fx.In Globals *globals.Globals Logger *logger.Logger } // Config holds application configuration. type Config struct { Port int Debug bool DataDir string DockerHost string SentryDSN string MaintenanceMode bool MetricsUsername string MetricsPassword string SessionSecret string params *Params log *slog.Logger } // New creates a new Config instance from environment and config files. func New(_ fx.Lifecycle, params Params) (*Config, error) { log := params.Logger.Get() name := params.Globals.Appname if name == "" { name = "upaas" } setupViper(name) cfg, err := buildConfig(log, ¶ms) if err != nil { return nil, err } configureDebugLogging(cfg, params) return cfg, nil } func setupViper(name string) { // Config file settings viper.SetConfigName(name) viper.SetConfigType("yaml") viper.AddConfigPath("/etc/" + name) viper.AddConfigPath("$HOME/.config/" + name) viper.AddConfigPath(".") // Environment variables override everything viper.SetEnvPrefix("UPAAS") viper.AutomaticEnv() // Defaults viper.SetDefault("PORT", defaultPort) viper.SetDefault("DEBUG", false) viper.SetDefault("DATA_DIR", "./data") viper.SetDefault("DOCKER_HOST", "unix:///var/run/docker.sock") viper.SetDefault("SENTRY_DSN", "") viper.SetDefault("MAINTENANCE_MODE", false) viper.SetDefault("METRICS_USERNAME", "") viper.SetDefault("METRICS_PASSWORD", "") viper.SetDefault("SESSION_SECRET", "") } func buildConfig(log *slog.Logger, params *Params) (*Config, error) { // Read config file (optional) err := viper.ReadInConfig() if err != nil { var configFileNotFoundError viper.ConfigFileNotFoundError if !errors.As(err, &configFileNotFoundError) { log.Error("config file malformed", "error", err) return nil, fmt.Errorf("config file malformed: %w", err) } // Config file not found is OK } // Build config struct cfg := &Config{ Port: viper.GetInt("PORT"), Debug: viper.GetBool("DEBUG"), DataDir: viper.GetString("DATA_DIR"), DockerHost: viper.GetString("DOCKER_HOST"), SentryDSN: viper.GetString("SENTRY_DSN"), MaintenanceMode: viper.GetBool("MAINTENANCE_MODE"), MetricsUsername: viper.GetString("METRICS_USERNAME"), MetricsPassword: viper.GetString("METRICS_PASSWORD"), SessionSecret: viper.GetString("SESSION_SECRET"), params: params, log: log, } // Generate session secret if not set if cfg.SessionSecret == "" { cfg.SessionSecret = "change-me-in-production-please" log.Warn( "using default session secret, " + "set UPAAS_SESSION_SECRET in production", ) } return cfg, nil } func configureDebugLogging(cfg *Config, params Params) { // Enable debug logging if configured if cfg.Debug { params.Logger.EnableDebugLogging() cfg.log = params.Logger.Get() } } // DatabasePath returns the full path to the SQLite database file. func (c *Config) DatabasePath() string { return c.DataDir + "/upaas.db" }