package version import ( "context" "crypto/tls" "encoding/json" "errors" "fmt" "github.com/1Panel-dev/1Panel/agent/app/dto" "github.com/1Panel-dev/1Panel/agent/app/model" "github.com/1Panel-dev/1Panel/agent/constant" "github.com/1Panel-dev/1Panel/agent/global" "github.com/1Panel-dev/1Panel/agent/utils/common" "io" "net" "net/http" "strconv" "strings" "time" ) func GetUpgradeVersionInfo() (*dto.UpgradeInfo, error) { var upgrade dto.UpgradeInfo var currentVersion model.Setting if err := global.CoreDB.Model(&model.Setting{}).Where("key = ?", "SystemVersion").First(¤tVersion).Error; err != nil { global.LOG.Errorf("load %s from db setting failed, err: %v", "SystemVersion", err) return nil, err } var developerMode model.Setting if err := global.CoreDB.Model(&model.Setting{}).Where("key = ?", "DeveloperMode").First(&developerMode).Error; err != nil { global.LOG.Errorf("load %s from db setting failed, err: %v", "DeveloperMode", err) return nil, err } upgrade.TestVersion, upgrade.NewVersion, upgrade.LatestVersion = loadVersionByMode(developerMode.Value, currentVersion.Value) var itemVersion string if len(upgrade.LatestVersion) != 0 { itemVersion = upgrade.LatestVersion } if len(upgrade.NewVersion) != 0 { itemVersion = upgrade.NewVersion } if (global.CONF.Base.Mode == "dev" || developerMode.Value == constant.StatusEnable) && len(upgrade.TestVersion) != 0 { itemVersion = upgrade.TestVersion } if len(itemVersion) == 0 { return &upgrade, nil } mode := global.CONF.Base.Mode if strings.Contains(itemVersion, "beta") { mode = "beta" } notes, err := loadReleaseNotes(fmt.Sprintf("%s/%s/%s/release/1panel-%s-release-notes", global.CONF.RemoteURL.RepoUrl, mode, itemVersion, itemVersion)) if err != nil { return nil, fmt.Errorf("load releases-notes of version %s failed, err: %v", itemVersion, err) } upgrade.ReleaseNote = notes return &upgrade, nil } func loadVersionByMode(developer, currentVersion string) (string, string, string) { var current, latest string if global.CONF.Base.Mode == "dev" { betaVersionLatest := loadVersion(true, currentVersion, "beta") devVersionLatest := loadVersion(true, currentVersion, "dev") if common.ComparePanelVersion(betaVersionLatest, devVersionLatest) { return betaVersionLatest, "", "" } return devVersionLatest, "", "" } betaVersionLatest := "" latest = loadVersion(true, currentVersion, "stable") current = loadVersion(false, currentVersion, "stable") if developer == constant.StatusEnable { betaVersionLatest = loadVersion(true, currentVersion, "beta") } if current != latest { return betaVersionLatest, current, latest } versionPart := strings.Split(current, ".") if len(versionPart) < 3 { return betaVersionLatest, current, latest } num, _ := strconv.Atoi(versionPart[1]) if num == 0 { return betaVersionLatest, current, latest } if num >= 10 { if current[:6] == currentVersion[:6] { return betaVersionLatest, current, "" } return betaVersionLatest, "", latest } if current[:5] == currentVersion[:5] { return betaVersionLatest, current, "" } return betaVersionLatest, "", latest } func loadVersion(isLatest bool, currentVersion, mode string) string { path := fmt.Sprintf("%s/%s/latest", global.CONF.RemoteURL.RepoUrl, mode) if !isLatest { path = fmt.Sprintf("%s/%s/latest.current", global.CONF.RemoteURL.RepoUrl, mode) } _, latestVersionRes, err := HandleRequest(path, http.MethodGet, constant.TimeOut20s) if err != nil { global.LOG.Errorf("load latest version from oss failed, err: %v", err) return "" } version := string(latestVersionRes) if strings.Contains(version, "<") { global.LOG.Errorf("load latest version from oss failed, err: %v", version) return "" } if isLatest { return checkVersion(version, currentVersion) } versionMap := make(map[string]string) if err := json.Unmarshal(latestVersionRes, &versionMap); err != nil { global.LOG.Errorf("load latest version from oss failed (error unmarshal), err: %v", err) return "" } versionPart := strings.Split(currentVersion, ".") if len(versionPart) < 3 { global.LOG.Errorf("current version is error format: %s", currentVersion) return "" } num, _ := strconv.Atoi(versionPart[1]) if num == 0 { global.LOG.Errorf("current version is error format: %s", currentVersion) return "" } if num >= 10 { if version, ok := versionMap[currentVersion[0:5]]; ok { return checkVersion(version, currentVersion) } return "" } if version, ok := versionMap[currentVersion[0:4]]; ok { return checkVersion(version, currentVersion) } return "" } func checkVersion(v2, v1 string) string { addSuffix := false if !strings.Contains(v1, "-") { v1 = v1 + "-lts" } if !strings.Contains(v2, "-") { addSuffix = true v2 = v2 + "-lts" } if common.ComparePanelVersion(v2, v1) { if addSuffix { return strings.TrimSuffix(v2, "-lts") } return v2 } return "" } func loadReleaseNotes(path string) (string, error) { _, releaseNotes, err := HandleRequest(path, http.MethodGet, constant.TimeOut20s) if err != nil { return "", err } return string(releaseNotes), nil } func HandleRequest(url, method string, timeout int) (int, []byte, error) { defer func() { if r := recover(); r != nil { global.LOG.Errorf("handle request failed, error message: %v", r) return } }() transport := loadRequestTransport() client := http.Client{Timeout: time.Duration(timeout) * time.Second, Transport: transport} ctx, cancel := context.WithTimeout(context.Background(), time.Duration(timeout)*time.Second) defer cancel() request, err := http.NewRequestWithContext(ctx, method, url, nil) if err != nil { return 0, nil, err } request.Header.Set("Content-Type", "application/json") resp, err := client.Do(request) if err != nil { return 0, nil, err } if resp.StatusCode != http.StatusOK { return 0, nil, errors.New(resp.Status) } body, err := io.ReadAll(resp.Body) if err != nil { return 0, nil, err } defer resp.Body.Close() return resp.StatusCode, body, nil } func loadRequestTransport() *http.Transport { return &http.Transport{ TLSClientConfig: &tls.Config{InsecureSkipVerify: true}, DialContext: (&net.Dialer{ Timeout: 60 * time.Second, KeepAlive: 60 * time.Second, }).DialContext, TLSHandshakeTimeout: 5 * time.Second, ResponseHeaderTimeout: 10 * time.Second, IdleConnTimeout: 15 * time.Second, } }