e6956aa001
- React frontend with route-level code splitting - Backend rebranded from Baihu to TaskPool - DB brand migration script and local compatibility
162 lines
3.9 KiB
Go
162 lines
3.9 KiB
Go
package main
|
|
|
|
import (
|
|
"archive/tar"
|
|
"compress/gzip"
|
|
"io"
|
|
"net/http"
|
|
"os"
|
|
"os/exec"
|
|
"path/filepath"
|
|
"runtime"
|
|
"strings"
|
|
"syscall"
|
|
"time"
|
|
)
|
|
|
|
// selfUpdate 自动更新
|
|
func (a *Agent) selfUpdate() {
|
|
// 获取当前可执行文件路径
|
|
exePath, err := os.Executable()
|
|
if err != nil {
|
|
log.Errorf("获取可执行文件路径失败: %v", err)
|
|
return
|
|
}
|
|
exePath, _ = filepath.Abs(exePath)
|
|
|
|
// 下载新版本 tar.gz
|
|
downloadURL := a.config.ServerURL + "/api/agent/download?os=" + runtime.GOOS + "&arch=" + runtime.GOARCH
|
|
req, err := http.NewRequest("GET", downloadURL, nil)
|
|
if err != nil {
|
|
log.Errorf("创建下载请求失败: %v", err)
|
|
return
|
|
}
|
|
req.Header.Set("Authorization", "Bearer "+a.config.Token)
|
|
|
|
client := &http.Client{Timeout: 5 * time.Minute}
|
|
resp, err := client.Do(req)
|
|
if err != nil {
|
|
log.Errorf("下载新版本失败: %v", err)
|
|
return
|
|
}
|
|
defer resp.Body.Close()
|
|
|
|
if resp.StatusCode != http.StatusOK {
|
|
log.Errorf("下载新版本失败: HTTP %d", resp.StatusCode)
|
|
return
|
|
}
|
|
|
|
// 读取 tar.gz 内容
|
|
gzReader, err := gzip.NewReader(resp.Body)
|
|
if err != nil {
|
|
log.Errorf("解压 gzip 失败: %v", err)
|
|
return
|
|
}
|
|
defer gzReader.Close()
|
|
|
|
tarReader := tar.NewReader(gzReader)
|
|
|
|
// 解压并找到二进制文件
|
|
var newBinary []byte
|
|
binaryName := "taskpool-agent"
|
|
if runtime.GOOS == "windows" {
|
|
binaryName = "taskpool-agent.exe"
|
|
}
|
|
|
|
for {
|
|
header, err := tarReader.Next()
|
|
if err == io.EOF {
|
|
break
|
|
}
|
|
if err != nil {
|
|
log.Errorf("读取 tar 失败: %v", err)
|
|
return
|
|
}
|
|
|
|
if header.Typeflag == tar.TypeReg && header.Name == binaryName {
|
|
newBinary, err = io.ReadAll(tarReader)
|
|
if err != nil {
|
|
log.Errorf("读取二进制文件失败: %v", err)
|
|
return
|
|
}
|
|
break
|
|
}
|
|
}
|
|
|
|
if newBinary == nil {
|
|
log.Errorf("tar.gz 中未找到 %s", binaryName)
|
|
return
|
|
}
|
|
|
|
// 保存到临时文件(放到 data 目录)
|
|
os.MkdirAll(dataDir, 0755)
|
|
tmpFile := filepath.Join(dataDir, binaryName+".new")
|
|
if err := os.WriteFile(tmpFile, newBinary, 0755); err != nil {
|
|
log.Errorf("保存新版本失败: %v", err)
|
|
return
|
|
}
|
|
|
|
// 计算基础路径(去掉所有 .bak 后缀)
|
|
basePath := exePath
|
|
for strings.HasSuffix(basePath, ".bak") {
|
|
basePath = strings.TrimSuffix(basePath, ".bak")
|
|
}
|
|
backupFile := basePath + ".bak"
|
|
|
|
// 如果当前运行的就是 .bak 文件,直接删除它(更新后会用新版本)
|
|
// 否则需要备份当前文件
|
|
if exePath != backupFile {
|
|
os.Remove(backupFile)
|
|
if err := os.Rename(exePath, backupFile); err != nil {
|
|
log.Errorf("备份旧版本失败: %v", err)
|
|
os.Remove(tmpFile)
|
|
return
|
|
}
|
|
}
|
|
|
|
// 替换为新版本(放到 basePath,即不带 .bak 的路径)
|
|
if err := os.Rename(tmpFile, basePath); err != nil {
|
|
log.Errorf("替换新版本失败: %v", err)
|
|
if exePath != backupFile {
|
|
os.Rename(backupFile, exePath) // 恢复旧版本
|
|
}
|
|
return
|
|
}
|
|
|
|
// 如果之前运行的是 .bak 文件,现在可以删除它了
|
|
if exePath == backupFile {
|
|
os.Remove(exePath)
|
|
}
|
|
|
|
log.Info("更新完成,正在重启...")
|
|
|
|
// 重启服务
|
|
a.restart()
|
|
}
|
|
|
|
// restart 重启服务
|
|
func (a *Agent) restart() {
|
|
exePath, _ := os.Executable()
|
|
|
|
// 计算基础路径(去掉所有 .bak 后缀),确保启动的是正确的可执行文件
|
|
basePath := exePath
|
|
for strings.HasSuffix(basePath, ".bak") {
|
|
basePath = strings.TrimSuffix(basePath, ".bak")
|
|
}
|
|
|
|
// 删除 PID 文件,避免新进程检测到旧 PID 而拒绝启动
|
|
removePidFile()
|
|
|
|
if runtime.GOOS == "windows" {
|
|
// Windows: 启动新进程后退出
|
|
cmd := exec.Command(basePath, "start")
|
|
cmd.Start()
|
|
os.Exit(0)
|
|
} else {
|
|
// Linux/macOS: 使用 exec 替换当前进程,直接运行(不需要 daemon)
|
|
// 因为 syscall.Exec 会替换当前进程,当前进程本身就是 daemon
|
|
// --restart 标记告诉新进程这是重启,只输出到文件
|
|
syscall.Exec(basePath, []string{basePath, "run", "--restart"}, os.Environ())
|
|
}
|
|
}
|