diff --git a/update.go b/update.go index 37b94b8..12b1c8b 100644 --- a/update.go +++ b/update.go @@ -11,6 +11,7 @@ import ( "path/filepath" "strconv" "strings" + "syscall" "time" wruntime "github.com/wailsapp/wails/v2/pkg/runtime" @@ -159,14 +160,26 @@ func (a *App) DownloadAndApplyUpdate(url string) error { _ = os.Rename(oldExe, exe) // roll back return fmt.Errorf("install new exe: %w", err) } - applog.Printf("update: installed new exe, relaunching") + // Clear the "downloaded from the internet" mark (NTFS Zone.Identifier stream). + // Otherwise Windows SmartScreen wants to prompt "are you sure you want to open + // this?" — but since we launch the exe programmatically that prompt never shows, + // and the launch is silently blocked. This is exactly why the relaunch failed. + _ = os.Remove(exe + ":Zone.Identifier") + applog.Printf("update: installed new exe, scheduling relaunch") - // Relaunch with a flag so the fresh instance waits for THIS one to exit and - // free the single-instance mutex instead of bailing out immediately. - cmd := exec.Command(exe, "--post-update") - cmd.Dir = dir + // Relaunch via a detached, hidden PowerShell that WAITS for this process to exit + // (so the single-instance mutex is free) and THEN starts the new exe. Launching + // the new exe directly while we're still alive raced the mutex and often left + // nothing running; waiting for our own exit first makes the restart reliable, + // and the launcher outlives us. + quoted := strings.ReplaceAll(exe, "'", "''") + ps := fmt.Sprintf( + "Wait-Process -Id %d -ErrorAction SilentlyContinue; Start-Sleep -Milliseconds 400; Start-Process -FilePath '%s' -ArgumentList '--post-update'", + os.Getpid(), quoted) + cmd := exec.Command("powershell", "-NoProfile", "-WindowStyle", "Hidden", "-Command", ps) + cmd.SysProcAttr = &syscall.SysProcAttr{HideWindow: true, CreationFlags: 0x08000000} // CREATE_NO_WINDOW if err := cmd.Start(); err != nil { - return fmt.Errorf("relaunch: %w", err) + return fmt.Errorf("schedule relaunch: %w", err) } if a.ctx != nil { wruntime.Quit(a.ctx)