mirror of
https://github.com/1Panel-dev/1Panel.git
synced 2025-01-19 08:19:15 +08:00
feat: 实现远程数据备份恢复功能 (#1774)
This commit is contained in:
parent
87a7cf3aca
commit
e83e592e0a
@ -15,6 +15,7 @@ type MysqlDBInfo struct {
|
|||||||
CreatedAt time.Time `json:"createdAt"`
|
CreatedAt time.Time `json:"createdAt"`
|
||||||
Name string `json:"name"`
|
Name string `json:"name"`
|
||||||
From string `json:"from"`
|
From string `json:"from"`
|
||||||
|
MysqlName string `json:"mysqlName"`
|
||||||
Format string `json:"format"`
|
Format string `json:"format"`
|
||||||
Username string `json:"username"`
|
Username string `json:"username"`
|
||||||
Password string `json:"password"`
|
Password string `json:"password"`
|
||||||
|
@ -57,7 +57,7 @@ func (u *RemoteDBRepo) GetList(opts ...DBOption) ([]model.RemoteDB, error) {
|
|||||||
|
|
||||||
func (c *RemoteDBRepo) WithByFrom(from string) DBOption {
|
func (c *RemoteDBRepo) WithByFrom(from string) DBOption {
|
||||||
return func(g *gorm.DB) *gorm.DB {
|
return func(g *gorm.DB) *gorm.DB {
|
||||||
return g.Where("`from` != ?", from)
|
return g.Where("`from` == ?", from)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -106,15 +106,11 @@ func handleAppBackup(install *model.AppInstall, backupDir, fileName string) erro
|
|||||||
|
|
||||||
resource, _ := appInstallResourceRepo.GetFirst(appInstallResourceRepo.WithAppInstallId(install.ID))
|
resource, _ := appInstallResourceRepo.GetFirst(appInstallResourceRepo.WithAppInstallId(install.ID))
|
||||||
if resource.ID != 0 && resource.ResourceId != 0 {
|
if resource.ID != 0 && resource.ResourceId != 0 {
|
||||||
mysqlInfo, err := appInstallRepo.LoadBaseInfo(constant.AppMysql, "")
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
db, err := mysqlRepo.Get(commonRepo.WithByID(resource.ResourceId))
|
db, err := mysqlRepo.Get(commonRepo.WithByID(resource.ResourceId))
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
if err := handleMysqlBackup(mysqlInfo, tmpDir, db.Name, fmt.Sprintf("%s.sql.gz", install.Name)); err != nil {
|
if err := handleMysqlBackup(db.Name, tmpDir, fmt.Sprintf("%s.sql.gz", install.Name)); err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -198,7 +194,11 @@ func handleAppRecover(install *model.AppInstall, recoverFile string, isRollback
|
|||||||
}
|
}
|
||||||
_ = appInstallResourceRepo.BatchUpdateBy(map[string]interface{}{"resource_id": newDB.ID}, commonRepo.WithByID(resource.ID))
|
_ = appInstallResourceRepo.BatchUpdateBy(map[string]interface{}{"resource_id": newDB.ID}, commonRepo.WithByID(resource.ID))
|
||||||
|
|
||||||
if err := handleMysqlRecover(mysqlInfo, tmpPath, newDB.Name, fmt.Sprintf("%s.sql.gz", install.Name), true); err != nil {
|
if err := handleMysqlRecover(dto.CommonRecover{
|
||||||
|
Name: newDB.MysqlName,
|
||||||
|
DetailName: newDB.Name,
|
||||||
|
File: tmpPath + "/" + fmt.Sprintf("%s/%s.sql.gz", tmpPath, install.Name),
|
||||||
|
}, true); err != nil {
|
||||||
global.LOG.Errorf("handle recover from sql.gz failed, err: %v", err)
|
global.LOG.Errorf("handle recover from sql.gz failed, err: %v", err)
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
@ -1,10 +1,8 @@
|
|||||||
package service
|
package service
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"compress/gzip"
|
|
||||||
"fmt"
|
"fmt"
|
||||||
"os"
|
"os"
|
||||||
"os/exec"
|
|
||||||
"path"
|
"path"
|
||||||
"path/filepath"
|
"path/filepath"
|
||||||
"strings"
|
"strings"
|
||||||
@ -12,9 +10,9 @@ import (
|
|||||||
|
|
||||||
"github.com/1Panel-dev/1Panel/backend/app/dto"
|
"github.com/1Panel-dev/1Panel/backend/app/dto"
|
||||||
"github.com/1Panel-dev/1Panel/backend/app/model"
|
"github.com/1Panel-dev/1Panel/backend/app/model"
|
||||||
"github.com/1Panel-dev/1Panel/backend/app/repo"
|
|
||||||
"github.com/1Panel-dev/1Panel/backend/global"
|
"github.com/1Panel-dev/1Panel/backend/global"
|
||||||
"github.com/1Panel-dev/1Panel/backend/utils/files"
|
"github.com/1Panel-dev/1Panel/backend/utils/files"
|
||||||
|
"github.com/1Panel-dev/1Panel/backend/utils/mysql/client"
|
||||||
"github.com/pkg/errors"
|
"github.com/pkg/errors"
|
||||||
)
|
)
|
||||||
|
|
||||||
@ -23,24 +21,22 @@ func (u *BackupService) MysqlBackup(req dto.CommonBackup) error {
|
|||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
app, err := appInstallRepo.LoadBaseInfo("mysql", "")
|
|
||||||
if err != nil {
|
timeNow := time.Now().Format("20060102150405")
|
||||||
|
targetDir := fmt.Sprintf("%s/database/mysql/%s/%s", localDir, req.Name, req.DetailName)
|
||||||
|
fileName := fmt.Sprintf("%s_%s.sql.gz", req.DetailName, timeNow)
|
||||||
|
|
||||||
|
if err := handleMysqlBackup(req.DetailName, targetDir, fileName); err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
timeNow := time.Now().Format("20060102150405")
|
|
||||||
backupDir := fmt.Sprintf("%s/database/mysql/%s/%s", localDir, req.Name, req.DetailName)
|
|
||||||
fileName := fmt.Sprintf("%s_%s.sql.gz", req.DetailName, timeNow)
|
|
||||||
if err := handleMysqlBackup(app, backupDir, req.DetailName, fileName); err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
record := &model.BackupRecord{
|
record := &model.BackupRecord{
|
||||||
Type: "mysql",
|
Type: "mysql",
|
||||||
Name: app.Name,
|
Name: req.Name,
|
||||||
DetailName: req.DetailName,
|
DetailName: req.DetailName,
|
||||||
Source: "LOCAL",
|
Source: "LOCAL",
|
||||||
BackupType: "LOCAL",
|
BackupType: "LOCAL",
|
||||||
FileDir: backupDir,
|
FileDir: targetDir,
|
||||||
FileName: fileName,
|
FileName: fileName,
|
||||||
}
|
}
|
||||||
if err := backupRepo.CreateRecord(record); err != nil {
|
if err := backupRepo.CreateRecord(record); err != nil {
|
||||||
@ -50,26 +46,13 @@ func (u *BackupService) MysqlBackup(req dto.CommonBackup) error {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func (u *BackupService) MysqlRecover(req dto.CommonRecover) error {
|
func (u *BackupService) MysqlRecover(req dto.CommonRecover) error {
|
||||||
app, err := appInstallRepo.LoadBaseInfo("mysql", "")
|
if err := handleMysqlRecover(req, false); err != nil {
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
fileOp := files.NewFileOp()
|
|
||||||
if !fileOp.Stat(req.File) {
|
|
||||||
return errors.New(fmt.Sprintf("%s file is not exist", req.File))
|
|
||||||
}
|
|
||||||
global.LOG.Infof("recover database %s-%s from backup file %s", req.Name, req.DetailName, req.File)
|
|
||||||
if err := handleMysqlRecover(app, path.Dir(req.File), req.DetailName, path.Base(req.File), false); err != nil {
|
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (u *BackupService) MysqlRecoverByUpload(req dto.CommonRecover) error {
|
func (u *BackupService) MysqlRecoverByUpload(req dto.CommonRecover) error {
|
||||||
app, err := appInstallRepo.LoadBaseInfo("mysql", "")
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
file := req.File
|
file := req.File
|
||||||
fileName := path.Base(req.File)
|
fileName := path.Base(req.File)
|
||||||
if strings.HasSuffix(fileName, ".tar.gz") {
|
if strings.HasSuffix(fileName, ".tar.gz") {
|
||||||
@ -106,78 +89,92 @@ func (u *BackupService) MysqlRecoverByUpload(req dto.CommonRecover) error {
|
|||||||
}()
|
}()
|
||||||
}
|
}
|
||||||
|
|
||||||
if err := handleMysqlRecover(app, path.Dir(file), req.DetailName, fileName, false); err != nil {
|
req.File = path.Dir(file) + "/" + fileName
|
||||||
|
if err := handleMysqlRecover(req, false); err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
global.LOG.Info("recover from uploads successful!")
|
global.LOG.Info("recover from uploads successful!")
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func handleMysqlBackup(app *repo.RootInfo, backupDir, dbName, fileName string) error {
|
func handleMysqlBackup(dbName, targetDir, fileName string) error {
|
||||||
fileOp := files.NewFileOp()
|
dbInfo, err := mysqlRepo.Get(commonRepo.WithByName(dbName))
|
||||||
if !fileOp.Stat(backupDir) {
|
if err != nil {
|
||||||
if err := os.MkdirAll(backupDir, os.ModePerm); err != nil {
|
return err
|
||||||
return fmt.Errorf("mkdir %s failed, err: %v", backupDir, err)
|
}
|
||||||
}
|
cli, _, err := LoadMysqlClientByFrom(dbInfo.From)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
}
|
}
|
||||||
outfile, _ := os.OpenFile(backupDir+"/"+fileName, os.O_RDWR|os.O_CREATE, 0755)
|
|
||||||
global.LOG.Infof("start to mysqldump | gzip > %s.gzip", backupDir+"/"+fileName)
|
|
||||||
cmd := exec.Command("docker", "exec", app.ContainerName, "mysqldump", "-uroot", "-p"+app.Password, dbName)
|
|
||||||
gzipCmd := exec.Command("gzip", "-cf")
|
|
||||||
gzipCmd.Stdin, _ = cmd.StdoutPipe()
|
|
||||||
gzipCmd.Stdout = outfile
|
|
||||||
_ = gzipCmd.Start()
|
|
||||||
_ = cmd.Run()
|
|
||||||
_ = gzipCmd.Wait()
|
|
||||||
|
|
||||||
|
backupInfo := client.BackupInfo{
|
||||||
|
Name: dbName,
|
||||||
|
Format: dbInfo.Format,
|
||||||
|
TargetDir: targetDir,
|
||||||
|
FileName: fileName,
|
||||||
|
|
||||||
|
Timeout: 300,
|
||||||
|
}
|
||||||
|
if err := cli.Backup(backupInfo); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func handleMysqlRecover(mysqlInfo *repo.RootInfo, recoverDir, dbName, fileName string, isRollback bool) error {
|
func handleMysqlRecover(req dto.CommonRecover, isRollback bool) error {
|
||||||
isOk := false
|
isOk := false
|
||||||
if !isRollback {
|
fileOp := files.NewFileOp()
|
||||||
rollbackFile := fmt.Sprintf("%s/original/database/%s_%s.sql.gz", global.CONF.System.BaseDir, mysqlInfo.Name, time.Now().Format("20060102150405"))
|
if !fileOp.Stat(req.File) {
|
||||||
if err := handleMysqlBackup(mysqlInfo, path.Dir(rollbackFile), dbName, path.Base(rollbackFile)); err != nil {
|
return errors.New(fmt.Sprintf("%s file is not exist", req.File))
|
||||||
return fmt.Errorf("backup mysql db %s for rollback before recover failed, err: %v", mysqlInfo.Name, err)
|
}
|
||||||
|
dbInfo, err := mysqlRepo.Get(commonRepo.WithByName(req.DetailName))
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
cli, _, err := LoadMysqlClientByFrom(dbInfo.From)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
if isRollback {
|
||||||
|
rollbackFile := fmt.Sprintf("%s/original/database/%s_%s.sql.gz", global.CONF.System.BaseDir, req.DetailName, time.Now().Format("20060102150405"))
|
||||||
|
if err := cli.Backup(client.BackupInfo{
|
||||||
|
Name: req.DetailName,
|
||||||
|
Format: dbInfo.Format,
|
||||||
|
TargetDir: path.Dir(rollbackFile),
|
||||||
|
FileName: path.Base(rollbackFile),
|
||||||
|
|
||||||
|
Timeout: 300,
|
||||||
|
}); err != nil {
|
||||||
|
return fmt.Errorf("backup mysql db %s for rollback before recover failed, err: %v", req.DetailName, err)
|
||||||
}
|
}
|
||||||
defer func() {
|
defer func() {
|
||||||
if !isOk {
|
if !isOk {
|
||||||
global.LOG.Info("recover failed, start to rollback now")
|
global.LOG.Info("recover failed, start to rollback now")
|
||||||
if err := handleMysqlRecover(mysqlInfo, path.Dir(rollbackFile), dbName, path.Base(rollbackFile), true); err != nil {
|
if err := cli.Recover(client.RecoverInfo{
|
||||||
global.LOG.Errorf("rollback mysql db %s from %s failed, err: %v", dbName, rollbackFile, err)
|
Name: req.DetailName,
|
||||||
return
|
Format: dbInfo.Format,
|
||||||
|
SourceFile: rollbackFile,
|
||||||
|
|
||||||
|
Timeout: 300,
|
||||||
|
}); err != nil {
|
||||||
|
global.LOG.Errorf("rollback mysql db %s from %s failed, err: %v", req.DetailName, rollbackFile, err)
|
||||||
}
|
}
|
||||||
global.LOG.Infof("rollback mysql db %s from %s successful", dbName, rollbackFile)
|
global.LOG.Infof("rollback mysql db %s from %s successful", req.DetailName, rollbackFile)
|
||||||
_ = os.RemoveAll(rollbackFile)
|
_ = os.RemoveAll(rollbackFile)
|
||||||
} else {
|
} else {
|
||||||
_ = os.RemoveAll(rollbackFile)
|
_ = os.RemoveAll(rollbackFile)
|
||||||
}
|
}
|
||||||
}()
|
}()
|
||||||
}
|
}
|
||||||
file := recoverDir + "/" + fileName
|
if err := cli.Recover(client.RecoverInfo{
|
||||||
fi, _ := os.Open(file)
|
Name: req.DetailName,
|
||||||
defer fi.Close()
|
Format: dbInfo.Format,
|
||||||
cmd := exec.Command("docker", "exec", "-i", mysqlInfo.ContainerName, "mysql", "-uroot", "-p"+mysqlInfo.Password, dbName)
|
SourceFile: req.File,
|
||||||
if strings.HasSuffix(fileName, ".gz") {
|
|
||||||
gzipFile, err := os.Open(file)
|
Timeout: 300,
|
||||||
if err != nil {
|
}); err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
|
||||||
defer gzipFile.Close()
|
|
||||||
gzipReader, err := gzip.NewReader(gzipFile)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
defer gzipReader.Close()
|
|
||||||
cmd.Stdin = gzipReader
|
|
||||||
} else {
|
|
||||||
cmd.Stdin = fi
|
|
||||||
}
|
}
|
||||||
stdout, err := cmd.CombinedOutput()
|
|
||||||
stdStr := strings.ReplaceAll(string(stdout), "mysql: [Warning] Using a password on the command line interface can be insecure.\n", "")
|
|
||||||
if err != nil || strings.HasPrefix(string(stdStr), "ERROR ") {
|
|
||||||
return errors.New(stdStr)
|
|
||||||
}
|
|
||||||
isOk = true
|
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
@ -10,7 +10,6 @@ import (
|
|||||||
"time"
|
"time"
|
||||||
|
|
||||||
"github.com/1Panel-dev/1Panel/backend/app/model"
|
"github.com/1Panel-dev/1Panel/backend/app/model"
|
||||||
"github.com/1Panel-dev/1Panel/backend/app/repo"
|
|
||||||
"github.com/1Panel-dev/1Panel/backend/constant"
|
"github.com/1Panel-dev/1Panel/backend/constant"
|
||||||
"github.com/1Panel-dev/1Panel/backend/global"
|
"github.com/1Panel-dev/1Panel/backend/global"
|
||||||
"github.com/1Panel-dev/1Panel/backend/utils/cloud_storage"
|
"github.com/1Panel-dev/1Panel/backend/utils/cloud_storage"
|
||||||
@ -118,11 +117,7 @@ func (u *CronjobService) handleBackup(cronjob *model.Cronjob, startTime time.Tim
|
|||||||
|
|
||||||
switch cronjob.Type {
|
switch cronjob.Type {
|
||||||
case "database":
|
case "database":
|
||||||
app, err := appInstallRepo.LoadBaseInfo("mysql", "")
|
paths, err := u.handleDatabase(*cronjob, backup, startTime)
|
||||||
if err != nil {
|
|
||||||
return "", err
|
|
||||||
}
|
|
||||||
paths, err := u.handleDatabase(*cronjob, app, backup, startTime)
|
|
||||||
return strings.Join(paths, ","), err
|
return strings.Join(paths, ","), err
|
||||||
case "website":
|
case "website":
|
||||||
paths, err := u.handleWebsite(*cronjob, backup, startTime)
|
paths, err := u.handleWebsite(*cronjob, backup, startTime)
|
||||||
@ -252,7 +247,7 @@ func handleUnTar(sourceFile, targetDir string) error {
|
|||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (u *CronjobService) handleDatabase(cronjob model.Cronjob, app *repo.RootInfo, backup model.BackupAccount, startTime time.Time) ([]string, error) {
|
func (u *CronjobService) handleDatabase(cronjob model.Cronjob, backup model.BackupAccount, startTime time.Time) ([]string, error) {
|
||||||
var paths []string
|
var paths []string
|
||||||
localDir, err := loadLocalDir()
|
localDir, err := loadLocalDir()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
@ -282,15 +277,20 @@ func (u *CronjobService) handleDatabase(cronjob model.Cronjob, app *repo.RootInf
|
|||||||
var record model.BackupRecord
|
var record model.BackupRecord
|
||||||
|
|
||||||
record.Type = "mysql"
|
record.Type = "mysql"
|
||||||
record.Name = app.Name
|
|
||||||
record.Source = "LOCAL"
|
record.Source = "LOCAL"
|
||||||
record.BackupType = backup.Type
|
record.BackupType = backup.Type
|
||||||
|
|
||||||
backupDir := fmt.Sprintf("%s/database/mysql/%s/%s", localDir, app.Name, dbName)
|
dbInfo, err := mysqlRepo.Get(commonRepo.WithByName(dbName))
|
||||||
record.FileName = fmt.Sprintf("db_%s_%s.sql.gz", dbName, startTime.Format("20060102150405"))
|
if err != nil {
|
||||||
if err = handleMysqlBackup(app, backupDir, dbName, record.FileName); err != nil {
|
|
||||||
return paths, err
|
return paths, err
|
||||||
}
|
}
|
||||||
|
record.Name = dbInfo.MysqlName
|
||||||
|
backupDir := fmt.Sprintf("%s/database/mysql/%s/%s", localDir, record.Name, dbName)
|
||||||
|
record.FileName = fmt.Sprintf("db_%s_%s.sql.gz", dbName, startTime.Format("20060102150405"))
|
||||||
|
if err = handleMysqlBackup(dbName, backupDir, record.FileName); err != nil {
|
||||||
|
return paths, err
|
||||||
|
}
|
||||||
|
|
||||||
record.DetailName = dbName
|
record.DetailName = dbName
|
||||||
record.FileDir = backupDir
|
record.FileDir = backupDir
|
||||||
itemFileDir := strings.TrimPrefix(backupDir, localDir+"/")
|
itemFileDir := strings.TrimPrefix(backupDir, localDir+"/")
|
||||||
|
@ -93,7 +93,7 @@ func (u *MysqlService) Create(ctx context.Context, req dto.MysqlDBCreate) (*mode
|
|||||||
return nil, errors.New("Cannot set 127.0.0.1 as address")
|
return nil, errors.New("Cannot set 127.0.0.1 as address")
|
||||||
}
|
}
|
||||||
|
|
||||||
cli, version, err := loadClientByFrom(req.From)
|
cli, version, err := LoadMysqlClientByFrom(req.From)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
@ -105,11 +105,7 @@ func (u *MysqlService) Create(ctx context.Context, req dto.MysqlDBCreate) (*mode
|
|||||||
}
|
}
|
||||||
createItem.MysqlName = app.Name
|
createItem.MysqlName = app.Name
|
||||||
} else {
|
} else {
|
||||||
mysqlData, err := remoteDBRepo.Get(remoteDBRepo.WithByFrom(req.From))
|
createItem.MysqlName = req.From
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
createItem.MysqlName = mysqlData.Name
|
|
||||||
}
|
}
|
||||||
defer cli.Close()
|
defer cli.Close()
|
||||||
if err := cli.Create(client.CreateInfo{
|
if err := cli.Create(client.CreateInfo{
|
||||||
@ -162,7 +158,7 @@ func (u *MysqlService) Delete(ctx context.Context, req dto.MysqlDBDelete) error
|
|||||||
if err != nil && !req.ForceDelete {
|
if err != nil && !req.ForceDelete {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
cli, version, err := loadClientByFrom(db.From)
|
cli, version, err := LoadMysqlClientByFrom(db.From)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
@ -207,7 +203,7 @@ func (u *MysqlService) ChangePassword(info dto.ChangeDBInfo) error {
|
|||||||
if cmd.CheckIllegal(info.Value) {
|
if cmd.CheckIllegal(info.Value) {
|
||||||
return buserr.New(constant.ErrCmdIllegal)
|
return buserr.New(constant.ErrCmdIllegal)
|
||||||
}
|
}
|
||||||
cli, version, err := loadClientByFrom(info.From)
|
cli, version, err := LoadMysqlClientByFrom(info.From)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
@ -236,22 +232,31 @@ func (u *MysqlService) ChangePassword(info dto.ChangeDBInfo) error {
|
|||||||
}
|
}
|
||||||
|
|
||||||
if info.ID != 0 {
|
if info.ID != 0 {
|
||||||
// appRess, _ := appInstallResourceRepo.GetBy(appInstallResourceRepo.WithLinkId(app.ID), appInstallResourceRepo.WithResourceId(mysqlData.ID))
|
var appRess []model.AppInstallResource
|
||||||
// for _, appRes := range appRess {
|
if info.From == "local" {
|
||||||
// appInstall, err := appInstallRepo.GetFirst(commonRepo.WithByID(appRes.AppInstallId))
|
app, err := appInstallRepo.LoadBaseInfo("mysql", "")
|
||||||
// if err != nil {
|
if err != nil {
|
||||||
// return err
|
return err
|
||||||
// }
|
}
|
||||||
// appModel, err := appRepo.GetFirst(commonRepo.WithByID(appInstall.AppId))
|
appRess, _ = appInstallResourceRepo.GetBy(appInstallResourceRepo.WithLinkId(app.ID), appInstallResourceRepo.WithResourceId(mysqlData.ID))
|
||||||
// if err != nil {
|
} else {
|
||||||
// return err
|
appRess, _ = appInstallResourceRepo.GetBy(appInstallResourceRepo.WithResourceId(mysqlData.ID))
|
||||||
// }
|
}
|
||||||
|
for _, appRes := range appRess {
|
||||||
|
appInstall, err := appInstallRepo.GetFirst(commonRepo.WithByID(appRes.AppInstallId))
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
appModel, err := appRepo.GetFirst(commonRepo.WithByID(appInstall.AppId))
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
// global.LOG.Infof("start to update mysql password used by app %s-%s", appModel.Key, appInstall.Name)
|
global.LOG.Infof("start to update mysql password used by app %s-%s", appModel.Key, appInstall.Name)
|
||||||
// if err := updateInstallInfoInDB(appModel.Key, appInstall.Name, "user-password", true, info.Value); err != nil {
|
if err := updateInstallInfoInDB(appModel.Key, appInstall.Name, "user-password", true, info.Value); err != nil {
|
||||||
// return err
|
return err
|
||||||
// }
|
}
|
||||||
// }
|
}
|
||||||
global.LOG.Info("excute password change sql successful")
|
global.LOG.Info("excute password change sql successful")
|
||||||
_ = mysqlRepo.Update(mysqlData.ID, map[string]interface{}{"password": info.Value})
|
_ = mysqlRepo.Update(mysqlData.ID, map[string]interface{}{"password": info.Value})
|
||||||
return nil
|
return nil
|
||||||
@ -270,7 +275,7 @@ func (u *MysqlService) ChangeAccess(info dto.ChangeDBInfo) error {
|
|||||||
if cmd.CheckIllegal(info.Value) {
|
if cmd.CheckIllegal(info.Value) {
|
||||||
return buserr.New(constant.ErrCmdIllegal)
|
return buserr.New(constant.ErrCmdIllegal)
|
||||||
}
|
}
|
||||||
cli, version, err := loadClientByFrom(info.From)
|
cli, version, err := LoadMysqlClientByFrom(info.From)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
@ -535,15 +540,17 @@ func updateMyCnf(oldFiles []string, group string, param string, value interface{
|
|||||||
return newFiles
|
return newFiles
|
||||||
}
|
}
|
||||||
|
|
||||||
func loadClientByFrom(from string) (mysql.MysqlClient, string, error) {
|
func LoadMysqlClientByFrom(from string) (mysql.MysqlClient, string, error) {
|
||||||
var (
|
var (
|
||||||
dbInfo client.DBInfo
|
dbInfo client.DBInfo
|
||||||
version string
|
version string
|
||||||
err error
|
err error
|
||||||
)
|
)
|
||||||
|
|
||||||
|
dbInfo.From = from
|
||||||
|
dbInfo.Timeout = 300
|
||||||
if from != "local" {
|
if from != "local" {
|
||||||
databaseItem, err := remoteDBRepo.Get(remoteDBRepo.WithByFrom(from))
|
databaseItem, err := remoteDBRepo.Get(commonRepo.WithByName(from))
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, "", err
|
return nil, "", err
|
||||||
}
|
}
|
||||||
|
@ -17,7 +17,7 @@ type MysqlClient interface {
|
|||||||
ChangePassword(info client.PasswordChangeInfo) error
|
ChangePassword(info client.PasswordChangeInfo) error
|
||||||
ChangeAccess(info client.AccessChangeInfo) error
|
ChangeAccess(info client.AccessChangeInfo) error
|
||||||
|
|
||||||
Backup(info client.BackupInfo) (string, error)
|
Backup(info client.BackupInfo) error
|
||||||
Recover(info client.RecoverInfo) error
|
Recover(info client.RecoverInfo) error
|
||||||
|
|
||||||
Close()
|
Close()
|
||||||
|
@ -56,6 +56,7 @@ type BackupInfo struct {
|
|||||||
Name string `json:"name"`
|
Name string `json:"name"`
|
||||||
Format string `json:"format"`
|
Format string `json:"format"`
|
||||||
TargetDir string `json:"targetDir"`
|
TargetDir string `json:"targetDir"`
|
||||||
|
FileName string `json:"fileName"`
|
||||||
|
|
||||||
Timeout uint `json:"timeout"` // second
|
Timeout uint `json:"timeout"` // second
|
||||||
}
|
}
|
||||||
|
@ -205,16 +205,15 @@ func (r *Local) ChangeAccess(info AccessChangeInfo) error {
|
|||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (r *Local) Backup(info BackupInfo) (string, error) {
|
func (r *Local) Backup(info BackupInfo) error {
|
||||||
fileOp := files.NewFileOp()
|
fileOp := files.NewFileOp()
|
||||||
if !fileOp.Stat(info.TargetDir) {
|
if !fileOp.Stat(info.TargetDir) {
|
||||||
if err := os.MkdirAll(info.TargetDir, os.ModePerm); err != nil {
|
if err := os.MkdirAll(info.TargetDir, os.ModePerm); err != nil {
|
||||||
return "", fmt.Errorf("mkdir %s failed, err: %v", info.TargetDir, err)
|
return fmt.Errorf("mkdir %s failed, err: %v", info.TargetDir, err)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
fileName := fmt.Sprintf("%s/%s_%s.sql.gz", info.TargetDir, info.Name, time.Now().Format("20060102150405"))
|
outfile, _ := os.OpenFile(info.FileName, os.O_RDWR|os.O_CREATE, 0755)
|
||||||
outfile, _ := os.OpenFile(fileName, os.O_RDWR|os.O_CREATE, 0755)
|
global.LOG.Infof("start to mysqldump | gzip > %s.gzip", info.TargetDir+"/"+info.FileName)
|
||||||
global.LOG.Infof("start to mysqldump | gzip > %s.gzip", info.TargetDir+"/"+fileName)
|
|
||||||
cmd := exec.Command("docker", "exec", r.ContainerName, "mysqldump", "-uroot", "-p"+r.Password, info.Name)
|
cmd := exec.Command("docker", "exec", r.ContainerName, "mysqldump", "-uroot", "-p"+r.Password, info.Name)
|
||||||
gzipCmd := exec.Command("gzip", "-cf")
|
gzipCmd := exec.Command("gzip", "-cf")
|
||||||
gzipCmd.Stdin, _ = cmd.StdoutPipe()
|
gzipCmd.Stdin, _ = cmd.StdoutPipe()
|
||||||
@ -222,7 +221,7 @@ func (r *Local) Backup(info BackupInfo) (string, error) {
|
|||||||
_ = gzipCmd.Start()
|
_ = gzipCmd.Start()
|
||||||
_ = cmd.Run()
|
_ = cmd.Run()
|
||||||
_ = gzipCmd.Wait()
|
_ = gzipCmd.Wait()
|
||||||
return fileName, nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (r *Local) Recover(info RecoverInfo) error {
|
func (r *Local) Recover(info RecoverInfo) error {
|
||||||
|
@ -208,26 +208,26 @@ func (r *Remote) ChangeAccess(info AccessChangeInfo) error {
|
|||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (r *Remote) Backup(info BackupInfo) (string, error) {
|
func (r *Remote) Backup(info BackupInfo) error {
|
||||||
fileOp := files.NewFileOp()
|
fileOp := files.NewFileOp()
|
||||||
if !fileOp.Stat(info.TargetDir) {
|
if !fileOp.Stat(info.TargetDir) {
|
||||||
if err := os.MkdirAll(info.TargetDir, os.ModePerm); err != nil {
|
if err := os.MkdirAll(info.TargetDir, os.ModePerm); err != nil {
|
||||||
return "", fmt.Errorf("mkdir %s failed, err: %v", info.TargetDir, err)
|
return fmt.Errorf("mkdir %s failed, err: %v", info.TargetDir, err)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
fileName := fmt.Sprintf("%s/%s_%s.sql", info.TargetDir, info.Name, time.Now().Format("20060102150405"))
|
fileNameItem := info.TargetDir + "/" + strings.TrimSuffix(info.FileName, ".gz")
|
||||||
dns := fmt.Sprintf("%s:%s@tcp(%s:%v)/%s?charset=%s&parseTime=true&loc=Asia%sShanghai", r.User, r.Password, r.Address, r.Port, info.Name, info.Format, "%2F")
|
dns := fmt.Sprintf("%s:%s@tcp(%s:%v)/%s?charset=%s&parseTime=true&loc=Asia%sShanghai", r.User, r.Password, r.Address, r.Port, info.Name, info.Format, "%2F")
|
||||||
|
|
||||||
f, _ := os.OpenFile(fileName, os.O_RDWR|os.O_CREATE, 0755)
|
f, _ := os.OpenFile(fileNameItem, os.O_RDWR|os.O_CREATE, 0755)
|
||||||
defer f.Close()
|
defer f.Close()
|
||||||
if err := mysqldump.Dump(dns, mysqldump.WithData(), mysqldump.WithWriter(f)); err != nil {
|
if err := mysqldump.Dump(dns, mysqldump.WithData(), mysqldump.WithDropTable(), mysqldump.WithWriter(f)); err != nil {
|
||||||
return "", err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
if err := fileOp.Compress([]string{fileName}, info.TargetDir, path.Base(fileName)+".gz", files.Gz); err != nil {
|
if err := fileOp.Compress([]string{fileNameItem}, info.TargetDir, info.FileName, files.Gz); err != nil {
|
||||||
return "", err
|
return err
|
||||||
}
|
}
|
||||||
return fileName, nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (r *Remote) Recover(info RecoverInfo) error {
|
func (r *Remote) Recover(info RecoverInfo) error {
|
||||||
@ -235,13 +235,7 @@ func (r *Remote) Recover(info RecoverInfo) error {
|
|||||||
fileName := info.SourceFile
|
fileName := info.SourceFile
|
||||||
if strings.HasSuffix(info.SourceFile, ".sql.gz") {
|
if strings.HasSuffix(info.SourceFile, ".sql.gz") {
|
||||||
fileName = strings.TrimSuffix(info.SourceFile, ".gz")
|
fileName = strings.TrimSuffix(info.SourceFile, ".gz")
|
||||||
if err := fileOp.Decompress(info.SourceFile, fileName, files.Gz); err != nil {
|
if err := fileOp.Decompress(info.SourceFile, path.Dir(fileName), files.Gz); err != nil {
|
||||||
return err
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if strings.HasSuffix(info.SourceFile, ".tar.gz") {
|
|
||||||
fileName = strings.TrimSuffix(info.SourceFile, ".tar.gz")
|
|
||||||
if err := fileOp.Decompress(info.SourceFile, fileName, files.TarGz); err != nil {
|
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -251,7 +245,7 @@ func (r *Remote) Recover(info RecoverInfo) error {
|
|||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
defer f.Close()
|
defer f.Close()
|
||||||
if err := mysqldump.Source(dns, f); err != nil {
|
if err := mysqldump.Source(dns, f, mysqldump.WithMergeInsert(1000)); err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
return nil
|
return nil
|
||||||
|
@ -17,6 +17,7 @@ export namespace Database {
|
|||||||
id: number;
|
id: number;
|
||||||
createdAt: Date;
|
createdAt: Date;
|
||||||
name: string;
|
name: string;
|
||||||
|
mysqlName: string;
|
||||||
from: string;
|
from: string;
|
||||||
format: string;
|
format: string;
|
||||||
username: string;
|
username: string;
|
||||||
|
@ -215,7 +215,6 @@ import { ElForm } from 'element-plus';
|
|||||||
import { Cronjob } from '@/api/interface/cronjob';
|
import { Cronjob } from '@/api/interface/cronjob';
|
||||||
import { addCronjob, editCronjob } from '@/api/modules/cronjob';
|
import { addCronjob, editCronjob } from '@/api/modules/cronjob';
|
||||||
import { loadDBNames } from '@/api/modules/database';
|
import { loadDBNames } from '@/api/modules/database';
|
||||||
import { CheckAppInstalled } from '@/api/modules/app';
|
|
||||||
import { GetWebsiteOptions } from '@/api/modules/website';
|
import { GetWebsiteOptions } from '@/api/modules/website';
|
||||||
import DrawerHeader from '@/components/drawer-header/index.vue';
|
import DrawerHeader from '@/components/drawer-header/index.vue';
|
||||||
import { MsgError, MsgSuccess } from '@/utils/message';
|
import { MsgError, MsgSuccess } from '@/utils/message';
|
||||||
@ -454,14 +453,8 @@ const loadContainers = async () => {
|
|||||||
};
|
};
|
||||||
|
|
||||||
const checkMysqlInstalled = async () => {
|
const checkMysqlInstalled = async () => {
|
||||||
const res = await CheckAppInstalled('mysql');
|
const data = await loadDBNames();
|
||||||
mysqlInfo.isExist = res.data.isExist;
|
mysqlInfo.dbNames = data.data;
|
||||||
mysqlInfo.name = res.data.name;
|
|
||||||
mysqlInfo.version = res.data.version;
|
|
||||||
if (mysqlInfo.isExist) {
|
|
||||||
const data = await loadDBNames();
|
|
||||||
mysqlInfo.dbNames = data.data;
|
|
||||||
}
|
|
||||||
};
|
};
|
||||||
|
|
||||||
function isBackup() {
|
function isBackup() {
|
||||||
|
@ -21,7 +21,7 @@
|
|||||||
<el-option
|
<el-option
|
||||||
v-for="(item, index) in dbOptions"
|
v-for="(item, index) in dbOptions"
|
||||||
:key="index"
|
:key="index"
|
||||||
:value="item.address"
|
:value="item.name"
|
||||||
:label="item.name"
|
:label="item.name"
|
||||||
></el-option>
|
></el-option>
|
||||||
</el-option-group>
|
</el-option-group>
|
||||||
@ -265,14 +265,6 @@ const onOpenDialog = async () => {
|
|||||||
};
|
};
|
||||||
|
|
||||||
const dialogBackupRef = ref();
|
const dialogBackupRef = ref();
|
||||||
const onOpenBackupDialog = async (dbName: string) => {
|
|
||||||
let params = {
|
|
||||||
type: 'mysql',
|
|
||||||
name: mysqlName.value,
|
|
||||||
detailName: dbName,
|
|
||||||
};
|
|
||||||
dialogBackupRef.value!.acceptParams(params);
|
|
||||||
};
|
|
||||||
|
|
||||||
const uploadRef = ref();
|
const uploadRef = ref();
|
||||||
|
|
||||||
@ -422,22 +414,21 @@ const buttons = [
|
|||||||
},
|
},
|
||||||
{
|
{
|
||||||
label: i18n.global.t('database.backupList'),
|
label: i18n.global.t('database.backupList'),
|
||||||
disabled: (row: Database.MysqlDBInfo) => {
|
|
||||||
return row.from !== 'local';
|
|
||||||
},
|
|
||||||
click: (row: Database.MysqlDBInfo) => {
|
click: (row: Database.MysqlDBInfo) => {
|
||||||
onOpenBackupDialog(row.name);
|
let params = {
|
||||||
|
type: 'mysql',
|
||||||
|
name: row.mysqlName,
|
||||||
|
detailName: row.name,
|
||||||
|
};
|
||||||
|
dialogBackupRef.value!.acceptParams(params);
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
label: i18n.global.t('database.loadBackup'),
|
label: i18n.global.t('database.loadBackup'),
|
||||||
disabled: (row: Database.MysqlDBInfo) => {
|
|
||||||
return row.from !== 'local';
|
|
||||||
},
|
|
||||||
click: (row: Database.MysqlDBInfo) => {
|
click: (row: Database.MysqlDBInfo) => {
|
||||||
let params = {
|
let params = {
|
||||||
type: 'mysql',
|
type: 'mysql',
|
||||||
name: mysqlName.value,
|
name: mysqlName.value || row.name,
|
||||||
detailName: row.name,
|
detailName: row.name,
|
||||||
};
|
};
|
||||||
uploadRef.value!.acceptParams(params);
|
uploadRef.value!.acceptParams(params);
|
||||||
|
Loading…
x
Reference in New Issue
Block a user