selfupdate

package
v0.3.4 Latest Latest
Warning

This package is not in the latest version of its module.

Go to latest
Published: Sep 6, 2026 License: Apache-2.0 Imports: 16 Imported by: 0

Documentation

Overview

Package selfupdate implements `brain update`: check GitHub for a newer release, verify it actually runs on this machine, then replace the running binary.

This is the one place in the codebase that makes a network call on its own initiative rather than because a user typed a command that needs one — and even here, only when the user typed `brain update` specifically. It never runs on a schedule, from doctor, or in the background, and the request it makes carries no vault path, no machine identifier, no telemetry: a bare GET with a User-Agent naming this binary and nothing else. See Update's doc comment for the verification chain that runs before anything is replaced.

Index

Constants

This section is empty.

Variables

This section is empty.

Functions

func AssetName

func AssetName(version, goos, goarch string) string

AssetName is what scripts/release.sh names one platform's archive: brain_<version>_<goos>_<goarch>.tar.gz, or .zip on Windows. Go's own GOOS/GOARCH spellings are what the script uses directly — no npm-style x64 translation is needed here.

func ExtractBinary

func ExtractBinary(archive []byte, goos string) ([]byte, error)

ExtractBinary pulls the brain executable out of a release archive. The archive holds it one directory down (brain_<version>_<goos>_<goarch>/brain), so this matches on suffix rather than an exact path.

func IsDevBuild

func IsDevBuild(version string) bool

IsDevBuild reports whether version is the unstamped default a plain `go build` produces. Updating one would silently replace a local build with a release binary — a footgun this refuses outright rather than warns about.

func VerifyChecksum

func VerifyChecksum(sums []byte, name string, data []byte) error

VerifyChecksum checks data against the sha256 recorded for name in a SHA256SUMS file, in the two-column format `shasum -a 256` produces.

Types

type Asset

type Asset struct {
	Name string
	URL  string
	Size int64
}

Asset is one file attached to a GitHub release.

func FindAsset

func FindAsset(assets []Asset, name string) (Asset, bool)

FindAsset returns the asset with the given name.

type Client

type Client struct {
	HTTP    *http.Client
	APIBase string
	Repo    string
	// Agent is the User-Agent sent on every request. It names the binary and
	// nothing else — no hostname, no vault path, no user identifier.
	Agent string
}

Client talks to GitHub. Every field is overridable so tests point this at an httptest.Server instead of the real API — nothing in this package should ever need real network access to be exercised.

func NewClient

func NewClient(version string) *Client

NewClient is what `brain update` uses. version is stamped into the User-Agent so a look at GitHub's own request logs identifies which release is asking, nothing more.

func (*Client) Fetch

func (c *Client) Fetch(url string) ([]byte, error)

Fetch downloads one asset. Same bare-GET discipline as Latest.

func (*Client) Latest

func (c *Client) Latest() (Release, error)

Latest fetches the newest release. The request is a bare GET: no query string beyond the release path, no body, no header beyond User-Agent and Accept.

type Error

type Error struct {
	Step Step
	Err  error
}

Error names the step that failed. Every path out of Update that is not a clean success or "already current" returns one of these — invariant 4: a failure is reported, never swallowed into a success-shaped result.

func (*Error) Error

func (e *Error) Error() string

func (*Error) Unwrap

func (e *Error) Unwrap() error

type InstallKind

type InstallKind int

InstallKind is how this binary got onto the machine, which decides whether `brain update` has anything to replace.

const (
	// Standalone is a binary in an ordinary place — go install, a release
	// tarball extracted by hand, a plain reinstall. Replacing it in place is
	// exactly what the user wants.
	Standalone InstallKind = iota
	// NPX resolves a fresh copy from the registry on every invocation. There
	// is nothing durable here to replace.
	NPX
	// NPMManaged is installed through npm's per-platform package mechanism
	// (global or local). Replacing the binary works, but npm's own version
	// bookkeeping will not reflect it.
	NPMManaged
)

func DetectInstall

func DetectInstall(path string) InstallKind

DetectInstall classifies a resolved executable path. Callers should run it through filepath.EvalSymlinks first.

type Options

type Options struct {
	Client     *Client
	Verify     func(path, wantVersion string) error
	Executable func() (string, error)
}

Options lets tests substitute the network client, the "does this binary actually run" check, and how the running executable is resolved — so the whole flow is exercisable against an httptest.Server and a fake verify function, never a real GitHub API or a real exec. The zero value is what `brain update` uses.

type Release

type Release struct {
	Version string // the tag name, e.g. "v0.3.0"
	Assets  []Asset
}

Release is what the GitHub releases/latest endpoint returns, trimmed to what this package needs.

func CheckOnly

func CheckOnly(currentVersion string, opts Options) (Release, error)

CheckOnly resolves the latest release without downloading or installing anything — `brain update --check`.

type Result

type Result struct {
	From, To string
	Asset    string
	Size     int64
	Checksum string
	Replaced string
}

Result is what a successful Update did, printed verbatim by the CLI. To == From (both set, Asset empty) means the check ran and nothing needed doing.

func Update

func Update(currentVersion string, opts Options) (Result, error)

Update is the whole flow, in the order that matters:

  1. Refuse outright on an unstamped dev build.
  2. Resolve the running executable. If it resolves to an npx cache, refuse with no network call at all — there is nothing to check.
  3. Ask GitHub what the latest release is. If it is what we already are, stop here; nothing downloaded, nothing written.
  4. Confirm the executable's directory is writable, so a permission failure is reported before a byte is downloaded.
  5. Download the archive and SHA256SUMS, and verify the archive's checksum before touching anything else.
  6. Extract the binary, write it into the executable's own directory (so the final rename is same-filesystem), and run it to confirm it reports the version it claims to. Only a binary that passes this is ever installed.
  7. Swap it in.

Every failure names the step it failed at (see Error) rather than a bare "could not update", and nothing is replaced until step 6 has actually run the new binary — a checksum proves transport, not that the bytes work on this machine.

type Step

type Step string

Step names which stage of Update ran or failed. A checksum mismatch and a binary that will not run are different sentences, and a caller should be able to tell a user which one happened rather than a bare "update failed".

const (
	StepCheck    Step = "check"
	StepDownload Step = "download"
	StepChecksum Step = "checksum"
	StepVerify   Step = "verify"
	StepReplace  Step = "replace"
)

Jump to

Keyboard shortcuts

? : This menu
/ : Search site
f or F : Jump to
y or Y : Canonical URL