No description
Find a file
2026-04-08 17:37:46 +02:00
cobra go: fix parent module version 2026-04-08 17:37:46 +02:00
testdata lint: update copyright year to 2026 2026-04-08 17:09:01 +02:00
.golangci.yaml lint: update copyright year to 2026 2026-04-08 17:09:01 +02:00
bi.go lint: update copyright year to 2026 2026-04-08 17:09:01 +02:00
bi_test.go lint: update copyright year to 2026 2026-04-08 17:09:01 +02:00
check.go lint: update copyright year to 2026 2026-04-08 17:09:01 +02:00
download.go new: add cobra sub-command integration for selfupdate 2026-04-08 17:25:26 +02:00
download_test.go lint: update copyright year to 2026 2026-04-08 17:09:01 +02:00
flags.go lint: update copyright year to 2026 2026-04-08 17:09:01 +02:00
GEMINI.md ai: add GEMINI file 2026-04-08 11:54:06 +02:00
go.mod go: update dependencies 2026-04-08 10:25:20 +02:00
go.sum go: update dependencies 2026-04-08 10:25:20 +02:00
handler.go lint: update copyright year to 2026 2026-04-08 17:09:01 +02:00
handler_test.go lint: update copyright year to 2026 2026-04-08 17:09:01 +02:00
init.go lint: update copyright year to 2026 2026-04-08 17:09:01 +02:00
latest.go new: add cobra sub-command integration for selfupdate 2026-04-08 17:25:26 +02:00
latest_test.go lint: update copyright year to 2026 2026-04-08 17:09:01 +02:00
LICENSE.txt first PoC 2025-05-25 14:09:32 +02:00
meta.go lint: update copyright year to 2026 2026-04-08 17:09:01 +02:00
meta_test.go lint: update copyright year to 2026 2026-04-08 17:09:01 +02:00
README.md doc: add verbose go doc comments and README 2026-04-08 17:07:27 +02:00
release.go lint: update copyright year to 2026 2026-04-08 17:09:01 +02:00
release_test.go lint: update copyright year to 2026 2026-04-08 17:09:01 +02:00
repo.go lint: update copyright year to 2026 2026-04-08 17:09:01 +02:00
repo_test.go lint: update copyright year to 2026 2026-04-08 17:09:01 +02:00
selfupdate.go new: add cobra sub-command integration for selfupdate 2026-04-08 17:25:26 +02:00
selfupdate_test.go lint: update copyright year to 2026 2026-04-08 17:09:01 +02:00
server_test.go lint: update copyright year to 2026 2026-04-08 17:09:01 +02:00
version.go lint: update copyright year to 2026 2026-04-08 17:09:01 +02:00

selfupdate

go.schlittermann.de/heiko/selfupdate is a Go library that adds self-update functionality to your application. It automatically discovers releases from Forgejo/Gitea repositories using Go module metadata, matches the correct binary for the current OS and architecture, and replaces the running executable atomically.

Installation

go get go.schlittermann.de/heiko/selfupdate

Quick Start

The simplest way to integrate selfupdate is a single line before flag.Parse():

package main

import (
    "flag"

    "go.schlittermann.de/heiko/selfupdate"
)

func main() {
    // If -update is present on the command line, selfupdate takes
    // over and the program does not continue past this point.
    selfupdate.HandleFlag(flag.CommandLine, "update")
    flag.Parse()

    // ... rest of your application
}

Your users can then run:

# Check if a newer version is available
myapp -update -check

# Download the latest version and replace the running binary
myapp -update -dst exe

# Download to a specific file
myapp -update -dst /usr/local/bin/myapp

# Download into a directory (note the trailing slash)
myapp -update -dst /usr/local/bin/

# Include pre-releases when checking or downloading
myapp -update -check -prerelease
myapp -update -dst exe -prerelease

# Access a private repository using an API token
myapp -update -dst exe -token file:/path/to/tokenfile

The -token flag accepts token specifiers as understood by the go.schlittermann.de/heiko/secret package (e.g. file:, env:, or a literal string).

How It Works

When -update is passed, the library:

  1. Reads debug.BuildInfo from the running binary to determine its Go module path (e.g. go.example.com/org/myapp).
  2. Fetches https://<module-path>?go-get=1 to discover the underlying Git repository URL from the go-import HTML meta tag.
  3. Queries the Forgejo/Gitea release API for the latest release (optionally including pre-releases).
  4. Matches the release asset whose name follows the pattern <basename>-<GOOS>-<GOARCH>[.exe] (e.g. myapp-linux-amd64).
  5. Downloads the asset to a temporary file.
  6. Verifies the downloaded binary's BuildInfo.Main.Path matches the current one.
  7. Copies the file permissions from the running executable.
  8. Atomically replaces the destination via rename.

Asset Naming Convention

Release assets must follow this naming pattern for automatic matching:

<basename>-<os>-<arch>[.exe]
  • <basename> is the last element of the Go module path (e.g. for go.example.com/org/myapp the basename is myapp).
  • <os> is the target GOOS (e.g. linux, darwin, windows).
  • <arch> is the target GOARCH (e.g. amd64, arm64).
  • .exe is appended automatically on Windows.

You can override the expected asset name with the -asset flag.

Custom Configuration

The package-level functions (HandleFlag, End) operate on a Default configuration. For full control, create your own Config:

c := selfupdate.Config{
    Client:      myHTTPClient,
    Logger:      log.Default(),
    HTTPTimeout: 60 * time.Second,
}
c.HandleFlag(flag.CommandLine, "update")

Config Fields

Field Type Description
Client *http.Client HTTP client (embedded). The default preserves auth headers across redirects.
Logger *log.Logger Logger (embedded). Defaults to io.Discard.
HTTPTimeout time.Duration Timeout for individual HTTP requests. Defaults to 30 s.

Programmatic API

Instead of the flag-based workflow you can drive updates from code.

Check for Updates

c := selfupdate.Default
current, latest, err := c.Check(false) // false = stable only
if err != nil {
    log.Fatal(err)
}
fmt.Println("running:", current)
fmt.Println("latest: ", latest)

Check returns two values implementing the Version interface:

type Version interface {
    Version() string  // semver tag, e.g. "v1.2.3"
    Path() string     // executable path (current) or release URL (latest)
    String() string   // human-readable representation
}

The current version is a BuildInfo (read from the running binary), the latest version is a *Release (fetched from the repository).

Download a Specific Release

c := selfupdate.Default

// Check returns the latest release as the second value
_, latest, err := c.Check(false)
if err != nil {
    log.Fatal(err)
}

// Download the asset matching the running binary
err = c.Download(
    latest.(*selfupdate.Release),
    selfupdate.Current.Asset(), // e.g. "myapp-linux-amd64"
    "exe",                       // "exe" = replace running binary
    nil,                         // nil = use default verification
)

The dst argument to Download supports three forms:

Value Behavior
"exe" Replace the currently running executable
"/path/file" Save to an explicit file path
"/path/dir/" Save into a directory (trailing slash required)

BuildInfo

selfupdate.Current is a BuildInfo populated at init time from the running binary. It provides:

selfupdate.Current.Version() // Go module version, e.g. "v1.2.3"
selfupdate.Current.Path()    // Executable path on disk
selfupdate.Current.Asset()   // Expected release asset name

// Read BuildInfo from another binary
bi, err := selfupdate.ReadFile("/usr/local/bin/myapp")

Sentinel Errors

Error Meaning
ErrNotWanted The -update flag was not present on the command line.
ErrNeedUpdate A newer version is available (returned by -check).

License

Apache-2.0