package main import ( "flag" "fmt" "io" "net/http" "net/url" "os" "path/filepath" "sentinel/pkg/device" "sentinel/pkg/log" "sentinel/pkg/net" "strconv" "time" ) func main() { // 定义命令行参数 version := flag.String("version", "", "current version of main program") flag.Parse() if *version == "" { // updater 视角:-1 表示“未知版本”,一定触发更新检测 *version = "0" log.Println("[updater] --version not provided, fallback to -1") fmt.Println("[updater] 主程序版本号:", *version) } 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 } selfDir := filepath.Dir(selfPath) starter := newMainProgramStarter() targetExe := filepath.Join(selfDir, starter.GetMainName()) // 2. 对比版本号,没有新版本则直接启动原程序 if info.Version <= version { fmt.Println("[updater] 暂未发现新版本,启动原程序") if err := starter.Start(targetExe); 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 err } base := filepath.Base(u.Path) ext := filepath.Ext(base) tmpFile, err := os.CreateTemp(tmpDir, "main_*"+ext) if err != nil { return err } defer tmpFile.Close() 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 } // 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) } } // 6. 启动主程序,同时完全退出自己 if err := starter.Start(targetExe); err != nil { return err } fmt.Printf("[updater] 更新完成,新程序已启动,退出更新程序") os.Exit(0) return nil }