package handler import ( "bash_go_service/shared/pkg/client" "bash_go_service/shared/pkg/constants" "bash_go_service/shared/pkg/logger" "crypto/md5" "fmt" "io" "os" "os/exec" "path/filepath" "time" "version/api" ) const ( updaterExeName = "updater-main" ) func DoUpdate() { logger.Debug("start check updating") res, err := api.GetVersion() if err != nil { logger.Error("Failed to get version: %v", err) return } logger.Debug("res: %v", res) if !res.NeedUpdate { logger.Info("No update needed.") return } exePath, err := os.Executable() if err != nil { logger.Error("Failed to get executable path: %v", err) return } exeDir := filepath.Dir(exePath) exeBase := filepath.Base(exePath) // 备份旧程序 backupName := fmt.Sprintf("%s.bak.%s", exeBase, time.Now().Format("20060102_150405")) backupPath := filepath.Join(exeDir, backupName) logger.Debug("backupPath: %v", backupPath) err = copyFile(exePath, backupPath) if err != nil { logger.Error("Failed to backup current executable: %v", err) return } logger.Info("Backup created: %s", backupPath) // 下载新程序到临时路径 newExePath := filepath.Join(exeDir, exeBase+".new") cli := client.NewClient() query := map[string]string{ "version": res.Version, } err = cli.Download(constants.DownloadNewApi, query, newExePath) if err != nil { logger.Error("Download failed: %v", err) _ = os.Rename(backupPath, exePath) // rollback return } logger.Info("New executable downloaded to: %s", newExePath) // 🔥 校验 MD5 if res.MD5 == "" { logger.Error("MD5 is empty") return } match, Merr := checkFileMD5(newExePath, res.MD5) if Merr != nil { logger.Error("Failed to check MD5: %v", Merr) _ = os.Rename(backupPath, exePath) return } if !match { logger.Error("MD5 mismatch: expected %s", res.MD5) _ = os.Rename(backupPath, exePath) return } logger.Info("MD5 checksum verified.") // 设置新文件执行权限 err = os.Chmod(newExePath, 0755) if err != nil { logger.Error("Failed to set executable permission: %v", err) _ = os.Remove(newExePath) _ = copyFile(backupPath, exePath) return } logger.Info("Executable permission set for new file.") // 启动 updater 进行替换 updaterPath := filepath.Join(exeDir, updaterExeName) cmd := exec.Command(updaterPath, backupPath, newExePath, exePath, "true") cmd.Stdout = os.Stdout cmd.Stderr = os.Stderr err = cmd.Start() if err != nil { logger.Error("Failed to start updater: %v", err) _ = os.Rename(backupPath, exePath) return } logger.Info("Updater started, exiting current process.") os.Exit(0) } // MD5 校验函数 func checkFileMD5(filePath, expectedMD5 string) (bool, error) { f, err := os.Open(filePath) if err != nil { return false, err } defer f.Close() hash := md5.New() if _, err := io.Copy(hash, f); err != nil { return false, err } actualMD5 := fmt.Sprintf("%x", hash.Sum(nil)) return actualMD5 == expectedMD5, nil } // 复制文件函数 func copyFile(src, dst string) error { sourceFile, err := os.Open(src) if err != nil { return err } defer sourceFile.Close() destFile, err := os.Create(dst) if err != nil { return err } defer destFile.Close() _, err = io.Copy(destFile, sourceFile) return err }