backea/internal/backup/restore.go
2025-03-20 22:14:45 +01:00

180 lines
5.3 KiB
Go

package backup
import (
client "backea/internal/client"
"bufio"
"context"
"fmt"
"log"
"os"
"os/exec"
"path/filepath"
"strings"
)
// RestoreManager handles restoring files from Kopia backups
type RestoreManager struct {
B2Client *client.Client
}
// NewRestoreManager creates a new restore manager
func NewRestoreManager(b2Client *client.Client) *RestoreManager {
return &RestoreManager{
B2Client: b2Client,
}
}
// getStoredPassword retrieves a password from kopia.env file
func (r *RestoreManager) getStoredPassword(serviceName string) (string, error) {
// Define the expected key in the env file
passwordKey := fmt.Sprintf("KOPIA_%s_PASSWORD", strings.ToUpper(serviceName))
// Try to read from kopia.env
kopiaEnvPath := "kopia.env"
if _, err := os.Stat(kopiaEnvPath); err != nil {
return "", fmt.Errorf("kopia.env file not found: %w", err)
}
// File exists, check if the password is there
file, err := os.Open(kopiaEnvPath)
if err != nil {
return "", fmt.Errorf("failed to open kopia.env: %w", err)
}
defer file.Close()
scanner := bufio.NewScanner(file)
for scanner.Scan() {
line := scanner.Text()
if strings.HasPrefix(line, passwordKey+"=") {
parts := strings.SplitN(line, "=", 2)
if len(parts) == 2 {
// Found the password, remove quotes if present
password := parts[1]
password = strings.Trim(password, "\"")
return password, nil
}
}
}
if err := scanner.Err(); err != nil {
return "", fmt.Errorf("error reading kopia.env: %w", err)
}
return "", fmt.Errorf("password for service %s not found in kopia.env", serviceName)
}
// connectToRepository connects to an existing Kopia repository
func (r *RestoreManager) connectToRepository(ctx context.Context, serviceName string) error {
if r.B2Client == nil {
return fmt.Errorf("B2 client not initialized")
}
// Load environment variables for B2 credentials
keyID := os.Getenv("B2_KEY_ID")
applicationKey := os.Getenv("B2_APPLICATION_KEY")
if keyID == "" || applicationKey == "" {
return fmt.Errorf("B2_KEY_ID and B2_APPLICATION_KEY must be set in .env file")
}
// Get stored password for this service
password, err := r.getStoredPassword(serviceName)
if err != nil {
return fmt.Errorf("failed to get stored password: %w", err)
}
// Generate bucket name from service name
bucketName := fmt.Sprintf("backea-%s", strings.ToLower(serviceName))
// Check if bucket exists
_, err = r.B2Client.GetBucket(ctx, bucketName)
if err != nil {
return fmt.Errorf("bucket %s not found: %w", bucketName, err)
}
// Check if kopia repository is already connected
cmd := exec.Command("kopia", "repository", "status")
err = cmd.Run()
if err == nil {
// Already connected
log.Printf("Already connected to a repository, disconnecting first")
disconnectCmd := exec.Command("kopia", "repository", "disconnect")
if err := disconnectCmd.Run(); err != nil {
return fmt.Errorf("failed to disconnect from current repository: %w", err)
}
}
// Connect to the repository
log.Printf("Connecting to B2 repository for %s", serviceName)
connectCmd := exec.Command(
"kopia", "repository", "connect", "b2",
"--bucket", bucketName,
"--key-id", keyID,
"--key", applicationKey,
"--password", password,
)
connectOutput, err := connectCmd.CombinedOutput()
if err != nil {
return fmt.Errorf("failed to connect to repository: %w\nOutput: %s", err, connectOutput)
}
log.Printf("Successfully connected to repository for %s", serviceName)
return nil
}
// ListSnapshots lists all snapshots in the repository for a given service
func (r *RestoreManager) ListSnapshots(ctx context.Context, serviceName string) error {
// Connect to the repository
if err := r.connectToRepository(ctx, serviceName); err != nil {
return err
}
// List all snapshots
log.Printf("Listing snapshots for %s", serviceName)
listCmd := exec.Command("kopia", "snapshot", "list")
listOutput, err := listCmd.CombinedOutput()
if err != nil {
return fmt.Errorf("failed to list snapshots: %w\nOutput: %s", err, listOutput)
}
// Print the output
fmt.Println("Available snapshots:")
fmt.Println(string(listOutput))
return nil
}
// RestoreFile restores a file or directory from a specific snapshot to a target location
func (r *RestoreManager) RestoreFile(ctx context.Context, serviceName, snapshotID, sourcePath, targetPath string) error {
// Connect to the repository
if err := r.connectToRepository(ctx, serviceName); err != nil {
return err
}
// Create a subdirectory with the snapshot ID name
snapshotDirPath := filepath.Join(targetPath, snapshotID)
if err := os.MkdirAll(snapshotDirPath, 0755); err != nil {
return fmt.Errorf("failed to create target directory: %w", err)
}
// Construct the kopia restore command
log.Printf("Restoring from snapshot %s to %s", snapshotID, snapshotDirPath)
// Build the command with all required parameters
restoreCmd := exec.Command(
"kopia", "snapshot", "restore",
snapshotID, // Just the snapshot ID
snapshotDirPath, // Target location where files will be restored
)
// Execute the command and capture output
restoreOutput, err := restoreCmd.CombinedOutput()
if err != nil {
return fmt.Errorf("failed to restore files: %w\nOutput: %s", err, restoreOutput)
}
log.Printf("Successfully restored files to %s", snapshotDirPath)
log.Printf("Restore output: %s", string(restoreOutput))
return nil
}