package config import ( "os" "strconv" "strings" "time" "github.com/joho/godotenv" ) type Config struct { Environment string LogLevel string // Service ports APIGatewayPort int ChatSvcPort int AgentSvcPort int SearchSvcPort int LLMSvcPort int ScraperSvcPort int AdminSvcPort int // Service URLs ChatSvcURL string AgentSvcURL string SearchSvcURL string LLMSvcURL string ScraperSvcURL string MemorySvcURL string LibrarySvcURL string AdminSvcURL string // External services SearXNGURL string SearXNGFallbackURL []string Crawl4AIURL string RedisURL string DatabaseURL string DiscoverSvcURL string CollectionSvcURL string FileSvcURL string ThreadSvcURL string ComputerSvcURL string FinanceHeatmapURL string LearningSvcURL string TravelSvcURL string SandboxSvcURL string MedicineSvcURL string // TravelPayouts TravelPayoutsToken string TravelPayoutsMarker string // MinIO / S3 storage MinioEndpoint string MinioAccessKey string MinioSecretKey string MinioBucket string MinioUseSSL bool MinioPublicURL string // Auth JWTSecret string AuthSvcURL string // LLM defaults DefaultLLMProvider string DefaultLLMModel string OpenAIAPIKey string AnthropicAPIKey string GeminiAPIKey string // Timeweb Cloud AI TimewebAPIBaseURL string TimewebAgentAccessID string TimewebAPIKey string TimewebProxySource string // Ollama (local LLM) OllamaBaseURL string OllamaModelKey string OllamaEmbeddingModel string OllamaNumParallel int OllamaAPIToken string // Timeouts HTTPTimeout time.Duration LLMTimeout time.Duration ScrapeTimeout time.Duration SearchTimeout time.Duration // CORS AllowedOrigins []string } var cfg *Config func Load() (*Config, error) { _ = godotenv.Load() _ = godotenv.Load("../.env") _ = godotenv.Load("../../.env") cfg = &Config{ Environment: getEnv("ENVIRONMENT", "production"), LogLevel: getEnv("LOG_LEVEL", "info"), APIGatewayPort: getEnvInt("API_GATEWAY_PORT", 3015), ChatSvcPort: getEnvInt("CHAT_SVC_PORT", 3005), AgentSvcPort: getEnvInt("AGENT_SVC_PORT", 3018), SearchSvcPort: getEnvInt("SEARCH_SVC_PORT", 3001), LLMSvcPort: getEnvInt("LLM_SVC_PORT", 3020), ScraperSvcPort: getEnvInt("SCRAPER_SVC_PORT", 3021), AdminSvcPort: getEnvInt("ADMIN_SVC_PORT", 3040), ChatSvcURL: getEnv("CHAT_SVC_URL", "http://localhost:3005"), AgentSvcURL: getEnv("MASTER_AGENTS_SVC_URL", "http://localhost:3018"), SearchSvcURL: getEnv("SEARCH_SVC_URL", "http://localhost:3001"), LLMSvcURL: getEnv("LLM_SVC_URL", "http://localhost:3020"), ScraperSvcURL: getEnv("SCRAPER_SVC_URL", "http://localhost:3021"), MemorySvcURL: getEnv("MEMORY_SVC_URL", ""), LibrarySvcURL: getEnv("LIBRARY_SVC_URL", "http://localhost:3009"), AdminSvcURL: getEnv("ADMIN_SVC_URL", "http://localhost:3040"), SearXNGURL: getEnv("SEARXNG_URL", "http://searxng:8080"), SearXNGFallbackURL: strings.Split(getEnv("SEARXNG_FALLBACK_URL", ""), ","), Crawl4AIURL: getEnv("CRAWL4AI_URL", "http://crawl4ai:11235"), RedisURL: getEnv("REDIS_URL", "redis://localhost:6379"), DatabaseURL: getEnv("DATABASE_URL", ""), DiscoverSvcURL: getEnv("DISCOVER_SVC_URL", "http://localhost:3002"), CollectionSvcURL: getEnv("COLLECTION_SVC_URL", "http://localhost:3025"), FileSvcURL: getEnv("FILE_SVC_URL", "http://localhost:3026"), ThreadSvcURL: getEnv("THREAD_SVC_URL", "http://localhost:3027"), ComputerSvcURL: getEnv("COMPUTER_SVC_URL", "http://localhost:3030"), FinanceHeatmapURL: getEnv("FINANCE_HEATMAP_SVC_URL", "http://localhost:3033"), LearningSvcURL: getEnv("LEARNING_SVC_URL", "http://localhost:3034"), TravelSvcURL: getEnv("TRAVEL_SVC_URL", "http://localhost:3035"), SandboxSvcURL: getEnv("SANDBOX_SVC_URL", "http://localhost:3036"), MedicineSvcURL: getEnv("MEDICINE_SVC_URL", "http://localhost:3037"), TravelPayoutsToken: getEnv("TRAVELPAYOUTS_TOKEN", ""), TravelPayoutsMarker: getEnv("TRAVELPAYOUTS_MARKER", ""), MinioEndpoint: getEnv("MINIO_ENDPOINT", getEnv("S3_ENDPOINT", "minio:9000")), MinioAccessKey: getEnv("MINIO_ACCESS_KEY", getEnv("S3_ACCESS_KEY", "minioadmin")), MinioSecretKey: getEnv("MINIO_SECRET_KEY", getEnv("S3_SECRET_KEY", "minioadmin")), MinioBucket: getEnv("MINIO_BUCKET", getEnv("S3_BUCKET", "gooseek")), MinioUseSSL: getEnv("MINIO_USE_SSL", getEnv("S3_USE_SSL", "false")) == "true", MinioPublicURL: getEnv("MINIO_PUBLIC_URL", getEnv("S3_PUBLIC_URL", "")), JWTSecret: getEnv("JWT_SECRET", ""), AuthSvcURL: getEnv("AUTH_SVC_URL", ""), DefaultLLMProvider: getEnv("DEFAULT_LLM_PROVIDER", "openai"), DefaultLLMModel: getEnv("DEFAULT_LLM_MODEL", "gpt-4o-mini"), OpenAIAPIKey: getEnv("OPENAI_API_KEY", ""), AnthropicAPIKey: getEnv("ANTHROPIC_API_KEY", ""), GeminiAPIKey: getEnv("GEMINI_API_KEY", ""), TimewebAPIBaseURL: getEnv("TIMEWEB_API_BASE_URL", "https://api.timeweb.cloud"), TimewebAgentAccessID: getEnv("TIMEWEB_AGENT_ACCESS_ID", ""), TimewebAPIKey: getEnv("TIMEWEB_API_KEY", ""), TimewebProxySource: getEnv("TIMEWEB_X_PROXY_SOURCE", "gooseek"), OllamaBaseURL: getEnv("OLLAMA_BASE_URL", "http://ollama:11434"), OllamaModelKey: getEnv("OLLAMA_MODEL", "qwen3.5:9b"), OllamaEmbeddingModel: getEnv("OLLAMA_EMBEDDING_MODEL", "qwen3-embedding:0.6b"), OllamaNumParallel: getEnvInt("OLLAMA_NUM_PARALLEL", 2), OllamaAPIToken: getEnv("OLLAMA_API_TOKEN", ""), HTTPTimeout: time.Duration(getEnvInt("HTTP_TIMEOUT_MS", 60000)) * time.Millisecond, LLMTimeout: time.Duration(getEnvInt("LLM_TIMEOUT_MS", 120000)) * time.Millisecond, ScrapeTimeout: time.Duration(getEnvInt("SCRAPE_TIMEOUT_MS", 25000)) * time.Millisecond, SearchTimeout: time.Duration(getEnvInt("SEARCH_TIMEOUT_MS", 10000)) * time.Millisecond, AllowedOrigins: parseOrigins(getEnv("ALLOWED_ORIGINS", "*")), } return cfg, nil } func Get() *Config { if cfg == nil { cfg, _ = Load() } return cfg } func getEnv(key, defaultValue string) string { if value := os.Getenv(key); value != "" { return value } return defaultValue } func getEnvInt(key string, defaultValue int) int { if value := os.Getenv(key); value != "" { if i, err := strconv.Atoi(value); err == nil { return i } } return defaultValue } func GetEnvInt(key string, defaultValue int) int { return getEnvInt(key, defaultValue) } func parseOrigins(s string) []string { if s == "*" { return []string{"*"} } origins := strings.Split(s, ",") result := make([]string, 0, len(origins)) for _, o := range origins { if trimmed := strings.TrimSpace(o); trimmed != "" { result = append(result, trimmed) } } return result }