···44S3_BUCKET_NAME="my-super-duper-bucket"
55DID="the-did-to-backup"
66PDS_HOST="https://your-pds.com"
77-TANGLED_KNOT_DATABASE_DIRECTORY="/path/to/database/directory"
88-TANGLED_KNOT_REPOSITORY_DIRECTORY="/path/to/repository/directory"
77+TANGLED_KNOT_DATABASE_DIRECTORY="./tangled/path/to/database/directory" # ./tangled is from the docker-compose volume
88+TANGLED_KNOT_REPOSITORY_DIRECTORY="./tangled/path/to/repository/directory" # ./tangled is from the docker-compose volume
99BUGSNAG_API_KEY="enter-api-key-to-enable"
1010BLOB_DIR="./blobs"
···99 "log/slog"
1010 "net/http"
1111 "os"
1212+ "path"
1213 "path/filepath"
1314 "time"
1415···54555556 defer resp.Body.Close()
56575757- _, err = s.minioClient.PutObject(ctx, s.bucketName, "pds-repo", resp.Body, -1, minio.PutObjectOptions{})
5858+ b, err := io.ReadAll(resp.Body)
5959+ if err != nil {
6060+ return fmt.Errorf("reading repo response body: %w", err)
6161+ }
6262+6363+ filename := path.Join(s.blobDir, fmt.Sprintf("%s-%d-repo.car", s.did, time.Now().UnixMilli()))
6464+ f, err := os.Create(filename)
6565+ if err != nil {
6666+ return fmt.Errorf("creating temp file: %w", err)
6767+ }
6868+ defer func() {
6969+ f.Close()
7070+7171+ err = os.Remove(filename)
7272+ if err != nil {
7373+ slog.Error("failed to delete pds repo file after uploading", "error", err, "filename", f.Name())
7474+ }
7575+ }()
7676+7777+ zipWriter := zip.NewWriter(f)
7878+ zipFile, err := zipWriter.Create("repo.car")
7979+ if err != nil {
8080+ return fmt.Errorf("create zip file: %w", err)
8181+ }
8282+8383+ _, err = zipFile.Write(b)
8484+ if err != nil {
8585+ return fmt.Errorf("write repo to file: %w", err)
8686+ }
8787+8888+ zipWriter.Close()
8989+9090+ // reset the reader back to the start so that the minio upload can read the data that's been written.
9191+ _, err = f.Seek(0, 0)
9292+ if err != nil {
9393+ return fmt.Errorf("setting seek on written file: %w", err)
9494+ }
9595+9696+ fi, err := f.Stat()
9797+ if err != nil {
9898+ return fmt.Errorf("stat file: %w", err)
9999+ }
100100+101101+ _, err = s.minioClient.PutObject(ctx, s.bucketName, "pds-repo.zip", f, fi.Size(), minio.PutObjectOptions{})
58102 if err != nil {
59103 return fmt.Errorf("stream repo to bucket: %w", err)
60104 }
+4-2
readme.md
···18181919For PDS data backup you need to ensure that `DID` and `PDS_HOST` are populated. (You can run this tool on any machine to back PDS data up)
20202121-For Knot data backup you need to ensure that `TANGLED_KNOT_DATABASE_DIRECTORY` and `TANGLED_KNOT_REPOSITORY_DIRECTORY` are populated. (You need to run this tool on your Knot server to back up Knot data)
2121+For Knot data backup you need to ensure that `TANGLED_KNOT_DATABASE_DIRECTORY` and `TANGLED_KNOT_REPOSITORY_DIRECTORY` are populated. (You need to run this tool on your Knot server to back up Knot data).
22222323-Run `go run .`
2323+If using Docker, in the `docker-compose.yaml` file there is a mount that needs to be set which should point to the directory on your host machine that contains your Tangled Knot application running (it has read only permissions, don't worry). Then inside the `.env` file you need to set the `TANGLED_KNOT_DATABASE_DIRECTORY` and `TANGLED_KNOT_REPOSITORY_DIRECTORY` envs to be the directories inside that Knot application volume.
2424+2525+Run `go run .` or use Docker.
24262527### Todo
2628
+65-15
tangled_knot.go
···44 "archive/tar"
55 "compress/gzip"
66 "context"
77+ "fmt"
78 "io"
89 "log/slog"
910 "os"
1111+ "path"
1012 "path/filepath"
1313+ "time"
11141215 "github.com/bugsnag/bugsnag-go/v2"
1316 "github.com/minio/minio-go/v7"
···2629 slog.Info("TANGLED_KNOT_DATABASE_DIRECTORY env not set - skipping knot DB backup")
2730 }
28312929- pipeReader, pipeWriter := io.Pipe()
3030- defer pipeReader.Close()
3232+ filename := path.Join(s.blobDir, fmt.Sprintf("%d-knot.zip", time.Now().UnixMilli()))
3333+ f, err := os.Create(filename)
3434+ if err != nil {
3535+ slog.Error("creating temp file", "error", err)
3636+ return
3737+ }
3838+ defer func() {
3939+ f.Close()
31403232- go compress(dir, pipeWriter)
4141+ err = os.Remove(filename)
4242+ if err != nil {
4343+ slog.Error("failed to delete knot db zip file after uploading", "error", err, "filename", f.Name())
4444+ }
4545+ }()
33463434- _, err := s.minioClient.PutObject(ctx, s.bucketName, "knot-db.zip", pipeReader, -1, minio.PutObjectOptions{})
4747+ compress(dir, f)
4848+4949+ // reset the reader back to the start so that the minio upload can read the data that's been written.
5050+ _, err = f.Seek(0, 0)
5151+ if err != nil {
5252+ slog.Error("setting seek on written file", "error", err)
5353+ return
5454+ }
5555+5656+ fi, err := f.Stat()
5757+ if err != nil {
5858+ slog.Error("failed to stat knot db zip file", "error", err)
5959+ return
6060+ }
6161+6262+ _, err = s.minioClient.PutObject(ctx, s.bucketName, "knot-db.zip", f, fi.Size(), minio.PutObjectOptions{})
3563 if err != nil {
3664 slog.Error("stream knot DB to bucket", "error", err)
3765 bugsnag.Notify(err)
···4472 slog.Info("TANGLED_KNOT_REPOSITORY_DIRECTORY env not set - skipping knot repo backup")
4573 }
46744747- pipeReader, pipeWriter := io.Pipe()
4848- defer pipeReader.Close()
7575+ filename := path.Join(s.blobDir, fmt.Sprintf("%d-knot-repos.zip", time.Now().UnixMilli()))
7676+ f, err := os.Create(filename)
7777+ if err != nil {
7878+ slog.Error("creating temp file", "error", err)
7979+ return
8080+ }
8181+ defer func() {
8282+ f.Close()
49835050- go compress(dir, pipeWriter)
8484+ err = os.Remove(filename)
8585+ if err != nil {
8686+ slog.Error("failed to delete knot repos zip file after uploading", "error", err, "filename", f.Name())
8787+ }
8888+ }()
51895252- _, err := s.minioClient.PutObject(ctx, s.bucketName, "knot-repos.zip", pipeReader, -1, minio.PutObjectOptions{})
9090+ compress(dir, f)
9191+9292+ // reset the reader back to the start so that the minio upload can read the data that's been written.
9393+ _, err = f.Seek(0, 0)
5394 if err != nil {
5454- slog.Error("stream knot repos to bucket", "error", err)
9595+ slog.Error("setting seek on written file", "error", err)
9696+ return
9797+ }
9898+9999+ fi, err := f.Stat()
100100+ if err != nil {
101101+ slog.Error("failed to stat knot db zip file", "error", err)
102102+ return
103103+ }
104104+105105+ _, err = s.minioClient.PutObject(ctx, s.bucketName, "knot-repos.zip", f, fi.Size(), minio.PutObjectOptions{})
106106+ if err != nil {
107107+ slog.Error("write knot repos to bucket", "error", err)
55108 bugsnag.Notify(err)
56109 }
57110}
581115959-func compress(src string, writer io.WriteCloser) error {
112112+func compress(src string, writer io.Writer) error {
60113 zipWriter := gzip.NewWriter(writer)
61114 tarWriter := tar.NewWriter(zipWriter)
6262-6363- defer writer.Close()
6464- defer zipWriter.Close()
6565- defer tarWriter.Close()
6611567116 filepath.Walk(src, func(file string, fi os.FileInfo, err error) error {
68117 header, err := tar.FileInfoHeader(fi, file)
···87136 return err
88137 }
89138 }
139139+90140 return nil
91141 })
92142···98148 if err := zipWriter.Close(); err != nil {
99149 return err
100150 }
101101- //
151151+102152 return nil
103153}