Firmware CI releases + Gitea auto-update in the server
Build and push server image / build-and-push (push) Successful in 39s
Build and push server image / build-and-push (push) Successful in 39s
New Gitea Actions workflow builds both board variants and publishes them as release assets whenever firmware/version.txt is bumped. The server can now poll that repo's releases (next to the existing manual upload) and either surface an "Update frame" button or, with "Automatically apply updates" checked, stage the new build itself -- the frame still only updates on its own next wake either way.
This commit is contained in:
@@ -75,6 +75,25 @@
|
||||
{% endfor %}
|
||||
</select>
|
||||
</label>
|
||||
<label>Firmware Gitea repo URL
|
||||
<input type="text" id="firmware_update_repo_url" placeholder="https://git.example.com/owner/repo"
|
||||
value="{{ cfg.firmware_update_repo_url }}">
|
||||
</label>
|
||||
<label>Frame's board
|
||||
<select id="firmware_board_variant">
|
||||
<option value="xiao" {% if cfg.firmware_board_variant == "xiao" %}selected{% endif %}>Seeed XIAO ESP32-C6</option>
|
||||
<option value="devkit" {% if cfg.firmware_board_variant == "devkit" %}selected{% endif %}>ESP32-C6-DevKitC-1</option>
|
||||
</select>
|
||||
</label>
|
||||
<div class="checkbox-row">
|
||||
<input type="checkbox" id="firmware_auto_update" {% if cfg.firmware_auto_update %}checked{% endif %}>
|
||||
<label for="firmware_auto_update">Automatically apply updates</label>
|
||||
</div>
|
||||
<p class="sub" style="margin-top: 8px;">When a Gitea repo URL is set, the
|
||||
server periodically checks its releases for a newer build for the
|
||||
board above. With auto-apply off, an "Update frame" button appears
|
||||
below when one's found; with it on, the server stages the new
|
||||
build itself -- the frame still only updates on its own next wake.</p>
|
||||
<button type="button" class="secondary" id="load-albums">Load Albums</button>
|
||||
<button type="submit">Save</button>
|
||||
</form>
|
||||
@@ -111,6 +130,8 @@
|
||||
</p>
|
||||
<input type="file" id="firmware-file" accept=".bin">
|
||||
<button type="button" class="secondary" id="firmware-upload">Upload firmware</button>
|
||||
<p class="sub" id="firmware-gitea-status" style="display: none; margin-top: 10px;"></p>
|
||||
<button type="button" id="firmware-update-btn" style="display: none;">Update frame</button>
|
||||
</section>
|
||||
</div>
|
||||
</div>
|
||||
@@ -145,6 +166,9 @@
|
||||
quiet_hours_start: document.getElementById('quiet_hours_start').value || '22:00',
|
||||
quiet_hours_end: document.getElementById('quiet_hours_end').value || '07:00',
|
||||
timezone: document.getElementById('timezone').value || 'UTC',
|
||||
firmware_update_repo_url: document.getElementById('firmware_update_repo_url').value || '',
|
||||
firmware_board_variant: document.getElementById('firmware_board_variant').value,
|
||||
firmware_auto_update: String(document.getElementById('firmware_auto_update').checked),
|
||||
});
|
||||
const resp = await fetch('/api/config', {
|
||||
method: 'POST',
|
||||
@@ -495,6 +519,57 @@
|
||||
}
|
||||
});
|
||||
|
||||
async function loadFirmwareCheck() {
|
||||
const statusEl = document.getElementById('firmware-gitea-status');
|
||||
const btn = document.getElementById('firmware-update-btn');
|
||||
try {
|
||||
const resp = await fetch('/api/firmware/check');
|
||||
if (!resp.ok) {
|
||||
return;
|
||||
}
|
||||
const data = await resp.json();
|
||||
if (!data.enabled) {
|
||||
statusEl.style.display = 'none';
|
||||
btn.style.display = 'none';
|
||||
return;
|
||||
}
|
||||
statusEl.style.display = 'block';
|
||||
if (data.update_available) {
|
||||
statusEl.textContent = `Update available: v${data.latest_version}.`;
|
||||
btn.style.display = 'inline-block';
|
||||
} else if (data.latest_version) {
|
||||
statusEl.textContent = `Up to date (v${data.latest_version}).`;
|
||||
btn.style.display = 'none';
|
||||
} else {
|
||||
statusEl.textContent = 'No releases found yet.';
|
||||
btn.style.display = 'none';
|
||||
}
|
||||
} catch (e) {
|
||||
// A failed check is silent -- the manual upload path still works
|
||||
// regardless, and this just retries on the next poll.
|
||||
}
|
||||
}
|
||||
|
||||
document.getElementById('firmware-update-btn').addEventListener('click', async () => {
|
||||
const btn = document.getElementById('firmware-update-btn');
|
||||
btn.disabled = true;
|
||||
try {
|
||||
const resp = await fetch('/api/firmware/apply-latest', { method: 'POST' });
|
||||
if (!resp.ok) {
|
||||
throw new Error(await resp.text());
|
||||
}
|
||||
const result = await resp.json();
|
||||
document.getElementById('firmware-available').textContent =
|
||||
`Uploaded: v${result.version} -- the frame updates itself on its next wake if it's running something else.`;
|
||||
showStatus(true, `Firmware v${result.version} staged from Gitea.`);
|
||||
loadFirmwareCheck();
|
||||
} catch (e) {
|
||||
showStatus(false, e.message);
|
||||
} finally {
|
||||
btn.disabled = false;
|
||||
}
|
||||
});
|
||||
|
||||
let lastDevice = null;
|
||||
|
||||
async function loadQueue() {
|
||||
@@ -672,6 +747,7 @@
|
||||
loadQueue();
|
||||
loadBatteryLog();
|
||||
loadStats();
|
||||
loadFirmwareCheck();
|
||||
|
||||
// Redraw the canvas chart (and the "Last seen" alert color) with the
|
||||
// new theme's colors as soon as the toggle in the header is used --
|
||||
@@ -698,5 +774,10 @@
|
||||
// from elsewhere, battery report, firmware version) without a manual
|
||||
// refresh. Skipped mid-drag (see loadQueue above).
|
||||
setInterval(loadQueue, 10000);
|
||||
|
||||
// Separate, slower poll for the Gitea release check -- cheap either
|
||||
// way since the server itself throttles actual Gitea API calls to
|
||||
// once per gitea_releases.UPDATE_CHECK_INTERVAL_S.
|
||||
setInterval(loadFirmwareCheck, 60000);
|
||||
</script>
|
||||
{% endblock %}
|
||||
|
||||
Reference in New Issue
Block a user