""" Configuration module for UnitForge application. This module handles environment variable loading and provides default values for configuration settings. """ import os from pathlib import Path from typing import Optional, List import json import ast try: from dotenv import load_dotenv # Try to load .env file from project root env_path = Path(__file__).parent.parent.parent.parent / '.env' if env_path.exists(): load_dotenv(env_path) except ImportError: # python-dotenv not installed, continue with os.getenv pass class Settings: """Application settings loaded from environment variables.""" def __init__(self): """Initialize settings from environment variables.""" # Application info self.app_name: str = os.getenv("APP_NAME", "UnitForge") self.app_version: str = os.getenv("APP_VERSION", "1.0.0") self.app_description: str = os.getenv( "APP_DESCRIPTION", "Create, validate, and manage systemd unit files" ) # External links self.github_url: str = os.getenv( "GITHUB_URL", "https://github.com/will666/unitforge" ) self.documentation_url: str = os.getenv( "DOCUMENTATION_URL", "https://unitforge.readthedocs.io/" ) self.bug_reports_url: str = os.getenv( "BUG_REPORTS_URL", "https://github.com/will666/unitforge/issues" ) # Contact information self.contact_email: str = os.getenv("CONTACT_EMAIL", "contact@unitforge.dev") # Application settings self.environment: str = os.getenv("ENVIRONMENT", "production") self.log_level: str = os.getenv("LOG_LEVEL", "info") # Server configuration self.host: str = os.getenv("HOST", "0.0.0.0") self.port: int = int(os.getenv("PORT", "8000")) self.debug: bool = os.getenv("DEBUG", "").lower() in ("true", "1", "yes", "on") self.reload: bool = os.getenv("RELOAD", "").lower() in ("true", "1", "yes", "on") self.workers: int = int(os.getenv("WORKERS", "4")) # API configuration self.api_title: str = os.getenv("API_TITLE", self.app_name) self.api_version: str = os.getenv("API_VERSION", self.app_version) self.api_description: str = os.getenv("API_DESCRIPTION", self.app_description) self.api_docs_url: str = os.getenv("DOCS_URL", "/api/docs") self.api_redoc_url: str = os.getenv("REDOC_URL", "/api/redoc") # Security settings self.secret_key: Optional[str] = os.getenv("SECRET_KEY") self.allowed_hosts: List[str] = self._parse_list("ALLOWED_HOSTS", ["*"]) # CORS settings self.cors_origins: list = self._parse_cors_origins() # File upload settings self.max_upload_size: int = int(os.getenv("MAX_UPLOAD_SIZE", "1048576")) self.allowed_extensions: List[str] = self._parse_list( "ALLOWED_EXTENSIONS", [".service", ".timer", ".socket", ".mount", ".target", ".path"] ) # Template settings self.template_cache_ttl: int = int(os.getenv("TEMPLATE_CACHE_TTL", "300")) self.validation_cache_ttl: int = int(os.getenv("VALIDATION_CACHE_TTL", "60")) # Paths self.frontend_dir: str = os.getenv("FRONTEND_DIR", "frontend") self.backend_dir: str = os.getenv("BACKEND_DIR", "backend") self.static_dir: str = os.getenv("STATIC_DIR", "frontend/static") self.templates_dir: str = os.getenv("TEMPLATES_DIR", "frontend/templates") # Feature flags self.enable_api_metrics: bool = self._get_bool("ENABLE_API_METRICS", False) self.enable_request_logging: bool = self._get_bool("ENABLE_REQUEST_LOGGING", True) self.enable_template_caching: bool = self._get_bool("ENABLE_TEMPLATE_CACHING", True) self.enable_validation_caching: bool = self._get_bool("ENABLE_VALIDATION_CACHING", True) # Performance settings self.request_timeout: int = int(os.getenv("REQUEST_TIMEOUT", "30")) self.keepalive_timeout: int = int(os.getenv("KEEPALIVE_TIMEOUT", "5")) self.max_connections: int = int(os.getenv("MAX_CONNECTIONS", "100")) # Logging configuration self.log_format: str = os.getenv( "LOG_FORMAT", "%(asctime)s - %(name)s - %(levelname)s - %(message)s" ) self.log_date_format: str = os.getenv("LOG_DATE_FORMAT", "%Y-%m-%d %H:%M:%S") self.access_log: bool = self._get_bool("ACCESS_LOG", True) # CLI configuration self.cli_verbose: bool = self._get_bool("CLI_VERBOSE", False) self.cli_color: bool = self._get_bool("CLI_COLOR", True) self.cli_progress: bool = self._get_bool("CLI_PROGRESS", True) # Validation settings self.strict_validation: bool = self._get_bool("STRICT_VALIDATION", False) self.show_warnings: bool = self._get_bool("SHOW_WARNINGS", True) self.max_validation_errors: int = int(os.getenv("MAX_VALIDATION_ERRORS", "50")) # Template generation defaults self.default_user: str = os.getenv("DEFAULT_USER", "www-data") self.default_group: str = os.getenv("DEFAULT_GROUP", "www-data") self.default_restart_policy: str = os.getenv("DEFAULT_RESTART_POLICY", "on-failure") self.default_wanted_by: str = os.getenv("DEFAULT_WANTED_BY", "multi-user.target") # Security headers self.security_headers: bool = self._get_bool("SECURITY_HEADERS", True) self.hsts_max_age: int = int(os.getenv("HSTS_MAX_AGE", "31536000")) self.csp_enabled: bool = self._get_bool("CSP_ENABLED", True) # Monitoring self.health_check_enabled: bool = self._get_bool("HEALTH_CHECK_ENABLED", True) self.metrics_enabled: bool = self._get_bool("METRICS_ENABLED", False) self.tracing_enabled: bool = self._get_bool("TRACING_ENABLED", False) # Asset optimization self.hot_reload: bool = self._get_bool("HOT_RELOAD", False) self.source_maps: bool = self._get_bool("SOURCE_MAPS", False) self.minify_assets: bool = self._get_bool("MINIFY_ASSETS", True) self.compress_responses: bool = self._get_bool("COMPRESS_RESPONSES", True) # Documentation self.docs_auto_reload: bool = self._get_bool("DOCS_AUTO_RELOAD", False) self.api_docs_enabled: bool = self._get_bool("API_DOCS_ENABLED", True) self.swagger_ui_enabled: bool = self._get_bool("SWAGGER_UI_ENABLED", True) self.redoc_enabled: bool = self._get_bool("REDOC_ENABLED", True) def _get_bool(self, key: str, default: bool = False) -> bool: """Get boolean value from environment variable.""" value = os.getenv(key, "").lower() if value in ("true", "1", "yes", "on"): return True elif value in ("false", "0", "no", "off"): return False return default def _parse_list(self, key: str, default: List[str]) -> List[str]: """Parse list from environment variable.""" value = os.getenv(key) if not value: return default # Try to parse as JSON list first try: parsed = json.loads(value) if isinstance(parsed, list): return [str(item) for item in parsed] except (json.JSONDecodeError, ValueError): pass # Try to parse as Python literal try: parsed = ast.literal_eval(value) if isinstance(parsed, list): return [str(item) for item in parsed] except (ValueError, SyntaxError): pass # Fall back to comma-separated values return [item.strip() for item in value.split(",") if item.strip()] def _parse_cors_origins(self) -> list: """Parse CORS origins from environment variable.""" origins_str = os.getenv("CORS_ORIGINS", "*") if origins_str == "*": return ["*"] # Try to parse as list first try: parsed = self._parse_list("CORS_ORIGINS", []) if parsed: return parsed except Exception: pass # Fall back to comma-separated values return [origin.strip() for origin in origins_str.split(",") if origin.strip()] def get_template_context(self) -> dict: """Get context variables for template rendering.""" return { "app_name": self.app_name, "app_version": self.app_version, "app_description": self.app_description, "github_url": self.github_url, "documentation_url": self.documentation_url, "bug_reports_url": self.bug_reports_url, "api_docs_url": self.api_docs_url, "contact_email": self.contact_email, } def is_development(self) -> bool: """Check if running in development environment.""" return self.environment.lower() == "development" or self.debug def is_production(self) -> bool: """Check if running in production environment.""" return self.environment.lower() == "production" and not self.debug # Global settings instance settings = Settings()