feat: add /host-sys volume mount and HOST_SYS_PATH environment variable for sysfs serial fallback and SMART health detection

Add HOST_SYS_PATH=/host-sys to .env.example and app.py config with /host-sys default. Update _device_serial() to check HOST_SYS_PATH/class/block and HOST_SYS_PATH/block before fallback /sys paths for serial reads. Update raid_status() SMART regex to match both "SMART overall-health self-assessment test result" and "SMART Health Status" with case-insensitive flag for NVMe/SSD compatibility. Add /
This commit is contained in:
2026-08-16 12:14:10 +02:00
parent f7638dd5d5
commit 10eb3a4191
3 changed files with 11 additions and 2 deletions
+1
View File
@@ -37,4 +37,5 @@ RAID_DEVICE=/dev/md127
STORAGE_CACHE_SECONDS=900 STORAGE_CACHE_SECONDS=900
# Optional: SMART-Abfragen aktivieren (benötigt Zugriff auf Host-Devices) # Optional: SMART-Abfragen aktivieren (benötigt Zugriff auf Host-Devices)
SMART_ENABLED=false SMART_ENABLED=false
HOST_SYS_PATH=/host-sys
LOG_LEVEL=INFO LOG_LEVEL=INFO
+3 -2
View File
@@ -41,6 +41,7 @@ REQUEST_TIMEOUT = int(os.environ.get("REQUEST_TIMEOUT", "20"))
DATASTORE_PATH = os.environ.get("DATASTORE_PATH", "/nesflix").rstrip("/") or "/" DATASTORE_PATH = os.environ.get("DATASTORE_PATH", "/nesflix").rstrip("/") or "/"
RAID_DEVICE = os.environ.get("RAID_DEVICE", "/dev/md127") RAID_DEVICE = os.environ.get("RAID_DEVICE", "/dev/md127")
HOST_DEVICE_PATH = os.environ.get("HOST_DEVICE_PATH", "/host-dev") HOST_DEVICE_PATH = os.environ.get("HOST_DEVICE_PATH", "/host-dev")
HOST_SYS_PATH = os.environ.get("HOST_SYS_PATH", "/host-sys")
STORAGE_CACHE_SECONDS = int(os.environ.get("STORAGE_CACHE_SECONDS", "900")) STORAGE_CACHE_SECONDS = int(os.environ.get("STORAGE_CACHE_SECONDS", "900"))
SMART_ENABLED = os.environ.get("SMART_ENABLED", "false").lower() in {"1", "true", "yes", "on"} SMART_ENABLED = os.environ.get("SMART_ENABLED", "false").lower() in {"1", "true", "yes", "on"}
@@ -243,7 +244,7 @@ def _flatten_lsblk(nodes):
def _device_serial(device): def _device_serial(device):
"""Read a disk serial from sysfs when lsblk cannot expose it in Docker.""" """Read a disk serial from sysfs when lsblk cannot expose it in Docker."""
name = Path(device).name name = Path(device).name
for path in (Path("/sys/class/block") / name / "device/serial", Path("/sys/block") / name / "device/serial"): for path in (Path(HOST_SYS_PATH) / "class/block" / name / "device/serial", Path(HOST_SYS_PATH) / "block" / name / "device/serial", Path("/sys/class/block") / name / "device/serial", Path("/sys/block") / name / "device/serial"):
try: try:
value = path.read_text().strip() value = path.read_text().strip()
if value: if value:
@@ -357,7 +358,7 @@ def raid_status():
if SMART_ENABLED and shutil.which("smartctl"): if SMART_ENABLED and shutil.which("smartctl"):
smart_device = disk["device"].replace("/dev/", f"{HOST_DEVICE_PATH.rstrip('/')}/", 1) smart_device = disk["device"].replace("/dev/", f"{HOST_DEVICE_PATH.rstrip('/')}/", 1)
smart_output, _ = _read_command(["smartctl", "-H", smart_device], timeout=6) smart_output, _ = _read_command(["smartctl", "-H", smart_device], timeout=6)
health = re.search(r"SMART overall-health self-assessment test result:\s*(.+)", smart_output) health = re.search(r"(?:SMART overall-health self-assessment test result|SMART Health Status):\s*(.+)", smart_output, re.IGNORECASE)
disk["smart"] = health.group(1).strip() if health else "Nicht verfügbar" disk["smart"] = health.group(1).strip() if health else "Nicht verfügbar"
missing = members_match and members_match.group(1) != members_match.group(2) missing = members_match and members_match.group(1) != members_match.group(2)
failed = detail.get("failed_devices", 0) > 0 or any(disk["status"] == "failed" for disk in devices) failed = detail.get("failed_devices", 0) > 0 or any(disk["status"] == "failed" for disk in devices)
+7
View File
@@ -7,6 +7,11 @@ services:
- "8099:8099" - "8099:8099"
devices: devices:
- "${RAID_DEVICE:-/dev/md127}:${RAID_DEVICE:-/dev/md127}" - "${RAID_DEVICE:-/dev/md127}:${RAID_DEVICE:-/dev/md127}"
cap_add:
- SYS_RAWIO
device_cgroup_rules:
- "b 8:* rwm"
- "b 9:* rwm"
extra_hosts: extra_hosts:
- "host.docker.internal:host-gateway" - "host.docker.internal:host-gateway"
env_file: env_file:
@@ -20,6 +25,8 @@ services:
- ${DATASTORE_HOST_PATH:-/nesflix}:/nesflix:ro - ${DATASTORE_HOST_PATH:-/nesflix}:/nesflix:ro
# Read-only Host-Devices für lsblk/smartctl; Media Max führt keine RAID-Schreiboperationen aus. # Read-only Host-Devices für lsblk/smartctl; Media Max führt keine RAID-Schreiboperationen aus.
- /dev:/host-dev:ro - /dev:/host-dev:ro
# Read-only Hardware-Metadaten für Seriennummern und SMART-Zuordnung.
- /sys:/host-sys:ro
- ./data:/data - ./data:/data
networks: networks:
- media - media