mirror of
https://github.com/acepanel/panel.git
synced 2026-02-04 07:57:21 +08:00
feat: 初步完成升级方法
This commit is contained in:
@@ -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
|
||||
|
||||
@@ -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
|
||||
}
|
||||
|
||||
|
||||
@@ -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
|
||||
}
|
||||
|
||||
|
||||
@@ -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
|
||||
}
|
||||
|
||||
|
||||
@@ -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
|
||||
}
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -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
|
||||
}
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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"
|
||||
}
|
||||
@@ -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")
|
||||
|
||||
@@ -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)
|
||||
|
||||
Reference in New Issue
Block a user