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 if os.Getenv("BACKUP_TANGLED_KNOT") != "true" {
18 return
19 }
20
21 backupKnotDB(ctx, minioClient, bucketName)
22}
23
24func backupKnotDB(ctx context.Context, minioClient *minio.Client, bucketName string) {
25 dir := os.Getenv("TANGLED_KNOT_DATABASE_DIRECTORY")
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
42 pipeReader, pipeWriter := io.Pipe()
43 defer pipeReader.Close()
44
45 go compress(dir, pipeWriter)
46
47 _, err := minioClient.PutObject(ctx, bucketName, "knot-repos.zip", pipeReader, -1, minio.PutObjectOptions{})
48 if err != nil {
49 slog.Error("stream knot repos to bucket: %w")
50 bugsnag.Notify(err)
51 }
52}
53
54func compress(src string, writer io.WriteCloser) error {
55 zipWriter := gzip.NewWriter(writer)
56 tarWriter := tar.NewWriter(zipWriter)
57
58 defer writer.Close()
59 defer zipWriter.Close()
60 defer tarWriter.Close()
61
62 filepath.Walk(src, func(file string, fi os.FileInfo, err error) error {
63 header, err := tar.FileInfoHeader(fi, file)
64 if err != nil {
65 return err
66 }
67
68 // must provide real name
69 // (see https://golang.org/src/archive/tar/common.go?#L626)
70 header.Name = filepath.ToSlash(file)
71
72 if err := tarWriter.WriteHeader(header); err != nil {
73 return err
74 }
75 // if not a dir, write file content
76 if !fi.IsDir() {
77 data, err := os.Open(file)
78 if err != nil {
79 return err
80 }
81 if _, err := io.Copy(tarWriter, data); err != nil {
82 return err
83 }
84 }
85 return nil
86 })
87
88 // produce tar
89 if err := tarWriter.Close(); err != nil {
90 return err
91 }
92 // produce gzip
93 if err := zipWriter.Close(); err != nil {
94 return err
95 }
96 //
97 return nil
98}