180 lines
5.3 KiB
Go
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
|
|
}
|