Delete metadata directories a backup left without a manifest (closes #390)
check / check (push) Waiting to run

A backup that stopped between uploading db.zst.age and its manifest
left that directory under metadata/ on the destination for good:
prune deleted nothing there.

The blob step of prune, which snapshot create --prune also runs, now
deletes each directory under metadata/ that has no manifest.json.zst,
that no local snapshot record claims, and all of whose objects are
older than prune_grace_period; an object listed with no time keeps its
directory, and only names that are remote keys are deleted. The prompt
and the --force output name each such directory, the --json result
counts them (metadata_dirs_deleted, metadata_dirs_failed), and a failed
delete fails the run. Every description of the grace period now
covers these directories.

Model: opus-5-5
This commit is contained in:
2026-10-15 00:53:18 +00:00
parent e9b17cc5fd
commit 1b6b0b7199
15 changed files with 570 additions and 122 deletions
+9 -7
View File
@@ -239,13 +239,15 @@ storage_url: ""
# Default: 3
# compression_level: 3
# How old a blob that no snapshot lists must be before prune deletes it.
# A backup uploads its blobs before the manifest that lists them, so on a
# destination shared between hosts or users this must be longer than the
# longest backup run. It does not protect a blob a backup reuses from an
# earlier run that stopped before its manifest: that blob keeps the time of
# its first upload, so a prune can delete it.
# Accepts: 36h, 90m, etc. 0s deletes them at once.
# How old a blob that no snapshot lists must be before prune deletes it,
# and how old every object in a directory under metadata/ with no manifest
# must be before prune deletes that directory. A backup uploads its blobs
# and its db.zst.age before its manifest, so on a destination shared
# between hosts or users this must be longer than the longest backup run.
# It does not protect a blob a backup reuses from an earlier run that
# stopped before its manifest: that blob keeps the time of its first
# upload, so a prune can delete it.
# Accepts: 36h, 90m, etc. 0s deletes such blobs and directories at once.
# Default: 24h
# prune_grace_period: 24h
+7 -2
View File
@@ -13,8 +13,9 @@ func NewPruneCommand() *cobra.Command {
cmd := &cobra.Command{
Use: "prune",
Short: "Tidy local database and remote storage",
Long: `Removes orphaned data from both the local index database and
unreferenced blobs from the backup destination store.
Long: `Removes orphaned data from the local index database, and
unreferenced blobs and directories under metadata/ with no manifest
from the backup destination store.
Local cleanup drops incomplete snapshots and any files, chunks, or
blobs no longer referenced by a completed snapshot. Remote cleanup
@@ -26,6 +27,10 @@ another sharing the destination store, that has not yet uploaded its
manifest. The grace period does not protect a blob such a backup
reuses from an earlier run that stopped before its manifest: that
blob keeps the time of its first upload, so it can be deleted.
Remote cleanup also deletes each directory under metadata/ with no
manifest, as a backup that stopped before its manifest leaves, once
all its objects are older than prune_grace_period and its snapshot is
not in the local index.
Snapshot create --prune runs the same cleanup automatically; this
command is the manual entry point for the same work (e.g. after a
+6 -4
View File
@@ -132,10 +132,12 @@ on Linux), then /etc/vaultik/config.yml.`,
cmd.Flags().BoolVar(&opts.Cron, "cron", false,
"Run in cron mode (silent unless warning or error)")
cmd.Flags().BoolVar(&opts.Prune, "prune", false,
"After backup, drop older snapshots of the same name and remove "+
"orphaned blobs older than prune_grace_period (not after a "+
"backup that skipped unreadable files or found a configured "+
"path empty)")
"After backup, drop older snapshots of the same name, remove "+
"orphaned blobs older than prune_grace_period, and delete each "+
"directory under metadata/ with no manifest once all its "+
"objects are older than prune_grace_period and its snapshot is "+
"not in the local index (not after a backup that skipped "+
"unreadable files or found a configured path empty)")
cmd.Flags().StringVar(&opts.KeepNewerThan, "keep-newer-than", "",
"With --prune: keep snapshots newer than this duration "+
"(e.g. 30d, 4w, 6mo, 1y; m is minutes, mo is months) "+