All checks were successful
Build Multi-Platform Binaries / build-frontend (push) Successful in 27s
Build Multi-Platform Binaries / build-binaries (amd64, darwin, server, false) (push) Successful in 1m1s
Build Multi-Platform Binaries / build-binaries (amd64, linux, client, true) (push) Successful in 44s
Build Multi-Platform Binaries / build-binaries (amd64, linux, server, true) (push) Successful in 1m27s
Build Multi-Platform Binaries / build-binaries (amd64, windows, client, true) (push) Successful in 44s
Build Multi-Platform Binaries / build-binaries (amd64, windows, server, true) (push) Successful in 1m26s
Build Multi-Platform Binaries / build-binaries (arm, 7, linux, client, true) (push) Successful in 50s
Build Multi-Platform Binaries / build-binaries (arm, 7, linux, server, true) (push) Successful in 1m43s
Build Multi-Platform Binaries / build-binaries (arm64, darwin, server, false) (push) Successful in 1m2s
Build Multi-Platform Binaries / build-binaries (arm64, linux, client, true) (push) Successful in 44s
Build Multi-Platform Binaries / build-binaries (arm64, linux, server, true) (push) Successful in 1m25s
Build Multi-Platform Binaries / build-binaries (arm64, windows, server, false) (push) Successful in 1m0s
202 lines
5.0 KiB
Go
202 lines
5.0 KiB
Go
package handler
|
||
|
||
import (
|
||
"fmt"
|
||
"os"
|
||
"os/exec"
|
||
"path/filepath"
|
||
"runtime"
|
||
"strings"
|
||
|
||
"github.com/gotunnel/pkg/update"
|
||
"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
|
||
|
||
// 查找对应平台的资产
|
||
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{
|
||
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
|
||
|
||
// 查找对应平台的资产
|
||
var downloadURL string
|
||
var assetName string
|
||
var assetSize int64
|
||
|
||
if asset := findAssetForPlatform(release.Assets, "client", osName, arch); asset != nil {
|
||
downloadURL = asset.BrowserDownloadURL
|
||
assetName = asset.Name
|
||
assetSize = asset.Size
|
||
}
|
||
|
||
return &UpdateInfo{
|
||
Available: true,
|
||
Current: "",
|
||
Latest: latestVersion,
|
||
ReleaseNote: release.Body,
|
||
DownloadURL: downloadURL,
|
||
AssetName: assetName,
|
||
AssetSize: assetSize,
|
||
}, nil
|
||
}
|
||
|
||
// 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 nil
|
||
}
|
||
|
||
// performSelfUpdate 执行自更新
|
||
func performSelfUpdate(downloadURL string, restart bool) error {
|
||
// 使用共享的下载和解压逻辑
|
||
binaryPath, cleanup, err := update.DownloadAndExtract(downloadURL, "server")
|
||
if err != nil {
|
||
return err
|
||
}
|
||
defer cleanup()
|
||
|
||
// 获取当前可执行文件路径
|
||
currentPath, err := os.Executable()
|
||
if err != nil {
|
||
return fmt.Errorf("get executable: %w", err)
|
||
}
|
||
currentPath, _ = filepath.EvalSymlinks(currentPath)
|
||
|
||
// Windows 需要特殊处理(运行中的文件无法直接替换)
|
||
if runtime.GOOS == "windows" {
|
||
return performWindowsUpdate(binaryPath, currentPath, restart)
|
||
}
|
||
|
||
// Linux/Mac: 直接替换
|
||
backupPath := currentPath + ".bak"
|
||
|
||
// 备份当前文件
|
||
if err := os.Rename(currentPath, backupPath); err != nil {
|
||
return fmt.Errorf("backup current: %w", err)
|
||
}
|
||
|
||
// 复制新文件(不能用 rename,可能跨文件系统)
|
||
if err := update.CopyFile(binaryPath, currentPath); err != nil {
|
||
os.Rename(backupPath, currentPath)
|
||
return fmt.Errorf("replace binary: %w", err)
|
||
}
|
||
|
||
// 设置执行权限
|
||
if err := os.Chmod(currentPath, 0755); err != nil {
|
||
os.Rename(backupPath, currentPath)
|
||
return fmt.Errorf("chmod new 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)
|
||
}
|