Refuse a backup destination an unplugged volume left empty (closes #239)
check / check (push) Waiting to run

Every backup now writes vaultik-destination.txt at the root of the
backup destination store. While the local index holds snapshots, a
destination without it is not present: snapshot create refuses before
writing, prune and snapshot purge fail, and snapshot list and snapshot
remove warn, as for a destination that cannot be listed. remote nuke
deletes the marker last. The file:// backend no longer creates its
destination directory and checks for it on every write.

Tests now back up into existing directories, and fixtures standing for
a written destination carry the marker.

Judgement call: the marker is rewritten on every backup.
Deviation: for the complexity limit, snapshot name selection left CreateSnapshot and the --force/--json test moved into confirmRemoveSnapshot, both unchanged.

Model: opus-5-5
This commit is contained in:
2026-10-13 04:27:54 +00:00
parent 167c0a0746
commit a8d209475b
36 changed files with 564 additions and 102 deletions
+10 -6
View File
@@ -98,10 +98,11 @@ func TestEntryPruneJSONStdoutIsExactlyOneDocument(t *testing.T) {
}
}
// writeHermeticPruneConfig builds a config over a temp directory with an
// empty destination directory and, if seedStale is set, creates the
// index database up front with one snapshot record that has no
// counterpart on the destination store. Returns the config path.
// writeHermeticPruneConfig builds a config over a temp directory with a
// destination directory that holds only the marker a backup writes and,
// if seedStale is set, creates the index database up front with one
// snapshot record that has no counterpart on the destination store.
// Returns the config path.
func writeHermeticPruneConfig(t *testing.T, seedStale bool) string {
t.Helper()
@@ -118,9 +119,12 @@ func writeHermeticPruneConfig(t *testing.T, seedStale bool) string {
require.NoError(t,
os.WriteFile(configPath, []byte(contents), configFileMode))
// prune fails on a destination directory that does not exist, so the
// empty store is created here rather than left to a first backup.
// prune fails on a destination directory that does not exist, and on
// one without the marker a backup writes while the local index holds
// a snapshot, so both are created here as a first backup leaves them.
require.NoError(t, os.Mkdir(storeDir, 0o750))
require.NoError(t, os.WriteFile(
filepath.Join(storeDir, "vaultik-destination.txt"), nil, 0o600))
// The PID lock lives under xdg.DataHome, which xdg resolves at
// package init; point it at the temp dir so the test neither