Files
vaultik/internal/cli/prune.go
T
sneak 782cd17076
check / check (pull_request) Successful in 1m21s
Scope the PID lock to mutating commands (closes #150)
RunWithApp took the process-wide PID lock for every fx-backed command,
so read-only commands (info, snapshot list, snapshot verify, remote
info) failed with "already running" while a backup held it.

AppOptions now carries a lockMode, and each command declares its mode at
the call site. Only mutating commands (snapshot create, snapshot purge,
snapshot remove, prune, remote nuke) acquire the lock; read-only ones run
without it and are never blocked. snapshot restore is classified
read-only: it writes only to its target directory, not the local index
or remote store. The acquire/skip decision moves to a small
acquireLockIfMutating helper, covered by a test that a read-only command
runs while the lock is held and two mutators still exclude. The README
locking section is rewritten to match.

Model: opus-4-8
2026-09-22 08:42:57 +00:00

66 lines
1.8 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.
Snapshot create --prune and snapshot remove run 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).`,
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) {
if opts.JSON {
return
}
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")
return cmd
}