Quellcode durchsuchen

fix: use absolute paths for system commands to prevent FileNotFoundError in systemd

When running as a systemd service, PATH may not include directories for
nmcli, sudo, systemctl, git, etc. Use shutil.which() with fallbacks to
resolve absolute paths at import time.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
tuanchris vor 4 Monaten
Ursprung
Commit
6dd7d45960
4 geänderte Dateien mit 28 neuen und 17 gelöschten Zeilen
  1. 4 4
      main.py
  2. 7 3
      modules/screen/screen_controller.py
  3. 11 6
      modules/update/update_manager.py
  4. 6 4
      modules/wifi/manager.py

+ 4 - 4
main.py

@@ -4039,10 +4039,10 @@ async def shutdown_system():
         def delayed_shutdown():
             time.sleep(2)  # Give time for response to be sent
             try:
-                subprocess.run(["sudo", "systemctl", "poweroff"], check=True)
+                subprocess.run(["/usr/bin/sudo", "/usr/bin/systemctl", "poweroff"], check=True)
                 logger.info("System shutdown command executed successfully")
             except FileNotFoundError:
-                logger.error("systemctl command not found")
+                logger.error("sudo or systemctl command not found - ensure systemd is available")
             except Exception as e:
                 logger.error(f"Error executing host shutdown command: {e}")
 
@@ -4068,10 +4068,10 @@ async def restart_system():
         def delayed_restart():
             time.sleep(2)  # Give time for response to be sent
             try:
-                subprocess.run(["sudo", "systemctl", "restart", "dune-weaver"], check=True)
+                subprocess.run(["/usr/bin/sudo", "/usr/bin/systemctl", "restart", "dune-weaver"], check=True)
                 logger.info("Service restart command executed successfully")
             except FileNotFoundError:
-                logger.error("systemctl command not found")
+                logger.error("sudo or systemctl command not found - ensure systemd is available")
             except Exception as e:
                 logger.error(f"Error executing service restart: {e}")
 

+ 7 - 3
modules/screen/screen_controller.py

@@ -6,11 +6,15 @@ machines without /sys/class/backlight the controller reports available=False
 and all commands no-op gracefully.
 """
 import logging
+import shutil
 import subprocess
 from pathlib import Path
 
 logger = logging.getLogger(__name__)
 
+SUDO = shutil.which("sudo") or "/usr/bin/sudo"
+SH = shutil.which("sh") or "/bin/sh"
+
 
 class ScreenController:
     def __init__(self):
@@ -74,7 +78,7 @@ class ScreenController:
         value = max(0, min(value, self.max_brightness))
         try:
             subprocess.run(
-                ["sudo", "sh", "-c", f"echo {value} > {self.brightness_path}"],
+                [SUDO, SH, "-c", f"echo {value} > {self.brightness_path}"],
                 check=True, timeout=5
             )
             self._current_brightness = value
@@ -95,7 +99,7 @@ class ScreenController:
                 # Restore brightness + unblank framebuffer
                 restore = self._current_brightness if self._current_brightness > 0 else self.max_brightness
                 subprocess.run(
-                    ["sudo", "sh", "-c",
+                    [SUDO, SH, "-c",
                      f"echo 0 > /sys/class/graphics/fb0/blank && echo {restore} > {self.brightness_path}"],
                     check=True, timeout=5
                 )
@@ -104,7 +108,7 @@ class ScreenController:
             else:
                 # Zero brightness + blank framebuffer
                 subprocess.run(
-                    ["sudo", "sh", "-c",
+                    [SUDO, SH, "-c",
                      f"echo 0 > {self.brightness_path} && echo 1 > /sys/class/graphics/fb0/blank"],
                     check=True, timeout=5
                 )

+ 11 - 6
modules/update/update_manager.py

@@ -1,25 +1,30 @@
+import shutil
 import subprocess
 import logging
 
 # Configure logging
 logger = logging.getLogger(__name__)
 
+GIT = shutil.which("git") or "/usr/bin/git"
+SUDO = shutil.which("sudo") or "/usr/bin/sudo"
+SYSTEMCTL = shutil.which("systemctl") or "/usr/bin/systemctl"
+
 def check_git_updates():
     """Check for available Git updates."""
     try:
         logger.debug("Checking for Git updates")
-        subprocess.run(["git", "fetch", "--tags", "--force"], check=True)
+        subprocess.run([GIT, "fetch", "--tags", "--force"], check=True)
         latest_remote_tag = subprocess.check_output(
-            ["git", "describe", "--tags", "--abbrev=0", "origin/main"]
+            [GIT, "describe", "--tags", "--abbrev=0", "origin/main"]
         ).strip().decode()
         latest_local_tag = subprocess.check_output(
-            ["git", "describe", "--tags", "--abbrev=0"]
+            [GIT, "describe", "--tags", "--abbrev=0"]
         ).strip().decode()
 
         tag_behind_count = 0
         if latest_local_tag != latest_remote_tag:
             tags = subprocess.check_output(
-                ["git", "tag", "--merged", "origin/main"], text=True
+                [GIT, "tag", "--merged", "origin/main"], text=True
             ).splitlines()
 
             found_local = False
@@ -73,7 +78,7 @@ def update_software():
     # Step 1: Pull latest code via git
     logger.info("Pulling latest code from git...")
     git_result = run_command(
-        ["git", "pull", "--ff-only"],
+        [GIT, "pull", "--ff-only"],
         "Failed to pull latest code from git"
     )
     if git_result:
@@ -89,7 +94,7 @@ def update_software():
     # Step 3: Restart the service
     logger.info("Restarting dune-weaver service...")
     restart_result = run_command(
-        ["sudo", "systemctl", "restart", "dune-weaver"],
+        [SUDO, SYSTEMCTL, "restart", "dune-weaver"],
         "Failed to restart dune-weaver service"
     )
 

+ 6 - 4
modules/wifi/manager.py

@@ -5,6 +5,7 @@ Handles scanning, connecting, and managing WiFi connections.
 """
 
 import subprocess
+import shutil
 import os
 import logging
 import asyncio
@@ -12,11 +13,12 @@ import asyncio
 logger = logging.getLogger(__name__)
 
 HOTSPOT_CON_NAME = "DuneWeaver-Hotspot"
+NMCLI = shutil.which("nmcli") or "/usr/bin/nmcli"
 
 
 def run_nmcli(*args: str, timeout: int = 30) -> str:
     """Run nmcli and return stdout."""
-    cmd = ["nmcli"] + list(args)
+    cmd = [NMCLI] + list(args)
     logger.debug(f"Running nmcli: {' '.join(cmd)}")
     result = subprocess.run(cmd, capture_output=True, text=True, timeout=timeout)
 
@@ -30,7 +32,7 @@ def run_nmcli(*args: str, timeout: int = 30) -> str:
 
 def run_nmcli_check(*args: str, timeout: int = 30) -> subprocess.CompletedProcess:
     """Run nmcli and return the full CompletedProcess (for checking returncode)."""
-    cmd = ["nmcli"] + list(args)
+    cmd = [NMCLI] + list(args)
     logger.debug(f"Running nmcli: {' '.join(cmd)}")
     result = subprocess.run(cmd, capture_output=True, text=True, timeout=timeout)
 
@@ -229,7 +231,7 @@ async def connect_to_network(ssid: str, password: str) -> dict:
     try:
         # Delete any stale connection profile for this SSID (ignore if not found)
         subprocess.run(
-            ["nmcli", "con", "delete", ssid],
+            [NMCLI, "con", "delete", ssid],
             capture_output=True, text=True, timeout=10,
         )
 
@@ -292,7 +294,7 @@ def save_network(ssid: str, password: str) -> dict:
     try:
         # Delete any stale connection profile for this SSID (ignore if not found)
         subprocess.run(
-            ["nmcli", "con", "delete", ssid],
+            [NMCLI, "con", "delete", ssid],
             capture_output=True, text=True, timeout=10,
         )