Compare commits
2 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| 9d174d8db1 | |||
| 19f851afb0 |
@@ -48,16 +48,13 @@ jobs:
|
||||
key: go-${{ runner.os }}-${{ hashFiles('**/go.sum') }}
|
||||
restore-keys: go-${{ runner.os }}-
|
||||
|
||||
# Cache apt downloads — saves ~50s on the wine + innoextract install.
|
||||
# Bump the cache key (-v2, -v3, ...) when the package list changes.
|
||||
- name: Cache apt packages
|
||||
id: apt-cache
|
||||
uses: actions/cache@v4
|
||||
with:
|
||||
path: |
|
||||
/var/cache/apt/archives
|
||||
/var/lib/apt/lists
|
||||
key: apt-trixie-wine-innoextract-v2
|
||||
# NOTE: actions/cache for the apt archive (~300 MB) is disabled. The
|
||||
# save step works (~28s in v0.1.4) but restore times out on the
|
||||
# next run — Gitea's cache server can't push 300 MB back fast
|
||||
# enough. The Wine + Inno Setup install stays at ~1m20s. The
|
||||
# right fix is a pre-baked Docker image (golang:1.25 + wine +
|
||||
# innoextract + xauth + wine32:i386) pushed to git.okcu.io as
|
||||
# the job's container.image. Tracked as future work.
|
||||
|
||||
- name: Extract version from tag
|
||||
id: version
|
||||
|
||||
@@ -0,0 +1,5 @@
|
||||
//go:build !windows
|
||||
|
||||
package main
|
||||
|
||||
func autoUpdateOnStartup() {}
|
||||
@@ -0,0 +1,106 @@
|
||||
//go:build windows
|
||||
|
||||
package main
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"os"
|
||||
"os/exec"
|
||||
"time"
|
||||
"unsafe"
|
||||
|
||||
"git.okcu.io/root/drover-go/internal/updater"
|
||||
"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.
|
||||
//
|
||||
// Network failures, server outages, and "no updates available" are silent
|
||||
// fall-throughs — startup must never block on them.
|
||||
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)
|
||||
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))
|
||||
return
|
||||
}
|
||||
|
||||
if err := relaunchSelf(); err != nil {
|
||||
errorDialog(fmt.Sprintf("Update applied but re-launch failed: %v\n\nPlease restart drover.exe manually.", err))
|
||||
return
|
||||
}
|
||||
|
||||
// Successfully spawned the new version — exit cleanly so it can take 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")
|
||||
|
||||
bodyW, _ := windows.UTF16PtrFromString(msg)
|
||||
titleW, _ := windows.UTF16PtrFromString("Drover-Go — Error")
|
||||
|
||||
// MB_OK | MB_ICONERROR | MB_TOPMOST
|
||||
const flags = 0x00000000 | 0x00000010 | 0x00040000
|
||||
|
||||
messageBox.Call(0, uintptr(unsafe.Pointer(bodyW)), uintptr(unsafe.Pointer(titleW)), flags)
|
||||
}
|
||||
|
||||
// relaunchSelf starts a fresh copy of the (now-updated) executable in the
|
||||
// background and returns. The caller is expected to os.Exit(0) immediately
|
||||
// after — the OS handles the brief overlap fine, and the new process inherits
|
||||
// nothing from us beyond the working directory and arguments.
|
||||
func relaunchSelf() error {
|
||||
exe, err := os.Executable()
|
||||
if err != nil {
|
||||
return fmt.Errorf("locate self: %w", err)
|
||||
}
|
||||
cmd := exec.Command(exe, os.Args[1:]...)
|
||||
cmd.Stdin, cmd.Stdout, cmd.Stderr = nil, nil, nil
|
||||
return cmd.Start()
|
||||
}
|
||||
+5
-3
@@ -45,10 +45,12 @@ func newRootCmd() *cobra.Command {
|
||||
Version: fmt.Sprintf("%s (commit %s, built %s)", Version, Commit, BuildDate),
|
||||
SilenceUsage: true,
|
||||
SilenceErrors: false,
|
||||
// No subcommand and no flags = end-user double-clicked the exe;
|
||||
// open the smoke-test window instead of dumping CLI help to a
|
||||
// console they didn't ask for.
|
||||
// No subcommand and no flags = end-user double-clicked the exe.
|
||||
// First do a quick update check (silent if no network or already
|
||||
// current); if an update is available we prompt, apply, and
|
||||
// re-launch ourselves. Then show the smoke-test window.
|
||||
RunE: func(cmd *cobra.Command, args []string) error {
|
||||
autoUpdateOnStartup()
|
||||
showTestWindow()
|
||||
return nil
|
||||
},
|
||||
|
||||
Reference in New Issue
Block a user