diff --git a/database/database.go b/database/database.go index 0f4c4ba..146a09c 100644 --- a/database/database.go +++ b/database/database.go @@ -6,7 +6,9 @@ import ( "crypto/rand" "encoding/binary" "fmt" + "net/http" "os" + "strconv" "sync" "time" @@ -247,3 +249,17 @@ func (vdb *VspDatabase) GetCookieSecret() ([]byte, error) { return cookieSecret, err } + +// BackupDB streams a backup of the database over an http response writer. +func (vdb *VspDatabase) BackupDB(w http.ResponseWriter) error { + w.Header().Set("Content-Type", "application/octet-stream") + w.Header().Set("Content-Disposition", `attachment; filename="vspd.db"`) + + err := vdb.db.View(func(tx *bolt.Tx) error { + w.Header().Set("Content-Length", strconv.Itoa(int(tx.Size()))) + _, err := tx.WriteTo(w) + return err + }) + + return err +} diff --git a/webapi/admin.go b/webapi/admin.go index 7a99bdf..32d7b4d 100644 --- a/webapi/admin.go +++ b/webapi/admin.go @@ -14,8 +14,8 @@ func adminPage(c *gin.Context) { }) } -// ticketSearch is the handler for "POST /admin/ticket". The "hash" param will -// be used to retrieve a ticket from the database. +// ticketSearch is the handler for "POST /admin/ticket". The hash param will be +// used to retrieve a ticket from the database. func ticketSearch(c *gin.Context) { hash := c.PostForm("hash") @@ -59,6 +59,16 @@ func adminLogout(c *gin.Context) { setAdminStatus(nil, c) } +// downloadDatabaseBackup is the handler for "GET /backup". A binary +// representation of the whole database is generated and returned to the client. +func downloadDatabaseBackup(c *gin.Context) { + err := db.BackupDB(c.Writer) + if err != nil { + log.Errorf("Error backing up database: %v", err) + c.String(http.StatusInternalServerError, "Error backing up database") + } +} + // setAdminStatus stores the authentication status of the current session and // redirects the client to GET /admin. func setAdminStatus(admin interface{}, c *gin.Context) { diff --git a/webapi/templates/admin.html b/webapi/templates/admin.html index 6c126e1..bfc10f1 100644 --- a/webapi/templates/admin.html +++ b/webapi/templates/admin.html @@ -4,13 +4,16 @@

Admin

+ + Backup Database +
- +
- +
{{ with .SearchResult }} diff --git a/webapi/templates/login.html b/webapi/templates/login.html index f9263ab..b86f080 100644 --- a/webapi/templates/login.html +++ b/webapi/templates/login.html @@ -4,7 +4,7 @@

Login

- +
{{ if .IncorrectPassword }} diff --git a/webapi/webapi.go b/webapi/webapi.go index a352b29..9aef5ee 100644 --- a/webapi/webapi.go +++ b/webapi/webapi.go @@ -207,6 +207,7 @@ func router(debugMode bool, cookieSecret []byte, dcrd rpc.DcrdConnect, wallets r ) admin.GET("", adminPage) admin.POST("/ticket", ticketSearch) + admin.GET("/backup", downloadDatabaseBackup) admin.POST("/logout", adminLogout) // These API routes access dcrd and the voting wallets, and they need