package config import ( "os" "strconv" "github.com/joho/godotenv" ) type Config struct { Port string Env string LogLevel string DatabaseURL string JWTSecret string CORSOrigins string RateLimitRPS int SMTPHost string SMTPPort int SMTPUser string SMTPPass string SMTPFrom string SenderAPIToken string SendPulseID string SendPulseSecret string R2SigningSecret string R2PublicBaseURL string FirebaseCredentialsFile string R2AccountID string R2APIToken string R2ImgDomain string R2VidDomain string R2Endpoint string R2AccessKey string R2SecretKey string R2MediaBucket string R2VideoBucket string APIBaseURL string AppBaseURL string OpenRouterAPIKey string AIGatewayURL string AIGatewayToken string AzureOpenAIAPIKey string AzureOpenAIEndpoint string AzureOpenAIAPIVersion string } func LoadConfig() *Config { // Try current directory first err := godotenv.Load() if err != nil { // Try parent directory (common for VPS structure /opt/sojorn/.env while binary is in /opt/sojorn/go-backend) _ = godotenv.Load("../.env") // Try absolute path specified by user _ = godotenv.Load("/opt/sojorn/.env") } return &Config{ Port: getEnv("PORT", "8080"), Env: getEnv("ENV", "development"), LogLevel: getEnv("LOG_LEVEL", "info"), DatabaseURL: getEnv("DATABASE_URL", ""), JWTSecret: getEnv("JWT_SECRET", ""), CORSOrigins: getEnv("CORS_ORIGINS", "*"), RateLimitRPS: getEnvInt("RATE_LIMIT_RPS", 10), SMTPHost: getEnv("SMTP_HOST", "smtp.sender.net"), SMTPPort: getEnvInt("SMTP_PORT", 587), SMTPUser: getEnv("SMTP_USER", ""), SMTPPass: getEnv("SMTP_PASS", ""), SMTPFrom: getEnv("SMTP_FROM", "no-reply@sojorn.net"), SenderAPIToken: getEnv("SENDER_API_TOKEN", ""), SendPulseID: getEnv("SENDPULSE_ID", ""), SendPulseSecret: getEnv("SENDPULSE_SECRET", ""), R2SigningSecret: getEnv("R2_SIGNING_SECRET", ""), // Default to the public CDN domain to avoid mixed-content/http defaults. R2PublicBaseURL: getEnv("R2_PUBLIC_BASE_URL", "https://img.sojorn.net"), FirebaseCredentialsFile: getEnv("FIREBASE_CREDENTIALS_FILE", "firebase-service-account.json"), R2AccountID: getEnv("R2_ACCOUNT_ID", ""), R2APIToken: getEnv("R2_API_TOKEN", ""), R2ImgDomain: getEnv("R2_IMG_DOMAIN", "img.sojorn.net"), R2VidDomain: getEnv("R2_VID_DOMAIN", "quips.sojorn.net"), R2Endpoint: getEnv("R2_ENDPOINT", ""), R2AccessKey: getEnv("R2_ACCESS_KEY", ""), R2SecretKey: getEnv("R2_SECRET_KEY", ""), R2MediaBucket: getEnv("R2_MEDIA_BUCKET", "sojorn-media"), R2VideoBucket: getEnv("R2_VIDEO_BUCKET", "sojorn-videos"), APIBaseURL: getEnv("API_BASE_URL", "https://api.sojorn.net"), AppBaseURL: getEnv("APP_BASE_URL", "https://mp.ls"), OpenRouterAPIKey: getEnv("OPENROUTER_API", ""), AIGatewayURL: getEnv("AI_GATEWAY_URL", ""), AIGatewayToken: getEnv("AI_GATEWAY_TOKEN", ""), AzureOpenAIAPIKey: getEnv("AZURE_OPENAI_API_KEY", ""), AzureOpenAIEndpoint: getEnv("AZURE_OPENAI_ENDPOINT", ""), AzureOpenAIAPIVersion: getEnv("AZURE_OPENAI_API_VERSION", "2024-02-15-preview"), } } func getEnv(key, fallback string) string { if value, exists := os.LookupEnv(key); exists { return value } return fallback } func getEnvInt(key string, fallback int) int { valueStr := getEnv(key, "") if value, err := strconv.Atoi(valueStr); err == nil { return value } return fallback }