package config import ( "fmt" "log/slog" "os" "git.eeqj.de/sneak/webhooker/internal/globals" "git.eeqj.de/sneak/webhooker/internal/logger" pkgconfig "git.eeqj.de/sneak/webhooker/pkg/config" "go.uber.org/fx" // 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) _ "github.com/joho/godotenv/autoload" ) const ( // EnvironmentDev represents development environment EnvironmentDev = "dev" // EnvironmentProd represents production environment EnvironmentProd = "prod" // DevSessionKey is an insecure default session key for development // This is "webhooker-dev-session-key-insecure!" base64 encoded DevSessionKey = "d2ViaG9va2VyLWRldi1zZXNzaW9uLWtleS1pbnNlY3VyZSE=" ) // nolint:revive // ConfigParams is a standard fx naming convention type ConfigParams struct { fx.In Globals *globals.Globals Logger *logger.Logger } type Config struct { DBURL string Debug bool MaintenanceMode bool DevelopmentMode bool DevAdminUsername string DevAdminPassword string Environment string MetricsPassword string MetricsUsername string Port int SentryDSN string SessionKey string params *ConfigParams log *slog.Logger } // IsDev returns true if running in development environment func (c *Config) IsDev() bool { return c.Environment == EnvironmentDev } // IsProd returns true if running in production environment func (c *Config) IsProd() bool { return c.Environment == EnvironmentProd } // nolint:revive // lc parameter is required by fx even if unused func New(lc fx.Lifecycle, params ConfigParams) (*Config, error) { log := params.Logger.Get() // Determine environment from WEBHOOKER_ENVIRONMENT env var, default to dev environment := os.Getenv("WEBHOOKER_ENVIRONMENT") if environment == "" { environment = EnvironmentDev } // Validate environment if environment != EnvironmentDev && environment != EnvironmentProd { return nil, fmt.Errorf("WEBHOOKER_ENVIRONMENT must be either '%s' or '%s', got '%s'", EnvironmentDev, EnvironmentProd, environment) } // Set the environment in the config package pkgconfig.SetEnvironment(environment) // Load configuration values 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"), log: log, params: ¶ms, } // Validate database URL if s.DBURL == "" { return nil, fmt.Errorf("database URL (dburl) is required") } // In production, require session key if s.IsProd() && s.SessionKey == "" { return nil, fmt.Errorf("SESSION_KEY is required in production environment") } // In development mode, warn if using default session key if s.IsDev() && s.SessionKey == DevSessionKey { log.Warn("Using insecure default session key for development mode") } if s.Debug { params.Logger.EnableDebugLogging() s.log = params.Logger.Get() log.Debug("Debug mode enabled") } // Log configuration summary (without secrets) log.Info("Configuration loaded", "environment", s.Environment, "port", s.Port, "debug", s.Debug, "maintenanceMode", s.MaintenanceMode, "developmentMode", s.DevelopmentMode, "hasSessionKey", s.SessionKey != "", "hasSentryDSN", s.SentryDSN != "", "hasMetricsAuth", s.MetricsUsername != "" && s.MetricsPassword != "", ) return s, nil }