完善自我更新逻辑
This commit is contained in:
+108
-42
@@ -1,83 +1,149 @@
|
||||
package main
|
||||
|
||||
import (
|
||||
"crypto/sha256"
|
||||
"flag"
|
||||
"fmt"
|
||||
"io"
|
||||
"net/http"
|
||||
"net/url"
|
||||
"os"
|
||||
"os/exec"
|
||||
"path/filepath"
|
||||
"sentinel/pkg/log"
|
||||
"sentinel/pkg/utils"
|
||||
|
||||
"runtime"
|
||||
"sentinel/pkg/device"
|
||||
"sentinel/pkg/log"
|
||||
"sentinel/pkg/net"
|
||||
"strconv"
|
||||
"syscall"
|
||||
"time"
|
||||
)
|
||||
|
||||
func main() {
|
||||
deviceID := device.GetDeviceID()
|
||||
fmt.Printf("[updater] device id: %s\n", deviceID)
|
||||
// 定义命令行参数
|
||||
version := flag.String("version", "", "current version of main program")
|
||||
|
||||
exeDir, _ := os.Executable()
|
||||
target := filepath.Join(filepath.Dir(exeDir), "main_program_binary_name") // TODO: 替换
|
||||
flag.Parse()
|
||||
|
||||
if err := RunUpdate(deviceID, target); err != nil {
|
||||
log.Fatalf("[updater] update failed: %v", err)
|
||||
if *version == "" {
|
||||
// updater 视角:-1 表示“未知版本”,一定触发更新检测
|
||||
*version = "0"
|
||||
log.Println("[updater] --version not provided, fallback to -1")
|
||||
fmt.Println("[updater] 主程序版本号:", *version)
|
||||
}
|
||||
fmt.Println("[updater] update finished")
|
||||
}
|
||||
|
||||
// RunUpdate 检查更新、下载、替换主程序并启动新程序
|
||||
func RunUpdate(deviceID string, targetExe string) error {
|
||||
deviceID := device.GetDeviceID()
|
||||
fmt.Printf("[updater] 当前设备id: %s\n", deviceID)
|
||||
versionInt, err := strconv.Atoi(*version)
|
||||
if err != nil {
|
||||
log.Println("[updater] invalid version:", *version)
|
||||
versionInt = 0
|
||||
}
|
||||
if err := RunUpdate(deviceID, versionInt); err != nil {
|
||||
log.Fatalf("[updater] 更新失败: %v", err)
|
||||
}
|
||||
fmt.Println("[updater] 更新程序结束")
|
||||
}
|
||||
func RunUpdate(deviceID string, version int) error {
|
||||
// 1. 检查更新
|
||||
info, err := api.CheckUpdate(deviceID)
|
||||
if err != nil {
|
||||
fmt.Println("[updater] 请求错误,请检查网络")
|
||||
return err
|
||||
}
|
||||
fmt.Println("[updater] 新版本:", info.Version)
|
||||
fmt.Println("[updater] 新内容:", info.Notes)
|
||||
fmt.Println("[updater] 下载地址:", info.DownloadURL)
|
||||
|
||||
// 获取主程序路径
|
||||
selfPath, err := os.Executable()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
// 2. 比对本地版本
|
||||
if info.Version <= utils.APP_VERSION {
|
||||
fmt.Println("[updater] already latest version:", utils.APP_VERSION)
|
||||
return nil
|
||||
}
|
||||
fmt.Println("[updater] updating to version:", info.Version, "notes:", info.Notes)
|
||||
selfDir := filepath.Dir(selfPath)
|
||||
targetExe := filepath.Join(selfDir, "main.exe") // Windows 固定名,可根据实际改
|
||||
|
||||
// 3. 下载新版本到临时目录
|
||||
tmpFile := filepath.Join(os.TempDir(), "new_program_tmp")
|
||||
out, err := os.Create(tmpFile)
|
||||
// 2. 对比版本号,没有新版本则直接启动原程序
|
||||
if info.Version <= version {
|
||||
fmt.Println("[updater] 暂未发现新版本,启动原程序")
|
||||
cmd := exec.Command(targetExe)
|
||||
cmd.Stdout = os.Stdout
|
||||
cmd.Stderr = os.Stderr
|
||||
if runtime.GOOS == "windows" {
|
||||
cmd.SysProcAttr = &syscall.SysProcAttr{
|
||||
CreationFlags: syscall.CREATE_NEW_PROCESS_GROUP,
|
||||
}
|
||||
}
|
||||
if err := cmd.Start(); err != nil {
|
||||
return err
|
||||
}
|
||||
os.Exit(0)
|
||||
}
|
||||
|
||||
// 3. 有新版本则先备份到 ./tmp/old_app/
|
||||
backupDir := filepath.Join(selfDir, "tmp", "old_app")
|
||||
_ = os.MkdirAll(backupDir, 0755)
|
||||
backupFile := filepath.Join(backupDir, "main_"+strconv.Itoa(version)+".bak")
|
||||
if err := os.Rename(targetExe, backupFile); err != nil {
|
||||
fmt.Println("[updater] 备份失败,但继续更新:", err)
|
||||
}
|
||||
|
||||
// 4. 下载新版本到 ./tmp
|
||||
tmpDir := filepath.Join(selfDir, "tmp")
|
||||
_ = os.MkdirAll(tmpDir, 0755)
|
||||
|
||||
u, err := url.Parse(info.DownloadURL)
|
||||
if err != nil {
|
||||
return fmt.Errorf("create temp file failed: %w", err)
|
||||
return err
|
||||
}
|
||||
defer out.Close()
|
||||
base := filepath.Base(u.Path)
|
||||
ext := filepath.Ext(base)
|
||||
|
||||
resp2, err := http.Get(info.DownloadURL)
|
||||
tmpFile, err := os.CreateTemp(tmpDir, "main_*"+ext)
|
||||
if err != nil {
|
||||
return fmt.Errorf("download failed: %w", err)
|
||||
return err
|
||||
}
|
||||
defer resp2.Body.Close()
|
||||
defer tmpFile.Close()
|
||||
|
||||
h := sha256.New()
|
||||
mw := io.MultiWriter(out, h)
|
||||
if _, err := io.Copy(mw, resp2.Body); err != nil {
|
||||
return fmt.Errorf("write temp file failed: %w", err)
|
||||
client := &http.Client{Timeout: 30 * time.Second}
|
||||
resp, err := client.Get(info.DownloadURL)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
defer resp.Body.Close()
|
||||
|
||||
if _, err := io.Copy(tmpFile, resp.Body); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// 4. 替换 targetExe
|
||||
backup := targetExe + ".bak"
|
||||
_ = os.Remove(backup)
|
||||
_ = os.Rename(targetExe, backup) // 备份旧版本
|
||||
if err := os.Rename(tmpFile, targetExe); err != nil {
|
||||
return fmt.Errorf("replace main program failed: %w", err)
|
||||
// 5. 重命名新文件到 ./main.exe
|
||||
tmpFile.Close() // 关闭临时文件才能重命名
|
||||
maxRetry := 20
|
||||
for i := 0; i < maxRetry; i++ {
|
||||
err := os.Rename(tmpFile.Name(), targetExe)
|
||||
if err == nil {
|
||||
break
|
||||
}
|
||||
fmt.Println("[updater] 文件被占用,等待 500ms 再尝试...")
|
||||
time.Sleep(500 * time.Millisecond)
|
||||
if i == maxRetry-1 {
|
||||
return fmt.Errorf("替换失败: %w", err)
|
||||
}
|
||||
}
|
||||
fmt.Println("[updater] replaced main program")
|
||||
|
||||
// 5. 启动新主程序
|
||||
// 6. 启动主程序,同时完全退出自己
|
||||
cmd := exec.Command(targetExe)
|
||||
cmd.Stdout = os.Stdout
|
||||
cmd.Stderr = os.Stderr
|
||||
if runtime.GOOS == "windows" {
|
||||
cmd.SysProcAttr = &syscall.SysProcAttr{
|
||||
CreationFlags: syscall.CREATE_NEW_PROCESS_GROUP,
|
||||
}
|
||||
}
|
||||
if err := cmd.Start(); err != nil {
|
||||
return fmt.Errorf("start new program failed: %w", err)
|
||||
return err
|
||||
}
|
||||
|
||||
fmt.Println("[updater] new program started successfully")
|
||||
fmt.Printf("[updater] 更新完成,新程序已启动 (pid=%d),退出更新程序\n", cmd.Process.Pid)
|
||||
os.Exit(0)
|
||||
return nil
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user