Add manual, optionally-encrypted backup download (no auto-upload)
New POST /backup/download (admin-only) tars the whole jsondb directory (all servers/clients/users/settings) and streams it back as a file download. If a passphrase is given, the archive is encrypted first (AES-256-GCM, scrypt-derived key, backup/encrypt.go) - a small self-contained format, not gpg/OpenPGP-compatible, to avoid shelling out to an external binary or adding a PGP dependency. Deliberately does NOT upload anywhere automatically (e.g. to Nextcloud) - the archive only ever leaves the server as this one HTTP response to the requesting admin, who is responsible for storing it themselves. New "Download Backup" button + passphrase modal on the Global Settings page.
This commit is contained in:
@@ -23,6 +23,7 @@ import (
|
||||
"golang.zx2c4.com/wireguard/wgctrl"
|
||||
"golang.zx2c4.com/wireguard/wgctrl/wgtypes"
|
||||
|
||||
"github.com/ngoduykhanh/wireguard-ui/backup"
|
||||
"github.com/ngoduykhanh/wireguard-ui/emailer"
|
||||
"github.com/ngoduykhanh/wireguard-ui/model"
|
||||
"github.com/ngoduykhanh/wireguard-ui/store"
|
||||
@@ -1587,6 +1588,42 @@ func GetHashesChanges(db store.IStore) echo.HandlerFunc {
|
||||
}
|
||||
}
|
||||
|
||||
// DownloadBackup handler builds a tar.gz snapshot of the entire jsondb
|
||||
// directory (all servers/clients/users/settings, everything needed to
|
||||
// restore this installation) and returns it as a file download. If a
|
||||
// non-empty "passphrase" is given in the JSON body, the archive is
|
||||
// encrypted (AES-256-GCM via backup.Encrypt) before being returned.
|
||||
// Admin-only. The archive is never transmitted anywhere automatically -
|
||||
// it only ever goes out as this one HTTP response to the requesting admin.
|
||||
func DownloadBackup(db store.IStore) echo.HandlerFunc {
|
||||
return func(c echo.Context) error {
|
||||
var payload struct {
|
||||
Passphrase string `json:"passphrase"`
|
||||
}
|
||||
// best-effort bind; an empty/absent body just means "no encryption"
|
||||
c.Bind(&payload)
|
||||
|
||||
archiveData, err := backup.BuildArchive(db.GetPath())
|
||||
if err != nil {
|
||||
log.Error("Cannot build backup archive: ", err)
|
||||
return c.JSON(http.StatusInternalServerError, jsonHTTPResponse{false, "Cannot build backup archive"})
|
||||
}
|
||||
|
||||
filename := fmt.Sprintf("wireguard-ui-multi-backup-%s.tar.gz", time.Now().UTC().Format("20060102-150405"))
|
||||
if payload.Passphrase != "" {
|
||||
archiveData, err = backup.Encrypt(archiveData, payload.Passphrase)
|
||||
if err != nil {
|
||||
log.Error("Cannot encrypt backup archive: ", err)
|
||||
return c.JSON(http.StatusInternalServerError, jsonHTTPResponse{false, "Cannot encrypt backup archive"})
|
||||
}
|
||||
filename += ".enc"
|
||||
}
|
||||
|
||||
c.Response().Header().Set(echo.HeaderContentDisposition, fmt.Sprintf("attachment; filename=%s", filename))
|
||||
return c.Blob(http.StatusOK, "application/octet-stream", archiveData)
|
||||
}
|
||||
}
|
||||
|
||||
// AboutPage handler
|
||||
func AboutPage() echo.HandlerFunc {
|
||||
return func(c echo.Context) error {
|
||||
|
||||
Reference in New Issue
Block a user