Compare commits
4 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| 15495d41ea | |||
| 9d174d8db1 | |||
| 19f851afb0 | |||
| 9bdbcd4d88 |
@@ -25,6 +25,11 @@ jobs:
|
|||||||
test:
|
test:
|
||||||
runs-on: go
|
runs-on: go
|
||||||
steps:
|
steps:
|
||||||
|
- name: Disable apt auto-clean (preserve cache for actions/cache)
|
||||||
|
run: |
|
||||||
|
rm -f /etc/apt/apt.conf.d/docker-clean
|
||||||
|
echo 'Binary::apt::APT::Keep-Downloaded-Packages "true";' > /etc/apt/apt.conf.d/keep-cache
|
||||||
|
|
||||||
- name: Install Node (required by actions/cache)
|
- name: Install Node (required by actions/cache)
|
||||||
run: |
|
run: |
|
||||||
apt-get update >/dev/null
|
apt-get update >/dev/null
|
||||||
@@ -61,6 +66,11 @@ jobs:
|
|||||||
runs-on: go
|
runs-on: go
|
||||||
needs: test
|
needs: test
|
||||||
steps:
|
steps:
|
||||||
|
- name: Disable apt auto-clean (preserve cache for actions/cache)
|
||||||
|
run: |
|
||||||
|
rm -f /etc/apt/apt.conf.d/docker-clean
|
||||||
|
echo 'Binary::apt::APT::Keep-Downloaded-Packages "true";' > /etc/apt/apt.conf.d/keep-cache
|
||||||
|
|
||||||
- name: Install Node (required by actions/cache)
|
- name: Install Node (required by actions/cache)
|
||||||
run: |
|
run: |
|
||||||
apt-get update >/dev/null
|
apt-get update >/dev/null
|
||||||
|
|||||||
@@ -16,6 +16,15 @@ jobs:
|
|||||||
release:
|
release:
|
||||||
runs-on: go
|
runs-on: go
|
||||||
steps:
|
steps:
|
||||||
|
# Debian-based Docker images ship /etc/apt/apt.conf.d/docker-clean
|
||||||
|
# which deletes /var/cache/apt/archives after every apt-get install.
|
||||||
|
# That defeats actions/cache for the apt cache; disable it before
|
||||||
|
# any apt operations.
|
||||||
|
- name: Disable apt auto-clean (preserve cache for actions/cache)
|
||||||
|
run: |
|
||||||
|
rm -f /etc/apt/apt.conf.d/docker-clean
|
||||||
|
echo 'Binary::apt::APT::Keep-Downloaded-Packages "true";' > /etc/apt/apt.conf.d/keep-cache
|
||||||
|
|
||||||
- name: Install Node (required by actions/cache)
|
- name: Install Node (required by actions/cache)
|
||||||
run: |
|
run: |
|
||||||
apt-get update >/dev/null
|
apt-get update >/dev/null
|
||||||
@@ -39,15 +48,13 @@ jobs:
|
|||||||
key: go-${{ runner.os }}-${{ hashFiles('**/go.sum') }}
|
key: go-${{ runner.os }}-${{ hashFiles('**/go.sum') }}
|
||||||
restore-keys: go-${{ runner.os }}-
|
restore-keys: go-${{ runner.os }}-
|
||||||
|
|
||||||
# Cache apt downloads — saves ~50s on the wine + innoextract install.
|
# NOTE: actions/cache for the apt archive (~300 MB) is disabled. The
|
||||||
# Bump the cache key (-v2, -v3, ...) when the package list changes.
|
# save step works (~28s in v0.1.4) but restore times out on the
|
||||||
- name: Cache apt packages
|
# next run — Gitea's cache server can't push 300 MB back fast
|
||||||
uses: actions/cache@v4
|
# enough. The Wine + Inno Setup install stays at ~1m20s. The
|
||||||
with:
|
# right fix is a pre-baked Docker image (golang:1.25 + wine +
|
||||||
path: |
|
# innoextract + xauth + wine32:i386) pushed to git.okcu.io as
|
||||||
/var/cache/apt/archives
|
# the job's container.image. Tracked as future work.
|
||||||
/var/lib/apt/lists
|
|
||||||
key: apt-trixie-wine-innoextract-v1
|
|
||||||
|
|
||||||
- name: Extract version from tag
|
- name: Extract version from tag
|
||||||
id: version
|
id: version
|
||||||
|
|||||||
@@ -0,0 +1,5 @@
|
|||||||
|
//go:build !windows
|
||||||
|
|
||||||
|
package main
|
||||||
|
|
||||||
|
func autoUpdateOnStartup() {}
|
||||||
@@ -0,0 +1,83 @@
|
|||||||
|
//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 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, 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() {
|
||||||
|
src := updater.NewForgejoSource("git.okcu.io", "root", "drover-go", "windows-amd64.exe")
|
||||||
|
|
||||||
|
checkCtx, cancelCheck := context.WithTimeout(context.Background(), 8*time.Second)
|
||||||
|
rel, hasUpdate, err := updater.CheckForUpdate(checkCtx, src, Version)
|
||||||
|
cancelCheck()
|
||||||
|
if err != nil || !hasUpdate {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
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
|
||||||
|
}
|
||||||
|
|
||||||
|
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 takes over.
|
||||||
|
os.Exit(0)
|
||||||
|
}
|
||||||
|
|
||||||
|
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),
|
Version: fmt.Sprintf("%s (commit %s, built %s)", Version, Commit, BuildDate),
|
||||||
SilenceUsage: true,
|
SilenceUsage: true,
|
||||||
SilenceErrors: false,
|
SilenceErrors: false,
|
||||||
// No subcommand and no flags = end-user double-clicked the exe;
|
// No subcommand and no flags = end-user double-clicked the exe.
|
||||||
// open the smoke-test window instead of dumping CLI help to a
|
// First do a quick update check (silent if no network or already
|
||||||
// console they didn't ask for.
|
// 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 {
|
RunE: func(cmd *cobra.Command, args []string) error {
|
||||||
|
autoUpdateOnStartup()
|
||||||
showTestWindow()
|
showTestWindow()
|
||||||
return nil
|
return nil
|
||||||
},
|
},
|
||||||
|
|||||||
Reference in New Issue
Block a user