mirror of
https://github.com/ChuckBuilds/LEDMatrix.git
synced 2026-10-04 22:35:08 +00:00
start_web_conditionally.py read the flag with
`config_data.get("web_display_autostart", False)`, so a config that simply
lacked the key got no web interface. Both config/config.template.json and
first_time_install.sh ship the key as true, so the code default contradicted
the shipped default in two places: absence means an older or hand-edited
config, not a request to stay down.
The failure mode was silent in the worst way. The "not starting" path exits 0,
so `systemctl status ledmatrix-web` reported the unit as successfully started
while nothing was listening on the port, and the only trace was one journal
line saying the flag was "false or not set" -- which reads as a deliberate
setting rather than a missing key.
Also start the web interface when config.json is missing or unparseable,
instead of exiting. The web interface is how a config gets created and
repaired, so a broken config is exactly when the user needs it most; leaving
it down means there is no way back in. Only an explicit false/off disables
autostart now, and the disabled message says "explicitly disabled" so the
journal distinguishes a real setting from a default.
Co-authored-by: Claude Opus 5 <noreply@anthropic.com>
143 lines
7.0 KiB
Python
143 lines
7.0 KiB
Python
import json
|
|
import os
|
|
import sys
|
|
import subprocess
|
|
from pathlib import Path
|
|
|
|
# Get project root directory (parent of scripts/utils/)
|
|
PROJECT_DIR = os.path.dirname(os.path.dirname(os.path.dirname(os.path.abspath(__file__))))
|
|
CONFIG_FILE = os.path.join(PROJECT_DIR, 'config', 'config.json')
|
|
WEB_INTERFACE_SCRIPT = os.path.join(PROJECT_DIR, 'web_interface', 'start.py')
|
|
# Marker file created by first_time_install.sh to indicate dependencies are installed
|
|
DEPS_MARKER = os.path.join(PROJECT_DIR, '.web_deps_installed')
|
|
|
|
|
|
def dependencies_installed():
|
|
"""Check if dependencies were installed during first-time setup."""
|
|
return os.path.exists(DEPS_MARKER)
|
|
|
|
def install_dependencies():
|
|
"""Install required dependencies using system Python."""
|
|
print("Installing dependencies...")
|
|
try:
|
|
requirements_file = os.path.join(PROJECT_DIR, 'web_interface', 'requirements.txt')
|
|
# Use --ignore-installed to handle system packages (like psutil) that can't be uninstalled
|
|
# This allows pip to install even if a system package version conflicts
|
|
result = subprocess.run([
|
|
sys.executable, '-m', 'pip', 'install', '--break-system-packages', '--ignore-installed', '-r', requirements_file
|
|
], capture_output=True, text=True)
|
|
|
|
if result.returncode != 0:
|
|
# Check if the error is just about psutil version conflict
|
|
if 'psutil' in result.stderr.lower() and ('uninstall' in result.stderr.lower() or 'cannot uninstall' in result.stderr.lower()):
|
|
print("Warning: psutil version conflict detected (system package vs requirements).")
|
|
print("Attempting to install other dependencies without psutil...")
|
|
# Try installing without psutil
|
|
with open(requirements_file, 'r') as f:
|
|
lines = f.readlines()
|
|
# Filter out psutil line
|
|
filtered_lines = [line for line in lines if 'psutil' not in line.lower()]
|
|
temp_reqs = os.path.join(PROJECT_DIR, 'web_interface', 'requirements_temp.txt')
|
|
with open(temp_reqs, 'w') as f:
|
|
f.writelines(filtered_lines)
|
|
try:
|
|
subprocess.check_call([
|
|
sys.executable, '-m', 'pip', 'install', '--break-system-packages', '--ignore-installed', '-r', temp_reqs
|
|
])
|
|
print("Dependencies installed successfully (psutil skipped - using system version)")
|
|
finally:
|
|
if os.path.exists(temp_reqs):
|
|
os.remove(temp_reqs)
|
|
else:
|
|
# Re-raise the error if it's not about psutil
|
|
print(f"Failed to install dependencies: {result.stderr}")
|
|
return False
|
|
else:
|
|
print("Dependencies installed successfully")
|
|
|
|
# Install rgbmatrix module from local source (optional - not required for web interface)
|
|
print("Installing rgbmatrix module (optional)...")
|
|
rgbmatrix_path = Path(PROJECT_DIR) / 'rpi-rgb-led-matrix-master' / 'bindings' / 'python'
|
|
if rgbmatrix_path.exists():
|
|
try:
|
|
subprocess.check_call([
|
|
sys.executable, '-m', 'pip', 'install', '--break-system-packages', '-e', str(rgbmatrix_path)
|
|
], stdout=subprocess.DEVNULL, stderr=subprocess.DEVNULL)
|
|
print("rgbmatrix module installed successfully")
|
|
except subprocess.CalledProcessError:
|
|
print("Warning: rgbmatrix module installation failed (not required for web interface, continuing...)")
|
|
else:
|
|
print("rgbmatrix module path not found (not required for web interface, continuing...)")
|
|
|
|
return True
|
|
except subprocess.CalledProcessError as e:
|
|
print(f"Failed to install dependencies: {e}")
|
|
return False
|
|
|
|
#: String spellings that turn autostart OFF. Anything else -- including the key
|
|
#: being absent entirely -- leaves it on.
|
|
DISABLED_STRINGS = ("off", "false", "no", "0")
|
|
|
|
|
|
def autostart_enabled(config_data):
|
|
"""Whether to bring the web interface up. Defaults to True.
|
|
|
|
config.template.json and first_time_install.sh both ship
|
|
``web_display_autostart`` as true, so a config that lacks the key is an
|
|
older or hand-edited one rather than a request to stay down. Defaulting to
|
|
False meant any such config silently got no web interface -- and because
|
|
the "not starting" path exits 0, systemd reported the unit as successfully
|
|
started while nothing was listening. Only an explicit false/off disables it.
|
|
"""
|
|
value = config_data.get("web_display_autostart", True)
|
|
if isinstance(value, str):
|
|
return value.strip().lower() not in DISABLED_STRINGS
|
|
return bool(value)
|
|
|
|
|
|
def main():
|
|
try:
|
|
with open(CONFIG_FILE, 'r') as f:
|
|
config_data = json.load(f)
|
|
except FileNotFoundError:
|
|
# The web interface is how a config gets created and repaired, so a
|
|
# missing one is the case where the user needs it most.
|
|
print(f"Config file {CONFIG_FILE} not found. Starting the web interface so it can be configured.")
|
|
config_data = {}
|
|
except (json.JSONDecodeError, OSError) as e:
|
|
print(f"Error reading config file {CONFIG_FILE}: {e}. Starting the web interface anyway so the config can be repaired.")
|
|
config_data = {}
|
|
|
|
if autostart_enabled(config_data):
|
|
print("Configuration 'web_display_autostart' is enabled. Starting web interface...")
|
|
|
|
# Only install dependencies if not already done during first-time setup
|
|
if not dependencies_installed():
|
|
print("First run detected: Installing dependencies...")
|
|
if not install_dependencies():
|
|
print("Failed to install dependencies. Exiting.")
|
|
sys.exit(1)
|
|
# Create marker file after successful install
|
|
Path(DEPS_MARKER).touch()
|
|
print("Dependencies installed and marker file created.")
|
|
else:
|
|
print("Dependencies already installed (marker file found). Skipping installation.")
|
|
|
|
try:
|
|
# Replace the current process with web_interface.py using system Python
|
|
# This is important for systemd to correctly manage the web server process.
|
|
# Ensure PYTHONPATH is set correctly if web_interface.py has relative imports to src
|
|
# The WorkingDirectory in systemd service should handle this for web_interface.py
|
|
print(f"Launching web interface v3: {sys.executable} {WEB_INTERFACE_SCRIPT}")
|
|
os.execvp(sys.executable, [sys.executable, WEB_INTERFACE_SCRIPT]) # nosec B606 - both args are fixed constants
|
|
except Exception as e:
|
|
print(f"Failed to exec web interface: {e}")
|
|
sys.exit(1) # Failed to start
|
|
else:
|
|
print("Configuration 'web_display_autostart' is explicitly disabled. Web interface will not be started.")
|
|
sys.exit(0) # Exit gracefully, service considered successful
|
|
|
|
if __name__ == '__main__':
|
|
main()
|
|
|