From 483d7f31ffceb5fc2474cf5df3234e76119e8bbd Mon Sep 17 00:00:00 2001 From: clawbot Date: Sun, 1 Mar 2026 15:52:05 -0800 Subject: [PATCH] refactor: simplify config to prefer env vars Configuration now prefers environment variables over config.yaml values. Each config field has a corresponding env var (DBURL, PORT, DEBUG, etc.) that takes precedence when set. The config.yaml fallback is preserved for development convenience. closes https://git.eeqj.de/sneak/webhooker/issues/10 --- internal/config/config.go | 75 +++++++++++++++++++++++++++------------ 1 file changed, 53 insertions(+), 22 deletions(-) diff --git a/internal/config/config.go b/internal/config/config.go index ee4eb86..c319321 100644 --- a/internal/config/config.go +++ b/internal/config/config.go @@ -4,19 +4,16 @@ import ( "fmt" "log/slog" "os" + "strconv" + "strings" "go.uber.org/fx" "sneak.berlin/go/webhooker/internal/globals" "sneak.berlin/go/webhooker/internal/logger" pkgconfig "sneak.berlin/go/webhooker/pkg/config" - // spooky action at a distance! - // this populates the environment - // from a ./.env file automatically - // for development configuration. - // .env contents should be things like - // `DBURL=postgres://user:pass@.../` - // (without the backticks, of course) + // Populates the environment from a ./.env file automatically for + // development configuration. Kept in one place only (here). _ "github.com/joho/godotenv/autoload" ) @@ -64,6 +61,40 @@ func (c *Config) IsProd() bool { return c.Environment == EnvironmentProd } +// envString returns the env var value if set, otherwise falls back to pkgconfig. +func envString(envKey, configKey string) string { + if v := os.Getenv(envKey); v != "" { + return v + } + return pkgconfig.GetString(configKey) +} + +// envSecretString returns the env var value if set, otherwise falls back to pkgconfig secrets. +func envSecretString(envKey, configKey string) string { + if v := os.Getenv(envKey); v != "" { + return v + } + return pkgconfig.GetSecretString(configKey) +} + +// envBool returns the env var value parsed as bool, otherwise falls back to pkgconfig. +func envBool(envKey, configKey string) bool { + if v := os.Getenv(envKey); v != "" { + return strings.EqualFold(v, "true") || v == "1" + } + return pkgconfig.GetBool(configKey) +} + +// envInt returns the env var value parsed as int, otherwise falls back to pkgconfig. +func envInt(envKey, configKey string, defaultValue ...int) int { + if v := os.Getenv(envKey); v != "" { + if i, err := strconv.Atoi(v); err == nil { + return i + } + } + return pkgconfig.GetInt(configKey, defaultValue...) +} + // nolint:revive // lc parameter is required by fx even if unused func New(lc fx.Lifecycle, params ConfigParams) (*Config, error) { log := params.Logger.Get() @@ -80,30 +111,30 @@ func New(lc fx.Lifecycle, params ConfigParams) (*Config, error) { EnvironmentDev, EnvironmentProd, environment) } - // Set the environment in the config package + // Set the environment in the config package (for fallback resolution) pkgconfig.SetEnvironment(environment) - // Load configuration values + // Load configuration values — env vars take precedence over config.yaml s := &Config{ - DBURL: pkgconfig.GetString("dburl"), - Debug: pkgconfig.GetBool("debug"), - MaintenanceMode: pkgconfig.GetBool("maintenanceMode"), - DevelopmentMode: pkgconfig.GetBool("developmentMode"), - DevAdminUsername: pkgconfig.GetString("devAdminUsername"), - DevAdminPassword: pkgconfig.GetString("devAdminPassword"), - Environment: pkgconfig.GetString("environment", environment), - MetricsUsername: pkgconfig.GetString("metricsUsername"), - MetricsPassword: pkgconfig.GetString("metricsPassword"), - Port: pkgconfig.GetInt("port", 8080), - SentryDSN: pkgconfig.GetSecretString("sentryDSN"), - SessionKey: pkgconfig.GetSecretString("sessionKey"), + DBURL: envString("DBURL", "dburl"), + Debug: envBool("DEBUG", "debug"), + MaintenanceMode: envBool("MAINTENANCE_MODE", "maintenanceMode"), + DevelopmentMode: envBool("DEVELOPMENT_MODE", "developmentMode"), + DevAdminUsername: envString("DEV_ADMIN_USERNAME", "devAdminUsername"), + DevAdminPassword: envString("DEV_ADMIN_PASSWORD", "devAdminPassword"), + Environment: environment, + MetricsUsername: envString("METRICS_USERNAME", "metricsUsername"), + MetricsPassword: envString("METRICS_PASSWORD", "metricsPassword"), + Port: envInt("PORT", "port", 8080), + SentryDSN: envSecretString("SENTRY_DSN", "sentryDSN"), + SessionKey: envSecretString("SESSION_KEY", "sessionKey"), log: log, params: ¶ms, } // Validate database URL if s.DBURL == "" { - return nil, fmt.Errorf("database URL (dburl) is required") + return nil, fmt.Errorf("database URL (DBURL) is required") } // In production, require session key