Remove old config copies from updates

Settings -> Upkeep -> Backup & restore lists the config.json.bak-* files
that update leaves behind and removes one or all of them; they hold the
same secrets as a backup. Removing needs a signed-in user and is logged.
After a successful update only the newest 3 copies are kept, and a copy
that would overwrite an older one (version unknown, or the same version
twice) gets the time appended. The backup card now also names preshared
keys and authenticator app secrets.

The update notice uses the existing compareVersions instead of its own.
This commit is contained in:
Daniel Redetzke
2026-10-05 12:34:22 +03:00
parent fc85da2407
commit 7ad72472ea
8 changed files with 264 additions and 33 deletions
+65
View File
@@ -0,0 +1,65 @@
package main
import (
"os"
"path/filepath"
"strings"
"testing"
"time"
)
func TestUpdateBackups(t *testing.T) {
dir := t.TempDir()
cfg := filepath.Join(dir, "config.json")
_ = os.WriteFile(cfg, []byte("{}"), 0o600)
now := time.Date(2026, 10, 5, 12, 9, 0, 0, time.UTC)
// A second copy of the same version gets the time appended instead of
// overwriting the first.
first := newUpdateBackupPath(cfg, "unknown", now)
if filepath.Base(first) != "config.json.bak-unknown" {
t.Fatalf("first copy: %s", first)
}
_ = os.WriteFile(first, []byte("{}"), 0o600)
second := newUpdateBackupPath(cfg, "unknown", now)
if filepath.Base(second) != "config.json.bak-unknown-20261005-1209" {
t.Fatalf("second copy: %s", second)
}
_ = os.WriteFile(second, []byte("{}"), 0o600)
for i, v := range []string{"v0.2.0", "v0.3.0", "v0.4.0"} {
f := filepath.Join(dir, "config.json.bak-"+v)
_ = os.WriteFile(f, []byte("{}"), 0o600)
_ = os.Chtimes(f, now, now.Add(time.Duration(i+1)*time.Hour))
}
_ = os.Chtimes(first, now, now.Add(-2*time.Hour))
_ = os.Chtimes(second, now, now.Add(-time.Hour))
_ = os.Mkdir(filepath.Join(dir, "config.json.bak-dir"), 0o700) // not a file: ignored
list, err := listUpdateBackups(cfg)
if err != nil {
t.Fatal(err)
}
var got []string
for _, b := range list {
got = append(got, b.Version)
}
if strings.Join(got, " ") != "v0.4.0 v0.3.0 v0.2.0 unknown unknown" {
t.Fatalf("versions, newest first: %v", got)
}
for _, bad := range []string{"config.json", "config.json.bak-", "config.json.bak-dir", "../config.json.bak-v0.4.0", "config.json.bak-v0.4.0/x"} {
if err := removeUpdateBackup(cfg, bad); err == nil {
t.Errorf("removed %q", bad)
}
}
if n, err := pruneUpdateBackups(cfg, keepUpdateBackups); err != nil || n != 2 {
t.Fatalf("prune: %d, %v", n, err)
}
if list, _ = listUpdateBackups(cfg); len(list) != 3 || list[2].Version != "v0.2.0" {
t.Fatalf("after prune: %v", list)
}
if _, err := os.Stat(cfg); err != nil {
t.Fatal("config.json is gone")
}
}