Spaces:
Sleeping
Sleeping
| #!/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() | |