2
0
mirror of https://github.com/acepanel/panel.git synced 2026-02-04 07:57:21 +08:00

feat: 初步完成升级方法

This commit is contained in:
耗子
2024-10-12 04:31:23 +08:00
parent 09698f7cd3
commit 497e357d65
13 changed files with 176 additions and 507 deletions

View File

@@ -1,7 +1,6 @@
package fail2ban
import (
"fmt"
"net/http"
"regexp"
"strings"
@@ -108,7 +107,7 @@ func (s *Service) Add(w http.ResponseWriter, r *http.Request) {
case "website":
website, err := s.websiteRepo.GetByName(jailWebsiteName)
if err != nil {
service.Error(w, http.StatusUnprocessableEntity, fmt.Sprintf("获取网站配置失败:%s", err))
service.Error(w, http.StatusUnprocessableEntity, "获取网站配置失败:%v", err)
return
}
var ports string

View File

@@ -86,7 +86,7 @@ func (s *Service) Load(w http.ResponseWriter, r *http.Request) {
client := resty.New().SetTimeout(10 * time.Second)
resp, err := client.R().Get(fmt.Sprintf("http://127.0.0.1/phpfpm_status/%d", s.version))
if err != nil || !resp.IsSuccess() {
service.Error(w, http.StatusInternalServerError, fmt.Sprintf("获取负载状态失败:%v", err))
service.Error(w, http.StatusInternalServerError, "获取负载状态失败:%v", err)
return
}

View File

@@ -64,7 +64,7 @@ func (s *Service) List(w http.ResponseWriter, r *http.Request) {
currentModule.AuthUser = value
currentModule.Secret, err = shell.Execf(`grep -E '^%s:.*$' /etc/rsyncd.secrets | awk -F ':' '{print $2}'`, currentModule.AuthUser)
if err != nil {
service.Error(w, http.StatusInternalServerError, "获取模块"+currentModule.AuthUser+"的密钥失败")
service.Error(w, http.StatusInternalServerError, "获取模块%s的密钥失败", currentModule.AuthUser)
return
}
case "hosts allow":
@@ -99,7 +99,7 @@ func (s *Service) Create(w http.ResponseWriter, r *http.Request) {
return
}
if strings.Contains(config, "["+req.Name+"]") {
service.Error(w, http.StatusUnprocessableEntity, "模块 "+req.Name+" 已存在")
service.Error(w, http.StatusUnprocessableEntity, "模块%s已存在", req.Name)
return
}

View File

@@ -96,7 +96,7 @@ func (s *Service) UpdateConfig(w http.ResponseWriter, r *http.Request) {
}
if err = systemctl.Restart(s.name); err != nil {
service.Error(w, http.StatusInternalServerError, fmt.Sprintf("重启 %s 服务失败", s.name))
service.Error(w, http.StatusInternalServerError, "重启 %s 服务失败", s.name)
return
}

View File

@@ -37,4 +37,5 @@ type SettingRepo interface {
Delete(key SettingKey) error
GetPanelSetting(ctx context.Context) (*request.PanelSetting, error)
UpdatePanelSetting(ctx context.Context, setting *request.PanelSetting) (bool, error)
UpdatePanel(version, url, checksum string) error
}

View File

@@ -23,7 +23,7 @@ func initOrm() {
zapLogger.LogMode(logLevel)
zapLogger.SetAsDefault()
db, err := gorm.Open(sqlite.Open(filepath.Join(app.Root, "panel/storage/panel.db")), &gorm.Config{
db, err := gorm.Open(sqlite.Open(filepath.Join(app.Root, "panel/storage/app.db")), &gorm.Config{
Logger: zapLogger,
SkipDefaultTransaction: true,
DisableForeignKeyConstraintWhenMigrating: true,

View File

@@ -3,10 +3,13 @@ package data
import (
"context"
"errors"
"fmt"
"path/filepath"
"time"
"github.com/go-rat/utils/hash"
"github.com/goccy/go-yaml"
"github.com/gookit/color"
"github.com/spf13/cast"
"gorm.io/gorm"
@@ -14,6 +17,7 @@ import (
"github.com/TheTNB/panel/internal/biz"
"github.com/TheTNB/panel/internal/http/request"
"github.com/TheTNB/panel/pkg/io"
"github.com/TheTNB/panel/pkg/shell"
"github.com/TheTNB/panel/pkg/types"
)
@@ -167,3 +171,117 @@ func (r *settingRepo) UpdatePanelSetting(ctx context.Context, setting *request.P
return restartFlag, nil
}
func (r *settingRepo) UpdatePanel(version, url, checksum string) error {
name := filepath.Base(url)
color.Greenln("目标版本: %s", version)
color.Greenln("下载链接: %s", url)
color.Greenln("文件名: %s", name)
color.Greenln("前置检查...")
if io.Exists("/tmp/panel-storage.zip") || io.Exists("/tmp/panel-config.zip") {
return errors.New("检测到 /tmp 存在临时文件,可能是上次更新失败导致的,请谨慎排除后重试")
}
color.Greenln("备份面板数据...")
// 备份面板
if err := io.Compress([]string{filepath.Join(app.Root, "panel")}, filepath.Join(app.Root, fmt.Sprintf("backup/panel/panel-%s.zip", time.Now().Format("20060102150405"))), io.Zip); err != nil {
color.Redln("备份面板失败")
return err
}
if err := io.Compress([]string{filepath.Join(app.Root, "panel/storage")}, "/tmp/panel-storage.zip", io.Zip); err != nil {
color.Redln("备份面板数据失败")
return err
}
if err := io.Compress([]string{filepath.Join(app.Root, "panel/config")}, "/tmp/panel-config.zip", io.Zip); err != nil {
color.Redln("备份面板配置失败")
return err
}
if !io.Exists("/tmp/panel-storage.zip") || !io.Exists("/tmp/panel-config.zip") {
return errors.New("备份面板数据失败")
}
color.Greenln("备份完成")
color.Greenln("清理旧版本...")
if _, err := shell.Execf("rm -rf %s/panel/*", app.Root); err != nil {
color.Redln("清理旧版本失败")
return err
}
color.Greenln("清理完成")
color.Greenln("正在下载...")
if _, err := shell.Execf("wget -T 120 -t 3 -O %s/panel/%s %s", app.Root, name, url); err != nil {
color.Redln("下载失败")
return err
}
if _, err := shell.Execf("wget -T 20 -t 3 -O %s/panel/%s %s", app.Root, name+".sha256", checksum); err != nil {
color.Redln("下载失败")
return err
}
if !io.Exists(filepath.Join(app.Root, "panel", name)) || !io.Exists(filepath.Join(app.Root, "panel", name+".sha256")) {
return errors.New("下载失败")
}
color.Greenln("下载完成")
color.Greenln("校验下载文件...")
check, err := shell.Execf("cd %s/panel && sha256sum -c %s --ignore-missing", app.Root, name+".sha256")
if check != name+": OK" || err != nil {
return errors.New("下载文件校验失败")
}
if err = io.Remove(filepath.Join(app.Root, "panel", name+".sha256")); err != nil {
color.Redln("清理校验文件失败")
return err
}
color.Greenln("文件校验完成")
color.Greenln("更新新版本...")
if _, err = shell.Execf("cd %s/panel && unzip -o %s && rm -rf %s", app.Root, name, name); err != nil {
color.Redln("更新失败")
return err
}
if !io.Exists(filepath.Join(app.Root, "panel", "web")) {
return errors.New("更新失败,可能是下载过程中出现了问题")
}
color.Greenln("更新完成")
color.Greenln("恢复面板数据...")
if err = io.UnCompress("/tmp/panel-storage.zip", filepath.Join(app.Root, "panel/storage"), io.Zip); err != nil {
color.Redln("恢复面板数据失败")
return err
}
if err = io.UnCompress("/tmp/panel-config.zip", filepath.Join(app.Root, "panel/config"), io.Zip); err != nil {
color.Redln("恢复面板配置失败")
return err
}
if !io.Exists(filepath.Join(app.Root, "panel/storage/app.db")) {
return errors.New("恢复面板数据失败")
}
color.Greenln("恢复完成")
color.Greenln("运行升级后脚本...")
if _, err = shell.Execf("curl -fsLm 10 https://dl.cdn.haozi.net/panel/auto_update.sh | bash"); err != nil {
color.Redln("运行面板升级后脚本失败")
return err
}
if _, err = shell.Execf(`wget -O /etc/systemd/system/panel.service https://dl.cdn.haozi.net/panel/panel.service && sed -i "s|/www|%s|g" /etc/systemd/system/panel.service`, app.Root); err != nil {
color.Redln("下载面板服务文件失败")
return err
}
if _, err = shell.Execf("panel-cli setting write version %s", version); err != nil {
color.Redln("写入面板版本号失败")
return err
}
color.Greenln("设置面板文件权限...")
_ = io.Chmod("/etc/systemd/system/panel.servic", 0700)
_ = io.Chmod(filepath.Join(app.Root, "panel"), 0700)
color.Greenln("设置完成")
color.Greenln("升级完成")
_, _ = shell.Execf("systemctl daemon-reload")
_ = io.Remove("/tmp/panel-storage.zip")
_ = io.Remove("/tmp/panel-config.zip")
return nil
}

View File

@@ -36,12 +36,12 @@ func Success(w http.ResponseWriter, data any) {
}
// Error 响应错误
func Error(w http.ResponseWriter, code int, message string) {
func Error(w http.ResponseWriter, code int, format string, args ...any) {
render := chix.NewRender(w)
defer render.Release()
render.Status(code)
render.JSON(&ErrorResponse{
Message: message,
Message: fmt.Sprintf(format, args...),
})
}
@@ -117,8 +117,10 @@ func Bind[T any](r *http.Request) (*T, error) {
func Paginate[T any](r *http.Request, allItems []T) (pagedItems []T, total uint) {
req, err := Bind[request.Paginate](r)
if err != nil {
req.Page = 1
req.Limit = 10
req = &request.Paginate{
Page: 1,
Limit: 10,
}
}
total = uint(len(allItems))
startIndex := (req.Page - 1) * req.Limit

View File

@@ -7,12 +7,14 @@ import (
"strings"
"github.com/go-rat/chix"
"github.com/go-rat/utils/env"
"github.com/hashicorp/go-version"
"github.com/spf13/cast"
"github.com/TheTNB/panel/internal/app"
"github.com/TheTNB/panel/internal/biz"
"github.com/TheTNB/panel/internal/data"
"github.com/TheTNB/panel/pkg/api"
"github.com/TheTNB/panel/pkg/db"
"github.com/TheTNB/panel/pkg/shell"
"github.com/TheTNB/panel/pkg/tools"
@@ -20,6 +22,7 @@ import (
)
type InfoService struct {
api *api.API
taskRepo biz.TaskRepo
websiteRepo biz.WebsiteRepo
appRepo biz.AppRepo
@@ -29,6 +32,7 @@ type InfoService struct {
func NewInfoService() *InfoService {
return &InfoService{
api: api.NewAPI(app.Version),
taskRepo: data.NewTaskRepo(),
websiteRepo: data.NewWebsiteRepo(),
appRepo: data.NewAppRepo(),
@@ -204,7 +208,7 @@ func (s *InfoService) InstalledDbAndPhp(w http.ResponseWriter, r *http.Request)
func (s *InfoService) CheckUpdate(w http.ResponseWriter, r *http.Request) {
current := app.Conf.MustString("app.version")
latest, err := tools.GetLatestPanelVersion()
latest, err := s.api.LatestVersion()
if err != nil {
Error(w, http.StatusInternalServerError, "获取最新版本失败")
return
@@ -234,7 +238,7 @@ func (s *InfoService) CheckUpdate(w http.ResponseWriter, r *http.Request) {
func (s *InfoService) UpdateInfo(w http.ResponseWriter, r *http.Request) {
current := app.Conf.MustString("app.version")
latest, err := tools.GetLatestPanelVersion()
latest, err := s.api.LatestVersion()
if err != nil {
Error(w, http.StatusInternalServerError, "获取最新版本失败")
return
@@ -255,23 +259,13 @@ func (s *InfoService) UpdateInfo(w http.ResponseWriter, r *http.Request) {
return
}
versions, err := tools.GenerateVersions(current, latest.Version)
versions, err := s.api.IntermediateVersions()
if err != nil {
Error(w, http.StatusInternalServerError, "获取更新信息失败")
Error(w, http.StatusInternalServerError, "获取更新信息失败%v", err)
return
}
var versionInfo []tools.PanelInfo
for _, v := range versions {
info, err := tools.GetPanelVersion(v)
if err != nil {
continue
}
versionInfo = append(versionInfo, info)
}
Success(w, versionInfo)
Success(w, versions)
}
func (s *InfoService) Update(w http.ResponseWriter, r *http.Request) {
@@ -281,18 +275,44 @@ func (s *InfoService) Update(w http.ResponseWriter, r *http.Request) {
}
if err := app.Orm.Exec("PRAGMA wal_checkpoint(TRUNCATE)").Error; err != nil {
types.Status = types.StatusFailed
Error(w, http.StatusInternalServerError, fmt.Sprintf("面板数据库异常,已终止操作:%s", err.Error()))
Error(w, http.StatusInternalServerError, "面板数据库异常,已终止操作:%v", err)
return
}
panel, err := tools.GetLatestPanelVersion()
panel, err := s.api.LatestVersion()
if err != nil {
Error(w, http.StatusInternalServerError, "获取最新版本失败")
Error(w, http.StatusInternalServerError, "获取最新版本失败%v", err)
return
}
// TODO 需要修改接口直接把arch传过去
var ver, url, checksum string
if env.IsX86() {
for _, v := range panel.Downloads {
if v.Arch == "amd64" {
ver = panel.Version
url = v.URL
checksum = v.Checksum
break
}
}
} else if env.IsArm() {
for _, v := range panel.Downloads {
if v.Arch == "arm64" {
ver = panel.Version
url = v.URL
checksum = v.Checksum
break
}
}
} else {
Error(w, http.StatusInternalServerError, "不支持的架构")
return
}
types.Status = types.StatusUpgrade
if err = tools.UpdatePanel(panel); err != nil {
if err = s.settingRepo.UpdatePanel(ver, url, checksum); err != nil {
types.Status = types.StatusFailed
Error(w, http.StatusInternalServerError, err.Error())
return

View File

@@ -8,8 +8,14 @@ import (
type Version struct {
CreatedAt time.Time `json:"created_at"`
UpdatedAt time.Time `json:"updated_at"`
Type string `json:"type"`
Version string `json:"version"`
Description string `json:"description"`
Downloads []struct {
URL string `json:"url"`
Arch string `json:"arch"`
Checksum string `json:"checksum"`
} `json:"downloads"`
}
type Versions []Version

View File

@@ -1,21 +0,0 @@
package arch
import "runtime"
// IsArm returns whether the current CPU architecture is ARM.
// IsArm 返回当前 CPU 架构是否为 ARM。
func IsArm() bool {
return runtime.GOARCH == "arm" || runtime.GOARCH == "arm64"
}
// IsX86 returns whether the current CPU architecture is X86.
// IsX86 返回当前 CPU 架构是否为 X86。
func IsX86() bool {
return runtime.GOARCH == "386" || runtime.GOARCH == "amd64"
}
// Is64Bit returns whether the current CPU architecture is 64-bit.
// Is64Bit 返回当前 CPU 架构是否为 64 位。
func Is64Bit() bool {
return runtime.GOARCH == "amd64" || runtime.GOARCH == "arm64"
}

View File

@@ -3,8 +3,6 @@ package tools
import (
"errors"
"fmt"
"os"
"strings"
"time"
@@ -16,10 +14,7 @@ import (
"github.com/shirou/gopsutil/load"
"github.com/shirou/gopsutil/mem"
"github.com/shirou/gopsutil/net"
"github.com/spf13/cast"
"github.com/TheTNB/panel/pkg/arch"
"github.com/TheTNB/panel/pkg/io"
"github.com/TheTNB/panel/pkg/shell"
)
@@ -65,415 +60,6 @@ func GetMonitoringInfo() MonitoringInfo {
return res
}
// GenerateVersions 获取版本列表
func GenerateVersions(start, end string) ([]string, error) {
var versions []string
start = strings.TrimPrefix(start, "v")
end = strings.TrimPrefix(end, "v")
startParts := strings.Split(start, ".")
endParts := strings.Split(end, ".")
if len(startParts) != 3 || len(endParts) != 3 {
return nil, fmt.Errorf("版本格式错误")
}
startMajor := cast.ToInt(startParts[0])
startMinor := cast.ToInt(startParts[1])
startPatch := cast.ToInt(startParts[2])
endMajor := cast.ToInt(endParts[0])
endMinor := cast.ToInt(endParts[1])
endPatch := cast.ToInt(endParts[2])
for major := startMajor; major <= endMajor; major++ {
for minor := 0; minor <= 99; minor++ {
for patch := 0; patch <= 99; patch++ {
if major == startMajor && minor < startMinor {
continue
}
if major == startMajor && minor == startMinor && patch <= startPatch {
continue
}
if major == endMajor && minor > endMinor {
return versions, nil
}
if major == endMajor && minor == endMinor && patch > endPatch {
return versions, nil
}
versions = append(versions, fmt.Sprintf("%d.%d.%d", major, minor, patch))
}
}
}
if len(versions) == 0 {
return []string{}, nil
}
return versions, nil
}
type PanelInfo struct {
Name string `json:"name"`
Version string `json:"version"`
DownloadName string `json:"download_name"`
DownloadUrl string `json:"download_url"`
Body string `json:"body"`
Date string `json:"date"`
Checksums string `json:"checksums"`
ChecksumsUrl string `json:"checksums_url"`
}
// GetLatestPanelVersion 获取最新版本
func GetLatestPanelVersion() (PanelInfo, error) {
var info PanelInfo
var output string
var err error
isChina := IsChina()
if isChina {
output, err = shell.Execf(`curl -sSL "https://git.haozi.net/api/v4/projects/opensource%%2Fpanel/releases/permalink/latest"`)
} else {
output, err = shell.Execf(`curl -sSL "https://api.github.com/repos/TheTNB/panel/releases/latest"`)
}
if len(output) == 0 || err != nil {
return info, errors.New("获取最新版本失败")
}
file, err := os.CreateTemp("", "panel")
if err != nil {
return info, errors.New("创建临时文件失败")
}
defer os.Remove(file.Name())
_, err = file.Write([]byte(output))
if err != nil {
return info, errors.New("写入临时文件失败")
}
err = file.Close()
if err != nil {
return info, errors.New("关闭临时文件失败")
}
fileName := file.Name()
var name, version, body, date, downloadName, downloadUrl, checksums, checksumsUrl string
if isChina {
if name, err = shell.Execf("jq -r '.name' %s", fileName); err != nil {
return info, errors.New("获取最新版本失败")
}
if version, err = shell.Execf("jq -r '.tag_name' %s", fileName); err != nil {
return info, errors.New("获取最新版本失败")
}
if body, err = shell.Execf("jq -r '.description' %s", fileName); err != nil {
return info, errors.New("获取最新版本失败")
}
if date, err = shell.Execf("jq -r '.created_at' %s", fileName); err != nil {
return info, errors.New("获取最新版本失败")
}
if checksums, err = shell.Execf("jq -r '.assets.links[] | select(.name | contains(\"checksums\")) | .name' %s", fileName); err != nil {
return info, errors.New("获取最新版本失败")
}
if checksumsUrl, err = shell.Execf("jq -r '.assets.links[] | select(.name | contains(\"checksums\")) | .direct_asset_url' %s", fileName); err != nil {
return info, errors.New("获取最新版本失败")
}
if arch.IsArm() {
if downloadName, err = shell.Execf("jq -r '.assets.links[] | select(.name | contains(\"arm64\")) | .name' %s", fileName); err != nil {
return info, errors.New("获取最新版本失败")
}
if downloadUrl, err = shell.Execf("jq -r '.assets.links[] | select(.name | contains(\"arm64\")) | .direct_asset_url' %s", fileName); err != nil {
return info, errors.New("获取最新版本失败")
}
} else {
if downloadName, err = shell.Execf("jq -r '.assets.links[] | select(.name | contains(\"amd64v2\")) | .name' %s", fileName); err != nil {
return info, errors.New("获取最新版本失败")
}
if downloadUrl, err = shell.Execf("jq -r '.assets.links[] | select(.name | contains(\"amd64v2\")) | .direct_asset_url' %s", fileName); err != nil {
return info, errors.New("获取最新版本失败")
}
}
} else {
if name, err = shell.Execf("jq -r '.name' %s", fileName); err != nil {
return info, errors.New("获取最新版本失败")
}
if version, err = shell.Execf("jq -r '.tag_name' %s", fileName); err != nil {
return info, errors.New("获取最新版本失败")
}
if body, err = shell.Execf("jq -r '.body' %s", fileName); err != nil {
return info, errors.New("获取最新版本失败")
}
if date, err = shell.Execf("jq -r '.published_at' %s", fileName); err != nil {
return info, errors.New("获取最新版本失败")
}
if checksums, err = shell.Execf("jq -r '.assets[] | select(.name | contains(\"checksums\")) | .name' %s", fileName); err != nil {
return info, errors.New("获取最新版本失败")
}
if checksumsUrl, err = shell.Execf("jq -r '.assets[] | select(.name | contains(\"checksums\")) | .browser_download_url' %s", fileName); err != nil {
return info, errors.New("获取最新版本失败")
}
if arch.IsArm() {
if downloadName, err = shell.Execf("jq -r '.assets[] | select(.name | contains(\"arm64\")) | .name' %s", fileName); err != nil {
return info, errors.New("获取最新版本失败")
}
if downloadUrl, err = shell.Execf("jq -r '.assets[] | select(.name | contains(\"arm64\")) | .browser_download_url' %s", fileName); err != nil {
return info, errors.New("获取最新版本失败")
}
} else {
if downloadName, err = shell.Execf("jq -r '.assets[] | select(.name | contains(\"amd64v2\")) | .name' %s", fileName); err != nil {
return info, errors.New("获取最新版本失败")
}
if downloadUrl, err = shell.Execf("jq -r '.assets[] | select(.name | contains(\"amd64v2\")) | .browser_download_url' %s", fileName); err != nil {
return info, errors.New("获取最新版本失败")
}
}
}
info.Name = name
info.Version = version
info.Body = body
info.Date = date
info.DownloadName = downloadName
info.DownloadUrl = downloadUrl
info.Checksums = checksums
info.ChecksumsUrl = checksumsUrl
return info, nil
}
// GetPanelVersion 获取指定面板版本
func GetPanelVersion(version string) (PanelInfo, error) {
var info PanelInfo
var output string
var err error
isChina := IsChina()
if !strings.HasPrefix(version, "v") {
version = "v" + version
}
if isChina {
output, err = shell.Execf(`curl -sSL "https://git.haozi.net/api/v4/projects/opensource%%2Fpanel/releases/%s"`, version)
} else {
output, err = shell.Execf(`curl -sSL "https://api.github.com/repos/TheTNB/panel/releases/tags/%s"`, version)
}
if len(output) == 0 || err != nil {
return info, errors.New("获取面板版本失败")
}
file, err := os.CreateTemp("", "panel")
if err != nil {
return info, errors.New("创建临时文件失败")
}
defer os.Remove(file.Name())
_, err = file.Write([]byte(output))
if err != nil {
return info, errors.New("写入临时文件失败")
}
err = file.Close()
if err != nil {
return info, errors.New("关闭临时文件失败")
}
fileName := file.Name()
var name, version2, body, date, downloadName, downloadUrl, checksums, checksumsUrl string
if isChina {
if name, err = shell.Execf("jq -r '.name' %s", fileName); err != nil {
return info, errors.New("获取面板版本失败")
}
if version2, err = shell.Execf("jq -r '.tag_name' %s", fileName); err != nil {
return info, errors.New("获取面板版本失败")
}
if body, err = shell.Execf("jq -r '.description' %s", fileName); err != nil {
return info, errors.New("获取面板版本失败")
}
if date, err = shell.Execf("jq -r '.created_at' %s", fileName); err != nil {
return info, errors.New("获取面板版本失败")
}
if checksums, err = shell.Execf("jq -r '.assets.links[] | select(.name | contains(\"checksums\")) | .name' %s", fileName); err != nil {
return info, errors.New("获取面板版本失败")
}
if checksumsUrl, err = shell.Execf("jq -r '.assets.links[] | select(.name | contains(\"checksums\")) | .direct_asset_url' %s", fileName); err != nil {
return info, errors.New("获取面板版本失败")
}
if arch.IsArm() {
if downloadName, err = shell.Execf("jq -r '.assets.links[] | select(.name | contains(\"arm64\")) | .name' %s", fileName); err != nil {
return info, errors.New("获取面板版本失败")
}
if downloadUrl, err = shell.Execf("jq -r '.assets.links[] | select(.name | contains(\"arm64\")) | .direct_asset_url' %s", fileName); err != nil {
return info, errors.New("获取面板版本失败")
}
} else {
if downloadName, err = shell.Execf("jq -r '.assets.links[] | select(.name | contains(\"amd64v2\")) | .name' %s", fileName); err != nil {
return info, errors.New("获取面板版本失败")
}
if downloadUrl, err = shell.Execf("jq -r '.assets.links[] | select(.name | contains(\"amd64v2\")) | .direct_asset_url' %s", fileName); err != nil {
return info, errors.New("获取面板版本失败")
}
}
} else {
if name, err = shell.Execf("jq -r '.name' %s", fileName); err != nil {
return info, errors.New("获取面板版本失败")
}
if version2, err = shell.Execf("jq -r '.tag_name' %s", fileName); err != nil {
return info, errors.New("获取面板版本失败")
}
if body, err = shell.Execf("jq -r '.body' %s", fileName); err != nil {
return info, errors.New("获取面板版本失败")
}
if date, err = shell.Execf("jq -r '.published_at' %s", fileName); err != nil {
return info, errors.New("获取面板版本失败")
}
if checksums, err = shell.Execf("jq -r '.assets[] | select(.name | contains(\"checksums\")) | .name' %s", fileName); err != nil {
return info, errors.New("获取面板版本失败")
}
if checksumsUrl, err = shell.Execf("jq -r '.assets[] | select(.name | contains(\"checksums\")) | .browser_download_url' %s", fileName); err != nil {
return info, errors.New("获取面板版本失败")
}
if arch.IsArm() {
if downloadName, err = shell.Execf("jq -r '.assets[] | select(.name | contains(\"arm64\")) | .name' %s", fileName); err != nil {
return info, errors.New("获取面板版本失败")
}
if downloadUrl, err = shell.Execf("jq -r '.assets[] | select(.name | contains(\"arm64\")) | .browser_download_url' %s", fileName); err != nil {
return info, errors.New("获取面板版本失败")
}
} else {
if downloadName, err = shell.Execf("jq -r '.assets[] | select(.name | contains(\"amd64v2\")) | .name' %s", fileName); err != nil {
return info, errors.New("获取面板版本失败")
}
if downloadUrl, err = shell.Execf("jq -r '.assets[] | select(.name | contains(\"amd64v2\")) | .browser_download_url' %s", fileName); err != nil {
return info, errors.New("获取面板版本失败")
}
}
}
info.Name = name
info.Version = version2
info.Body = body
info.Date = date
info.DownloadName = downloadName
info.DownloadUrl = downloadUrl
info.Checksums = checksums
info.ChecksumsUrl = checksumsUrl
return info, nil
}
// UpdatePanel 更新面板
func UpdatePanel(panelInfo PanelInfo) error {
color.Greenln("目标版本: %s", panelInfo.Version)
color.Greenln("下载链接: %s", panelInfo.DownloadUrl)
color.Greenln("前置检查...")
if io.Exists("/tmp/panel-storage.zip") || io.Exists("/tmp/panel.conf.bak") {
return errors.New("检测到 /tmp 存在临时文件,可能是上次更新失败导致的,请谨慎排除后重试")
}
color.Greenln("备份面板数据...")
// 备份面板
if err := io.Compress([]string{"/www/panel"}, fmt.Sprintf("/www/backup/panel/panel-%s.zip", time.Now().Format("20060102150405")), io.Zip); err != nil {
color.Redln("备份面板失败")
return err
}
if _, err := shell.Execf("cd /www/panel/storage && zip -r /tmp/panel-storage.zip *"); err != nil {
color.Redln("备份面板数据失败")
return err
}
if _, err := shell.Execf("cp -f /www/panel/panel.conf /tmp/panel.conf.bak"); err != nil {
color.Redln("备份面板配置失败")
return err
}
if !io.Exists("/tmp/panel-storage.zip") || !io.Exists("/tmp/panel.conf.bak") {
return errors.New("备份面板数据失败")
}
color.Greenln("备份完成")
color.Greenln("清理旧版本...")
if _, err := shell.Execf("rm -rf /www/panel/*"); err != nil {
color.Redln("清理旧版本失败")
return err
}
color.Greenln("清理完成")
color.Greenln("正在下载...")
if _, err := shell.Execf("wget -T 120 -t 3 -O /www/panel/%s %s", panelInfo.DownloadName, panelInfo.DownloadUrl); err != nil {
color.Redln("下载失败")
return err
}
if _, err := shell.Execf("wget -T 20 -t 3 -O /www/panel/%s %s", panelInfo.Checksums, panelInfo.ChecksumsUrl); err != nil {
color.Redln("下载失败")
return err
}
if !io.Exists("/www/panel/"+panelInfo.DownloadName) || !io.Exists("/www/panel/"+panelInfo.Checksums) {
return errors.New("下载失败")
}
color.Greenln("下载完成")
color.Greenln("校验下载文件...")
check, err := shell.Execf("cd /www/panel && sha256sum -c %s", panelInfo.Checksums+" --ignore-missing")
if check != panelInfo.DownloadName+": OK" || err != nil {
return errors.New("下载文件校验失败")
}
if err = io.Remove("/www/panel/" + panelInfo.Checksums); err != nil {
color.Redln("清理临时文件失败")
return err
}
color.Greenln("文件校验完成")
color.Greenln("更新新版本...")
if _, err = shell.Execf("cd /www/panel && unzip -o %s && rm -rf %s", panelInfo.DownloadName, panelInfo.DownloadName); err != nil {
color.Redln("更新失败")
return err
}
if !io.Exists("/www/panel/panel") {
return errors.New("更新失败,可能是下载过程中出现了问题")
}
color.Greenln("更新完成")
color.Greenln("恢复面板数据...")
if _, err = shell.Execf("cp -f /tmp/panel-storage.zip /www/panel/storage/panel-storage.zip && cd /www/panel/storage && unzip -o panel-storage.zip && rm -rf panel-storage.zip"); err != nil {
color.Redln("恢复面板数据失败")
return err
}
if _, err = shell.Execf("cp -f /tmp/panel.conf.bak /www/panel/panel.conf"); err != nil {
color.Redln("恢复面板配置失败")
return err
}
if _, err = shell.Execf("cp -f /www/panel/scripts/panel.sh /usr/bin/panel"); err != nil {
color.Redln("恢复面板脚本失败")
return err
}
if !io.Exists("/www/panel/storage/panel.db") || !io.Exists("/www/panel/panel.conf") {
return errors.New("恢复面板数据失败")
}
color.Greenln("恢复完成")
color.Greenln("设置面板文件权限...")
_, _ = shell.Execf("chmod -R 700 /www/panel")
_, _ = shell.Execf("chmod -R 700 /usr/bin/panel")
color.Greenln("设置完成")
color.Greenln("运行升级后脚本...")
if _, err = shell.Execf("bash /www/panel/scripts/update_panel.sh"); err != nil {
color.Redln("运行面板升级后脚本失败")
return err
}
if _, err = shell.Execf("cp -f /www/panel/scripts/panel.service /etc/systemd/system/panel.service"); err != nil {
color.Redln("写入面板服务文件失败")
return err
}
_, _ = shell.Execf("systemctl daemon-reload")
if _, err = shell.Execf("panel writeSetting version %s", panelInfo.Version); err != nil {
color.Redln("写入面板版本号失败")
return err
}
color.Greenln("升级完成")
_, _ = shell.Execf("rm -rf /tmp/panel-storage.zip")
_, _ = shell.Execf("rm -rf /tmp/panel.conf.bak")
return nil
}
func RestartPanel() {
color.Greenln("重启面板...")
err := shell.ExecfAsync("sleep 2 && systemctl restart panel")

View File

@@ -18,48 +18,6 @@ func (s *HelperTestSuite) TestGetMonitoringInfo() {
s.NotNil(GetMonitoringInfo())
}
func (s *HelperTestSuite) TestGenerateVersions() {
versions, err := GenerateVersions("1.0.0", "1.0.3")
s.NoError(err)
s.Equal([]string{"1.0.1", "1.0.2", "1.0.3"}, versions)
versions, err = GenerateVersions("v1.0.0", "v1.0.3")
s.NoError(err)
s.Equal([]string{"1.0.1", "1.0.2", "1.0.3"}, versions)
versions, err = GenerateVersions("1.0.0", "1.0.0")
s.NoError(err)
s.Equal([]string(nil), versions)
versions, err = GenerateVersions("1.0.0", "1.1.1")
s.NoError(err)
s.Equal([]string{
"1.0.1", "1.0.2", "1.0.3", "1.0.4", "1.0.5", "1.0.6", "1.0.7", "1.0.8", "1.0.9", "1.0.10",
"1.0.11", "1.0.12", "1.0.13", "1.0.14", "1.0.15", "1.0.16", "1.0.17", "1.0.18", "1.0.19", "1.0.20",
"1.0.21", "1.0.22", "1.0.23", "1.0.24", "1.0.25", "1.0.26", "1.0.27", "1.0.28", "1.0.29", "1.0.30",
"1.0.31", "1.0.32", "1.0.33", "1.0.34", "1.0.35", "1.0.36", "1.0.37", "1.0.38", "1.0.39", "1.0.40",
"1.0.41", "1.0.42", "1.0.43", "1.0.44", "1.0.45", "1.0.46", "1.0.47", "1.0.48", "1.0.49", "1.0.50",
"1.0.51", "1.0.52", "1.0.53", "1.0.54", "1.0.55", "1.0.56", "1.0.57", "1.0.58", "1.0.59", "1.0.60",
"1.0.61", "1.0.62", "1.0.63", "1.0.64", "1.0.65", "1.0.66", "1.0.67", "1.0.68", "1.0.69", "1.0.70",
"1.0.71", "1.0.72", "1.0.73", "1.0.74", "1.0.75", "1.0.76", "1.0.77", "1.0.78", "1.0.79", "1.0.80",
"1.0.81", "1.0.82", "1.0.83", "1.0.84", "1.0.85", "1.0.86", "1.0.87", "1.0.88", "1.0.89", "1.0.90",
"1.0.91", "1.0.92", "1.0.93", "1.0.94", "1.0.95", "1.0.96", "1.0.97", "1.0.98", "1.0.99", "1.1.0",
"1.1.1",
}, versions)
}
/*func (s *HelperTestSuite) TestGetLatestPanelVersion() {
version, err := GetLatestPanelVersion()
s.NotEmpty(version)
s.Nil(err)
}*/
func (s *HelperTestSuite) TestGetPanelVersion() {
version, err := GetPanelVersion("v2.1.29")
s.NotEmpty(version)
s.Nil(err)
}
func (s *HelperTestSuite) TestGetPublicIP() {
ip, err := GetPublicIP()
s.Nil(err)