From a073d6b2720ff136ad4b47d405697e557aebab07 Mon Sep 17 00:00:00 2001 From: qian Date: Fri, 14 Aug 2026 23:02:03 +0800 Subject: [PATCH] =?UTF-8?q?fix(sys):=E4=BF=AE=E5=A4=8D=E6=9C=8D=E5=8A=A1?= =?UTF-8?q?=E5=99=A8=E6=9B=B4=E6=96=B0=E8=B7=A8=E5=88=86=E5=8C=BA=E5=A4=8D?= =?UTF-8?q?=E5=88=B6=E6=96=87=E4=BB=B6=E5=AF=BC=E8=87=B4=E7=9A=84=E9=87=8D?= =?UTF-8?q?=E5=91=BD=E5=90=8D=E5=A4=B1=E8=B4=A5=E9=97=AE=E9=A2=98?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- internal/logic/update/replacer.go | 46 +++++++++++++++++-------------- 1 file changed, 25 insertions(+), 21 deletions(-) diff --git a/internal/logic/update/replacer.go b/internal/logic/update/replacer.go index 023ff93..34449ad 100644 --- a/internal/logic/update/replacer.go +++ b/internal/logic/update/replacer.go @@ -84,9 +84,24 @@ func performUpdate(ctx context.Context, targetVersion, downloadURL, checksumURL return } - // Step 3: Extract binary from tarball + // Step 3: 定位当前二进制路径 + // 新二进制必须提取到与当前二进制相同的目录(同一文件系统), + // 否则后续 os.Rename 会因跨设备(EXDEV)失败,如 /tmp 与部署目录不在同一分区 + currentExe, err := os.Executable() + if err != nil { + manager.setProgressError("获取当前程序路径失败", err.Error()) + _ = os.Remove(dlResult.FilePath) + return + } + // Resolve symlinks + currentExe, err = filepath.EvalSymlinks(currentExe) + if err != nil { + g.Log().Warningf(ctx, "Failed to resolve symlink: %v", err) + } + + // Step 4: Extract binary from tarball(提取到当前二进制同目录) manager.setProgress(PhaseBackingUp, "正在提取二进制文件...", 10) - newBinaryPath, err := extractBinary(ctx, dlResult.FilePath) + newBinaryPath, err := extractBinary(ctx, dlResult.FilePath, currentExe+".new") if err != nil { manager.setProgressError("提取文件失败", err.Error()) g.Log().Errorf(ctx, "Extract failed: %v", err) @@ -95,19 +110,8 @@ func performUpdate(ctx context.Context, targetVersion, downloadURL, checksumURL } _ = os.Remove(dlResult.FilePath) // clean up tarball - // Step 4: Backup current binary + // Step 5: Backup current binary manager.setProgress(PhaseBackingUp, "正在备份当前版本...", 30) - currentExe, err := os.Executable() - if err != nil { - manager.setProgressError("获取当前程序路径失败", err.Error()) - _ = os.Remove(newBinaryPath) - return - } - // Resolve symlinks - currentExe, err = filepath.EvalSymlinks(currentExe) - if err != nil { - g.Log().Warningf(ctx, "Failed to resolve symlink: %v", err) - } backupPath := fmt.Sprintf("%s.backup.%s", currentExe, gtime.Now().Format("YmdHis")) if err := copyFile(currentExe, backupPath); err != nil { @@ -128,7 +132,7 @@ func performUpdate(ctx context.Context, targetVersion, downloadURL, checksumURL g.Log().Warningf(ctx, "Failed to save rollback info: %v", err) } - // Step 5: Replace binary + // Step 6: Replace binary manager.setProgress(PhaseReplacing, "正在替换程序文件...", 60) if err := replaceBinary(ctx, currentExe, newBinaryPath); err != nil { manager.setProgressError("替换程序文件失败", err.Error()) @@ -137,7 +141,7 @@ func performUpdate(ctx context.Context, targetVersion, downloadURL, checksumURL return } - // Step 6: Write pending verification marker + // Step 7: Write pending verification marker pendingData, _ := json.Marshal(struct { Version string `json:"version"` OldBinary string `json:"old_binary"` @@ -147,7 +151,7 @@ func performUpdate(ctx context.Context, targetVersion, downloadURL, checksumURL }) _ = os.WriteFile(filepath.Join(updateDir, pendingVerificationFile), pendingData, 0644) - // Step 7: Restart + // Step 8: Restart manager.setProgress(PhaseRestarting, "正在重启服务...", 90) g.Log().Info(ctx, "Update complete, exiting for restart...") time.Sleep(500 * time.Millisecond) // give status API a moment to respond @@ -155,8 +159,10 @@ func performUpdate(ctx context.Context, targetVersion, downloadURL, checksumURL os.Exit(0) } -// extractBinary extracts the team-api binary from a tar.gz archive -func extractBinary(ctx context.Context, tarballPath string) (string, error) { +// extractBinary extracts the team-api binary from a tar.gz archive to outputPath. +// outputPath 必须与被替换的二进制位于同一文件系统(通常为同目录), +// 否则 replaceBinary 中的 os.Rename 会因跨设备(EXDEV)失败 +func extractBinary(ctx context.Context, tarballPath, outputPath string) (string, error) { f, err := os.Open(tarballPath) if err != nil { return "", err @@ -185,8 +191,6 @@ func extractBinary(ctx context.Context, tarballPath string) (string, error) { // Look for the binary file (not directory) baseName := filepath.Base(hdr.Name) if baseName == "team-api" && hdr.Typeflag == tar.TypeReg { - // Extract to temp file - outputPath := filepath.Join(updateDir, "team-api.new") outf, err := os.OpenFile(outputPath, os.O_CREATE|os.O_WRONLY|os.O_TRUNC, 0755) if err != nil { return "", err