From d4c24f02d4c6e89596c6dfd64549597f9ae710aa Mon Sep 17 00:00:00 2001 From: nessi Date: Mon, 17 Aug 2026 19:27:21 +0200 Subject: [PATCH] feat: add smart_serial() function to extract manufacturer serial numbers from SMART data with fallback chain and parallel execution Add smart_serial() function with smartctl -i command execution, fallback to -d sat mode, and Serial Number: field regex extraction with WWN/0x prefix filtering to prefer physical serial over logical identifiers. Add serial_futures dict with service_check_executor.submit() calls for parallel serial reads alongside smart_futures/temperature_futures. Update raid_status() disk --- app.py | 16 +++++++++++++++- 1 file changed, 15 insertions(+), 1 deletion(-) diff --git a/app.py b/app.py index fb08f86..23013a9 100644 --- a/app.py +++ b/app.py @@ -365,6 +365,18 @@ def smart_status(device): health = re.search(r"(?:SMART overall-health self-assessment test result|SMART Health Status):\s*(.+)", smart_output, re.IGNORECASE) return health.group(1).strip() if health else ("Fehler: " + smart_error.strip()[:80] if smart_error.strip() else "Nicht unterstützt") +def smart_serial(device): + """Read the manufacturer serial, not a WWN or SCSI by-id alias.""" + if not shutil.which("smartctl"): + return "" + smart_device = device.replace("/dev/", f"{HOST_DEVICE_PATH.rstrip('/')}/", 1) + for command in (["smartctl", "-i", "-d", "sat", smart_device], ["smartctl", "-i", smart_device]): + output, _ = _read_command(command, timeout=6, warn_on_nonzero=False) + match = re.search(r"^\s*Serial Number:\s*(\S+)\s*$", output, re.IGNORECASE | re.MULTILINE) + if match and not match.group(1).lower().startswith(("wwn-", "0x")): + return match.group(1).strip() + return "" + def smart_temperature(device): """Read the drive temperature in a controller-friendly way.""" if not SMART_ENABLED or not shutil.which("smartctl"): @@ -457,9 +469,11 @@ def raid_status(): except (ValueError, AttributeError): block_devices = {} smart_futures = {} + serial_futures = {} temperature_futures = {} if shutil.which("smartctl"): smart_futures = {disk["device"]: service_check_executor.submit(smart_status, disk["device"]) for disk in devices} + serial_futures = {disk["device"]: service_check_executor.submit(smart_serial, disk["device"]) for disk in devices} temperature_futures = {disk["device"]: service_check_executor.submit(smart_temperature, disk["device"]) for disk in devices} for disk in devices: info = block_devices.get(disk["device"], {}) @@ -473,7 +487,7 @@ def raid_status(): info = host_items[0] if host_items else {} except (ValueError, AttributeError): info = {} - serial = info.get("serial") or _device_serial(disk["device"]) + serial = (serial_futures[disk["device"]].result() if disk["device"] in serial_futures else "") or info.get("serial") or _device_serial(disk["device"]) disk.update({"size": format_bytes(info.get("size") or 0) if info.get("size") else "—", "model": info.get("model") or "—", "serial": serial or "—"}) serial_log_key = "serial:" + disk["device"] if not serial and serial_log_key not in hardware_log_once: