diff options
-rw-r--r-- | system/admin/upload/backup.go | 78 | ||||
-rw-r--r-- | system/api/analytics/backup.go | 26 | ||||
-rw-r--r-- | system/auth.go | 34 | ||||
-rw-r--r-- | system/db/backup.go | 26 |
4 files changed, 164 insertions, 0 deletions
diff --git a/system/admin/upload/backup.go b/system/admin/upload/backup.go new file mode 100644 index 0000000..a666073 --- /dev/null +++ b/system/admin/upload/backup.go @@ -0,0 +1,78 @@ +package upload + +import ( + "archive/tar" + "compress/gzip" + "fmt" + "io" + "net/http" + "os" + "path/filepath" + "time" +) + +// Backup creates an archive of a project's uploads and writes it +// to the response as a download +func Backup(res http.ResponseWriter) error { + ts := time.Now().Unix() + filename := fmt.Sprintf("uploads-%d.bak.tar.gz", ts) + tmp := os.TempDir() + + // create uploads-{stamp}.bak.tar.gz + f, err := os.Create(filepath.Join(tmp, filename)) + if err != nil { + return err + } + defer f.Close() + + // loop through directory and gzip files + // add all to uploads.bak.tar.gz tarball + gz := gzip.NewWriter(f) + tarball := tar.NewWriter(gz) + err = filepath.Walk("uploads", func(path string, info os.FileInfo, err error) error { + if err != nil { + return err + } + + h := &tar.Header{ + Name: info.Name(), + Size: info.Size(), + Mode: int64(info.Mode()), + ModTime: info.ModTime(), + } + + err = tarball.WriteHeader(h) + if err != nil { + return err + } + + src, err := os.Open(path) + if err != nil { + return err + } + + _, err = io.Copy(tarball, src) + + return err + }) + + // write data to response + data, err := os.Open(filepath.Join(tmp, filename)) + if err != nil { + return err + } + defer data.Close() + + disposition := `attachment; filename=%s` + info, err := data.Stat() + if err != nil { + return err + } + + res.Header().Set("Content-Type", "application/octet-stream") + res.Header().Set("Content-Disposition", fmt.Sprintf(disposition, ts)) + res.Header().Set("Content-Length", fmt.Sprintf("%d", info.Size())) + + _, err = io.Copy(res, data) + return err +} diff --git a/system/api/analytics/backup.go b/system/api/analytics/backup.go new file mode 100644 index 0000000..07b1a46 --- /dev/null +++ b/system/api/analytics/backup.go @@ -0,0 +1,26 @@ +package analytics + +import ( + "fmt" + "net/http" + "time" + + "github.com/boltdb/bolt" +) + +// Backup writes a snapshot of the analytics.db database to an HTTP response +func Backup(res http.ResponseWriter) error { + err := store.View(func(tx *bolt.Tx) error { + ts := time.Now().Unix() + disposition := `attachment; filename="analytics-%d.db.bak"` + + res.Header().Set("Content-Type", "application/octet-stream") + res.Header().Set("Content-Disposition", fmt.Sprintf(disposition, ts)) + res.Header().Set("Content-Length", fmt.Sprintf("%d", int(tx.Size()))) + + _, err := tx.WriteTo(res) + return err + }) + + return err +} diff --git a/system/auth.go b/system/auth.go new file mode 100644 index 0000000..cf1adf2 --- /dev/null +++ b/system/auth.go @@ -0,0 +1,34 @@ +package system + +import ( + "net/http" + + "github.com/ponzu-cms/ponzu/system/db" +) + +// BasicAuth adds HTTP Basic Auth check for requests that should implement it +func BasicAuth(next http.HandlerFunc) http.HandlerFunc { + return http.HandlerFunc(func(res http.ResponseWriter, req *http.Request) { + u := db.ConfigCache("backup_basic_auth_user").(string) + p := db.ConfigCache("backup_basic_auth_password").(string) + + if u == "" || p == "" { + res.WriteHeader(http.StatusForbidden) + return + } + + user, password, ok := req.BasicAuth() + + if !ok { + res.WriteHeader(http.StatusForbidden) + return + } + + if u != user || p != password { + res.WriteHeader(http.StatusUnauthorized) + return + } + + next.ServeHTTP(res, req) + }) +} diff --git a/system/db/backup.go b/system/db/backup.go new file mode 100644 index 0000000..735abe4 --- /dev/null +++ b/system/db/backup.go @@ -0,0 +1,26 @@ +package db + +import ( + "fmt" + "net/http" + "time" + + "github.com/boltdb/bolt" +) + +// Backup writes a snapshot of the system.db database to an HTTP response +func Backup(res http.ResponseWriter) error { + err := store.View(func(tx *bolt.Tx) error { + ts := time.Now().Unix() + disposition := `attachment; filename="system-%d.db.bak"` + + res.Header().Set("Content-Type", "application/octet-stream") + res.Header().Set("Content-Disposition", fmt.Sprintf(disposition, ts)) + res.Header().Set("Content-Length", fmt.Sprintf("%d", int(tx.Size()))) + + _, err := tx.WriteTo(res) + return err + }) + + return err +} |