241 lines
9.3 KiB
Python
241 lines
9.3 KiB
Python
"""
|
|
Configuration module for UnitForge application.
|
|
|
|
This module handles environment variable loading and provides
|
|
default values for configuration settings.
|
|
"""
|
|
|
|
import ast
|
|
import json
|
|
import os
|
|
from pathlib import Path
|
|
from typing import List, Optional
|
|
|
|
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) -> None:
|
|
"""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") # nosec B104
|
|
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 (ValueError, SyntaxError, json.JSONDecodeError, TypeError):
|
|
# Fall back to comma-separated parsing if JSON/literal parsing fails
|
|
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()
|