check / check (push) Waiting to run
snapshot restore said nothing about a requested path that matched no entry in the snapshot (a typo, a relative path, or a path through a configured snapshot path that is a symlink, whose entries are stored under the resolved directory) and exited 0, also when nothing matched at all. Each such path is now named in a warning, and restore returns an error after restoring the paths that do match. Making a path through a symlink match is not part of this change. Judgement call: when files also fail to restore, that error is the one returned; the unmatched paths are still named. Model: opus-5-5
111 lines
3.4 KiB
Go
111 lines
3.4 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).
|
|
|
|
Requires the age private key in the VAULTIK_AGE_SECRET_KEY environment
|
|
variable. The variable may hold the whole age-keygen file (comments and
|
|
all of its identities are accepted); read it from the file rather than
|
|
typing the key, so it does not land in your shell history:
|
|
|
|
export VAULTIK_AGE_SECRET_KEY="$(cat vaultik_backup_private_key.txt)"
|
|
|
|
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.Error("Restore operation failed", "error", err)
|
|
ReportErrorf("Restore failed: %v", err)
|
|
})
|
|
}
|