diff --git a/SPEC.md b/SPEC.md index 2769a40..10a13a4 100644 --- a/SPEC.md +++ b/SPEC.md @@ -263,11 +263,13 @@ executable location as-is and leaves the config untouched. idle the process exits with code 3 so the service recovery restarts it on the new version. Interactive runs only log "restart to apply". `dev` builds and builds without an embedded public key never update. -- **`--force-update`** runs the same check immediately: it downloads, - verifies and stages a newer release, and if the service is running it - restarts it right away (otherwise the new version applies on next - start). On Windows it elevates via UAC only when the stage or restart - needs permissions the caller does not have. +- **`--force-update`** runs the same check immediately, single-shot: one + attempt with a 30 s timeout, then exit — "up to date" (exit 0) or the + error (exit 1), no retries. When a newer release is found it downloads, + verifies and stages it, and if the service is running it restarts it + right away (otherwise the new version applies on next start). On Windows + it elevates via UAC only when the stage or restart needs permissions the + caller does not have. - **Signing setup (one time)**: `openssl genpkey -algorithm ed25519 -out private.pem`; `openssl pkey -in private.pem -pubout -out public.pem`. Private key → repo secret `RELEASE_SIGNING_KEY`; public key → committed into diff --git a/cmd/gpu-turnstile/main.go b/cmd/gpu-turnstile/main.go index 393b660..bb004a2 100644 --- a/cmd/gpu-turnstile/main.go +++ b/cmd/gpu-turnstile/main.go @@ -315,7 +315,9 @@ func forceUpdateCommand(configPath string, elevatedChild bool) int { defer logCloser.Close() u := &update.Updater{Repo: cfg.UpdateRepo, Asset: cfg.UpdateAsset, Version: version, Log: log} - ctx, cancel := context.WithTimeout(context.Background(), 2*time.Minute) + // Single-shot: one attempt, fail fast when the server is unreachable + // instead of hanging in a TCP connect for minutes. + ctx, cancel := context.WithTimeout(context.Background(), 30*time.Second) defer cancel() staged, err := u.Check(ctx, exePath) if err != nil && isPermission(err) && !service.Elevated() {