diff --git a/internal/apps/fail2ban/service.go b/internal/apps/fail2ban/service.go index 9613808f..ab79b0b6 100644 --- a/internal/apps/fail2ban/service.go +++ b/internal/apps/fail2ban/service.go @@ -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 diff --git a/internal/apps/php/service.go b/internal/apps/php/service.go index b766f7a9..4db5dbdb 100644 --- a/internal/apps/php/service.go +++ b/internal/apps/php/service.go @@ -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 } diff --git a/internal/apps/rsync/service.go b/internal/apps/rsync/service.go index 51b0ad2a..555d19d3 100644 --- a/internal/apps/rsync/service.go +++ b/internal/apps/rsync/service.go @@ -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 } diff --git a/internal/apps/supervisor/service.go b/internal/apps/supervisor/service.go index b44bd92b..ca7466c5 100644 --- a/internal/apps/supervisor/service.go +++ b/internal/apps/supervisor/service.go @@ -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 } diff --git a/internal/biz/setting.go b/internal/biz/setting.go index 5b50d0f5..9dba3d17 100644 --- a/internal/biz/setting.go +++ b/internal/biz/setting.go @@ -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 } diff --git a/internal/bootstrap/db.go b/internal/bootstrap/db.go index fb98852e..d1b5341c 100644 --- a/internal/bootstrap/db.go +++ b/internal/bootstrap/db.go @@ -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, diff --git a/internal/data/setting.go b/internal/data/setting.go index 5b886867..d63fe52f 100644 --- a/internal/data/setting.go +++ b/internal/data/setting.go @@ -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 +} diff --git a/internal/service/base.go b/internal/service/base.go index 0409ee03..702ed5cb 100644 --- a/internal/service/base.go +++ b/internal/service/base.go @@ -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 diff --git a/internal/service/info.go b/internal/service/info.go index ae651faf..b0b3476b 100644 --- a/internal/service/info.go +++ b/internal/service/info.go @@ -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 diff --git a/pkg/api/version.go b/pkg/api/version.go index 3983211c..67625183 100644 --- a/pkg/api/version.go +++ b/pkg/api/version.go @@ -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 diff --git a/pkg/arch/arch.go b/pkg/arch/arch.go deleted file mode 100644 index 92b98f25..00000000 --- a/pkg/arch/arch.go +++ /dev/null @@ -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" -} diff --git a/pkg/tools/tools.go b/pkg/tools/tools.go index 6764f137..48f9199c 100644 --- a/pkg/tools/tools.go +++ b/pkg/tools/tools.go @@ -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") diff --git a/pkg/tools/tools_test.go b/pkg/tools/tools_test.go index 42bc32c5..6d9fe30c 100644 --- a/pkg/tools/tools_test.go +++ b/pkg/tools/tools_test.go @@ -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)