mirror of
				https://github.com/1Panel-dev/1Panel.git
				synced 2025-10-31 11:15:58 +08:00 
			
		
		
		
	
		
			
				
	
	
		
			1070 lines
		
	
	
	
		
			35 KiB
		
	
	
	
		
			Go
		
	
	
	
	
	
			
		
		
	
	
			1070 lines
		
	
	
	
		
			35 KiB
		
	
	
	
		
			Go
		
	
	
	
	
	
| package service
 | |
| 
 | |
| import (
 | |
| 	"context"
 | |
| 	"encoding/json"
 | |
| 	"fmt"
 | |
| 	"os"
 | |
| 	"path"
 | |
| 	"regexp"
 | |
| 	"strconv"
 | |
| 	"strings"
 | |
| 	"sync"
 | |
| 	"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/cmd"
 | |
| 	"github.com/1Panel-dev/1Panel/backend/utils/compose"
 | |
| 	"github.com/1Panel-dev/1Panel/backend/utils/docker"
 | |
| 	"github.com/1Panel-dev/1Panel/backend/utils/files"
 | |
| 	"github.com/jinzhu/copier"
 | |
| 	"github.com/pkg/errors"
 | |
| )
 | |
| 
 | |
| type SnapshotService struct {
 | |
| 	OriginalPath string
 | |
| }
 | |
| 
 | |
| type ISnapshotService interface {
 | |
| 	SearchWithPage(req dto.SearchWithPage) (int64, interface{}, error)
 | |
| 	SnapshotCreate(req dto.SnapshotCreate) error
 | |
| 	SnapshotRecover(req dto.SnapshotRecover) error
 | |
| 	SnapshotRollback(req dto.SnapshotRecover) error
 | |
| 	SnapshotImport(req dto.SnapshotImport) error
 | |
| 	Delete(req dto.BatchDeleteReq) error
 | |
| 
 | |
| 	LoadSnapShotStatus(id uint) (*dto.SnapshotStatus, error)
 | |
| 
 | |
| 	UpdateDescription(req dto.UpdateDescription) error
 | |
| 	readFromJson(path string) (SnapshotJson, error)
 | |
| 
 | |
| 	HandleSnapshot(isCronjob bool, req dto.SnapshotCreate, timeNow string) (string, string, error)
 | |
| }
 | |
| 
 | |
| func NewISnapshotService() ISnapshotService {
 | |
| 	return &SnapshotService{}
 | |
| }
 | |
| 
 | |
| func (u *SnapshotService) SearchWithPage(req dto.SearchWithPage) (int64, interface{}, error) {
 | |
| 	total, systemBackups, err := snapshotRepo.Page(req.Page, req.PageSize, commonRepo.WithLikeName(req.Info))
 | |
| 	var dtoSnap []dto.SnapshotInfo
 | |
| 	for _, systemBackup := range systemBackups {
 | |
| 		var item dto.SnapshotInfo
 | |
| 		if err := copier.Copy(&item, &systemBackup); err != nil {
 | |
| 			return 0, nil, errors.WithMessage(constant.ErrStructTransform, err.Error())
 | |
| 		}
 | |
| 		dtoSnap = append(dtoSnap, item)
 | |
| 	}
 | |
| 	return total, dtoSnap, err
 | |
| }
 | |
| 
 | |
| func (u *SnapshotService) SnapshotImport(req dto.SnapshotImport) error {
 | |
| 	if len(req.Names) == 0 {
 | |
| 		return fmt.Errorf("incorrect snapshot request body: %v", req.Names)
 | |
| 	}
 | |
| 	for _, snapName := range req.Names {
 | |
| 		snap, _ := snapshotRepo.Get(commonRepo.WithByName(strings.ReplaceAll(snapName, ".tar.gz", "")))
 | |
| 		if snap.ID != 0 {
 | |
| 			return constant.ErrRecordExist
 | |
| 		}
 | |
| 	}
 | |
| 	for _, snap := range req.Names {
 | |
| 		shortName := strings.TrimPrefix(snap, "snapshot_")
 | |
| 		nameItems := strings.Split(shortName, "_")
 | |
| 		if !strings.HasPrefix(shortName, "1panel_v") || !strings.HasSuffix(shortName, ".tar.gz") || len(nameItems) != 3 {
 | |
| 			return fmt.Errorf("incorrect snapshot name format of %s", shortName)
 | |
| 		}
 | |
| 		formatTime, err := time.Parse("20060102150405", strings.ReplaceAll(nameItems[2], ".tar.gz", ""))
 | |
| 		if err != nil {
 | |
| 			return fmt.Errorf("incorrect snapshot name format of %s", shortName)
 | |
| 		}
 | |
| 		if strings.HasSuffix(snap, ".tar.gz") {
 | |
| 			snap = strings.ReplaceAll(snap, ".tar.gz", "")
 | |
| 		}
 | |
| 		itemSnap := model.Snapshot{
 | |
| 			Name:        snap,
 | |
| 			From:        req.From,
 | |
| 			Version:     nameItems[1],
 | |
| 			Description: req.Description,
 | |
| 			Status:      constant.StatusSuccess,
 | |
| 			BaseModel: model.BaseModel{
 | |
| 				CreatedAt: formatTime,
 | |
| 				UpdatedAt: formatTime,
 | |
| 			},
 | |
| 		}
 | |
| 		if err := snapshotRepo.Create(&itemSnap); err != nil {
 | |
| 			return err
 | |
| 		}
 | |
| 	}
 | |
| 	return nil
 | |
| }
 | |
| 
 | |
| func (u *SnapshotService) UpdateDescription(req dto.UpdateDescription) error {
 | |
| 	return snapshotRepo.Update(req.ID, map[string]interface{}{"description": req.Description})
 | |
| }
 | |
| 
 | |
| func (u *SnapshotService) LoadSnapShotStatus(id uint) (*dto.SnapshotStatus, error) {
 | |
| 	var data dto.SnapshotStatus
 | |
| 	status, err := snapshotRepo.GetStatus(id)
 | |
| 	if err != nil {
 | |
| 		return nil, err
 | |
| 	}
 | |
| 	if err := copier.Copy(&data, &status); err != nil {
 | |
| 		return nil, errors.WithMessage(constant.ErrStructTransform, err.Error())
 | |
| 	}
 | |
| 	return &data, nil
 | |
| }
 | |
| 
 | |
| type SnapshotJson struct {
 | |
| 	OldBaseDir       string `json:"oldBaseDir"`
 | |
| 	OldDockerDataDir string `json:"oldDockerDataDir"`
 | |
| 	OldBackupDataDir string `json:"oldBackupDataDir"`
 | |
| 	OldPanelDataDir  string `json:"oldPanelDataDir"`
 | |
| 
 | |
| 	BaseDir            string `json:"baseDir"`
 | |
| 	DockerDataDir      string `json:"dockerDataDir"`
 | |
| 	BackupDataDir      string `json:"backupDataDir"`
 | |
| 	PanelDataDir       string `json:"panelDataDir"`
 | |
| 	LiveRestoreEnabled bool   `json:"liveRestoreEnabled"`
 | |
| }
 | |
| 
 | |
| func (u *SnapshotService) SnapshotCreate(req dto.SnapshotCreate) error {
 | |
| 	if _, _, err := u.HandleSnapshot(false, req, time.Now().Format("20060102150405")); err != nil {
 | |
| 		return err
 | |
| 	}
 | |
| 	return nil
 | |
| }
 | |
| 
 | |
| func (u *SnapshotService) SnapshotRecover(req dto.SnapshotRecover) error {
 | |
| 	global.LOG.Info("start to recover panel by snapshot now")
 | |
| 	snap, err := snapshotRepo.Get(commonRepo.WithByID(req.ID))
 | |
| 	if err != nil {
 | |
| 		return err
 | |
| 	}
 | |
| 	if !req.IsNew && len(snap.InterruptStep) != 0 && len(snap.RollbackStatus) != 0 {
 | |
| 		return fmt.Errorf("the snapshot has been rolled back and cannot be restored again")
 | |
| 	}
 | |
| 	isNewSnapshot := isNewSnapVersion(snap.Version)
 | |
| 	isReTry := false
 | |
| 	if len(snap.InterruptStep) != 0 && !req.IsNew {
 | |
| 		isReTry = true
 | |
| 	}
 | |
| 	backup, err := backupRepo.Get(commonRepo.WithByType(snap.From))
 | |
| 	if err != nil {
 | |
| 		return err
 | |
| 	}
 | |
| 	client, err := NewIBackupService().NewClient(&backup)
 | |
| 	if err != nil {
 | |
| 		return err
 | |
| 	}
 | |
| 	localDir, err := loadLocalDir()
 | |
| 	if err != nil {
 | |
| 		return err
 | |
| 	}
 | |
| 	baseDir := path.Join(global.CONF.System.TmpDir, fmt.Sprintf("system/%s", snap.Name))
 | |
| 	if _, err := os.Stat(baseDir); err != nil && os.IsNotExist(err) {
 | |
| 		_ = os.MkdirAll(baseDir, os.ModePerm)
 | |
| 	}
 | |
| 
 | |
| 	_ = snapshotRepo.Update(snap.ID, map[string]interface{}{"recover_status": constant.StatusWaiting})
 | |
| 	_ = settingRepo.Update("SystemStatus", "Recovering")
 | |
| 	go func() {
 | |
| 		_ = global.Cron.Stop()
 | |
| 		defer func() {
 | |
| 			global.Cron.Start()
 | |
| 		}()
 | |
| 		operation := "recover"
 | |
| 		if isReTry {
 | |
| 			operation = "re-recover"
 | |
| 		}
 | |
| 		if !isReTry || snap.InterruptStep == "Download" || (isReTry && req.ReDownload) {
 | |
| 			itemBackupPath := strings.TrimPrefix(backup.BackupPath, "/")
 | |
| 			itemBackupPath = strings.TrimSuffix(itemBackupPath, "/")
 | |
| 			ok, err := client.Download(fmt.Sprintf("%s/system_snapshot/%s.tar.gz", itemBackupPath, snap.Name), fmt.Sprintf("%s/%s.tar.gz", baseDir, snap.Name))
 | |
| 			if err != nil || !ok {
 | |
| 				if req.ReDownload {
 | |
| 					updateRecoverStatus(snap.ID, snap.InterruptStep, constant.StatusFailed, fmt.Sprintf("download file %s from %s failed, err: %v", snap.Name, backup.Type, err))
 | |
| 					return
 | |
| 				}
 | |
| 				updateRecoverStatus(snap.ID, "Download", constant.StatusFailed, fmt.Sprintf("download file %s from %s failed, err: %v", snap.Name, backup.Type, err))
 | |
| 				return
 | |
| 			}
 | |
| 			isReTry = false
 | |
| 		}
 | |
| 		fileOp := files.NewFileOp()
 | |
| 		if !isReTry || snap.InterruptStep == "Decompress" || (isReTry && req.ReDownload) {
 | |
| 			if err := handleUnTar(fmt.Sprintf("%s/%s.tar.gz", baseDir, snap.Name), baseDir); err != nil {
 | |
| 				if req.ReDownload {
 | |
| 					updateRecoverStatus(snap.ID, snap.InterruptStep, constant.StatusFailed, fmt.Sprintf("decompress file failed, err: %v", err))
 | |
| 					return
 | |
| 				}
 | |
| 				updateRecoverStatus(snap.ID, "Decompress", constant.StatusFailed, fmt.Sprintf("decompress file failed, err: %v", err))
 | |
| 				return
 | |
| 			}
 | |
| 			isReTry = false
 | |
| 		}
 | |
| 		rootDir := fmt.Sprintf("%s/%s", baseDir, snap.Name)
 | |
| 
 | |
| 		snapJson, err := u.readFromJson(fmt.Sprintf("%s/snapshot.json", rootDir))
 | |
| 		if err != nil {
 | |
| 			updateRecoverStatus(snap.ID, "Readjson", constant.StatusFailed, fmt.Sprintf("decompress file failed, err: %v", err))
 | |
| 			return
 | |
| 		}
 | |
| 		if snap.InterruptStep == "Readjson" {
 | |
| 			isReTry = false
 | |
| 		}
 | |
| 		u.OriginalPath = fmt.Sprintf("%s/1panel_original/original_%s", snapJson.BaseDir, snap.Name)
 | |
| 		_ = os.MkdirAll(u.OriginalPath, os.ModePerm)
 | |
| 
 | |
| 		snapJson.OldBaseDir = global.CONF.System.BaseDir
 | |
| 		snapJson.OldPanelDataDir = path.Join(global.CONF.System.BaseDir, "1panel")
 | |
| 		snapJson.OldBackupDataDir = localDir
 | |
| 		recoverPanelDir := fmt.Sprintf("%s/%s/1panel", baseDir, snap.Name)
 | |
| 		liveRestore := false
 | |
| 
 | |
| 		if !isNewSnapshot {
 | |
| 			if !isReTry || snap.InterruptStep == "LoadDockerJson" {
 | |
| 				snapJson.OldDockerDataDir, liveRestore, err = u.loadDockerDataDir()
 | |
| 				if err != nil {
 | |
| 					updateRecoverStatus(snap.ID, "LoadDockerJson", constant.StatusFailed, fmt.Sprintf("load docker data dir failed, err: %v", err))
 | |
| 					return
 | |
| 				}
 | |
| 				isReTry = false
 | |
| 			}
 | |
| 			if liveRestore {
 | |
| 				if err := u.updateLiveRestore(false); err != nil {
 | |
| 					updateRecoverStatus(snap.ID, "UpdateLiveRestore", constant.StatusFailed, fmt.Sprintf("update docker daemon.json live-restore conf failed, err: %v", err))
 | |
| 					return
 | |
| 				}
 | |
| 				isReTry = false
 | |
| 			}
 | |
| 			_ = u.saveJson(snapJson, rootDir)
 | |
| 
 | |
| 			_, _ = cmd.Exec("systemctl stop docker")
 | |
| 			if !isReTry || snap.InterruptStep == "DockerDir" {
 | |
| 				if err := u.handleDockerDatas(fileOp, operation, rootDir, snapJson.DockerDataDir); err != nil {
 | |
| 					updateRecoverStatus(snap.ID, "DockerDir", constant.StatusFailed, err.Error())
 | |
| 					return
 | |
| 				}
 | |
| 				isReTry = false
 | |
| 			}
 | |
| 		} else {
 | |
| 			if !isReTry || snap.InterruptStep == "DockerDir" {
 | |
| 				if err := u.handleDockerDatasWithSave(fileOp, operation, rootDir, ""); err != nil {
 | |
| 					updateRecoverStatus(snap.ID, "DockerDir", constant.StatusFailed, err.Error())
 | |
| 					return
 | |
| 				}
 | |
| 				isReTry = false
 | |
| 			}
 | |
| 		}
 | |
| 
 | |
| 		if !isReTry || snap.InterruptStep == "DaemonJson" {
 | |
| 			if err := u.handleDaemonJson(fileOp, operation, rootDir+"/docker/daemon.json", u.OriginalPath); err != nil {
 | |
| 				updateRecoverStatus(snap.ID, "DaemonJson", constant.StatusFailed, err.Error())
 | |
| 				return
 | |
| 			}
 | |
| 			isReTry = false
 | |
| 		}
 | |
| 		_, _ = cmd.Exec("systemctl restart docker")
 | |
| 
 | |
| 		if !isReTry || snap.InterruptStep == "1PanelBinary" {
 | |
| 			if err := u.handlePanelBinary(fileOp, operation, recoverPanelDir+"/1panel", u.OriginalPath+"/1panel"); err != nil {
 | |
| 				updateRecoverStatus(snap.ID, "1PanelBinary", constant.StatusFailed, err.Error())
 | |
| 				return
 | |
| 			}
 | |
| 			isReTry = false
 | |
| 		}
 | |
| 		if !isReTry || snap.InterruptStep == "1PctlBinary" {
 | |
| 			if err := u.handlePanelctlBinary(fileOp, operation, recoverPanelDir+"/1pctl", u.OriginalPath+"/1pctl"); err != nil {
 | |
| 				updateRecoverStatus(snap.ID, "1PctlBinary", constant.StatusFailed, err.Error())
 | |
| 				return
 | |
| 			}
 | |
| 			isReTry = false
 | |
| 		}
 | |
| 		if !isReTry || snap.InterruptStep == "1PanelService" {
 | |
| 			if err := u.handlePanelService(fileOp, operation, recoverPanelDir+"/1panel.service", u.OriginalPath+"/1panel.service"); err != nil {
 | |
| 				updateRecoverStatus(snap.ID, "1PanelService", constant.StatusFailed, err.Error())
 | |
| 				return
 | |
| 			}
 | |
| 			isReTry = false
 | |
| 		}
 | |
| 
 | |
| 		if !isReTry || snap.InterruptStep == "1PanelBackups" {
 | |
| 			if err := u.handleBackupDatas(fileOp, operation, rootDir, snapJson.BackupDataDir); err != nil {
 | |
| 				updateRecoverStatus(snap.ID, "1PanelBackups", constant.StatusFailed, err.Error())
 | |
| 				return
 | |
| 			}
 | |
| 			isReTry = false
 | |
| 		}
 | |
| 
 | |
| 		if !isReTry || snap.InterruptStep == "1PanelData" {
 | |
| 			if err := u.handlePanelDatas(snap.ID, fileOp, operation, rootDir, snapJson.PanelDataDir, localDir, snapJson.OldDockerDataDir); err != nil {
 | |
| 				updateRecoverStatus(snap.ID, "1PanelData", constant.StatusFailed, err.Error())
 | |
| 				return
 | |
| 			}
 | |
| 			isReTry = false
 | |
| 		}
 | |
| 
 | |
| 		if isNewSnapshot {
 | |
| 			_ = rebuildAllAppInstall()
 | |
| 		}
 | |
| 
 | |
| 		_ = os.RemoveAll(rootDir)
 | |
| 		global.LOG.Info("recover successful")
 | |
| 		_, _ = cmd.Exec("systemctl daemon-reload && systemctl restart 1panel.service")
 | |
| 	}()
 | |
| 	return nil
 | |
| }
 | |
| 
 | |
| func (u *SnapshotService) SnapshotRollback(req dto.SnapshotRecover) error {
 | |
| 	global.LOG.Info("start to rollback now")
 | |
| 	snap, err := snapshotRepo.Get(commonRepo.WithByID(req.ID))
 | |
| 	if err != nil {
 | |
| 		return err
 | |
| 	}
 | |
| 	if snap.InterruptStep == "Download" || snap.InterruptStep == "Decompress" || snap.InterruptStep == "Readjson" {
 | |
| 		return nil
 | |
| 	}
 | |
| 	localDir, err := loadLocalDir()
 | |
| 	if err != nil {
 | |
| 		return err
 | |
| 	}
 | |
| 	fileOp := files.NewFileOp()
 | |
| 	isNewSnapshot := isNewSnapVersion(snap.Version)
 | |
| 
 | |
| 	rootDir := path.Join(localDir, fmt.Sprintf("system/%s/%s", snap.Name, snap.Name))
 | |
| 
 | |
| 	_ = settingRepo.Update("SystemStatus", "Rollbacking")
 | |
| 	_ = snapshotRepo.Update(snap.ID, map[string]interface{}{"rollback_status": constant.StatusWaiting})
 | |
| 	go func() {
 | |
| 		_ = global.Cron.Stop()
 | |
| 		defer func() {
 | |
| 			global.Cron.Start()
 | |
| 		}()
 | |
| 		snapJson, err := u.readFromJson(fmt.Sprintf("%s/snapshot.json", rootDir))
 | |
| 		if err != nil {
 | |
| 			updateRollbackStatus(snap.ID, constant.StatusFailed, fmt.Sprintf("decompress file failed, err: %v", err))
 | |
| 			return
 | |
| 		}
 | |
| 		u.OriginalPath = fmt.Sprintf("%s/1panel_original/original_%s", snapJson.OldBaseDir, snap.Name)
 | |
| 		if _, err := os.Stat(u.OriginalPath); err != nil && os.IsNotExist(err) {
 | |
| 			return
 | |
| 		}
 | |
| 
 | |
| 		if !isNewSnapshot {
 | |
| 			_, _ = cmd.Exec("systemctl stop docker")
 | |
| 			if err := u.handleDockerDatas(fileOp, "rollback", u.OriginalPath, snapJson.OldDockerDataDir); err != nil {
 | |
| 				updateRollbackStatus(snap.ID, constant.StatusFailed, err.Error())
 | |
| 				return
 | |
| 			}
 | |
| 			defer func() {
 | |
| 				_, _ = cmd.Exec("systemctl restart docker")
 | |
| 			}()
 | |
| 			if snap.InterruptStep == "DockerDir" {
 | |
| 				return
 | |
| 			}
 | |
| 			if snapJson.LiveRestoreEnabled {
 | |
| 				if err := u.updateLiveRestore(true); err != nil {
 | |
| 					updateRollbackStatus(snap.ID, constant.StatusFailed, err.Error())
 | |
| 					return
 | |
| 				}
 | |
| 			}
 | |
| 			if snap.InterruptStep == "UpdateLiveRestore" {
 | |
| 				return
 | |
| 			}
 | |
| 		} else {
 | |
| 			if err := u.handleDockerDatasWithSave(fileOp, "rollback", u.OriginalPath, ""); err != nil {
 | |
| 				updateRollbackStatus(snap.ID, constant.StatusFailed, err.Error())
 | |
| 				return
 | |
| 			}
 | |
| 			defer func() {
 | |
| 				_ = rebuildAllAppInstall()
 | |
| 			}()
 | |
| 			if snap.InterruptStep == "DockerDir" {
 | |
| 				return
 | |
| 			}
 | |
| 		}
 | |
| 
 | |
| 		if err := u.handleDaemonJson(fileOp, "rollback", u.OriginalPath+"/daemon.json", ""); err != nil {
 | |
| 			updateRollbackStatus(snap.ID, constant.StatusFailed, err.Error())
 | |
| 			return
 | |
| 		}
 | |
| 		if snap.InterruptStep == "DaemonJson" {
 | |
| 			return
 | |
| 		}
 | |
| 
 | |
| 		if err := u.handlePanelBinary(fileOp, "rollback", u.OriginalPath+"/1panel", ""); err != nil {
 | |
| 			updateRollbackStatus(snap.ID, constant.StatusFailed, err.Error())
 | |
| 			return
 | |
| 		}
 | |
| 		if snap.InterruptStep == "1PanelBinary" {
 | |
| 			return
 | |
| 		}
 | |
| 
 | |
| 		if err := u.handlePanelctlBinary(fileOp, "rollback", u.OriginalPath+"/1pctl", ""); err != nil {
 | |
| 			updateRollbackStatus(snap.ID, constant.StatusFailed, err.Error())
 | |
| 			return
 | |
| 		}
 | |
| 		if snap.InterruptStep == "1PctlBinary" {
 | |
| 			return
 | |
| 		}
 | |
| 
 | |
| 		if err := u.handlePanelService(fileOp, "rollback", u.OriginalPath+"/1panel.service", ""); err != nil {
 | |
| 			updateRollbackStatus(snap.ID, constant.StatusFailed, err.Error())
 | |
| 			return
 | |
| 		}
 | |
| 		if snap.InterruptStep == "1PanelService" {
 | |
| 			_, _ = cmd.Exec("systemctl daemon-reload && systemctl restart 1panel.service")
 | |
| 			return
 | |
| 		}
 | |
| 
 | |
| 		if err := u.handleBackupDatas(fileOp, "rollback", u.OriginalPath, snapJson.OldBackupDataDir); err != nil {
 | |
| 			updateRollbackStatus(snap.ID, constant.StatusFailed, err.Error())
 | |
| 			return
 | |
| 		}
 | |
| 		if snap.InterruptStep == "1PanelBackups" {
 | |
| 			_, _ = cmd.Exec("systemctl daemon-reload && systemctl restart 1panel.service")
 | |
| 			return
 | |
| 		}
 | |
| 
 | |
| 		if err := u.handlePanelDatas(snap.ID, fileOp, "rollback", u.OriginalPath, snapJson.OldPanelDataDir, "", ""); err != nil {
 | |
| 			updateRollbackStatus(snap.ID, constant.StatusFailed, err.Error())
 | |
| 			return
 | |
| 		}
 | |
| 		if snap.InterruptStep == "1PanelData" {
 | |
| 			_, _ = cmd.Exec("systemctl daemon-reload && systemctl restart 1panel.service")
 | |
| 			return
 | |
| 		}
 | |
| 
 | |
| 		_ = os.RemoveAll(rootDir)
 | |
| 		global.LOG.Info("rollback successful")
 | |
| 		_, _ = cmd.Exec("systemctl daemon-reload && systemctl restart 1panel.service")
 | |
| 	}()
 | |
| 	return nil
 | |
| }
 | |
| 
 | |
| func (u *SnapshotService) saveJson(snapJson SnapshotJson, path string) error {
 | |
| 	remarkInfo, _ := json.MarshalIndent(snapJson, "", "\t")
 | |
| 	if err := os.WriteFile(fmt.Sprintf("%s/snapshot.json", path), remarkInfo, 0640); err != nil {
 | |
| 		return err
 | |
| 	}
 | |
| 	return nil
 | |
| }
 | |
| 
 | |
| func (u *SnapshotService) readFromJson(path string) (SnapshotJson, error) {
 | |
| 	var snap SnapshotJson
 | |
| 	if _, err := os.Stat(path); err != nil {
 | |
| 		return snap, fmt.Errorf("find snapshot json file in recover package failed, err: %v", err)
 | |
| 	}
 | |
| 	fileByte, err := os.ReadFile(path)
 | |
| 	if err != nil {
 | |
| 		return snap, fmt.Errorf("read file from path %s failed, err: %v", path, err)
 | |
| 	}
 | |
| 	if err := json.Unmarshal(fileByte, &snap); err != nil {
 | |
| 		return snap, fmt.Errorf("unmarshal snapjson failed, err: %v", err)
 | |
| 	}
 | |
| 	return snap, nil
 | |
| }
 | |
| 
 | |
| func (u *SnapshotService) HandleSnapshot(isCronjob bool, req dto.SnapshotCreate, timeNow string) (string, string, error) {
 | |
| 	localDir, err := loadLocalDir()
 | |
| 	if err != nil {
 | |
| 		return "", "", err
 | |
| 	}
 | |
| 	var (
 | |
| 		rootDir    string
 | |
| 		snap       model.Snapshot
 | |
| 		snapStatus model.SnapshotStatus
 | |
| 	)
 | |
| 
 | |
| 	if req.ID == 0 {
 | |
| 		versionItem, _ := settingRepo.Get(settingRepo.WithByKey("SystemVersion"))
 | |
| 		name := fmt.Sprintf("1panel_%s_%s", versionItem.Value, timeNow)
 | |
| 		if isCronjob {
 | |
| 			name = fmt.Sprintf("snapshot_1panel_%s_%s", versionItem.Value, timeNow)
 | |
| 		}
 | |
| 		rootDir = path.Join(localDir, "system", name)
 | |
| 
 | |
| 		snap = model.Snapshot{
 | |
| 			Name:        name,
 | |
| 			Description: req.Description,
 | |
| 			From:        req.From,
 | |
| 			Version:     versionItem.Value,
 | |
| 			Status:      constant.StatusWaiting,
 | |
| 		}
 | |
| 		_ = snapshotRepo.Create(&snap)
 | |
| 		snapStatus.SnapID = snap.ID
 | |
| 		_ = snapshotRepo.CreateStatus(&snapStatus)
 | |
| 	} else {
 | |
| 		snap, err = snapshotRepo.Get(commonRepo.WithByID(req.ID))
 | |
| 		if err != nil {
 | |
| 			return "", "", err
 | |
| 		}
 | |
| 		snapStatus, _ = snapshotRepo.GetStatus(snap.ID)
 | |
| 		if snapStatus.ID == 0 {
 | |
| 			snapStatus.SnapID = snap.ID
 | |
| 			_ = snapshotRepo.CreateStatus(&snapStatus)
 | |
| 		}
 | |
| 		rootDir = path.Join(localDir, fmt.Sprintf("system/%s", snap.Name))
 | |
| 	}
 | |
| 
 | |
| 	var wg sync.WaitGroup
 | |
| 	itemHelper := snapHelper{SnapID: snap.ID, Status: &snapStatus, Wg: &wg, FileOp: files.NewFileOp(), Ctx: context.Background()}
 | |
| 	backupPanelDir := path.Join(rootDir, "1panel")
 | |
| 	_ = os.MkdirAll(backupPanelDir, os.ModePerm)
 | |
| 	backupDockerDir := path.Join(rootDir, "docker")
 | |
| 	_ = os.MkdirAll(backupDockerDir, os.ModePerm)
 | |
| 
 | |
| 	jsonItem := SnapshotJson{
 | |
| 		BaseDir:       global.CONF.System.BaseDir,
 | |
| 		BackupDataDir: localDir,
 | |
| 		PanelDataDir:  path.Join(global.CONF.System.BaseDir, "1panel"),
 | |
| 	}
 | |
| 
 | |
| 	if snapStatus.PanelInfo != constant.StatusDone {
 | |
| 		wg.Add(1)
 | |
| 		go snapJson(itemHelper, jsonItem, rootDir)
 | |
| 	}
 | |
| 	if snapStatus.Panel != constant.StatusDone {
 | |
| 		wg.Add(1)
 | |
| 		go snapPanel(itemHelper, backupPanelDir)
 | |
| 	}
 | |
| 	if snapStatus.DaemonJson != constant.StatusDone {
 | |
| 		wg.Add(1)
 | |
| 		go snapDaemonJson(itemHelper, backupDockerDir)
 | |
| 	}
 | |
| 	if snapStatus.AppData != constant.StatusDone {
 | |
| 		wg.Add(1)
 | |
| 		go snapAppData(itemHelper, backupDockerDir)
 | |
| 	}
 | |
| 	if snapStatus.BackupData != constant.StatusDone {
 | |
| 		wg.Add(1)
 | |
| 		go snapBackup(itemHelper, localDir, backupPanelDir)
 | |
| 	}
 | |
| 	if snapStatus.PanelData != constant.StatusDone {
 | |
| 		wg.Add(1)
 | |
| 		go snapPanelData(itemHelper, localDir, backupPanelDir)
 | |
| 	}
 | |
| 
 | |
| 	if !isCronjob {
 | |
| 		go func() {
 | |
| 			wg.Wait()
 | |
| 			if !checkIsAllDone(snap.ID) {
 | |
| 				_ = snapshotRepo.Update(snap.ID, map[string]interface{}{"status": constant.StatusFailed})
 | |
| 				return
 | |
| 			}
 | |
| 			snapCompress(itemHelper, rootDir)
 | |
| 			if snapStatus.Compress != constant.StatusDone {
 | |
| 				_ = snapshotRepo.Update(snap.ID, map[string]interface{}{"status": constant.StatusFailed})
 | |
| 				return
 | |
| 			}
 | |
| 
 | |
| 			snapUpload(itemHelper, req.From, fmt.Sprintf("%s.tar.gz", rootDir))
 | |
| 			if snapStatus.Upload != constant.StatusDone {
 | |
| 				_ = snapshotRepo.Update(snap.ID, map[string]interface{}{"status": constant.StatusFailed})
 | |
| 				return
 | |
| 			}
 | |
| 			_ = snapshotRepo.Update(snap.ID, map[string]interface{}{"status": constant.StatusSuccess})
 | |
| 		}()
 | |
| 		return "", "", nil
 | |
| 	}
 | |
| 	wg.Wait()
 | |
| 	if !checkIsAllDone(snap.ID) {
 | |
| 		_ = snapshotRepo.Update(snap.ID, map[string]interface{}{"status": constant.StatusFailed})
 | |
| 		return loadLogByStatus(snapStatus), snap.Name, fmt.Errorf("snapshot %s backup failed", snap.Name)
 | |
| 	}
 | |
| 	snapCompress(itemHelper, rootDir)
 | |
| 	if snapStatus.Compress != constant.StatusDone {
 | |
| 		_ = snapshotRepo.Update(snap.ID, map[string]interface{}{"status": constant.StatusFailed})
 | |
| 		return loadLogByStatus(snapStatus), snap.Name, fmt.Errorf("snapshot %s compress failed", snap.Name)
 | |
| 	}
 | |
| 	snapUpload(itemHelper, req.From, fmt.Sprintf("%s.tar.gz", rootDir))
 | |
| 	if snapStatus.Upload != constant.StatusDone {
 | |
| 		_ = snapshotRepo.Update(snap.ID, map[string]interface{}{"status": constant.StatusFailed})
 | |
| 		return loadLogByStatus(snapStatus), snap.Name, fmt.Errorf("snapshot %s upload failed", snap.Name)
 | |
| 	}
 | |
| 	_ = snapshotRepo.Update(snap.ID, map[string]interface{}{"status": constant.StatusSuccess})
 | |
| 	return loadLogByStatus(snapStatus), snap.Name, nil
 | |
| }
 | |
| 
 | |
| func (u *SnapshotService) handleDockerDatas(fileOp files.FileOp, operation string, source, target string) error {
 | |
| 	switch operation {
 | |
| 	case "snapshot":
 | |
| 		if err := handleSnapTar(source, target, "docker_data.tar.gz", ""); err != nil {
 | |
| 			return fmt.Errorf("backup docker data failed, err: %v", err)
 | |
| 		}
 | |
| 	case "recover":
 | |
| 		if err := handleSnapTar(target, u.OriginalPath, "docker_data.tar.gz", ""); err != nil {
 | |
| 			return fmt.Errorf("backup docker data failed, err: %v", err)
 | |
| 		}
 | |
| 		if err := u.handleUnTar(source+"/docker/docker_data.tar.gz", target); err != nil {
 | |
| 			return fmt.Errorf("recover docker data failed, err: %v", err)
 | |
| 		}
 | |
| 	case "re-recover":
 | |
| 		if err := u.handleUnTar(source+"/docker/docker_data.tar.gz", target); err != nil {
 | |
| 			return fmt.Errorf("re-recover docker data failed, err: %v", err)
 | |
| 		}
 | |
| 	case "rollback":
 | |
| 		if err := u.handleUnTar(source+"/docker_data.tar.gz", target); err != nil {
 | |
| 			return fmt.Errorf("rollback docker data failed, err: %v", err)
 | |
| 		}
 | |
| 	}
 | |
| 	global.LOG.Info("handle docker data dir successful!")
 | |
| 	return nil
 | |
| }
 | |
| 
 | |
| func (u *SnapshotService) handleDockerDatasWithSave(fileOp files.FileOp, operation, source, target string) error {
 | |
| 	switch operation {
 | |
| 	case "recover":
 | |
| 		if err := u.handleDockerDatasWithSave(fileOp, "snapshot", "", u.OriginalPath); err != nil {
 | |
| 			return fmt.Errorf("backup docker data failed, err: %v", err)
 | |
| 		}
 | |
| 		std, err := cmd.Execf("docker load < %s", path.Join(source, "docker/docker_image.tar"))
 | |
| 		if err != nil {
 | |
| 			return errors.New(std)
 | |
| 		}
 | |
| 	case "re-recover":
 | |
| 		std, err := cmd.Execf("docker load < %s", path.Join(source, "docker/docker_image.tar"))
 | |
| 		if err != nil {
 | |
| 			return errors.New(std)
 | |
| 		}
 | |
| 	case "rollback":
 | |
| 		std, err := cmd.Execf("docker load < %s", path.Join(source, "docker_image.tar"))
 | |
| 		if err != nil {
 | |
| 			return errors.New(std)
 | |
| 		}
 | |
| 	}
 | |
| 	global.LOG.Info("handle docker data successful!")
 | |
| 	return nil
 | |
| }
 | |
| 
 | |
| func (u *SnapshotService) handleDaemonJson(fileOp files.FileOp, operation string, source, target string) error {
 | |
| 	daemonJsonPath := "/etc/docker/daemon.json"
 | |
| 	if operation == "snapshot" || operation == "recover" {
 | |
| 		_, err := os.Stat(daemonJsonPath)
 | |
| 		if os.IsNotExist(err) {
 | |
| 			global.LOG.Info("no daemon.json in snapshot and system now, nothing happened")
 | |
| 		}
 | |
| 		if err == nil {
 | |
| 			if err := fileOp.CopyFile(daemonJsonPath, target); err != nil {
 | |
| 				return fmt.Errorf("backup docker daemon.json failed, err: %v", err)
 | |
| 			}
 | |
| 		}
 | |
| 	}
 | |
| 	if operation == "recover" || operation == "rollback" || operation == "re-recover" {
 | |
| 		_, sourceErr := os.Stat(source)
 | |
| 		if os.IsNotExist(sourceErr) {
 | |
| 			_ = os.Remove(daemonJsonPath)
 | |
| 		}
 | |
| 		if sourceErr == nil {
 | |
| 			if err := fileOp.CopyFile(source, "/etc/docker"); err != nil {
 | |
| 				return fmt.Errorf("recover docker daemon.json failed, err: %v", err)
 | |
| 			}
 | |
| 		}
 | |
| 	}
 | |
| 	global.LOG.Info("handle docker daemon.json successful!")
 | |
| 	return nil
 | |
| }
 | |
| 
 | |
| func (u *SnapshotService) handlePanelBinary(fileOp files.FileOp, operation string, source, target string) error {
 | |
| 	panelPath := "/usr/local/bin/1panel"
 | |
| 	if operation == "snapshot" || operation == "recover" {
 | |
| 		if _, err := os.Stat(panelPath); err != nil {
 | |
| 			return fmt.Errorf("1panel binary is not found in %s, err: %v", panelPath, err)
 | |
| 		} else {
 | |
| 			if err := cpBinary([]string{panelPath}, target); err != nil {
 | |
| 				return fmt.Errorf("backup 1panel binary failed, err: %v", err)
 | |
| 			}
 | |
| 		}
 | |
| 	}
 | |
| 	if operation == "recover" || operation == "rollback" || operation == "re-recover" {
 | |
| 		if _, err := os.Stat(source); err != nil {
 | |
| 			return fmt.Errorf("1panel binary is not found in snapshot, err: %v", err)
 | |
| 		} else {
 | |
| 			if err := cpBinary([]string{source}, "/usr/local/bin/1panel"); err != nil {
 | |
| 				return fmt.Errorf("recover 1panel binary failed, err: %v", err)
 | |
| 			}
 | |
| 		}
 | |
| 	}
 | |
| 	global.LOG.Info("handle binary panel successful!")
 | |
| 	return nil
 | |
| }
 | |
| 
 | |
| func (u *SnapshotService) handlePanelctlBinary(fileOp files.FileOp, operation string, source, target string) error {
 | |
| 	panelctlPath := "/usr/local/bin/1pctl"
 | |
| 	if operation == "snapshot" || operation == "recover" {
 | |
| 		if _, err := os.Stat(panelctlPath); err != nil {
 | |
| 			return fmt.Errorf("1pctl binary is not found in %s, err: %v", panelctlPath, err)
 | |
| 		} else {
 | |
| 			if err := cpBinary([]string{panelctlPath}, target); err != nil {
 | |
| 				return fmt.Errorf("backup 1pctl binary failed, err: %v", err)
 | |
| 			}
 | |
| 		}
 | |
| 	}
 | |
| 	if operation == "recover" || operation == "rollback" || operation == "re-recover" {
 | |
| 		if _, err := os.Stat(source); err != nil {
 | |
| 			return fmt.Errorf("1pctl binary is not found in snapshot, err: %v", err)
 | |
| 		} else {
 | |
| 			if err := cpBinary([]string{source}, "/usr/local/bin/1pctl"); err != nil {
 | |
| 				return fmt.Errorf("recover 1pctl binary failed, err: %v", err)
 | |
| 			}
 | |
| 		}
 | |
| 	}
 | |
| 	global.LOG.Info("handle binary 1pactl successful!")
 | |
| 	return nil
 | |
| }
 | |
| 
 | |
| func (u *SnapshotService) handlePanelService(fileOp files.FileOp, operation string, source, target string) error {
 | |
| 	panelServicePath := "/etc/systemd/system/1panel.service"
 | |
| 	if operation == "snapshot" || operation == "recover" {
 | |
| 		if _, err := os.Stat(panelServicePath); err != nil {
 | |
| 			return fmt.Errorf("1panel service is not found in %s, err: %v", panelServicePath, err)
 | |
| 		} else {
 | |
| 			if err := cpBinary([]string{panelServicePath}, target); err != nil {
 | |
| 				return fmt.Errorf("backup 1panel service failed, err: %v", err)
 | |
| 			}
 | |
| 		}
 | |
| 	}
 | |
| 	if operation == "recover" || operation == "rollback" || operation == "re-recover" {
 | |
| 		if _, err := os.Stat(source); err != nil {
 | |
| 			return fmt.Errorf("1panel service is not found in snapshot, err: %v", err)
 | |
| 		} else {
 | |
| 			if err := cpBinary([]string{source}, "/etc/systemd/system/1panel.service"); err != nil {
 | |
| 				return fmt.Errorf("recover 1panel service failed, err: %v", err)
 | |
| 			}
 | |
| 		}
 | |
| 	}
 | |
| 	global.LOG.Info("handle panel service successful!")
 | |
| 	return nil
 | |
| }
 | |
| 
 | |
| func (u *SnapshotService) handleBackupDatas(fileOp files.FileOp, operation string, source, target string) error {
 | |
| 	switch operation {
 | |
| 	case "recover":
 | |
| 		if err := handleSnapTar(target, u.OriginalPath, "1panel_backup.tar.gz", "./system;"); err != nil {
 | |
| 			return fmt.Errorf("restore original local backup dir data failed, err: %v", err)
 | |
| 		}
 | |
| 		if err := u.handleUnTar(source+"/1panel/1panel_backup.tar.gz", target); err != nil {
 | |
| 			return fmt.Errorf("recover local backup dir data failed, err: %v", err)
 | |
| 		}
 | |
| 	case "re-recover":
 | |
| 		if err := u.handleUnTar(source+"/1panel/1panel_backup.tar.gz", target); err != nil {
 | |
| 			return fmt.Errorf("retry recover  local backup dir data failed, err: %v", err)
 | |
| 		}
 | |
| 	case "rollback":
 | |
| 		if err := u.handleUnTar(source+"/1panel_backup.tar.gz", target); err != nil {
 | |
| 			return fmt.Errorf("rollback local backup dir data failed, err: %v", err)
 | |
| 		}
 | |
| 	}
 | |
| 	global.LOG.Info("handle backup data successful!")
 | |
| 	return nil
 | |
| }
 | |
| 
 | |
| func (u *SnapshotService) handlePanelDatas(snapID uint, fileOp files.FileOp, operation string, source, target, backupDir, dockerDir string) error {
 | |
| 	switch operation {
 | |
| 	case "snapshot":
 | |
| 		exclusionRules := "./tmp;./log;./cache;"
 | |
| 		if strings.Contains(backupDir, source) {
 | |
| 			exclusionRules += ("." + strings.ReplaceAll(backupDir, source, "") + ";")
 | |
| 		}
 | |
| 		if strings.Contains(dockerDir, source) {
 | |
| 			exclusionRules += ("." + strings.ReplaceAll(dockerDir, source, "") + ";")
 | |
| 		}
 | |
| 
 | |
| 		if err := handleSnapTar(source, target, "1panel_data.tar.gz", exclusionRules); err != nil {
 | |
| 			return fmt.Errorf("backup panel data failed, err: %v", err)
 | |
| 		}
 | |
| 	case "recover":
 | |
| 		exclusionRules := "./tmp;./log;./cache;"
 | |
| 		if strings.Contains(backupDir, target) {
 | |
| 			exclusionRules += ("." + strings.ReplaceAll(backupDir, target, "") + ";")
 | |
| 		}
 | |
| 		if strings.Contains(dockerDir, target) {
 | |
| 			exclusionRules += ("." + strings.ReplaceAll(dockerDir, target, "") + ";")
 | |
| 		}
 | |
| 
 | |
| 		_ = snapshotRepo.Update(snapID, map[string]interface{}{"recover_status": ""})
 | |
| 		if err := handleSnapTar(target, u.OriginalPath, "1panel_data.tar.gz", exclusionRules); err != nil {
 | |
| 			return fmt.Errorf("restore original panel data failed, err: %v", err)
 | |
| 		}
 | |
| 		_ = snapshotRepo.Update(snapID, map[string]interface{}{"recover_status": constant.StatusWaiting})
 | |
| 
 | |
| 		_ = fileOp.Fs.RemoveAll(path.Join(target, "apps"))
 | |
| 		if err := u.handleUnTar(source+"/1panel/1panel_data.tar.gz", target); err != nil {
 | |
| 			return fmt.Errorf("recover panel data failed, err: %v", err)
 | |
| 		}
 | |
| 	case "re-recover":
 | |
| 		_ = fileOp.Fs.RemoveAll(path.Join(target, "apps"))
 | |
| 		if err := u.handleUnTar(source+"/1panel/1panel_data.tar.gz", target); err != nil {
 | |
| 			return fmt.Errorf("retry recover panel data failed, err: %v", err)
 | |
| 		}
 | |
| 	case "rollback":
 | |
| 		_ = fileOp.Fs.RemoveAll(path.Join(target, "apps"))
 | |
| 		if err := u.handleUnTar(source+"/1panel_data.tar.gz", target); err != nil {
 | |
| 			return fmt.Errorf("rollback panel data failed, err: %v", err)
 | |
| 		}
 | |
| 	}
 | |
| 	return nil
 | |
| }
 | |
| 
 | |
| func (u *SnapshotService) loadDockerDataDir() (string, bool, error) {
 | |
| 	client, err := docker.NewDockerClient()
 | |
| 	if err != nil {
 | |
| 		return "", false, fmt.Errorf("new docker client failed, err: %v", err)
 | |
| 	}
 | |
| 	info, err := client.Info(context.Background())
 | |
| 	if err != nil {
 | |
| 		return "", false, fmt.Errorf("load docker info failed, err: %v", err)
 | |
| 	}
 | |
| 	return info.DockerRootDir, info.LiveRestoreEnabled, nil
 | |
| }
 | |
| 
 | |
| func (u *SnapshotService) Delete(req dto.BatchDeleteReq) error {
 | |
| 	backups, _ := snapshotRepo.GetList(commonRepo.WithIdsIn(req.Ids))
 | |
| 	localDir, err := loadLocalDir()
 | |
| 	if err != nil {
 | |
| 		return err
 | |
| 	}
 | |
| 	for _, snap := range backups {
 | |
| 		itemFile := path.Join(localDir, "system", snap.Name)
 | |
| 		_ = os.RemoveAll(itemFile)
 | |
| 
 | |
| 		itemTarFile := path.Join(global.CONF.System.TmpDir, "system", snap.Name+".tar.gz")
 | |
| 		_ = os.Remove(itemTarFile)
 | |
| 
 | |
| 		_ = snapshotRepo.DeleteStatus(snap.ID)
 | |
| 	}
 | |
| 	if err := snapshotRepo.Delete(commonRepo.WithIdsIn(req.Ids)); err != nil {
 | |
| 		return err
 | |
| 	}
 | |
| 
 | |
| 	return nil
 | |
| }
 | |
| 
 | |
| func updateRecoverStatus(id uint, interruptStep, status string, message string) {
 | |
| 	if status != constant.StatusSuccess {
 | |
| 		global.LOG.Errorf("recover failed, err: %s", message)
 | |
| 	}
 | |
| 	if err := snapshotRepo.Update(id, map[string]interface{}{
 | |
| 		"interrupt_step":    interruptStep,
 | |
| 		"recover_status":    status,
 | |
| 		"recover_message":   message,
 | |
| 		"last_recovered_at": time.Now().Format("2006-01-02 15:04:05"),
 | |
| 	}); err != nil {
 | |
| 		global.LOG.Errorf("update snap recover status failed, err: %v", err)
 | |
| 	}
 | |
| 	_ = settingRepo.Update("SystemStatus", "Free")
 | |
| }
 | |
| func updateRollbackStatus(id uint, status string, message string) {
 | |
| 	_ = settingRepo.Update("SystemStatus", "Free")
 | |
| 	if status == constant.StatusSuccess {
 | |
| 		if err := snapshotRepo.Update(id, map[string]interface{}{
 | |
| 			"recover_status":     "",
 | |
| 			"recover_message":    "",
 | |
| 			"interrupt_step":     "",
 | |
| 			"rollback_status":    "",
 | |
| 			"rollback_message":   "",
 | |
| 			"last_rollbacked_at": time.Now().Format("2006-01-02 15:04:05"),
 | |
| 		}); err != nil {
 | |
| 			global.LOG.Errorf("update snap recover status failed, err: %v", err)
 | |
| 		}
 | |
| 		return
 | |
| 	}
 | |
| 	global.LOG.Errorf("rollback failed, err: %s", message)
 | |
| 	if err := snapshotRepo.Update(id, map[string]interface{}{
 | |
| 		"rollback_status":    status,
 | |
| 		"rollback_message":   message,
 | |
| 		"last_rollbacked_at": time.Now().Format("2006-01-02 15:04:05"),
 | |
| 	}); err != nil {
 | |
| 		global.LOG.Errorf("update snap recover status failed, err: %v", err)
 | |
| 	}
 | |
| }
 | |
| 
 | |
| func cpBinary(src []string, dst string) error {
 | |
| 	global.LOG.Debugf(fmt.Sprintf("\\cp -f %s %s", strings.Join(src, " "), dst))
 | |
| 	stdout, err := cmd.Exec(fmt.Sprintf("\\cp -f %s %s", strings.Join(src, " "), dst))
 | |
| 	if err != nil {
 | |
| 		return fmt.Errorf("cp file failed, stdout: %v, err: %v", stdout, err)
 | |
| 	}
 | |
| 	return nil
 | |
| }
 | |
| 
 | |
| func (u *SnapshotService) updateLiveRestore(enabled bool) error {
 | |
| 	if _, err := os.Stat(constant.DaemonJsonPath); err != nil {
 | |
| 		return fmt.Errorf("load docker daemon.json conf failed, err: %v", err)
 | |
| 	}
 | |
| 	file, err := os.ReadFile(constant.DaemonJsonPath)
 | |
| 	if err != nil {
 | |
| 		return err
 | |
| 	}
 | |
| 	daemonMap := make(map[string]interface{})
 | |
| 	_ = json.Unmarshal(file, &daemonMap)
 | |
| 
 | |
| 	if !enabled {
 | |
| 		delete(daemonMap, "live-restore")
 | |
| 	} else {
 | |
| 		daemonMap["live-restore"] = enabled
 | |
| 	}
 | |
| 	newJson, err := json.MarshalIndent(daemonMap, "", "\t")
 | |
| 	if err != nil {
 | |
| 		return err
 | |
| 	}
 | |
| 	if err := os.WriteFile(constant.DaemonJsonPath, newJson, 0640); err != nil {
 | |
| 		return err
 | |
| 	}
 | |
| 
 | |
| 	stdout, err := cmd.Exec("systemctl restart docker")
 | |
| 	if err != nil {
 | |
| 		return errors.New(stdout)
 | |
| 	}
 | |
| 
 | |
| 	ticker := time.NewTicker(3 * time.Second)
 | |
| 	ctx, cancel := context.WithTimeout(context.Background(), time.Second*30)
 | |
| 	defer cancel()
 | |
| 	for range ticker.C {
 | |
| 		select {
 | |
| 		case <-ctx.Done():
 | |
| 			return errors.New("the docker service cannot be restarted")
 | |
| 		default:
 | |
| 			stdout, err := cmd.Exec("systemctl is-active docker")
 | |
| 			if string(stdout) == "active\n" && err == nil {
 | |
| 				global.LOG.Info("docker restart with new live-restore successful!")
 | |
| 				return nil
 | |
| 			}
 | |
| 		}
 | |
| 	}
 | |
| 	return nil
 | |
| }
 | |
| 
 | |
| func (u *SnapshotService) handleUnTar(sourceDir, targetDir string) error {
 | |
| 	if _, err := os.Stat(targetDir); err != nil && os.IsNotExist(err) {
 | |
| 		if err = os.MkdirAll(targetDir, os.ModePerm); err != nil {
 | |
| 			return err
 | |
| 		}
 | |
| 	}
 | |
| 
 | |
| 	commands := fmt.Sprintf("tar -zxf %s -C %s .", sourceDir, targetDir)
 | |
| 	global.LOG.Debug(commands)
 | |
| 	stdout, err := cmd.ExecWithTimeOut(commands, 30*time.Minute)
 | |
| 	if err != nil {
 | |
| 		if len(stdout) != 0 {
 | |
| 			global.LOG.Errorf("do handle untar failed, stdout: %s, err: %v", stdout, err)
 | |
| 			return fmt.Errorf("do handle untar failed, stdout: %s, err: %v", stdout, err)
 | |
| 		}
 | |
| 	}
 | |
| 	return nil
 | |
| }
 | |
| 
 | |
| func rebuildAllAppInstall() error {
 | |
| 	global.LOG.Debug("start to rebuild all app")
 | |
| 	appInstalls, err := appInstallRepo.ListBy()
 | |
| 	if err != nil {
 | |
| 		global.LOG.Errorf("get all app installed for rebuild failed, err: %v", err)
 | |
| 		return err
 | |
| 	}
 | |
| 	var wg sync.WaitGroup
 | |
| 	for i := 0; i < len(appInstalls); i++ {
 | |
| 		wg.Add(1)
 | |
| 		appInstalls[i].Status = constant.Rebuilding
 | |
| 		_ = appInstallRepo.Save(context.Background(), &appInstalls[i])
 | |
| 		go func(app model.AppInstall) {
 | |
| 			defer wg.Done()
 | |
| 			dockerComposePath := app.GetComposePath()
 | |
| 			out, err := compose.Down(dockerComposePath)
 | |
| 			if err != nil {
 | |
| 				_ = handleErr(app, err, out)
 | |
| 				return
 | |
| 			}
 | |
| 			out, err = compose.Up(dockerComposePath)
 | |
| 			if err != nil {
 | |
| 				_ = handleErr(app, err, out)
 | |
| 				return
 | |
| 			}
 | |
| 			app.Status = constant.Running
 | |
| 			_ = appInstallRepo.Save(context.Background(), &app)
 | |
| 		}(appInstalls[i])
 | |
| 	}
 | |
| 	wg.Wait()
 | |
| 	return nil
 | |
| }
 | |
| 
 | |
| func isNewSnapVersion(version string) bool {
 | |
| 	versionItem := "v1.5.0"
 | |
| 	if version == versionItem {
 | |
| 		return true
 | |
| 	}
 | |
| 	version1s := strings.Split(version, ".")
 | |
| 	version2s := strings.Split(versionItem, ".")
 | |
| 
 | |
| 	n := min(len(version1s), len(version2s))
 | |
| 	re := regexp.MustCompile("[0-9]+")
 | |
| 	for i := 0; i < n; i++ {
 | |
| 		sVersion1s := re.FindAllString(version1s[i], -1)
 | |
| 		sVersion2s := re.FindAllString(version2s[i], -1)
 | |
| 		if len(sVersion1s) == 0 {
 | |
| 			return false
 | |
| 		}
 | |
| 		if len(sVersion2s) == 0 {
 | |
| 			return false
 | |
| 		}
 | |
| 		v1num, _ := strconv.Atoi(sVersion1s[0])
 | |
| 		v2num, _ := strconv.Atoi(sVersion2s[0])
 | |
| 		if v1num == v2num {
 | |
| 			continue
 | |
| 		} else {
 | |
| 			return v1num > v2num
 | |
| 		}
 | |
| 	}
 | |
| 	return true
 | |
| }
 | |
| 
 | |
| func min(a, b int) int {
 | |
| 	if a < b {
 | |
| 		return a
 | |
| 	}
 | |
| 	return b
 | |
| }
 | |
| 
 | |
| func checkIsAllDone(snapID uint) bool {
 | |
| 	status, err := snapshotRepo.GetStatus(snapID)
 | |
| 	if err != nil {
 | |
| 		return false
 | |
| 	}
 | |
| 	if status.Panel != constant.StatusDone {
 | |
| 		return false
 | |
| 	}
 | |
| 	if status.PanelInfo != constant.StatusDone {
 | |
| 		return false
 | |
| 	}
 | |
| 	if status.DaemonJson != constant.StatusDone {
 | |
| 		return false
 | |
| 	}
 | |
| 	if status.AppData != constant.StatusDone {
 | |
| 		return false
 | |
| 	}
 | |
| 	if status.PanelData != constant.StatusDone {
 | |
| 		return false
 | |
| 	}
 | |
| 	if status.BackupData != constant.StatusDone {
 | |
| 		return false
 | |
| 	}
 | |
| 	return true
 | |
| }
 | |
| 
 | |
| func loadLogByStatus(status model.SnapshotStatus) string {
 | |
| 	logs := ""
 | |
| 	logs += fmt.Sprintf("Write 1Panel basic information: %s \n", status.PanelInfo)
 | |
| 	logs += fmt.Sprintf("Backup 1Panel system files: %s \n", status.Panel)
 | |
| 	logs += fmt.Sprintf("Backup Docker configuration file: %s \n", status.DaemonJson)
 | |
| 	logs += fmt.Sprintf("Backup installed apps from 1Panel: %s \n", status.AppData)
 | |
| 	logs += fmt.Sprintf("Backup 1Panel data directory: %s \n", status.PanelData)
 | |
| 	logs += fmt.Sprintf("Backup local backup directory for 1Panel: %s \n", status.BackupData)
 | |
| 	logs += fmt.Sprintf("Create snapshot file: %s \n", status.Compress)
 | |
| 	logs += fmt.Sprintf("Snapshot size: %s \n", status.Size)
 | |
| 	logs += fmt.Sprintf("Upload snapshot file: %s \n", status.Upload)
 | |
| 
 | |
| 	return logs
 | |
| }
 |