apigateway / scripts /setup /check_env_config.py
jebin2's picture
restructure
dd5c695
#!/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()