Add self-update, including in Docker
Sandbar can install new releases itself using Reef's selfupdate, automatically with SANDBAR_AUTO_UPDATE=1 or from a button on the Account page. In a container the new binary is written to <data>/bin and takes over from the image at start, until the image catches up. Tags now publish a Gitea release with archives and SHA256SUMS as well as the image. Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
This commit is contained in:
1 parent
955e01333d
commit
73f45fb3ae
14 files changed
+610
-20
No files matched your search
@@ -755,8 +755,36 @@ function passwordDialog(u) {
|
||||
}
|
||||
|
||||
// ---------- account ----------
|
||||
// Administrators see which release is running and can install a newer one. The server restarts
|
||||
// into the new binary, so the page waits for it to come back.
|
||||
function updateCard(u) {
|
||||
const status = h('p', {style:'margin:0'}), detail = h('p', {class:'dim', style:'margin:0'}), actions = h('div', {class:'actions form-actions'});
|
||||
const paint = u => {
|
||||
status.replaceChildren(`Running ${u.current}. `, u.error ? 'The newest release could not be checked.' : u.newer ? `${u.latest} is available.` : u.latest ? 'This is the newest release.' : '');
|
||||
detail.textContent = u.error ? u.error : !u.canInstall ? u.reason : u.autoUpdate ? 'Automatic updates are on. Sandbar checks every few hours.' : 'Automatic updates are off. Set SANDBAR_AUTO_UPDATE=1 and restart to turn them on.';
|
||||
actions.replaceChildren(...[h('button', {class:'ghost', onclick:act(async () => { paint(await api('GET', V1 + '/system/update?fresh=1')); })}, 'Check again'),
|
||||
u.newer && u.canInstall ? h('button', {class:'btn', onclick:ask({title:`Update to ${u.latest}?`, danger:false, confirm:'Update now',
|
||||
body:'Sandbar downloads the release, checks it, and restarts. Monitoring and paging pause for a few seconds. Your data is not changed.'}, async () => {
|
||||
await api('POST', V1 + '/system/update', {}); wait(u.current); })}, `Update to ${u.latest}`) : null].filter(Boolean));
|
||||
};
|
||||
const wait = async from => {
|
||||
actions.replaceChildren(); status.textContent = 'Updating.';
|
||||
for (let i = 0; i < 120; i++) {
|
||||
await new Promise(r => setTimeout(r, 1500));
|
||||
let s; try { s = await api('GET', V1 + '/system/update'); } catch (e) { detail.textContent = 'Restarting.'; continue; }
|
||||
if (s.current !== from) { toast(`Updated to ${s.current}.`); return refresh(); }
|
||||
if (s.failed) { toast(s.failed, true); return paint(s); }
|
||||
detail.textContent = s.line || s.stage;
|
||||
}
|
||||
toast('Sandbar did not come back in time. Check the server.', true);
|
||||
};
|
||||
paint(u);
|
||||
return card('Software update', h('div', {class:'stack'}, status, detail, actions));
|
||||
}
|
||||
|
||||
views.account = async () => {
|
||||
const keys = await api('GET', V1 + '/apikeys'), pw = h('input', {type:'password', autocomplete:'new-password'});
|
||||
const upd = S.me.role === 'admin' ? await api('GET', V1 + '/system/update').catch(() => null) : null;
|
||||
S.headerAction = null;
|
||||
return h('div', {}, card('Password', h('div', {class:'stack'}, field('New password', pw, 'At least 10 characters. You are signed out on other devices.'),
|
||||
h('div', {class:'actions form-actions'}, h('button', {class:'btn', onclick:act(async () => { await api('POST', `${V1}/users/${S.me.id}/password`, {password:pw.value}); pw.value = ''; toast('Password changed. Sign in again.'); S.me = null; boot(); })}, 'Change password')))),
|
||||
@@ -766,7 +794,7 @@ views.account = async () => {
|
||||
h('button', {class:'ghost', onclick:() => { const n = h('input'); dialogForm('Create an API key', null, [field('Name', n, 'Where you will use it, such as "prometheus".')], 'Create key', async () => {
|
||||
const k = await api('POST', V1 + '/apikeys', {name:n.value}); refresh();
|
||||
const done = modal('Copy your API key', h('div', {class:'stack'}, h('p', {class:'dim', style:'margin:0'}, 'This is the only time it is shown.'), cmdBox(k.token), h('div', {class:'actions form-actions'}, h('button', {class:'btn', onclick:() => done()}, 'Done')))); }); }}, icon('add', 16), 'Create API key')),
|
||||
card('Your personal topic', h('p', {class:'dim'}, 'Subscribe to this topic in the ntfy app, or in a browser on the Topics page, to be paged when an escalation policy reaches you.'), cmdBox(S.me.topic)));
|
||||
card('Your personal topic', h('p', {class:'dim'}, 'Subscribe to this topic in the ntfy app, or in a browser on the Topics page, to be paged when an escalation policy reaches you.'), cmdBox(S.me.topic)), upd ? updateCard(upd) : null);
|
||||
};
|
||||
|
||||
boot();
|
||||
|
||||
Reference in new issue
Block a user