Crea servizio watchdog configurabile

This commit is contained in:
2026-06-30 20:28:02 +02:00
parent 124cc370e5
commit 4dca5fbf95
17 changed files with 904 additions and 0 deletions

153
src/bakrest/config.py Normal file
View File

@@ -0,0 +1,153 @@
from __future__ import annotations
import shutil
import tomllib
from dataclasses import dataclass
from pathlib import Path
from typing import Any
from .default_config import DEFAULT_CONFIG_TOML
from .paths import default_config_path, expand_path
@dataclass(frozen=True)
class ServerCheckConfig:
type: str
port: int
interval_seconds: int
timeout_seconds: int
@dataclass(frozen=True)
class BackupConfig:
server_host: str
remote_destination: str
rsync_path: str
shutdown_on_success: bool
shutdown_command: str
server_check: ServerCheckConfig
@dataclass(frozen=True)
class WatchConfig:
include_dirs: tuple[Path, ...]
include_extensions: frozenset[str]
exclude_dirs: tuple[Path | str, ...]
exclude_patterns: tuple[str, ...]
@dataclass(frozen=True)
class StorageConfig:
change_registry: Path
logs_dir: Path
status_file: Path
@dataclass(frozen=True)
class StabilityConfig:
min_age_seconds: int
unchanged_check_interval_seconds: int
unchanged_checks_required: int
@dataclass(frozen=True)
class AppConfig:
path: Path
service_autostart: bool
tray_autostart: bool
watch: WatchConfig
backup: BackupConfig
storage: StorageConfig
stability: StabilityConfig
def ensure_default_config(path: Path | None = None) -> Path:
config_path = path or default_config_path()
if config_path.exists():
return config_path
config_path.parent.mkdir(parents=True, exist_ok=True)
config_path.write_text(DEFAULT_CONFIG_TOML, encoding="utf-8")
return config_path
def copy_example_config(destination: Path) -> None:
destination.parent.mkdir(parents=True, exist_ok=True)
shutil.copyfile(Path("config/config.example.toml"), destination)
def load_config(path: Path | None = None) -> AppConfig:
config_path = ensure_default_config(path)
data = tomllib.loads(config_path.read_text(encoding="utf-8"))
return parse_config(data, config_path)
def parse_config(data: dict[str, Any], path: Path) -> AppConfig:
service = data.get("service", {})
tray = data.get("tray", {})
watch = data.get("watch", {})
backup = data.get("backup", {})
server_check = backup.get("server_check", {})
storage = data.get("storage", {})
stability = data.get("stability", {})
include_dirs = tuple(expand_path(item) for item in watch.get("include_dirs", []))
include_extensions = frozenset(
_normalize_extension(item) for item in watch.get("include_extensions", [])
)
exclude_dirs = tuple(_normalize_exclude_dir(item) for item in watch.get("exclude_dirs", []))
storage_config = StorageConfig(
change_registry=expand_path(
storage.get("change_registry", "%PROGRAMDATA%\\BakRest\\changes.sqlite")
),
logs_dir=expand_path(storage.get("logs_dir", "%PROGRAMDATA%\\BakRest\\logs")),
status_file=expand_path(storage.get("status_file", "%PROGRAMDATA%\\BakRest\\status.json")),
)
return AppConfig(
path=path,
service_autostart=bool(service.get("autostart", True)),
tray_autostart=bool(tray.get("autostart", True)),
watch=WatchConfig(
include_dirs=include_dirs,
include_extensions=include_extensions,
exclude_dirs=exclude_dirs,
exclude_patterns=tuple(watch.get("exclude_patterns", [])),
),
backup=BackupConfig(
server_host=str(backup.get("server_host", "backup-server")),
remote_destination=str(backup.get("remote_destination", "")),
rsync_path=str(backup.get("rsync_path", "rsync")),
shutdown_on_success=bool(backup.get("shutdown_on_success", True)),
shutdown_command=str(backup.get("shutdown_command", "shutdown /s /t 0")),
server_check=ServerCheckConfig(
type=str(server_check.get("type", "tcp")).lower(),
port=int(server_check.get("port", 22)),
interval_seconds=int(server_check.get("interval_seconds", 300)),
timeout_seconds=int(server_check.get("timeout_seconds", 3)),
),
),
storage=storage_config,
stability=StabilityConfig(
min_age_seconds=int(stability.get("min_age_seconds", 60)),
unchanged_check_interval_seconds=int(
stability.get("unchanged_check_interval_seconds", 5)
),
unchanged_checks_required=int(stability.get("unchanged_checks_required", 2)),
),
)
def _normalize_extension(value: str) -> str:
value = value.strip().lower()
if not value:
return value
return value if value.startswith(".") else f".{value}"
def _normalize_exclude_dir(value: str) -> Path | str:
expanded = expand_path(value)
if "%" in value or not expanded.is_absolute():
return value.lower()
return expanded