Skip to content

Self update

Dan Riddell edited this page Sep 13, 2026 · 1 revision

Self-update

Any binary released with letsgo gets verified self-update for the cost of an import. The selfupdate package is the same machinery letsgo update uses, pointed at your project instead.

import "github.com/danielriddell21/letsgo/selfupdate"

update, err := selfupdate.Check(ctx, selfupdate.Options{
	Repo:    "you/tool",
	Current: version,
})
if err != nil || update == nil {
	return err // a nil update means the running binary is current
}
return update.Apply(ctx)

Nothing is written until Apply, so a program can offer an update rather than take one. Check on its own is safe to run at startup.

Why it can be trusted

Self-update is the one place where a program replaces its own code, so it is worth being the place where the checking is strictest.

Every release publishes a manifest recording each archive's digest and the digest of the binary inside it. The updater does not have to trust what it downloads:

  1. The downloaded archive is checked against the manifest.
  2. The binary extracted from it is checked against the manifest again.

Both digests were fixed by the build that published them, and letsgo verify proves independently that the build came from the source. So the chain runs from your source to the bytes now running, with no step asking you to take a server's word for it.

Downloads are bounded. An archive of a Go binary is tens of megabytes; past that limit it is a broken or hostile endpoint, and reading it would be a way to exhaust memory rather than a way to update.

Replacing a linked binary

Apply resolves symlinks before replacing, so updating a binary reached through a link replaces the binary rather than the link. A path that cannot be resolved is used as it is, which makes the update fail on the real problem rather than on this.

ApplyTo takes an explicit path when you want to choose the target yourself.

Links

Clone this wiki locally