refactor(api): resolve sudo/systemctl/reboot/poweroff paths at startup

Use shutil.which() with safe fallbacks for the four privileged binaries
instead of relying on bare names being resolved by the subprocess shell
search. Resolves paths once at module load rather than per-call.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
Chuck
2026-05-12 14:49:34 -04:00
parent 246ea54635
commit aae95a1015

View File

@@ -2,6 +2,7 @@ from flask import Blueprint, request, jsonify, Response, send_from_directory
import json import json
import os import os
import re import re
import shutil
import socket import socket
import sys import sys
import subprocess import subprocess
@@ -16,6 +17,11 @@ from typing import Optional, Tuple, Dict, Any, Type
logger = logging.getLogger(__name__) logger = logging.getLogger(__name__)
SUDO_BIN = shutil.which("sudo") or "/usr/bin/sudo"
SYSTEMCTL_BIN = shutil.which("systemctl") or "/usr/bin/systemctl"
REBOOT_BIN = shutil.which("reboot") or "/usr/sbin/reboot"
POWEROFF_BIN = shutil.which("poweroff") or "/usr/sbin/poweroff"
# Import new infrastructure # Import new infrastructure
from src.web_interface.api_helpers import success_response, error_response, validate_request_json from src.web_interface.api_helpers import success_response, error_response, validate_request_json
from src.web_interface.errors import ErrorCode from src.web_interface.errors import ErrorCode
@@ -218,7 +224,7 @@ def _ensure_display_service_running():
if status.get('active'): if status.get('active'):
status['started'] = False status['started'] = False
return status return status
result = _run_systemctl_command(['sudo', 'systemctl', 'start', 'ledmatrix.service']) result = _run_systemctl_command([SUDO_BIN, SYSTEMCTL_BIN, 'start', 'ledmatrix.service'])
service_status = _get_display_service_status() service_status = _get_display_service_status()
result['started'] = result.get('returncode') == 0 result['started'] = result.get('returncode') == 0
result['active'] = service_status.get('active') result['active'] = service_status.get('active')
@@ -227,7 +233,7 @@ def _ensure_display_service_running():
def _stop_display_service(): def _stop_display_service():
"""Stop the ledmatrix display service.""" """Stop the ledmatrix display service."""
result = _run_systemctl_command(['sudo', 'systemctl', 'stop', 'ledmatrix.service']) result = _run_systemctl_command([SUDO_BIN, SYSTEMCTL_BIN, 'stop', 'ledmatrix.service'])
status = _get_display_service_status() status = _get_display_service_status()
result['active'] = status.get('active') result['active'] = status.get('active')
result['status'] = status result['status'] = status
@@ -1716,7 +1722,7 @@ def execute_system_action():
if mode: if mode:
# For on-demand modes, we would need to integrate with the display controller # For on-demand modes, we would need to integrate with the display controller
# For now, just start the display service # For now, just start the display service
result = subprocess.run(['sudo', 'systemctl', 'start', 'ledmatrix.service'], result = subprocess.run([SUDO_BIN, SYSTEMCTL_BIN, 'start', 'ledmatrix.service'],
capture_output=True, text=True, timeout=15) capture_output=True, text=True, timeout=15)
return jsonify({ return jsonify({
'status': 'success' if result.returncode == 0 else 'error', 'status': 'success' if result.returncode == 0 else 'error',
@@ -1727,22 +1733,22 @@ def execute_system_action():
'stderr': result.stderr 'stderr': result.stderr
}) })
else: else:
result = subprocess.run(['sudo', 'systemctl', 'start', 'ledmatrix.service'], result = subprocess.run([SUDO_BIN, SYSTEMCTL_BIN, 'start', 'ledmatrix.service'],
capture_output=True, text=True, timeout=15) capture_output=True, text=True, timeout=15)
elif action == 'stop_display': elif action == 'stop_display':
result = subprocess.run(['sudo', 'systemctl', 'stop', 'ledmatrix.service'], result = subprocess.run([SUDO_BIN, SYSTEMCTL_BIN, 'stop', 'ledmatrix.service'],
capture_output=True, text=True, timeout=15) capture_output=True, text=True, timeout=15)
elif action == 'enable_autostart': elif action == 'enable_autostart':
result = subprocess.run(['sudo', 'systemctl', 'enable', 'ledmatrix.service'], result = subprocess.run([SUDO_BIN, SYSTEMCTL_BIN, 'enable', 'ledmatrix.service'],
capture_output=True, text=True, timeout=15) capture_output=True, text=True, timeout=15)
elif action == 'disable_autostart': elif action == 'disable_autostart':
result = subprocess.run(['sudo', 'systemctl', 'disable', 'ledmatrix.service'], result = subprocess.run([SUDO_BIN, SYSTEMCTL_BIN, 'disable', 'ledmatrix.service'],
capture_output=True, text=True, timeout=15) capture_output=True, text=True, timeout=15)
elif action == 'reboot_system': elif action == 'reboot_system':
result = subprocess.run(['sudo', 'reboot'], result = subprocess.run([SUDO_BIN, REBOOT_BIN],
capture_output=True, text=True, timeout=10) capture_output=True, text=True, timeout=10)
elif action == 'shutdown_system': elif action == 'shutdown_system':
result = subprocess.run(['sudo', 'poweroff'], result = subprocess.run([SUDO_BIN, POWEROFF_BIN],
capture_output=True, text=True, timeout=10) capture_output=True, text=True, timeout=10)
elif action == 'git_pull': elif action == 'git_pull':
# Use PROJECT_ROOT instead of hardcoded path # Use PROJECT_ROOT instead of hardcoded path
@@ -1824,10 +1830,10 @@ def execute_system_action():
'stderr': result.stderr 'stderr': result.stderr
}) })
elif action == 'restart_display_service': elif action == 'restart_display_service':
result = subprocess.run(['sudo', 'systemctl', 'restart', 'ledmatrix.service'], result = subprocess.run([SUDO_BIN, SYSTEMCTL_BIN, 'restart', 'ledmatrix.service'],
capture_output=True, text=True, timeout=15) capture_output=True, text=True, timeout=15)
elif action == 'restart_web_service': elif action == 'restart_web_service':
result = subprocess.run(['sudo', 'systemctl', 'restart', 'ledmatrix-web.service'], result = subprocess.run([SUDO_BIN, SYSTEMCTL_BIN, 'restart', 'ledmatrix-web.service'],
capture_output=True, text=True, timeout=15) capture_output=True, text=True, timeout=15)
else: else:
return jsonify({'status': 'error', 'message': f'Unknown action: {action}'}), 400 return jsonify({'status': 'error', 'message': f'Unknown action: {action}'}), 400