diff --git a/README.md b/README.md index abc1a21..0b3f406 100644 --- a/README.md +++ b/README.md @@ -95,7 +95,7 @@ vaultik [--config ] config init vaultik [--config ] config edit vaultik [--config ] config get vaultik [--config ] config set -vaultik [--config ] snapshot create [snapshot-names...] [--cron] [--prune] [--keep-newer-than ] [--skip-errors] +vaultik [--config ] snapshot create [snapshot-names...] [--cron] [--prune] [--keep-newer-than ] vaultik [--config ] snapshot list [--json] vaultik [--config ] snapshot verify [--deep] [--json] vaultik [--config ] snapshot purge [--keep-latest | --older-than ] [--snapshot ...] [--force] @@ -117,7 +117,8 @@ vaultik version * `--config `: Path to config file (default: `$VAULTIK_CONFIG`, then platform config dir, then `/etc/vaultik/config.yml`) * `--verbose`, `-v`: Enable verbose output * `--debug`: Enable debug output -* `--quiet`, `-q`: Suppress non-error output +* `--quiet`, `-q`: Suppress non-error output (also suppresses startup banner) +* `--skip-errors`: Continue past per-file errors instead of aborting (applies to `snapshot create` and `restore`) ### environment variables @@ -173,7 +174,6 @@ in the file are preserved; intermediate maps are created as needed. snapshot per name; use `--keep-newer-than` for a rolling window. * `--keep-newer-than `: With `--prune`, keep snapshots newer than this duration instead of only the latest (e.g. `4w`, `30d`, `6mo`, `1y`) -* `--skip-errors`: Skip file read errors (log them loudly but continue) **`snapshot list`**: List all snapshots with their timestamps and sizes. * `--json`: Output in JSON format diff --git a/internal/cli/restore.go b/internal/cli/restore.go index 4b3012a..dca4ebd 100644 --- a/internal/cli/restore.go +++ b/internal/cli/restore.go @@ -127,6 +127,7 @@ func buildRestoreInvokes(snapshotID string, opts *RestoreOptions) []fx.Option { TargetDir: opts.TargetDir, Paths: opts.Paths, Verify: opts.Verify, + SkipErrors: GetRootFlags().SkipErrors, } if err := app.Vaultik.Restore(restoreOpts); err != nil { if err != context.Canceled { diff --git a/internal/cli/root.go b/internal/cli/root.go index 7fc5a03..267f97d 100644 --- a/internal/cli/root.go +++ b/internal/cli/root.go @@ -45,6 +45,7 @@ type RootFlags struct { Verbose bool Debug bool Quiet bool + SkipErrors bool } var rootFlags RootFlags @@ -84,6 +85,7 @@ on the source system.`, cmd.PersistentFlags().BoolVarP(&rootFlags.Verbose, "verbose", "v", false, "Enable verbose output") cmd.PersistentFlags().BoolVar(&rootFlags.Debug, "debug", false, "Enable debug output") cmd.PersistentFlags().BoolVarP(&rootFlags.Quiet, "quiet", "q", false, "Suppress non-error output") + cmd.PersistentFlags().BoolVar(&rootFlags.SkipErrors, "skip-errors", false, "Continue past per-file errors instead of aborting (applies to snapshot create and restore)") // Add subcommands cmd.AddCommand( diff --git a/internal/cli/snapshot.go b/internal/cli/snapshot.go index e34cd4c..e91cff4 100644 --- a/internal/cli/snapshot.go +++ b/internal/cli/snapshot.go @@ -49,6 +49,8 @@ specifying a path using --config or by setting VAULTIK_CONFIG to a path.`, RunE: func(cmd *cobra.Command, args []string) error { // Pass snapshot names from args opts.Snapshots = args + // --skip-errors is a global flag on the root command. + opts.SkipErrors = rootFlags.SkipErrors // Use unified config resolution configPath, err := ResolveConfigPath() if err != nil { @@ -103,7 +105,6 @@ specifying a path using --config or by setting VAULTIK_CONFIG to a path.`, cmd.Flags().BoolVar(&opts.Cron, "cron", false, "Run in cron mode (silent unless error)") cmd.Flags().BoolVar(&opts.Prune, "prune", false, "After backup, drop older snapshots of the same name and remove orphaned blobs") cmd.Flags().StringVar(&opts.KeepNewerThan, "keep-newer-than", "", "With --prune: keep snapshots newer than this duration (e.g. 4w, 30d, 6mo) instead of only the latest") - cmd.Flags().BoolVar(&opts.SkipErrors, "skip-errors", false, "Skip file read errors (log them loudly but continue)") return cmd } diff --git a/internal/vaultik/restore.go b/internal/vaultik/restore.go index 272b1fe..76f5e03 100644 --- a/internal/vaultik/restore.go +++ b/internal/vaultik/restore.go @@ -35,6 +35,7 @@ type RestoreOptions struct { TargetDir string Paths []string // Optional paths to restore (empty = all) Verify bool // Verify restored files by checking chunk hashes + SkipErrors bool // Continue past file-restore errors instead of aborting } // RestoreResult contains statistics from a restore operation @@ -195,6 +196,10 @@ func (v *Vaultik) restoreAllFiles( if err := v.restoreFile(v.ctx, repos, file, opts.TargetDir, identity, chunkToBlobMap, blobCache, result); err != nil { log.Error("Failed to restore file", "path", file.Path, "error", err) + if !opts.SkipErrors { + return nil, fmt.Errorf("restoring %s: %w (pass --skip-errors to continue past restore failures)", file.Path, err) + } + v.UI.Error("Failed to restore %s: %v. Skipping (--skip-errors).", v.UI.Path(file.Path.String()), err) result.FilesFailed++ result.FailedFiles = append(result.FailedFiles, file.Path.String()) // Update progress bar even on failure