mirror of
https://github.com/Syngnat/GoNavi.git
synced 2026-07-12 16:02:48 +08:00
🐛 fix(update): 修复 dev→latest 安装后丢包且仍停在旧版的问题
- 安装成功且 relaunch 发出后再删除下载包,失败保留 latest 包便于手动安装 - macOS open 使用路径形式,避免 -a 传完整路径导致重启失败 - 明确覆盖「当前运行的 .app」(桌面 dev 包),避免误以为只更新了 Applications - Windows 同样先启动目标再删 SOURCE,防止重启失败时只剩旧包
This commit is contained in:
@@ -1765,16 +1765,25 @@ exit /b 1
|
||||
|
||||
:move_done
|
||||
del /F /Q "%TARGET_OLD%" >> "%LOG_FILE%" 2>&1
|
||||
if exist "%SOURCE%" del /F /Q "%SOURCE%" >> "%LOG_FILE%" 2>&1
|
||||
rem 先重启,成功后再删安装包,避免失败时 latest 包被删、用户只剩旧 dev 可运行
|
||||
call :log launching target: %TARGET%
|
||||
start "" /D "%TARGET_DIR%" "%TARGET%" >> "%LOG_FILE%" 2>&1
|
||||
if %ERRORLEVEL% NEQ 0 (
|
||||
call :log cmd start failed, trying powershell Start-Process
|
||||
powershell -NoProfile -ExecutionPolicy Bypass -Command "Start-Process -FilePath '%TARGET%' -WorkingDirectory '%TARGET_DIR%'" >> "%LOG_FILE%" 2>&1
|
||||
if !ERRORLEVEL! NEQ 0 (
|
||||
call :log relaunch failed
|
||||
call :log relaunch failed - package kept for manual install: %SOURCE%
|
||||
exit /b 1
|
||||
)
|
||||
)
|
||||
rem relaunch 已发出后再清理下载包与 staging
|
||||
if exist "%SOURCE%" (
|
||||
if /I not "%SOURCE%"=="%TARGET%" (
|
||||
if /I not "%SOURCE_EXE%"=="%TARGET%" (
|
||||
del /F /Q "%SOURCE%" >> "%LOG_FILE%" 2>&1
|
||||
)
|
||||
)
|
||||
)
|
||||
rmdir /S /Q "%STAGED%" >> "%LOG_FILE%" 2>&1
|
||||
call :log update finished
|
||||
exit /b 0
|
||||
@@ -1943,15 +1952,25 @@ replace_app_direct() {
|
||||
}
|
||||
|
||||
relaunch_app() {
|
||||
if /usr/bin/open -n -a "$TARGET_APP" >>"$LOG_FILE" 2>&1; then
|
||||
return 0
|
||||
fi
|
||||
# open -a 需要应用名,不能传完整路径;路径必须用 open -n "xxx.app"
|
||||
if /usr/bin/open -n "$TARGET_APP" >>"$LOG_FILE" 2>&1; then
|
||||
log "relaunch via open -n path ok"
|
||||
return 0
|
||||
fi
|
||||
log "open failed, trying binary launch"
|
||||
nohup "$TARGET_APP/$APP_BIN_REL" >>"$LOG_FILE" 2>&1 &
|
||||
return 0
|
||||
local app_name
|
||||
app_name=$(basename "$TARGET_APP" .app)
|
||||
if [ -n "$app_name" ] && /usr/bin/open -n -a "$app_name" >>"$LOG_FILE" 2>&1; then
|
||||
log "relaunch via open -n -a name ok: $app_name"
|
||||
return 0
|
||||
fi
|
||||
log "open failed, trying binary launch: $TARGET_APP/$APP_BIN_REL"
|
||||
if [ -x "$TARGET_APP/$APP_BIN_REL" ]; then
|
||||
nohup "$TARGET_APP/$APP_BIN_REL" >>"$LOG_FILE" 2>&1 &
|
||||
log "relaunch via binary pid=$!"
|
||||
return 0
|
||||
fi
|
||||
log "relaunch failed: no launch method succeeded"
|
||||
return 1
|
||||
}
|
||||
|
||||
log "updater started package=$PACKAGE target=$TARGET_APP pid=$PID"
|
||||
@@ -1980,29 +1999,36 @@ log "install target: $TARGET_APP"
|
||||
if ! replace_app_direct; then
|
||||
log "direct replace failed, trying admin replace"
|
||||
if ! run_admin_replace >>"$LOG_FILE" 2>&1; then
|
||||
log "admin replace failed"
|
||||
log "admin replace failed — package kept at: $PACKAGE"
|
||||
cleanup_mount
|
||||
exit 1
|
||||
fi
|
||||
fi
|
||||
|
||||
if ! resolve_app_binary_rel "$TARGET_APP"; then
|
||||
log "target app binary missing after replace: $TARGET_APP"
|
||||
log "target app binary missing after replace: $TARGET_APP — package kept at: $PACKAGE"
|
||||
cleanup_mount
|
||||
exit 1
|
||||
fi
|
||||
if [ ! -x "$TARGET_APP/$APP_BIN_REL" ]; then
|
||||
log "target app binary not executable: $TARGET_APP/$APP_BIN_REL"
|
||||
log "target app binary not executable: $TARGET_APP/$APP_BIN_REL — package kept at: $PACKAGE"
|
||||
cleanup_mount
|
||||
exit 1
|
||||
fi
|
||||
|
||||
cleanup_mount
|
||||
# 不要删除 STAGED(脚本自身所在目录),只删安装包与临时解压目录
|
||||
# 仅清理临时解压目录;安装包在 relaunch 成功后再删,失败则保留便于手动安装
|
||||
/bin/rm -rf "$EXTRACT_DIR" >>"$LOG_FILE" 2>&1 || true
|
||||
|
||||
if ! relaunch_app; then
|
||||
log "update files replaced but relaunch failed — package kept for manual install: $PACKAGE"
|
||||
log "please open: $TARGET_APP"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
# relaunch 已发出:再删安装包(用户已不需要 dmg/zip)
|
||||
/bin/rm -f "$PACKAGE" >>"$LOG_FILE" 2>&1 || true
|
||||
relaunch_app
|
||||
log "relaunch requested"
|
||||
log "relaunch requested; package cleaned if possible"
|
||||
exit 0
|
||||
`, pid, packagePath, targetApp, stagedDir, mountDir, logPath)
|
||||
}
|
||||
@@ -2055,14 +2081,24 @@ func detectMacAppPath(exePath string) string {
|
||||
func resolveMacUpdateTarget(exePath string) string {
|
||||
targetApp := detectMacAppPath(exePath)
|
||||
if targetApp == "" {
|
||||
logger.Warnf("无法从可执行路径解析 .app,回退 /Applications/GoNavi.app:exe=%s", exePath)
|
||||
return "/Applications/GoNavi.app"
|
||||
}
|
||||
targetApp = filepath.Clean(targetApp)
|
||||
// Gatekeeper App Translocation 路径不可用于稳定覆盖更新,统一回退到 /Applications。
|
||||
// Gatekeeper App Translocation 路径不可用于稳定覆盖更新。
|
||||
// 优先使用 /Applications 中已有的正式安装;否则仍回退到标准路径(避免写进临时隔离目录)。
|
||||
if strings.Contains(targetApp, string(filepath.Separator)+"AppTranslocation"+string(filepath.Separator)) {
|
||||
logger.Warnf("检测到 AppTranslocation 运行路径,更新目标回退至 /Applications/GoNavi.app:%s", targetApp)
|
||||
return "/Applications/GoNavi.app"
|
||||
applicationsTarget := "/Applications/GoNavi.app"
|
||||
if st, err := os.Stat(applicationsTarget); err == nil && st.IsDir() {
|
||||
logger.Warnf("检测到 AppTranslocation,更新目标使用已有 Applications 安装:%s(来自 %s)", applicationsTarget, targetApp)
|
||||
return applicationsTarget
|
||||
}
|
||||
logger.Warnf("检测到 AppTranslocation 且 Applications 无安装,仍将更新到 %s(来自 %s)", applicationsTarget, targetApp)
|
||||
return applicationsTarget
|
||||
}
|
||||
// 正在运行的是桌面/便携 .app(含 dev 包):必须覆盖「当前这份」而不是误写到 /Applications,
|
||||
// 否则会出现:latest 包被删、用户仍打开旧的 Desktop dev 包。
|
||||
logger.Infof("macOS 更新目标使用当前运行的应用包:%s", targetApp)
|
||||
return targetApp
|
||||
}
|
||||
|
||||
|
||||
@@ -23,11 +23,12 @@ func TestBuildMacScriptContainsHardeningGuards(t *testing.T) {
|
||||
"replace_app_direct",
|
||||
"run_admin_replace",
|
||||
"relaunch_app",
|
||||
"open -n -a",
|
||||
`open -n "$TARGET_APP"`,
|
||||
// 安装包扩展名分支
|
||||
"dmg)",
|
||||
"zip)",
|
||||
// 不要在脚本运行中 rm -rf 整个 STAGED(脚本自身所在目录)
|
||||
// relaunch 成功后再删安装包,失败则保留
|
||||
"package kept for manual install",
|
||||
`/bin/rm -f "$PACKAGE"`,
|
||||
}
|
||||
for _, token := range mustContain {
|
||||
@@ -38,6 +39,12 @@ func TestBuildMacScriptContainsHardeningGuards(t *testing.T) {
|
||||
if strings.Contains(script, `rm -rf "$MOUNT_DIR" "$DMG" "$STAGED"`) {
|
||||
t.Fatal("mac update script must not delete STAGED while the script may still be running from it")
|
||||
}
|
||||
// 确保不会在 relaunch 之前无条件删除安装包
|
||||
rmIdx := strings.Index(script, `/bin/rm -f "$PACKAGE"`)
|
||||
relaunchIdx := strings.Index(script, "if ! relaunch_app; then")
|
||||
if rmIdx < 0 || relaunchIdx < 0 || rmIdx < relaunchIdx {
|
||||
t.Fatalf("package cleanup must happen only after relaunch attempt (rmIdx=%d relaunchIdx=%d)", rmIdx, relaunchIdx)
|
||||
}
|
||||
if !strings.Contains(script, "/tmp/GoNavi-1.2.3-MacOS-Arm64.dmg") {
|
||||
t.Fatal("expected package path embedded in script")
|
||||
}
|
||||
|
||||
@@ -741,14 +741,21 @@ func TestBuildWindowsScriptReplacesTargetWithDownloadedExe(t *testing.T) {
|
||||
`:replace_binary`,
|
||||
`move /Y "%TARGET%" "%TARGET_OLD%"`,
|
||||
`copy /Y "%SOURCE_EXE%" "%TARGET%"`,
|
||||
`if exist "%SOURCE%" del /F /Q "%SOURCE%"`,
|
||||
`start "" /D "%TARGET_DIR%" "%TARGET%"`,
|
||||
`package kept for manual install`,
|
||||
`del /F /Q "%SOURCE%"`,
|
||||
}
|
||||
for _, want := range mustContain {
|
||||
if !strings.Contains(script, want) {
|
||||
t.Fatalf("windows update script missing required token: %s\nscript:\n%s", want, script)
|
||||
}
|
||||
}
|
||||
// relaunch 必须在删除安装包之前
|
||||
startIdx := strings.Index(script, `start "" /D "%TARGET_DIR%" "%TARGET%"`)
|
||||
delIdx := strings.LastIndex(script, `del /F /Q "%SOURCE%"`)
|
||||
if startIdx < 0 || delIdx < 0 || delIdx < startIdx {
|
||||
t.Fatalf("source package must be deleted only after relaunch attempt (start=%d del=%d)", startIdx, delIdx)
|
||||
}
|
||||
if strings.Contains(script, "launch_downloaded_exe") {
|
||||
t.Fatalf("windows update script should not launch downloaded exe side-by-side\nscript:\n%s", script)
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user