from __future__ import annotations from dataclasses import dataclass from typing import Any from pobsync.config.merge import build_effective_config from .config_repository import global_config_object_data, host_config_object_data from .host_ops import collect_host_checks from .models import GlobalConfig, HostConfig from .self_check import SelfCheck DRY_RUN_BLOCKING_CHECKS = { "Host global config", "Host address", "Host SSH key file", "Host effective source root", "Host effective SSH user", "Host effective SSH port", "Host effective SSH credential", "Host effective rsync recursion", } @dataclass(frozen=True) class BackupGate: state: str message: str checks: list[SelfCheck] real_blockers: list[SelfCheck] dry_run_blockers: list[SelfCheck] warnings: list[SelfCheck] @property def can_queue_real(self) -> bool: return not self.real_blockers @property def can_queue_dry_run(self) -> bool: return not self.dry_run_blockers def collect_backup_gate(host: HostConfig, global_config: GlobalConfig | None = None) -> BackupGate: checks = collect_host_checks(host, global_config) real_blockers = [check for check in checks if check.status == "failed"] dry_run_blockers = [check for check in real_blockers if check.name in DRY_RUN_BLOCKING_CHECKS] warnings = [check for check in checks if check.status == "warning"] if real_blockers: state = "blocked" message = "Real backups are blocked until failed host checks are resolved." elif warnings: state = "warning" message = "Backups can run, but review the warnings first." else: state = "ready" message = "This host is ready for backup runs." return BackupGate( state=state, message=message, checks=checks, real_blockers=real_blockers, dry_run_blockers=dry_run_blockers, warnings=warnings, ) def effective_host_config_preview(host: HostConfig, global_config: GlobalConfig) -> dict[str, Any]: config = build_effective_config(global_config_object_data(global_config), host_config_object_data(host)) credential = host.ssh_credential or global_config.default_ssh_credential ssh = config.get("ssh", {}) or {} rsync = config.get("rsync", {}) or {} retention = config.get("retention", {}) or {} return { "source_root": config.get("source_root", ""), "destination_subdir": (config.get("defaults", {}) or {}).get("destination_subdir", ""), "includes": list(config.get("includes") or []), "excludes": list(config.get("excludes_effective") or []), "ssh": { "user": ssh.get("user", ""), "port": ssh.get("port", ""), "options": list(ssh.get("options") or []), "credential": str(credential) if credential else "", }, "rsync": { "binary": rsync.get("binary", ""), "args": list(rsync.get("args_effective") or []), "timeout_seconds": rsync.get("timeout_seconds", 0), "bwlimit_kbps": rsync.get("bwlimit_kbps", 0), }, "retention": { "daily": retention.get("daily", 0), "weekly": retention.get("weekly", 0), "monthly": retention.get("monthly", 0), "yearly": retention.get("yearly", 0), }, }