diff --git a/cmd/backup/config/config.go b/cmd/backup/config/config.go index b2b0aef..78d2e34 100644 --- a/cmd/backup/config/config.go +++ b/cmd/backup/config/config.go @@ -35,11 +35,11 @@ func init() { logrus.Infof("Run mode:%s", mode) // 加载配置文件 - dir, err := config.WalkWorkDir() + etc, err := config.WorkEtcPath() if err != nil { panic(err) } - path := filepath.Join(dir, "etc", "app.yml") + path := filepath.Join(etc, "app.yml") data, err := os.ReadFile(path) if err != nil { diff --git a/cmd/backup/handler/timer/db/db.go b/cmd/backup/handler/timer/db/db.go new file mode 100644 index 0000000..531d8df --- /dev/null +++ b/cmd/backup/handler/timer/db/db.go @@ -0,0 +1,7 @@ +package db + +// Storage 备份恢复器 +type Storage interface { + Backup(name string) (string, error) + Restore(path string) error +} diff --git a/cmd/backup/handler/timer/db/mgodb.go b/cmd/backup/handler/timer/db/mgodb.go new file mode 100644 index 0000000..282c68f --- /dev/null +++ b/cmd/backup/handler/timer/db/mgodb.go @@ -0,0 +1,72 @@ +package db + +import ( + "context" + "fmt" + "net/url" + "os/exec" + "time" + + "github.com/eiblog/eiblog/cmd/backup/config" + "github.com/eiblog/eiblog/pkg/connector/db" +) + +// MongoStorage 备份恢复器 +type MongoStorage struct{} + +// Backup 备份 +func (r MongoStorage) Backup(name string) (string, error) { + ctx, cancel := context.WithTimeout(context.Background(), time.Second*20) + defer cancel() + + // dump + u, err := url.Parse(config.Conf.Database.Source) + if err != nil { + return "", err + } + arg := fmt.Sprintf("mongodump -h %s -d eiblog -o /tmp", u.Host) + cmd := exec.CommandContext(ctx, "sh", "-c", arg) + err = cmd.Run() + if err != nil { + return "", err + } + // tar + arg = fmt.Sprintf("tar czf /tmp/%s -C /tmp eiblog", name) + cmd = exec.CommandContext(ctx, "sh", "-c", arg) + err = cmd.Run() + if err != nil { + return "", err + } + return "/tmp/" + name, nil +} + +// Restore 恢复 +func (r MongoStorage) Restore(path string) error { + ctx, cancel := context.WithTimeout(context.Background(), time.Second*20) + defer cancel() + + // drop database + mdb, err := db.NewMDB(config.Conf.Database) + if err != nil { + return err + } + err = mdb.Drop(ctx) + if err != nil { + return err + } + // unarchive + arg := fmt.Sprintf("tar xzf %s -C /tmp", path) + cmd := exec.CommandContext(ctx, "sh", "-c", arg) + err = cmd.Run() + if err != nil { + return err + } + // restore + u, err := url.Parse(config.Conf.Database.Source) + if err != nil { + return err + } + arg = fmt.Sprintf("mongorestore -h %s -d eiblog /tmp/eiblog", u.Host) + cmd = exec.CommandContext(ctx, "sh", "-c", arg) + return cmd.Run() +} diff --git a/cmd/backup/handler/timer/qiniu/qiniu.go b/cmd/backup/handler/timer/qiniu/qiniu.go deleted file mode 100644 index 39d9ba7..0000000 --- a/cmd/backup/handler/timer/qiniu/qiniu.go +++ /dev/null @@ -1,138 +0,0 @@ -package qiniu - -import ( - "context" - "errors" - "fmt" - "net/url" - "os" - "os/exec" - "path/filepath" - "time" - - "github.com/eiblog/eiblog/cmd/backup/config" - "github.com/eiblog/eiblog/cmd/backup/handler/internal" - "github.com/eiblog/eiblog/pkg/connector/db" - "github.com/eiblog/eiblog/pkg/third/qiniu" -) - -// BackupRestorer qiniu backup restorer -type BackupRestorer struct{} - -// Backup implements timer.BackupRestorer -func (s BackupRestorer) Backup(now time.Time) error { - switch config.Conf.Database.Driver { - case "mongodb": - return backupFromMongoDB(now) - default: - return errors.New("unsupported source backup to qiniu: " + - config.Conf.Database.Driver) - } -} - -// Restore implements timer.BackupRestorer -func (s BackupRestorer) Restore() error { - switch config.Conf.Database.Driver { - case "mongodb": - return restoreToMongoDB() - default: - return errors.New("unsupported source restore from qiniu: " + - config.Conf.Database.Driver) - } -} - -func backupFromMongoDB(now time.Time) error { - ctx, cancel := context.WithTimeout(context.Background(), time.Minute*20) - defer cancel() - - // dump - u, err := url.Parse(config.Conf.Database.Source) - if err != nil { - return err - } - arg := fmt.Sprintf("mongodump -h %s -d eiblog -o /tmp", u.Host) - cmd := exec.CommandContext(ctx, "sh", "-c", arg) - err = cmd.Run() - if err != nil { - return err - } - // tar - name := fmt.Sprintf("eiblog-%s.tar.gz", now.Format("2006-01-02")) - arg = fmt.Sprintf("tar czf /tmp/%s -C /tmp eiblog", name) - cmd = exec.CommandContext(ctx, "sh", "-c", arg) - err = cmd.Run() - if err != nil { - return err - } - - // upload file - f, err := os.Open("/tmp/" + name) - if err != nil { - return err - } - s, err := f.Stat() - if err != nil { - return err - } - uploadParams := qiniu.UploadParams{ - Name: filepath.Join("blog", name), // blog/eiblog-xx.tar.gz - Size: s.Size(), - Data: f, - NoCompletePath: true, - } - _, err = internal.QiniuClient.Upload(uploadParams) - if err != nil { - return err - } - // after days delete - deleteParams := qiniu.DeleteParams{ - Name: filepath.Join("blog", name), // blog/eiblog-xx.tar.gz - Days: config.Conf.Validity, - NoCompletePath: true, - } - return internal.QiniuClient.Delete(deleteParams) -} - -func restoreToMongoDB() error { - // backup file - params := qiniu.ContentParams{ - Prefix: "blog/", - } - raw, err := internal.QiniuClient.Content(params) - if err != nil { - return err - } - f, err := os.OpenFile("/tmp/eiblog.tar.gz", os.O_WRONLY|os.O_CREATE, 0644) - if err != nil { - return err - } - _, _ = f.Write(raw) - defer f.Close() - - ctx, cancel := context.WithTimeout(context.Background(), time.Minute*20) - defer cancel() - // drop database - mdb, err := db.NewMDB(config.Conf.Database) - if err != nil { - return err - } - err = mdb.Drop(ctx) - if err != nil { - return err - } - // unarchive - arg := "tar xzf /tmp/eiblog.tar.gz -C /tmp" - cmd := exec.CommandContext(ctx, "sh", "-c", arg) - err = cmd.Run() - if err != nil { - return err - } - // restore - u, err := url.Parse(config.Conf.Database.Source) - if err != nil { - return err - } - arg = fmt.Sprintf("mongorestore -h %s -d eiblog /tmp/eiblog", u.Host) - cmd = exec.CommandContext(ctx, "sh", "-c", arg) - return cmd.Run() -} diff --git a/cmd/backup/handler/timer/timer.go b/cmd/backup/handler/timer/timer.go index 56dd401..2140470 100644 --- a/cmd/backup/handler/timer/timer.go +++ b/cmd/backup/handler/timer/timer.go @@ -2,49 +2,71 @@ package timer import ( "errors" + "fmt" "strconv" "time" "github.com/eiblog/eiblog/cmd/backup/config" - "github.com/eiblog/eiblog/cmd/backup/handler/timer/qiniu" + "github.com/eiblog/eiblog/cmd/backup/handler/timer/db" + "github.com/eiblog/eiblog/cmd/backup/handler/timer/to" "github.com/sirupsen/logrus" ) -// BackupRestorer 备份恢复器 -type BackupRestorer interface { - Backup(now time.Time) error - Restore() error -} - // Start to backup with ticker func Start(restore bool) (err error) { - var storage BackupRestorer + var ( + storage db.Storage + backupTo to.BackupRestorer + ) - // backup instance + // backup from + switch config.Conf.Database.Driver { + case "mongodb": + storage = db.MongoStorage{} + + default: + return errors.New("timer: unknown backup from driver: " + + config.Conf.Database.Driver) + } + + // backup to switch config.Conf.BackupTo { case "qiniu": - storage = qiniu.BackupRestorer{} + backupTo = to.QiniuBackupRestorer{} default: return errors.New("timer: unknown backup to driver: " + config.Conf.BackupTo) } + + // restore if restore { - err = storage.Restore() + path, err := backupTo.Download() + if err != nil { + return err + } + err = storage.Restore(path) if err != nil { return err } logrus.Info("timer: Restore success") } - // parse duration + + // backup interval, err := ParseDuration(config.Conf.Interval) if err != nil { return err } t := time.NewTicker(interval) for now := range t.C { - err = storage.Backup(now) + name := fmt.Sprintf("eiblog-%s.tar.gz", now.Format("2006-01-02")) + path, err := storage.Backup(name) + if err != nil { + logrus.Error("timer: Start.Backup: ", now.Format(time.RFC3339), err) + continue + } + err = backupTo.Upload(path) if err != nil { logrus.Error("timer: Start.Backup: ", now.Format(time.RFC3339), err) } diff --git a/cmd/backup/handler/timer/to/qiniu.go b/cmd/backup/handler/timer/to/qiniu.go new file mode 100644 index 0000000..b261b27 --- /dev/null +++ b/cmd/backup/handler/timer/to/qiniu.go @@ -0,0 +1,66 @@ +package to + +import ( + "os" + "path/filepath" + + "github.com/eiblog/eiblog/cmd/backup/config" + "github.com/eiblog/eiblog/cmd/backup/handler/internal" + "github.com/eiblog/eiblog/pkg/third/qiniu" +) + +// QiniuBackupRestorer qiniu backup restorer +type QiniuBackupRestorer struct{} + +// Upload implements timer.BackupRestorer +func (s QiniuBackupRestorer) Upload(path string) error { + name := filepath.Base(path) + + // upload file + f, err := os.Open(path) + if err != nil { + return err + } + fi, err := f.Stat() + if err != nil { + return err + } + uploadParams := qiniu.UploadParams{ + Name: filepath.Join("blog", name), // blog/eiblog-xx.tar.gz + Size: fi.Size(), + Data: f, + NoCompletePath: true, + } + _, err = internal.QiniuClient.Upload(uploadParams) + if err != nil { + return err + } + // after days delete + deleteParams := qiniu.DeleteParams{ + Name: filepath.Join("blog", name), // blog/eiblog-xx.tar.gz + Days: config.Conf.Validity, + NoCompletePath: true, + } + return internal.QiniuClient.Delete(deleteParams) +} + +// Download implements timer.BackupRestorer +func (s QiniuBackupRestorer) Download() (string, error) { + // backup file + params := qiniu.ContentParams{ + Prefix: "blog/", + } + raw, err := internal.QiniuClient.Content(params) + if err != nil { + return "", err + } + + path := filepath.Join("/tmp", "eiblog.tar.gz") + f, err := os.OpenFile(path, os.O_WRONLY|os.O_CREATE, 0644) + if err != nil { + return "", err + } + _, _ = f.Write(raw) + defer f.Close() + return path, nil +} diff --git a/cmd/backup/handler/timer/to/to.go b/cmd/backup/handler/timer/to/to.go new file mode 100644 index 0000000..a04d9a9 --- /dev/null +++ b/cmd/backup/handler/timer/to/to.go @@ -0,0 +1,7 @@ +package to + +// BackupRestorer 备份存储接口 +type BackupRestorer interface { + Upload(path string) error + Download() (path string, err error) +}