mirror of
https://github.com/ChuckBuilds/LEDMatrix.git
synced 2026-10-04 06:15:09 +00:00
* feat(web): weekly automatic updates with health check and rollback A General-tab toggle (off by default) checks for and installs LEDMatrix and plugin updates once a week, overnight in the configured timezone. - Pre-update checks skip (and report) instead of forcing: local edits or commits, merge/live rebase, no upstream, low disk, missing health check, or a version that was already rolled back. An abandoned rebase (HEAD back on a branch) is cleared, since it would otherwise block every pull. - The pull reuses the Update Code path (now perform_core_update(), which reports dependency install failures as data). - ledmatrix-update-verify.service, started via a .path unit from a request file, restarts the services from its own cgroup, requires them to come up and stay up, and otherwise resets to the previous commit and reinstalls the previous requirements. It runs a copy of the checker taken before the pull. - No SSH needed: switching the toggle on restarts the display service, which (as root) installs the two units from the repo templates for the web user. first_time_install.sh installs them too and takes --enable-auto-update / LEDMATRIX_AUTO_UPDATE (passed through by one-shot-install.sh). - Plugins update after the code passes its check; failures, blocks and rollbacks raise an Overview banner and show under the toggle. Tested end to end on a Pi: web-UI setup, a good update, a broken web service and a broken display (both rolled back), a blocked local edit, and an abandoned rebase found on the device. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> * chore(auto-update): address static-analysis findings - Replace the subprocess.CompletedProcess the verifier fabricated for a command that could not start with a plain namedtuple; nothing is executed there, but the scanner flags any CompletedProcess built from variables. - Mark the subprocess imports with the repo's standard B404 annotation (all calls are list-form argv, no shell). - Mark the rollback-failed message as not SQL (B608 matched its wording). Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> * fix(auto-update): CI failures on Linux - Keep the setup result when chown fails. CI runs as a non-root user, where chown to the web user raises; that discarded the result file, so the General tab would never learn whether setup worked. Regression test added. - Register the two new /api/v3/system/auto-update routes in the URL map snapshot. - Use utility classes app.css defines (space-y-1, hover:text-red-600). Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> * fix(auto-update): address review feedback - Health check: a failed restart command no longer lets the check run against the still-running old process; it counts as a failure (and after a rollback, as a failed rollback). An unreadable restart count is never treated as stable, since a crash loop looks healthy between attempts. - Installer writes the auto_update setting to a temp file and swaps it in, keeping mode and owner, so a running config watcher never reads a truncated config.json. - Verify unit quotes its command-line paths (install folders with spaces); setup refuses folder names systemd would reinterpret (%, quotes, backslashes, control characters) and says so on the General tab. - The auto-update status route no longer returns exception text. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> * fix(auto-update): keep error detail in the status route's 500 test_web_error_detail requires every 5xx handler to log the traceback and return describe_exception(e), which redacts credentials, so failures are diagnosable from the web UI. Dropping it for CodeQL broke that policy. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> * fix(auto-update): dismiss route rejects non-object JSON with 400 A JSON array or scalar body made `.get('alert_id')` raise, returning 500. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> * fix(auto-update): let the app-wide handler answer status-route errors CodeQL (py/stack-trace-exposure, #709) flagged the route's own except, which returned describe_exception(e). web_interface/app.py's error handler already logs the traceback and returns the same redacted detail for any unhandled exception, so the local copy is removed: same response, no new exception-to-response flow, and test_web_error_detail's policy still holds. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> --------- Co-authored-by: Claude Opus 5 <noreply@anthropic.com>
170 lines
7.0 KiB
Bash
Executable File
170 lines
7.0 KiB
Bash
Executable File
#!/bin/bash
|
|
|
|
# Exit on error
|
|
set -e
|
|
|
|
# Get the actual user who invoked sudo
|
|
if [ -n "$SUDO_USER" ]; then
|
|
ACTUAL_USER="$SUDO_USER"
|
|
else
|
|
ACTUAL_USER=$(whoami)
|
|
fi
|
|
|
|
# Get the home directory of the actual user
|
|
USER_HOME=$(eval echo ~$ACTUAL_USER)
|
|
|
|
# Determine the Project Root Directory (parent of scripts/install/)
|
|
PROJECT_ROOT_DIR=$(cd "$(dirname "$0")/../.." && pwd)
|
|
|
|
# shellcheck source=scripts/install/lib_systemd_render.sh
|
|
source "$PROJECT_ROOT_DIR/scripts/install/lib_systemd_render.sh"
|
|
|
|
echo "Installing LED Matrix Display Service for user: $ACTUAL_USER"
|
|
echo "Using home directory: $USER_HOME"
|
|
echo "Project root directory: $PROJECT_ROOT_DIR"
|
|
|
|
# Render the main display unit from its template. The display service runs as
|
|
# root (it needs GPIO), so __USER__ is always root here -- unlike the web unit
|
|
# below, which runs as whoever installed it.
|
|
#
|
|
# A missing template or a failed render is fatal: falling through would leave
|
|
# whatever unit already sits at /etc/systemd/system/ledmatrix.service (from a
|
|
# previous install) untouched, and the enable/start step below would then
|
|
# silently reuse that stale unit instead of the one this run was asked to
|
|
# install.
|
|
if [ -f "$PROJECT_ROOT_DIR/systemd/ledmatrix.service" ]; then
|
|
ESCAPED_PROJECT_ROOT_DIR=$(sed_escape_replacement "$PROJECT_ROOT_DIR")
|
|
MAIN_UNIT_TMP=$(mktemp)
|
|
trap 'rm -f "$MAIN_UNIT_TMP"' EXIT
|
|
if ! sed "s|__PROJECT_ROOT_DIR__|$ESCAPED_PROJECT_ROOT_DIR|g; s|__USER__|root|g" \
|
|
"$PROJECT_ROOT_DIR/systemd/ledmatrix.service" > "$MAIN_UNIT_TMP"; then
|
|
echo "ERROR: failed to render ledmatrix.service from its template." >&2
|
|
exit 1
|
|
fi
|
|
# Copy the service file to the systemd directory
|
|
sudo cp "$MAIN_UNIT_TMP" /etc/systemd/system/ledmatrix.service
|
|
# Clean up
|
|
rm -f "$MAIN_UNIT_TMP"
|
|
trap - EXIT
|
|
else
|
|
echo "ERROR: ledmatrix.service template not found at $PROJECT_ROOT_DIR/systemd/ledmatrix.service." >&2
|
|
exit 1
|
|
fi
|
|
|
|
|
|
# Reload systemd to recognize the new service (or modified service)
|
|
sudo systemctl daemon-reload
|
|
|
|
if [ -f "/etc/systemd/system/ledmatrix.service" ]; then
|
|
echo "Enabling ledmatrix.service (main display) to start on boot..."
|
|
sudo systemctl enable ledmatrix.service
|
|
echo "Starting ledmatrix.service (main display)..."
|
|
sudo systemctl start ledmatrix.service
|
|
else
|
|
echo "Skipping enable/start for ledmatrix.service as it was not configured."
|
|
fi
|
|
|
|
# === LEDMatrix Web Interface service (ledmatrix-web.service) ===
|
|
echo "Installing LEDMatrix Web Interface service (ledmatrix-web.service)..."
|
|
|
|
# Rendered from systemd/ledmatrix-web.service, the same template
|
|
# install_web_service.sh uses. This was an inline heredoc until it drifted from
|
|
# the template: it had lost Wants=network-online.target, RestartSec,
|
|
# SyslogIdentifier, CacheDirectory and Environment=USE_THREADING. Because
|
|
# src/startup_validator.py compares the installed unit against the template,
|
|
# every boot warned "re-run install_service.sh" -- and doing so reinstalled the
|
|
# same stale copy, so the warning could never clear.
|
|
#
|
|
# As with the main unit above, a missing template or a failed render is
|
|
# fatal -- otherwise the enable/start check below would fall back to
|
|
# whatever unit (possibly stale) already exists at the destination path.
|
|
if [ -f "$PROJECT_ROOT_DIR/systemd/ledmatrix-web.service" ]; then
|
|
ESCAPED_ACTUAL_USER=$(sed_escape_replacement "$ACTUAL_USER")
|
|
WEB_UNIT_TMP=$(mktemp)
|
|
trap 'rm -f "$WEB_UNIT_TMP"' EXIT
|
|
if ! sed "s|__PROJECT_ROOT_DIR__|$ESCAPED_PROJECT_ROOT_DIR|g; s|__USER__|$ESCAPED_ACTUAL_USER|g" \
|
|
"$PROJECT_ROOT_DIR/systemd/ledmatrix-web.service" > "$WEB_UNIT_TMP"; then
|
|
echo "ERROR: failed to render ledmatrix-web.service from its template." >&2
|
|
exit 1
|
|
fi
|
|
sudo cp "$WEB_UNIT_TMP" /etc/systemd/system/ledmatrix-web.service
|
|
rm -f "$WEB_UNIT_TMP"
|
|
trap - EXIT
|
|
else
|
|
echo "ERROR: ledmatrix-web.service template not found at $PROJECT_ROOT_DIR/systemd/ledmatrix-web.service." >&2
|
|
exit 1
|
|
fi
|
|
|
|
# Health check / rollback units for automatic updates; see install_web_service.sh.
|
|
for VERIFY_UNIT in ledmatrix-update-verify.service ledmatrix-update-verify.path; do
|
|
if [ -f "$PROJECT_ROOT_DIR/systemd/$VERIFY_UNIT" ]; then
|
|
VERIFY_UNIT_TMP=$(mktemp)
|
|
if sed "s|__PROJECT_ROOT_DIR__|$ESCAPED_PROJECT_ROOT_DIR|g; s|__USER__|$ESCAPED_ACTUAL_USER|g" "$PROJECT_ROOT_DIR/systemd/$VERIFY_UNIT" > "$VERIFY_UNIT_TMP"; then
|
|
sudo cp "$VERIFY_UNIT_TMP" "/etc/systemd/system/$VERIFY_UNIT"
|
|
else
|
|
echo "WARNING: failed to render $VERIFY_UNIT; automatic code updates will stay paused." >&2
|
|
fi
|
|
rm -f "$VERIFY_UNIT_TMP"
|
|
fi
|
|
done
|
|
|
|
echo "Reloading systemd daemon for web service..."
|
|
sudo systemctl daemon-reload
|
|
|
|
if [ -f "/etc/systemd/system/ledmatrix-web.service" ]; then
|
|
echo "Enabling ledmatrix-web.service to start on boot..."
|
|
sudo systemctl enable ledmatrix-web.service
|
|
|
|
if [ -f /etc/systemd/system/ledmatrix-update-verify.path ]; then
|
|
echo "Enabling ledmatrix-update-verify.path (automatic update health check)..."
|
|
sudo systemctl enable --now ledmatrix-update-verify.path || echo "WARNING: could not enable ledmatrix-update-verify.path; automatic code updates will stay paused" >&2
|
|
fi
|
|
|
|
echo "Starting ledmatrix-web.service..."
|
|
sudo systemctl start ledmatrix-web.service
|
|
|
|
echo "LEDMatrix Web Interface service (ledmatrix-web.service) installation complete."
|
|
echo "It will start based on the 'web_display_autostart' setting in config/config.json."
|
|
else
|
|
echo "Skipping enable/start for ledmatrix-web.service as it was not configured."
|
|
fi
|
|
# === End of LEDMatrix Web Interface service ===
|
|
|
|
|
|
# Check the status
|
|
echo "Service status for main display (ledmatrix.service):"
|
|
sudo systemctl status ledmatrix.service || echo "ledmatrix.service not found or failed to get status."
|
|
echo "Service status for web interface (ledmatrix-web.service):"
|
|
sudo systemctl status ledmatrix-web.service || echo "ledmatrix-web.service not found or failed to get status."
|
|
|
|
echo ""
|
|
echo "LED Matrix Services have been processed."
|
|
echo ""
|
|
echo "To stop the main display when you SSH in:"
|
|
echo " sudo systemctl stop ledmatrix.service"
|
|
echo "To stop the web interface:"
|
|
echo " sudo systemctl stop ledmatrix-web.service"
|
|
|
|
echo ""
|
|
echo "To check if the main display service is running:"
|
|
echo " sudo systemctl status ledmatrix.service"
|
|
echo "To check if the web interface service is running:"
|
|
echo " sudo systemctl status ledmatrix-web.service"
|
|
|
|
echo ""
|
|
echo "To restart the main display service:"
|
|
echo " sudo systemctl restart ledmatrix.service"
|
|
echo "To restart the web interface service:"
|
|
echo " sudo systemctl restart ledmatrix-web.service"
|
|
|
|
echo ""
|
|
echo "To view logs for the main display:"
|
|
echo " journalctl -u ledmatrix.service"
|
|
echo "To view logs for the web interface:"
|
|
echo " journalctl -u ledmatrix-web.service"
|
|
|
|
echo ""
|
|
echo "To disable autostart for the main display:"
|
|
echo " sudo systemctl disable ledmatrix.service"
|
|
echo "To disable autostart for the web interface:"
|
|
echo " sudo systemctl disable ledmatrix-web.service" |