diff --git a/backend/services/zfs_runner.py b/backend/services/zfs_runner.py index 32dc54c..189e507 100644 --- a/backend/services/zfs_runner.py +++ b/backend/services/zfs_runner.py @@ -15,6 +15,10 @@ import re logger = logging.getLogger(__name__) +# Detect ZFS availability once at import time — avoids repeated ERROR logs on LXC/non-ZFS systems +import shutil as _shutil +ZFS_AVAILABLE = bool(_shutil.which("zpool")) + # Cache with TTL @dataclass class CacheEntry: @@ -83,7 +87,7 @@ class ZFSRunner: logger.error(f"Command timeout after {timeout}s: {' '.join(cmd)}") return "", f"Command timeout after {timeout}s", -1 except FileNotFoundError: - logger.error(f"Command not found: {' '.join(cmd)}") + logger.debug(f"Command not found: {' '.join(cmd)}") return "", f"Command not found: {cmd[0]}", -1 except Exception as e: logger.error(f"Command execution error: {e}") @@ -100,6 +104,9 @@ class ZFSRunner: if cached: return cached + if not ZFS_AVAILABLE: + return [] + stdout, stderr, rc = self.run_command(["zpool", "list", "-H", "-p"]) if rc != 0: @@ -266,6 +273,9 @@ class ZFSRunner: """ Get detailed pool status including VDEV tree and error counters """ + if not ZFS_AVAILABLE: + return {} + stdout, stderr, rc = self.run_command(["zpool", "status", pool_name]) if rc != 0: @@ -347,6 +357,9 @@ class ZFSRunner: if cached and cached.get(pool_name): return cached[pool_name] + if not ZFS_AVAILABLE: + return [] + stdout, stderr, rc = self.run_command([ "zfs", "list", "-d", str(max_depth), "-H", "-p", "-o", "name,used,avail,refer,mountpoint,type", @@ -446,6 +459,9 @@ class ZFSRunner: if cached: return cached + if not ZFS_AVAILABLE: + return [] + if dataset_name: # No -d limit so sub-datasets (e.g. tank/share) are included cmd = ["zfs", "list", "-t", "snapshot", "-r", "-H", "-p",