feat(install): updates refresh systemd units; new installs run the newest release (#729)

Updates that move HEAD now install changed systemd units through a root-owned helper (/usr/local/sbin/ledmatrix-refresh-units, two literal sudo lines), with a backup restored on rollback; a refresh that fails part-way puts the old units back. Devices without the new sudo rule keep updating and are told to re-run the installer once. The one-shot installer now checks out the newest vX.Y.Z release (LEDMATRIX_CHANNEL=beta keeps main) and never moves an existing checkout backwards.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
Chuck
2026-10-03 13:09:53 -04:00
committed by GitHub
co-authored by Claude Opus 5.5
parent 515248b34e
commit f841fa36b6
24 changed files with 1974 additions and 29 deletions
+34
View File
@@ -82,3 +82,37 @@ class TestRestartIsRequestedWhenCodeChanged:
data = _pull(client)
assert data['status'] == 'error'
assert data['restart_required'] is False
class TestUnitsAreRefreshedWithTheCode:
"""New code may bring new systemd unit settings; installing them is part of the update."""
@pytest.fixture
def refresh(self, monkeypatch):
from web_interface import unit_refresh
calls = []
def fake():
calls.append(True)
return {'status': 'refreshed', 'message': 'Service settings updated (ledmatrix.service).',
'units': ['ledmatrix.service']}
monkeypatch.setattr(unit_refresh, 'refresh_after_update', fake)
return calls
def test_an_update_that_moved_head_refreshes_the_units(self, client, refresh):
with patch.object(mod.subprocess, 'run', _git(['aaa111', 'bbb222'])):
data = _pull(client)
assert refresh == [True]
assert data['unit_refresh']['status'] == 'refreshed'
assert 'Service settings updated' in data['message']
def test_nothing_new_touches_no_units(self, client, refresh):
with patch.object(mod.subprocess, 'run',
_git(['aaa111', 'aaa111'], pull_out='Already up to date.\n')):
data = _pull(client)
assert refresh == [] and data['unit_refresh'] is None
def test_a_failed_pull_touches_no_units(self, client, refresh):
with patch.object(mod.subprocess, 'run', _git(['aaa111'], pull_rc=1)):
data = _pull(client)
assert refresh == [] and data['unit_refresh'] is None