package storage import ( "context" "fmt" "os" "path/filepath" "time" "github.com/aws/aws-sdk-go-v2/aws" "github.com/aws/aws-sdk-go-v2/config" "github.com/aws/aws-sdk-go-v2/credentials" "github.com/aws/aws-sdk-go-v2/service/s3" dcfg "git.misaka.ren/M1saka/docker_backup/internal/config" ) // S3Backend uploads to S3-compatible storage. type S3Backend struct { cfg *dcfg.S3Config client *s3.Client } // NewS3Backend creates a configured S3 backend. func NewS3Backend(cfg *dcfg.S3Config) (*S3Backend, error) { ctx, cancel := context.WithTimeout(context.Background(), 30*time.Second) defer cancel() awsCfg, err := config.LoadDefaultConfig(ctx, config.WithRegion(cfg.Region), config.WithCredentialsProvider( credentials.NewStaticCredentialsProvider(cfg.AccessKey, cfg.SecretKey, ""), ), ) if err != nil { return nil, fmt.Errorf("load aws config: %w", err) } s3Opts := func(o *s3.Options) { if cfg.Endpoint != "" { o.BaseEndpoint = aws.String(cfg.Endpoint) o.UsePathStyle = true } } return &S3Backend{ cfg: cfg, client: s3.NewFromConfig(awsCfg, s3Opts), }, nil } // Upload implements Backend. func (s *S3Backend) Upload(ctx context.Context, projectName, localPath string) error { file, err := os.Open(localPath) if err != nil { return fmt.Errorf("open: %w", err) } defer file.Close() key := filepath.Base(localPath) if s.cfg.PathPrefix != "" { key = s.cfg.PathPrefix + projectName + "/" + key } else { key = projectName + "/" + key } _, err = s.client.PutObject(ctx, &s3.PutObjectInput{ Bucket: aws.String(s.cfg.Bucket), Key: aws.String(key), Body: file, ContentType: aws.String("application/gzip"), }) if err != nil { return fmt.Errorf("s3 put object: %w", err) } return nil }