Files
FabledSteward/steward/templates/settings/backup_restore.html
T
bvandeusenandClaude Opus 5.5 fb99f8b551
CI / lint (push) Successful in 2s
CI / integration (push) Successful in 1m27s
CI / unit (push) Failing after 1m37s
CI / publish (push) Skipped
feat(backup): restore from Settings → Backups, with restoring mode (#5816, #5817)
#5816 "Restoring mode": while a backup goes back in, the scheduler holds,
ingest and HTMX answer 503 with Retry-After (agents keep their samples),
and every page says what is happening. A safety backup of Steward as it
stands is made first, to /data/backups and the bucket. The backup's key
is put in place, the restore audited, and Steward exits with code 3 so
Swarm, compose or systemd starts it fresh. A failed in-place restore
changes nothing and says why on Backups; a full replace that fails after
the wipe restarts into first-run setup.

#5817 "Settings → Backups: Restore": a Restore button on each sent backup
and on the file card. The chosen backup is checked (the same comparison
as Check, now a shared partial) and held server-side for an hour under a
token; it starts only once the admin types "restore", and an older
backup also needs "Replace everything" ticked. A newer backup, or one
whose key can't open its secrets, gets no button.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-15 11:03:01 -04:00

59 lines
3.3 KiB
HTML

{# settings/backup_restore.html — restoring a backup, before anything changes
(#5817): what it would bring back (the same check as Check, #5823), what a
restore does, then a typed confirmation. A backup that can't go in says
why and offers no button. #}
{% extends "base.html" %}
{% from "_macros.html" import crumbs %}
{% from "settings/_tabs.html" import settings_tabs %}
{% block title %}Settings — Backups — Restore — Steward{% endblock %}
{% block section_tabs %}{{ settings_tabs("backups") }}{% endblock %}
{% block breadcrumb %}{{ crumbs([("Settings", "/settings/"), ("Backups", "/settings/backups/"), ("Restore " ~ (name or "a backup"), "")]) }}{% endblock %}
{% block content %}
{% if not report %}
<p class="page-verdict"><span class="tone-crit">{{ name or "The backup" }} can't be restored. {{ error }}</span></p>
{% else %}
{% include "settings/_backup_report.html" %}
<div class="card" style="max-width:640px;margin-top:1.25rem;">
<h2 class="section-title" style="margin-bottom:0.5rem;">Restore {{ name }}</h2>
{% if report.schema == "newer" %}
<p class="tone-crit">This Steward is older than the one that made the backup. Update Steward, then restore it.</p>
{% elif not report.ok %}
<p class="tone-crit">Its key doesn't open all of its secrets, so passwords and keys wouldn't come back. Restore a
different backup.</p>
{% else %}
<ul class="field-hint" style="margin:0 0 1rem 1.1rem;line-height:1.6;">
<li>A safety backup of Steward as it stands is made first, on <code>/data/backups</code> and the bucket, so
this can be undone the same way.</li>
{% if report.schema == "same" %}
<li>Settings, users, hosts, monitors, rules and dashboards become the backup's. The audit log and the history
of what is still here stay; history of anything the backup doesn't have goes with it.</li>
<li>If anything goes wrong part-way, nothing changes.</li>
{% else %}
<li><span class="tone-warn">The backup is from an older Steward, so it replaces everything</span>: the audit
log, alert history, metrics and check results go too, and Steward brings the backup up to date as it
starts. If this fails part-way, Steward starts empty, at first-run setup, where the safety backup can be
uploaded.</li>
{% endif %}
<li>While it runs, pages say Steward is restoring and agents hold their readings. Steward then restarts, and
you sign in again as a user from the backup.</li>
</ul>
{% if error %}<p class="tone-crit" style="margin-bottom:0.75rem;">{{ error }}</p>{% endif %}
<form method="post" action="/settings/backups/restore/start/">{{ csrf_field() }}
<input type="hidden" name="token" value="{{ held.token }}">
{% if report.schema == "older" %}
<label style="display:flex;align-items:center;gap:0.4rem;font-weight:normal;margin-bottom:0.75rem;">
<input type="checkbox" name="replace_all"> Replace everything, history included</label>
{% endif %}
<div class="form-group" style="max-width:260px;">
<label for="r-confirm">Type <code>{{ word }}</code> to confirm</label>
<input type="text" id="r-confirm" name="confirm" autocomplete="off" required>
</div>
<button type="submit" class="btn btn-danger">Restore</button>
</form>
{% endif %}
</div>
{% endif %}
<p style="margin-top:1rem;"><a href="/settings/backups/">Back to Backups</a></p>
{% endblock %}