localgreenchain/lib/config/env.ts
Claude 5ea8bab5c3
Add production deployment infrastructure (Agent 4)
- Docker: Multi-stage Dockerfile with security hardening, docker-compose
  for production and development environments
- Environment: Comprehensive .env.example with all config options,
  lib/config/env.ts for typed environment validation
- Logging: Structured JSON logging with request/response middleware
- Monitoring: Prometheus metrics endpoint, Grafana dashboard, health
  checks (liveness/readiness probes)
- Security: Security headers, rate limiting, CORS middleware
- CI/CD: GitHub Actions workflows for CI, production deploy, and
  preview deployments
- Error tracking: Sentry integration foundation

Files created:
- Docker: Dockerfile, docker-compose.yml, docker-compose.dev.yml, .dockerignore
- Config: lib/config/env.ts, lib/config/index.ts
- Logging: lib/logging/logger.ts, lib/logging/middleware.ts
- Monitoring: lib/monitoring/sentry.ts, lib/monitoring/metrics.ts,
  lib/monitoring/health.ts
- Security: lib/security/headers.ts, lib/security/rateLimit.ts,
  lib/security/cors.ts
- API: pages/api/health/*, pages/api/metrics.ts
- Infra: infra/prometheus/prometheus.yml, infra/grafana/*
2025-11-23 03:54:03 +00:00

280 lines
8.2 KiB
TypeScript

/**
* 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 };