A tool for backing up ATProto related data to S3
1package main
2
3import (
4 "context"
5 "log/slog"
6 "net/http"
7 "os"
8 "os/signal"
9 "time"
10
11 "github.com/bugsnag/bugsnag-go/v2"
12 "github.com/joho/godotenv"
13 "github.com/minio/minio-go/v7"
14 "github.com/minio/minio-go/v7/pkg/credentials"
15 "github.com/robfig/cron"
16)
17
18type service struct {
19 pdsHost string
20 did string
21 blobDir string
22 bucketName string
23 httpClient *http.Client
24 minioClient *minio.Client
25}
26
27func main() {
28 ctx, stop := signal.NotifyContext(context.Background(), os.Interrupt, os.Kill)
29 defer stop()
30
31 envLocation := os.Getenv("ENV_LOCATION")
32 if envLocation == "" {
33 envLocation = ".env"
34 }
35
36 err := godotenv.Load(envLocation)
37 if err != nil {
38 if !os.IsNotExist(err) {
39 slog.Error("load env", "error", err)
40 return
41 }
42 }
43
44 configureBugsnag()
45
46 minioClient, err := createMinioClient()
47 if err != nil {
48 slog.Error("create minio client", "error", err)
49 bugsnag.Notify(err)
50 return
51 }
52
53 bucketName := os.Getenv("S3_BUCKET_NAME")
54
55 err = minioClient.MakeBucket(ctx, bucketName, minio.MakeBucketOptions{})
56 if err != nil {
57 slog.Error("create bucket", "error", err)
58 bugsnag.Notify(err)
59 return
60 }
61
62 pdsHost := os.Getenv("PDS_HOST")
63 did := os.Getenv("DID")
64 blobDir := os.Getenv("BLOB_DIR")
65
66 service := service{
67 pdsHost: pdsHost,
68 did: did,
69 blobDir: blobDir,
70 bucketName: bucketName,
71 minioClient: minioClient,
72 httpClient: &http.Client{
73 Timeout: time.Second * 5,
74 Transport: &http.Transport{
75 IdleConnTimeout: time.Second * 90,
76 },
77 },
78 }
79
80 service.backupPDS(ctx)
81 service.backupTangledKnot(ctx)
82
83 c := cron.New()
84
85 c.AddFunc("@hourly", func() {
86 service.backupPDS(ctx)
87 })
88 c.AddFunc("@hourly", func() {
89 service.backupTangledKnot(ctx)
90 })
91
92 c.Start()
93 defer c.Stop()
94
95 <-ctx.Done()
96}
97
98func createMinioClient() (*minio.Client, error) {
99 endpoint := os.Getenv("S3_ENDPOINT")
100 accessKeyID := os.Getenv("S3_ACCESS_ID")
101 secretAccessKey := os.Getenv("S3_SECRET_ACCESS_KEY")
102 useSSL := true
103
104 return minio.New(endpoint, &minio.Options{
105 Creds: credentials.NewStaticV4(accessKeyID, secretAccessKey, ""),
106 Secure: useSSL,
107 })
108}
109
110func configureBugsnag() {
111 apiKey := os.Getenv("BUGSNAG_API_KEY")
112 if apiKey == "" {
113 slog.Info("bugsnag not configured")
114 return
115 }
116 bugsnag.Configure(bugsnag.Configuration{
117 APIKey: apiKey,
118 ReleaseStage: "production",
119 })
120}