pip can echo a private index URL with embedded credentials;
permission_utils redacts it, the stdlib-only verifier cannot, so it
logs the exit code only.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
The health check reinstalled the previous requirements by trying the
next bash path after any failure, including a 600 s pip timeout. Two
files, two paths: up to 40 minutes of pip alone, while systemd stops
ledmatrix-update-verify.service at TimeoutStartSec=30min -- killing the
rollback half-way and leaving the update 'verifying' until the web UI
calls it lost.
- Like permission_utils.install_requirements_file, only a sudo refusal
moves on to the next bash; a pip that ran and failed or timed out is
not repeated. The refusal wording is one list
(permission_utils.SUDO_REFUSAL_PHRASES), mirrored in the stdlib-only
verifier and pinned equal by a test.
- All reinstalls in one rollback share a 600 s budget.
- WORST_CASE_SECONDS adds up every timeout on the longest path (27.5
min); a test holds it under the unit's TimeoutStartSec and that under
the web UI's VERIFY_LOST_SECONDS.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
The automatic update's preflight ignored mode-only changes and anything
whose status line contained plugins/ or plugin-repos/, then promised
"Automatic updates will not stash your changes". perform_core_update
used plain git status (modes count) and ignored only 'plugins/', then
ran 'git stash push -- :!plugins', which nothing ever pops. So an edit
to a bundled plugin under plugin-repos/, or the installer's chmods on
tracked scripts, passed the preflight and was stashed away for good.
- auto_update.local_changes() is the one predicate both use:
core.fileMode=false, porcelain -z, and plugins/ and plugin-repos/
excluded by leading folder rather than substring (a core file under
web_interface/static/v3/js/plugins/ now counts).
- Update Code's explicit stash leaves out both plugin folders; the
pull's --autostash carries their edits and mode changes across and
reapplies them.
- The automatic updater calls perform_core_update(stash_local_changes=
False), which refuses instead of stashing edits that appeared after
the preflight; update_core reports that as 'blocked'.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
* 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>