From ed75ecc63bad35f090f4d20da4819f63da84d27f Mon Sep 17 00:00:00 2001 From: zhengkunwang <31820853+zhengkunwang223@users.noreply.github.com> Date: Fri, 27 Sep 2024 22:23:38 +0800 Subject: [PATCH] =?UTF-8?q?feat:=20=E7=BD=91=E7=AB=99=E5=A4=87=E4=BB=BD/?= =?UTF-8?q?=E6=81=A2=E5=A4=8D=E5=A2=9E=E5=8A=A0=E4=BB=BB=E5=8A=A1=E6=97=A5?= =?UTF-8?q?=E5=BF=97=20(#6603)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- agent/app/dto/backup.go | 3 + agent/app/repo/backup.go | 10 + agent/app/service/backup_app.go | 127 ++++--- agent/app/service/backup_website.go | 354 ++++++++++-------- agent/app/service/cronjob_backup.go | 4 +- agent/app/service/website.go | 10 +- agent/app/service/website_utils.go | 4 +- agent/app/task/task.go | 2 +- .../cmd/server/nginx_conf/php_extensions.json | 2 +- agent/i18n/lang/en.yaml | 12 +- agent/i18n/lang/zh-Hant.yaml | 12 +- agent/i18n/lang/zh.yaml | 11 +- frontend/src/api/interface/backup.ts | 2 + frontend/src/api/interface/website.ts | 2 + frontend/src/components/backup/index.vue | 88 +++-- .../website/website/config/basic/index.vue | 2 +- .../website/config/basic/resource/index.vue | 61 ++- 17 files changed, 432 insertions(+), 274 deletions(-) diff --git a/agent/app/dto/backup.go b/agent/app/dto/backup.go index 15537a500..555e65fa1 100644 --- a/agent/app/dto/backup.go +++ b/agent/app/dto/backup.go @@ -9,6 +9,7 @@ type CommonBackup struct { Name string `json:"name"` DetailName string `json:"detailName"` Secret string `json:"secret"` + TaskID string `json:"taskID"` } type CommonRecover struct { DownloadAccountID uint `json:"downloadAccountID" validate:"required"` @@ -17,6 +18,8 @@ type CommonRecover struct { DetailName string `json:"detailName"` File string `json:"file"` Secret string `json:"secret"` + TaskID string `json:"taskID"` + BackupRecordID uint `json:"backupRecordID"` } type RecordSearch struct { diff --git a/agent/app/repo/backup.go b/agent/app/repo/backup.go index 32af7dd5e..695e3a741 100644 --- a/agent/app/repo/backup.go +++ b/agent/app/repo/backup.go @@ -72,3 +72,13 @@ func (u *BackupRepo) WithByCronID(cronjobID uint) DBOption { return g.Where("cronjob_id = ?", cronjobID) } } + +func (u *BackupRepo) GetRecord(opts ...DBOption) (*model.BackupRecord, error) { + var record *model.BackupRecord + db := global.DB.Model(&model.BackupRecord{}) + for _, opt := range opts { + db = opt(db) + } + err := db.Find(record).Error + return record, err +} diff --git a/agent/app/service/backup_app.go b/agent/app/service/backup_app.go index 660a47773..983de5de1 100644 --- a/agent/app/service/backup_app.go +++ b/agent/app/service/backup_app.go @@ -4,6 +4,8 @@ import ( "context" "encoding/json" "fmt" + "github.com/1Panel-dev/1Panel/agent/app/task" + "github.com/1Panel-dev/1Panel/agent/i18n" "io/fs" "os" "path" @@ -36,7 +38,7 @@ func (u *BackupService) AppBackup(req dto.CommonBackup) (*model.BackupRecord, er backupDir := path.Join(global.CONF.System.Backup, itemDir) fileName := fmt.Sprintf("%s_%s.tar.gz", req.DetailName, timeNow+common.RandStrAndNum(5)) - if err := handleAppBackup(&install, backupDir, fileName, "", req.Secret); err != nil { + if err := handleAppBackup(&install, nil, backupDir, fileName, "", req.Secret, ""); err != nil { return nil, err } @@ -80,57 +82,88 @@ func (u *BackupService) AppRecover(req dto.CommonRecover) error { return nil } -func handleAppBackup(install *model.AppInstall, backupDir, fileName string, excludes string, secret string) error { - fileOp := files.NewFileOp() - tmpDir := fmt.Sprintf("%s/%s", backupDir, strings.ReplaceAll(fileName, ".tar.gz", "")) - if !fileOp.Stat(tmpDir) { - if err := os.MkdirAll(tmpDir, os.ModePerm); err != nil { - return fmt.Errorf("mkdir %s failed, err: %v", backupDir, err) +func backupDatabaseWithTask(parentTask *task.Task, resourceKey, tmpDir, name string, databaseID uint) error { + switch resourceKey { + case constant.AppMysql, constant.AppMariaDB: + db, err := mysqlRepo.Get(commonRepo.WithByID(databaseID)) + if err != nil { + return err } - } - defer func() { - _ = os.RemoveAll(tmpDir) - }() - - remarkInfo, _ := json.Marshal(install) - remarkInfoPath := fmt.Sprintf("%s/app.json", tmpDir) - if err := fileOp.SaveFile(remarkInfoPath, string(remarkInfo), fs.ModePerm); err != nil { - return err - } - - appPath := install.GetPath() - if err := handleTar(appPath, tmpDir, "app.tar.gz", excludes, ""); err != nil { - return err - } - - resources, _ := appInstallResourceRepo.GetBy(appInstallResourceRepo.WithAppInstallId(install.ID)) - for _, resource := range resources { - switch resource.Key { - case constant.AppMysql, constant.AppMariaDB: - db, err := mysqlRepo.Get(commonRepo.WithByID(resource.ResourceId)) - if err != nil { - return err - } - if err := handleMysqlBackup(db.MysqlName, resource.Key, db.Name, tmpDir, fmt.Sprintf("%s.sql.gz", install.Name)); err != nil { - return err - } - case constant.AppPostgresql: - db, err := postgresqlRepo.Get(commonRepo.WithByID(resource.ResourceId)) - if err != nil { - return err - } - if err := handlePostgresqlBackup(db.PostgresqlName, db.Name, tmpDir, fmt.Sprintf("%s.sql.gz", install.Name)); err != nil { - return err - } + parentTask.LogStart(task.GetTaskName(db.Name, task.TaskBackup, task.TaskScopeDatabase)) + if err := handleMysqlBackup(db.MysqlName, resourceKey, db.Name, tmpDir, fmt.Sprintf("%s.sql.gz", name)); err != nil { + return err } - } - - if err := handleTar(tmpDir, backupDir, fileName, "", secret); err != nil { - return err + parentTask.LogSuccess(task.GetTaskName(db.Name, task.TaskBackup, task.TaskScopeDatabase)) + case constant.AppPostgresql: + db, err := postgresqlRepo.Get(commonRepo.WithByID(databaseID)) + if err != nil { + return err + } + parentTask.LogStart(task.GetTaskName(db.Name, task.TaskBackup, task.TaskScopeDatabase)) + if err := handlePostgresqlBackup(db.PostgresqlName, db.Name, tmpDir, fmt.Sprintf("%s.sql.gz", name)); err != nil { + return err + } + parentTask.LogSuccess(task.GetTaskName(db.Name, task.TaskBackup, task.TaskScopeDatabase)) } return nil } +func handleAppBackup(install *model.AppInstall, parentTask *task.Task, backupDir, fileName, excludes, secret, taskID string) error { + var ( + err error + backupTask *task.Task + ) + backupTask = parentTask + if parentTask == nil { + backupTask, err = task.NewTaskWithOps(install.Name, task.TaskBackup, task.TaskScopeApp, taskID, install.ID) + if err != nil { + return err + } + } + + backupApp := func(t *task.Task) error { + fileOp := files.NewFileOp() + tmpDir := fmt.Sprintf("%s/%s", backupDir, strings.ReplaceAll(fileName, ".tar.gz", "")) + if !fileOp.Stat(tmpDir) { + if err := os.MkdirAll(tmpDir, os.ModePerm); err != nil { + return fmt.Errorf("mkdir %s failed, err: %v", backupDir, err) + } + } + defer func() { + _ = os.RemoveAll(tmpDir) + }() + + remarkInfo, _ := json.Marshal(install) + remarkInfoPath := fmt.Sprintf("%s/app.json", tmpDir) + if err := fileOp.SaveFile(remarkInfoPath, string(remarkInfo), fs.ModePerm); err != nil { + return err + } + + appPath := install.GetPath() + if err := handleTar(appPath, tmpDir, "app.tar.gz", excludes, ""); err != nil { + return err + } + + resources, _ := appInstallResourceRepo.GetBy(appInstallResourceRepo.WithAppInstallId(install.ID)) + for _, resource := range resources { + if err = backupDatabaseWithTask(t, resource.Key, tmpDir, install.Name, resource.ResourceId); err != nil { + return err + } + } + t.LogStart(i18n.GetMsgByKey("CompressDir")) + if err := handleTar(tmpDir, backupDir, fileName, "", secret); err != nil { + return err + } + t.Log(i18n.GetWithName("CompressFileSuccess", fileName)) + return nil + } + backupTask.AddSubTask(task.GetTaskName(install.Name, task.TaskBackup, task.TaskScopeApp), backupApp, nil) + if parentTask != nil { + return backupApp(parentTask) + } + return backupTask.Execute() +} + func handleAppRecover(install *model.AppInstall, recoverFile string, isRollback bool, secret string) error { isOk := false fileOp := files.NewFileOp() @@ -160,7 +193,7 @@ func handleAppRecover(install *model.AppInstall, recoverFile string, isRollback if !isRollback { rollbackFile := path.Join(global.CONF.System.TmpDir, fmt.Sprintf("app/%s_%s.tar.gz", install.Name, time.Now().Format(constant.DateTimeSlimLayout))) - if err := handleAppBackup(install, path.Dir(rollbackFile), path.Base(rollbackFile), "", ""); err != nil { + if err := handleAppBackup(install, nil, path.Dir(rollbackFile), path.Base(rollbackFile), "", "", ""); err != nil { return fmt.Errorf("backup app %s for rollback before recover failed, err: %v", install.Name, err) } defer func() { diff --git a/agent/app/service/backup_website.go b/agent/app/service/backup_website.go index 1ca1728e5..30e1670da 100644 --- a/agent/app/service/backup_website.go +++ b/agent/app/service/backup_website.go @@ -3,6 +3,11 @@ package service import ( "encoding/json" "fmt" + "github.com/1Panel-dev/1Panel/agent/app/task" + "github.com/1Panel-dev/1Panel/agent/i18n" + "github.com/1Panel-dev/1Panel/agent/utils/cmd" + "github.com/1Panel-dev/1Panel/agent/utils/compose" + "github.com/pkg/errors" "io/fs" "os" "path" @@ -14,11 +19,8 @@ import ( "github.com/1Panel-dev/1Panel/agent/buserr" "github.com/1Panel-dev/1Panel/agent/constant" "github.com/1Panel-dev/1Panel/agent/global" - "github.com/1Panel-dev/1Panel/agent/utils/cmd" "github.com/1Panel-dev/1Panel/agent/utils/common" - "github.com/1Panel-dev/1Panel/agent/utils/compose" "github.com/1Panel-dev/1Panel/agent/utils/files" - "github.com/pkg/errors" ) func (u *BackupService) WebsiteBackup(req dto.CommonBackup) error { @@ -31,214 +33,234 @@ func (u *BackupService) WebsiteBackup(req dto.CommonBackup) error { itemDir := fmt.Sprintf("website/%s", req.Name) backupDir := path.Join(global.CONF.System.Backup, itemDir) fileName := fmt.Sprintf("%s_%s.tar.gz", website.PrimaryDomain, timeNow+common.RandStrAndNum(5)) - if err := handleWebsiteBackup(&website, backupDir, fileName, "", req.Secret); err != nil { - return err - } - record := &model.BackupRecord{ - Type: "website", - Name: website.PrimaryDomain, - DetailName: req.DetailName, - SourceAccountIDs: "1", - DownloadAccountID: 1, - FileDir: itemDir, - FileName: fileName, - } - if err := backupRepo.CreateRecord(record); err != nil { - global.LOG.Errorf("save backup record failed, err: %v", err) - return err - } + go func() { + if err = handleWebsiteBackup(&website, backupDir, fileName, "", req.Secret, req.TaskID); err != nil { + global.LOG.Errorf("backup website %s failed, err: %v", website.Alias, err) + return + } + record := &model.BackupRecord{ + Type: "website", + Name: website.PrimaryDomain, + DetailName: req.DetailName, + SourceAccountIDs: "1", + DownloadAccountID: 1, + FileDir: itemDir, + FileName: fileName, + } + if err = backupRepo.CreateRecord(record); err != nil { + global.LOG.Errorf("save backup record failed, err: %v", err) + return + } + }() return nil } func (u *BackupService) WebsiteRecover(req dto.CommonRecover) error { - fileOp := files.NewFileOp() - if !fileOp.Stat(req.File) { - return buserr.WithName("ErrFileNotFound", req.File) - } website, err := websiteRepo.GetFirst(websiteRepo.WithAlias(req.DetailName)) if err != nil { return err } - global.LOG.Infof("recover website %s from backup file %s", req.Name, req.File) - if err := handleWebsiteRecover(&website, req.File, false, req.Secret); err != nil { - return err - } + go func() { + if err := handleWebsiteRecover(&website, req.File, false, req.Secret, req.TaskID); err != nil { + global.LOG.Errorf("recover website %s failed, err: %v", website.Alias, err) + } + }() return nil } -func handleWebsiteRecover(website *model.Website, recoverFile string, isRollback bool, secret string) error { - fileOp := files.NewFileOp() - tmpPath := strings.ReplaceAll(recoverFile, ".tar.gz", "") - if err := handleUnTar(recoverFile, path.Dir(recoverFile), secret); err != nil { - return err - } - defer func() { - _ = os.RemoveAll(tmpPath) - }() - - var oldWebsite model.Website - websiteJson, err := os.ReadFile(tmpPath + "/website.json") +func handleWebsiteRecover(website *model.Website, recoverFile string, isRollback bool, secret, taskID string) error { + recoverTask, err := task.NewTaskWithOps(website.PrimaryDomain, task.TaskRecover, task.TaskScopeWebsite, taskID, website.ID) if err != nil { return err } - if err := json.Unmarshal(websiteJson, &oldWebsite); err != nil { - return fmt.Errorf("unmarshal app.json failed, err: %v", err) - } - - if err := checkValidOfWebsite(&oldWebsite, website); err != nil { - return err - } - - temPathWithName := tmpPath + "/" + website.Alias - if !fileOp.Stat(tmpPath+"/website.json") || !fileOp.Stat(temPathWithName+".conf") || !fileOp.Stat(temPathWithName+".web.tar.gz") { - return buserr.WithDetail(constant.ErrBackupExist, ".conf or .web.tar.gz", nil) - } - if website.Type == constant.Deployment { - if !fileOp.Stat(temPathWithName + ".app.tar.gz") { - return buserr.WithDetail(constant.ErrBackupExist, ".app.tar.gz", nil) - } - } - - isOk := false - if !isRollback { - rollbackFile := path.Join(global.CONF.System.TmpDir, fmt.Sprintf("website/%s_%s.tar.gz", website.Alias, time.Now().Format(constant.DateTimeSlimLayout))) - if err := handleWebsiteBackup(website, path.Dir(rollbackFile), path.Base(rollbackFile), "", ""); err != nil { - return fmt.Errorf("backup website %s for rollback before recover failed, err: %v", website.Alias, err) + recoverTask.AddSubTask(task.GetTaskName(website.PrimaryDomain, task.TaskRecover, task.TaskScopeWebsite), func(t *task.Task) error { + fileOp := files.NewFileOp() + tmpPath := strings.ReplaceAll(recoverFile, ".tar.gz", "") + t.Log(i18n.GetWithName("DeCompressFile", recoverFile)) + if err = handleUnTar(recoverFile, path.Dir(recoverFile), secret); err != nil { + return err } defer func() { - if !isOk { - global.LOG.Info("recover failed, start to rollback now") - if err := handleWebsiteRecover(website, rollbackFile, true, ""); err != nil { - global.LOG.Errorf("rollback website %s from %s failed, err: %v", website.Alias, rollbackFile, err) - return - } - global.LOG.Infof("rollback website %s from %s successful", website.Alias, rollbackFile) - _ = os.RemoveAll(rollbackFile) - } else { - _ = os.RemoveAll(rollbackFile) - } + _ = os.RemoveAll(tmpPath) }() - } - nginxInfo, err := appInstallRepo.LoadBaseInfo(constant.AppOpenresty, "") - if err != nil { - return err - } - nginxConfPath := fmt.Sprintf("%s/openresty/%s/conf/conf.d", constant.AppInstallDir, nginxInfo.Name) - if err := fileOp.CopyFile(fmt.Sprintf("%s/%s.conf", tmpPath, website.Alias), nginxConfPath); err != nil { - global.LOG.Errorf("handle recover from conf.d failed, err: %v", err) - return err - } - - switch website.Type { - case constant.Deployment: - app, err := appInstallRepo.GetFirst(commonRepo.WithByID(website.AppInstallID)) + var oldWebsite model.Website + websiteJson, err := os.ReadFile(tmpPath + "/website.json") if err != nil { return err } - if err := handleAppRecover(&app, fmt.Sprintf("%s/%s.app.tar.gz", tmpPath, website.Alias), true, ""); err != nil { - global.LOG.Errorf("handle recover from app.tar.gz failed, err: %v", err) + if err = json.Unmarshal(websiteJson, &oldWebsite); err != nil { + return fmt.Errorf("unmarshal app.json failed, err: %v", err) + } + + if err = checkValidOfWebsite(&oldWebsite, website); err != nil { + t.Log(i18n.GetWithName("ErrCheckValid", err.Error())) return err } - if _, err := compose.Restart(fmt.Sprintf("%s/%s/%s/docker-compose.yml", constant.AppInstallDir, app.App.Key, app.Name)); err != nil { - global.LOG.Errorf("docker-compose restart failed, err: %v", err) - return err + + temPathWithName := tmpPath + "/" + website.Alias + if !fileOp.Stat(tmpPath+"/website.json") || !fileOp.Stat(temPathWithName+".conf") || !fileOp.Stat(temPathWithName+".web.tar.gz") { + return buserr.WithDetail(constant.ErrBackupExist, ".conf or .web.tar.gz", nil) } - case constant.Runtime: - runtime, err := runtimeRepo.GetFirst(commonRepo.WithByID(website.RuntimeID)) + if website.Type == constant.Deployment { + if !fileOp.Stat(temPathWithName + ".app.tar.gz") { + return buserr.WithDetail(constant.ErrBackupExist, ".app.tar.gz", nil) + } + } + + isOk := false + if !isRollback { + rollbackFile := path.Join(global.CONF.System.TmpDir, fmt.Sprintf("website/%s_%s.tar.gz", website.Alias, time.Now().Format(constant.DateTimeSlimLayout))) + if err := handleWebsiteBackup(website, path.Dir(rollbackFile), path.Base(rollbackFile), "", "", ""); err != nil { + return fmt.Errorf("backup website %s for rollback before recover failed, err: %v", website.Alias, err) + } + defer func() { + if !isOk { + t.LogStart(i18n.GetMsgByKey("Rollback")) + if err := handleWebsiteRecover(website, rollbackFile, true, "", taskID); err != nil { + t.LogFailedWithErr(i18n.GetMsgByKey("Rollback"), err) + return + } + t.LogSuccess(i18n.GetMsgByKey("Rollback")) + _ = os.RemoveAll(rollbackFile) + } else { + _ = os.RemoveAll(rollbackFile) + } + }() + } + + nginxInfo, err := appInstallRepo.LoadBaseInfo(constant.AppOpenresty, "") if err != nil { return err } - if runtime.Type == constant.RuntimeNode || runtime.Type == constant.RuntimeJava || runtime.Type == constant.RuntimeGo { - if err := handleRuntimeRecover(runtime, fmt.Sprintf("%s/%s.runtime.tar.gz", tmpPath, website.Alias), true, ""); err != nil { + if err = fileOp.CopyFile(fmt.Sprintf("%s/%s.conf", tmpPath, website.Alias), GetSitePath(*website, SiteConfDir)); err != nil { + return err + } + + switch website.Type { + case constant.Deployment: + app, err := appInstallRepo.GetFirst(commonRepo.WithByID(website.AppInstallID)) + if err != nil { return err } - global.LOG.Info("put runtime.tar.gz into tmp dir successful") + taskName := task.GetTaskName(app.Name, task.TaskRecover, task.TaskScopeApp) + t.LogStart(taskName) + if err := handleAppRecover(&app, fmt.Sprintf("%s/%s.app.tar.gz", tmpPath, website.Alias), true, ""); err != nil { + t.LogFailedWithErr(taskName, err) + return err + } + t.LogSuccess(taskName) + if _, err = compose.DownAndUp(fmt.Sprintf("%s/%s/%s/docker-compose.yml", constant.AppInstallDir, app.App.Key, app.Name)); err != nil { + t.LogFailedWithErr("Run", err) + return err + } + case constant.Runtime: + runtime, err := runtimeRepo.GetFirst(commonRepo.WithByID(website.RuntimeID)) + if err != nil { + return err + } + taskName := task.GetTaskName(runtime.Name, task.TaskRecover, task.TaskScopeRuntime) + t.LogStart(taskName) + if err := handleRuntimeRecover(runtime, fmt.Sprintf("%s/%s.runtime.tar.gz", tmpPath, website.Alias), true, ""); err != nil { + t.LogFailedWithErr(taskName, err) + return err + } + t.LogSuccess(taskName) } - } - - siteDir := fmt.Sprintf("%s/openresty/%s/www/sites", constant.AppInstallDir, nginxInfo.Name) - if err := handleUnTar(fmt.Sprintf("%s/%s.web.tar.gz", tmpPath, website.Alias), siteDir, ""); err != nil { - global.LOG.Errorf("handle recover from web.tar.gz failed, err: %v", err) - return err - } - stdout, err := cmd.Execf("docker exec -i %s nginx -s reload", nginxInfo.ContainerName) - if err != nil { - global.LOG.Errorf("nginx -s reload failed, err: %s", stdout) - return errors.New(string(stdout)) - } - - oldWebsite.ID = website.ID - if err := websiteRepo.SaveWithoutCtx(&oldWebsite); err != nil { - global.LOG.Errorf("handle save website data failed, err: %v", err) - return err - } - isOk = true - return nil + taskName := i18n.GetMsgByKey("TaskRecover") + i18n.GetMsgByKey("websiteDir") + t.Log(taskName) + if err = handleUnTar(fmt.Sprintf("%s/%s.web.tar.gz", tmpPath, website.Alias), GetWebSiteRootDir(), ""); err != nil { + t.LogFailedWithErr(taskName, err) + return err + } + stdout, err := cmd.Execf("docker exec -i %s nginx -s reload", nginxInfo.ContainerName) + if err != nil { + return errors.New(stdout) + } + oldWebsite.ID = website.ID + if err := websiteRepo.SaveWithoutCtx(&oldWebsite); err != nil { + return err + } + isOk = true + return nil + }, nil) + return recoverTask.Execute() } -func handleWebsiteBackup(website *model.Website, backupDir, fileName string, excludes string, secret string) error { - fileOp := files.NewFileOp() - tmpDir := fmt.Sprintf("%s/%s", backupDir, strings.ReplaceAll(fileName, ".tar.gz", "")) - if !fileOp.Stat(tmpDir) { - if err := os.MkdirAll(tmpDir, os.ModePerm); err != nil { - return fmt.Errorf("mkdir %s failed, err: %v", backupDir, err) - } - } - defer func() { - _ = os.RemoveAll(tmpDir) - }() - - remarkInfo, _ := json.Marshal(website) - if err := fileOp.SaveFile(tmpDir+"/website.json", string(remarkInfo), fs.ModePerm); err != nil { - return err - } - global.LOG.Info("put website.json into tmp dir successful") - - nginxInfo, err := appInstallRepo.LoadBaseInfo(constant.AppOpenresty, "") +func handleWebsiteBackup(website *model.Website, backupDir, fileName, excludes, secret, taskID string) error { + backupTask, err := task.NewTaskWithOps(website.PrimaryDomain, task.TaskBackup, task.TaskScopeWebsite, taskID, website.ID) if err != nil { return err } - nginxConfFile := fmt.Sprintf("%s/openresty/%s/conf/conf.d/%s.conf", constant.AppInstallDir, nginxInfo.Name, website.Alias) - if err := fileOp.CopyFile(nginxConfFile, tmpDir); err != nil { - return err - } - global.LOG.Info("put openresty conf into tmp dir successful") + backupTask.AddSubTask(task.GetTaskName(website.PrimaryDomain, task.TaskBackup, task.TaskScopeWebsite), func(t *task.Task) error { + fileOp := files.NewFileOp() + tmpDir := fmt.Sprintf("%s/%s", backupDir, strings.ReplaceAll(fileName, ".tar.gz", "")) + if !fileOp.Stat(tmpDir) { + if err := os.MkdirAll(tmpDir, os.ModePerm); err != nil { + return fmt.Errorf("mkdir %s failed, err: %v", backupDir, err) + } + } + defer func() { + _ = os.RemoveAll(tmpDir) + }() - switch website.Type { - case constant.Deployment: - app, err := appInstallRepo.GetFirst(commonRepo.WithByID(website.AppInstallID)) - if err != nil { + remarkInfo, _ := json.Marshal(website) + if err = fileOp.SaveFile(tmpDir+"/website.json", string(remarkInfo), fs.ModePerm); err != nil { return err } - if err := handleAppBackup(&app, tmpDir, fmt.Sprintf("%s.app.tar.gz", website.Alias), excludes, ""); err != nil { + nginxConfFile := GetSitePath(*website, SiteConf) + if err = fileOp.CopyFile(nginxConfFile, tmpDir); err != nil { return err } - global.LOG.Info("put app.tar.gz into tmp dir successful") - case constant.Runtime: - runtime, err := runtimeRepo.GetFirst(commonRepo.WithByID(website.RuntimeID)) - if err != nil { - return err - } - if runtime.Type == constant.RuntimeNode || runtime.Type == constant.RuntimeJava || runtime.Type == constant.RuntimeGo { - if err := handleRuntimeBackup(runtime, tmpDir, fmt.Sprintf("%s.runtime.tar.gz", website.Alias), excludes, ""); err != nil { + t.Log(i18n.GetMsgByKey("BackupNginxConfig")) + + switch website.Type { + case constant.Deployment: + app, err := appInstallRepo.GetFirst(commonRepo.WithByID(website.AppInstallID)) + if err != nil { return err } - global.LOG.Info("put runtime.tar.gz into tmp dir successful") + t.LogStart(task.GetTaskName(app.Name, task.TaskBackup, task.TaskScopeApp)) + if err = handleAppBackup(&app, backupTask, tmpDir, fmt.Sprintf("%s.app.tar.gz", website.Alias), excludes, "", ""); err != nil { + return err + } + t.LogSuccess(task.GetTaskName(app.Name, task.TaskBackup, task.TaskScopeApp)) + case constant.Runtime: + runtime, err := runtimeRepo.GetFirst(commonRepo.WithByID(website.RuntimeID)) + if err != nil { + return err + } + t.LogStart(task.GetTaskName(runtime.Name, task.TaskBackup, task.TaskScopeRuntime)) + if err = handleRuntimeBackup(runtime, tmpDir, fmt.Sprintf("%s.runtime.tar.gz", website.Alias), excludes, ""); err != nil { + return err + } + t.LogSuccess(task.GetTaskName(runtime.Name, task.TaskBackup, task.TaskScopeRuntime)) + if website.DbID > 0 { + if err = backupDatabaseWithTask(t, website.DbType, tmpDir, website.PrimaryDomain, website.DbID); err != nil { + return err + } + } + case constant.Static: + if website.DbID > 0 { + if err = backupDatabaseWithTask(t, website.DbType, tmpDir, website.PrimaryDomain, website.DbID); err != nil { + return err + } + } } - } - websiteDir := fmt.Sprintf("%s/openresty/%s/www/sites/%s", constant.AppInstallDir, nginxInfo.Name, website.Alias) - if err := handleTar(websiteDir, tmpDir, fmt.Sprintf("%s.web.tar.gz", website.Alias), excludes, ""); err != nil { - return err - } - global.LOG.Info("put web.tar.gz into tmp dir successful, now start to tar tmp dir") - if err := handleTar(tmpDir, backupDir, fileName, "", secret); err != nil { - return err - } - - return nil + websiteDir := GetSitePath(*website, SiteDir) + t.LogStart(i18n.GetMsgByKey("CompressDir")) + if err = handleTar(websiteDir, tmpDir, fmt.Sprintf("%s.web.tar.gz", website.Alias), excludes, ""); err != nil { + return err + } + if err = handleTar(tmpDir, backupDir, fileName, "", secret); err != nil { + return err + } + t.Log(i18n.GetWithName("CompressFileSuccess", fileName)) + return nil + }, nil) + return backupTask.Execute() } func checkValidOfWebsite(oldWebsite, website *model.Website) error { diff --git a/agent/app/service/cronjob_backup.go b/agent/app/service/cronjob_backup.go index b00b45976..d017159db 100644 --- a/agent/app/service/cronjob_backup.go +++ b/agent/app/service/cronjob_backup.go @@ -41,7 +41,7 @@ func (u *CronjobService) handleApp(cronjob model.Cronjob, startTime time.Time) e record.DownloadAccountID, record.SourceAccountIDs = cronjob.DownloadAccountID, cronjob.SourceAccountIDs backupDir := path.Join(global.CONF.System.TmpDir, fmt.Sprintf("app/%s/%s", app.App.Key, app.Name)) record.FileName = fmt.Sprintf("app_%s_%s.tar.gz", app.Name, startTime.Format(constant.DateTimeSlimLayout)+common.RandStrAndNum(5)) - if err := handleAppBackup(&app, backupDir, record.FileName, cronjob.ExclusionRules, cronjob.Secret); err != nil { + if err := handleAppBackup(&app, nil, backupDir, record.FileName, cronjob.ExclusionRules, cronjob.Secret, ""); err != nil { return err } downloadPath, err := u.uploadCronjobBackFile(cronjob, accountMap, path.Join(backupDir, record.FileName)) @@ -74,7 +74,7 @@ func (u *CronjobService) handleWebsite(cronjob model.Cronjob, startTime time.Tim record.DownloadAccountID, record.SourceAccountIDs = cronjob.DownloadAccountID, cronjob.SourceAccountIDs backupDir := path.Join(global.CONF.System.TmpDir, fmt.Sprintf("website/%s", web.PrimaryDomain)) record.FileName = fmt.Sprintf("website_%s_%s.tar.gz", web.PrimaryDomain, startTime.Format(constant.DateTimeSlimLayout)+common.RandStrAndNum(5)) - if err := handleWebsiteBackup(&web, backupDir, record.FileName, cronjob.ExclusionRules, cronjob.Secret); err != nil { + if err := handleWebsiteBackup(&web, backupDir, record.FileName, cronjob.ExclusionRules, cronjob.Secret, ""); err != nil { return err } downloadPath, err := u.uploadCronjobBackFile(cronjob, accountMap, path.Join(backupDir, record.FileName)) diff --git a/agent/app/service/website.go b/agent/app/service/website.go index cf9061138..d3b420d95 100644 --- a/agent/app/service/website.go +++ b/agent/app/service/website.go @@ -1172,11 +1172,7 @@ func (w WebsiteService) OpWebsiteLog(req request.WebsiteLogReq) (*response.Websi if err != nil { return nil, err } - nginx, err := getNginxFull(&website) - if err != nil { - return nil, err - } - sitePath := path.Join(nginx.SiteDir, "sites", website.Alias) + sitePath := GetSitePath(website, SiteDir) res := &response.WebsiteLog{ Content: "", } @@ -1243,7 +1239,7 @@ func (w WebsiteService) OpWebsiteLog(req request.WebsiteLogReq) (*response.Websi return nil, err } case constant.DeleteLog: - logPath := path.Join(nginx.Install.GetPath(), "www", "sites", website.Alias, "log", req.LogType) + logPath := path.Join(sitePath, "log", req.LogType) if err := files.NewFileOp().WriteFile(logPath, strings.NewReader(""), 0755); err != nil { return nil, err } @@ -3145,7 +3141,7 @@ func (w WebsiteService) ListDatabases() ([]response.Database, error) { } pgSqls, _ := postgresqlRepo.List() for _, db := range pgSqls { - database, _ := databaseRepo.Get(commonRepo.WithByName(db.Name)) + database, _ := databaseRepo.Get(commonRepo.WithByName(db.PostgresqlName)) if database.ID > 0 { res = append(res, response.Database{ ID: db.ID, diff --git a/agent/app/service/website_utils.go b/agent/app/service/website_utils.go index 189edcfec..5710c11e2 100644 --- a/agent/app/service/website_utils.go +++ b/agent/app/service/website_utils.go @@ -1205,6 +1205,7 @@ const ( SiteReWritePath = "SiteReWritePath" SiteRedirectDir = "SiteRedirectDir" SiteCacheDir = "SiteCacheDir" + SiteConfDir = "SiteConfDir" ) func GetSitePath(website model.Website, confType string) string { @@ -1231,7 +1232,8 @@ func GetSitePath(website model.Website, confType string) string { return path.Join(GteSiteDir(website.Alias), "rewrite", website.Alias+".conf") case SiteRedirectDir: return path.Join(GteSiteDir(website.Alias), "redirect") - + case SiteConfDir: + return path.Join(GetWebSiteRootDir(), "conf.d") } return "" } diff --git a/agent/app/task/task.go b/agent/app/task/task.go index e17f42b52..dcbabe0cb 100644 --- a/agent/app/task/task.go +++ b/agent/app/task/task.go @@ -251,7 +251,7 @@ func (t *Task) LogFailedWithErr(msg string, err error) { func (t *Task) LogSuccess(msg string) { t.Logger.Printf(msg + i18n.GetMsgByKey("Success")) } -func (t *Task) LogSuccessf(format string, v ...any) { +func (t *Task) LogSuccessF(format string, v ...any) { t.Logger.Printf(fmt.Sprintf(format, v...) + i18n.GetMsgByKey("Success")) } diff --git a/agent/cmd/server/nginx_conf/php_extensions.json b/agent/cmd/server/nginx_conf/php_extensions.json index bf7ff0e94..5db17a28d 100644 --- a/agent/cmd/server/nginx_conf/php_extensions.json +++ b/agent/cmd/server/nginx_conf/php_extensions.json @@ -1,7 +1,7 @@ [ { "name": "ionCube", - "check": "ioncube_loader", + "check": "ionCube Loader", "file": "ioncube_loader.so", "versions": ["56", "70", "71", "72", "73", "74", "81", "82"], "installed": false diff --git a/agent/i18n/lang/en.yaml b/agent/i18n/lang/en.yaml index b958dfd44..df4a7536c 100644 --- a/agent/i18n/lang/en.yaml +++ b/agent/i18n/lang/en.yaml @@ -251,7 +251,6 @@ TaskSync: "Sync" LocalApp: "Local App" SubTask: "Subtask" RuntimeExtension: "Runtime Extension" -TaskBuild: "Build" # task - snapshot Snapshot: "Snapshot" @@ -300,4 +299,13 @@ ImageRenameTag: "Rename image tag" ImageNewTag: "New image tag {{ .name }}" ImaegPushRes: "Image push output: {{ .name }}" ComposeCreate: "Create compose" -ComposeCreateRes: "Compose creation output: {{ .name }}" \ No newline at end of file +ComposeCreateRes: "Compose creation output: {{ .name }}" + +# task - website +BackupNginxConfig: "Backup website OpenResty configuration file" +CompressFileSuccess: "Directory compression successful, compressed as {{.name}}" +CompressDir: "Compress directory" +DeCompressFile: "Decompress file {{ .name }}" +ErrCheckValid: "Failed to validate backup file, {{ .name }}" +Rollback: "Rollback" +websiteDir: "Website directory" \ No newline at end of file diff --git a/agent/i18n/lang/zh-Hant.yaml b/agent/i18n/lang/zh-Hant.yaml index 224ad8dab..89bc40450 100644 --- a/agent/i18n/lang/zh-Hant.yaml +++ b/agent/i18n/lang/zh-Hant.yaml @@ -253,7 +253,6 @@ TaskSync: "同步" LocalApp: "本地應用" SubTask: "子任務" RuntimeExtension: "運行環境擴展" -TaskBuild: "構建" # task - snapshot @@ -303,4 +302,13 @@ ImageRenameTag: "修改鏡像 Tag" ImageNewTag: "新鏡像 Tag {{ .name }}" ImaegPushRes: "鏡像推送輸出:{{ .name }}" ComposeCreate: "創建編排" -ComposeCreateRes: "編排創建輸出:{{ .name }}" \ No newline at end of file +ComposeCreateRes: "編排創建輸出:{{ .name }}" + +# task - website +BackupNginxConfig: "備份網站 OpenResty 配置檔案" +CompressFileSuccess: "壓縮目錄成功,壓縮為 {{.name}}" +CompressDir: "壓縮目錄" +DeCompressFile: "解壓檔案 {{ .name }}" +ErrCheckValid: "校驗備份檔案失敗,{{ .name }}" +Rollback: "回滾" +websiteDir: "網站目錄" \ No newline at end of file diff --git a/agent/i18n/lang/zh.yaml b/agent/i18n/lang/zh.yaml index 2b8ed3f3f..c9eebbf06 100644 --- a/agent/i18n/lang/zh.yaml +++ b/agent/i18n/lang/zh.yaml @@ -329,4 +329,13 @@ ImageRenameTag: "修改镜像 Tag" ImageNewTag: "新镜像 Tag {{ .name }}" ImaegPushRes: "镜像推送输出:{{ .name }}" ComposeCreate: "创建编排" -ComposeCreateRes: "编排创建输出:{{ .name }}" \ No newline at end of file +ComposeCreateRes: "编排创建输出:{{ .name }}" + +# task - website +BackupNginxConfig: "备份网站 OpenResty 配置文件" +CompressFileSuccess: "压缩目录成功,压缩为 {{.name}}" +CompressDir: "压缩目录" +DeCompressFile: "解压文件 {{ .name }}" +ErrCheckValid: "校验备份文件失败,{{ .name }}" +Rollback: "回滚" +websiteDir: "网站目录" \ No newline at end of file diff --git a/frontend/src/api/interface/backup.ts b/frontend/src/api/interface/backup.ts index 487586a65..01cd406a8 100644 --- a/frontend/src/api/interface/backup.ts +++ b/frontend/src/api/interface/backup.ts @@ -70,6 +70,7 @@ export namespace Backup { name: string; detailName: string; secret: string; + taskID: string; } export interface Recover { downloadAccountID: number; @@ -78,5 +79,6 @@ export namespace Backup { detailName: string; file: string; secret: string; + taskID: string; } } diff --git a/frontend/src/api/interface/website.ts b/frontend/src/api/interface/website.ts index e556a2b0c..7bdbbbd2f 100644 --- a/frontend/src/api/interface/website.ts +++ b/frontend/src/api/interface/website.ts @@ -24,6 +24,8 @@ export namespace Website { accessLog?: boolean; errorLog?: boolean; childSites?: string[]; + dbID: number; + dbType: string; } export interface WebsiteDTO extends Website { diff --git a/frontend/src/components/backup/index.vue b/frontend/src/components/backup/index.vue index 364adf704..c02cffb77 100644 --- a/frontend/src/components/backup/index.vue +++ b/frontend/src/components/backup/index.vue @@ -85,7 +85,7 @@