package config import ( "fmt" "os" "strconv" "strings" "time" "smoop-api/internal/vault" ) type Config struct { DB struct { DSN string } MinIO struct { Endpoint string AccessKey string SecretKey string UseSSL bool RecordsBucket string LivestreamBucket string PresignTTL time.Duration } JWTSecret []byte } func Load() (*Config, error) { addr := os.Getenv("VAULT_ADDR") token := os.Getenv("VAULT_TOKEN") // New style: explicit KV v2 mount + key mount := os.Getenv("VAULT_KV_MOUNT") // e.g. "kv" or "secret" key := os.Getenv("VAULT_KV_KEY") // e.g. "snoop" // Back-compat: allow legacy VAULT_KV_PATH like "kv/data/snoop" and derive mount+key if (mount == "" || key == "") && os.Getenv("VAULT_KV_PATH") != "" { legacy := strings.Trim(os.Getenv("VAULT_KV_PATH"), "/") parts := strings.Split(legacy, "/") if len(parts) >= 2 { mount = parts[0] key = parts[len(parts)-1] } } if addr == "" || token == "" || mount == "" || key == "" { return nil, fmt.Errorf("VAULT_ADDR, VAULT_TOKEN, VAULT_KV_MOUNT and VAULT_KV_KEY must be set (or provide legacy VAULT_KV_PATH)") } raw, err := vault.ReadKVv2(addr, token, mount, key) if err != nil { return nil, err } getStr := func(k string) (string, error) { v, ok := raw[k].(string) if !ok || v == "" { return "", fmt.Errorf("missing secret key: %s", k) } return v, nil } getBool := func(k string) (bool, error) { v, ok := raw[k] if !ok { return false, fmt.Errorf("missing secret key: %s", k) } switch t := v.(type) { case bool: return t, nil case string: if t == "true" || t == "1" { return true, nil } return false, nil default: return false, fmt.Errorf("invalid bool for key %s", k) } } dbDSN, err := getStr("db_dsn") if err != nil { return nil, err } endpoint, err := getStr("minio_endpoint") if err != nil { return nil, err } ak, err := getStr("minio_access_key") if err != nil { return nil, err } sk, err := getStr("minio_secret_key") if err != nil { return nil, err } useSSL, err := getBool("minio_use_ssl") if err != nil { return nil, err } jwt, err := getStr("jwt_secret") if err != nil { return nil, err } recordsBucket := "records" if v, ok := raw["minio_records_bucket"].(string); ok && v != "" { recordsBucket = v } liveBucket := "livestream" if v, ok := raw["minio_livestream_bucket"].(string); ok && v != "" { liveBucket = v } presignTTL := 15 * time.Minute if v, ok := raw["minio_presign_ttl_seconds"].(string); ok && v != "" { var sec int fmt.Sscanf(v, "%d", &sec) if sec > 0 { presignTTL = time.Duration(sec) * time.Second } } cfg := &Config{} cfg.DB.DSN = dbDSN cfg.MinIO.Endpoint = endpoint cfg.MinIO.AccessKey = ak cfg.MinIO.SecretKey = sk cfg.MinIO.UseSSL = useSSL cfg.MinIO.RecordsBucket = recordsBucket cfg.MinIO.LivestreamBucket = liveBucket cfg.MinIO.PresignTTL = presignTTL cfg.JWTSecret = []byte(jwt) return cfg, nil } func LoadDev() (*Config, error) { getRequired := func(k string) (string, error) { v := os.Getenv(k) if v == "" { return "", fmt.Errorf("missing required env %s", k) } return v, nil } getBoolEnv := func(k string, def bool) bool { v := strings.ToLower(strings.TrimSpace(os.Getenv(k))) if v == "true" || v == "1" || v == "yes" { return true } if v == "false" || v == "0" || v == "no" { return false } return def } getIntEnv := func(k string, def int) int { if v := strings.TrimSpace(os.Getenv(k)); v != "" { if n, err := strconv.Atoi(v); err == nil { return n } } return def } dbDSN, err := getRequired("DB_DSN") if err != nil { return nil, err } endpoint, err := getRequired("MINIO_ENDPOINT") if err != nil { return nil, err } ak, err := getRequired("MINIO_ACCESS_KEY") if err != nil { return nil, err } sk, err := getRequired("MINIO_SECRET_KEY") if err != nil { return nil, err } jwt, err := getRequired("JWT_SECRET") if err != nil { return nil, err } useSSL := getBoolEnv("MINIO_USE_SSL", false) recordsBucket := os.Getenv("MINIO_RECORDS_BUCKET") if recordsBucket == "" { recordsBucket = "records" } liveBucket := os.Getenv("MINIO_LIVESTREAM_BUCKET") if liveBucket == "" { liveBucket = "livestream" } presignTTL := time.Duration(getIntEnv("MINIO_PRESIGN_TTL_SECONDS", 900)) * time.Second cfg := &Config{} cfg.DB.DSN = dbDSN cfg.MinIO.Endpoint = endpoint cfg.MinIO.AccessKey = ak cfg.MinIO.SecretKey = sk cfg.MinIO.UseSSL = useSSL cfg.MinIO.RecordsBucket = recordsBucket cfg.MinIO.LivestreamBucket = liveBucket cfg.MinIO.PresignTTL = presignTTL cfg.JWTSecret = []byte(jwt) return cfg, nil }