/** * Environment Configuration * Agent 4: Production Deployment * * Validates and exports environment variables with type safety. * Throws errors in production if required variables are missing. */ type LogLevel = 'error' | 'warn' | 'info' | 'debug' | 'trace'; type LogFormat = 'json' | 'pretty'; type PrivacyMode = 'standard' | 'enhanced' | 'maximum'; type LocationObfuscation = 'none' | 'fuzzy' | 'region' | 'hidden'; type StorageProvider = 'local' | 's3' | 'r2' | 'minio'; interface EnvConfig { // Application nodeEnv: 'development' | 'production' | 'test'; port: number; apiUrl: string; appName: string; // Database databaseUrl: string; dbHost: string; dbPort: number; dbUser: string; dbPassword: string; dbName: string; // Redis redisUrl: string; redisHost: string; redisPort: number; // Authentication nextAuthUrl: string; nextAuthSecret: string; githubClientId?: string; githubClientSecret?: string; googleClientId?: string; googleClientSecret?: string; // Sentry sentryDsn?: string; sentryOrg?: string; sentryProject?: string; sentryAuthToken?: string; // Logging logLevel: LogLevel; logFormat: LogFormat; // Monitoring prometheusEnabled: boolean; metricsPort: number; // Plants.net plantsNetApiKey?: string; // Tor torEnabled: boolean; torSocksHost: string; torSocksPort: number; torControlPort: number; torHiddenServiceDir: string; // Privacy defaultPrivacyMode: PrivacyMode; allowAnonymousRegistration: boolean; locationObfuscationDefault: LocationObfuscation; // Storage storageProvider: StorageProvider; s3Bucket?: string; s3Region?: string; s3AccessKeyId?: string; s3SecretAccessKey?: string; s3Endpoint?: string; // Email smtpHost: string; smtpPort: number; smtpUser?: string; smtpPassword?: string; smtpFrom: string; // Rate Limiting rateLimitWindowMs: number; rateLimitMaxRequests: number; // Security corsOrigins: string[]; cspReportUri?: string; // Feature Flags isProduction: boolean; isDevelopment: boolean; isTest: boolean; } function getEnvString(key: string, defaultValue?: string): string { const value = process.env[key] ?? defaultValue; if (value === undefined) { if (process.env.NODE_ENV === 'production') { throw new Error(`Missing required environment variable: ${key}`); } return ''; } return value; } function getEnvNumber(key: string, defaultValue: number): number { const value = process.env[key]; if (value === undefined) { return defaultValue; } const parsed = parseInt(value, 10); if (isNaN(parsed)) { throw new Error(`Environment variable ${key} must be a number, got: ${value}`); } return parsed; } function getEnvBoolean(key: string, defaultValue: boolean): boolean { const value = process.env[key]; if (value === undefined) { return defaultValue; } return value.toLowerCase() === 'true' || value === '1'; } function getEnvArray(key: string, defaultValue: string[] = []): string[] { const value = process.env[key]; if (value === undefined || value === '') { return defaultValue; } return value.split(',').map((s) => s.trim()).filter(Boolean); } function validateLogLevel(value: string): LogLevel { const validLevels: LogLevel[] = ['error', 'warn', 'info', 'debug', 'trace']; if (!validLevels.includes(value as LogLevel)) { return 'info'; } return value as LogLevel; } function validateLogFormat(value: string): LogFormat { return value === 'pretty' ? 'pretty' : 'json'; } function validatePrivacyMode(value: string): PrivacyMode { const validModes: PrivacyMode[] = ['standard', 'enhanced', 'maximum']; if (!validModes.includes(value as PrivacyMode)) { return 'standard'; } return value as PrivacyMode; } function validateLocationObfuscation(value: string): LocationObfuscation { const validModes: LocationObfuscation[] = ['none', 'fuzzy', 'region', 'hidden']; if (!validModes.includes(value as LocationObfuscation)) { return 'fuzzy'; } return value as LocationObfuscation; } function validateStorageProvider(value: string): StorageProvider { const validProviders: StorageProvider[] = ['local', 's3', 'r2', 'minio']; if (!validProviders.includes(value as StorageProvider)) { return 'local'; } return value as StorageProvider; } function validateNodeEnv(value: string): 'development' | 'production' | 'test' { if (value === 'production' || value === 'test') { return value; } return 'development'; } /** * Load and validate environment configuration */ function loadEnv(): EnvConfig { const nodeEnv = validateNodeEnv(process.env.NODE_ENV || 'development'); return { // Application nodeEnv, port: getEnvNumber('PORT', 3001), apiUrl: getEnvString('NEXT_PUBLIC_API_URL', 'http://localhost:3001'), appName: getEnvString('NEXT_PUBLIC_APP_NAME', 'LocalGreenChain'), // Database databaseUrl: getEnvString('DATABASE_URL', 'postgresql://lgc:lgc_password@localhost:5432/localgreenchain'), dbHost: getEnvString('DB_HOST', 'localhost'), dbPort: getEnvNumber('DB_PORT', 5432), dbUser: getEnvString('DB_USER', 'lgc'), dbPassword: getEnvString('DB_PASSWORD', 'lgc_password'), dbName: getEnvString('DB_NAME', 'localgreenchain'), // Redis redisUrl: getEnvString('REDIS_URL', 'redis://localhost:6379'), redisHost: getEnvString('REDIS_HOST', 'localhost'), redisPort: getEnvNumber('REDIS_PORT', 6379), // Authentication nextAuthUrl: getEnvString('NEXTAUTH_URL', 'http://localhost:3001'), nextAuthSecret: getEnvString('NEXTAUTH_SECRET', 'development-secret-change-in-production'), githubClientId: process.env.GITHUB_CLIENT_ID, githubClientSecret: process.env.GITHUB_CLIENT_SECRET, googleClientId: process.env.GOOGLE_CLIENT_ID, googleClientSecret: process.env.GOOGLE_CLIENT_SECRET, // Sentry sentryDsn: process.env.SENTRY_DSN || process.env.NEXT_PUBLIC_SENTRY_DSN, sentryOrg: process.env.SENTRY_ORG, sentryProject: process.env.SENTRY_PROJECT, sentryAuthToken: process.env.SENTRY_AUTH_TOKEN, // Logging logLevel: validateLogLevel(getEnvString('LOG_LEVEL', 'info')), logFormat: validateLogFormat(getEnvString('LOG_FORMAT', 'json')), // Monitoring prometheusEnabled: getEnvBoolean('PROMETHEUS_ENABLED', false), metricsPort: getEnvNumber('METRICS_PORT', 9091), // Plants.net plantsNetApiKey: process.env.PLANTS_NET_API_KEY, // Tor torEnabled: getEnvBoolean('TOR_ENABLED', false), torSocksHost: getEnvString('TOR_SOCKS_HOST', '127.0.0.1'), torSocksPort: getEnvNumber('TOR_SOCKS_PORT', 9050), torControlPort: getEnvNumber('TOR_CONTROL_PORT', 9051), torHiddenServiceDir: getEnvString('TOR_HIDDEN_SERVICE_DIR', '/var/lib/tor/localgreenchain'), // Privacy defaultPrivacyMode: validatePrivacyMode(getEnvString('DEFAULT_PRIVACY_MODE', 'standard')), allowAnonymousRegistration: getEnvBoolean('ALLOW_ANONYMOUS_REGISTRATION', true), locationObfuscationDefault: validateLocationObfuscation(getEnvString('LOCATION_OBFUSCATION_DEFAULT', 'fuzzy')), // Storage storageProvider: validateStorageProvider(getEnvString('STORAGE_PROVIDER', 'local')), s3Bucket: process.env.S3_BUCKET, s3Region: process.env.S3_REGION, s3AccessKeyId: process.env.S3_ACCESS_KEY_ID, s3SecretAccessKey: process.env.S3_SECRET_ACCESS_KEY, s3Endpoint: process.env.S3_ENDPOINT, // Email smtpHost: getEnvString('SMTP_HOST', 'localhost'), smtpPort: getEnvNumber('SMTP_PORT', 1025), smtpUser: process.env.SMTP_USER, smtpPassword: process.env.SMTP_PASSWORD, smtpFrom: getEnvString('SMTP_FROM', 'noreply@localgreenchain.local'), // Rate Limiting rateLimitWindowMs: getEnvNumber('RATE_LIMIT_WINDOW_MS', 60000), rateLimitMaxRequests: getEnvNumber('RATE_LIMIT_MAX_REQUESTS', 100), // Security corsOrigins: getEnvArray('CORS_ORIGINS', ['http://localhost:3001']), cspReportUri: process.env.CSP_REPORT_URI, // Feature Flags isProduction: nodeEnv === 'production', isDevelopment: nodeEnv === 'development', isTest: nodeEnv === 'test', }; } // Export singleton config export const env = loadEnv(); // Re-export types export type { EnvConfig, LogLevel, LogFormat, PrivacyMode, LocationObfuscation, StorageProvider };