- Go 100%
| cobra | ||
| testdata | ||
| .golangci.yaml | ||
| bi.go | ||
| bi_test.go | ||
| check.go | ||
| download.go | ||
| download_test.go | ||
| flags.go | ||
| GEMINI.md | ||
| go.mod | ||
| go.sum | ||
| handler.go | ||
| handler_test.go | ||
| init.go | ||
| latest.go | ||
| latest_test.go | ||
| LICENSE.txt | ||
| meta.go | ||
| meta_test.go | ||
| README.md | ||
| release.go | ||
| release_test.go | ||
| repo.go | ||
| repo_test.go | ||
| selfupdate.go | ||
| selfupdate_test.go | ||
| server_test.go | ||
| version.go | ||
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:
- Reads
debug.BuildInfofrom the running binary to determine its Go module path (e.g.go.example.com/org/myapp). - Fetches
https://<module-path>?go-get=1to discover the underlying Git repository URL from thego-importHTML meta tag. - Queries the Forgejo/Gitea release API for the latest release (optionally including pre-releases).
- Matches the release asset whose name follows the pattern
<basename>-<GOOS>-<GOARCH>[.exe](e.g.myapp-linux-amd64). - Downloads the asset to a temporary file.
- Verifies the downloaded binary's
BuildInfo.Main.Pathmatches the current one. - Copies the file permissions from the running executable.
- 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. forgo.example.com/org/myappthe basename ismyapp).<os>is the targetGOOS(e.g.linux,darwin,windows).<arch>is the targetGOARCH(e.g.amd64,arm64)..exeis 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