Documentation
¶
Overview ¶
Package updater — self-update support for nightme.
The package owns three jobs, in order:
Lookup: fetch the GitHub release metadata for a given tag, including the list of release assets and SHA256SUMS.
Match: pick the single asset that matches the running binary's GOOS/GOARCH. Assets follow the "nightme_<version>_<os>_<arch>.<ext>" naming convention used by the project's release workflow (see the SHA256SUMS listing for v0.3.7 as the canonical example).
Download: fetch the matched asset to a staging path under DataDir/updates/<version>/, with cancellable context, stdlib-only progress reporting, and SHA256 verification against the SHA256SUMS file from the same release.
The package is stdlib-only. We could pull in rhysd/go-github-selfupdate but it bundles its own GitHub client, semaphore, and progress bar; for a single-binary release like nightme the stdlib path is ~200 lines and keeps the supply-chain surface small.
Layering:
- cmd/nightme/update.go (CLI shell)
- internal/updater (this package: Lookup / Match / Download)
- cmd/nightme/update.go (Install — next commit: selfupdate binary swap + daemon restart).
Index ¶
- Constants
- Variables
- func ExtractArchive(archivePath, stagingDir string) (string, error)
- func FormatBytes(n int64) string
- func FormatSpeed(bytes int64, elapsed time.Duration) string
- func QuietProgress(int64, int64, time.Duration)
- func StagingDir(dataDir, version string) (string, error)
- type Asset
- type CheckResult
- type DownloadResult
- type InstallResult
- type ProgressFunc
- type Release
Constants ¶
const DefaultTimeout = 5 * time.Minute
DefaultTimeout caps the entire download path (lookup + checksum + asset). Production callers pass a derived context so Ctrl-C cancels cleanly.
Variables ¶
var LookupURL = "https://api.github.com"
LookupURL is the base URL GitHub's releases API lives at. Held as a var so tests can swap it for an httptest server without having to plumb a base URL through every caller.
Production callers should leave this untouched; the default (api.github.com) is what we ship.
Functions ¶
func ExtractArchive ¶
ExtractArchive pulls the nightme binary out of the .tar.gz / .zip downloaded by Download. Exposed here so install.go (next commit) can reuse it without depending on archive-specific code paths in two places. Returns the absolute path to the extracted binary inside stagingDir.
func FormatBytes ¶
FormatBytes is exposed for progress reporter reuse.
func FormatSpeed ¶
FormatSpeed returns a human-readable bytes/sec string for the progress reporter (e.g. "1.2 MB/s"). Exposed so tests can pin the formatter output without re-implementing the math.
func QuietProgress ¶
QuietProgress is a no-op ProgressFunc for callers that want to silence the progress reporter (CI, scripted runs, tests).
func StagingDir ¶
StagingDir returns the canonical staging path for a given version: <DataDir>/updates/<version>/. Callers should pass the value from config.Config.Paths.DataDir; an empty DataDir disables staging (returns "" + error).
Types ¶
type Asset ¶
type Asset struct {
Name string `json:"name"`
BrowserDownloadURL string `json:"browser_download_url"`
Size int64 `json:"size"`
}
Asset is one downloadable file in a release. The fields we need are name (for matching + SHA256SUMS lookup) and browser_download_url (the actual asset URL). Size is useful for the progress bar's total field.
func MatchAsset ¶
MatchAsset picks the asset matching the running binary's GOOS/GOARCH using the "nightme_<version>_<os>_<arch>.<ext>" convention. When version is empty the matcher accepts any version segment so callers can use it across releases.
Returns nil (no error) when no asset matches — the caller surfaces this as "no binary for darwin/amd64 in this release", which is the correct diagnostic for an unsupported OS/arch.
type CheckResult ¶
CheckResult is the unified stage-1 output. It bundles the raw *Release (so downstream stages can pick the asset + SHA256SUMS without a second API call) with the user- facing Latest string and the Outdated bool.
Latest — tag of the release we're targeting (e.g. "v0.3.7") Outdated — true when current < latest under semver rules Release — full *Release for stages 2 + 3
func Check ¶
func Check(ctx context.Context, tag string) (*CheckResult, error)
Check is stage 1: resolve the latest (or pinned) GitHub release and decide whether the running build is out of date. It does NOT touch the filesystem or any binaries.
Tag is the optional `--tag vX.Y.Z` override; empty means "latest". The current version is read from the package- level version.Version via version.Compare.
Errors are surfaced verbatim — the CLI translates them into the "1/3 check failed" line.
type DownloadResult ¶
type DownloadResult struct {
Asset Asset
StagingPath string // absolute path under stagingDir
SHA256Hex string // hex-encoded hash of the downloaded bytes
Bytes int64 // total bytes written (== Asset.Size on success)
Cached bool // true when a local archive already matched SHA256SUMS
}
DownloadResult is what Download returns on success. The caller (CLI / install command) reads StagingPath to swap the binary in place.
func Download ¶
func Download( ctx context.Context, release *Release, asset *Asset, stagingDir string, progress ProgressFunc, ) (*DownloadResult, error)
Download fetches the asset to stagingDir/<asset.Name> with cancellable context, periodic progress reporting, and a SHA256SUMS-driven integrity check.
The downloaded archive (.tar.gz on unix, .zip on windows) is kept as-is in the staging dir; Install (next commit) is responsible for extracting and replacing the binary.
stagingDir is typically <DataDir>/updates/<version>/. The function creates it (parents included) if it does not exist.
progress may be nil for silent downloads.
The SHA256SUMS file is fetched separately from the same release; if it cannot be downloaded the function fails closed (errors.New("checksums unreachable")) so callers never silently install an unverified binary.
type InstallResult ¶
type InstallResult struct {
NewBinaryPath string // path to the binary now on disk (== target)
OldBinaryPath string // path to the backup of the previous binary
ExtractedFrom string // archive we extracted
}
InstallResult is what Install returns on success. The caller (CLI) reads NewBinaryPath to print "the new binary is at X" and OldBinaryPath to mention the rollback path.
func Install ¶
func Install(stagedBinaryPath, targetPath string) (*InstallResult, error)
Install replaces the running binary with a previously downloaded + extracted one.
stagedBinaryPath -- the nightme / nightme.exe produced by
ExtractArchive, sitting in
<DataDir>/updates/<version>/
targetPath -- the on-disk binary the user is currently
invoking (os.Executable())
Steps:
- Refuse to install when source == target (copying onto itself on Windows is a permissions nightmare; on unix it'd succeed but is almost certainly a caller bug).
- Verify stagedBinaryPath is a regular file, readable, and executable-sized.
- Move targetPath → targetPath + ".old" (the backup). Move is rename(2) on unix — atomic on the same filesystem — so a crashed install leaves either the old binary in place or the new one in place; never a half-written file at targetPath.
- Copy stagedBinaryPath → targetPath.
- chmod 0755 on targetPath (the staging dir might have lost the +x bit during extraction under some umasks).
Errors before step 3 are pure: nothing on disk has changed. Errors during step 4 attempt to roll back by renaming targetPath.old back to targetPath. If the rollback also fails the error wraps the rollback so the operator knows to run `mv <target>.old <target>` by hand.
type ProgressFunc ¶
ProgressFunc is called periodically during Download with the current bytes read, total bytes (when known), and elapsed wall time. Implementations typically render an ASCII progress bar to the terminal. Callers may pass nil to skip progress reporting entirely (faster path for tests / quiet mode).
Frequency is best-effort: the downloader flushes a progress event on every chunk boundary AND on every Tick interval (200ms), whichever fires first. Total may be -1 if the server did not send Content-Length.
func NewASCIIProgressBar ¶
func NewASCIIProgressBar(out io.Writer, total int64) ProgressFunc
NewASCIIProgressBar returns a ProgressFunc that renders a single-line ASCII bar to out. The bar overwrites itself with \r on every tick and the caller prints a final newline (Download flushes an empty event by virtue of total == done).
Layout (width = 30 cells):
[============== ] 47% 1.2 MB / 2.6 MB 4.3 MB/s ETA 5s
total <= 0 (server omitted Content-Length) renders an indeterminate bar that only shows downloaded bytes — the bar cell count grows as bytes arrive.
type Release ¶
Release is the subset of the GitHub release payload we read. We don't decode every field — the asset list and the tag name are all that Lookup consumers need.