check / check (push) Waiting to run
snapshot verify --help now says --deep needs the age private key, from VAULTIK_AGE_SECRET_KEY or age_secret_key, as snapshot restore does. Both now name the config key as well as the environment variable. The help of snapshot create, list, purge, remove, prune, remote info and remote nuke says which access to the destination it needs. config.example.yml gains age_secret_key, and no longer tells the reader to pipe age-keygen into grep, which discards the private key. Only help strings and comments change. Model: fable-5-1
119 lines
3.8 KiB
Go
119 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 (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)"
|
|
|
|
Also needs permission to read the backup destination store (and to list
|
|
it when a remote key is given), 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.Error("Restore operation failed", "error", err)
|
|
ReportErrorf("Restore failed: %v", err)
|
|
})
|
|
}
|