From a7d5fb5459e1d27955e8169bb6cf3bfc07650d66 Mon Sep 17 00:00:00 2001 From: benya Date: Sun, 8 Feb 2026 01:50:39 +0300 Subject: [PATCH] Add SMART short test and status --- handlers/system.py | 48 +++++++++++++++++++++++++++++++++++++++++++++- keyboards.py | 1 + system_checks.py | 14 ++++++++++++++ 3 files changed, 62 insertions(+), 1 deletion(-) diff --git a/handlers/system.py b/handlers/system.py index 96fab3a..a919681 100644 --- a/handlers/system.py +++ b/handlers/system.py @@ -5,7 +5,7 @@ from aiogram.types import Message, CallbackQuery, InlineKeyboardMarkup, InlineKe from app import dp, cfg from auth import is_admin_msg from keyboards import system_info_kb, system_ops_kb, system_logs_kb -from system_checks import security, disks, hardware +from system_checks import security, disks, hardware, list_disks, smart_last_test from services.http_checks import get_url_checks, check_url from services.queue import enqueue from services.updates import list_updates, apply_updates @@ -71,6 +71,52 @@ async def metrics(msg: Message): await msg.answer(summarize(state.METRICS_STORE, minutes=15), reply_markup=system_info_kb) +@dp.message(F.text == "πŸ§ͺ SMART test") +async def smart_test(msg: Message): + if not is_admin_msg(msg): + return + + disks_list = list_disks() + if not disks_list: + await msg.answer("πŸ’½ Disks\n\n❌ No disks found", reply_markup=system_info_kb) + return + + await msg.answer("πŸ§ͺ Starting SMART short tests…", reply_markup=system_info_kb) + + async def worker(): + lines = ["πŸ§ͺ SMART short tests\n"] + for dev in disks_list: + rc, out = await run_cmd(["sudo", "smartctl", "-t", "short", dev], timeout=20) + if rc != 0: + lines.append(f"πŸ”΄ {dev}: {out.strip() or 'error'}") + continue + summary = "started" + for line in out.splitlines(): + if "Please wait" in line or "will complete" in line: + summary = line.strip() + break + lines.append(f"🟒 {dev}: {summary}") + await msg.answer("\n".join(lines), reply_markup=system_info_kb) + + asyncio.create_task(worker()) + + +@dp.message(F.text == "πŸ§ͺ SMART status") +async def smart_status(msg: Message): + if not is_admin_msg(msg): + return + disks_list = list_disks() + if not disks_list: + await msg.answer("πŸ’½ Disks\n\n❌ No disks found", reply_markup=system_info_kb) + return + + lines = ["πŸ§ͺ SMART last tests\n"] + for dev in disks_list: + last = smart_last_test(dev) + lines.append(f"{dev}: {last}") + await msg.answer("\n".join(lines), reply_markup=system_info_kb) + + @dp.message(F.text == "🧾 Audit") async def audit_log(msg: Message): if not is_admin_msg(msg): diff --git a/keyboards.py b/keyboards.py index 601fbcf..ef35f3c 100644 --- a/keyboards.py +++ b/keyboards.py @@ -64,6 +64,7 @@ system_info_kb = ReplyKeyboardMarkup( keyboard=[ [KeyboardButton(text="πŸ’½ Disks"), KeyboardButton(text="πŸ” Security")], [KeyboardButton(text="πŸ“ˆ Metrics"), KeyboardButton(text="🧱 Hardware")], + [KeyboardButton(text="πŸ§ͺ SMART test"), KeyboardButton(text="πŸ§ͺ SMART status")], [KeyboardButton(text="⬅️ System")], ], resize_keyboard=True, diff --git a/system_checks.py b/system_checks.py index 90b26b8..8a37836 100644 --- a/system_checks.py +++ b/system_checks.py @@ -122,6 +122,20 @@ def disk_temperature(dev: str) -> str: return "n/a" +def smart_last_test(dev: str) -> str: + out = _cmd(f"smartctl -l selftest {dev}") + if not out or "ERROR:" in out: + return "n/a" + + for line in out.splitlines(): + if "No self-tests have been logged" in line: + return "no tests" + if line.lstrip().startswith("#"): + return line.strip() + + return "n/a" + + def disks() -> str: disks = list_disks()