"""Carga de variables desde config/.env.""" import os from dataclasses import dataclass from pathlib import Path from dotenv import load_dotenv from ..constants import ( APP_DIR, DIR_ENTRADA, DIR_FALLADOS, DIR_PROCESADOS, DIR_TEMP, ENV_PATH, default_seven_zip_path, ) from ..utils.logger import app_logger def _env_bool(name: str, default: bool = False) -> bool: raw = os.getenv(name, "").strip().lower() if not raw: return default return raw in ("1", "true", "yes", "on") def _env_int( name: str, default: int, *, min_value: int | None = None, max_value: int | None = None, ) -> int: """Lee un entero de entorno con validación; ante valor inválido loguea y usa el default.""" raw = os.getenv(name, "").strip() if not raw: return default try: value = int(raw) except ValueError: app_logger.warning(f"{name}='{raw}' no es un entero válido; se usa {default}") return default if min_value is not None and value < min_value: app_logger.warning(f"{name}={value} < {min_value} (mínimo); se usa {default}") return default if max_value is not None and value > max_value: app_logger.warning(f"{name}={value} > {max_value} (máximo); se usa {default}") return default return value def load_env_file() -> bool: """Carga config/.env si existe.""" if ENV_PATH.is_file(): load_dotenv(ENV_PATH, override=True) return True return False def is_panel_configured() -> bool: """True si URL, token e instancia del panel están definidos.""" url = os.getenv("CLOUDRESTORE_PANEL_API_URL", "").strip() token = os.getenv("CLOUDRESTORE_PANEL_API_TOKEN", "").strip() instance = os.getenv("CLOUDRESTORE_PANEL_INSTANCE_KEY", "").strip() return bool(url and token and instance) @dataclass class LaunchOptions: minimized: bool start_engine: bool register_autostart: bool def get_launch_options() -> LaunchOptions: load_env_file() return LaunchOptions( minimized=_env_bool("CLOUDRESTORE_START_MINIMIZED", False), start_engine=_env_bool("CLOUDRESTORE_AUTO_START", True), register_autostart=_env_bool("CLOUDRESTORE_REGISTER_AUTOSTART", True), ) def _path_from_env(name: str, default: Path) -> str: value = os.getenv(name, "").strip() return value if value else str(default) def apply_env_overrides(config: dict) -> dict: """Aplica variables de entorno sobre la configuración en memoria.""" load_env_file() paths = config.setdefault("paths", {}) paths["input_folder"] = _path_from_env("CLOUDRESTORE_INPUT_FOLDER", DIR_ENTRADA) paths["processed_folder"] = _path_from_env( "CLOUDRESTORE_PROCESSED_FOLDER", DIR_PROCESADOS ) paths["failed_folder"] = _path_from_env("CLOUDRESTORE_FAILED_FOLDER", DIR_FALLADOS) paths["extract_folder"] = _path_from_env("CLOUDRESTORE_EXTRACT_FOLDER", DIR_TEMP) data_sql = os.getenv("CLOUDRESTORE_DATA_SQL_FOLDER", "").strip() if data_sql: paths["data_sql_folder"] = data_sql seven_zip = os.getenv("CLOUDRESTORE_SEVEN_ZIP_EXE", "").strip() paths["seven_zip_exe"] = seven_zip or default_seven_zip_path() panel = config.setdefault("panel", {}) api_url = os.getenv("CLOUDRESTORE_PANEL_API_URL", "").strip() if api_url: panel["api_url"] = api_url api_token = os.getenv("CLOUDRESTORE_PANEL_API_TOKEN", "").strip() if api_token: panel["api_token"] = api_token instance = os.getenv("CLOUDRESTORE_PANEL_INSTANCE_KEY", "").strip() if instance: panel["instance_key"] = instance if os.getenv("CLOUDRESTORE_PANEL_VERIFY_SSL"): panel["verify_ssl"] = _env_bool("CLOUDRESTORE_PANEL_VERIFY_SSL", False) sql = config.setdefault("sql", {}) server = os.getenv("CLOUDRESTORE_SQL_SERVER", "").strip() if server: sql["server"] = server if os.getenv("CLOUDRESTORE_SQL_USERNAME"): sql["username"] = os.getenv("CLOUDRESTORE_SQL_USERNAME", "").strip() if os.getenv("CLOUDRESTORE_SQL_USE_WINDOWS_AUTH"): sql["use_windows_auth"] = _env_bool("CLOUDRESTORE_SQL_USE_WINDOWS_AUTH", False) retention = config.setdefault("retention", {}) if os.getenv("CLOUDRESTORE_RETENTION_ENABLED"): retention["enabled"] = _env_bool("CLOUDRESTORE_RETENTION_ENABLED", True) if os.getenv("CLOUDRESTORE_RETENTION_DAYS"): retention["days"] = _env_int( "CLOUDRESTORE_RETENTION_DAYS", retention.get("days", 2), min_value=0 ) if os.getenv("CLOUDRESTORE_RETENTION_FAILED_DAYS"): retention["failed_days"] = _env_int( "CLOUDRESTORE_RETENTION_FAILED_DAYS", retention.get("failed_days", 7), min_value=0 ) if os.getenv("CLOUDRESTORE_RETENTION_RUN_AT_HOUR"): retention["run_at_hour"] = _env_int( "CLOUDRESTORE_RETENTION_RUN_AT_HOUR", retention.get("run_at_hour", 3), min_value=0, max_value=23, ) if os.getenv("CLOUDRESTORE_RETENTION_CHECK_INTERVAL_SECONDS"): retention["check_interval_seconds"] = _env_int( "CLOUDRESTORE_RETENTION_CHECK_INTERVAL_SECONDS", retention.get("check_interval_seconds", 3600), min_value=60, ) if os.getenv("CLOUDRESTORE_RETENTION_DRY_RUN"): retention["dry_run"] = _env_bool("CLOUDRESTORE_RETENTION_DRY_RUN", True) return config def render_env_template(app_dir: Path | None = None) -> str: """Genera contenido inicial de config/.env.""" base = (app_dir or APP_DIR).resolve() sep = "\\" if os.name == "nt" else "/" def p(*parts: str) -> str: return sep.join([str(base), *parts]) return f"""# CloudRestoreAS — configuración local (editar y reiniciar la app) # Arranca el motor al abrir. La ventana SIEMPRE se muestra al iniciar; cerrar (X) la # minimiza a la bandeja del sistema (START_MINIMIZED ya no oculta la ventana). CLOUDRESTORE_AUTO_START=true CLOUDRESTORE_START_MINIMIZED=false CLOUDRESTORE_REGISTER_AUTOSTART=true # Servicio PANEL_BASES_ANEXO24 (enrutamiento automático de nodos) CLOUDRESTORE_PANEL_API_URL= CLOUDRESTORE_PANEL_API_TOKEN= CLOUDRESTORE_PANEL_INSTANCE_KEY= CLOUDRESTORE_PANEL_VERIFY_SSL=false # Carpetas de trabajo CLOUDRESTORE_INPUT_FOLDER={p("Entrada")} CLOUDRESTORE_PROCESSED_FOLDER={p("Procesados")} CLOUDRESTORE_FAILED_FOLDER={p("Fallados")} CLOUDRESTORE_EXTRACT_FOLDER={p("Temp")} # SQL local (solo si no usa panel para credenciales) # CLOUDRESTORE_SQL_SERVER=localhost # CLOUDRESTORE_SQL_USERNAME= # CLOUDRESTORE_DATA_SQL_FOLDER= # Retención (limpieza diaria de respaldos obsoletos para no saturar el disco) # CLOUDRESTORE_RETENTION_ENABLED=true # CLOUDRESTORE_RETENTION_DAYS=2 # Procesados: por nodo, respecto al más reciente # CLOUDRESTORE_RETENTION_FAILED_DAYS=7 # Fallados: por antigüedad absoluta # CLOUDRESTORE_RETENTION_RUN_AT_HOUR=3 # hora local de la corrida diaria (0-23) # CLOUDRESTORE_RETENTION_CHECK_INTERVAL_SECONDS=3600 # CLOUDRESTORE_RETENTION_DRY_RUN=true # true = solo simula; poner false para borrar """