Documentation
¶
Overview ¶
Package selfupdate — the error-code range owned by this domain (ADR 0005 §Registry).
Package selfupdate — the sentinels every implementation of this domain returns.
The first five are the trust chain, and their ORDER is the contract: a build with no vendor key cannot install at all; a manifest with no signature is not authenticated; a signature that does not verify ends it; only then is the archive's digest compared against a manifest that is now trusted. A digest checked against an unauthenticated manifest proves nothing, which is why CodeChecksumMismatch can only be reached after CodeSignatureInvalid was not.
Package selfupdate is the contract for a binary that replaces itself: the ports it needs from its environment, and the values it reports.
The domain exists because the hard part of a self-update is not the download. It is the ORDER in which a candidate becomes trusted — a detached signature over a checksum manifest, then the archive's digest against that now-authenticated manifest, and only then anything touching the disk — and the fact that a process replacing its own executable has no second chance if it gets it wrong.
Three ports, and each is a seam a test drives rather than an abstraction for its own sake: Getter is the network, FileSystem is the disk, Copier is the stream between them. There is **no registry**: a registry's key would name a release host, and the whole trust chain is anchored to a key pinned at build time for exactly one host.
Package selfupdate — the three ports a self-update needs from its environment: the network, the disk, and the stream between them.
Package selfupdate — the values a self-update reports.
Index ¶
Constants ¶
const CodeAPIBodyTooLarge errs.Code = 0x00_02_22_07 // 0.2.34.7
CodeAPIBodyTooLarge identifies a release-metadata response exceeding its cap.
const CodeArchiveTooLarge errs.Code = 0x00_02_22_06 // 0.2.34.6
CodeArchiveTooLarge identifies a release archive exceeding the size cap.
const CodeBinaryNotInArchive errs.Code = 0x00_02_22_10 // 0.2.34.16
CodeBinaryNotInArchive identifies a verified archive that does not contain the expected binary.
const CodeCandidateNotFound errs.Code = 0x00_02_22_0C // 0.2.34.12
CodeCandidateNotFound identifies a requested release candidate that does not exist.
const CodeChecksumMismatch errs.Code = 0x00_02_22_05 // 0.2.34.5
CodeChecksumMismatch identifies an archive whose digest did not match its authenticated manifest entry.
const CodeChecksumMissing errs.Code = 0x00_02_22_04 // 0.2.34.4
CodeChecksumMissing identifies an archive with no entry in the authenticated checksum manifest.
const CodeDevBuild errs.Code = 0x00_02_22_0B // 0.2.34.11
CodeDevBuild identifies a version check on a build with no release version to compare against.
const CodeDownloadFailed errs.Code = 0x00_02_22_0A // 0.2.34.10
CodeDownloadFailed identifies a download that did not complete.
const CodeDraftRelease errs.Code = 0x00_02_22_0E // 0.2.34.14
CodeDraftRelease identifies a draft release, which has no downloadable asset.
const CodeElevationNotAuthorised errs.Code = 0x00_02_22_12 // 0.2.34.18
CodeElevationNotAuthorised identifies a replacement needing privilege the operator did not opt into.
const CodeInsecureRedirect errs.Code = 0x00_02_22_08 // 0.2.34.8
CodeInsecureRedirect identifies a redirect leaving HTTPS, or exceeding the hop bound.
const CodeInvalidTag errs.Code = 0x00_02_22_0F // 0.2.34.15
CodeInvalidTag identifies a tag that is not a valid semantic version.
const CodeNoVendorKey errs.Code = 0x00_02_22_01 // 0.2.34.1
CodeNoVendorKey identifies an install attempted by a build that carries no vendor key, so nothing could have authenticated the release.
const CodeNotPrerelease errs.Code = 0x00_02_22_0D // 0.2.34.13
CodeNotPrerelease identifies a tag requested as a candidate that the host reports as a stable release.
const CodeSignatureInvalid errs.Code = 0x00_02_22_03 // 0.2.34.3
CodeSignatureInvalid identifies a checksum manifest whose signature did not verify against the vendor key.
const CodeSignatureMissing errs.Code = 0x00_02_22_02 // 0.2.34.2
CodeSignatureMissing identifies a release whose detached signature over the checksum manifest could not be fetched.
const CodeUnexpectedStatus errs.Code = 0x00_02_22_09 // 0.2.34.9
CodeUnexpectedStatus identifies a release host answering with a status the update path does not accept.
const CodeUnknownArchive errs.Code = 0x00_02_22_11 // 0.2.34.17
CodeUnknownArchive identifies an asset whose container format is not handled.
Variables ¶
var ( // NoVendorKey is returned when the running build carries no vendor key. It // is a REFUSAL, not a degradation: without a key nothing could have // authenticated the release, so there is no weaker check to fall back to. NoVendorKey = errs.Static(CodeNoVendorKey, "NO_VENDOR_KEY", "this build cannot verify a release and will not install one", "core/selfupdate: no vendor public key was linked into this build", 0, 0) // SignatureMissing is returned when the detached signature over the checksum // manifest could not be fetched. SignatureMissing = errs.Static(CodeSignatureMissing, "SIGNATURE_MISSING", "the release is not signed", "core/selfupdate: the detached signature over the checksum manifest was absent", 0, 0) // SignatureInvalid is returned when the checksum manifest's signature does // not verify against the vendor key. SignatureInvalid = errs.Static(CodeSignatureInvalid, "SIGNATURE_INVALID", "the release signature did not verify", "core/selfupdate: the manifest signature failed ed25519 verification against the vendor key", 0, 0) // ChecksumMissing is returned when the authenticated manifest has no entry // for the archive being installed. ChecksumMissing = errs.Static(CodeChecksumMissing, "CHECKSUM_MISSING", "the release has no checksum for this platform", "core/selfupdate: the authenticated manifest carries no entry for the requested asset", 0, 0) // ChecksumMismatch is returned when the archive's digest does not match its // authenticated manifest entry. ChecksumMismatch = errs.Static(CodeChecksumMismatch, "CHECKSUM_MISMATCH", "the release archive does not match its checksum", "core/selfupdate: the archive digest differs from its authenticated manifest entry", 0, 0) // ArchiveTooLarge is returned when a release archive exceeds the size cap. // The cap exists because the bytes are buffered in memory before anything // has vouched for them. ArchiveTooLarge = errs.Static(CodeArchiveTooLarge, "ARCHIVE_TOO_LARGE", "the release archive is larger than this build accepts", "core/selfupdate: the archive exceeded the in-memory size cap", 0, 0) // APIBodyTooLarge is returned when a release-metadata response exceeds its // cap. APIBodyTooLarge = errs.Static(CodeAPIBodyTooLarge, "API_BODY_TOO_LARGE", "the release metadata is larger than this build accepts", "core/selfupdate: the release API response exceeded its read cap", 0, 0) // InsecureRedirect is returned when a redirect leaves HTTPS or exceeds the // hop bound. Following it would hand the archive fetch to a plaintext hop. InsecureRedirect = errs.Static(CodeInsecureRedirect, "INSECURE_REDIRECT", "the release host redirected somewhere this build will not follow", "core/selfupdate: a redirect left https or exceeded the hop bound", 0, 0) // UnexpectedStatus is returned when the release host answers with a status // the update path does not accept. UnexpectedStatus = errs.Static(CodeUnexpectedStatus, "UNEXPECTED_STATUS", "the release host answered unexpectedly", "core/selfupdate: the release host returned a status the update path does not accept", 0, 0) // DownloadFailed is returned when a download does not complete. DownloadFailed = errs.Static(CodeDownloadFailed, "DOWNLOAD_FAILED", "the release could not be downloaded", "core/selfupdate: the release download did not complete", 0, 0) // DevBuild is returned when a version check runs on a build with no release // version to compare against. DevBuild = errs.Static(CodeDevBuild, "DEV_BUILD", "this build has no version to compare against a release", "core/selfupdate: the running build carries no release version", 0, 0) // CandidateNotFound is returned when a requested release candidate does not // exist. CandidateNotFound = errs.Static(CodeCandidateNotFound, "CANDIDATE_NOT_FOUND", "no such release candidate", "core/selfupdate: the requested candidate tag was not found on the release host", 0, 0) // NotPrerelease is returned when a tag requested as a candidate is reported // as a stable release. NotPrerelease = errs.Static(CodeNotPrerelease, "NOT_PRERELEASE", "that tag is a stable release, not a candidate", "core/selfupdate: the requested tag is not marked prerelease", 0, 0) // DraftRelease is returned for a draft release, which has no downloadable // asset. DraftRelease = errs.Static(CodeDraftRelease, "DRAFT_RELEASE", "that release is still a draft", "core/selfupdate: the requested release is a draft and exposes no asset", 0, 0) // InvalidTag is returned for a tag that is not a valid semantic version. InvalidTag = errs.Static(CodeInvalidTag, "INVALID_TAG", "that is not a valid version tag", "core/selfupdate: the tag is not a valid semantic version", 0, 0) // BinaryNotInArchive is returned when a verified archive does not contain // the expected binary. The archive is authentic; it is simply not what was // expected, which is a packaging fault rather than an attack. BinaryNotInArchive = errs.Static(CodeBinaryNotInArchive, "BINARY_NOT_IN_ARCHIVE", "the release archive does not contain the expected binary", "core/selfupdate: the verified archive holds no entry matching the binary name", 0, 0) // UnknownArchive is returned for an asset whose container format is not // handled. UnknownArchive = errs.Static(CodeUnknownArchive, "UNKNOWN_ARCHIVE", "the release archive format is not supported", "core/selfupdate: the asset is neither a tar.gz nor a zip", 0, 0) // ElevationNotAuthorised is returned when replacing the binary needs // privilege the operator did not opt into. Escalating without being asked is // the one thing an updater must never do quietly. ElevationNotAuthorised = errs.Static(CodeElevationNotAuthorised, "ELEVATION_NOT_AUTHORISED", "replacing this binary needs privilege that was not authorised", "core/selfupdate: the replacement required elevation and the opt-in was absent", 0, 0) )
Functions ¶
This section is empty.
Types ¶
type CandidateValue ¶
type CandidateValue struct {
// Tag is the release tag, e.g. "v1.9.0-rc.3".
Tag string
// Name is the release's human title, which a host may leave empty.
Name string
// CreatedAt is the RFC 3339 publication timestamp, as text.
CreatedAt string
}
CandidateValue is one release candidate: a developer channel entry that is never what a stable check returns.
All three fields are carried VERBATIM as the release host reported them. They are displayed to an operator choosing a candidate, never parsed and never compared — the ordering decision is made on Tag by semver, elsewhere.
type Copier ¶
type Copier interface {
// Copy copies from src to dst, returning the number of bytes written.
Copy(dst io.Writer, src io.Reader) (written int64, err error)
}
Copier streams the verified archive to its temporary destination. It is a port so a test can fail the copy midway — the case where a partially written binary must never be renamed into place.
type FileSystem ¶
type FileSystem interface {
// Executable returns the path of the current executable.
Executable() (path string, err error)
// EvalSymlinks resolves symlinks in the given path.
EvalSymlinks(path string) (resolved string, err error)
// CreateTemp creates a temporary file.
CreateTemp(dir, pattern string) (file *os.File, err error)
// Chmod changes file permissions.
Chmod(name string, mode os.FileMode) error
// Rename renames a file. It is the atomic half of the replacement and must
// happen on the same filesystem as the target.
Rename(oldpath, newpath string) error
// Remove removes a file.
Remove(name string) error
}
FileSystem is the disk half of replacing a running binary.
EvalSymlinks is not optional decoration: an executable reached through a symlink must be replaced at its REAL path, or the update writes over the link and the next run still executes the old binary.
type Getter ¶
type Getter interface {
// Get performs an HTTP GET request.
Get(url string) (resp *http.Response, err error)
}
Getter performs the HTTP GETs a self-update needs: the release metadata, the archive, the checksum manifest and its signature.
It is one method because that is all the domain needs, and a caller supplying a policy-carrying client (timeouts, redirect rules, proxies) is supplying it once for all four.
type UpdateValue ¶
type UpdateValue struct {
// CurrentVersion is the version of the running binary.
CurrentVersion string
// LatestVersion is the version that was found, or the empty string when
// none was.
LatestVersion string
// Available reports whether LatestVersion is newer than CurrentVersion.
Available bool
}
UpdateValue is the outcome of a version check or an install: whether a newer release exists, and the two versions that were compared.
Available is reported separately from a version comparison the caller could redo, because the comparison is semver-aware and a string compare of the two fields would disagree with it on a prerelease.