1
0
mirror of https://github.com/1Panel-dev/1Panel.git synced 2025-01-31 14:08:06 +08:00

feat: 网站备份/恢复增加任务日志 (#6603)

This commit is contained in:
zhengkunwang 2024-09-27 22:23:38 +08:00 committed by GitHub
parent 4f1bdf5338
commit ed75ecc63b
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
17 changed files with 432 additions and 274 deletions

View File

@ -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 {

View File

@ -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
}

View File

@ -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() {

View File

@ -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 {

View File

@ -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))

View File

@ -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,

View File

@ -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 ""
}

View File

@ -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"))
}

View File

@ -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

View File

@ -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 }}"
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"

View File

@ -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 }}"
ComposeCreateRes: "編排創建輸出:{{ .name }}"
# task - website
BackupNginxConfig: "備份網站 OpenResty 配置檔案"
CompressFileSuccess: "壓縮目錄成功,壓縮為 {{.name}}"
CompressDir: "壓縮目錄"
DeCompressFile: "解壓檔案 {{ .name }}"
ErrCheckValid: "校驗備份檔案失敗,{{ .name }}"
Rollback: "回滾"
websiteDir: "網站目錄"

View File

@ -329,4 +329,13 @@ ImageRenameTag: "修改镜像 Tag"
ImageNewTag: "新镜像 Tag {{ .name }}"
ImaegPushRes: "镜像推送输出:{{ .name }}"
ComposeCreate: "创建编排"
ComposeCreateRes: "编排创建输出:{{ .name }}"
ComposeCreateRes: "编排创建输出:{{ .name }}"
# task - website
BackupNginxConfig: "备份网站 OpenResty 配置文件"
CompressFileSuccess: "压缩目录成功,压缩为 {{.name}}"
CompressDir: "压缩目录"
DeCompressFile: "解压文件 {{ .name }}"
ErrCheckValid: "校验备份文件失败,{{ .name }}"
Rollback: "回滚"
websiteDir: "网站目录"

View File

@ -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;
}
}

View File

@ -24,6 +24,8 @@ export namespace Website {
accessLog?: boolean;
errorLog?: boolean;
childSites?: string[];
dbID: number;
dbType: string;
}
export interface WebsiteDTO extends Website {

View File

@ -85,7 +85,7 @@
</el-form>
<template #footer>
<span class="dialog-footer">
<el-button @click="handleClose" :disabled="loading">
<el-button @click="handleBackupClose" :disabled="loading">
{{ $t('commons.button.cancel') }}
</el-button>
<el-button type="primary" @click="onSubmit" :disabled="loading">
@ -96,11 +96,12 @@
</el-dialog>
<OpDialog ref="opRef" @search="search" />
<TaskLog ref="taskLogRef" @close="search" />
</template>
<script lang="ts" setup>
import { reactive, ref } from 'vue';
import { computeSize, dateFormat, downloadFile } from '@/utils/util';
import { computeSize, dateFormat, downloadFile, newUUID } from '@/utils/util';
import {
getLocalBackupDir,
handleBackup,
@ -113,10 +114,12 @@ import i18n from '@/lang';
import { Backup } from '@/api/interface/backup';
import router from '@/routers';
import { MsgSuccess } from '@/utils/message';
import TaskLog from '@/components/task-log/index.vue';
const selects = ref<any>([]);
const loading = ref();
const opRef = ref();
const taskLogRef = ref();
const data = ref();
const paginationConfig = reactive({
@ -136,6 +139,7 @@ const secret = ref();
const open = ref();
const isBackup = ref();
const recordInfo = ref();
interface DialogProps {
type: string;
@ -159,6 +163,7 @@ const handleClose = () => {
};
const handleBackupClose = () => {
open.value = false;
search();
};
const loadBackupDir = async () => {
@ -190,44 +195,60 @@ const search = async () => {
});
};
const onSubmit = async (row?: any) => {
if (isBackup.value) {
let params = {
type: type.value,
name: name.value,
detailName: detailName.value,
secret: secret.value,
};
loading.value = true;
await handleBackup(params)
.then(() => {
loading.value = false;
handleClose();
handleBackupClose();
MsgSuccess(i18n.global.t('commons.msg.operationSuccess'));
search();
})
.catch(() => {
loading.value = false;
});
return;
const openTaskLog = (taskID: string) => {
taskLogRef.value.openWithTaskID(taskID);
};
const backup = async (close: boolean) => {
const taskID = newUUID();
let params = {
type: type.value,
name: name.value,
detailName: detailName.value,
secret: secret.value,
taskID: taskID,
};
loading.value = true;
try {
await handleBackup(params);
loading.value = false;
if (close) {
handleClose();
MsgSuccess(i18n.global.t('commons.msg.operationSuccess'));
search();
} else {
openTaskLog(taskID);
}
handleBackupClose();
} catch (error) {
loading.value = false;
}
};
const recover = async (close: boolean, row?: any) => {
const taskID = newUUID();
let params = {
downloadAccountID: row.downloadAccountID,
type: type.value,
name: name.value,
detailName: detailName.value,
file: row.file,
file: row.fileDir + '/' + row.fileName,
secret: secret.value,
taskID: taskID,
backupRecordID: row.id,
};
loading.value = true;
await handleRecover(params)
.then(() => {
loading.value = false;
handleClose();
handleBackupClose();
MsgSuccess(i18n.global.t('commons.msg.operationSuccess'));
search();
if (close) {
handleClose();
MsgSuccess(i18n.global.t('commons.msg.operationSuccess'));
search();
} else {
openTaskLog(taskID);
}
})
.catch(() => {
loading.value = false;
@ -245,7 +266,7 @@ const onBackup = async () => {
cancelButtonText: i18n.global.t('commons.button.cancel'),
},
).then(async () => {
onSubmit();
backup(true);
});
return;
}
@ -263,13 +284,22 @@ const onRecover = async (row: Backup.RecordInfo) => {
cancelButtonText: i18n.global.t('commons.button.cancel'),
},
).then(async () => {
onSubmit(row);
recover(true, row);
});
return;
}
recordInfo.value = row;
open.value = true;
};
const onSubmit = () => {
if (isBackup.value) {
backup(false);
} else {
recover(false, recordInfo.value);
}
};
const onDownload = async (row: Backup.RecordInfo) => {
let params = {
downloadAccountID: row.downloadAccountID,

View File

@ -45,7 +45,7 @@
<PHP :website="website" v-if="tabIndex == '13'"></PHP>
</el-tab-pane>
<el-tab-pane :label="$t('logs.resource')" name="14">
<Resource :id="id" :websiteType="website.type" v-if="tabIndex == '14'"></Resource>
<Resource :id="id" v-if="tabIndex == '14'"></Resource>
</el-tab-pane>
<el-tab-pane :label="$t('website.other')" name="12">
<Other :id="id" v-if="tabIndex == '12'"></Other>

View File

@ -11,11 +11,16 @@
label-position="left"
label-width="90px"
class="mt-5"
v-if="websiteType === 'static' || websiteType === 'runtime'"
v-if="website.type === 'static' || website.type === 'runtime'"
>
<el-form-item :label="$t('website.changeDatabase')" prop="databaseID">
<el-select v-model="req.databaseID" class="w-full" @change="changeDatabase">
<el-option v-for="(item, index) in databases" :key="index" :label="item.name" :value="item.id">
<el-form-item :label="$t('website.changeDatabase')" prop="db">
<el-select v-model="req.db" class="w-full" @change="changeDatabase">
<el-option
v-for="(item, index) in databases"
:key="index"
:label="item.name"
:value="item.id + item.type"
>
<div class="flex justify-between items-center">
<span>{{ item.name }}</span>
<el-tag>{{ item.type }}</el-tag>
@ -34,7 +39,7 @@
</div>
</template>
<script setup lang="ts">
import { ChangeDatabase, GetWebsiteDatabase, GetWebsiteResource } from '@/api/modules/website';
import { ChangeDatabase, GetWebsite, GetWebsiteDatabase, GetWebsiteResource } from '@/api/modules/website';
import i18n from '@/lang';
import { MsgSuccess } from '@/utils/message';
@ -43,18 +48,20 @@ const props = defineProps({
type: Number,
default: 0,
},
websiteType: {
type: String,
default: '',
},
});
const data = ref([]);
const req = reactive({
websiteID: props.id,
databaseID: 0,
databaseType: '',
db: '',
});
const databases = ref([]);
const website = ref({
type: '',
dbID: 0,
dbType: '',
});
const search = async () => {
try {
@ -68,13 +75,31 @@ const listDatabases = async () => {
const res = await GetWebsiteDatabase();
databases.value = res.data;
if (databases.value.length > 0) {
req.databaseID = databases.value[0].id;
if (website.value.dbID > 0) {
for (let i = 0; i < databases.value.length; i++) {
if (
databases.value[i].id === website.value.dbID &&
databases.value[i].type === website.value.dbType
) {
req.db = databases.value[i].id + databases.value[i].type;
break;
}
}
} else {
req.db = databases.value[0];
}
}
} catch (error) {}
};
const changeDatabase = () => {
req.databaseType = databases.value.find((item) => item.id === req.databaseID)?.type;
for (let i = 0; i < databases.value.length; i++) {
if (databases.value[i].id + databases.value[i].type === req.db) {
req.databaseID = databases.value[i].id;
req.databaseType = databases.value[i].type;
break;
}
}
};
const submit = async () => {
@ -85,9 +110,17 @@ const submit = async () => {
} catch (error) {}
};
const getwebsite = async () => {
try {
const res = await GetWebsite(props.id);
website.value = res.data;
req.db = '';
search();
listDatabases();
} catch (error) {}
};
onMounted(() => {
console.log('websiteType', props.websiteType);
search();
listDatabases();
getwebsite();
});
</script>