package kopia import ( "backea/internal/logging" "context" "fmt" "os" "os/exec" "strings" b2_client "backea/internal/client" ) // KopiaError represents an error in Kopia operations type KopiaError struct { Message string Cause error } func (e *KopiaError) Error() string { if e.Cause != nil { return e.Message + ": " + e.Cause.Error() } return e.Message } func (e *KopiaError) Unwrap() error { return e.Cause } func NewKopiaError(message string, cause error) *KopiaError { return &KopiaError{ Message: message, Cause: cause, } } // B2Provider implements the Provider interface for Backblaze B2 type B2Provider struct { logger *logging.Logger } // NewB2Provider creates a new B2 provider func NewB2Provider() *B2Provider { return &B2Provider{ logger: logging.GetLogger(), } } // Connect connects to a B2 repository func (p *B2Provider) Connect(ctx context.Context, serviceName string, password string, configPath string) error { keyID := os.Getenv("B2_KEY_ID") applicationKey := os.Getenv("B2_APPLICATION_KEY") if keyID == "" || applicationKey == "" { return NewKopiaError("B2_KEY_ID and B2_APPLICATION_KEY must be set in .env file", nil) } bucketName := p.GetBucketName(serviceName) B2Client, err := b2_client.NewClientFromEnv() if err != nil { return NewKopiaError("failed to initialize B2 client", err) } if B2Client == nil { return NewKopiaError("B2 client not initialized", nil) } _, err = B2Client.GetBucket(ctx, bucketName) if err != nil { p.logger.Info("Bucket %s not found, creating...", bucketName) _, err = B2Client.CreateBucket(ctx, bucketName, false) if err != nil { return NewKopiaError("failed to create bucket", err) } p.logger.Info("Created bucket: %s", bucketName) } 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 { p.logger.Info("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 NewKopiaError(fmt.Sprintf("failed to create repository: %s", string(createOutput)), err) } } return nil } // GetRepositoryParams returns parameters for B2 operations func (p *B2Provider) GetRepositoryParams(serviceName string) ([]string, error) { keyID := os.Getenv("B2_KEY_ID") applicationKey := os.Getenv("B2_APPLICATION_KEY") if keyID == "" || applicationKey == "" { return nil, NewKopiaError("B2_KEY_ID and B2_APPLICATION_KEY must be set in .env file", nil) } bucketName := p.GetBucketName(serviceName) return []string{ "b2", "--bucket", bucketName, "--key-id", keyID, "--key", applicationKey, }, nil } // GetBucketName returns a bucket name for the service func (p *B2Provider) GetBucketName(serviceName string) string { sanitized := strings.ReplaceAll(serviceName, ".", "-") return fmt.Sprintf("backea-%s", sanitized) } // GetProviderType returns the provider type identifier func (p *B2Provider) GetProviderType() string { return "b2" }