restore
This commit is contained in:
@@ -103,6 +103,34 @@ func main() {
|
|||||||
os.Exit(1)
|
os.Exit(1)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
case "mount":
|
||||||
|
// Mount a restored dataset to access files
|
||||||
|
if len(os.Args) < 3 {
|
||||||
|
fmt.Println("Usage: zfs-restore mount <dataset> [mountpoint]")
|
||||||
|
fmt.Println("\nExamples:")
|
||||||
|
fmt.Println(" zfs-restore mount tank/restored /mnt/recover")
|
||||||
|
fmt.Println(" zfs-restore mount tank/restored # interactive")
|
||||||
|
os.Exit(1)
|
||||||
|
}
|
||||||
|
|
||||||
|
dataset := os.Args[2]
|
||||||
|
mountpoint := ""
|
||||||
|
|
||||||
|
if len(os.Args) > 3 {
|
||||||
|
mountpoint = os.Args[3]
|
||||||
|
} else {
|
||||||
|
fmt.Printf("Mountpoint [/mnt/recover]: ")
|
||||||
|
fmt.Scanln(&mountpoint)
|
||||||
|
if mountpoint == "" {
|
||||||
|
mountpoint = "/mnt/recover"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if err := client.MountDataset(dataset, mountpoint); err != nil {
|
||||||
|
fmt.Printf("Error: %v\n", err)
|
||||||
|
os.Exit(1)
|
||||||
|
}
|
||||||
|
|
||||||
case "help", "-h", "--help":
|
case "help", "-h", "--help":
|
||||||
printUsage()
|
printUsage()
|
||||||
|
|
||||||
@@ -119,11 +147,13 @@ func printUsage() {
|
|||||||
fmt.Println("\nCommands:")
|
fmt.Println("\nCommands:")
|
||||||
fmt.Println(" list - List available snapshots")
|
fmt.Println(" list - List available snapshots")
|
||||||
fmt.Println(" restore <#|latest> <dataset> [--force] - Restore snapshot to ZFS")
|
fmt.Println(" restore <#|latest> <dataset> [--force] - Restore snapshot to ZFS")
|
||||||
|
fmt.Println(" mount <dataset> [mountpoint] - Mount dataset to recover files")
|
||||||
fmt.Println(" help - Show this help message")
|
fmt.Println(" help - Show this help message")
|
||||||
fmt.Println("\nQuick Examples:")
|
fmt.Println("\nQuick Examples:")
|
||||||
fmt.Println(" zfs-restore list - See available backups")
|
fmt.Println(" zfs-restore list - See available backups")
|
||||||
fmt.Println(" zfs-restore restore latest tank/data - Restore most recent backup")
|
fmt.Println(" zfs-restore restore latest tank/data - Restore most recent backup")
|
||||||
fmt.Println(" zfs-restore restore 1 tank/restored - Restore snapshot #1")
|
fmt.Println(" zfs-restore restore 1 tank/restored - Restore snapshot #1")
|
||||||
|
fmt.Println(" zfs-restore mount tank/restored /mnt - Mount to recover files")
|
||||||
fmt.Println("\nEnvironment Variables (can be set in .env file):")
|
fmt.Println("\nEnvironment Variables (can be set in .env file):")
|
||||||
fmt.Println(" CLIENT_ID - Client identifier (default: client1)")
|
fmt.Println(" CLIENT_ID - Client identifier (default: client1)")
|
||||||
fmt.Println(" API_KEY - API key for authentication (default: secret123)")
|
fmt.Println(" API_KEY - API key for authentication (default: secret123)")
|
||||||
|
|||||||
@@ -148,6 +148,26 @@ func (c *Client) RestoreSnapshot(snapshot *SnapshotMetadata, targetDataset strin
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// If force is used, we need to handle carefully to avoid data loss
|
||||||
|
// First, let's try to download - only destroy if download succeeds
|
||||||
|
var originalExists bool
|
||||||
|
if force {
|
||||||
|
if _, err := zfs.GetDataset(targetDataset); err == nil {
|
||||||
|
originalExists = true
|
||||||
|
fmt.Printf("→ Target dataset exists, will overwrite\n")
|
||||||
|
} else {
|
||||||
|
originalExists = false
|
||||||
|
fmt.Printf("→ Target dataset does not exist, will create new\n")
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
// Check if target dataset exists
|
||||||
|
if !force {
|
||||||
|
if _, err := zfs.GetDataset(targetDataset); err == nil {
|
||||||
|
return fmt.Errorf("target dataset %s already exists. Use --force to overwrite", targetDataset)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// Request download from server
|
// Request download from server
|
||||||
downloadURL := fmt.Sprintf("%s/download?client_id=%s&api_key=%s&snapshot_id=%s",
|
downloadURL := fmt.Sprintf("%s/download?client_id=%s&api_key=%s&snapshot_id=%s",
|
||||||
c.ServerURL, c.ClientID, c.APIKey, snapshot.SnapshotID)
|
c.ServerURL, c.ClientID, c.APIKey, snapshot.SnapshotID)
|
||||||
@@ -165,6 +185,18 @@ func (c *Client) RestoreSnapshot(snapshot *SnapshotMetadata, targetDataset strin
|
|||||||
return fmt.Errorf("download failed: %s", body)
|
return fmt.Errorf("download failed: %s", body)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Download succeeded - now safe to destroy if needed
|
||||||
|
if force && originalExists {
|
||||||
|
fmt.Printf("→ Destroying existing dataset %s...\n", targetDataset)
|
||||||
|
cmd := exec.Command("zfs", "destroy", "-r", targetDataset)
|
||||||
|
output, err := cmd.CombinedOutput()
|
||||||
|
if err != nil {
|
||||||
|
fmt.Printf(" Destroy output: %s\n", string(output))
|
||||||
|
return fmt.Errorf("failed to destroy existing dataset: %v", err)
|
||||||
|
}
|
||||||
|
fmt.Printf(" Destroyed successfully\n")
|
||||||
|
}
|
||||||
|
|
||||||
// Create decompression reader if needed
|
// Create decompression reader if needed
|
||||||
var reader io.Reader = resp.Body
|
var reader io.Reader = resp.Body
|
||||||
if snapshot.Compressed {
|
if snapshot.Compressed {
|
||||||
@@ -194,6 +226,13 @@ func (c *Client) RestoreSnapshot(snapshot *SnapshotMetadata, targetDataset strin
|
|||||||
fmt.Printf("\n✓ Snapshot restored successfully!\n")
|
fmt.Printf("\n✓ Snapshot restored successfully!\n")
|
||||||
fmt.Printf(" Dataset: %s\n", targetDataset)
|
fmt.Printf(" Dataset: %s\n", targetDataset)
|
||||||
|
|
||||||
|
// Verify the dataset exists after restore
|
||||||
|
if _, err := zfs.GetDataset(targetDataset); err == nil {
|
||||||
|
fmt.Printf(" Verified: dataset exists\n")
|
||||||
|
} else {
|
||||||
|
fmt.Printf(" Warning: could not verify dataset exists: %v\n", err)
|
||||||
|
}
|
||||||
|
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -242,15 +281,22 @@ func (c *Client) RestoreToFile(snapshot *SnapshotMetadata, outputFile string) er
|
|||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// MountSnapshot mounts a restored dataset to a specified mountpoint.
|
// MountDataset mounts a restored dataset to a specified mountpoint for file recovery.
|
||||||
// This allows browsing the restored files.
|
func (c *Client) MountDataset(dataset, mountpoint string) error {
|
||||||
func (c *Client) MountSnapshot(dataset, mountpoint string) error {
|
fmt.Printf("\n=== Mounting Dataset ===\n")
|
||||||
|
fmt.Printf("Dataset: %s\n", dataset)
|
||||||
|
fmt.Printf("Mountpoint: %s\n\n", mountpoint)
|
||||||
|
|
||||||
ds, err := zfs.GetDataset(dataset)
|
ds, err := zfs.GetDataset(dataset)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return fmt.Errorf("dataset not found: %v", err)
|
return fmt.Errorf("dataset not found: %v", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
// Create mountpoint if it doesn't exist
|
// Check current mountpoint
|
||||||
|
currentMP, _ := ds.GetProperty("mountpoint")
|
||||||
|
fmt.Printf("Current mountpoint: %s\n", currentMP)
|
||||||
|
|
||||||
|
// Create mountpoint directory if it doesn't exist
|
||||||
if err := os.MkdirAll(mountpoint, 0755); err != nil {
|
if err := os.MkdirAll(mountpoint, 0755); err != nil {
|
||||||
return fmt.Errorf("failed to create mountpoint: %v", err)
|
return fmt.Errorf("failed to create mountpoint: %v", err)
|
||||||
}
|
}
|
||||||
@@ -260,13 +306,17 @@ func (c *Client) MountSnapshot(dataset, mountpoint string) error {
|
|||||||
return fmt.Errorf("failed to set mountpoint: %v", err)
|
return fmt.Errorf("failed to set mountpoint: %v", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
// Mount the dataset
|
// Mount the dataset if not already mounted
|
||||||
cmd := exec.Command("zfs", "mount", dataset)
|
cmd := exec.Command("zfs", "mount", dataset)
|
||||||
if err := cmd.Run(); err != nil {
|
if err := cmd.Run(); err != nil {
|
||||||
return fmt.Errorf("failed to mount: %v", err)
|
// Might already be mounted, that's OK
|
||||||
|
fmt.Printf(" (dataset may already be mounted)\n")
|
||||||
}
|
}
|
||||||
|
|
||||||
fmt.Printf("✓ Mounted %s at %s\n", dataset, mountpoint)
|
fmt.Printf("\n✓ Mounted successfully!\n")
|
||||||
|
fmt.Printf(" Access files at: %s\n", mountpoint)
|
||||||
|
fmt.Printf(" When done, run: umount %s\n", mountpoint)
|
||||||
|
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user