Files
vaultik/internal/cli/snapshot_restore.go
T
clawbot 2bf51d65e3
check / check (push) Failing after 19m7s
Fix nine review findings in the documents and help text
- snapshot verify and snapshot restore also list the destination for a
  snapshot ID that is not in the local index: README, REPOSTRUCTURE,
  ARCHITECTURE, DATAMODEL and the two commands' help.
- prune on a separate machine deletes only unused data and does not
  remove old snapshots.
- The --quiet usage string names everything the flag leaves out.
- A warning written as a log record stays on stderr.
- config set on a config that does not load refuses three things.
- No test is held back by a build tag.
- A plain docker build . adds no -dirty.
- The backup's order of work records directories.
- TODO.md says what parallel downloads would speed up and names
  restorePlan and restoreSweeper.

Model: fable-5-1
2026-11-11 07:11:05 +00:00

120 lines
3.8 KiB
Go

package cli
import (
"github.com/spf13/cobra"
"sneak.berlin/go/vaultik/internal/log"
"sneak.berlin/go/vaultik/internal/vaultik"
)
// restoreMinArgs is the minimum positional argument count of
// `snapshot restore <snapshot-id> <target-dir> [paths...]`.
const restoreMinArgs = 2
// RestoreOptions contains options for the restore command
type RestoreOptions struct {
TargetDir string
Paths []string // Optional paths to restore (empty = all)
Verify bool // Verify restored files after restore
}
// newSnapshotRestoreCommand creates the 'snapshot restore' subcommand
func newSnapshotRestoreCommand() *cobra.Command {
opts := &RestoreOptions{}
cmd := &cobra.Command{
Use: "restore <snapshot-id> <target-dir> [paths...]",
Short: "Restore files from a snapshot",
Long: `Download and decrypt files from a backup snapshot.
This command will restore files from the specified snapshot to the
target directory.
If no paths are specified, all files are restored.
If paths are specified, only matching files/directories are restored.
A path that matches nothing in the snapshot is named in a warning, and
the command exits non-zero after restoring the paths that match.
The snapshot may be named by its ID or, when restoring on a host with no
local index, by the remote key that 'snapshot list' prints for a
remote-only snapshot (an unambiguous leading part is enough).
Each file is restored under its original absolute path inside the target
directory. File owners are restored only when the command runs as root.
Requires the age private key, in the VAULTIK_AGE_SECRET_KEY environment
variable or as age_secret_key in the config file; the environment
variable wins. The variable may hold the whole age-keygen file, with its
comments and all of its keys. Read the key from the file rather than
typing it, so that it does not land in your shell history:
export VAULTIK_AGE_SECRET_KEY="$(cat vaultik_backup_private_key.txt)"
Also needs permission to read the backup destination store (and to list
it when given a remote key, or a snapshot ID that is not in the local
index), and free space in the temporary directory for the decrypted
blobs.
Examples:
# Restore entire snapshot
vaultik snapshot restore myhost_docs_2025-01-01T12:00:00Z /restore
# Restore specific file
vaultik snapshot restore myhost_docs_2025-01-01T12:00:00Z /restore \
/home/user/important.txt
# Restore specific directory
vaultik snapshot restore myhost_docs_2025-01-01T12:00:00Z /restore \
/home/user/documents/
# Restore and verify all files
vaultik snapshot restore --verify myhost_docs_2025-01-01T12:00:00Z /restore`,
Args: cobra.MinimumNArgs(restoreMinArgs),
RunE: func(cmd *cobra.Command, args []string) error {
return runRestore(cmd, args, opts)
},
}
cmd.Flags().BoolVar(&opts.Verify, "verify", false,
"Verify restored files by checking chunk hashes")
return cmd
}
// runRestore parses arguments and runs the restore operation through the
// app framework.
func runRestore(cmd *cobra.Command, args []string, opts *RestoreOptions) error {
snapshotID := args[0]
opts.TargetDir = args[1]
if len(args) > restoreMinArgs {
opts.Paths = args[restoreMinArgs:]
}
configPath, err := ResolveConfigPath()
if err != nil {
return err
}
rootFlags := GetRootFlags()
return RunOperation(cmd.Context(), AppOptions{
ConfigPath: configPath,
LogOptions: log.Options{
Verbose: rootFlags.Verbose,
Debug: rootFlags.Debug,
Quiet: rootFlags.Quiet,
},
Mode: readOnly,
}, func(v *vaultik.Vaultik) error {
return v.Restore(&vaultik.RestoreOptions{
SnapshotID: snapshotID,
TargetDir: opts.TargetDir,
Paths: opts.Paths,
Verify: opts.Verify,
SkipErrors: rootFlags.SkipErrors,
})
}, func(err error) {
log.Info("Restore operation failed", "error", err)
ReportErrorf("Restore failed: %v", err)
})
}