1
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

This commit is contained in:
2026-01-02 23:53:54 +08:00
parent d63ff7169e
commit 0389437fdb
3 changed files with 80 additions and 308 deletions

View File

@@ -8,6 +8,7 @@ import (
"os/exec"
"path/filepath"
"runtime"
"strings"
"time"
"github.com/gotunnel/pkg/version"
@@ -36,16 +37,14 @@ func checkUpdateForComponent(component string) (*UpdateInfo, error) {
available := version.CompareVersions(currentVersion, latestVersion) < 0
// 查找对应平台的资产
assetName := getAssetNameForPlatform(component, runtime.GOOS, runtime.GOARCH)
var downloadURL string
var assetName string
var assetSize int64
for _, asset := range release.Assets {
if asset.Name == assetName {
downloadURL = asset.BrowserDownloadURL
assetSize = asset.Size
break
}
if asset := findAssetForPlatform(release.Assets, component, runtime.GOOS, runtime.GOARCH); asset != nil {
downloadURL = asset.BrowserDownloadURL
assetName = asset.Name
assetSize = asset.Size
}
return &UpdateInfo{
@@ -76,16 +75,14 @@ func checkClientUpdateForPlatform(osName, arch string) (*UpdateInfo, error) {
latestVersion := release.TagName
// 查找对应平台的资产
assetName := getAssetNameForPlatform("client", osName, arch)
var downloadURL string
var assetName string
var assetSize int64
for _, asset := range release.Assets {
if asset.Name == assetName {
downloadURL = asset.BrowserDownloadURL
assetSize = asset.Size
break
}
if asset := findAssetForPlatform(release.Assets, "client", osName, arch); asset != nil {
downloadURL = asset.BrowserDownloadURL
assetName = asset.Name
assetSize = asset.Size
}
return &UpdateInfo{
@@ -99,13 +96,21 @@ func checkClientUpdateForPlatform(osName, arch string) (*UpdateInfo, error) {
}, nil
}
// getAssetNameForPlatform 获取指定平台的资产名称
func getAssetNameForPlatform(component, osName, arch string) string {
ext := ""
if osName == "windows" {
ext = ".exe"
// findAssetForPlatform 在 Release 资产中查找匹配的文件
// CI 格式: gotunnel-server-v1.0.0-linux-amd64.tar.gz
// 或者: gotunnel-client-v1.0.0-windows-amd64.zip
func findAssetForPlatform(assets []version.ReleaseAsset, component, osName, arch string) *version.ReleaseAsset {
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 fmt.Sprintf("%s_%s_%s%s", component, osName, arch, ext)
return nil
}
// performSelfUpdate 执行自更新

View File

@@ -1,256 +0,0 @@
package router
import (
"fmt"
"io"
"net/http"
"os"
"os/exec"
"path/filepath"
"runtime"
"time"
"github.com/gotunnel/pkg/version"
)
// UpdateInfo 更新信息
type UpdateInfo struct {
Available bool `json:"available"`
Current string `json:"current"`
Latest string `json:"latest"`
ReleaseNote string `json:"release_note"`
DownloadURL string `json:"download_url"`
AssetName string `json:"asset_name"`
AssetSize int64 `json:"asset_size"`
}
// checkUpdateForComponent 检查组件更新
func checkUpdateForComponent(component string) (*UpdateInfo, error) {
release, err := version.GetLatestRelease()
if err != nil {
return nil, fmt.Errorf("get latest release: %w", err)
}
latestVersion := release.TagName
currentVersion := version.Version
available := version.CompareVersions(currentVersion, latestVersion) < 0
// 查找对应平台的资产
assetName := getAssetNameForPlatform(component, runtime.GOOS, runtime.GOARCH)
var downloadURL string
var assetSize int64
for _, asset := range release.Assets {
if asset.Name == assetName {
downloadURL = asset.BrowserDownloadURL
assetSize = asset.Size
break
}
}
return &UpdateInfo{
Available: available,
Current: currentVersion,
Latest: latestVersion,
ReleaseNote: release.Body,
DownloadURL: downloadURL,
AssetName: assetName,
AssetSize: assetSize,
}, nil
}
// checkClientUpdateForPlatform 检查指定平台的客户端更新
func checkClientUpdateForPlatform(osName, arch string) (*UpdateInfo, error) {
if osName == "" {
osName = runtime.GOOS
}
if arch == "" {
arch = runtime.GOARCH
}
release, err := version.GetLatestRelease()
if err != nil {
return nil, fmt.Errorf("get latest release: %w", err)
}
latestVersion := release.TagName
// 查找对应平台的资产
assetName := getAssetNameForPlatform("client", osName, arch)
var downloadURL string
var assetSize int64
for _, asset := range release.Assets {
if asset.Name == assetName {
downloadURL = asset.BrowserDownloadURL
assetSize = asset.Size
break
}
}
return &UpdateInfo{
Available: true, // 客户端版本由服务端判断
Current: "", // 客户端版本需要单独获取
Latest: latestVersion,
ReleaseNote: release.Body,
DownloadURL: downloadURL,
AssetName: assetName,
AssetSize: assetSize,
}, nil
}
// getAssetNameForPlatform 获取指定平台的资产名称
func getAssetNameForPlatform(component, osName, arch string) string {
ext := ""
if osName == "windows" {
ext = ".exe"
}
return fmt.Sprintf("%s_%s_%s%s", component, osName, arch, ext)
}
// performSelfUpdate 执行自更新
func performSelfUpdate(downloadURL string, restart bool) error {
// 下载新版本
tempDir := os.TempDir()
tempFile := filepath.Join(tempDir, "gotunnel_update_"+time.Now().Format("20060102150405"))
if runtime.GOOS == "windows" {
tempFile += ".exe"
}
if err := downloadFile(downloadURL, tempFile); err != nil {
return fmt.Errorf("download update: %w", err)
}
// 设置执行权限
if runtime.GOOS != "windows" {
if err := os.Chmod(tempFile, 0755); err != nil {
os.Remove(tempFile)
return fmt.Errorf("chmod: %w", err)
}
}
// 获取当前可执行文件路径
currentPath, err := os.Executable()
if err != nil {
os.Remove(tempFile)
return fmt.Errorf("get executable: %w", err)
}
currentPath, _ = filepath.EvalSymlinks(currentPath)
// Windows 需要特殊处理(运行中的文件无法直接替换)
if runtime.GOOS == "windows" {
return performWindowsUpdate(tempFile, currentPath, restart)
}
// Linux/Mac: 直接替换
backupPath := currentPath + ".bak"
// 备份当前文件
if err := os.Rename(currentPath, backupPath); err != nil {
os.Remove(tempFile)
return fmt.Errorf("backup current: %w", err)
}
// 移动新文件
if err := os.Rename(tempFile, currentPath); err != nil {
os.Rename(backupPath, currentPath)
return fmt.Errorf("replace binary: %w", err)
}
// 删除备份
os.Remove(backupPath)
if restart {
// 重启进程
restartProcess(currentPath)
}
return nil
}
// performWindowsUpdate Windows 平台更新
func performWindowsUpdate(newFile, currentPath string, restart bool) error {
// 创建批处理脚本来替换文件并重启
batchScript := fmt.Sprintf(`@echo off
ping 127.0.0.1 -n 2 > nul
del "%s"
move "%s" "%s"
`, currentPath, newFile, currentPath)
if restart {
batchScript += fmt.Sprintf(`start "" "%s"
`, currentPath)
}
batchScript += "del \"%~f0\"\n"
batchPath := filepath.Join(os.TempDir(), "gotunnel_update.bat")
if err := os.WriteFile(batchPath, []byte(batchScript), 0755); err != nil {
return fmt.Errorf("write batch: %w", err)
}
cmd := exec.Command("cmd", "/C", "start", "/MIN", batchPath)
if err := cmd.Start(); err != nil {
return fmt.Errorf("start batch: %w", err)
}
// 退出当前进程
os.Exit(0)
return nil
}
// restartProcess 重启进程
func restartProcess(path string) {
cmd := exec.Command(path, os.Args[1:]...)
cmd.Stdout = os.Stdout
cmd.Stderr = os.Stderr
cmd.Start()
os.Exit(0)
}
// downloadFile 下载文件
func downloadFile(url, dest string) error {
client := &http.Client{Timeout: 10 * time.Minute}
resp, err := client.Get(url)
if err != nil {
return err
}
defer resp.Body.Close()
if resp.StatusCode != http.StatusOK {
return fmt.Errorf("download failed: %s", resp.Status)
}
out, err := os.Create(dest)
if err != nil {
return err
}
defer out.Close()
_, err = io.Copy(out, resp.Body)
return err
}
// VersionInfo 版本信息
type VersionInfo 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"`
}
// getVersionInfo 获取版本信息
func getVersionInfo() VersionInfo {
info := version.GetInfo()
return VersionInfo{
Version: info.Version,
GitCommit: info.GitCommit,
BuildTime: info.BuildTime,
GoVersion: info.GoVersion,
OS: info.OS,
Arch: info.Arch,
}
}