server client restore
This commit is contained in:
253
cmd/zfs-client/main.go
Normal file
253
cmd/zfs-client/main.go
Normal file
@@ -0,0 +1,253 @@
|
||||
// Command zfs-client is the CLI tool for creating and uploading ZFS snapshots.
|
||||
// It provides commands for backup, status checking, snapshot rotation, and incremental backups.
|
||||
package main
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"os"
|
||||
|
||||
"git.ma-al.com/goc_marek/zfs/internal/client"
|
||||
)
|
||||
|
||||
func main() {
|
||||
if len(os.Args) < 2 {
|
||||
printUsage()
|
||||
os.Exit(1)
|
||||
}
|
||||
|
||||
// Load configuration from environment and .env file
|
||||
config := client.LoadConfig()
|
||||
c := client.New(config)
|
||||
|
||||
command := os.Args[1]
|
||||
|
||||
switch command {
|
||||
case "backup":
|
||||
// Default: create manual backup (full or incremental)
|
||||
fmt.Println("=== Creating and sending backup ===\n")
|
||||
|
||||
snapshot, err := c.CreateSnapshot()
|
||||
if err != nil {
|
||||
fmt.Printf("Error creating snapshot: %v\n", err)
|
||||
os.Exit(1)
|
||||
}
|
||||
|
||||
if err := c.SendSnapshot(snapshot); err != nil {
|
||||
fmt.Printf("Error sending snapshot: %v\n", err)
|
||||
os.Exit(1)
|
||||
}
|
||||
|
||||
fmt.Println("\n✓ Backup completed successfully!")
|
||||
|
||||
case "backup-full":
|
||||
// Force full backup (no incremental)
|
||||
fmt.Println("=== Creating full backup ===\n")
|
||||
|
||||
snapshot, err := c.CreateSnapshot()
|
||||
if err != nil {
|
||||
fmt.Printf("Error creating snapshot: %v\n", err)
|
||||
os.Exit(1)
|
||||
}
|
||||
|
||||
if err := c.SendIncremental(snapshot, ""); err != nil {
|
||||
fmt.Printf("Error sending snapshot: %v\n", err)
|
||||
os.Exit(1)
|
||||
}
|
||||
|
||||
// Create bookmark for future incremental backups
|
||||
if err := c.CreateBookmark(snapshot); err != nil {
|
||||
fmt.Printf("Warning: failed to create bookmark: %v\n", err)
|
||||
}
|
||||
|
||||
fmt.Println("\n✓ Full backup completed successfully!")
|
||||
|
||||
case "backup-incremental":
|
||||
// Incremental backup from last bookmark
|
||||
fmt.Println("=== Creating incremental backup ===\n")
|
||||
|
||||
// Check for existing bookmark
|
||||
lastBookmark, err := c.GetLastBookmark()
|
||||
if err != nil {
|
||||
fmt.Printf("Error checking bookmarks: %v\n", err)
|
||||
os.Exit(1)
|
||||
}
|
||||
|
||||
if lastBookmark == "" {
|
||||
fmt.Println("No existing bookmark found. Use 'backup-full' for initial backup.")
|
||||
os.Exit(1)
|
||||
}
|
||||
|
||||
snapshot, err := c.CreateSnapshot()
|
||||
if err != nil {
|
||||
fmt.Printf("Error creating snapshot: %v\n", err)
|
||||
os.Exit(1)
|
||||
}
|
||||
|
||||
if err := c.SendIncremental(snapshot, lastBookmark); err != nil {
|
||||
fmt.Printf("Error sending incremental snapshot: %v\n", err)
|
||||
os.Exit(1)
|
||||
}
|
||||
|
||||
// Create bookmark for future incremental backups
|
||||
if err := c.CreateBookmark(snapshot); err != nil {
|
||||
fmt.Printf("Warning: failed to create bookmark: %v\n", err)
|
||||
}
|
||||
|
||||
fmt.Println("\n✓ Incremental backup completed successfully!")
|
||||
|
||||
case "snapshot":
|
||||
// Create typed snapshots (hourly, daily, weekly, monthly)
|
||||
if len(os.Args) < 3 {
|
||||
fmt.Println("Usage: zfs-client snapshot <hourly|daily|weekly|monthly>")
|
||||
os.Exit(1)
|
||||
}
|
||||
|
||||
snapType := client.SnapshotType(os.Args[2])
|
||||
switch snapType {
|
||||
case client.SnapshotHourly, client.SnapshotDaily, client.SnapshotWeekly, client.SnapshotMonthly:
|
||||
// Valid type
|
||||
default:
|
||||
fmt.Printf("Invalid snapshot type: %s\n", snapType)
|
||||
fmt.Println("Valid types: hourly, daily, weekly, monthly")
|
||||
os.Exit(1)
|
||||
}
|
||||
|
||||
fmt.Printf("=== Creating %s snapshot ===\n\n", snapType)
|
||||
|
||||
snapshot, err := c.CreateSnapshotWithType(snapType)
|
||||
if err != nil {
|
||||
fmt.Printf("Error creating snapshot: %v\n", err)
|
||||
os.Exit(1)
|
||||
}
|
||||
|
||||
// Check for existing bookmark for incremental
|
||||
lastBookmark, _ := c.GetLastBookmark()
|
||||
|
||||
if err := c.SendIncremental(snapshot, lastBookmark); err != nil {
|
||||
fmt.Printf("Error sending snapshot: %v\n", err)
|
||||
os.Exit(1)
|
||||
}
|
||||
|
||||
// Create bookmark
|
||||
if err := c.CreateBookmark(snapshot); err != nil {
|
||||
fmt.Printf("Warning: failed to create bookmark: %v\n", err)
|
||||
}
|
||||
|
||||
// Rotate local snapshots using server policy if available
|
||||
policy, err := getRotationPolicy(c)
|
||||
if err != nil {
|
||||
fmt.Printf("Warning: failed to get rotation policy: %v\n", err)
|
||||
policy = client.DefaultPolicy()
|
||||
}
|
||||
if err := c.RotateLocalSnapshots(policy); err != nil {
|
||||
fmt.Printf("Warning: failed to rotate snapshots: %v\n", err)
|
||||
}
|
||||
|
||||
fmt.Printf("\n✓ %s snapshot completed successfully!\n", snapType)
|
||||
|
||||
case "rotate":
|
||||
// Rotate local snapshots using server policy if available
|
||||
fmt.Println("=== Rotating local snapshots ===\n")
|
||||
|
||||
policy, err := getRotationPolicy(c)
|
||||
if err != nil {
|
||||
fmt.Printf("Warning: failed to get rotation policy: %v\n", err)
|
||||
policy = client.DefaultPolicy()
|
||||
}
|
||||
if err := c.RotateLocalSnapshots(policy); err != nil {
|
||||
fmt.Printf("Error rotating snapshots: %v\n", err)
|
||||
os.Exit(1)
|
||||
}
|
||||
|
||||
fmt.Println("\n✓ Rotation completed!")
|
||||
|
||||
case "rotate-remote":
|
||||
// Request server to rotate remote snapshots
|
||||
if err := c.RequestRotation(); err != nil {
|
||||
fmt.Printf("Error requesting rotation: %v\n", err)
|
||||
os.Exit(1)
|
||||
}
|
||||
|
||||
case "status":
|
||||
if err := c.GetStatus(); err != nil {
|
||||
fmt.Printf("Error getting status: %v\n", err)
|
||||
os.Exit(1)
|
||||
}
|
||||
|
||||
case "bookmarks":
|
||||
// List bookmarks
|
||||
fmt.Println("=== ZFS Bookmarks ===\n")
|
||||
|
||||
bookmark, err := c.GetLastBookmark()
|
||||
if err != nil {
|
||||
fmt.Printf("Error: %v\n", err)
|
||||
os.Exit(1)
|
||||
}
|
||||
|
||||
if bookmark == "" {
|
||||
fmt.Println("No bookmarks found")
|
||||
} else {
|
||||
fmt.Printf("Last bookmark: %s\n", bookmark)
|
||||
}
|
||||
|
||||
case "help", "-h", "--help":
|
||||
printUsage()
|
||||
|
||||
default:
|
||||
fmt.Printf("Unknown command: %s\n", command)
|
||||
printUsage()
|
||||
os.Exit(1)
|
||||
}
|
||||
}
|
||||
|
||||
// getRotationPolicy fetches the rotation policy from the server.
|
||||
// If the server has a policy configured, it must be used.
|
||||
// Otherwise, the default policy is returned.
|
||||
func getRotationPolicy(c *client.Client) (*client.SnapshotPolicy, error) {
|
||||
serverPolicy, err := c.GetRotationPolicy()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
if serverPolicy.ServerManaged && serverPolicy.RotationPolicy != nil {
|
||||
fmt.Println(" Using server-managed rotation policy")
|
||||
return serverPolicy.RotationPolicy, nil
|
||||
}
|
||||
|
||||
// No server policy, use default
|
||||
fmt.Println(" Using default rotation policy")
|
||||
return client.DefaultPolicy(), nil
|
||||
}
|
||||
|
||||
func printUsage() {
|
||||
fmt.Println("ZFS Snapshot Backup Client")
|
||||
fmt.Println("\nUsage: zfs-client [command]")
|
||||
fmt.Println("\nCommands:")
|
||||
fmt.Println(" backup - Create snapshot and send (auto incremental if bookmark exists)")
|
||||
fmt.Println(" backup-full - Create full backup (no incremental)")
|
||||
fmt.Println(" backup-incremental - Create incremental backup from last bookmark")
|
||||
fmt.Println(" snapshot <type> - Create typed snapshot (hourly|daily|weekly|monthly)")
|
||||
fmt.Println(" rotate - Rotate local snapshots based on retention policy")
|
||||
fmt.Println(" rotate-remote - Request server to rotate old remote snapshots")
|
||||
fmt.Println(" status - Check server status and quota")
|
||||
fmt.Println(" bookmarks - List ZFS bookmarks")
|
||||
fmt.Println(" help - Show this help message")
|
||||
fmt.Println("\nSnapshot Retention Policy (default):")
|
||||
fmt.Println(" Hourly: 24 snapshots")
|
||||
fmt.Println(" Daily: 7 snapshots")
|
||||
fmt.Println(" Weekly: 4 snapshots")
|
||||
fmt.Println(" Monthly: 12 snapshots")
|
||||
fmt.Println("\nEnvironment Variables (can be set in .env file):")
|
||||
fmt.Println(" CLIENT_ID - Client identifier (default: client1)")
|
||||
fmt.Println(" API_KEY - API key for authentication (default: secret123)")
|
||||
fmt.Println(" SERVER_URL - Backup server URL (default: http://localhost:8080)")
|
||||
fmt.Println(" LOCAL_DATASET - ZFS dataset to backup (default: tank/data)")
|
||||
fmt.Println(" COMPRESS - Enable gzip compression (default: true)")
|
||||
fmt.Println(" STORAGE_TYPE - Storage type: s3 or local (default: s3)")
|
||||
fmt.Println("\nExamples:")
|
||||
fmt.Println(" zfs-client backup")
|
||||
fmt.Println(" zfs-client backup-full")
|
||||
fmt.Println(" zfs-client snapshot hourly")
|
||||
fmt.Println(" zfs-client rotate")
|
||||
fmt.Println(" CLIENT_ID=myclient zfs-client backup")
|
||||
}
|
||||
178
cmd/zfs-restore/main.go
Normal file
178
cmd/zfs-restore/main.go
Normal file
@@ -0,0 +1,178 @@
|
||||
// Command zfs-restore is a CLI tool for restoring ZFS snapshots from a backup server.
|
||||
// It provides commands for listing, restoring, and mounting snapshots.
|
||||
package main
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"os"
|
||||
"sort"
|
||||
|
||||
"git.ma-al.com/goc_marek/zfs/internal/restore"
|
||||
)
|
||||
|
||||
func main() {
|
||||
if len(os.Args) < 2 {
|
||||
printUsage()
|
||||
os.Exit(1)
|
||||
}
|
||||
|
||||
// Load configuration from environment and .env file
|
||||
cfg := restore.LoadConfig()
|
||||
client := restore.New(cfg.ClientID, cfg.APIKey, cfg.ServerURL)
|
||||
|
||||
command := os.Args[1]
|
||||
|
||||
switch command {
|
||||
case "list":
|
||||
snapshots, err := client.ListSnapshots()
|
||||
if err != nil {
|
||||
fmt.Printf("Error: %v\n", err)
|
||||
os.Exit(1)
|
||||
}
|
||||
client.DisplaySnapshots(snapshots)
|
||||
|
||||
case "restore":
|
||||
if len(os.Args) < 4 {
|
||||
fmt.Println("Usage: zfs-restore restore <snapshot-number> <target-dataset> [--force]")
|
||||
os.Exit(1)
|
||||
}
|
||||
|
||||
snapshots, err := client.ListSnapshots()
|
||||
if err != nil {
|
||||
fmt.Printf("Error: %v\n", err)
|
||||
os.Exit(1)
|
||||
}
|
||||
|
||||
// Sort by timestamp (newest first)
|
||||
sort.Slice(snapshots, func(i, j int) bool {
|
||||
return snapshots[i].Timestamp.After(snapshots[j].Timestamp)
|
||||
})
|
||||
|
||||
// Parse snapshot number
|
||||
var snapNum int
|
||||
fmt.Sscanf(os.Args[2], "%d", &snapNum)
|
||||
|
||||
if snapNum < 1 || snapNum > len(snapshots) {
|
||||
fmt.Printf("Invalid snapshot number. Use 'list' to see available snapshots.\n")
|
||||
os.Exit(1)
|
||||
}
|
||||
|
||||
snapshot := snapshots[snapNum-1]
|
||||
targetDataset := os.Args[3]
|
||||
force := len(os.Args) > 4 && os.Args[4] == "--force"
|
||||
|
||||
if err := client.RestoreSnapshot(snapshot, targetDataset, force); err != nil {
|
||||
fmt.Printf("Error: %v\n", err)
|
||||
os.Exit(1)
|
||||
}
|
||||
|
||||
case "save":
|
||||
if len(os.Args) < 4 {
|
||||
fmt.Println("Usage: zfs-restore save <snapshot-number> <output-file>")
|
||||
os.Exit(1)
|
||||
}
|
||||
|
||||
snapshots, err := client.ListSnapshots()
|
||||
if err != nil {
|
||||
fmt.Printf("Error: %v\n", err)
|
||||
os.Exit(1)
|
||||
}
|
||||
|
||||
sort.Slice(snapshots, func(i, j int) bool {
|
||||
return snapshots[i].Timestamp.After(snapshots[j].Timestamp)
|
||||
})
|
||||
|
||||
var snapNum int
|
||||
fmt.Sscanf(os.Args[2], "%d", &snapNum)
|
||||
|
||||
if snapNum < 1 || snapNum > len(snapshots) {
|
||||
fmt.Printf("Invalid snapshot number.\n")
|
||||
os.Exit(1)
|
||||
}
|
||||
|
||||
snapshot := snapshots[snapNum-1]
|
||||
outputFile := os.Args[3]
|
||||
|
||||
if err := client.RestoreToFile(snapshot, outputFile); err != nil {
|
||||
fmt.Printf("Error: %v\n", err)
|
||||
os.Exit(1)
|
||||
}
|
||||
|
||||
case "mount":
|
||||
if len(os.Args) < 4 {
|
||||
fmt.Println("Usage: zfs-restore mount <dataset> <mountpoint>")
|
||||
os.Exit(1)
|
||||
}
|
||||
|
||||
dataset := os.Args[2]
|
||||
mountpoint := os.Args[3]
|
||||
|
||||
if err := client.MountSnapshot(dataset, mountpoint); err != nil {
|
||||
fmt.Printf("Error: %v\n", err)
|
||||
os.Exit(1)
|
||||
}
|
||||
|
||||
case "latest":
|
||||
if len(os.Args) < 3 {
|
||||
fmt.Println("Usage: zfs-restore latest <target-dataset> [--force]")
|
||||
os.Exit(1)
|
||||
}
|
||||
|
||||
snapshots, err := client.ListSnapshots()
|
||||
if err != nil {
|
||||
fmt.Printf("Error: %v\n", err)
|
||||
os.Exit(1)
|
||||
}
|
||||
|
||||
if len(snapshots) == 0 {
|
||||
fmt.Println("No snapshots available")
|
||||
os.Exit(1)
|
||||
}
|
||||
|
||||
// Sort and get latest
|
||||
sort.Slice(snapshots, func(i, j int) bool {
|
||||
return snapshots[i].Timestamp.After(snapshots[j].Timestamp)
|
||||
})
|
||||
|
||||
latest := snapshots[0]
|
||||
targetDataset := os.Args[2]
|
||||
force := len(os.Args) > 3 && os.Args[3] == "--force"
|
||||
|
||||
fmt.Printf("Restoring latest snapshot from %s\n", latest.Timestamp.Format("2006-01-02 15:04:05"))
|
||||
|
||||
if err := client.RestoreSnapshot(latest, targetDataset, force); err != nil {
|
||||
fmt.Printf("Error: %v\n", err)
|
||||
os.Exit(1)
|
||||
}
|
||||
|
||||
case "help", "-h", "--help":
|
||||
printUsage()
|
||||
|
||||
default:
|
||||
fmt.Printf("Unknown command: %s\n", command)
|
||||
printUsage()
|
||||
os.Exit(1)
|
||||
}
|
||||
}
|
||||
|
||||
func printUsage() {
|
||||
fmt.Println("ZFS Snapshot Restore Tool")
|
||||
fmt.Println("\nUsage: zfs-restore [command] [options]")
|
||||
fmt.Println("\nCommands:")
|
||||
fmt.Println(" list - List available snapshots")
|
||||
fmt.Println(" restore <#> <dataset> [--force] - Restore snapshot to ZFS dataset")
|
||||
fmt.Println(" latest <dataset> [--force] - Restore most recent snapshot")
|
||||
fmt.Println(" save <#> <file> - Save snapshot to file")
|
||||
fmt.Println(" mount <dataset> <mountpoint> - Mount restored dataset")
|
||||
fmt.Println(" help - Show this help message")
|
||||
fmt.Println("\nExamples:")
|
||||
fmt.Println(" zfs-restore list")
|
||||
fmt.Println(" zfs-restore restore 1 tank/restored")
|
||||
fmt.Println(" zfs-restore latest tank/restored --force")
|
||||
fmt.Println(" zfs-restore save 2 backup.zfs.gz")
|
||||
fmt.Println(" zfs-restore mount tank/restored /mnt/restore")
|
||||
fmt.Println("\nEnvironment Variables (can be set in .env file):")
|
||||
fmt.Println(" CLIENT_ID - Client identifier (default: client1)")
|
||||
fmt.Println(" API_KEY - API key for authentication (default: secret123)")
|
||||
fmt.Println(" SERVER_URL - Backup server URL (default: http://localhost:8080)")
|
||||
}
|
||||
69
cmd/zfs-server/main.go
Normal file
69
cmd/zfs-server/main.go
Normal file
@@ -0,0 +1,69 @@
|
||||
// Command zfs-server is the main entry point for the ZFS snapshot backup server.
|
||||
// It initializes the server with S3 or local storage and starts the HTTP API.
|
||||
package main
|
||||
|
||||
import (
|
||||
"log"
|
||||
"net/http"
|
||||
"os"
|
||||
"time"
|
||||
|
||||
"git.ma-al.com/goc_marek/zfs/internal/server"
|
||||
)
|
||||
|
||||
func main() {
|
||||
// Load configuration from .env file and environment
|
||||
cfg := server.LoadConfig()
|
||||
|
||||
// Initialize backends
|
||||
var s3Backend *server.S3Backend
|
||||
var err error
|
||||
|
||||
if cfg.S3Enabled {
|
||||
s3Backend, err = server.NewS3Backend(cfg.S3Endpoint, cfg.S3AccessKey, cfg.S3SecretKey, cfg.S3BucketName, cfg.S3UseSSL)
|
||||
if err != nil {
|
||||
log.Fatalf("Failed to initialize S3 backend: %v", err)
|
||||
}
|
||||
log.Printf("Initialized S3 backend: %s/%s", cfg.S3Endpoint, cfg.S3BucketName)
|
||||
} else {
|
||||
log.Println("S3 backend disabled, using local ZFS storage only")
|
||||
}
|
||||
|
||||
localBackend := server.NewLocalBackend(cfg.BaseDataset)
|
||||
|
||||
// Create metadata directory if needed
|
||||
dir := cfg.MetadataFile
|
||||
if idx := len(dir) - 1; idx > 0 {
|
||||
for i := len(dir) - 1; i >= 0; i-- {
|
||||
if dir[i] == '/' {
|
||||
dir = dir[:i]
|
||||
break
|
||||
}
|
||||
}
|
||||
os.MkdirAll(dir, 0755)
|
||||
}
|
||||
|
||||
srv := server.New(cfg.ConfigFile, cfg.MetadataFile, s3Backend, localBackend)
|
||||
|
||||
// Register HTTP routes
|
||||
mux := http.NewServeMux()
|
||||
srv.RegisterRoutes(mux)
|
||||
|
||||
// Create HTTP server with timeouts for security and reliability
|
||||
httpServer := &http.Server{
|
||||
Addr: ":" + cfg.Port,
|
||||
Handler: mux,
|
||||
ReadTimeout: 30 * time.Second, // Prevent slowloris attacks
|
||||
WriteTimeout: 30 * time.Minute, // Allow large file uploads
|
||||
IdleTimeout: 120 * time.Second, // Close idle connections
|
||||
}
|
||||
|
||||
log.Printf("ZFS Snapshot Server starting on port %s", cfg.Port)
|
||||
log.Printf("Config file: %s", cfg.ConfigFile)
|
||||
log.Printf("Metadata file: %s", cfg.MetadataFile)
|
||||
log.Printf("S3 enabled: %v", cfg.S3Enabled)
|
||||
|
||||
if err := httpServer.ListenAndServe(); err != nil {
|
||||
log.Fatal(err)
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user