package config import ( "bytes" "errors" "fmt" "io" "net/url" "os" "path/filepath" "strconv" "strings" "time" ) const ( EnvironmentLocal = "local" EnvironmentTest = "test" EnvironmentDev = "dev" EnvironmentStage = "stage" EnvironmentProd = "prod" ) type Config struct { ServiceName string Environment string LogLevel string HTTPAddr string DatabaseURL string RedisAddr string RedisPassword string RedisDB int AuthIssuerURL string AuthJWKSURL string AuthAudience string OTLPEndpoint string RateLimitRPS float64 RateLimitBurst int RateLimitUserRPS float64 RateLimitUserBurst int RateLimitTenantRPS float64 RateLimitTenantBurst int RateLimitDeviceRPS float64 RateLimitDeviceBurst int ProviderStartRPS float64 ProviderStartBurst int ProviderMaxActiveTasks int DeviceLocalSecret string DeviceSecretDirectory string DeviceClockSkew time.Duration RTCAppID string RTCAppKey string RTCTokenIssuer string RTCTokenSecret string CursorSigningSecret string ShareTokenSecret string RTCTokenTTL time.Duration AllocationTTL time.Duration HeartbeatInterval int ProviderCallbackSecret string ProviderCallbackSkew time.Duration PlatformHealthSecret string PlatformHealthSkew time.Duration MockProviderBaseURL string MockProviderAPIKey string VoiceProvider string VolcRTCBaseURL string VolcAccessKeyID string VolcSecretAccessKey string VolcSessionToken string VolcRTCRegion string VolcRTCAPIVersion string VolcVoiceChatTemplate string ProviderRequestTimeout time.Duration ProviderFailureThreshold int ProviderCircuitCooldown time.Duration DependencyTimeout time.Duration ExternalProbeInterval time.Duration ShutdownTimeout time.Duration WorkerPollInterval time.Duration RecoveryScanInterval time.Duration RecoveryStartingAfter time.Duration RecoveryStoppingAfter time.Duration ObjectStoreEndpoint string ObjectStorePublic string ObjectStoreAccessKey string ObjectStoreSecretKey string ObjectStoreBucket string ObjectStoreRegion string ObjectStoreSecure bool ObjectStorePublicSecure bool ExportDownloadTTL time.Duration ExportObjectTTL time.Duration MediaLocalDeleteGrace time.Duration AlertWebhookURL string AlertWebhookSecret string AlertWebhookTimeout time.Duration ContentGenerator string ArkBaseURL string ArkAPIKey string ArkContentModel string ArkRequestTimeout time.Duration ContentSafetyProvider string ContentSafetyEndpoint string ContentSafetyAPIKey string ContentSafetyPolicyRef string ContentSafetyTimeout time.Duration EmbeddingProvider string ArkEmbeddingModel string } func Load(serviceName string) (Config, error) { if strings.TrimSpace(serviceName) == "" { return Config{}, errors.New("service name is required") } redisDB, err := intEnv("REDIS_DB", 0) if err != nil { return Config{}, err } dependencyTimeout, err := durationEnv("DEPENDENCY_TIMEOUT", 2*time.Second) if err != nil { return Config{}, err } externalProbeInterval, err := durationEnv("EXTERNAL_PROBE_INTERVAL", 30*time.Second) if err != nil { return Config{}, err } shutdownTimeout, err := durationEnv("SHUTDOWN_TIMEOUT", 10*time.Second) if err != nil { return Config{}, err } pollInterval, err := durationEnv("WORKER_POLL_INTERVAL", 2*time.Second) if err != nil { return Config{}, err } recoveryScanInterval, err := durationEnv("RECOVERY_SCAN_INTERVAL", 5*time.Second) if err != nil { return Config{}, err } recoveryStartingAfter, err := durationEnv("RECOVERY_STARTING_AFTER", 20*time.Second) if err != nil { return Config{}, err } recoveryStoppingAfter, err := durationEnv("RECOVERY_STOPPING_AFTER", 20*time.Second) if err != nil { return Config{}, err } deviceClockSkew, err := durationEnv("DEVICE_CLOCK_SKEW", 5*time.Minute) if err != nil { return Config{}, err } rtcTokenTTL, err := durationEnv("RTC_TOKEN_TTL", 5*time.Minute) if err != nil { return Config{}, err } allocationTTL, err := durationEnv("CONVERSATION_ALLOCATION_TTL", 30*time.Second) if err != nil { return Config{}, err } providerCallbackSkew, err := durationEnv("PROVIDER_CALLBACK_CLOCK_SKEW", 5*time.Minute) if err != nil { return Config{}, err } platformHealthSkew, err := durationEnv("PLATFORM_HEALTH_CALLBACK_CLOCK_SKEW", 5*time.Minute) if err != nil { return Config{}, err } providerRequestTimeout, err := durationEnv("PROVIDER_REQUEST_TIMEOUT", 5*time.Second) if err != nil { return Config{}, err } providerCircuitCooldown, err := durationEnv("PROVIDER_CIRCUIT_COOLDOWN", time.Minute) if err != nil { return Config{}, err } exportDownloadTTL, err := durationEnv("EXPORT_DOWNLOAD_TTL", 10*time.Minute) if err != nil { return Config{}, err } exportObjectTTL, err := durationEnv("EXPORT_OBJECT_TTL", 24*time.Hour) if err != nil { return Config{}, err } mediaLocalDeleteGrace, err := durationEnv("MEDIA_LOCAL_DELETE_GRACE", time.Hour) if err != nil { return Config{}, err } alertWebhookTimeout, err := durationEnv("ALERT_WEBHOOK_TIMEOUT", 5*time.Second) if err != nil { return Config{}, err } arkRequestTimeout, err := durationEnv("ARK_REQUEST_TIMEOUT", 30*time.Second) if err != nil { return Config{}, err } contentSafetyTimeout, err := durationEnv("CONTENT_SAFETY_TIMEOUT", 5*time.Second) if err != nil { return Config{}, err } objectStoreSecure, err := boolEnv("OBJECT_STORE_SECURE", false) if err != nil { return Config{}, err } objectStorePublicSecure, err := boolEnv("OBJECT_STORE_PUBLIC_SECURE", objectStoreSecure) if err != nil { return Config{}, err } environment := stringEnv("APP_ENV", EnvironmentLocal) secretValues, err := loadSecretValues( "DATABASE_URL", "REDIS_PASSWORD", "DEVICE_LOCAL_SECRET", "RTC_APP_KEY", "RTC_TOKEN_SIGNING_SECRET", "CURSOR_SIGNING_SECRET", "SHARE_TOKEN_SIGNING_SECRET", "PROVIDER_CALLBACK_SECRET", "PLATFORM_HEALTH_CALLBACK_SECRET", "MOCK_PROVIDER_API_KEY", "VOLC_ACCESS_KEY_ID", "VOLC_SECRET_ACCESS_KEY", "VOLC_SESSION_TOKEN", "OBJECT_STORE_ACCESS_KEY", "OBJECT_STORE_SECRET_KEY", "ALERT_WEBHOOK_SECRET", "ARK_API_KEY", "CONTENT_SAFETY_API_KEY", ) if err != nil { return Config{}, err } deviceLocalSecret := secretValues["DEVICE_LOCAL_SECRET"] rtcTokenSecret := secretValues["RTC_TOKEN_SIGNING_SECRET"] cursorSigningSecret := secretValues["CURSOR_SIGNING_SECRET"] shareTokenSecret := secretValues["SHARE_TOKEN_SIGNING_SECRET"] providerCallbackSecret := secretValues["PROVIDER_CALLBACK_SECRET"] objectStoreEndpoint := strings.TrimSpace(os.Getenv("OBJECT_STORE_ENDPOINT")) objectStorePublic := strings.TrimSpace(os.Getenv("OBJECT_STORE_PUBLIC_ENDPOINT")) objectStoreAccessKey := secretValues["OBJECT_STORE_ACCESS_KEY"] objectStoreSecretKey := secretValues["OBJECT_STORE_SECRET_KEY"] objectStoreBucket := strings.TrimSpace(os.Getenv("OBJECT_STORE_EXPORT_BUCKET")) objectStoreRegion := strings.TrimSpace(os.Getenv("OBJECT_STORE_REGION")) if deviceLocalSecret == "" && serviceName == "api" && (environment == EnvironmentLocal || environment == EnvironmentTest) { deviceLocalSecret = "local-simulated-device-secret-32-bytes-minimum" } if rtcTokenSecret == "" && serviceName == "api" && (environment == EnvironmentLocal || environment == EnvironmentTest) { rtcTokenSecret = "local-simulated-rtc-token-secret-32-bytes-minimum" } if cursorSigningSecret == "" && serviceName == "api" && (environment == EnvironmentLocal || environment == EnvironmentTest) { cursorSigningSecret = "local-signed-pagination-cursor-secret-32-bytes-minimum" } if shareTokenSecret == "" && serviceName == "api" && (environment == EnvironmentLocal || environment == EnvironmentTest) { shareTokenSecret = "local-share-token-signing-secret-32-bytes-minimum" } if providerCallbackSecret == "" && serviceName == "callbacks" && (environment == EnvironmentLocal || environment == EnvironmentTest) { providerCallbackSecret = "local-provider-callback-secret-32-bytes-minimum" } if (serviceName == "api" || serviceName == "worker") && (environment == EnvironmentLocal || environment == EnvironmentTest) { if objectStoreEndpoint == "" { objectStoreEndpoint = "localhost:59000" } if objectStorePublic == "" { objectStorePublic = "localhost:59000" } if objectStoreAccessKey == "" { objectStoreAccessKey = "local_minio" } if objectStoreSecretKey == "" { objectStoreSecretKey = "local_minio_password" } if objectStoreBucket == "" { objectStoreBucket = "private-exports" } if objectStoreRegion == "" { objectStoreRegion = "us-east-1" } } cfg := Config{ ServiceName: serviceName, Environment: environment, LogLevel: stringEnv("LOG_LEVEL", "info"), HTTPAddr: stringEnv("HTTP_ADDR", defaultHTTPAddr(serviceName)), DatabaseURL: secretDefault(secretValues["DATABASE_URL"], "postgres://voice:voice_dev_only@localhost:55432/voice_platform?sslmode=disable"), RedisAddr: stringEnv("REDIS_ADDR", "localhost:56379"), RedisPassword: secretValues["REDIS_PASSWORD"], RedisDB: redisDB, AuthIssuerURL: stringEnv("AUTH_ISSUER_URL", "http://localhost:58180/realms/voice-local"), AuthJWKSURL: stringEnv("AUTH_JWKS_URL", "http://localhost:58180/realms/voice-local/protocol/openid-connect/certs"), AuthAudience: stringEnv("AUTH_AUDIENCE", "voice-platform-web"), OTLPEndpoint: stringEnvAllowEmpty("OTEL_EXPORTER_OTLP_ENDPOINT", "localhost:54317"), DependencyTimeout: dependencyTimeout, ExternalProbeInterval: externalProbeInterval, ShutdownTimeout: shutdownTimeout, WorkerPollInterval: pollInterval, RecoveryScanInterval: recoveryScanInterval, RecoveryStartingAfter: recoveryStartingAfter, RecoveryStoppingAfter: recoveryStoppingAfter, DeviceLocalSecret: deviceLocalSecret, DeviceSecretDirectory: strings.TrimSpace(os.Getenv("DEVICE_SECRET_DIRECTORY")), DeviceClockSkew: deviceClockSkew, RTCAppID: stringEnv("RTC_APP_ID", "local-public-rtc-app"), RTCAppKey: secretValues["RTC_APP_KEY"], RTCTokenIssuer: stringEnv("RTC_TOKEN_ISSUER", "local"), RTCTokenSecret: rtcTokenSecret, CursorSigningSecret: cursorSigningSecret, ShareTokenSecret: shareTokenSecret, RTCTokenTTL: rtcTokenTTL, AllocationTTL: allocationTTL, ProviderCallbackSecret: providerCallbackSecret, ProviderCallbackSkew: providerCallbackSkew, PlatformHealthSecret: secretValues["PLATFORM_HEALTH_CALLBACK_SECRET"], PlatformHealthSkew: platformHealthSkew, MockProviderBaseURL: stringEnv("MOCK_PROVIDER_BASE_URL", "http://localhost:59200"), MockProviderAPIKey: secretDefault(secretValues["MOCK_PROVIDER_API_KEY"], "local-mock-provider-key"), VoiceProvider: stringEnv("VOICE_PROVIDER", "mock"), VolcRTCBaseURL: stringEnv("VOLC_RTC_BASE_URL", "https://rtc.volcengineapi.com"), VolcAccessKeyID: secretValues["VOLC_ACCESS_KEY_ID"], VolcSecretAccessKey: secretValues["VOLC_SECRET_ACCESS_KEY"], VolcSessionToken: secretValues["VOLC_SESSION_TOKEN"], VolcRTCRegion: stringEnv("VOLC_RTC_REGION", "cn-north-1"), VolcRTCAPIVersion: stringEnv("VOLC_RTC_API_VERSION", "2024-12-01"), VolcVoiceChatTemplate: strings.TrimSpace(os.Getenv("VOLC_VOICE_CHAT_TEMPLATE_FILE")), ProviderRequestTimeout: providerRequestTimeout, ProviderCircuitCooldown: providerCircuitCooldown, ObjectStoreEndpoint: objectStoreEndpoint, ObjectStorePublic: objectStorePublic, ObjectStoreAccessKey: objectStoreAccessKey, ObjectStoreSecretKey: objectStoreSecretKey, ObjectStoreBucket: objectStoreBucket, ObjectStoreRegion: objectStoreRegion, ObjectStoreSecure: objectStoreSecure, ObjectStorePublicSecure: objectStorePublicSecure, ExportDownloadTTL: exportDownloadTTL, ExportObjectTTL: exportObjectTTL, MediaLocalDeleteGrace: mediaLocalDeleteGrace, AlertWebhookURL: strings.TrimSpace(os.Getenv("ALERT_WEBHOOK_URL")), AlertWebhookSecret: secretValues["ALERT_WEBHOOK_SECRET"], AlertWebhookTimeout: alertWebhookTimeout, ContentGenerator: stringEnv("CONTENT_GENERATOR", "local"), ArkBaseURL: stringEnv("ARK_BASE_URL", "https://ark.cn-beijing.volces.com/api/v3"), ArkAPIKey: secretValues["ARK_API_KEY"], ArkContentModel: strings.TrimSpace(os.Getenv("ARK_CONTENT_MODEL")), ArkRequestTimeout: arkRequestTimeout, ContentSafetyProvider: stringEnv("CONTENT_SAFETY_PROVIDER", "local"), ContentSafetyEndpoint: strings.TrimSpace(os.Getenv("CONTENT_SAFETY_ENDPOINT")), ContentSafetyAPIKey: secretValues["CONTENT_SAFETY_API_KEY"], ContentSafetyPolicyRef: strings.TrimSpace(os.Getenv("CONTENT_SAFETY_POLICY_REF")), ContentSafetyTimeout: contentSafetyTimeout, EmbeddingProvider: stringEnv("EMBEDDING_PROVIDER", "local"), ArkEmbeddingModel: strings.TrimSpace(os.Getenv("ARK_EMBEDDING_MODEL")), } cfg.RateLimitRPS, err = floatEnv("RATE_LIMIT_RPS", 50) if err != nil { return Config{}, err } cfg.RateLimitBurst, err = intEnv("RATE_LIMIT_BURST", 100) if err != nil { return Config{}, err } cfg.RateLimitUserRPS, err = floatEnv("RATE_LIMIT_USER_RPS", 20) if err != nil { return Config{}, err } cfg.RateLimitUserBurst, err = intEnv("RATE_LIMIT_USER_BURST", 40) if err != nil { return Config{}, err } cfg.RateLimitTenantRPS, err = floatEnv("RATE_LIMIT_TENANT_RPS", 200) if err != nil { return Config{}, err } cfg.RateLimitTenantBurst, err = intEnv("RATE_LIMIT_TENANT_BURST", 400) if err != nil { return Config{}, err } cfg.RateLimitDeviceRPS, err = floatEnv("RATE_LIMIT_DEVICE_RPS", 10) if err != nil { return Config{}, err } cfg.RateLimitDeviceBurst, err = intEnv("RATE_LIMIT_DEVICE_BURST", 20) if err != nil { return Config{}, err } cfg.ProviderStartRPS, err = floatEnv("PROVIDER_START_RPS", 20) if err != nil { return Config{}, err } cfg.ProviderStartBurst, err = intEnv("PROVIDER_START_BURST", 40) if err != nil { return Config{}, err } cfg.ProviderMaxActiveTasks, err = intEnv("PROVIDER_MAX_ACTIVE_TASKS", 100) if err != nil { return Config{}, err } cfg.HeartbeatInterval, err = intEnv("HEARTBEAT_INTERVAL_SECONDS", 15) if err != nil { return Config{}, err } cfg.ProviderFailureThreshold, err = intEnv("PROVIDER_FAILURE_THRESHOLD", 3) if err != nil { return Config{}, err } if err := cfg.Validate(); err != nil { return Config{}, err } return cfg, nil } func (c Config) Validate() error { validEnvironment := map[string]bool{ EnvironmentLocal: true, EnvironmentTest: true, EnvironmentDev: true, EnvironmentStage: true, EnvironmentProd: true, } if !validEnvironment[c.Environment] { return fmt.Errorf("APP_ENV %q is not supported", c.Environment) } if strings.TrimSpace(c.HTTPAddr) == "" { return errors.New("HTTP_ADDR is required") } if strings.TrimSpace(c.DatabaseURL) == "" { return errors.New("DATABASE_URL is required") } if strings.TrimSpace(c.RedisAddr) == "" { return errors.New("REDIS_ADDR is required") } if strings.TrimSpace(c.AuthIssuerURL) == "" || strings.TrimSpace(c.AuthJWKSURL) == "" || strings.TrimSpace(c.AuthAudience) == "" { return errors.New("AUTH_ISSUER_URL, AUTH_JWKS_URL and AUTH_AUDIENCE are required") } if c.DependencyTimeout <= 0 || c.ExternalProbeInterval <= 0 || c.ShutdownTimeout <= 0 || c.WorkerPollInterval <= 0 || c.RecoveryScanInterval <= 0 || c.RecoveryStartingAfter <= 0 || c.RecoveryStoppingAfter <= 0 { return errors.New("timeouts and worker poll interval must be positive") } if c.RateLimitRPS <= 0 || c.RateLimitBurst <= 0 || c.RateLimitUserRPS <= 0 || c.RateLimitUserBurst <= 0 || c.RateLimitTenantRPS <= 0 || c.RateLimitTenantBurst <= 0 || c.RateLimitDeviceRPS <= 0 || c.RateLimitDeviceBurst <= 0 || c.ProviderStartRPS <= 0 || c.ProviderStartBurst <= 0 || c.ProviderMaxActiveTasks <= 0 { return errors.New("rate limits, bursts and provider task capacity must be positive") } if c.ServiceName == "api" && c.DeviceSecretDirectory == "" && len(c.DeviceLocalSecret) < 32 { return errors.New("DEVICE_LOCAL_SECRET must contain at least 32 characters when DEVICE_SECRET_DIRECTORY is not configured") } if c.ServiceName == "api" && c.DeviceSecretDirectory != "" && !filepath.IsAbs(c.DeviceSecretDirectory) { return errors.New("DEVICE_SECRET_DIRECTORY must be an absolute path") } if c.DeviceClockSkew <= 0 { return errors.New("DEVICE_CLOCK_SKEW must be positive") } if c.ServiceName == "api" { if c.RTCTokenIssuer != "local" && c.RTCTokenIssuer != "volcengine" { return errors.New("RTC_TOKEN_ISSUER must be local or volcengine") } if c.RTCTokenIssuer == "local" && len(c.RTCTokenSecret) < 32 { return errors.New("RTC_TOKEN_SIGNING_SECRET must contain at least 32 characters for the local issuer") } if c.RTCTokenIssuer == "volcengine" && len(c.RTCAppKey) < 16 { return errors.New("RTC_APP_KEY must contain at least 16 characters for the Volcengine token issuer") } if c.RTCTokenIssuer == "volcengine" && len(c.RTCAppID) != 24 { return errors.New("RTC_APP_ID must contain exactly 24 characters for the Volcengine token issuer") } } if c.ServiceName == "api" && len(c.CursorSigningSecret) < 32 { return errors.New("CURSOR_SIGNING_SECRET must contain at least 32 characters") } if c.ServiceName == "api" && len(c.ShareTokenSecret) < 32 { return errors.New("SHARE_TOKEN_SIGNING_SECRET must contain at least 32 characters") } if c.RTCAppID == "" || c.RTCTokenTTL <= 0 || c.AllocationTTL <= 0 || c.HeartbeatInterval <= 0 { return errors.New("RTC app, token TTL, allocation TTL and heartbeat interval must be valid") } if c.ServiceName == "callbacks" && len(c.ProviderCallbackSecret) < 32 { return errors.New("PROVIDER_CALLBACK_SECRET must contain at least 32 characters") } if c.ServiceName == "callbacks" && len(c.PlatformHealthSecret) < 32 { return errors.New("PLATFORM_HEALTH_CALLBACK_SECRET must contain at least 32 characters") } if c.ServiceName == "worker" { if c.VoiceProvider != "mock" && c.VoiceProvider != "volcengine" { return errors.New("VOICE_PROVIDER must be mock or volcengine") } if c.VoiceProvider == "mock" && (c.MockProviderBaseURL == "" || c.MockProviderAPIKey == "") { return errors.New("MOCK_PROVIDER_BASE_URL and MOCK_PROVIDER_API_KEY are required for the mock voice provider") } if c.VoiceProvider == "volcengine" { if !isAbsoluteHTTPURL(c.VolcRTCBaseURL) || c.VolcAccessKeyID == "" || c.VolcSecretAccessKey == "" || c.VolcRTCRegion == "" || c.VolcRTCAPIVersion == "" || c.VolcVoiceChatTemplate == "" { return errors.New("Volcengine voice provider requires an RTC URL, AK/SK, region, API version and voice-chat template file") } } } if c.ProviderCallbackSkew <= 0 || c.PlatformHealthSkew <= 0 || c.ProviderRequestTimeout <= 0 || c.ProviderFailureThreshold < 1 || c.ProviderCircuitCooldown <= 0 { return errors.New("provider callback skew and request timeout must be positive") } if c.AlertWebhookTimeout <= 0 { return errors.New("ALERT_WEBHOOK_TIMEOUT must be positive") } if c.ArkRequestTimeout <= 0 { return errors.New("ARK_REQUEST_TIMEOUT must be positive") } if c.ContentSafetyTimeout <= 0 { return errors.New("CONTENT_SAFETY_TIMEOUT must be positive") } if c.ServiceName == "worker" && c.AlertWebhookURL != "" { parsed, err := url.Parse(c.AlertWebhookURL) if err != nil || parsed.Host == "" || (parsed.Scheme != "http" && parsed.Scheme != "https") { return errors.New("ALERT_WEBHOOK_URL must be an absolute HTTP(S) URL") } if len(c.AlertWebhookSecret) < 32 { return errors.New("ALERT_WEBHOOK_SECRET must contain at least 32 characters when webhook delivery is enabled") } } if c.ServiceName == "worker" { if c.ContentSafetyProvider != "local" && c.ContentSafetyProvider != "managed_http" { return errors.New("CONTENT_SAFETY_PROVIDER must be local or managed_http") } if c.ContentSafetyProvider == "managed_http" { if !isAbsoluteHTTPURL(c.ContentSafetyEndpoint) || c.ContentSafetyAPIKey == "" || c.ContentSafetyPolicyRef == "" { return errors.New("managed content safety requires an HTTP(S) endpoint, API key and policy reference") } } if c.ContentGenerator != "local" && c.ContentGenerator != "ark" { return errors.New("CONTENT_GENERATOR must be local or ark") } if c.ContentGenerator == "ark" { parsed, err := url.Parse(c.ArkBaseURL) if err != nil || parsed.Host == "" || (parsed.Scheme != "http" && parsed.Scheme != "https") { return errors.New("ARK_BASE_URL must be an absolute HTTP(S) URL") } if c.ArkAPIKey == "" || c.ArkContentModel == "" { return errors.New("ARK_API_KEY and ARK_CONTENT_MODEL are required for the Ark content generator") } } } if c.ServiceName == "api" || c.ServiceName == "worker" { if c.EmbeddingProvider != "local" && c.EmbeddingProvider != "ark" { return errors.New("EMBEDDING_PROVIDER must be local or ark") } if c.EmbeddingProvider == "ark" { parsed, err := url.Parse(c.ArkBaseURL) if err != nil || parsed.Host == "" || (parsed.Scheme != "http" && parsed.Scheme != "https") { return errors.New("ARK_BASE_URL must be an absolute HTTP(S) URL") } if c.ArkAPIKey == "" || c.ArkEmbeddingModel == "" { return errors.New("ARK_API_KEY and ARK_EMBEDDING_MODEL are required for the Ark embedding provider") } } } if c.ServiceName == "api" || c.ServiceName == "worker" { if c.ObjectStoreEndpoint == "" || c.ObjectStorePublic == "" || c.ObjectStoreAccessKey == "" || c.ObjectStoreSecretKey == "" || c.ObjectStoreBucket == "" || c.ObjectStoreRegion == "" { return errors.New("object store endpoint, public endpoint, credentials, region and export bucket are required") } if c.ExportDownloadTTL <= 0 || c.ExportDownloadTTL > 24*time.Hour || c.ExportObjectTTL <= 0 { return errors.New("export download and object TTL must be positive, and download TTL cannot exceed 24 hours") } if c.MediaLocalDeleteGrace <= 0 || c.MediaLocalDeleteGrace > 7*24*time.Hour { return errors.New("media local delete grace must be positive and cannot exceed 7 days") } } if c.Environment == EnvironmentProd { if err := c.validateProduction(); err != nil { return err } } return nil } func (c Config) validateProduction() error { if !isHTTPSURL(c.AuthIssuerURL) || !isHTTPSURL(c.AuthJWKSURL) { return errors.New("production OIDC issuer and JWKS URLs must use HTTPS") } databaseURL, err := url.Parse(c.DatabaseURL) if err != nil || databaseURL.Scheme == "" || strings.EqualFold(databaseURL.Query().Get("sslmode"), "disable") { return errors.New("production DATABASE_URL must enable transport security") } if strings.TrimSpace(c.RedisPassword) == "" || isPlaceholderSecret(c.RedisPassword) { return errors.New("production REDIS_PASSWORD must be a non-placeholder secret") } if c.ServiceName == "api" { if c.DeviceSecretDirectory == "" { return errors.New("production API must use DEVICE_SECRET_DIRECTORY for per-device credentials") } if isPlaceholderSecret(c.CursorSigningSecret) || isPlaceholderSecret(c.ShareTokenSecret) { return errors.New("production API signing secrets must not use local or example values") } if c.CursorSigningSecret == c.ShareTokenSecret { return errors.New("production API signing secrets must be distinct") } if isLocalValue(c.RTCAppID) || c.RTCTokenIssuer != "volcengine" || isPlaceholderSecret(c.RTCAppKey) { return errors.New("production RTC credentials must use a real app ID, Volcengine token issuer and non-placeholder app key") } if c.EmbeddingProvider != "ark" || !isHTTPSURL(c.ArkBaseURL) || isPlaceholderSecret(c.ArkAPIKey) || isLocalValue(c.ArkEmbeddingModel) { return errors.New("production memory retrieval must use Ark HTTPS, a non-placeholder API key and a real embedding model ID") } } if c.ServiceName == "callbacks" && isPlaceholderSecret(c.ProviderCallbackSecret) { return errors.New("production PROVIDER_CALLBACK_SECRET must not use a local or example value") } if c.ServiceName == "callbacks" && isPlaceholderSecret(c.PlatformHealthSecret) { return errors.New("production PLATFORM_HEALTH_CALLBACK_SECRET must not use a local or example value") } if c.ServiceName == "worker" { if c.VoiceProvider != "volcengine" || len(c.RTCAppID) != 24 || isLocalValue(c.RTCAppID) || !isHTTPSURL(c.VolcRTCBaseURL) || isLocalValue(c.VolcRTCBaseURL) || isPlaceholderSecret(c.VolcAccessKeyID) || isPlaceholderSecret(c.VolcSecretAccessKey) { return errors.New("production voice provider must use signed Volcengine RTC HTTPS with non-placeholder AK/SK") } if c.AlertWebhookURL != "" && (!isHTTPSURL(c.AlertWebhookURL) || isPlaceholderSecret(c.AlertWebhookSecret)) { return errors.New("production alert webhook must use HTTPS and a non-placeholder signing secret") } if c.ContentGenerator != "ark" || !isHTTPSURL(c.ArkBaseURL) || isPlaceholderSecret(c.ArkAPIKey) || isLocalValue(c.ArkContentModel) { return errors.New("production content generation must use Ark HTTPS, a non-placeholder API key and a real model ID") } if c.ContentSafetyProvider != "managed_http" || !isHTTPSURL(c.ContentSafetyEndpoint) || isLocalValue(c.ContentSafetyEndpoint) || isPlaceholderSecret(c.ContentSafetyAPIKey) || isLocalValue(c.ContentSafetyPolicyRef) { return errors.New("production content safety must use managed HTTPS with a non-placeholder API key and policy reference") } if c.EmbeddingProvider != "ark" || isLocalValue(c.ArkEmbeddingModel) { return errors.New("production memory indexing must use a real Ark embedding model ID") } } if c.ServiceName == "api" || c.ServiceName == "worker" { if !c.ObjectStoreSecure || !c.ObjectStorePublicSecure { return errors.New("production object storage must use TLS") } if isLocalValue(c.ObjectStoreEndpoint) || isLocalValue(c.ObjectStorePublic) { return errors.New("production object storage endpoints must not target localhost") } if isPlaceholderSecret(c.ObjectStoreAccessKey) || isPlaceholderSecret(c.ObjectStoreSecretKey) { return errors.New("production object storage credentials must not use local or example values") } } return nil } func isHTTPSURL(value string) bool { parsed, err := url.Parse(strings.TrimSpace(value)) return err == nil && strings.EqualFold(parsed.Scheme, "https") && parsed.Host != "" } func isAbsoluteHTTPURL(value string) bool { parsed, err := url.Parse(strings.TrimSpace(value)) return err == nil && (strings.EqualFold(parsed.Scheme, "http") || strings.EqualFold(parsed.Scheme, "https")) && parsed.Host != "" } func isLocalValue(value string) bool { normalized := strings.ToLower(strings.TrimSpace(value)) allZeroes := normalized != "" && strings.Trim(normalized, "0") == "" return strings.Contains(normalized, "localhost") || strings.Contains(normalized, "127.0.0.1") || strings.Contains(normalized, "mock-provider") || strings.Contains(normalized, "example.invalid") || strings.Contains(normalized, "replace-with") || strings.HasPrefix(normalized, "local-") || strings.HasPrefix(normalized, "local_") || allZeroes } func isPlaceholderSecret(value string) bool { normalized := strings.ToLower(strings.TrimSpace(value)) return len(normalized) < 32 || strings.Contains(normalized, "local-") || strings.Contains(normalized, "local_") || strings.Contains(normalized, "dev_only") || strings.Contains(normalized, "changeme") || strings.Contains(normalized, "example") } func loadSecretValues(names ...string) (map[string]string, error) { values := make(map[string]string, len(names)) for _, name := range names { value, err := secretEnv(name) if err != nil { return nil, err } values[name] = value } return values, nil } func secretEnv(name string) (string, error) { inline := strings.TrimSpace(os.Getenv(name)) filePath := strings.TrimSpace(os.Getenv(name + "_FILE")) if inline != "" && filePath != "" { return "", fmt.Errorf("%s and %s_FILE cannot both be configured", name, name) } if filePath == "" { return inline, nil } if !filepath.IsAbs(filePath) { return "", fmt.Errorf("%s_FILE must be an absolute path", name) } file, err := os.Open(filepath.Clean(filePath)) if err != nil { return "", fmt.Errorf("read %s_FILE: secret file is unavailable", name) } defer file.Close() info, err := file.Stat() if err != nil || !info.Mode().IsRegular() { return "", fmt.Errorf("read %s_FILE: secret path must be a regular file", name) } content, err := io.ReadAll(io.LimitReader(file, 64*1024+1)) if err != nil || len(content) > 64*1024 { return "", fmt.Errorf("read %s_FILE: secret file exceeds 64 KiB", name) } content = bytes.TrimRight(content, "\r\n") if bytes.IndexByte(content, 0) >= 0 { return "", fmt.Errorf("read %s_FILE: secret contains a NUL byte", name) } return string(content), nil } func secretDefault(value, fallback string) string { if value == "" { return fallback } return value } func defaultHTTPAddr(serviceName string) string { switch serviceName { case "callbacks": return "0.0.0.0:58081" case "worker": return "0.0.0.0:58082" default: return "0.0.0.0:58080" } } func stringEnv(key, fallback string) string { value := strings.TrimSpace(os.Getenv(key)) if value == "" { return fallback } return value } func stringEnvAllowEmpty(key, fallback string) string { value, exists := os.LookupEnv(key) if !exists { return fallback } return strings.TrimSpace(value) } func intEnv(key string, fallback int) (int, error) { value := strings.TrimSpace(os.Getenv(key)) if value == "" { return fallback, nil } parsed, err := strconv.Atoi(value) if err != nil { return 0, fmt.Errorf("%s must be an integer: %w", key, err) } return parsed, nil } func floatEnv(key string, fallback float64) (float64, error) { value := strings.TrimSpace(os.Getenv(key)) if value == "" { return fallback, nil } parsed, err := strconv.ParseFloat(value, 64) if err != nil { return 0, fmt.Errorf("%s must be a number: %w", key, err) } return parsed, nil } func boolEnv(key string, fallback bool) (bool, error) { value := strings.TrimSpace(os.Getenv(key)) if value == "" { return fallback, nil } parsed, err := strconv.ParseBool(value) if err != nil { return false, fmt.Errorf("%s must be a boolean: %w", key, err) } return parsed, nil } func durationEnv(key string, fallback time.Duration) (time.Duration, error) { value := strings.TrimSpace(os.Getenv(key)) if value == "" { return fallback, nil } parsed, err := time.ParseDuration(value) if err != nil { return 0, fmt.Errorf("%s must be a duration: %w", key, err) } return parsed, nil }