from __future__ import annotations from pathlib import Path from typing import Any from django.core.exceptions import ObjectDoesNotExist from pobsync.config.schemas import GLOBAL_SCHEMA, HOST_SCHEMA from pobsync.paths import PobsyncPaths from pobsync.util import write_yaml_atomic from pobsync.validate import validate_dict from .models import GlobalConfig, HostConfig class ConfigRepositoryError(RuntimeError): pass def _global_yaml_data(global_config: GlobalConfig) -> dict[str, Any]: data = dict(global_config.data or {}) data["backup_root"] = global_config.backup_root data["pobsync_home"] = global_config.pobsync_home data["ssh"] = { "user": global_config.ssh_user, "port": global_config.ssh_port, "options": list(global_config.ssh_options or []), } data["rsync"] = { "binary": global_config.rsync_binary, "args": list(global_config.rsync_args or []), "timeout_seconds": global_config.rsync_timeout_seconds, "bwlimit_kbps": global_config.rsync_bwlimit_kbps, "extra_args": list(global_config.rsync_extra_args or []), } data["defaults"] = { "source_root": global_config.default_source_root, "destination_subdir": global_config.default_destination_subdir, } data["excludes_default"] = list(global_config.excludes_default or []) data["retention_defaults"] = { "daily": global_config.retention_daily, "weekly": global_config.retention_weekly, "monthly": global_config.retention_monthly, "yearly": global_config.retention_yearly, } return validate_dict(data, GLOBAL_SCHEMA, path="global") def _host_yaml_data(host_config: HostConfig) -> dict[str, Any]: data = dict(host_config.config or {}) data["host"] = host_config.host data["address"] = host_config.address if host_config.ssh_user or host_config.ssh_port: data["ssh"] = {} if host_config.ssh_user: data["ssh"]["user"] = host_config.ssh_user if host_config.ssh_port is not None: data["ssh"]["port"] = host_config.ssh_port else: data.pop("ssh", None) if host_config.source_root: data["source_root"] = host_config.source_root else: data.pop("source_root", None) data["includes"] = list(host_config.includes or []) if host_config.excludes_replace is not None: data["excludes_replace"] = list(host_config.excludes_replace or []) data.pop("excludes_add", None) else: data["excludes_add"] = list(host_config.excludes_add or []) data.pop("excludes_replace", None) if host_config.rsync_extra_args: data["rsync"] = {"extra_args": list(host_config.rsync_extra_args or [])} else: data.pop("rsync", None) data["retention"] = { "daily": host_config.retention_daily, "weekly": host_config.retention_weekly, "monthly": host_config.retention_monthly, "yearly": host_config.retention_yearly, } return validate_dict(data, HOST_SCHEMA, path="host") def global_config_data(name: str = "default") -> dict[str, Any]: try: global_config = GlobalConfig.objects.get(name=name) except ObjectDoesNotExist as exc: raise ConfigRepositoryError(f"Missing GlobalConfig {name!r}") from exc return _global_yaml_data(global_config) def host_config_data(host: str) -> dict[str, Any]: try: host_config = HostConfig.objects.get(host=host, enabled=True) except ObjectDoesNotExist as exc: raise ConfigRepositoryError(f"Missing enabled HostConfig {host!r}") from exc return _host_yaml_data(host_config) def export_global_config(prefix: Path, name: str = "default") -> Path: try: global_config = GlobalConfig.objects.get(name=name) except ObjectDoesNotExist as exc: raise ConfigRepositoryError(f"Missing GlobalConfig {name!r}") from exc paths = PobsyncPaths(home=prefix) write_yaml_atomic(paths.global_config_path, _global_yaml_data(global_config)) return paths.global_config_path def export_host_config(prefix: Path, host: str) -> Path: try: host_config = HostConfig.objects.get(host=host, enabled=True) except ObjectDoesNotExist as exc: raise ConfigRepositoryError(f"Missing enabled HostConfig {host!r}") from exc paths = PobsyncPaths(home=prefix) target = paths.hosts_dir / f"{host_config.host}.yaml" write_yaml_atomic(target, _host_yaml_data(host_config)) return target def export_runtime_configs(prefix: Path, host: str | None = None) -> list[Path]: written = [export_global_config(prefix)] hosts = HostConfig.objects.filter(enabled=True).order_by("host") if host is not None: hosts = hosts.filter(host=host) for host_config in hosts: written.append(export_host_config(prefix, host_config.host)) return written