From 15495d41ea5332cc68258948841db08a36271645 Mon Sep 17 00:00:00 2001 From: root Date: Fri, 1 May 2026 03:58:49 +0300 Subject: [PATCH] auto-update: drop confirmation dialog, do it silently MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Chrome-style silent updater: no prompt, no progress UI, no buttons. On startup we check for updates (8s timeout), and if one is found we download + verify + apply + relaunch — total ~3-5s on a fast connection. The user sees the new version's window instead of the old one, period. Errors that do warrant a dialog (apply failed mid-write, sha256 mismatch, can't relaunch) still surface as a message box so the user knows their copy is on the previous version. 'No update' and network timeouts stay silent. Split timeouts so a 60s download doesn't get killed by the 8s check budget. Co-Authored-By: Claude Opus 4.7 (1M context) --- cmd/drover/autoupdate_windows.go | 69 +++++++++++--------------------- 1 file changed, 23 insertions(+), 46 deletions(-) diff --git a/cmd/drover/autoupdate_windows.go b/cmd/drover/autoupdate_windows.go index 26ce37c..4d76e3c 100644 --- a/cmd/drover/autoupdate_windows.go +++ b/cmd/drover/autoupdate_windows.go @@ -14,32 +14,35 @@ import ( "golang.org/x/sys/windows" ) -// autoUpdateOnStartup runs a non-interactive update check whenever drover.exe -// starts as a GUI app (no CLI subcommand). If an update is available, a -// Yes/No message box prompts the user; on Yes we download, verify, apply -// the update, then re-launch the binary so the new version is what they see. +// autoUpdateOnStartup silently checks for and applies updates whenever +// drover.exe starts as a GUI app (no CLI subcommand). Chrome-style: no +// prompt, no progress bar, no questions — if an update is available we +// download, verify, apply, and re-launch. The user just sees the new +// version's window appear instead of the old one. // -// Network failures, server outages, and "no updates available" are silent -// fall-throughs — startup must never block on them. +// Network failures, server outages, slow downloads, and "no updates +// available" are silent fall-throughs — startup must never block on them. +// +// Two split contexts: +// - check (8s) — quick HEAD-equivalent against the releases API +// - apply (60s) — actual download + sha256 + atomic replace func autoUpdateOnStartup() { - // Tight timeout — startup, not a long-running task. - ctx, cancel := context.WithTimeout(context.Background(), 8*time.Second) - defer cancel() - src := updater.NewForgejoSource("git.okcu.io", "root", "drover-go", "windows-amd64.exe") - rel, hasUpdate, err := updater.CheckForUpdate(ctx, src, Version) + + checkCtx, cancelCheck := context.WithTimeout(context.Background(), 8*time.Second) + rel, hasUpdate, err := updater.CheckForUpdate(checkCtx, src, Version) + cancelCheck() if err != nil || !hasUpdate { - // Silent: offline, slow network, or already up to date — none of - // these should interrupt the user. return } - if !confirmUpdateDialog(rel) { - return - } - - if err := updater.ApplyUpdate(ctx, rel, nil); err != nil { - errorDialog(fmt.Sprintf("Update failed: %v", err)) + applyCtx, cancelApply := context.WithTimeout(context.Background(), 60*time.Second) + defer cancelApply() + if err := updater.ApplyUpdate(applyCtx, rel, nil); err != nil { + // Apply failed — surface this one (sha mismatch, write error, + // disk full are not silent-fail cases). The user can keep using + // the current version after dismissing the dialog. + errorDialog(fmt.Sprintf("Update to %s failed: %v\n\nContinuing on current version.", rel.TagName, err)) return } @@ -48,36 +51,10 @@ func autoUpdateOnStartup() { return } - // Successfully spawned the new version — exit cleanly so it can take over. + // Successfully spawned the new version — exit cleanly so it takes over. os.Exit(0) } -// confirmUpdateDialog asks the user whether to apply the available update. -// Returns true on Yes (IDYES = 6). -func confirmUpdateDialog(rel *updater.Release) bool { - user32 := windows.NewLazySystemDLL("user32.dll") - messageBox := user32.NewProc("MessageBoxW") - - body := fmt.Sprintf( - "A new version is available.\n\n"+ - "Current: v%s\n"+ - "Latest: %s\n\n"+ - "Install it now? Drover-Go will restart automatically.", - Version, rel.TagName, - ) - title := "Drover-Go — Update available" - - bodyW, _ := windows.UTF16PtrFromString(body) - titleW, _ := windows.UTF16PtrFromString(title) - - // MB_YESNO | MB_ICONQUESTION | MB_SETFOREGROUND | MB_TOPMOST | MB_DEFBUTTON1 - const flags = 0x00000004 | 0x00000020 | 0x00010000 | 0x00040000 | 0x00000000 - - r, _, _ := messageBox.Call(0, uintptr(unsafe.Pointer(bodyW)), uintptr(unsafe.Pointer(titleW)), flags) - const IDYES = 6 - return r == IDYES -} - func errorDialog(msg string) { user32 := windows.NewLazySystemDLL("user32.dll") messageBox := user32.NewProc("MessageBoxW")