#!/usr/bin/env python3 """ Environment Configuration Checker Validates that all required environment variables are properly configured. Run this before deploying or starting the application. Usage: python scripts/setup/check_env_config.py python scripts/setup/check_env_config.py --strict # Exit with error if any issues found """ import os import sys import argparse from pathlib import Path from typing import Dict, List, Tuple, Optional class EnvChecker: """Check environment configuration for completeness and validity.""" # Required environment variables REQUIRED_VARS = { "JWT_SECRET": { "description": "Secret key for signing JWT tokens", "validate": lambda v: len(v) > 32 and v != "your-secret-key-here-change-me", "error": "Must be a secure random string (>32 chars). Generate with: python scripts/setup/generate_jwt_secret.py" }, "CORS_ORIGINS": { "description": "Allowed CORS origins for frontend", "validate": lambda v: len(v) > 0 and "localhost" in v.lower() or "http" in v, "error": "Must specify at least one origin (e.g., http://localhost:3000)" }, "AUTH_SIGN_IN_GOOGLE_CLIENT_ID": { "description": "Google OAuth Client ID", "validate": lambda v: v.endswith(".apps.googleusercontent.com"), "error": "Must be a valid Google Client ID from console.cloud.google.com" }, } # Optional but recommended RECOMMENDED_VARS = { "ENVIRONMENT": { "description": "Environment mode (production/development)", "validate": lambda v: v in ["production", "development"], "default": "development", "warning": "Should be 'production' or 'development'" }, "GEMINI_API_KEYS": { "description": "Gemini API keys for video generation", "validate": lambda v: len(v) > 20 and v != "your-gemini-api-key", "default": None, "warning": "Required for video generation features" }, "RAZORPAY_KEY_ID": { "description": "Razorpay payment gateway key ID", "validate": lambda v: len(v) > 10 and v != "your_razorpay_key_id", "default": None, "warning": "Required for payment features" }, "RAZORPAY_KEY_SECRET": { "description": "Razorpay payment gateway secret", "validate": lambda v: len(v) > 10 and v != "your_razorpay_key_secret", "default": None, "warning": "Required for payment features" }, "RAZORPAY_WEBHOOK_SECRET": { "description": "Razorpay webhook signature verification secret", "validate": lambda v: len(v) > 10 and v != "your_webhook_secret", "default": None, "warning": "Required for payment webhook verification" }, } # Configuration validation CONFIG_CHECKS = { "JWT_ACCESS_EXPIRY_MINUTES": { "description": "Access token expiry time", "validate": lambda v: v.isdigit() and 5 <= int(v) <= 60, "default": "15", "warning": "Recommended: 5-15 min (production), 30-60 min (development)" }, "JWT_REFRESH_EXPIRY_DAYS": { "description": "Refresh token expiry time", "validate": lambda v: v.isdigit() and 1 <= int(v) <= 90, "default": "7", "warning": "Recommended: 7-14 days (production), 30-90 days (development)" }, "JOB_PER_API_KEY": { "description": "Concurrent jobs per Gemini API key", "validate": lambda v: v.isdigit() and 1 <= int(v) <= 5, "default": "2", "warning": "Recommended: 1-3 to avoid rate limits" }, } def __init__(self, env_file: str = ".env"): self.env_file = Path(env_file) self.env_vars = self._load_env() self.errors: List[Tuple[str, str]] = [] self.warnings: List[Tuple[str, str]] = [] self.info: List[Tuple[str, str]] = [] def _load_env(self) -> Dict[str, str]: """Load environment variables from .env file.""" env_vars = {} if not self.env_file.exists(): return env_vars with open(self.env_file) as f: for line in f: line = line.strip() if line and not line.startswith("#") and "=" in line: key, value = line.split("=", 1) env_vars[key.strip()] = value.strip() return env_vars def check_required(self) -> None: """Check required environment variables.""" for var, config in self.REQUIRED_VARS.items(): value = self.env_vars.get(var) or os.getenv(var) if not value: self.errors.append((var, f"MISSING - {config['description']}. {config['error']}")) elif not config["validate"](value): self.errors.append((var, f"INVALID - {config['error']}")) else: self.info.append((var, "✓ Configured")) def check_recommended(self) -> None: """Check recommended environment variables.""" for var, config in self.RECOMMENDED_VARS.items(): value = self.env_vars.get(var) or os.getenv(var) if not value: if config.get("default"): self.info.append((var, f"Using default: {config['default']}")) else: self.warnings.append((var, f"NOT SET - {config['warning']}")) elif not config["validate"](value): self.warnings.append((var, config["warning"])) else: self.info.append((var, "✓ Configured")) def check_config(self) -> None: """Check configuration values.""" for var, config in self.CONFIG_CHECKS.items(): value = self.env_vars.get(var) or os.getenv(var) or config.get("default", "") if value and not config["validate"](str(value)): self.warnings.append((var, f"{value} - {config['warning']}")) def check_file_exists(self) -> None: """Check if .env file exists.""" if not self.env_file.exists(): self.errors.append((".env", "File not found! Copy .env.example to .env and configure it.")) def check_cors_security(self) -> None: """Check CORS configuration security.""" cors = self.env_vars.get("CORS_ORIGINS", "") env = self.env_vars.get("ENVIRONMENT", "development") if env == "production": if "localhost" in cors.lower(): self.warnings.append(("CORS_ORIGINS", "Contains localhost in production environment")) if not cors.startswith("https://"): self.warnings.append(("CORS_ORIGINS", "Should use HTTPS in production")) def run_all_checks(self) -> bool: """Run all validation checks. Returns True if no errors.""" self.check_file_exists() if self.env_file.exists(): self.check_required() self.check_recommended() self.check_config() self.check_cors_security() return len(self.errors) == 0 def print_report(self) -> None: """Print validation report.""" print("\n" + "=" * 70) print(" Environment Configuration Check") print("=" * 70) if self.errors: print("\n❌ ERRORS (Must Fix):") print("-" * 70) for var, msg in self.errors: print(f" [{var}]") print(f" {msg}\n") if self.warnings: print("\n⚠️ WARNINGS (Review Recommended):") print("-" * 70) for var, msg in self.warnings: print(f" [{var}] {msg}") if self.info and not self.errors: print("\n✅ Required Variables:") print("-" * 70) for var, msg in self.info: if var in self.REQUIRED_VARS: print(f" {var}: {msg}") print("\n" + "=" * 70) if not self.errors and not self.warnings: print("✅ All checks passed! Environment is properly configured.") elif not self.errors: print("⚠️ Configuration is valid but has warnings. Review recommended.") else: print("❌ Configuration has errors. Please fix them before running the app.") print("=" * 70 + "\n") def main(): parser = argparse.ArgumentParser( description="Validate environment configuration", formatter_class=argparse.RawDescriptionHelpFormatter ) parser.add_argument( "--env-file", default=".env", help="Path to .env file (default: .env)" ) parser.add_argument( "--strict", action="store_true", help="Exit with error code if any issues found" ) args = parser.parse_args() checker = EnvChecker(args.env_file) is_valid = checker.run_all_checks() checker.print_report() if args.strict and (checker.errors or checker.warnings): sys.exit(1) elif not is_valid: sys.exit(1) sys.exit(0) if __name__ == "__main__": main()