go-launcher

The Squirrel.Windows pattern for Go. External supervisor with versioned directories, crash-based rollback, and zero-dependency child integration.

Go Reference CI Go Report Card MIT License

The Problem

Every Go auto-update library uses self-surgery — the running binary replaces itself on disk, then restarts. If the new version crashes at startup, recovery logic never executes. If the replacement is interrupted, the binary is corrupted with no rollback path.

The Solution

Discord, Slack, and VS Code solved this years ago: a thin launcher manages versioned directories side-by-side. The old version stays intact until the new one proves stable. go-launcher brings this pattern to Go.

How it works

your-launcher          (thin binary, ~40 lines of your code + go-launcher)
  └── your-app         (your actual application, spawned as child process)

$DATA_DIR/
  versions/
    current/          # active version
    previous/         # rollback target
    staging/          # download in progress

1. Child detects update

Your app checks for new versions (your logic)

2. Request update

Child calls RequestUpdate() and exits

3. Download + verify

Launcher downloads to staging, verifies SHA-256

4. Rotate + launch

current/ → previous/, staging/ → current/

5. Probation

If new version crash-loops, automatic rollback

Features

Crash-based rollback

If the new version crash-loops, the previous version comes back automatically. No manual intervention.

Probation period

New versions must survive a configurable window before being marked stable.

Anti-oscillation

Prevents infinite swapping between two broken versions. Exits cleanly if both fail.

File-based IPC

No sockets, no pipes. Communication via env vars and files. Debuggable, cross-platform, crash-safe.

Zero-dep child

The child package has zero transitive dependencies. Standard library only.

Cross-platform

macOS, Windows, Linux. Platform-appropriate default paths for data and install directories.

Native macOS splash screen with animated spinner, progress bar, and status text

Native splash screen

Optional ui/splash package renders a native window during updates — Cocoa on macOS, Win32 on Windows. Animated spinner, progress bar, and status text keep users informed while the launcher downloads and applies a new version. No Electron. No web views. Zero extra dependencies.

Quick start

$ go get github.com/razvandimescu/go-launcher

Launcher (your thin supervisor binary):

package main

import (
    "context"
    "os"

    "github.com/razvandimescu/go-launcher"
    "github.com/razvandimescu/go-launcher/fetch"
)

func main() {
    l := launcher.New(launcher.Config{
        AppName:         "My App",
        ChildBinaryName: "my-app",
        DataDir:         launcher.DefaultDataDir("MyApp"),
        InstallDir:      launcher.DefaultInstallDir("MyApp"),
        EnvVarName:      "MYAPP_LAUNCHER_STATE_DIR",
        Fetcher:         fetch.GitHubRelease("myorg", "myapp", fetch.AssetPattern("my-app-*")),
    })

    os.Exit(l.Run(context.Background()))
}

Child (your actual application):

package main

import (
    "os"

    "github.com/razvandimescu/go-launcher/child"
)

func init() {
    child.SetEnvVar("MYAPP_LAUNCHER_STATE_DIR")
}

func main() {
    // Signal healthy startup
    if child.IsManaged() {
        child.TouchHeartbeat()
    }

    // ... your application runs ...

    // When you detect a new version:
    if child.IsManaged() {
        child.RequestUpdate("1.2.0", "https://example.com/v1.2.0", "sha256:abc...")
        os.Exit(0) // launcher handles the rest
    }
}

How existing libraries compare

Library Approach Rollback Supervisor Versioned dirs Built-in UI Windows
go-selfupdate Self-surgery Apply-time No No No Yes
minio/selfupdate Self-surgery No No No No Yes
go-github-selfupdate Self-surgery Apply-time No No No Yes
overseer Master/child No Yes No No No
fynelabs/selfupdate Self-surgery Apply-time No No No Yes
go-launcher External supervisor Crash-based Yes Yes Yes Yes

Apply-time rollback restores the old file if the rename fails during the swap. It does not help if the new version starts successfully but crashes 30 seconds later.

Crash-based rollback detects that the new version is crash-looping and automatically reverts to the previous known-good version — even if the new version ran briefly before crashing.