package config import ( "encoding/json" "errors" "fmt" "io" "net" "net/url" "os" "strings" "time" "aigw/internal/domain" ) type Config struct { Server ServerConfig `json:"server"` Auth AuthConfig `json:"auth"` ControlPlane ControlPlaneConfig `json:"control_plane"` Admin AdminConfig `json:"admin"` UpstreamHTTP UpstreamHTTPConfig `json:"upstream_http"` ProviderHealth ProviderHealthConfig `json:"provider_health"` Providers []ProviderConfig `json:"providers"` Models []ModelConfig `json:"models"` Billing BillingConfig `json:"billing"` Observability ObservabilityConfig `json:"observability"` } type ServerConfig struct { Address string `json:"-"` AddressEnv string `json:"address_env"` SplitListeners bool `json:"split_listeners"` PublicAddressEnv string `json:"public_address_env"` AdminAddressEnv string `json:"admin_address_env"` WebhookAddressEnv string `json:"webhook_address_env"` OperationsAddressEnv string `json:"operations_address_env"` TrustedProxyCIDRsEnv string `json:"trusted_proxy_cidrs_env"` RequireHTTPSEnv string `json:"require_https_env"` DeploymentRegionEnv string `json:"deployment_region_env"` PublicAddress string `json:"-"` AdminAddress string `json:"-"` WebhookAddress string `json:"-"` OperationsAddress string `json:"-"` TrustedProxyCIDRs []string `json:"-"` RequireHTTPS bool `json:"-"` DeploymentRegion string `json:"-"` MaxBodyBytes int64 `json:"max_body_bytes"` ReadHeaderTimeoutSecs int `json:"read_header_timeout_seconds"` IdleTimeoutSecs int `json:"idle_timeout_seconds"` ShutdownTimeoutSecs int `json:"shutdown_timeout_seconds"` } type AuthConfig struct { KeysEnv string `json:"keys_env"` AllowAnonymous bool `json:"allow_anonymous"` } type ControlPlaneConfig struct { Enabled bool `json:"enabled"` DatabaseURLEnv string `json:"database_url_env"` RedisURLEnv string `json:"redis_url_env"` CredentialKeyEnv string `json:"credential_key_env"` PreviousCredentialKeysEnv string `json:"previous_credential_keys_env"` RedisChannel string `json:"redis_channel"` SnapshotCacheKey string `json:"snapshot_cache_key"` ReloadIntervalSeconds int `json:"reload_interval_seconds"` AutoMigrate bool `json:"auto_migrate"` DatabaseURL string `json:"-"` RedisURL string `json:"-"` CredentialKey string `json:"-"` PreviousCredentialKeys []string `json:"-"` } type AdminConfig struct { Enabled bool `json:"enabled"` TokenEnv string `json:"token_env"` BasePath string `json:"base_path"` RegistrationEnabled bool `json:"registration_enabled"` SessionTTLHours int `json:"session_ttl_hours"` AuditRetentionDays int `json:"audit_retention_days"` SecurityRetentionDays int `json:"security_retention_days"` PublicURL string `json:"-"` PublicURLEnv string `json:"public_url_env"` InferencePublicURL string `json:"-"` InferencePublicURLEnv string `json:"inference_public_url_env"` Mail MailConfig `json:"mail"` WebAuthn WebAuthnConfig `json:"webauthn"` Token string `json:"-"` } type MailConfig struct { Enabled bool `json:"enabled"` FromName string `json:"from_name"` TLSMode string `json:"tls_mode"` FromAddressEnv string `json:"from_address_env"` SMTPAddressEnv string `json:"smtp_address_env"` SMTPUsernameEnv string `json:"smtp_username_env"` SMTPPasswordEnv string `json:"smtp_password_env"` FeedbackSecretEnv string `json:"feedback_secret_env"` LowBalanceMicros int64 `json:"low_balance_micros"` SpendAnomalyMultiplier int64 `json:"spend_anomaly_multiplier"` SpendAnomalyMinMicros int64 `json:"spend_anomaly_min_micros"` NotificationIntervalSeconds int `json:"notification_interval_seconds"` SMTPImplicitTLS bool `json:"smtp_implicit_tls"` FromAddress string `json:"-"` SMTPAddress string `json:"-"` SMTPUsername string `json:"-"` SMTPPassword string `json:"-"` FeedbackSecret string `json:"-"` } type WebAuthnConfig struct { Enabled bool `json:"enabled"` RPDisplayName string `json:"rp_display_name"` RPIDEnv string `json:"rp_id_env"` OriginsEnv string `json:"origins_env"` RPID string `json:"-"` Origins []string `json:"-"` } type UpstreamHTTPConfig struct { MaxIdleConnections int `json:"max_idle_connections"` MaxIdleConnectionsPerHost int `json:"max_idle_connections_per_host"` IdleConnectionTimeoutSecs int `json:"idle_connection_timeout_seconds"` ResponseHeaderTimeoutSecs int `json:"response_header_timeout_seconds"` } type ProviderHealthConfig struct { ActiveProbesEnabledEnv string `json:"active_probes_enabled_env"` ActiveProbesEnabled bool `json:"-"` SharedHistoryEnabledEnv string `json:"shared_history_enabled_env"` SharedHistoryEnabled bool `json:"-"` SharedHistoryStream string `json:"shared_history_stream"` SharedHistoryTTLSeconds int `json:"shared_history_ttl_seconds"` SharedHistoryMaxEvents int64 `json:"shared_history_max_events"` ProbeIntervalSeconds int `json:"probe_interval_seconds"` ProbeTimeoutSeconds int `json:"probe_timeout_seconds"` } type ProviderConfig struct { ID string `json:"id"` Slug string `json:"slug"` Protocol domain.Protocol `json:"protocol"` WireAPI string `json:"wire_api"` BaseURL string `json:"-"` BaseURLEnv string `json:"base_url_env"` APIKeyEnv string `json:"api_key_env"` APIKey string `json:"-"` } type ModelConfig struct { ID string `json:"id"` OwnedBy string `json:"owned_by"` Capabilities []string `json:"capabilities"` InputPriceMicrosPerMillion int64 `json:"input_price_micros_per_million"` OutputPriceMicrosPerMillion int64 `json:"output_price_micros_per_million"` CacheReadPriceMicrosPerMillion int64 `json:"cache_read_price_micros_per_million"` CacheWritePriceMicrosPerMillion int64 `json:"cache_write_price_micros_per_million"` Routes []RouteConfig `json:"routes"` } type RouteConfig struct { Provider string `json:"provider"` UpstreamModel string `json:"upstream_model"` Priority int `json:"priority"` Weight int `json:"weight"` } type ObservabilityConfig struct { UsageBuffer int `json:"usage_buffer"` ExposeMetrics bool `json:"expose_metrics"` } type BillingConfig struct { Enabled bool `json:"enabled"` Currency string `json:"currency"` DefaultMaxOutputTokens int64 `json:"default_max_output_tokens"` MinTopUpMinor int64 `json:"min_top_up_minor"` MaxTopUpMinor int64 `json:"max_top_up_minor"` SettlementSpoolPathEnv string `json:"settlement_spool_path_env"` SettlementSpoolPath string `json:"-"` Stripe StripeConfig `json:"stripe"` } type StripeConfig struct { Enabled bool `json:"enabled"` EnabledEnv string `json:"enabled_env"` APIKeyEnv string `json:"api_key_env"` WebhookSecretEnv string `json:"webhook_secret_env"` SuccessURLEnv string `json:"success_url_env"` CancelURLEnv string `json:"cancel_url_env"` PortalReturnURLEnv string `json:"portal_return_url_env"` AutomaticTaxEnabledEnv string `json:"automatic_tax_enabled_env"` TaxRegistrationConfirmedEnv string `json:"tax_registration_confirmed_env"` ProductTaxCodeEnv string `json:"product_tax_code_env"` SuccessURL string `json:"-"` CancelURL string `json:"-"` PortalReturnURL string `json:"-"` APIKey string `json:"-"` WebhookSecret string `json:"-"` AutomaticTaxEnabled bool `json:"-"` TaxRegistrationConfirmed bool `json:"-"` ProductTaxCode string `json:"-"` } func Load(path string) (Config, error) { f, err := os.Open(path) if err != nil { return Config{}, fmt.Errorf("open config: %w", err) } defer f.Close() var cfg Config decoder := json.NewDecoder(f) decoder.DisallowUnknownFields() if err := decoder.Decode(&cfg); err != nil { return Config{}, fmt.Errorf("decode config: %w", err) } if err := decoder.Decode(&struct{}{}); !errors.Is(err, io.EOF) { if err == nil { return Config{}, errors.New("decode config: multiple JSON values") } return Config{}, fmt.Errorf("decode config trailing data: %w", err) } applyDefaults(&cfg) if err := resolveSecrets(&cfg); err != nil { return Config{}, err } if err := Validate(cfg); err != nil { return Config{}, err } return cfg, nil } func applyDefaults(cfg *Config) { if cfg.Server.AddressEnv == "" { cfg.Server.AddressEnv = "AIGW_SERVER_ADDRESS" } if cfg.Server.Address == "" { cfg.Server.Address = ":8080" } if cfg.Server.PublicAddressEnv == "" { cfg.Server.PublicAddressEnv = "AIGW_PUBLIC_ADDRESS" } if cfg.Server.AdminAddressEnv == "" { cfg.Server.AdminAddressEnv = "AIGW_ADMIN_ADDRESS" } if cfg.Server.WebhookAddressEnv == "" { cfg.Server.WebhookAddressEnv = "AIGW_WEBHOOK_ADDRESS" } if cfg.Server.OperationsAddressEnv == "" { cfg.Server.OperationsAddressEnv = "AIGW_OPERATIONS_ADDRESS" } if cfg.Server.TrustedProxyCIDRsEnv == "" { cfg.Server.TrustedProxyCIDRsEnv = "AIGW_TRUSTED_PROXY_CIDRS" } if cfg.Server.RequireHTTPSEnv == "" { cfg.Server.RequireHTTPSEnv = "AIGW_REQUIRE_HTTPS" } if cfg.Server.DeploymentRegionEnv == "" { cfg.Server.DeploymentRegionEnv = "AIGW_DEPLOYMENT_REGION" } if cfg.Server.MaxBodyBytes == 0 { cfg.Server.MaxBodyBytes = 16 << 20 } if cfg.Server.ReadHeaderTimeoutSecs == 0 { cfg.Server.ReadHeaderTimeoutSecs = 10 } if cfg.Server.IdleTimeoutSecs == 0 { cfg.Server.IdleTimeoutSecs = 120 } if cfg.Server.ShutdownTimeoutSecs == 0 { cfg.Server.ShutdownTimeoutSecs = 20 } if cfg.Auth.KeysEnv == "" { cfg.Auth.KeysEnv = "AIGW_API_KEYS" } if cfg.ControlPlane.DatabaseURLEnv == "" { cfg.ControlPlane.DatabaseURLEnv = "AIGW_DATABASE_URL" } if cfg.ControlPlane.RedisURLEnv == "" { cfg.ControlPlane.RedisURLEnv = "AIGW_REDIS_URL" } if cfg.ControlPlane.CredentialKeyEnv == "" { cfg.ControlPlane.CredentialKeyEnv = "AIGW_CREDENTIAL_KEY" } if cfg.ControlPlane.PreviousCredentialKeysEnv == "" { cfg.ControlPlane.PreviousCredentialKeysEnv = "AIGW_CREDENTIAL_PREVIOUS_KEYS" } if cfg.ControlPlane.RedisChannel == "" { cfg.ControlPlane.RedisChannel = "aigw:control:changed" } if cfg.ControlPlane.SnapshotCacheKey == "" { cfg.ControlPlane.SnapshotCacheKey = "aigw:control:snapshot:v1" } if cfg.ControlPlane.ReloadIntervalSeconds == 0 { cfg.ControlPlane.ReloadIntervalSeconds = 30 } if cfg.Admin.TokenEnv == "" { cfg.Admin.TokenEnv = "AIGW_ADMIN_TOKEN" } if cfg.Admin.BasePath == "" { cfg.Admin.BasePath = "/admin" } if cfg.Admin.SessionTTLHours == 0 { cfg.Admin.SessionTTLHours = 12 } if cfg.Admin.AuditRetentionDays == 0 { cfg.Admin.AuditRetentionDays = 2555 } if cfg.Admin.SecurityRetentionDays == 0 { cfg.Admin.SecurityRetentionDays = 30 } if cfg.Admin.PublicURLEnv == "" { cfg.Admin.PublicURLEnv = "AIGW_PUBLIC_URL" } if cfg.Admin.InferencePublicURLEnv == "" { cfg.Admin.InferencePublicURLEnv = "AIGW_INFERENCE_PUBLIC_URL" } if cfg.Admin.Mail.FromName == "" { cfg.Admin.Mail.FromName = "AIGW" } if cfg.Admin.Mail.TLSMode == "" { cfg.Admin.Mail.TLSMode = "starttls" } if cfg.Admin.Mail.FromAddressEnv == "" { cfg.Admin.Mail.FromAddressEnv = "AIGW_SMTP_FROM_ADDRESS" } if cfg.Admin.Mail.SMTPAddressEnv == "" { cfg.Admin.Mail.SMTPAddressEnv = "AIGW_SMTP_ADDRESS" } if cfg.Admin.Mail.SMTPUsernameEnv == "" { cfg.Admin.Mail.SMTPUsernameEnv = "AIGW_SMTP_USERNAME" } if cfg.Admin.Mail.SMTPPasswordEnv == "" { cfg.Admin.Mail.SMTPPasswordEnv = "AIGW_SMTP_PASSWORD" } if cfg.Admin.Mail.FeedbackSecretEnv == "" { cfg.Admin.Mail.FeedbackSecretEnv = "AIGW_MAIL_FEEDBACK_SECRET" } if cfg.Admin.Mail.LowBalanceMicros == 0 { cfg.Admin.Mail.LowBalanceMicros = 5_000_000 } if cfg.Admin.Mail.SpendAnomalyMultiplier == 0 { cfg.Admin.Mail.SpendAnomalyMultiplier = 3 } if cfg.Admin.Mail.SpendAnomalyMinMicros == 0 { cfg.Admin.Mail.SpendAnomalyMinMicros = 10_000_000 } if cfg.Admin.Mail.NotificationIntervalSeconds == 0 { cfg.Admin.Mail.NotificationIntervalSeconds = 300 } if cfg.Admin.WebAuthn.RPDisplayName == "" { cfg.Admin.WebAuthn.RPDisplayName = "AIGW Console" } if cfg.Admin.WebAuthn.RPIDEnv == "" { cfg.Admin.WebAuthn.RPIDEnv = "AIGW_WEBAUTHN_RP_ID" } if cfg.Admin.WebAuthn.OriginsEnv == "" { cfg.Admin.WebAuthn.OriginsEnv = "AIGW_WEBAUTHN_ORIGINS" } if cfg.UpstreamHTTP.MaxIdleConnections == 0 { cfg.UpstreamHTTP.MaxIdleConnections = 4096 } if cfg.UpstreamHTTP.MaxIdleConnectionsPerHost == 0 { cfg.UpstreamHTTP.MaxIdleConnectionsPerHost = 1024 } if cfg.UpstreamHTTP.IdleConnectionTimeoutSecs == 0 { cfg.UpstreamHTTP.IdleConnectionTimeoutSecs = 90 } if cfg.UpstreamHTTP.ResponseHeaderTimeoutSecs == 0 { cfg.UpstreamHTTP.ResponseHeaderTimeoutSecs = 60 } if cfg.ProviderHealth.ActiveProbesEnabledEnv == "" { cfg.ProviderHealth.ActiveProbesEnabledEnv = "AIGW_PROVIDER_ACTIVE_PROBES_ENABLED" } if cfg.ProviderHealth.SharedHistoryEnabledEnv == "" { cfg.ProviderHealth.SharedHistoryEnabledEnv = "AIGW_PROVIDER_SHARED_HISTORY_ENABLED" } if cfg.ProviderHealth.SharedHistoryStream == "" { cfg.ProviderHealth.SharedHistoryStream = "aigw:provider-health:events" } if cfg.ProviderHealth.SharedHistoryTTLSeconds == 0 { cfg.ProviderHealth.SharedHistoryTTLSeconds = 900 } if cfg.ProviderHealth.SharedHistoryMaxEvents == 0 { cfg.ProviderHealth.SharedHistoryMaxEvents = 20000 } if cfg.ProviderHealth.ProbeIntervalSeconds == 0 { cfg.ProviderHealth.ProbeIntervalSeconds = 30 } if cfg.ProviderHealth.ProbeTimeoutSeconds == 0 { cfg.ProviderHealth.ProbeTimeoutSeconds = 5 } if cfg.Observability.UsageBuffer == 0 { cfg.Observability.UsageBuffer = 8192 } if cfg.Billing.Currency == "" { cfg.Billing.Currency = "usd" } if cfg.Billing.DefaultMaxOutputTokens == 0 { cfg.Billing.DefaultMaxOutputTokens = 4096 } if cfg.Billing.MaxTopUpMinor == 0 { cfg.Billing.MaxTopUpMinor = 1000000 } if cfg.Billing.MinTopUpMinor == 0 { cfg.Billing.MinTopUpMinor = 500 } if cfg.Billing.SettlementSpoolPathEnv == "" { cfg.Billing.SettlementSpoolPathEnv = "AIGW_SETTLEMENT_SPOOL_PATH" } if cfg.Billing.Stripe.APIKeyEnv == "" { cfg.Billing.Stripe.APIKeyEnv = "AIGW_STRIPE_API_KEY" } if cfg.Billing.Stripe.WebhookSecretEnv == "" { cfg.Billing.Stripe.WebhookSecretEnv = "AIGW_STRIPE_WEBHOOK_SECRET" } if cfg.Billing.Stripe.SuccessURLEnv == "" { cfg.Billing.Stripe.SuccessURLEnv = "AIGW_STRIPE_SUCCESS_URL" } if cfg.Billing.Stripe.CancelURLEnv == "" { cfg.Billing.Stripe.CancelURLEnv = "AIGW_STRIPE_CANCEL_URL" } if cfg.Billing.Stripe.PortalReturnURLEnv == "" { cfg.Billing.Stripe.PortalReturnURLEnv = "AIGW_STRIPE_PORTAL_RETURN_URL" } if cfg.Billing.Stripe.AutomaticTaxEnabledEnv == "" { cfg.Billing.Stripe.AutomaticTaxEnabledEnv = "AIGW_STRIPE_AUTOMATIC_TAX_ENABLED" } if cfg.Billing.Stripe.TaxRegistrationConfirmedEnv == "" { cfg.Billing.Stripe.TaxRegistrationConfirmedEnv = "AIGW_STRIPE_TAX_REGISTRATION_CONFIRMED" } if cfg.Billing.Stripe.ProductTaxCodeEnv == "" { cfg.Billing.Stripe.ProductTaxCodeEnv = "AIGW_STRIPE_PRODUCT_TAX_CODE" } for i := range cfg.Models { for j := range cfg.Models[i].Routes { if cfg.Models[i].Routes[j].Weight == 0 { cfg.Models[i].Routes[j].Weight = 1 } } } for i := range cfg.Providers { if cfg.Providers[i].Slug == "" { cfg.Providers[i].Slug = cfg.Providers[i].ID } if cfg.Providers[i].WireAPI == "" { if cfg.Providers[i].Protocol == domain.ProtocolAnthropic { cfg.Providers[i].WireAPI = "messages" } else { cfg.Providers[i].WireAPI = "chat_completions" } } } } func resolveSecrets(cfg *Config) error { if value := strings.TrimSpace(os.Getenv(cfg.Server.AddressEnv)); value != "" { cfg.Server.Address = value } if cfg.Server.SplitListeners { if err := resolveRequiredEnv(&cfg.Server.PublicAddress, cfg.Server.PublicAddressEnv, "server.public_address"); err != nil { return err } if err := resolveRequiredEnv(&cfg.Server.AdminAddress, cfg.Server.AdminAddressEnv, "server.admin_address"); err != nil { return err } if err := resolveRequiredEnv(&cfg.Server.WebhookAddress, cfg.Server.WebhookAddressEnv, "server.webhook_address"); err != nil { return err } if err := resolveRequiredEnv(&cfg.Server.OperationsAddress, cfg.Server.OperationsAddressEnv, "server.operations_address"); err != nil { return err } } for _, cidr := range strings.Split(os.Getenv(cfg.Server.TrustedProxyCIDRsEnv), ",") { if cidr = strings.TrimSpace(cidr); cidr != "" { cfg.Server.TrustedProxyCIDRs = append(cfg.Server.TrustedProxyCIDRs, cidr) } } var err error cfg.Server.RequireHTTPS, err = envBool(cfg.Server.RequireHTTPSEnv) if err != nil { return err } cfg.Server.DeploymentRegion = strings.ToLower(strings.TrimSpace(os.Getenv(cfg.Server.DeploymentRegionEnv))) cfg.ProviderHealth.ActiveProbesEnabled, err = envBool(cfg.ProviderHealth.ActiveProbesEnabledEnv) if err != nil { return err } cfg.ProviderHealth.SharedHistoryEnabled, err = envBool(cfg.ProviderHealth.SharedHistoryEnabledEnv) if err != nil { return err } if cfg.ControlPlane.Enabled { cfg.ControlPlane.DatabaseURL = os.Getenv(cfg.ControlPlane.DatabaseURLEnv) cfg.ControlPlane.RedisURL = os.Getenv(cfg.ControlPlane.RedisURLEnv) cfg.ControlPlane.CredentialKey = os.Getenv(cfg.ControlPlane.CredentialKeyEnv) for _, value := range strings.Split(os.Getenv(cfg.ControlPlane.PreviousCredentialKeysEnv), ",") { if value = strings.TrimSpace(value); value != "" { cfg.ControlPlane.PreviousCredentialKeys = append(cfg.ControlPlane.PreviousCredentialKeys, value) } } } if cfg.Admin.Enabled { cfg.Admin.Token = os.Getenv(cfg.Admin.TokenEnv) if err := resolveRequiredEnv(&cfg.Admin.PublicURL, cfg.Admin.PublicURLEnv, "admin.public_url"); err != nil { return err } cfg.Admin.InferencePublicURL = strings.TrimRight(strings.TrimSpace(os.Getenv(cfg.Admin.InferencePublicURLEnv)), "/") if cfg.Admin.InferencePublicURL == "" { publicURL, err := url.Parse(cfg.Admin.PublicURL) if err == nil && publicURL.Scheme != "" && publicURL.Host != "" { cfg.Admin.InferencePublicURL = publicURL.Scheme + "://" + publicURL.Host } } if cfg.Admin.Mail.Enabled { cfg.Admin.Mail.FromAddress = strings.TrimSpace(os.Getenv(cfg.Admin.Mail.FromAddressEnv)) cfg.Admin.Mail.SMTPAddress = strings.TrimSpace(os.Getenv(cfg.Admin.Mail.SMTPAddressEnv)) cfg.Admin.Mail.SMTPUsername = os.Getenv(cfg.Admin.Mail.SMTPUsernameEnv) cfg.Admin.Mail.SMTPPassword = os.Getenv(cfg.Admin.Mail.SMTPPasswordEnv) cfg.Admin.Mail.FeedbackSecret = os.Getenv(cfg.Admin.Mail.FeedbackSecretEnv) } if cfg.Admin.WebAuthn.Enabled { cfg.Admin.WebAuthn.RPID = strings.TrimSpace(os.Getenv(cfg.Admin.WebAuthn.RPIDEnv)) for _, origin := range strings.Split(os.Getenv(cfg.Admin.WebAuthn.OriginsEnv), ",") { if origin = strings.TrimSpace(origin); origin != "" { cfg.Admin.WebAuthn.Origins = append(cfg.Admin.WebAuthn.Origins, origin) } } } } if cfg.Billing.Enabled && cfg.Billing.Stripe.EnabledEnv != "" { var err error cfg.Billing.Stripe.Enabled, err = envBool(cfg.Billing.Stripe.EnabledEnv) if err != nil { return err } } if cfg.Billing.Enabled && cfg.Billing.Stripe.Enabled { cfg.Billing.Stripe.APIKey = os.Getenv(cfg.Billing.Stripe.APIKeyEnv) cfg.Billing.Stripe.WebhookSecret = os.Getenv(cfg.Billing.Stripe.WebhookSecretEnv) if err := resolveRequiredEnv(&cfg.Billing.Stripe.SuccessURL, cfg.Billing.Stripe.SuccessURLEnv, "billing.stripe.success_url"); err != nil { return err } if err := resolveRequiredEnv(&cfg.Billing.Stripe.CancelURL, cfg.Billing.Stripe.CancelURLEnv, "billing.stripe.cancel_url"); err != nil { return err } if err := resolveRequiredEnv(&cfg.Billing.Stripe.PortalReturnURL, cfg.Billing.Stripe.PortalReturnURLEnv, "billing.stripe.portal_return_url"); err != nil { return err } var err error cfg.Billing.Stripe.AutomaticTaxEnabled, err = envBool(cfg.Billing.Stripe.AutomaticTaxEnabledEnv) if err != nil { return err } cfg.Billing.Stripe.TaxRegistrationConfirmed, err = envBool(cfg.Billing.Stripe.TaxRegistrationConfirmedEnv) if err != nil { return err } cfg.Billing.Stripe.ProductTaxCode = strings.TrimSpace(os.Getenv(cfg.Billing.Stripe.ProductTaxCodeEnv)) } if cfg.Billing.Enabled { cfg.Billing.SettlementSpoolPath = strings.TrimSpace(os.Getenv(cfg.Billing.SettlementSpoolPathEnv)) } for i := range cfg.Providers { provider := &cfg.Providers[i] if provider.BaseURLEnv == "" { return fmt.Errorf("provider %q: base_url_env is required", provider.ID) } if err := resolveRequiredEnv(&provider.BaseURL, provider.BaseURLEnv, fmt.Sprintf("provider %q base_url", provider.ID)); err != nil { return err } if provider.APIKeyEnv == "" { continue } provider.APIKey = os.Getenv(provider.APIKeyEnv) if provider.APIKey == "" { return fmt.Errorf("provider %q: environment variable %s is empty", provider.ID, provider.APIKeyEnv) } } return nil } func envBool(name string) (bool, error) { value := strings.TrimSpace(os.Getenv(name)) if value == "" || strings.EqualFold(value, "false") || value == "0" { return false, nil } if strings.EqualFold(value, "true") || value == "1" { return true, nil } return false, fmt.Errorf("environment variable %s must be true/false or 1/0", name) } func resolveRequiredEnv(target *string, environment, field string) error { if environment == "" { return nil } value := strings.TrimSpace(os.Getenv(environment)) if value == "" { return fmt.Errorf("%s: environment variable %s is empty", field, environment) } *target = value return nil } func Validate(cfg Config) error { if cfg.Server.MaxBodyBytes < 1024 { return errors.New("server.max_body_bytes must be at least 1024") } if cfg.Observability.UsageBuffer < 1 { return errors.New("observability.usage_buffer must be positive") } if cfg.ProviderHealth.ProbeIntervalSeconds < 5 || cfg.ProviderHealth.ProbeIntervalSeconds > 3600 || cfg.ProviderHealth.ProbeTimeoutSeconds < 1 || cfg.ProviderHealth.ProbeTimeoutSeconds >= cfg.ProviderHealth.ProbeIntervalSeconds { return errors.New("provider_health probe interval must be 5-3600 seconds and timeout must be shorter than the interval") } if cfg.ProviderHealth.SharedHistoryTTLSeconds < 60 || cfg.ProviderHealth.SharedHistoryTTLSeconds > 86400 || cfg.ProviderHealth.SharedHistoryMaxEvents < 100 || cfg.ProviderHealth.SharedHistoryMaxEvents > 1_000_000 || strings.TrimSpace(cfg.ProviderHealth.SharedHistoryStream) == "" { return errors.New("provider_health shared history requires a stream, TTL of 60-86400 seconds, and 100-1000000 events") } if cfg.Server.SplitListeners { seen := map[string]string{} for name, address := range map[string]string{"public": cfg.Server.PublicAddress, "admin": cfg.Server.AdminAddress, "webhook": cfg.Server.WebhookAddress, "operations": cfg.Server.OperationsAddress} { if strings.TrimSpace(address) == "" { return fmt.Errorf("server %s listener address is empty", name) } if previous, ok := seen[address]; ok { return fmt.Errorf("server %s and %s listeners must use different addresses", previous, name) } seen[address] = name } } for _, cidr := range cfg.Server.TrustedProxyCIDRs { if _, _, err := net.ParseCIDR(cidr); err != nil { return fmt.Errorf("invalid trusted proxy CIDR %q", cidr) } } if cfg.ControlPlane.Enabled { if cfg.ControlPlane.DatabaseURL == "" { return fmt.Errorf("control_plane: environment variable %s is empty", cfg.ControlPlane.DatabaseURLEnv) } if cfg.ControlPlane.CredentialKey == "" { return fmt.Errorf("control_plane: environment variable %s is empty", cfg.ControlPlane.CredentialKeyEnv) } if cfg.ControlPlane.ReloadIntervalSeconds < 1 { return errors.New("control_plane.reload_interval_seconds must be positive") } } if cfg.Admin.Enabled { if !cfg.ControlPlane.Enabled { return errors.New("admin requires control_plane.enabled") } if cfg.Admin.Token == "" { return fmt.Errorf("admin: environment variable %s is empty", cfg.Admin.TokenEnv) } if !strings.HasPrefix(cfg.Admin.BasePath, "/") || cfg.Admin.BasePath == "/" { return errors.New("admin.base_path must start with / and cannot be /") } if cfg.Admin.SessionTTLHours < 1 || cfg.Admin.SessionTTLHours > 720 { return errors.New("admin.session_ttl_hours must be between 1 and 720") } if cfg.Admin.AuditRetentionDays < 30 || cfg.Admin.SecurityRetentionDays < 1 { return errors.New("admin audit retention must be at least 30 days and security retention at least 1 day") } publicURL, err := url.Parse(cfg.Admin.PublicURL) if err != nil || publicURL.Host == "" || (publicURL.Scheme != "http" && publicURL.Scheme != "https") { return errors.New("admin.public_url must resolve from an environment variable to an absolute http(s) URL") } inferenceURL, err := url.Parse(cfg.Admin.InferencePublicURL) if err != nil || inferenceURL.Host == "" || (inferenceURL.Scheme != "http" && inferenceURL.Scheme != "https") { return errors.New("admin.inference_public_url must resolve from an environment variable to an absolute http(s) URL") } if cfg.Admin.Mail.Enabled { if cfg.Admin.Mail.FromAddress == "" || cfg.Admin.Mail.SMTPAddress == "" { return errors.New("admin.mail requires SMTP address and from address environment variables") } if (cfg.Admin.Mail.SMTPUsername == "") != (cfg.Admin.Mail.SMTPPassword == "") { return errors.New("admin.mail SMTP username and password must both be set or both be empty") } if cfg.Admin.Mail.FeedbackSecret != "" && len(cfg.Admin.Mail.FeedbackSecret) < 32 { return errors.New("admin.mail feedback secret must contain at least 32 characters") } if cfg.Admin.Mail.LowBalanceMicros < 0 || cfg.Admin.Mail.SpendAnomalyMultiplier < 2 || cfg.Admin.Mail.SpendAnomalyMinMicros < 0 || cfg.Admin.Mail.NotificationIntervalSeconds < 30 { return errors.New("admin.mail notification thresholds are invalid") } switch cfg.Admin.Mail.TLSMode { case "starttls", "tls", "none": default: return errors.New("admin.mail.tls_mode must be starttls, tls, or none") } } if cfg.Admin.WebAuthn.Enabled { if cfg.Admin.WebAuthn.RPID == "" || len(cfg.Admin.WebAuthn.Origins) == 0 { return errors.New("admin.webauthn requires RP ID and origins environment variables") } for _, origin := range cfg.Admin.WebAuthn.Origins { parsed, err := url.Parse(origin) if err != nil || parsed.Host == "" || (parsed.Scheme != "https" && !(parsed.Scheme == "http" && parsed.Hostname() == "localhost")) { return fmt.Errorf("admin.webauthn origin %q must use https (http is allowed only for localhost)", origin) } } } } if cfg.Billing.Enabled { if !cfg.ControlPlane.Enabled { return errors.New("billing requires control_plane.enabled") } if cfg.Auth.AllowAnonymous { return errors.New("billing cannot be enabled with auth.allow_anonymous") } if len(cfg.Billing.Currency) != 3 || strings.ToLower(cfg.Billing.Currency) != cfg.Billing.Currency { return errors.New("billing.currency must be a lowercase ISO 4217 code") } if cfg.Billing.DefaultMaxOutputTokens < 1 { return errors.New("billing.default_max_output_tokens must be positive") } if cfg.Billing.MinTopUpMinor < 1 || cfg.Billing.MaxTopUpMinor < cfg.Billing.MinTopUpMinor { return errors.New("billing top-up bounds are invalid") } if strings.TrimSpace(cfg.Billing.SettlementSpoolPath) == "" { return fmt.Errorf("billing: environment variable %s is empty; durable settlement fallback is required", cfg.Billing.SettlementSpoolPathEnv) } if cfg.Billing.Stripe.Enabled { if cfg.Billing.Stripe.APIKey == "" { return fmt.Errorf("billing.stripe: environment variable %s is empty", cfg.Billing.Stripe.APIKeyEnv) } if cfg.Billing.Stripe.WebhookSecret == "" { return fmt.Errorf("billing.stripe: environment variable %s is empty", cfg.Billing.Stripe.WebhookSecretEnv) } if strings.TrimSpace(cfg.Billing.Stripe.SuccessURL) == "" || strings.TrimSpace(cfg.Billing.Stripe.CancelURL) == "" || strings.TrimSpace(cfg.Billing.Stripe.PortalReturnURL) == "" { return errors.New("billing.stripe success, cancel, and portal return URLs are required") } for name, value := range map[string]string{"success_url": cfg.Billing.Stripe.SuccessURL, "cancel_url": cfg.Billing.Stripe.CancelURL, "portal_return_url": cfg.Billing.Stripe.PortalReturnURL} { parsed, err := url.Parse(value) if err != nil || parsed.Host == "" || (parsed.Scheme != "http" && parsed.Scheme != "https") { return fmt.Errorf("billing.stripe.%s must be an absolute http(s) URL", name) } if cfg.Billing.Stripe.AutomaticTaxEnabled && (!cfg.Billing.Stripe.TaxRegistrationConfirmed || cfg.Billing.Stripe.ProductTaxCode == "") { return errors.New("Stripe automatic tax requires an explicit confirmed registration and product tax code") } } } } providers := make(map[string]ProviderConfig, len(cfg.Providers)) providerSlugs := make(map[string]string, len(cfg.Providers)) for _, provider := range cfg.Providers { if provider.ID == "" { return errors.New("provider id is required") } if !validProviderSlug(provider.Slug) { return fmt.Errorf("provider %q: slug must be 3-64 lowercase letters, numbers, or hyphens", provider.ID) } if existingID, exists := providerSlugs[provider.Slug]; exists { return fmt.Errorf("provider %q: duplicate public slug already used by provider %q", provider.ID, existingID) } providerSlugs[provider.Slug] = provider.ID if _, exists := providers[provider.ID]; exists { return fmt.Errorf("duplicate provider id %q", provider.ID) } if provider.Protocol != domain.ProtocolOpenAI && provider.Protocol != domain.ProtocolAnthropic { return fmt.Errorf("provider %q: unsupported protocol %q", provider.ID, provider.Protocol) } if provider.Protocol == domain.ProtocolOpenAI && provider.WireAPI != "chat_completions" && provider.WireAPI != "responses" && provider.WireAPI != "embeddings" { return fmt.Errorf("provider %q: wire_api must be chat_completions, responses, or embeddings", provider.ID) } if provider.Protocol == domain.ProtocolAnthropic && provider.WireAPI != "messages" { return fmt.Errorf("provider %q: wire_api must be messages", provider.ID) } parsed, err := url.Parse(provider.BaseURL) if err != nil || parsed.Host == "" || (parsed.Scheme != "http" && parsed.Scheme != "https") { return fmt.Errorf("provider %q: base_url must be an absolute http(s) URL", provider.ID) } if provider.APIKeyEnv == "" { return fmt.Errorf("provider %q: api_key_env is required", provider.ID) } providers[provider.ID] = provider } if len(providers) == 0 && !cfg.ControlPlane.Enabled { return errors.New("at least one provider is required") } models := make(map[string]struct{}, len(cfg.Models)) for _, model := range cfg.Models { if strings.TrimSpace(model.ID) == "" { return errors.New("model id is required") } if _, exists := models[model.ID]; exists { return fmt.Errorf("duplicate model id %q", model.ID) } models[model.ID] = struct{}{} if model.InputPriceMicrosPerMillion < 0 || model.OutputPriceMicrosPerMillion < 0 || model.CacheReadPriceMicrosPerMillion < 0 || model.CacheWritePriceMicrosPerMillion < 0 { return fmt.Errorf("model %q: prices cannot be negative", model.ID) } if len(model.Routes) == 0 { return fmt.Errorf("model %q: at least one route is required", model.ID) } for _, route := range model.Routes { if _, exists := providers[route.Provider]; !exists { return fmt.Errorf("model %q: unknown provider %q", model.ID, route.Provider) } if route.UpstreamModel == "" { return fmt.Errorf("model %q: upstream_model is required", model.ID) } if route.Priority < 0 { return fmt.Errorf("model %q: route priority cannot be negative", model.ID) } if route.Weight < 1 || route.Weight > 100 { return fmt.Errorf("model %q: route weight must be between 1 and 100", model.ID) } } } if len(models) == 0 && !cfg.ControlPlane.Enabled { return errors.New("at least one model is required") } return nil } func validProviderSlug(value string) bool { if len(value) < 3 || len(value) > 64 || value[0] == '-' || value[len(value)-1] == '-' { return false } for _, character := range value { if (character < 'a' || character > 'z') && (character < '0' || character > '9') && character != '-' { return false } } return true } func (c ServerConfig) ReadHeaderTimeout() time.Duration { return time.Duration(c.ReadHeaderTimeoutSecs) * time.Second } func (c ServerConfig) IdleTimeout() time.Duration { return time.Duration(c.IdleTimeoutSecs) * time.Second } func (c ServerConfig) ShutdownTimeout() time.Duration { return time.Duration(c.ShutdownTimeoutSecs) * time.Second }