package config import ( "fmt" "os" "time" ) type Config struct { Env string // dev | stage | prod Addr string // listen address, e.g. ":8090" KeycloakIssuer string // e.g. http://localhost:8080/realms/breakpilot-dev DatabaseURL string // postgres DSN (unused in skeleton; in-memory store) // Keycloak Admin API — only used if KeycloakAdminURL is set. Empty // values disable the adapter and tenant-registry falls back to the // Mock (dev convenience). KeycloakAdminURL string KeycloakRealm string KeycloakClientID string KeycloakClientSecret string KeycloakTimeout time.Duration // Inbound API auth (RBAC Phase 1). With AuthEnabled the server refuses // to start unless OIDC discovery against KeycloakIssuer succeeds, and // every non-health route requires a bearer token whose audience // contains AuthAudience. AuthEnabled bool AuthAudience string } func Load() (*Config, error) { env := getenv("APP_ENV", "dev") if env != "dev" && env != "stage" && env != "prod" { return nil, fmt.Errorf("invalid APP_ENV %q", env) } return &Config{ Env: env, // :8090 — Keycloak owns :8080 in the dev stack. Addr: getenv("ADDR", ":8090"), KeycloakIssuer: getenv("KEYCLOAK_ISSUER", "http://localhost:8080/realms/breakpilot-dev"), DatabaseURL: os.Getenv("DATABASE_URL"), KeycloakAdminURL: os.Getenv("KEYCLOAK_ADMIN_URL"), KeycloakRealm: getenv("KEYCLOAK_REALM", "breakpilot-dev"), KeycloakClientID: os.Getenv("KEYCLOAK_CLIENT_ID"), KeycloakClientSecret: os.Getenv("KEYCLOAK_CLIENT_SECRET"), KeycloakTimeout: 10 * time.Second, AuthEnabled: getenv("AUTH_ENABLED", "false") == "true", AuthAudience: getenv("AUTH_EXPECTED_AUDIENCE", "tenant-registry"), }, nil } func getenv(key, fallback string) string { if v := os.Getenv(key); v != "" { return v } return fallback }