A tool for backing up ATProto related data to S3
1package main 2 3import ( 4 "archive/tar" 5 "compress/gzip" 6 "context" 7 "io" 8 "log/slog" 9 "os" 10 "path/filepath" 11 12 "github.com/bugsnag/bugsnag-go/v2" 13 "github.com/minio/minio-go/v7" 14) 15 16func backupTangledKnot(ctx context.Context, minioClient *minio.Client, bucketName string) { 17 backupKnotDB(ctx, minioClient, bucketName) 18 backupKnotRepos(ctx, minioClient, bucketName) 19} 20 21func backupKnotDB(ctx context.Context, minioClient *minio.Client, bucketName string) { 22 dir := os.Getenv("TANGLED_KNOT_DATABASE_DIRECTORY") 23 if dir == "" { 24 slog.Info("TANGLED_KNOT_DATABASE_DIRECTORY env not set - skipping knot DB backup") 25 } 26 27 pipeReader, pipeWriter := io.Pipe() 28 defer pipeReader.Close() 29 30 go compress(dir, pipeWriter) 31 32 _, err := minioClient.PutObject(ctx, bucketName, "knot-db.zip", pipeReader, -1, minio.PutObjectOptions{}) 33 if err != nil { 34 slog.Error("stream knot DB to bucket: %w") 35 bugsnag.Notify(err) 36 } 37} 38 39func backupKnotRepos(ctx context.Context, minioClient *minio.Client, bucketName string) { 40 dir := os.Getenv("TANGLED_KNOT_REPOSITORY_DIRECTORY") 41 if dir == "" { 42 slog.Info("TANGLED_KNOT_REPOSITORY_DIRECTORY env not set - skipping knot repo backup") 43 } 44 45 pipeReader, pipeWriter := io.Pipe() 46 defer pipeReader.Close() 47 48 go compress(dir, pipeWriter) 49 50 _, err := minioClient.PutObject(ctx, bucketName, "knot-repos.zip", pipeReader, -1, minio.PutObjectOptions{}) 51 if err != nil { 52 slog.Error("stream knot repos to bucket: %w") 53 bugsnag.Notify(err) 54 } 55} 56 57func compress(src string, writer io.WriteCloser) error { 58 zipWriter := gzip.NewWriter(writer) 59 tarWriter := tar.NewWriter(zipWriter) 60 61 defer writer.Close() 62 defer zipWriter.Close() 63 defer tarWriter.Close() 64 65 filepath.Walk(src, func(file string, fi os.FileInfo, err error) error { 66 header, err := tar.FileInfoHeader(fi, file) 67 if err != nil { 68 return err 69 } 70 71 // must provide real name 72 // (see https://golang.org/src/archive/tar/common.go?#L626) 73 header.Name = filepath.ToSlash(file) 74 75 if err := tarWriter.WriteHeader(header); err != nil { 76 return err 77 } 78 // if not a dir, write file content 79 if !fi.IsDir() { 80 data, err := os.Open(file) 81 if err != nil { 82 return err 83 } 84 if _, err := io.Copy(tarWriter, data); err != nil { 85 return err 86 } 87 } 88 return nil 89 }) 90 91 // produce tar 92 if err := tarWriter.Close(); err != nil { 93 return err 94 } 95 // produce gzip 96 if err := zipWriter.Close(); err != nil { 97 return err 98 } 99 // 100 return nil 101}