All checks were successful
Build Multi-Platform Binaries / build-frontend (push) Successful in 38s
Build Multi-Platform Binaries / build-binaries (amd64, darwin, server, false) (push) Successful in 2m41s
Build Multi-Platform Binaries / build-binaries (amd64, linux, client, true) (push) Successful in 1m15s
Build Multi-Platform Binaries / build-binaries (amd64, linux, server, true) (push) Successful in 3m15s
Build Multi-Platform Binaries / build-binaries (amd64, windows, client, true) (push) Successful in 1m48s
Build Multi-Platform Binaries / build-binaries (amd64, windows, server, true) (push) Successful in 3m35s
Build Multi-Platform Binaries / build-binaries (arm, 7, linux, client, true) (push) Successful in 1m9s
Build Multi-Platform Binaries / build-binaries (arm, 7, linux, server, true) (push) Successful in 3m15s
Build Multi-Platform Binaries / build-binaries (arm64, darwin, server, false) (push) Successful in 2m58s
Build Multi-Platform Binaries / build-binaries (arm64, linux, client, true) (push) Successful in 1m28s
Build Multi-Platform Binaries / build-binaries (arm64, linux, server, true) (push) Successful in 3m43s
Build Multi-Platform Binaries / build-binaries (arm64, windows, server, false) (push) Successful in 2m53s
231 lines
5.7 KiB
Go
231 lines
5.7 KiB
Go
package version
|
||
|
||
import (
|
||
"encoding/json"
|
||
"fmt"
|
||
"io"
|
||
"net/http"
|
||
"runtime"
|
||
"strconv"
|
||
"strings"
|
||
"time"
|
||
)
|
||
|
||
// 版本信息
|
||
const Version = "1.0.0"
|
||
|
||
// 仓库信息
|
||
const (
|
||
RepoURL = "https://git.92coco.cn:8443/flik/GoTunnel"
|
||
APIBaseURL = "https://git.92coco.cn:8443/api/v1"
|
||
RepoOwner = "flik"
|
||
RepoName = "GoTunnel"
|
||
)
|
||
|
||
// Info 版本详细信息
|
||
type Info struct {
|
||
Version string `json:"version"`
|
||
GitCommit string `json:"git_commit"`
|
||
BuildTime string `json:"build_time"`
|
||
GoVersion string `json:"go_version"`
|
||
OS string `json:"os"`
|
||
Arch string `json:"arch"`
|
||
}
|
||
|
||
// GetInfo 获取版本信息
|
||
func GetInfo() Info {
|
||
return Info{
|
||
Version: Version,
|
||
GitCommit: "",
|
||
BuildTime: "",
|
||
GoVersion: runtime.Version(),
|
||
OS: runtime.GOOS,
|
||
Arch: runtime.GOARCH,
|
||
}
|
||
}
|
||
|
||
// ReleaseInfo Release 信息
|
||
type ReleaseInfo struct {
|
||
TagName string `json:"tag_name"`
|
||
Name string `json:"name"`
|
||
Body string `json:"body"`
|
||
PublishedAt string `json:"published_at"`
|
||
Assets []ReleaseAsset `json:"assets"`
|
||
}
|
||
|
||
// ReleaseAsset Release 资产
|
||
type ReleaseAsset struct {
|
||
Name string `json:"name"`
|
||
Size int64 `json:"size"`
|
||
BrowserDownloadURL string `json:"browser_download_url"`
|
||
}
|
||
|
||
// UpdateInfo 更新信息
|
||
type UpdateInfo struct {
|
||
Latest string `json:"latest"`
|
||
ReleaseNote string `json:"release_note"`
|
||
DownloadURL string `json:"download_url"`
|
||
AssetName string `json:"asset_name"`
|
||
AssetSize int64 `json:"asset_size"`
|
||
}
|
||
|
||
// GetLatestRelease 获取最新 Release
|
||
// Gitea 兼容:先尝试 /releases/latest,失败则尝试 /releases 取第一个
|
||
func GetLatestRelease() (*ReleaseInfo, error) {
|
||
client := &http.Client{Timeout: 30 * time.Second}
|
||
|
||
// 首先尝试 /releases/latest 端点(GitHub 兼容)
|
||
latestURL := fmt.Sprintf("%s/repos/%s/%s/releases/latest", APIBaseURL, RepoOwner, RepoName)
|
||
resp, err := client.Get(latestURL)
|
||
if err != nil {
|
||
return nil, fmt.Errorf("request failed: %w", err)
|
||
}
|
||
defer resp.Body.Close()
|
||
|
||
if resp.StatusCode == http.StatusOK {
|
||
var release ReleaseInfo
|
||
if err := json.NewDecoder(resp.Body).Decode(&release); err != nil {
|
||
return nil, fmt.Errorf("decode response: %w", err)
|
||
}
|
||
return &release, nil
|
||
}
|
||
|
||
// 如果 /releases/latest 不可用,尝试 /releases 并取第一个
|
||
resp.Body.Close()
|
||
listURL := fmt.Sprintf("%s/repos/%s/%s/releases?limit=1", APIBaseURL, RepoOwner, RepoName)
|
||
resp, err = client.Get(listURL)
|
||
if err != nil {
|
||
return nil, fmt.Errorf("request failed: %w", err)
|
||
}
|
||
defer resp.Body.Close()
|
||
|
||
if resp.StatusCode != http.StatusOK {
|
||
body, _ := io.ReadAll(resp.Body)
|
||
return nil, fmt.Errorf("API error: %s - %s", resp.Status, string(body))
|
||
}
|
||
|
||
var releases []ReleaseInfo
|
||
if err := json.NewDecoder(resp.Body).Decode(&releases); err != nil {
|
||
return nil, fmt.Errorf("decode response: %w", err)
|
||
}
|
||
|
||
if len(releases) == 0 {
|
||
return nil, fmt.Errorf("no releases found in repository")
|
||
}
|
||
|
||
return &releases[0], nil
|
||
}
|
||
|
||
// CheckUpdate 检查更新(返回最新版本信息)
|
||
func CheckUpdate(component string) (*UpdateInfo, error) {
|
||
release, err := GetLatestRelease()
|
||
if err != nil {
|
||
return nil, fmt.Errorf("get latest release: %w", err)
|
||
}
|
||
|
||
// 查找对应平台的资产
|
||
var downloadURL string
|
||
var assetName string
|
||
var assetSize int64
|
||
|
||
if asset := findAssetForPlatform(release.Assets, component, runtime.GOOS, runtime.GOARCH); asset != nil {
|
||
downloadURL = asset.BrowserDownloadURL
|
||
assetName = asset.Name
|
||
assetSize = asset.Size
|
||
}
|
||
|
||
return &UpdateInfo{
|
||
Latest: release.TagName,
|
||
ReleaseNote: release.Body,
|
||
DownloadURL: downloadURL,
|
||
AssetName: assetName,
|
||
AssetSize: assetSize,
|
||
}, nil
|
||
}
|
||
|
||
// CheckUpdateForPlatform 检查指定平台的更新
|
||
func CheckUpdateForPlatform(component, osName, arch string) (*UpdateInfo, error) {
|
||
release, err := GetLatestRelease()
|
||
if err != nil {
|
||
return nil, fmt.Errorf("get latest release: %w", err)
|
||
}
|
||
|
||
// 查找对应平台的资产
|
||
var downloadURL string
|
||
var assetName string
|
||
var assetSize int64
|
||
|
||
if asset := findAssetForPlatform(release.Assets, component, osName, arch); asset != nil {
|
||
downloadURL = asset.BrowserDownloadURL
|
||
assetName = asset.Name
|
||
assetSize = asset.Size
|
||
}
|
||
|
||
return &UpdateInfo{
|
||
Latest: release.TagName,
|
||
ReleaseNote: release.Body,
|
||
DownloadURL: downloadURL,
|
||
AssetName: assetName,
|
||
AssetSize: assetSize,
|
||
}, nil
|
||
}
|
||
|
||
// findAssetForPlatform 在 Release 资产中查找匹配的文件
|
||
func findAssetForPlatform(assets []ReleaseAsset, component, osName, arch string) *ReleaseAsset {
|
||
// 构建匹配模式
|
||
// CI 格式: gotunnel-server-v1.0.0-linux-amd64.tar.gz
|
||
// 或者: gotunnel-client-v1.0.0-windows-amd64.zip
|
||
prefix := fmt.Sprintf("gotunnel-%s-", component)
|
||
suffix := fmt.Sprintf("-%s-%s", osName, arch)
|
||
|
||
for i := range assets {
|
||
name := assets[i].Name
|
||
// 检查是否匹配 gotunnel-{component}-{version}-{os}-{arch}.{ext}
|
||
if strings.HasPrefix(name, prefix) && strings.Contains(name, suffix) {
|
||
return &assets[i]
|
||
}
|
||
}
|
||
return nil
|
||
}
|
||
|
||
// CompareVersions 比较版本号
|
||
// 返回: -1 (v1 < v2), 0 (v1 == v2), 1 (v1 > v2)
|
||
func CompareVersions(v1, v2 string) int {
|
||
parts1 := parseVersionParts(v1)
|
||
parts2 := parseVersionParts(v2)
|
||
|
||
maxLen := len(parts1)
|
||
if len(parts2) > maxLen {
|
||
maxLen = len(parts2)
|
||
}
|
||
|
||
for i := 0; i < maxLen; i++ {
|
||
var p1, p2 int
|
||
if i < len(parts1) {
|
||
p1 = parts1[i]
|
||
}
|
||
if i < len(parts2) {
|
||
p2 = parts2[i]
|
||
}
|
||
|
||
if p1 < p2 {
|
||
return -1
|
||
}
|
||
if p1 > p2 {
|
||
return 1
|
||
}
|
||
}
|
||
return 0
|
||
}
|
||
|
||
func parseVersionParts(v string) []int {
|
||
v = strings.TrimPrefix(v, "v")
|
||
parts := strings.Split(v, ".")
|
||
result := make([]int, len(parts))
|
||
for i, p := range parts {
|
||
n, _ := strconv.Atoi(p)
|
||
result[i] = n
|
||
}
|
||
return result
|
||
}
|