selfupdate

package
v0.4.2 Latest Latest
Warning

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

Go to latest
Published: Sep 3, 2026 License: GPL-3.0 Imports: 25 Imported by: 0

Documentation

Overview

Package selfupdate downloads, verifies, and installs magus release binaries.

Discovery reads ONLY the site's index.json (gen/public/release/index.json), whose Ed25519 signature (index.json.sig) is verified against the pinned release key before any data is trusted. The GitHub API is not used. This eliminates the unauthenticated 60 req/hr rate limit that blocked CI.

The discovery URL is overridable via Options.DiscoveryURL or the MAGUS_UPDATE_URL environment variable (env-only; there is no magus.yaml key). An organization that self-hosts the site gets a private update channel for free: point that URL at the hosted copy of index.json and index.json.sig.

Artifact download URLs come from the manifest inside the index; they may point at GitHub release assets. That is artifact hosting, not discovery, and is unaffected by this change.

Index

Constants

View Source
const (
	ReleaseOwner = "egladman"
	ReleaseRepo  = "magus"
	MaxManifest  = 64 << 10  // 64 KB
	MaxSig       = 1 << 10   // 1 KB
	MaxTarball   = 200 << 20 // 200 MB
	MaxIndex     = 1 << 20   // 1 MB

	// DefaultDiscoveryURL is the canonical URL for the machine-readable release
	// index. Override via Options.DiscoveryURL or MAGUS_UPDATE_URL.
	DefaultDiscoveryURL = "https://eli.gladman.cc/magus/public/release/index.json"
)

Release coordinates and size caps for the self-update download.

Variables

This section is empty.

Functions

func CheckFileWritable

func CheckFileWritable(path string) error

CheckFileWritable probes a path with O_WRONLY.

func CheckParentWritable

func CheckParentWritable(path string) error

CheckParentWritable probes the parent directory of path by creating a temp file.

func CheckWritable

func CheckWritable(path string) error

CheckWritable verifies the running binary can be replaced. On Unix-like systems, replacing the inode of an executing process is supported, so probe the binary path directly.

func Compare

func Compare(a, b string) int

Compare returns -1, 0, or 1. Non-semver inputs are treated as equal.

func ExtractBinary

func ExtractBinary(tarGz []byte) (io.Reader, error)

ExtractBinary reads the magus binary from a .tar.gz archive.

func FetchAndVerifyTarball

func FetchAndVerifyTarball(ctx context.Context, url, assetName string, m *Manifest, opts Options) (io.Reader, error)

FetchAndVerifyTarball downloads and SHA-256 verifies the tarball; returns a reader for the binary inside.

func FetchLimited

func FetchLimited(ctx context.Context, url string, maxBytes int64, opts Options) ([]byte, error)

FetchLimited fetches url with retry and enforces a byte limit.

func KeyID added in v0.4.0

func KeyID(pub ed25519.PublicKey) string

KeyID is the fingerprint of a public key: the first 16 hex digits of its SHA-256. Short enough to read out in an error, long enough that colliding with it is not a thing an attacker can arrange.

func PrintUpdateStatus

func PrintUpdateStatus(tagName, currentVersion string)

PrintUpdateStatus writes a one-line current-vs-available comparison.

func ResolveTargetPath

func ResolveTargetPath(binDir string) (string, error)

ResolveTargetPath returns the path where the binary should be installed.

Types

type Assets

type Assets struct {
	Tarball string
	Sums    string
	Sig     string
}

Assets holds the download URLs for a release's tarball, SHA256SUMS manifest, and Ed25519 signature.

func FindAssets

func FindAssets(rel *IndexRelease, assetName string) (Assets, error)

FindAssets locates the tarball, checksum file, and signature file within an IndexRelease. Download URLs are derived from the release page URL pattern (GitHub release assets).

type IndexArtifact added in v0.2.0

type IndexArtifact struct {
	Name string `json:"name"`
}

IndexArtifact is one artifact line inside IndexRelease.Artifacts.

type IndexRelease added in v0.2.0

type IndexRelease struct {
	Version   string          `json:"version"`
	Yanked    bool            `json:"yanked,omitempty"`
	Artifacts []IndexArtifact `json:"artifacts"`
}

IndexRelease represents one entry inside ReleaseIndex.Releases. The index JSON carries additional fields (date, notes, body, per-artifact platform/ size/sha256) that selfupdate does not read: json.Unmarshal drops unknown keys silently, and integrity comes from the separately verified SHA256SUMS file, not from unauthenticated index metadata.

func SelectRelease added in v0.2.0

func SelectRelease(idx *ReleaseIndex, tag string) (*IndexRelease, error)

SelectRelease returns the IndexRelease for the requested tag from idx. When tag is empty, the release with the highest valid semver Version among non-yanked entries is returned; positional order in the index is not trusted (an index that is not newest-first, whether by bug or tampering, must not select a stale release). Entries whose Version is not valid semver are rejected rather than considered.

type Key added in v0.4.0

type Key struct {
	// ID is the fingerprint, derived from Pub rather than recorded, so the file
	// cannot name a key it does not hold.
	ID    string
	State KeyState
	Pub   ed25519.PublicKey
}

Key is one entry in the release keyring.

type KeyState added in v0.4.0

type KeyState string

KeyState is what a key in the ring may do.

const (
	// KeyActive is the one key releases are currently signed with.
	KeyActive KeyState = "active"
	// KeyStandby is announced and trusted but has never signed. It exists so a
	// rotation has somewhere to go that the field already accepts.
	KeyStandby KeyState = "standby"
	// KeyRetired still verifies artifacts it signed and will sign no more. Drop the
	// entry entirely once nothing you must verify was signed by it.
	KeyRetired KeyState = "retired"
	// KeyRevoked must not verify anything. The state is local bookkeeping: it is what
	// release-index publishes as the signed index's revoked[], which is the only form
	// of the fact a binary in the field can learn.
	KeyRevoked KeyState = "revoked"
)

type Keyring added in v0.4.0

type Keyring []Key

Keyring is the ordered set of release keys, active first.

var ReleaseKeys Keyring

ReleaseKeys is the keyring every magus binary trusts: the Ed25519 public keys a release signature may come from. Signing uses exactly one of them; verification accepts any.

A ring rather than a key, because the rotation CONTRIBUTING used to describe was a chain - ship a compatibility release signed by the old key that embeds the new one - and SelectRelease takes the newest release by default. Anyone who skipped that one release jumped to a signature their binary had never been told to trust, and had no in-band way back. A standby key ships long before it signs anything, so by the time it is used the binaries in the field already trust it and there is no hop to skip.

func (Keyring) Active added in v0.4.0

func (k Keyring) Active() (Key, error)

Active returns the single signing key. Two active keys, or none, is a configuration error rather than something to resolve by picking one: which key signed a release has to be answerable from the file alone.

func (Keyring) IDs added in v0.4.0

func (k Keyring) IDs() []string

IDs lists every fingerprint in the ring, for an error that says which keys were tried.

func (Keyring) RevokedIDs added in v0.4.0

func (k Keyring) RevokedIDs() []string

RevokedIDs lists the fingerprints release-index publishes as the index's revoked[].

func (Keyring) Verifiers added in v0.4.0

func (k Keyring) Verifiers() Keyring

Verifiers returns the keys a signature may come from: every state except revoked.

func (Keyring) Verify added in v0.4.0

func (k Keyring) Verify(msg, sig []byte) (Key, error)

Verify reports which key in the ring signed msg. Trying the ring rather than trusting a signer named in the payload keeps key selection off untrusted input: the declared key_id is then a cross-check, not the thing that decides.

func (Keyring) Without added in v0.4.0

func (k Keyring) Without(ids []string) Keyring

Without returns the ring minus the named fingerprints. Applied to the ring carried forward after the signed index has been read, so a key the publisher revoked cannot verify anything downloaded afterwards - even though this binary was built trusting it.

type Manifest

type Manifest struct {
	Version string
	Hashes  map[string]string // asset filename -> lowercase hex sha256
}

Manifest holds the verified release version and asset hashes.

func FetchAndVerifyManifest

func FetchAndVerifyManifest(ctx context.Context, sumsURL, sigURL string, opts Options) (*Manifest, error)

FetchAndVerifyManifest downloads and Ed25519-verifies the SHA256SUMS file.

func ParseManifest

func ParseManifest(data []byte) (*Manifest, error)

ParseManifest decodes a SHA256SUMS file into an Manifest.

type Options

type Options struct {
	// Keys is the ring a signature may come from, normally ReleaseKeys. After the
	// signed index has been read it is narrowed by Keyring.Without(idx.Revoked), so a
	// key the publisher revoked cannot verify anything fetched afterwards.
	Keys         Keyring
	HTTPClient   *http.Client
	DiscoveryURL string // overrides DefaultDiscoveryURL; also MAGUS_UPDATE_URL env var
}

Options configures an update operation. Keys is required; verification fails closed when it is empty.

type ReleaseIndex added in v0.2.0

type ReleaseIndex struct {
	SchemaVersion int `json:"schema_version"`
	// KeyID names the key that signed this file. It is a cross-check, never the thing
	// that selects a key: an attacker controls it exactly as much as the rest of the
	// payload, so it is compared against the key that actually verified.
	KeyID string `json:"key_id"`
	// Revoked lists fingerprints no signature may come from. It is only believed when
	// the index carrying it was itself signed by a key not on the list - which is what
	// a standby key buys, and what makes a revocation an attacker cannot forge.
	Revoked []string `json:"revoked,omitzero"`
	// ExpiresAt bounds the one hole revocation cannot close: an attacker holding a
	// compromised key serving an OLD index that names no revocation. Past it the client
	// refuses the file rather than trusting a stale one, turning an indefinite
	// compromise into a denial of service. RFC3339; empty means no bound.
	ExpiresAt string         `json:"expires_at,omitzero"`
	Releases  []IndexRelease `json:"releases"`
}

ReleaseIndex is the JSON shape of gen/public/release/index.json (schema_version 1). Schema is frozen at birth; additive changes only.

func FetchAndVerifyIndex added in v0.2.0

func FetchAndVerifyIndex(ctx context.Context, opts Options) (*ReleaseIndex, error)

FetchAndVerifyIndex fetches index.json, verifies its Ed25519 signature against opts.Keys, and returns the parsed index. The signature file is fetched from the same base URL with ".sig" appended.

If the index is unreachable, FetchAndVerifyIndex returns an error and stops. There is no silent fallback.

Jump to

Keyboard shortcuts

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