mirror of
https://github.com/ChuckBuilds/LEDMatrix.git
synced 2026-10-13 18:46:37 +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>
This commit is contained in:
@@ -44,6 +44,44 @@
|
||||
</label>
|
||||
</div>
|
||||
|
||||
<!-- Weekly Automatic Updates -->
|
||||
<div class="form-group" id="setting-general-auto_update" data-setting-key="auto_update.enabled">
|
||||
<label class="flex items-center">
|
||||
<input type="checkbox"
|
||||
name="auto_update_enabled"
|
||||
value="true"
|
||||
{% if (main_config.auto_update or {}).enabled %}checked{% endif %}
|
||||
class="form-control h-4 w-4 text-blue-600 focus:ring-blue-500 border-gray-300 rounded">
|
||||
<span class="ml-2 text-sm font-medium text-gray-900">Automatically check for and install updates once a week</span>
|
||||
{{ ui.help_tip('Once a week, update LEDMatrix and every installed plugin that has a newer version.\nSkipped (and reported) if you have local changes, low disk space, or the rollback service is missing. After updating, the services are restarted and checked; if they do not stay healthy, the update is rolled back automatically.\nRuns overnight (2-5 AM in your timezone) when possible. Problems show as a banner on Overview. A stopped display is not started. Default: off.', 'Automatic Updates') }}
|
||||
</label>
|
||||
{% if auto_update_status and (main_config.auto_update or {}).enabled and auto_update_status.verifier_installed is sameas false %}
|
||||
<p class="mt-1 ml-6 text-xs text-amber-700">
|
||||
{% if auto_update_status.setup_status == 'failed' %}
|
||||
Setting up the update health check failed: {{ auto_update_status.setup_message }}
|
||||
LEDMatrix code updates are paused until it is fixed; plugin updates still run.
|
||||
{% else %}
|
||||
Setting up the update health check. The display service does this when it starts, so it is
|
||||
ready shortly after saving (or once the display is started). LEDMatrix code updates begin after that.
|
||||
{% endif %}
|
||||
</p>
|
||||
{% endif %}
|
||||
{% if auto_update_status and auto_update_status.verifying %}
|
||||
<p class="mt-1 ml-6 text-xs text-blue-700">An update was just installed and its health check is running.</p>
|
||||
{% endif %}
|
||||
{% if auto_update_status and (auto_update_status.last_run or auto_update_status.next_due) %}
|
||||
<div class="mt-1 ml-6 text-xs text-gray-500 space-y-0.5">
|
||||
{% if auto_update_status.last_run %}
|
||||
<p>Last automatic update: {{ auto_update_status.last_run }}
|
||||
<span class="{{ 'text-red-600' if auto_update_status.status == 'error' else '' }}">— {{ auto_update_status.summary }}</span></p>
|
||||
{% endif %}
|
||||
{% if auto_update_status.next_due %}
|
||||
<p>Next check: {{ auto_update_status.next_due }} or the first overnight window after it</p>
|
||||
{% endif %}
|
||||
</div>
|
||||
{% endif %}
|
||||
</div>
|
||||
|
||||
<!-- Timezone -->
|
||||
<div class="form-group" id="setting-general-timezone" data-setting-key="timezone">
|
||||
<label for="timezone" class="block text-sm font-medium text-gray-700">Timezone{{ ui.help_tip('Time zone used for clocks, schedules, and time-based content.\nChoose the zone where the display physically lives so on/off schedules fire at the correct local time.', 'Timezone') }}</label>
|
||||
|
||||
@@ -1,3 +1,44 @@
|
||||
<!-- Automatic update banner: a weekly update that failed, was blocked or was rolled back -->
|
||||
<div id="auto-update-banner" class="bg-red-50 border border-red-300 rounded-lg p-4 mb-4 flex items-start" style="display:none !important" role="alert">
|
||||
<div class="flex-shrink-0 mr-3 mt-0.5">
|
||||
<i class="fas fa-exclamation-circle text-red-500"></i>
|
||||
</div>
|
||||
<div class="flex-1">
|
||||
<p class="text-sm font-medium text-red-800">Automatic Update Needs Attention</p>
|
||||
<p class="text-sm text-red-700 mt-1" id="auto-update-banner-text"></p>
|
||||
<p class="text-xs text-red-600 mt-1">Details are under Automatic Updates on the General tab.</p>
|
||||
</div>
|
||||
<button type="button" onclick="window.dismissAutoUpdateBanner()" class="ml-4 flex-shrink-0 text-red-500 hover:text-red-700" aria-label="Dismiss">
|
||||
<i class="fas fa-times"></i>
|
||||
</button>
|
||||
</div>
|
||||
<script>
|
||||
(function () {
|
||||
fetch('/api/v3/system/auto-update')
|
||||
.then(function (r) { return r.json(); })
|
||||
.then(function (resp) {
|
||||
var d = resp.data || {};
|
||||
if (!d.alert) return;
|
||||
document.getElementById('auto-update-banner-text').textContent = d.alert;
|
||||
var banner = document.getElementById('auto-update-banner');
|
||||
banner.dataset.alertId = d.alert_id || '';
|
||||
banner.style.setProperty('display', 'flex', 'important');
|
||||
})
|
||||
.catch(function () {});
|
||||
|
||||
// Dismissal is stored server-side, so the banner stays gone on every
|
||||
// device until a new failure raises a new alert.
|
||||
window.dismissAutoUpdateBanner = function () {
|
||||
var banner = document.getElementById('auto-update-banner');
|
||||
banner.style.setProperty('display', 'none', 'important');
|
||||
fetch('/api/v3/system/auto-update/dismiss', {
|
||||
method: 'POST',
|
||||
headers: {'Content-Type': 'application/json'},
|
||||
body: JSON.stringify({alert_id: banner.dataset.alertId})
|
||||
}).catch(function () {});
|
||||
};
|
||||
}());
|
||||
</script>
|
||||
<!-- Reconciliation warning banner: shown when startup reconciliation found stale plugin config entries -->
|
||||
<div id="reconciliation-banner" class="bg-yellow-50 border border-yellow-300 rounded-lg p-4 mb-4 flex items-start" style="display:none !important" role="alert">
|
||||
<div class="flex-shrink-0 mr-3 mt-0.5">
|
||||
|
||||
Reference in New Issue
Block a user