mirror of
https://github.com/kernelkit/infix.git
synced 2026-07-22 01:13:00 +02:00
webui: add automatic .cfg migration when restoring a backup
Also adjust some card layout in both Backup and Restore cards. Signed-off-by: Joachim Wiberg <troglobit@gmail.com>
This commit is contained in:
@@ -273,6 +273,49 @@ func (h *SystemHandler) SupportBundle(w http.ResponseWriter, r *http.Request) {
|
||||
// target="running" (default): PUT to running so changes take effect immediately;
|
||||
// sets the cfg-unsaved cookie so the persistent notification prompts a save.
|
||||
// target="startup": PUT to startup only; reboot required to apply.
|
||||
// migrateConfig runs an uploaded .cfg backup through the on-target migrate(1)
|
||||
// tool so a config saved by an older release is brought up to the running
|
||||
// syntax before it is applied. The config is written to a temp file and
|
||||
// migrated in place — migrate only reads stdin from a TTY, so the file path is
|
||||
// the reliable interface. A config already at the current version comes back
|
||||
// unchanged. An error carrying migrate's stderr is returned for a config newer
|
||||
// than the system supports (downgrade) or any other migrate failure.
|
||||
func migrateConfig(ctx context.Context, raw []byte) ([]byte, error) {
|
||||
f, err := os.CreateTemp("", "restore-*.cfg")
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
tmp := f.Name()
|
||||
defer os.Remove(tmp)
|
||||
|
||||
if _, err := f.Write(raw); err != nil {
|
||||
f.Close()
|
||||
return nil, err
|
||||
}
|
||||
if err := f.Close(); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
var stderr bytes.Buffer
|
||||
cmd := exec.CommandContext(ctx, "/usr/sbin/migrate", "-i", "-e", tmp)
|
||||
cmd.Stderr = &stderr
|
||||
if err := cmd.Run(); err != nil {
|
||||
msg := strings.TrimSpace(stderr.String())
|
||||
msg = strings.ReplaceAll(msg, tmp+": ", "")
|
||||
msg = strings.TrimPrefix(msg, "Error: ")
|
||||
if msg == "" {
|
||||
msg = err.Error()
|
||||
}
|
||||
return nil, fmt.Errorf("%s", msg)
|
||||
}
|
||||
|
||||
if notes := strings.TrimSpace(stderr.String()); notes != "" {
|
||||
log.Printf("restore: migrated uploaded config:\n%s", notes)
|
||||
}
|
||||
|
||||
return os.ReadFile(tmp)
|
||||
}
|
||||
|
||||
func (h *SystemHandler) RestoreConfig(w http.ResponseWriter, r *http.Request) {
|
||||
if err := r.ParseMultipartForm(10 << 20); err != nil {
|
||||
http.Error(w, "bad request", http.StatusBadRequest)
|
||||
@@ -304,12 +347,22 @@ func (h *SystemHandler) RestoreConfig(w http.ResponseWriter, r *http.Request) {
|
||||
return
|
||||
}
|
||||
|
||||
// Bring an older backup up to the running syntax before applying it.
|
||||
migrated, err := migrateConfig(r.Context(), raw)
|
||||
if err != nil {
|
||||
log.Printf("restore: migrate: %v", err)
|
||||
w.Header().Set("Content-Type", "text/html")
|
||||
fmt.Fprintf(w, `<span class="sc-fd-err">Restore failed: %s</span>`,
|
||||
template.HTMLEscapeString(err.Error()))
|
||||
return
|
||||
}
|
||||
|
||||
target := "running"
|
||||
if r.FormValue("save-to-startup") == "on" {
|
||||
target = "startup"
|
||||
}
|
||||
|
||||
if err := h.RC.PutDatastore(r.Context(), target, check); err != nil {
|
||||
if err := h.RC.PutDatastore(r.Context(), target, json.RawMessage(migrated)); err != nil {
|
||||
log.Printf("restore(%s): %v", target, err)
|
||||
w.Header().Set("Content-Type", "text/html")
|
||||
fmt.Fprintf(w, `<span class="sc-fd-err">Restore failed: %s</span>`,
|
||||
|
||||
@@ -2813,6 +2813,19 @@ details.cfg-fold[open] > summary::before { transform: rotate(90deg); }
|
||||
margin-top: 0.75rem;
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
/* Restore card: let the form fill the body so its file selector and checkbox
|
||||
sit under the leading paragraph, with only the submit button anchored at the
|
||||
bottom (aligned with the other action cards). */
|
||||
#restore-form {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
flex: 1;
|
||||
}
|
||||
.sc-form-actions {
|
||||
margin-top: auto;
|
||||
padding-top: 0.75rem;
|
||||
}
|
||||
.sc-restore-warn-text {
|
||||
color: var(--warning-fg);
|
||||
font-size: 0.875rem;
|
||||
|
||||
@@ -9,7 +9,7 @@
|
||||
<div class="card-header">Backup</div>
|
||||
<div class="card-body sc-action-body">
|
||||
<p class="sc-desc">Download the current startup configuration as a JSON file.
|
||||
The file format is <pre>startup-config-$hostname-$date-$time.cfg</pre>
|
||||
The file format is <code>startup-config-$hostname-$date-$time.cfg</code>
|
||||
</p>
|
||||
<div>
|
||||
<a href="/config" hx-boost="false" class="btn btn-primary">Download config</a>
|
||||
@@ -22,6 +22,8 @@
|
||||
<div class="card-body sc-action-body">
|
||||
<p class="sc-desc">Restore a previously backed-up configuration. By default
|
||||
the restore operation is made to running config, this is the safe default.</p>
|
||||
<p class="sc-desc">If the file was saved by an older release, it is migrated to
|
||||
the current configuration syntax before being applied.</p>
|
||||
<form id="restore-form"
|
||||
hx-post="/maintenance/backup/restore"
|
||||
hx-target="#restore-result"
|
||||
@@ -37,7 +39,7 @@
|
||||
<span class="sc-restore-warn-text">Save to startup — may break system boot!</span>
|
||||
</label>
|
||||
<div id="restore-result"></div>
|
||||
<div style="margin-top:0.75rem">
|
||||
<div class="sc-form-actions">
|
||||
<button type="submit" class="btn btn-primary">Restore</button>
|
||||
</div>
|
||||
</form>
|
||||
|
||||
Reference in New Issue
Block a user