diff --git a/handlers/callbacks.py b/handlers/callbacks.py index a2d9ae5..e5e75f0 100644 --- a/handlers/callbacks.py +++ b/handlers/callbacks.py @@ -67,7 +67,7 @@ async def snapshot_details(cb: CallbackQuery): snap_id = cb.data.split(":", 1)[1] await cb.answer("Loading snapshot…") - # получаем статистику snapshot + # получаем статистику snapshot rc, raw = await run_cmd( ["restic", "stats", snap_id, "--json"], use_restic_env=True, diff --git a/services/health.py b/services/health.py index bd52f33..693679f 100644 --- a/services/health.py +++ b/services/health.py @@ -37,7 +37,7 @@ def _npm_api_base(cfg) -> str | None: def health(cfg, container_map: dict | None = None) -> str: - lines = ["рџ©є Health check\n"] + lines = ["🩺 Health check\n"] thresholds = cfg.get("thresholds", {}) disk_warn = int(thresholds.get("disk_warn", 80)) load_warn = float(thresholds.get("load_warn", 2.0)) @@ -45,9 +45,9 @@ def health(cfg, container_map: dict | None = None) -> str: env = os.environ.copy() env.update(RESTIC_ENV) subprocess.check_output(["restic", "snapshots"], timeout=10, env=env) - lines.append("рџџў Backup repo reachable") + lines.append("🟢 Backup repo reachable") except Exception: - lines.append("🔴 Backup repo unreachable") + lines.append("🔴 Backup repo unreachable") containers = container_map if container_map is not None else _containers_from_cfg(cfg) for alias, real in containers.items(): @@ -55,20 +55,20 @@ def health(cfg, container_map: dict | None = None) -> str: f"docker inspect -f '{{{{.State.Status}}}}' {real}" ) if out.strip() != "running": - lines.append(f"🔴 {alias} down") + lines.append(f"🔴 {alias} down") else: - lines.append(f"рџџў {alias} OK") + lines.append(f"🟢 {alias} OK") npm_cfg = cfg.get("npmplus", {}) npm_base = _npm_api_base(cfg) if npm_base: npm_status = _request_status(npm_base, npm_cfg.get("verify_tls", True)) if npm_status == 200: - lines.append("рџџў NPMplus API OK") + lines.append("🟢 NPMplus API OK") elif npm_status is None: - lines.append("🔴 NPMplus API unreachable") + lines.append("🔴 NPMplus API unreachable") else: - lines.append(f"рџџЎ NPMplus API HTTP {npm_status}") + lines.append(f"🟡 NPMplus API HTTP {npm_status}") g_cfg = cfg.get("gitea", {}) g_base = (g_cfg.get("base_url") or "").rstrip("/") @@ -84,22 +84,22 @@ def health(cfg, container_map: dict | None = None) -> str: g_status = status break if g_status == 200: - lines.append("рџџў Gitea API OK") + lines.append("🟢 Gitea API OK") elif g_status is None: - lines.append("🔴 Gitea API unreachable") + lines.append("🔴 Gitea API unreachable") else: - lines.append(f"рџџЎ Gitea API HTTP {g_status}") + lines.append(f"🟡 Gitea API HTTP {g_status}") usage, mount = worst_disk_usage() if usage is None: - lines.append("вљ пёЏ Disk n/a") + lines.append("⚠️ Disk n/a") elif usage > disk_warn: - lines.append(f"рџџЎ Disk {usage}% ({mount})") + lines.append(f"🟡 Disk {usage}% ({mount})") else: - lines.append(f"рџџў Disk {usage}% ({mount})") + lines.append(f"🟢 Disk {usage}% ({mount})") load = psutil.getloadavg()[0] - lines.append(f"{'рџџў' if load < load_warn else 'рџџЎ'} Load {load}") + lines.append(f"{'🟢' if load < load_warn else '🟡'} Load {load}") return "\n".join(lines) diff --git a/system_checks.py b/system_checks.py index 8a37836..d991604 100644 --- a/system_checks.py +++ b/system_checks.py @@ -82,6 +82,53 @@ def list_disks() -> list[str]: return disks +def list_md_arrays() -> list[str]: + out = _cmd("lsblk -dn -o NAME,TYPE") + arrays = [] + for line in out.splitlines(): + parts = line.split() + if len(parts) != 2: + continue + name, typ = parts + if typ == "raid" and name.startswith("md"): + arrays.append(f"/dev/{name}") + return arrays + + +def md_array_status(dev: str) -> str: + out = _cmd("cat /proc/mdstat") + if not out or "ERROR:" in out: + return "⚠️ n/a" + + name = dev.rsplit("/", 1)[-1] + lines = out.splitlines() + header = None + idx = -1 + for i, line in enumerate(lines): + s = line.strip() + if s.startswith(f"{name} :"): + header = s + idx = i + break + + if not header: + return "⚠️ not found in /proc/mdstat" + + if "inactive" in header: + return "🔴 inactive" + + # Typical mdstat health marker: [UU] for healthy mirrors/raid members. + block = [header] + for line in lines[idx + 1:]: + if not line.strip(): + break + block.append(line.strip()) + block_text = " ".join(block) + if "[U_" in block_text or "[_U" in block_text: + return "🟡 degraded" + return "🟢 active" + + def smart_health(dev: str) -> str: out = _cmd(f"smartctl -H {dev}") @@ -138,8 +185,9 @@ def smart_last_test(dev: str) -> str: def disks() -> str: disks = list_disks() + md_arrays = list_md_arrays() - if not disks: + if not disks and not md_arrays: return "💽 Disks\n\n❌ No disks found" lines = ["💽 Disks (SMART)\n"] @@ -158,6 +206,12 @@ def disks() -> str: lines.append(f"{icon} {d} — {health}, 🌡 {temp}") + if md_arrays: + lines.append("") + lines.append("🧱 RAID (md)") + for md in md_arrays: + lines.append(f"{md} — {md_array_status(md)}") + return "\n".join(lines)