from __future__ import annotations import socket import tomllib from dataclasses import dataclass, field from pathlib import Path @dataclass class TelegramConfig: token: str chat_id: str @dataclass class AlertConfig: critical_repeat_runs: int = 1 warning_repeat_runs: int = 6 @dataclass class ZFSConfig: pools: list[str] warn_pct: int = 85 crit_pct: int = 95 timeout: int = 10 @dataclass class SmartConfig: extra_disks: list[str] = field(default_factory=list) reallocated_max: int = 0 pending_max: int = 0 uncorrectable_max: int = 0 temp_max: int = 55 timeout: int = 15 @dataclass class NutConfig: enabled: bool = False ups: list[str] = field(default_factory=lambda: ["ups@localhost"]) batt_warn_pct: int = 50 batt_crit_pct: int = 20 load_warn_pct: int = 80 runtime_crit_sec: int = 300 timeout: int = 10 @dataclass class Config: telegram: TelegramConfig zfs: ZFSConfig smart: SmartConfig nut: NutConfig alert: AlertConfig hostname: str = field(default_factory=socket.gethostname) log_file: str = "/var/log/server-monitor/monitor.log" state_file: str = "/var/lib/server-monitor/state.json" def load_config(path: str = "/etc/server-monitor.toml") -> Config: p = Path(path) if not p.exists(): raise FileNotFoundError(f"Config fehlt: {path}") with open(p, "rb") as f: raw = tomllib.load(f) tg = raw.get("telegram", {}) if not tg.get("token") or tg["token"] in ("", "DEIN_TOKEN"): raise ValueError("telegram.token nicht gesetzt") if not tg.get("chat_id") or tg["chat_id"] in ("", "DEINE_CHAT_ID"): raise ValueError("telegram.chat_id nicht gesetzt") telegram = TelegramConfig(token=tg["token"], chat_id=str(tg["chat_id"])) zfs_raw = raw.get("zfs", {}) if not zfs_raw.get("pools"): raise ValueError("zfs.pools ist leer") zfs = ZFSConfig( pools=zfs_raw["pools"], warn_pct=zfs_raw.get("warn_pct", 85), crit_pct=zfs_raw.get("crit_pct", 95), timeout=zfs_raw.get("timeout", 10), ) sm = raw.get("smart", {}) smart = SmartConfig( extra_disks=sm.get("extra_disks", []), reallocated_max=sm.get("reallocated_max", 0), pending_max=sm.get("pending_max", 0), uncorrectable_max=sm.get("uncorrectable_max", 0), temp_max=sm.get("temp_max", 55), timeout=sm.get("timeout", 15), ) nut_raw = raw.get("nut", {}) nut = NutConfig( enabled=nut_raw.get("enabled", False), ups=nut_raw.get("ups", ["ups@localhost"]), batt_warn_pct=nut_raw.get("batt_warn_pct", 50), batt_crit_pct=nut_raw.get("batt_crit_pct", 20), load_warn_pct=nut_raw.get("load_warn_pct", 80), runtime_crit_sec=nut_raw.get("runtime_crit_sec", 300), timeout=nut_raw.get("timeout", 10), ) alert_raw = raw.get("alert", {}) alert = AlertConfig( critical_repeat_runs=alert_raw.get("critical_repeat_runs", 1), warning_repeat_runs=alert_raw.get("warning_repeat_runs", 6), ) mon = raw.get("monitoring", {}) return Config( telegram=telegram, zfs=zfs, smart=smart, nut=nut, alert=alert, hostname=mon.get("hostname") or socket.gethostname(), log_file=mon.get("log_file", "/var/log/server-monitor/monitor.log"), state_file=mon.get("state_file", "/var/lib/server-monitor/state.json"), )