package config import ( "strings" "github.com/spf13/viper" ) type Config struct { Server ServerConfig `mapstructure:"server"` Database DatabaseConfig `mapstructure:"database"` S3 S3Config `mapstructure:"s3"` Redis RedisConfig `mapstructure:"redis"` } type ServerConfig struct { Host string `mapstructure:"host"` Port int `mapstructure:"port"` } type DatabaseConfig struct { Host string `mapstructure:"host"` Port int `mapstructure:"port"` User string `mapstructure:"user"` Password string `mapstructure:"password"` DBName string `mapstructure:"dbname"` SSLMode string `mapstructure:"sslmode"` } type S3Config struct { Endpoint string `mapstructure:"endpoint"` Region string `mapstructure:"region"` Bucket string `mapstructure:"bucket"` AccessKeyID string `mapstructure:"access_key_id"` SecretAccessKey string `mapstructure:"secret_access_key"` UsePathStyle bool `mapstructure:"use_path_style"` } type RedisConfig struct { Host string `mapstructure:"host"` Port int `mapstructure:"port"` Password string `mapstructure:"password"` DB int `mapstructure:"db"` } func Load() (*Config, error) { viper.SetConfigName("config") viper.SetConfigType("yaml") viper.AddConfigPath(".") viper.AddConfigPath("/etc/orchard") // Environment variable overrides viper.SetEnvPrefix("ORCHARD") viper.AutomaticEnv() viper.SetEnvKeyReplacer(strings.NewReplacer(".", "_")) // Bind environment variables explicitly for nested config viper.BindEnv("server.host", "ORCHARD_SERVER_HOST") viper.BindEnv("server.port", "ORCHARD_SERVER_PORT") viper.BindEnv("database.host", "ORCHARD_DATABASE_HOST") viper.BindEnv("database.port", "ORCHARD_DATABASE_PORT") viper.BindEnv("database.user", "ORCHARD_DATABASE_USER") viper.BindEnv("database.password", "ORCHARD_DATABASE_PASSWORD") viper.BindEnv("database.dbname", "ORCHARD_DATABASE_DBNAME") viper.BindEnv("database.sslmode", "ORCHARD_DATABASE_SSLMODE") viper.BindEnv("s3.endpoint", "ORCHARD_S3_ENDPOINT") viper.BindEnv("s3.region", "ORCHARD_S3_REGION") viper.BindEnv("s3.bucket", "ORCHARD_S3_BUCKET") viper.BindEnv("s3.access_key_id", "ORCHARD_S3_ACCESS_KEY_ID") viper.BindEnv("s3.secret_access_key", "ORCHARD_S3_SECRET_ACCESS_KEY") viper.BindEnv("s3.use_path_style", "ORCHARD_S3_USE_PATH_STYLE") viper.BindEnv("redis.host", "ORCHARD_REDIS_HOST") viper.BindEnv("redis.port", "ORCHARD_REDIS_PORT") viper.BindEnv("redis.password", "ORCHARD_REDIS_PASSWORD") viper.BindEnv("redis.db", "ORCHARD_REDIS_DB") // Defaults viper.SetDefault("server.host", "0.0.0.0") viper.SetDefault("server.port", 8080) viper.SetDefault("database.host", "localhost") viper.SetDefault("database.port", 5432) viper.SetDefault("database.user", "orchard") viper.SetDefault("database.dbname", "orchard") viper.SetDefault("database.sslmode", "disable") viper.SetDefault("s3.region", "us-east-1") viper.SetDefault("s3.bucket", "orchard-artifacts") viper.SetDefault("s3.use_path_style", true) viper.SetDefault("redis.host", "localhost") viper.SetDefault("redis.port", 6379) viper.SetDefault("redis.db", 0) if err := viper.ReadInConfig(); err != nil { if _, ok := err.(viper.ConfigFileNotFoundError); !ok { return nil, err } } var cfg Config if err := viper.Unmarshal(&cfg); err != nil { return nil, err } return &cfg, nil }