Documentation
ΒΆ
Overview ΒΆ
Package selfupdate self-updates a Go CLI from its GitHub Releases, and prints the passive "a newer version is available" notice that goes with it.
The flow: ask GitHub for the latest release, compare it against the running version, download the asset for this platform, verify its SHA-256 against the release's checksums file (and, with a verify.Verifier, verify that checksums file's signature), unpack the executable, and atomically swap it in place of the running binary.
A minimal integration is three calls:
up, err := selfupdate.New(selfupdate.Config{Owner: "you", Repo: "mytool"})
if err != nil {
return err
}
selfupdate.CleanupLeftovers() // once, at startup
up.NotifyIfAvailable(os.Stderr, buildinfo.Version) // passive hint
err = up.Run(ctx, os.Stdout, buildinfo.Version, false) // the `update` subcommand
Only Owner and Repo are required; everything else is derived from them. If the binary is not named after the repository, set Config.AppName β every other default follows it. See Config for the full list, and the layout package for matching a release workflow that names its assets differently.
What it refuses to do ΒΆ
The defaults are chosen so that a hostile release, a hostile network, or a tampered cache cannot turn an update check into an install of something the project did not publish:
- Downloads are https-only to GitHub-controlled hosts, redirects included β a checksum only vouches for a download if the whole chain is authenticated.
- Release tags are validated at ingress, before they reach a terminal, a file name, or the on-disk cache.
- Size caps are errors, never truncation: a truncated binary bricks an install as thoroughly as a malicious one.
- Only a strictly newer version installs, so an attacker who controls the network can stall a user on their current version but cannot serve them an older, known-vulnerable release as an "update".
- Dev and source builds are never updated over.
- Error messages never echo server-supplied URLs or HTTP status text.
Checksums alone do not protect against someone who can edit release assets; see the verify package for closing that gap with a signing key.
Index ΒΆ
- Constants
- func CleanupLeftovers()
- type Asset
- type Config
- type Release
- type Result
- type Updater
- func (u *Updater) DisableEnvName() string
- func (u *Updater) Disabled(current string) bool
- func (u *Updater) LatestRelease(ctx context.Context) (*Release, error)
- func (u *Updater) NotifyIfAvailable(w io.Writer, current string)
- func (u *Updater) Refresh(current string) string
- func (u *Updater) Run(ctx context.Context, w io.Writer, current string, checkOnly bool) error
- func (u *Updater) SelfUpdate(ctx context.Context, current string, checkOnly bool) (*Result, error)
Constants ΒΆ
const ( // DefaultMaxAsset caps how many bytes are accepted from a release asset β the // download and, for archive layouts, the binary inside it β so a hostile or // corrupt response can't exhaust memory. Release binaries are a few MB. DefaultMaxAsset = int64(64 << 20) // 64 MiB // DefaultMaxChecksums is far tighter: a genuine checksums file is a few // hundred bytes, so there is no reason to buffer megabytes of "checksums" a // hostile release asset serves up. DefaultMaxChecksums = int64(1 << 20) // 1 MiB // DefaultCheckInterval is how often the passive notice refreshes its cached // "latest version", so normal commands never repeatedly hit the network. DefaultCheckInterval = 24 * time.Hour // DefaultNoticeTimeout bounds the background refresh, so a slow or // unreachable GitHub can't delay the user's command by more than this. DefaultNoticeTimeout = 1500 * time.Millisecond // DefaultUpdateTimeout bounds the whole check-download-verify-install cycle. // Generous compared to the notice, since the user explicitly asked to update // and is waiting on the result rather than being interrupted by it. DefaultUpdateTimeout = 60 * time.Second )
Default sizes and timings. All are overridable through Config.
Variables ΒΆ
This section is empty.
Functions ΒΆ
func CleanupLeftovers ΒΆ
func CleanupLeftovers()
CleanupLeftovers removes the "<exe>.old" file left behind by a prior Windows self-update. Call it once at startup; it is best effort and never errors, so calling it is always safe.
Types ΒΆ
type Config ΒΆ
type Config struct {
// Owner is the GitHub user or organisation that owns the releases. Required.
Owner string
// Repo is the repository name β where the releases live. Required.
//
// It seeds AppName, and through it every other default, so a project whose
// binary and repository share a name needs nothing else.
Repo string
// AppName is how the tool calls itself: the binary name, the name in
// user-facing text, and the basis for every other derived default. Defaults
// to Repo.
//
// Set it whenever the binary is not named after the repository β for a repo
// "compose-check-updates" shipping a binary "ccu", AppName: "ccu" is what
// makes the asset names, checksums file, opt-out variable, state directory
// and user agent all say "ccu" instead of the repository name.
AppName string
// UpdateCmd is the command shown in the "run X to upgrade" hint. Defaults to
// "<AppName> update".
UpdateCmd string
// DisableEnv is the environment variable that switches update checking off
// when set to anything non-empty. Defaults to "<APPNAME>_NO_UPDATE_CHECK",
// with non-alphanumerics replaced by underscores.
DisableEnv string
// Layout describes the release asset naming and packaging. Defaults to
// &layout.Archive{}.
Layout layout.Layout
// ChecksumsName names the checksums asset for a version. Defaults to
// "<AppName>_<version>_checksums.txt".
ChecksumsName func(version string) string
// Verifier authenticates the checksums file. Nil means checksums-only β see
// the verify package for what that does and does not protect against.
Verifier verify.Verifier
// HTTP is the client used for all requests. Nil gets one with a 30s timeout
// and an https-only redirect policy. A supplied client without a CheckRedirect
// has that policy installed on it.
HTTP *http.Client
// APIBase is the GitHub API root. Defaults to https://api.github.com; tests
// point it at a local server.
APIBase string
// UserAgent identifies this client to GitHub, which rejects requests without
// one outright. Defaults to "<AppName>-updater".
UserAgent string
// StatePath returns where the update-check cache lives. Defaults to
// "<os.UserConfigDir>/<AppName>/update-check.json" β %AppData% on Windows,
// ~/.config elsewhere.
StatePath func() (string, error)
// ExecutablePath returns the binary an update replaces. Defaults to the
// running executable with symlinks resolved, which is what you want in
// production.
//
// Override it in tests to point the install at a throwaway file, so a test
// that exercises the real install path does not overwrite the test binary.
ExecutablePath func() (string, error)
// MaxAsset, MaxChecksums, CheckInterval, NoticeTimeout and UpdateTimeout
// override the Default* constants when non-zero.
MaxAsset int64
MaxChecksums int64
CheckInterval time.Duration
NoticeTimeout time.Duration
UpdateTimeout time.Duration
}
Config describes one project's release setup.
Owner and Repo are required. Every other field has a working default derived from Repo, so the common case is a two-field literal.
type Result ΒΆ
type Result struct {
Current string // version before the attempt
Latest string // latest available version
Updated bool // whether the binary was replaced
ExePath string // the binary that was (or would be) replaced
}
Result reports the outcome of an update attempt.
type Updater ΒΆ
type Updater struct {
// contains filtered or unexported fields
}
Updater is a configured self-updater. Create one with New and keep it for the process's lifetime; it holds no per-call state and is safe for concurrent use.
func New ΒΆ
New returns an Updater for cfg, filling in every default. It fails only when Owner or Repo is missing β the two things it cannot invent.
func (*Updater) DisableEnvName ΒΆ
DisableEnvName returns the environment variable that switches update checking off for this Updater β the configured DisableEnv, or the name derived from Repo.
Useful for a help text or a `--version` footer that tells users how to silence the notice, without restating the derivation rule and letting it drift.
func (*Updater) Disabled ΒΆ
Disabled reports whether update checking is off for this run: the opt-out environment variable is set, or this is a source build with no release to compare against.
func (*Updater) LatestRelease ΒΆ
LatestRelease fetches the latest published release, for callers that want the tag or asset list rather than an update.
func (*Updater) NotifyIfAvailable ΒΆ
NotifyIfAvailable prints a one-line "newer version available" hint to w when the cached latest version is newer than current, then refreshes a stale cache. It never blocks longer than Config.NoticeTimeout.
Write to stderr, not stdout, so the tool's actual output stays pipeable.
The notice reflects the *previously* cached check; the refresh here is for the next run. That is what keeps it cheap on the hot path β the first run after a release ships stays silent, and the one after it tells the user.
func (*Updater) Refresh ΒΆ
Refresh returns the latest version when it is newer than current, and "" otherwise. It refreshes a stale cache first, so unlike Updater.NotifyIfAvailable it reports what is true now β which a TUI can afford, calling it off the UI thread. It blocks for at most Config.NoticeTimeout.
func (*Updater) Run ΒΆ
Run performs (or checks for) a self-update, writing human-readable progress to w. It is the whole body of a `mytool update` subcommand.
ctx bounds the whole check-download-verify-install cycle, so a caller that wires up signal handling can let Ctrl-C interrupt an in-flight download. If ctx has no deadline of its own, Config.UpdateTimeout is applied as one; pass context.Background() to just get that default.
Errors travel back unwrapped: the caller owns the exit code, this only decides what the user reads.
func (*Updater) SelfUpdate ΒΆ
SelfUpdate checks for a newer release and, unless checkOnly, downloads, verifies and installs it in place of the running binary. current is the running version, with or without a leading "v".
checkOnly makes exactly one network request β the release lookup β and returns as soon as it knows the latest version. It downloads nothing, writes nothing, and does not touch the notice cache. The Result still reports Latest, so a caller can report an available update without installing it.
The updated binary is NOT re-executed: the calling process keeps running the old code it already loaded, which is both simpler and safer than re-exec'ing mid-command with arguments that may no longer mean the same thing.
A dev or source build is refused outright β it has no release to compare against, and silently overwriting it with a published binary would discard the user's own build.
Directories
ΒΆ
| Path | Synopsis |
|---|---|
|
examples
|
|
|
minimal
command
Command minimal is the smallest useful integration of selfupdate: a passive notice on every run, and an `update` subcommand.
|
Command minimal is the smallest useful integration of selfupdate: a passive notice on every run, and an `update` subcommand. |
|
signed
command
Command signed shows the stronger posture: the release's checksums file must carry a valid Ed25519 signature before anything is installed.
|
Command signed shows the stronger posture: the release's checksums file must carry a valid Ed25519 signature before anything is installed. |
|
testing
Package testing shows how to test a selfupdate integration end to end, offline.
|
Package testing shows how to test a selfupdate integration end to end, offline. |
|
internal
|
|
|
checksum
Package checksum verifies a downloaded asset against a release's sha256sum -format checksums file.
|
Package checksum verifies a downloaded asset against a release's sha256sum -format checksums file. |
|
ghrelease
Package ghrelease is the GitHub Releases client the updater downloads through.
|
Package ghrelease is the GitHub Releases client the updater downloads through. |
|
install
Package install swaps the running executable for a new one.
|
Package install swaps the running executable for a new one. |
|
statecache
Package statecache stores the result of the last update check, so a passive notice costs nothing on the hot path.
|
Package statecache stores the result of the last update check, so a passive notice costs nothing on the hot path. |
|
Package layout describes how a project publishes its release binaries: what the asset for a given platform is called, and how to get an executable out of it.
|
Package layout describes how a project publishes its release binaries: what the asset for a given platform is called, and how to get an executable out of it. |
|
Package verify authenticates a release's checksums file, closing the gap that TLS and checksums leave open.
|
Package verify authenticates a release's checksums file, closing the gap that TLS and checksums leave open. |
|
Package version implements the version ordering the updater relies on: semver precedence, an ingress guard for version strings arriving off the network, and the "is this a source build?" question.
|
Package version implements the version ordering the updater relies on: semver precedence, an ingress guard for version strings arriving off the network, and the "is this a source build?" question. |