mirror of https://github.com/1Panel-dev/1Panel
267 lines
8.5 KiB
Go
267 lines
8.5 KiB
Go
package service
|
|
|
|
import (
|
|
"context"
|
|
"crypto/tls"
|
|
"errors"
|
|
"fmt"
|
|
"net/http"
|
|
"os"
|
|
"runtime"
|
|
"strconv"
|
|
"strings"
|
|
"time"
|
|
|
|
"gitee.com/openeuler/go-gitee/gitee"
|
|
"github.com/1Panel-dev/1Panel/backend/app/dto"
|
|
"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/files"
|
|
"github.com/google/go-github/github"
|
|
)
|
|
|
|
type UpgradeService struct{}
|
|
|
|
type IUpgradeService interface {
|
|
Upgrade(req dto.Upgrade) error
|
|
SearchUpgrade() (*dto.UpgradeInfo, error)
|
|
}
|
|
|
|
func NewIUpgradeService() IUpgradeService {
|
|
return &UpgradeService{}
|
|
}
|
|
|
|
func (u *UpgradeService) SearchUpgrade() (*dto.UpgradeInfo, error) {
|
|
currentVerion, err := settingRepo.Get(settingRepo.WithByKey("SystemVersion"))
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
var releaseInfo dto.UpgradeInfo
|
|
isGiteeOK := checkValid("https://gitee.com/wanghe-fit2cloud/1Panel")
|
|
if isGiteeOK {
|
|
releaseInfo, err = u.loadLatestFromGitee()
|
|
if err != nil {
|
|
global.LOG.Error(err)
|
|
}
|
|
}
|
|
if len(releaseInfo.NewVersion) == 0 {
|
|
isGithubOK := checkValid("https://gitee.com/1Panel-dev/1Panel")
|
|
if isGithubOK {
|
|
releaseInfo, err = u.loadLatestFromGithub()
|
|
if err != nil {
|
|
global.LOG.Error(err)
|
|
return nil, err
|
|
}
|
|
}
|
|
}
|
|
if len(releaseInfo.NewVersion) != 0 {
|
|
isNew, err := compareVersion(currentVerion.Value, releaseInfo.NewVersion)
|
|
if !isNew && err != nil {
|
|
return nil, err
|
|
}
|
|
return &releaseInfo, nil
|
|
}
|
|
|
|
return nil, errors.New("both gitee and github were unavailable")
|
|
}
|
|
|
|
func (u *UpgradeService) Upgrade(req dto.Upgrade) error {
|
|
global.LOG.Info("start to upgrade now...")
|
|
fileOp := files.NewFileOp()
|
|
timeStr := time.Now().Format("20060102150405")
|
|
rootDir := fmt.Sprintf("%s/upgrade_%s/downloads", constant.TmpDir, timeStr)
|
|
originalDir := fmt.Sprintf("%s/upgrade_%s/original", constant.TmpDir, timeStr)
|
|
if err := os.MkdirAll(rootDir, os.ModePerm); err != nil {
|
|
return err
|
|
}
|
|
if err := os.MkdirAll(originalDir, os.ModePerm); err != nil {
|
|
return err
|
|
}
|
|
|
|
downloadPath := fmt.Sprintf("https://gitee.com/%s/%s/releases/download/%s/", "wanghe-fit2cloud", "1Panel", req.Version)
|
|
isGiteeOK := checkValid(downloadPath)
|
|
if !isGiteeOK {
|
|
downloadPath = fmt.Sprintf("https://github.com/%s/%s/releases/download/%s/", "wanghe-fit2cloud", "1Panel", req.Version)
|
|
isGithubOK := checkValid(downloadPath)
|
|
if !isGithubOK {
|
|
return errors.New("both gitee and github were unavailabl")
|
|
}
|
|
}
|
|
|
|
fileName := fmt.Sprintf("1panel-%s-%s-%s.tar.gz", req.Version, "linux", runtime.GOARCH)
|
|
_ = settingRepo.Update("SystemStatus", "Upgrading")
|
|
go func() {
|
|
if err := fileOp.DownloadFile(downloadPath+fileName, rootDir+"/service.tar.gz"); err != nil {
|
|
global.LOG.Errorf("download service file failed, err: %v", err)
|
|
_ = settingRepo.Update("SystemStatus", "Free")
|
|
return
|
|
}
|
|
global.LOG.Info("download all file successful!")
|
|
defer func() {
|
|
_ = os.Remove(rootDir)
|
|
}()
|
|
if err := fileOp.Decompress(rootDir+"/service.tar.gz", rootDir, files.TarGz); err != nil {
|
|
global.LOG.Errorf("decompress file failed, err: %v", err)
|
|
_ = settingRepo.Update("SystemStatus", "Free")
|
|
return
|
|
}
|
|
|
|
if err := u.handleBackup(fileOp, originalDir); err != nil {
|
|
global.LOG.Errorf("handle backup original file failed, err: %v", err)
|
|
_ = settingRepo.Update("SystemStatus", "Free")
|
|
return
|
|
}
|
|
global.LOG.Info("backup original data successful, now start to upgrade!")
|
|
|
|
if err := cpBinary(rootDir+"/1panel", "/usr/local/bin/1panel"); err != nil {
|
|
u.handleRollback(fileOp, originalDir, 1)
|
|
global.LOG.Errorf("upgrade 1panel failed, err: %v", err)
|
|
return
|
|
}
|
|
if err := cpBinary(fmt.Sprintf("%s/1panel-online-installer-%s/1pctl", rootDir, req.Version), "/usr/local/bin/1pctl"); err != nil {
|
|
u.handleRollback(fileOp, originalDir, 2)
|
|
global.LOG.Errorf("upgrade 1pctl failed, err: %v", err)
|
|
return
|
|
}
|
|
if err := cpBinary(fmt.Sprintf("%s/1panel-online-installer-%s/1panel/conf/1panel.service", rootDir, req.Version), "/etc/systemd/system/1panel.service"); err != nil {
|
|
u.handleRollback(fileOp, originalDir, 3)
|
|
global.LOG.Errorf("upgrade 1panel.service failed, err: %v", err)
|
|
return
|
|
}
|
|
|
|
global.LOG.Info("upgrade successful!")
|
|
_ = settingRepo.Update("SystemVersion", req.Version)
|
|
_ = settingRepo.Update("SystemStatus", "Free")
|
|
_, _ = cmd.Exec("systemctl daemon-reload && systemctl restart 1panel.service")
|
|
}()
|
|
return nil
|
|
}
|
|
|
|
func (u *UpgradeService) handleBackup(fileOp files.FileOp, originalDir string) error {
|
|
if err := fileOp.Copy("/usr/local/bin/1panel", originalDir); err != nil {
|
|
return err
|
|
}
|
|
if err := fileOp.Copy("/usr/local/bin/1pctl", originalDir); err != nil {
|
|
return err
|
|
}
|
|
if err := fileOp.Copy("/etc/systemd/system/1panel.service", originalDir); err != nil {
|
|
return err
|
|
}
|
|
dbPath := global.CONF.System.DbPath + "/" + global.CONF.System.DbFile
|
|
if err := fileOp.Copy(dbPath, originalDir); err != nil {
|
|
return err
|
|
}
|
|
return nil
|
|
}
|
|
|
|
func (u *UpgradeService) handleRollback(fileOp files.FileOp, originalDir string, errStep int) {
|
|
dbPath := global.CONF.System.DbPath + "/1Panel.db"
|
|
_ = settingRepo.Update("SystemStatus", "Free")
|
|
if err := cpBinary(originalDir+"/1Panel.db", dbPath); err != nil {
|
|
global.LOG.Errorf("rollback 1panel failed, err: %v", err)
|
|
}
|
|
if err := cpBinary(originalDir+"/1panel", "/usr/local/bin/1panel"); err != nil {
|
|
global.LOG.Errorf("rollback 1pctl failed, err: %v", err)
|
|
}
|
|
if errStep == 1 {
|
|
return
|
|
}
|
|
if err := cpBinary(originalDir+"/1pctl", "/usr/local/bin/1pctl"); err != nil {
|
|
global.LOG.Errorf("rollback 1panel failed, err: %v", err)
|
|
}
|
|
if errStep == 2 {
|
|
return
|
|
}
|
|
if err := cpBinary(originalDir+"/1panel.service", "/etc/systemd/system/1panel.service"); err != nil {
|
|
global.LOG.Errorf("rollback 1panel failed, err: %v", err)
|
|
}
|
|
|
|
}
|
|
|
|
func (u *UpgradeService) loadLatestFromGithub() (dto.UpgradeInfo, error) {
|
|
var info dto.UpgradeInfo
|
|
client := github.NewClient(nil)
|
|
ctx, cancle := context.WithTimeout(context.Background(), 3*time.Second)
|
|
defer cancle()
|
|
stats, res, err := client.Repositories.GetLatestRelease(ctx, "wanghe-fit2cloud", "1Panel")
|
|
if res.StatusCode != 200 || err != nil {
|
|
return info, fmt.Errorf("load upgrade info from github failed, err: %v", err)
|
|
}
|
|
info.NewVersion = string(*stats.Name)
|
|
info.ReleaseNote = string(*stats.Body)
|
|
info.CreatedAt = stats.PublishedAt.Add(8 * time.Hour).Format("2006-01-02 15:04:05")
|
|
return info, nil
|
|
}
|
|
|
|
func (u *UpgradeService) loadLatestFromGitee() (dto.UpgradeInfo, error) {
|
|
var info dto.UpgradeInfo
|
|
client := gitee.NewAPIClient(gitee.NewConfiguration())
|
|
ctx, cancle := context.WithTimeout(context.Background(), 3*time.Second)
|
|
defer cancle()
|
|
stats, res, err := client.RepositoriesApi.GetV5ReposOwnerRepoReleasesLatest(ctx, "wanghe-fit2cloud", "1Panel", &gitee.GetV5ReposOwnerRepoReleasesLatestOpts{})
|
|
if res.StatusCode != 200 || err != nil {
|
|
return info, fmt.Errorf("load upgrade info from gitee failed, err: %v", err)
|
|
}
|
|
info.NewVersion = string(stats.Name)
|
|
info.ReleaseNote = string(stats.Body)
|
|
info.CreatedAt = stats.CreatedAt.Format("2006-01-02 15:04:05")
|
|
return info, nil
|
|
}
|
|
|
|
func compareVersion(version, newVersion string) (bool, error) {
|
|
if version == newVersion {
|
|
return false, nil
|
|
}
|
|
if len(version) == 0 || len(newVersion) == 0 {
|
|
return false, fmt.Errorf("incorrect version or new version entered %v -- %v", version, newVersion)
|
|
}
|
|
versions := strings.Split(strings.ReplaceAll(version, "v", ""), ".")
|
|
if len(versions) != 3 {
|
|
return false, fmt.Errorf("incorrect version input %v", version)
|
|
}
|
|
newVersions := strings.Split(strings.ReplaceAll(newVersion, "v", ""), ".")
|
|
if len(newVersions) != 3 {
|
|
return false, fmt.Errorf("incorrect newVersions input %v", version)
|
|
}
|
|
version1, _ := strconv.Atoi(versions[0])
|
|
newVersion1, _ := strconv.Atoi(newVersions[0])
|
|
if newVersion1 > version1 {
|
|
return true, nil
|
|
} else if newVersion1 == version1 {
|
|
version2, _ := strconv.Atoi(versions[1])
|
|
newVersion2, _ := strconv.Atoi(newVersions[1])
|
|
if newVersion2 > version2 {
|
|
return true, nil
|
|
} else if newVersion2 == version2 {
|
|
version3, _ := strconv.Atoi(versions[2])
|
|
newVersion3, _ := strconv.Atoi(newVersions[2])
|
|
if newVersion3 > version3 {
|
|
return true, nil
|
|
} else {
|
|
return false, nil
|
|
}
|
|
} else {
|
|
return false, nil
|
|
}
|
|
} else {
|
|
return false, nil
|
|
}
|
|
}
|
|
|
|
func checkValid(addr string) bool {
|
|
timeout := time.Duration(2 * time.Second)
|
|
tr := &http.Transport{
|
|
TLSClientConfig: &tls.Config{InsecureSkipVerify: true},
|
|
}
|
|
client := http.Client{
|
|
Transport: tr,
|
|
Timeout: timeout,
|
|
}
|
|
if _, err := client.Get(addr); err != nil {
|
|
return false
|
|
}
|
|
return true
|
|
}
|