Bruno Charest 70c15c9b6f
Some checks failed
Tests / Backend Tests (Python) (3.10) (push) Has been cancelled
Tests / Backend Tests (Python) (3.11) (push) Has been cancelled
Tests / Backend Tests (Python) (3.12) (push) Has been cancelled
Tests / Frontend Tests (JS) (push) Has been cancelled
Tests / Integration Tests (push) Has been cancelled
Tests / All Tests Passed (push) Has been cancelled
Add debug mode feature flag with environment variable parsing, UI badge indicator, secret redaction utility, and enhanced terminal session management with status checks and session limit error handling
2025-12-21 17:22:36 -05:00

152 lines
4.9 KiB
Python

"""
Configuration centralisée de l'application.
Toutes les variables d'environnement et paramètres sont centralisés ici.
"""
import os
from pathlib import Path
from typing import Optional
from pydantic import Field
from pydantic import field_validator
from pydantic_settings import BaseSettings
def parse_env_bool(value: object, default: bool = False) -> bool:
if value is None:
return default
if isinstance(value, bool):
return value
if isinstance(value, (int, float)):
return bool(value)
if isinstance(value, str):
normalized = value.strip().lower()
if normalized in {"1", "true", "yes"}:
return True
if normalized in {"0", "false", "no"}:
return False
return default
return default
class Settings(BaseSettings):
"""Configuration de l'application Homelab Automation."""
# === Chemins ===
base_dir: Path = Field(default_factory=lambda: Path(__file__).resolve().parent.parent)
logs_dir: Path = Field(default_factory=lambda: Path(os.environ.get("LOGS_DIR", "/logs")))
@property
def ansible_dir(self) -> Path:
"""Répertoire Ansible (relatif à base_dir.parent)"""
return self.base_dir.parent / "ansible"
@property
def tasks_logs_dir(self) -> Path:
"""Répertoire des logs de tâches markdown"""
return Path(os.environ.get("DIR_LOGS_TASKS", str(self.base_dir.parent / "tasks_logs")))
@property
def db_path(self) -> Path:
"""Chemin de la base de données SQLite"""
return self.logs_dir / "homelab.db"
# === SSH ===
ssh_key_path: str = Field(
default_factory=lambda: os.environ.get("SSH_KEY_PATH", str(Path.home() / ".ssh" / "id_rsa"))
)
ssh_user: str = Field(default_factory=lambda: os.environ.get("SSH_USER", "automation"))
ssh_remote_user: str = Field(default_factory=lambda: os.environ.get("SSH_REMOTE_USER", "root"))
# === API ===
api_key: str = Field(default_factory=lambda: os.environ.get("API_KEY", "dev-key-12345"))
api_title: str = "Homelab Automation Dashboard API"
api_version: str = "1.0.0"
api_description: str = "API REST moderne pour la gestion automatique d'homelab"
# === JWT Authentication ===
jwt_secret_key: str = Field(
default_factory=lambda: os.environ.get("JWT_SECRET_KEY", "dev-secret-key-change-in-production")
)
jwt_expire_minutes: int = Field(
default_factory=lambda: int(os.environ.get("JWT_EXPIRE_MINUTES", "1440"))
)
jwt_algorithm: str = "HS256"
# === Database ===
database_url: Optional[str] = Field(default=None)
@property
def async_database_url(self) -> str:
"""URL de connexion async pour SQLAlchemy"""
if self.database_url:
return self.database_url
return f"sqlite+aiosqlite:///{self.db_path}"
# === CORS ===
cors_origins: list = Field(default=["*"])
cors_allow_credentials: bool = True
cors_allow_methods: list = Field(default=["*"])
cors_allow_headers: list = Field(default=["*"])
# === Notifications ntfy ===
ntfy_enabled: bool = Field(
default_factory=lambda: os.environ.get("NTFY_ENABLED", "true").lower() == "true"
)
ntfy_base_url: str = Field(
default_factory=lambda: os.environ.get("NTFY_BASE_URL", "https://ntfy.sh")
)
ntfy_default_topic: str = Field(
default_factory=lambda: os.environ.get("NTFY_TOPIC", "homelab-automation")
)
ntfy_timeout: int = Field(
default_factory=lambda: int(os.environ.get("NTFY_TIMEOUT", "10"))
)
ntfy_username: Optional[str] = Field(
default_factory=lambda: os.environ.get("NTFY_USERNAME")
)
ntfy_password: Optional[str] = Field(
default_factory=lambda: os.environ.get("NTFY_PASSWORD")
)
ntfy_token: Optional[str] = Field(
default_factory=lambda: os.environ.get("NTFY_TOKEN")
)
# === Scheduler ===
scheduler_timezone: str = Field(
default_factory=lambda: os.environ.get("SCHEDULER_TIMEZONE", "America/Montreal")
)
scheduler_misfire_grace_time: int = 300
# === Cache ===
hosts_cache_ttl: int = 60 # secondes
inventory_cache_ttl: int = 60 # secondes
logs_index_rebuild_interval: int = 60 # secondes
# === Server ===
host: str = "0.0.0.0"
port: int = 8008
reload: bool = Field(
default_factory=lambda: os.environ.get("RELOAD", "true").lower() == "true"
)
log_level: str = "info"
# === Feature Flags ===
debug_mode: bool = Field(default=False, validation_alias="DEBUG_MODE")
@field_validator("debug_mode", mode="before")
@classmethod
def _validate_debug_mode(cls, v: object) -> bool:
return parse_env_bool(v, default=False)
class Config:
env_file = ".env"
env_file_encoding = "utf-8"
extra = "ignore"
# Instance singleton de la configuration
settings = Settings()
DEBUG_MODE_ENABLED: bool = settings.debug_mode