Adds a Tools/Utilities tab to the web interface with one-click
maintenance buttons that previously required SSH:
- Git status panel (branch, dirty state, recent commits)
- Pull latest (rebase) and force reset to origin/main
- Reinstall base requirements (pip, with output)
- Reinstall per-plugin requirements (pass/fail per plugin)
- Clear __pycache__ directories
- Quick-access restart for display and web services
Also exposes the hzeller row_address_type option (0–4) in the
Display settings tab. The backend already read this value from
config; the UI, API field list, and validation were missing.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Address remaining coderabbitai findings on PR #369:
- check_package_installed() did __import__(package_name) directly, but
python-dateutil and websocket-client import as dateutil/websocket. Both
always failed the "already installed" check and were reinstalled on
every run. Add an IMPORT_NAME_MAP for the mismatched names.
- _run() still read the entire temp file into memory before slicing the
tail. Stream it line-by-line into a deque(maxlen=ERROR_TAIL_LINES)
instead so memory use stays bounded for very chatty commands.
Codacy's Semgrep-based check still flagged the cmd-built subprocess.run
call as "without a static string" even with the Bandit nosec applied.
Add a nosemgrep marker alongside it - cmd is always a hardcoded
apt/pip argument list, never user input.
Address coderabbitai review on PR #369:
- _run() now streams combined stdout/stderr to a temp file and returns
only the last ERROR_TAIL_LINES lines, instead of buffering full
output in memory (Codacy also flagged the previous capture_output
call as a subprocess-without-static-string security issue; the new
call is annotated as safe since cmd is built from hardcoded args).
- `apt update` now runs once in main() instead of once per package
needing an apt fallback.
- wait_for_apt_lock: apt_update/apt_install now wait (up to 3min) for
unattended-upgrades to release the dpkg lock instead of failing
outright with "Command failed after 3 attempts" right after first boot.
- check_disk_space: new pre-flight check (Step 1) so a full SD card fails
fast with a clear message instead of a cryptic mid-build error.
- Step 6: wrap rpi-rgb-led-matrix git clone/submodule operations in retry
for resilience to transient network issues.
- Step 6: capture `pip install .` build output and print the last 50
lines on failure, so the actual cmake/compiler error is visible instead
of just "Failed to install rpi-rgb-led-matrix Python package".
install_dependencies_apt.py previously reported only which packages
failed, not why - the actual apt/pip error was discarded (apt) or
could scroll out of the on_error log tail (pip), leaving "Step 7:
Install web interface dependencies (line 915)" as the only visible
detail.
Capture command output for each install attempt and print a compact
DEPENDENCY INSTALLATION FAILURES summary with the last lines of error
output per package. Also run the installer with `python3 -u` for
real-time, correctly-ordered logging, and widen the on_error tail from
50 to 100 lines so the summary isn't cut off.
set +e alone doesn't suppress bash's ERR trap, so any non-zero exit from
first_time_install.sh inside the one-shot installer immediately triggered
the outer on_error handler with a generic "Main installation, line 370"
message — before the script could report the real exit code or point to
logs/. Suspend the trap for that block so the existing if/else handling
runs instead.