A backup uploads its blobs before the manifest that lists them, and the process lock covers one user on one machine, so a prune from another host or user sharing the destination deleted a running backup's blobs. Prune now deletes an unreferenced blob only once its listed time is older than the new config key prune_grace_period (default 24h; a negative value is refused). A newer blob, or one listed with no time, is kept and counted (blobs_too_new). The rclone backend takes each object's time from the listing (use_server_modtime). Decision: a blob a backup reuses from an earlier run that stopped before its manifest keeps its old time and is not protected; the docs and config comments say so. Model: opus-5-5
70 lines
2.3 KiB
Go
70 lines
2.3 KiB
Go
package cli
|
|
|
|
import (
|
|
"github.com/spf13/cobra"
|
|
"sneak.berlin/go/vaultik/internal/log"
|
|
"sneak.berlin/go/vaultik/internal/vaultik"
|
|
)
|
|
|
|
// NewPruneCommand creates the prune command
|
|
func NewPruneCommand() *cobra.Command {
|
|
opts := &vaultik.PruneOptions{}
|
|
|
|
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.
|
|
|
|
Local cleanup drops incomplete snapshots and any files, chunks, or
|
|
blobs no longer referenced by a completed snapshot. Remote cleanup
|
|
scans every snapshot manifest in the destination store, builds the
|
|
set of still-referenced blob hashes, and deletes any blob not in that
|
|
set that is older than prune_grace_period (default 24h). A newer one
|
|
is kept: it may belong to a backup still running, on this host or
|
|
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.
|
|
|
|
Snapshot create --prune runs the same cleanup automatically; this
|
|
command is the manual entry point for the same work (e.g. after a
|
|
crashed backup or to reclaim storage). Snapshot remove leaves blobs in
|
|
place; run this command afterwards to delete the ones no longer
|
|
referenced.`,
|
|
Args: cobra.NoArgs,
|
|
RunE: func(cmd *cobra.Command, _ []string) error {
|
|
// Use unified config resolution
|
|
configPath, err := ResolveConfigPath()
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
// Use the app framework like other commands
|
|
rootFlags := GetRootFlags()
|
|
|
|
return RunOperation(cmd.Context(), AppOptions{
|
|
ConfigPath: configPath,
|
|
LogOptions: log.Options{
|
|
Verbose: rootFlags.Verbose,
|
|
Debug: rootFlags.Debug,
|
|
Quiet: rootFlags.Quiet,
|
|
JSON: opts.JSON,
|
|
},
|
|
Mode: mutating,
|
|
}, func(v *vaultik.Vaultik) error {
|
|
return v.Prune(opts)
|
|
}, func(err error) {
|
|
log.Error("Prune operation failed", "error", err)
|
|
ReportErrorf("Prune failed: %v", err)
|
|
})
|
|
},
|
|
}
|
|
|
|
cmd.Flags().BoolVar(&opts.Force, "force", false, "Skip confirmation prompt")
|
|
cmd.Flags().BoolVar(&opts.JSON, "json", false,
|
|
"Output pruning stats as JSON; skips the confirmation prompt, as --force does")
|
|
|
|
return cmd
|
|
}
|