From eff313be4104bf267d84bfa8857488ce0b9d8e2f Mon Sep 17 00:00:00 2001 From: Harvey Tindall Date: Thu, 21 Dec 2023 17:42:07 +0000 Subject: [PATCH] backups: restore local backups in-app --- api.go | 26 +++++++++++++++++++++++++- daemon.go | 2 +- router.go | 1 + ts/modules/settings.ts | 6 ++++++ 4 files changed, 33 insertions(+), 2 deletions(-) diff --git a/api.go b/api.go index 52a670a..97d54a9 100644 --- a/api.go +++ b/api.go @@ -565,7 +565,7 @@ func (app *appContext) CreateBackup(gc *gin.Context) { // @Produce octet-stream // @Produce json // @Success 200 {body} file -// @Success 400 {object} boolResponse +// @Failure 400 {object} boolResponse // @Security Bearer // @tags Other func (app *appContext) GetBackup(gc *gin.Context) { @@ -608,3 +608,27 @@ func (app *appContext) GetBackups(gc *gin.Context) { } gc.JSON(200, resp) } + +// @Summary Restore a backup file stored locally to the server. +// @Param fname path string true "backup filename" +// @Router /backups/restore/{fname} [get] +// @Produce octet-stream +// @Produce json +// @Failure 400 {object} boolResponse +// @Security Bearer +// @tags Other +func (app *appContext) RestoreLocalBackup(gc *gin.Context) { + fname := gc.Param("fname") + // Hopefully this is enough to ensure the path isn't malicious. Hidden behind bearer auth anyway so shouldn't matter too much I guess. + ok := strings.HasPrefix(fname, BACKUP_PREFIX) && strings.HasSuffix(fname, BACKUP_SUFFIX) + t, err := time.Parse(BACKUP_DATEFMT, strings.TrimSuffix(strings.TrimPrefix(fname, BACKUP_PREFIX), BACKUP_SUFFIX)) + if !ok || err != nil || t.IsZero() { + app.debug.Printf("Ignoring backup DL request due to fname: %v\n", err) + respondBool(400, false, gc) + return + } + path := app.config.Section("backups").Key("path").String() + fullpath := filepath.Join(path, fname) + LOADBAK = fullpath + app.restart(gc) +} diff --git a/daemon.go b/daemon.go index 10f01c9..5c9b3f2 100644 --- a/daemon.go +++ b/daemon.go @@ -207,7 +207,7 @@ func (app *appContext) loadPendingBackup() { if LOADBAK == "" { return } - oldPath := filepath.Join(app.dataPath, "db-pre-"+filepath.Base(LOADBAK)) + oldPath := filepath.Join(app.dataPath, "db-"+string(time.Now().Unix())+"-pre-"+filepath.Base(LOADBAK)) app.info.Printf("Moving existing database to \"%s\"\n", oldPath) err := os.Rename(app.storage.db_path, oldPath) if err != nil { diff --git a/router.go b/router.go index 6c45ec0..fa49348 100644 --- a/router.go +++ b/router.go @@ -211,6 +211,7 @@ func (app *appContext) loadRoutes(router *gin.Engine) { api.POST(p+"/backups", app.CreateBackup) api.GET(p+"/backups/:fname", app.GetBackup) api.GET(p+"/backups", app.GetBackups) + api.POST(p+"/backups/restore/:fname", app.RestoreLocalBackup) if telegramEnabled || discordEnabled || matrixEnabled { api.GET(p+"/telegram/pin", app.TelegramGetPin) api.GET(p+"/telegram/verified/:pin", app.TelegramVerified) diff --git a/ts/modules/settings.ts b/ts/modules/settings.ts index 5877278..4244abf 100644 --- a/ts/modules/settings.ts +++ b/ts/modules/settings.ts @@ -797,6 +797,12 @@ export class settingsList { window.notifications.customPositive("pathCopied", "", window.lang.notif("pathCopied")); }); tr.querySelector(".backup-download").addEventListener("click", () => _download("/backups/" + b.name, b.name)); + tr.querySelector(".backup-restore").addEventListener("click", () => { + _post("/backups/restore/"+b.name, null, () => {}); + window.modals.backups.close(); + window.modals.settingsRefresh.modal.querySelector("span.heading").textContent = window.lang.strings("settingsRestarting"); + window.modals.settingsRefresh.show(); + }); table.appendChild(tr); } });