mirror of
https://github.com/usememos/memos.git
synced 2025-02-12 01:10:38 +01:00
feat: add support for auto backup db file (#1950)
Add support for auto backup db file
This commit is contained in:
parent
39351970d0
commit
d9b3501fae
@ -4,6 +4,7 @@ import (
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"net/http"
|
||||
"strconv"
|
||||
"strings"
|
||||
|
||||
"github.com/labstack/echo/v4"
|
||||
@ -39,6 +40,8 @@ const (
|
||||
SystemSettingMemoDisplayWithUpdatedTsName SystemSettingName = "memo-display-with-updated-ts"
|
||||
// SystemSettingOpenAIConfigName is the name of OpenAI config.
|
||||
SystemSettingOpenAIConfigName SystemSettingName = "openai-config"
|
||||
// SystemSettingAutoBackupIntervalName is the name of auto backup interval as seconds.
|
||||
SystemSettingAutoBackupIntervalName SystemSettingName = "auto-backup-interval"
|
||||
)
|
||||
|
||||
// CustomizedProfile is the struct definition for SystemSettingCustomizedProfileName system setting item.
|
||||
@ -139,6 +142,20 @@ func (upsert UpsertSystemSettingRequest) Validate() error {
|
||||
if err := json.Unmarshal([]byte(upsert.Value), &value); err != nil {
|
||||
return fmt.Errorf(systemSettingUnmarshalError, settingName)
|
||||
}
|
||||
case SystemSettingAutoBackupIntervalName:
|
||||
var value string
|
||||
if err := json.Unmarshal([]byte(upsert.Value), &value); err != nil {
|
||||
return fmt.Errorf(systemSettingUnmarshalError, settingName)
|
||||
}
|
||||
if value != "" {
|
||||
v, err := strconv.Atoi(value)
|
||||
if err != nil {
|
||||
return fmt.Errorf(systemSettingUnmarshalError, settingName)
|
||||
}
|
||||
if v < 0 {
|
||||
return fmt.Errorf("backup interval should > 0")
|
||||
}
|
||||
}
|
||||
case SystemSettingTelegramBotTokenName:
|
||||
if upsert.Value == "" {
|
||||
return nil
|
||||
|
2
go.mod
2
go.mod
@ -25,7 +25,7 @@ require (
|
||||
golang.org/x/mod v0.8.0
|
||||
golang.org/x/net v0.7.0
|
||||
golang.org/x/oauth2 v0.5.0
|
||||
modernc.org/sqlite v1.22.1
|
||||
modernc.org/sqlite v1.24.0
|
||||
)
|
||||
|
||||
require (
|
||||
|
4
go.sum
4
go.sum
@ -662,8 +662,8 @@ modernc.org/memory v1.5.0 h1:N+/8c5rE6EqugZwHii4IFsaJ7MUhoWX07J5tC/iI5Ds=
|
||||
modernc.org/memory v1.5.0/go.mod h1:PkUhL0Mugw21sHPeskwZW4D6VscE/GQJOnIpCnW6pSU=
|
||||
modernc.org/opt v0.1.3 h1:3XOZf2yznlhC+ibLltsDGzABUGVx8J6pnFMS3E4dcq4=
|
||||
modernc.org/opt v0.1.3/go.mod h1:WdSiB5evDcignE70guQKxYUl14mgWtbClRi5wmkkTX0=
|
||||
modernc.org/sqlite v1.22.1 h1:P2+Dhp5FR1RlVRkQ3dDfCiv3Ok8XPxqpe70IjYVA9oE=
|
||||
modernc.org/sqlite v1.22.1/go.mod h1:OrDj17Mggn6MhE+iPbBNf7RGKODDE9NFT0f3EwDzJqk=
|
||||
modernc.org/sqlite v1.24.0 h1:EsClRIWHGhLTCX44p+Ri/JLD+vFGo0QGjasg2/F9TlI=
|
||||
modernc.org/sqlite v1.24.0/go.mod h1:OrDj17Mggn6MhE+iPbBNf7RGKODDE9NFT0f3EwDzJqk=
|
||||
modernc.org/strutil v1.1.3 h1:fNMm+oJklMGYfU9Ylcywl0CO5O6nTfaowNsh2wpPjzY=
|
||||
modernc.org/strutil v1.1.3/go.mod h1:MEHNA7PdEnEwLvspRMtWTNnp2nnyvMfkimT1NKNAGbw=
|
||||
modernc.org/tcl v1.15.2 h1:C4ybAYCGJw968e+Me18oW55kD/FexcHbqH2xak1ROSY=
|
||||
|
48
server/backup.go
Normal file
48
server/backup.go
Normal file
@ -0,0 +1,48 @@
|
||||
package server
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"strconv"
|
||||
"time"
|
||||
|
||||
apiv1 "github.com/usememos/memos/api/v1"
|
||||
"github.com/usememos/memos/common/log"
|
||||
"github.com/usememos/memos/store"
|
||||
"go.uber.org/zap"
|
||||
)
|
||||
|
||||
func autoBackup(ctx context.Context, s *store.Store) {
|
||||
intervalStr := s.GetSystemSettingValueWithDefault(&ctx, apiv1.SystemSettingAutoBackupIntervalName.String(), "")
|
||||
if intervalStr == "" {
|
||||
log.Info("no SystemSettingAutoBackupIntervalName setting, disable auto backup")
|
||||
return
|
||||
}
|
||||
|
||||
interval, err := strconv.Atoi(intervalStr)
|
||||
if err != nil || interval <= 0 {
|
||||
log.Error(fmt.Sprintf("invalid SystemSettingAutoBackupIntervalName value %s, disable auto backup", intervalStr), zap.Error(err))
|
||||
return
|
||||
}
|
||||
|
||||
log.Info("enable auto backup every " + intervalStr + " seconds")
|
||||
ticker := time.NewTicker(time.Duration(interval) * time.Second)
|
||||
defer ticker.Stop()
|
||||
|
||||
var t time.Time
|
||||
for {
|
||||
select {
|
||||
case <-ctx.Done():
|
||||
log.Info("stop auto backup graceful.")
|
||||
return
|
||||
case t = <-ticker.C:
|
||||
}
|
||||
|
||||
filename := s.Profile.DSN + t.Format("-20060102-150405.bak")
|
||||
log.Info(fmt.Sprintf("create backup to %s", filename))
|
||||
err := s.BackupTo(ctx, filename)
|
||||
if err != nil {
|
||||
log.Error("fail to create backup", zap.Error(err))
|
||||
}
|
||||
}
|
||||
}
|
@ -103,6 +103,7 @@ func (s *Server) Start(ctx context.Context) error {
|
||||
}
|
||||
|
||||
go s.telegramBot.Start(ctx)
|
||||
go autoBackup(ctx, s.Store)
|
||||
|
||||
return s.e.Start(fmt.Sprintf(":%d", s.Profile.Port))
|
||||
}
|
||||
|
@ -3,9 +3,11 @@ package store
|
||||
import (
|
||||
"context"
|
||||
"database/sql"
|
||||
"fmt"
|
||||
"sync"
|
||||
|
||||
"github.com/usememos/memos/server/profile"
|
||||
"modernc.org/sqlite"
|
||||
)
|
||||
|
||||
// Store provides database access to all raw objects.
|
||||
@ -31,6 +33,43 @@ func (s *Store) GetDB() *sql.DB {
|
||||
return s.db
|
||||
}
|
||||
|
||||
func (s *Store) BackupTo(ctx context.Context, filename string) error {
|
||||
conn, err := s.db.Conn(ctx)
|
||||
if err != nil {
|
||||
return fmt.Errorf("fail to get conn %s", err)
|
||||
}
|
||||
defer conn.Close()
|
||||
|
||||
err = conn.Raw(func(driverConn any) error {
|
||||
type backuper interface {
|
||||
NewBackup(string) (*sqlite.Backup, error)
|
||||
}
|
||||
backupConn, ok := driverConn.(backuper)
|
||||
if !ok {
|
||||
return fmt.Errorf("db connection is not a sqlite backuper")
|
||||
}
|
||||
|
||||
bck, err := backupConn.NewBackup(filename)
|
||||
if err != nil {
|
||||
return fmt.Errorf("fail to create sqlite backup %s", err)
|
||||
}
|
||||
|
||||
for more := true; more; {
|
||||
more, err = bck.Step(-1)
|
||||
if err != nil {
|
||||
return fmt.Errorf("fail to execute sqlite backup %s", err)
|
||||
}
|
||||
}
|
||||
|
||||
return bck.Finish()
|
||||
})
|
||||
if err != nil {
|
||||
return fmt.Errorf("fail to backup %s", err)
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (s *Store) Vacuum(ctx context.Context) error {
|
||||
tx, err := s.db.BeginTx(ctx, nil)
|
||||
if err != nil {
|
||||
|
Loading…
x
Reference in New Issue
Block a user