2023-02-21 19:06:24 +08:00
|
|
|
package service
|
|
|
|
|
|
|
|
import (
|
|
|
|
"encoding/json"
|
|
|
|
"fmt"
|
|
|
|
"io/fs"
|
|
|
|
"os"
|
|
|
|
"path"
|
|
|
|
"strconv"
|
|
|
|
"strings"
|
|
|
|
"time"
|
|
|
|
|
|
|
|
"github.com/1Panel-dev/1Panel/backend/app/dto"
|
|
|
|
"github.com/1Panel-dev/1Panel/backend/app/model"
|
|
|
|
"github.com/1Panel-dev/1Panel/backend/constant"
|
|
|
|
"github.com/1Panel-dev/1Panel/backend/global"
|
|
|
|
"github.com/1Panel-dev/1Panel/backend/utils/compose"
|
|
|
|
"github.com/1Panel-dev/1Panel/backend/utils/files"
|
|
|
|
"github.com/joho/godotenv"
|
|
|
|
"github.com/pkg/errors"
|
|
|
|
"gopkg.in/yaml.v3"
|
|
|
|
)
|
|
|
|
|
|
|
|
func (u *BackupService) AppBackup(req dto.CommonBackup) error {
|
|
|
|
localDir, err := loadLocalDir()
|
|
|
|
if err != nil {
|
|
|
|
return err
|
|
|
|
}
|
|
|
|
app, err := appRepo.GetFirst(appRepo.WithKey(req.Name))
|
|
|
|
if err != nil {
|
|
|
|
return err
|
|
|
|
}
|
|
|
|
install, err := appInstallRepo.GetFirst(commonRepo.WithByName(req.DetailName), appInstallRepo.WithAppId(app.ID))
|
|
|
|
if err != nil {
|
|
|
|
return err
|
|
|
|
}
|
|
|
|
timeNow := time.Now().Format("20060102150405")
|
|
|
|
backupDir := fmt.Sprintf("%s/app/%s/%s", localDir, req.Name, req.DetailName)
|
|
|
|
|
|
|
|
fileName := fmt.Sprintf("%s_%s.tar.gz", req.DetailName, timeNow)
|
|
|
|
if err := handleAppBackup(&install, backupDir, fileName); err != nil {
|
|
|
|
return err
|
|
|
|
}
|
|
|
|
|
|
|
|
record := &model.BackupRecord{
|
|
|
|
Type: "app",
|
|
|
|
Name: req.Name,
|
|
|
|
DetailName: req.DetailName,
|
|
|
|
Source: "LOCAL",
|
|
|
|
BackupType: "LOCAL",
|
|
|
|
FileDir: backupDir,
|
|
|
|
FileName: fileName,
|
|
|
|
}
|
|
|
|
|
|
|
|
if err := backupRepo.CreateRecord(record); err != nil {
|
|
|
|
global.LOG.Errorf("save backup record failed, err: %v", err)
|
|
|
|
return err
|
|
|
|
}
|
|
|
|
return nil
|
|
|
|
}
|
|
|
|
|
|
|
|
func (u *BackupService) AppRecover(req dto.CommonRecover) error {
|
|
|
|
app, err := appRepo.GetFirst(appRepo.WithKey(req.Name))
|
|
|
|
if err != nil {
|
|
|
|
return err
|
|
|
|
}
|
|
|
|
install, err := appInstallRepo.GetFirst(commonRepo.WithByName(req.DetailName), appInstallRepo.WithAppId(app.ID))
|
|
|
|
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))
|
|
|
|
}
|
|
|
|
if _, err := compose.Down(install.GetComposePath()); err != nil {
|
|
|
|
return err
|
|
|
|
}
|
2023-03-01 11:38:08 +08:00
|
|
|
if err := handleAppRecover(&install, req.File, false); err != nil {
|
2023-02-21 19:06:24 +08:00
|
|
|
return err
|
|
|
|
}
|
|
|
|
return nil
|
|
|
|
}
|
|
|
|
|
|
|
|
type AppInfo struct {
|
|
|
|
AppDetailId uint `json:"appDetailId"`
|
|
|
|
Param string `json:"param"`
|
|
|
|
Version string `json:"version"`
|
|
|
|
}
|
|
|
|
|
|
|
|
func handleAppBackup(install *model.AppInstall, backupDir, fileName 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)
|
|
|
|
}()
|
|
|
|
|
|
|
|
var appInfo AppInfo
|
|
|
|
appInfo.Param = install.Param
|
|
|
|
appInfo.AppDetailId = install.AppDetailId
|
|
|
|
appInfo.Version = install.Version
|
|
|
|
remarkInfo, _ := json.Marshal(appInfo)
|
|
|
|
remarkInfoPath := fmt.Sprintf("%s/app.json", tmpDir)
|
|
|
|
if err := fileOp.SaveFile(remarkInfoPath, string(remarkInfo), fs.ModePerm); err != nil {
|
|
|
|
return err
|
|
|
|
}
|
|
|
|
|
|
|
|
appPath := fmt.Sprintf("%s/%s/%s", constant.AppInstallDir, install.App.Key, install.Name)
|
|
|
|
if err := fileOp.Compress([]string{appPath}, tmpDir, "app.tar.gz", files.TarGz); err != nil {
|
|
|
|
return err
|
|
|
|
}
|
|
|
|
if err := fileOp.Compress([]string{tmpDir}, backupDir, fileName, files.TarGz); err != nil {
|
|
|
|
return err
|
|
|
|
}
|
|
|
|
return nil
|
|
|
|
}
|
|
|
|
|
2023-03-01 11:38:08 +08:00
|
|
|
func handleAppRecover(install *model.AppInstall, recoverFile string, isRollback bool) error {
|
|
|
|
isOk := false
|
2023-02-21 19:06:24 +08:00
|
|
|
fileOp := files.NewFileOp()
|
|
|
|
if err := fileOp.Decompress(recoverFile, path.Dir(recoverFile), files.TarGz); err != nil {
|
|
|
|
return err
|
|
|
|
}
|
|
|
|
tmpPath := strings.ReplaceAll(recoverFile, ".tar.gz", "")
|
|
|
|
defer func() {
|
|
|
|
_ = os.RemoveAll(strings.ReplaceAll(recoverFile, ".tar.gz", ""))
|
|
|
|
}()
|
|
|
|
|
|
|
|
if !fileOp.Stat(tmpPath+"/app.json") || !fileOp.Stat(tmpPath+"/app.tar.gz") {
|
|
|
|
return errors.New("the wrong recovery package does not have app.json or app.tar.gz files")
|
|
|
|
}
|
2023-03-01 11:38:08 +08:00
|
|
|
if !isRollback {
|
|
|
|
rollbackFile := fmt.Sprintf("%s/original/app/%s_%s.tar.gz", global.CONF.System.BaseDir, install.Name, time.Now().Format("20060102150405"))
|
|
|
|
if err := handleAppBackup(install, path.Dir(rollbackFile), path.Base(rollbackFile)); err != nil {
|
|
|
|
global.LOG.Errorf("backup app %s for rollback before recover failed, err: %v", install.Name, err)
|
|
|
|
}
|
|
|
|
defer func() {
|
|
|
|
if !isOk {
|
|
|
|
if err := handleAppRecover(install, rollbackFile, true); err != nil {
|
|
|
|
global.LOG.Errorf("rollback app %s from %s failed, err: %v", install.Name, rollbackFile, err)
|
|
|
|
}
|
|
|
|
global.LOG.Infof("rollback app %s from %s successful", install.Name, rollbackFile)
|
|
|
|
_ = os.RemoveAll(rollbackFile)
|
|
|
|
} else {
|
|
|
|
_ = os.RemoveAll(rollbackFile)
|
|
|
|
}
|
|
|
|
}()
|
|
|
|
}
|
|
|
|
|
2023-02-21 19:06:24 +08:00
|
|
|
appjson, err := os.ReadFile(tmpPath + "/" + "app.json")
|
|
|
|
if err != nil {
|
|
|
|
return err
|
|
|
|
}
|
|
|
|
var appInfo AppInfo
|
|
|
|
_ = json.Unmarshal(appjson, &appInfo)
|
|
|
|
|
|
|
|
if err := fileOp.Decompress(tmpPath+"/app.tar.gz", fmt.Sprintf("%s/%s", constant.AppInstallDir, install.App.Key), files.TarGz); err != nil {
|
|
|
|
return err
|
|
|
|
}
|
|
|
|
composeContent, err := os.ReadFile(install.GetComposePath())
|
|
|
|
if err != nil {
|
|
|
|
return err
|
|
|
|
}
|
|
|
|
install.DockerCompose = string(composeContent)
|
|
|
|
envContent, err := os.ReadFile(fmt.Sprintf("%s/%s/%s/.env", constant.AppInstallDir, install.App.Key, install.Name))
|
|
|
|
if err != nil {
|
|
|
|
return err
|
|
|
|
}
|
|
|
|
install.Env = string(envContent)
|
|
|
|
envMaps, err := godotenv.Unmarshal(string(envContent))
|
|
|
|
if err != nil {
|
|
|
|
return err
|
|
|
|
}
|
|
|
|
install.HttpPort = 0
|
|
|
|
httpPort, ok := envMaps["PANEL_APP_PORT_HTTP"]
|
|
|
|
if ok {
|
|
|
|
httpPortN, _ := strconv.Atoi(httpPort)
|
|
|
|
install.HttpPort = httpPortN
|
|
|
|
}
|
|
|
|
install.HttpsPort = 0
|
|
|
|
httpsPort, ok := envMaps["PANEL_APP_PORT_HTTPS"]
|
|
|
|
if ok {
|
|
|
|
httpsPortN, _ := strconv.Atoi(httpsPort)
|
|
|
|
install.HttpsPort = httpsPortN
|
|
|
|
}
|
|
|
|
|
|
|
|
composeMap := make(map[string]interface{})
|
|
|
|
if err := yaml.Unmarshal(composeContent, &composeMap); err != nil {
|
|
|
|
return err
|
|
|
|
}
|
|
|
|
servicesMap := composeMap["services"].(map[string]interface{})
|
|
|
|
for k, v := range servicesMap {
|
|
|
|
install.ServiceName = k
|
|
|
|
value := v.(map[string]interface{})
|
|
|
|
install.ContainerName = value["container_name"].(string)
|
|
|
|
}
|
|
|
|
|
|
|
|
install.Param = appInfo.Param
|
|
|
|
if out, err := compose.Up(install.GetComposePath()); err != nil {
|
|
|
|
install.Message = err.Error()
|
|
|
|
if len(out) != 0 {
|
|
|
|
install.Message = out
|
|
|
|
}
|
|
|
|
return errors.New(out)
|
|
|
|
}
|
|
|
|
install.AppDetailId = appInfo.AppDetailId
|
|
|
|
install.Version = appInfo.Version
|
|
|
|
install.Status = constant.Running
|
|
|
|
if err := appInstallRepo.Save(install); err != nil {
|
|
|
|
return err
|
|
|
|
}
|
2023-03-01 11:38:08 +08:00
|
|
|
isOk = true
|
2023-02-21 19:06:24 +08:00
|
|
|
return nil
|
|
|
|
}
|