278 lines
8.2 KiB
Go
278 lines
8.2 KiB
Go
package backup
|
|
|
|
import (
|
|
b2_client "backea/internal/client"
|
|
"context"
|
|
"fmt"
|
|
"log"
|
|
"os"
|
|
"os/exec"
|
|
"path/filepath"
|
|
"strings"
|
|
)
|
|
|
|
// KopiaProvider defines the interface for different Kopia storage backends
|
|
type KopiaProvider interface {
|
|
// Connect connects to an existing repository or creates a new one
|
|
Connect(ctx context.Context, serviceName string, password string, configPath string) error
|
|
// GetRepositoryParams returns the parameters needed for repository operations
|
|
GetRepositoryParams(serviceName string) ([]string, error)
|
|
// GetBucketName returns the storage identifier (bucket name or path)
|
|
GetBucketName(serviceName string) string
|
|
// GetProviderType returns a string identifying the provider type
|
|
GetProviderType() string
|
|
}
|
|
|
|
// KopiaB2Provider implements the KopiaProvider interface for Backblaze B2
|
|
type KopiaB2Provider struct{}
|
|
|
|
// NewKopiaB2Provider creates a new B2 provider
|
|
func NewKopiaB2Provider() *KopiaB2Provider {
|
|
return &KopiaB2Provider{}
|
|
}
|
|
|
|
// Connect connects to a B2 repository
|
|
func (p *KopiaB2Provider) Connect(ctx context.Context, serviceName string, password string, configPath string) error {
|
|
// 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")
|
|
}
|
|
|
|
bucketName := p.GetBucketName(serviceName)
|
|
|
|
// Create B2 client and check if bucket exists, create if not
|
|
B2Client, _ := b2_client.NewClientFromEnv()
|
|
if B2Client == nil {
|
|
return fmt.Errorf("B2 client not initialized")
|
|
}
|
|
|
|
_, err := B2Client.GetBucket(ctx, bucketName)
|
|
if err != nil {
|
|
log.Printf("Bucket %s not found, creating...", bucketName)
|
|
_, err = B2Client.CreateBucket(ctx, bucketName, false)
|
|
if err != nil {
|
|
return fmt.Errorf("failed to create bucket: %w", err)
|
|
}
|
|
log.Printf("Created bucket: %s", bucketName)
|
|
}
|
|
|
|
// Try to connect to existing repository with config file
|
|
connectCmd := exec.CommandContext(
|
|
ctx,
|
|
"kopia",
|
|
"--config-file", configPath,
|
|
"repository", "connect", "b2",
|
|
"--bucket", bucketName,
|
|
"--key-id", keyID,
|
|
"--key", applicationKey,
|
|
"--password", password,
|
|
)
|
|
err = connectCmd.Run()
|
|
if err != nil {
|
|
// Connection failed, create new repository
|
|
log.Printf("Creating new B2 repository for %s", serviceName)
|
|
createCmd := exec.CommandContext(
|
|
ctx,
|
|
"kopia",
|
|
"--config-file", configPath,
|
|
"repository", "create", "b2",
|
|
"--bucket", bucketName,
|
|
"--key-id", keyID,
|
|
"--key", applicationKey,
|
|
"--password", password,
|
|
)
|
|
createOutput, err := createCmd.CombinedOutput()
|
|
if err != nil {
|
|
return fmt.Errorf("failed to create repository: %w\nOutput: %s", err, createOutput)
|
|
}
|
|
}
|
|
|
|
return nil
|
|
}
|
|
|
|
// GetRepositoryParams returns parameters for B2 operations
|
|
func (p *KopiaB2Provider) GetRepositoryParams(serviceName string) ([]string, error) {
|
|
keyID := os.Getenv("B2_KEY_ID")
|
|
applicationKey := os.Getenv("B2_APPLICATION_KEY")
|
|
if keyID == "" || applicationKey == "" {
|
|
return nil, fmt.Errorf("B2_KEY_ID and B2_APPLICATION_KEY must be set in .env file")
|
|
}
|
|
|
|
bucketName := p.GetBucketName(serviceName)
|
|
|
|
return []string{
|
|
"b2",
|
|
"--bucket", bucketName,
|
|
"--key-id", keyID,
|
|
"--key", applicationKey,
|
|
}, nil
|
|
}
|
|
|
|
// Modify the GetBucketName method in your KopiaB2Provider
|
|
func (p *KopiaB2Provider) GetBucketName(serviceName string) string {
|
|
// Backblaze dont allow .
|
|
sanitized := strings.ReplaceAll(serviceName, ".", "-")
|
|
|
|
// Add a prefix to make the bucket name unique
|
|
return fmt.Sprintf("backea-%s", sanitized)
|
|
}
|
|
|
|
// GetProviderType returns the provider type identifier
|
|
func (p *KopiaB2Provider) GetProviderType() string {
|
|
return "b2"
|
|
}
|
|
|
|
// KopiaLocalProvider implements the KopiaProvider interface for local storage
|
|
type KopiaLocalProvider struct {
|
|
BasePath string
|
|
}
|
|
|
|
// NewKopiaLocalProvider creates a new local provider
|
|
func NewKopiaLocalProvider(basePath string) *KopiaLocalProvider {
|
|
// If basePath is empty, use a default location
|
|
if basePath == "" {
|
|
basePath = filepath.Join(os.Getenv("HOME"), ".backea", "repos")
|
|
}
|
|
return &KopiaLocalProvider{
|
|
BasePath: basePath,
|
|
}
|
|
}
|
|
|
|
// Connect connects to a local repository
|
|
func (p *KopiaLocalProvider) Connect(ctx context.Context, serviceName string, password string, configPath string) error {
|
|
repoPath := filepath.Join(p.BasePath, serviceName)
|
|
log.Printf("Connecting to local repository at %s with config: %s", repoPath, configPath)
|
|
|
|
// Ensure the directory exists
|
|
if err := os.MkdirAll(repoPath, 0755); err != nil {
|
|
return fmt.Errorf("failed to create repository directory: %w", err)
|
|
}
|
|
|
|
// Try to connect to existing repository with config file
|
|
connectCmd := exec.CommandContext(
|
|
ctx,
|
|
"kopia",
|
|
"--config-file", configPath,
|
|
"repository", "connect", "filesystem",
|
|
"--path", repoPath,
|
|
"--password", password,
|
|
)
|
|
err := connectCmd.Run()
|
|
if err != nil {
|
|
// Connection failed, create new repository
|
|
log.Printf("Creating new local repository for %s at destination: %s", serviceName, repoPath)
|
|
createCmd := exec.CommandContext(
|
|
ctx,
|
|
"kopia",
|
|
"--config-file", configPath,
|
|
"repository", "create", "filesystem",
|
|
"--path", repoPath,
|
|
"--password", password,
|
|
)
|
|
createOutput, err := createCmd.CombinedOutput()
|
|
if err != nil {
|
|
return fmt.Errorf("failed to create repository: %w\nOutput: %s", err, createOutput)
|
|
}
|
|
}
|
|
|
|
return nil
|
|
}
|
|
|
|
// GetRepositoryParams returns parameters for local filesystem operations
|
|
func (p *KopiaLocalProvider) GetRepositoryParams(serviceName string) ([]string, error) {
|
|
repoPath := filepath.Join(p.BasePath, serviceName)
|
|
return []string{
|
|
"filesystem",
|
|
"--path", repoPath,
|
|
}, nil
|
|
}
|
|
|
|
// GetBucketName returns the path for a service
|
|
func (p *KopiaLocalProvider) GetBucketName(serviceName string) string {
|
|
return filepath.Join(p.BasePath, serviceName)
|
|
}
|
|
|
|
// GetProviderType returns the provider type identifier
|
|
func (p *KopiaLocalProvider) GetProviderType() string {
|
|
return "local"
|
|
}
|
|
|
|
// KopiaSFTPProvider implements the KopiaProvider interface for SFTP remote storage
|
|
type KopiaSFTPProvider struct {
|
|
Host string
|
|
BasePath string
|
|
Username string
|
|
KeyFile string
|
|
}
|
|
|
|
// NewKopiaSFTPProvider creates a new SFTP provider
|
|
func NewKopiaSFTPProvider(host, basePath, username, keyFile string) *KopiaSFTPProvider {
|
|
return &KopiaSFTPProvider{
|
|
Host: host,
|
|
BasePath: basePath,
|
|
Username: username,
|
|
KeyFile: keyFile,
|
|
}
|
|
}
|
|
|
|
// Connect connects to an SFTP repository
|
|
func (p *KopiaSFTPProvider) Connect(ctx context.Context, serviceName string, password string, configPath string) error {
|
|
repoPath := fmt.Sprintf("%s@%s:%s/%s", p.Username, p.Host, p.BasePath, serviceName)
|
|
|
|
// Try to connect to existing repository with config file
|
|
connectCmd := exec.CommandContext(
|
|
ctx,
|
|
"kopia",
|
|
"--config-file", configPath,
|
|
"repository", "connect", "sftp",
|
|
"--path", repoPath,
|
|
"--keyfile", p.KeyFile,
|
|
"--known-hosts", filepath.Join(os.Getenv("HOME"), ".ssh", "known_hosts"),
|
|
"--password", password,
|
|
)
|
|
err := connectCmd.Run()
|
|
if err != nil {
|
|
// Connection failed, create new repository
|
|
log.Printf("Creating new SFTP repository for %s at %s", serviceName, repoPath)
|
|
createCmd := exec.CommandContext(
|
|
ctx,
|
|
"kopia",
|
|
"--config-file", configPath,
|
|
"repository", "create", "sftp",
|
|
"--path", repoPath,
|
|
"--keyfile", p.KeyFile,
|
|
"--known-hosts", filepath.Join(os.Getenv("HOME"), ".ssh", "known_hosts"),
|
|
"--password", password,
|
|
)
|
|
createOutput, err := createCmd.CombinedOutput()
|
|
if err != nil {
|
|
return fmt.Errorf("failed to create repository: %w\nOutput: %s", err, createOutput)
|
|
}
|
|
}
|
|
|
|
return nil
|
|
}
|
|
|
|
// GetRepositoryParams returns parameters for SFTP operations
|
|
func (p *KopiaSFTPProvider) GetRepositoryParams(serviceName string) ([]string, error) {
|
|
repoPath := fmt.Sprintf("%s@%s:%s/%s", p.Username, p.Host, p.BasePath, serviceName)
|
|
return []string{
|
|
"sftp",
|
|
"--path", repoPath,
|
|
"--keyfile", p.KeyFile,
|
|
"--known-hosts", filepath.Join(os.Getenv("HOME"), ".ssh", "known_hosts"),
|
|
}, nil
|
|
}
|
|
|
|
// GetBucketName returns the path for a service
|
|
func (p *KopiaSFTPProvider) GetBucketName(serviceName string) string {
|
|
return fmt.Sprintf("%s/%s", p.BasePath, serviceName)
|
|
}
|
|
|
|
// GetProviderType returns the provider type identifier
|
|
func (p *KopiaSFTPProvider) GetProviderType() string {
|
|
return "sftp"
|
|
}
|