updater

package
v0.0.0-...-1096d29 Latest Latest
Warning

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

Go to latest
Published: May 25, 2026 License: MIT Imports: 21 Imported by: 0

Documentation

Overview

Package updater handles self-upgrading the twoctl binary from GitHub releases. State (last check time, skipped versions) is persisted to ~/.config/twoctl/state.json so the user's "skip this version" choice survives across runs.

Index

Constants

This section is empty.

Variables

This section is empty.

Functions

func Apply

func Apply(ctx context.Context, rel *Release) error

Apply downloads the release asset matching the host platform, verifies its SHA-256 against the release's checksums.txt, and replaces the running binary in place. The caller's next invocation of twoctl will run the new version.

func IsNewer

func IsNewer(current, latest string) bool

IsNewer reports whether `latest` is a strictly newer semver tag than `current`. Both are expected in `vX.Y.Z` (or `X.Y.Z`) form. A non-semver `current` (the "dev" build) is always treated as older so devs see the prompt. A non-semver `latest` is treated as "not newer" - we will not nag users to upgrade to a tag we can't reason about.

func SaveState

func SaveState(s *State) error

SaveState atomically writes the state file.

Types

type Asset

type Asset struct {
	Name               string `json:"name"`
	BrowserDownloadURL string `json:"browser_download_url"`
}

Asset is one downloadable file attached to a GitHub release.

type PromptResponse

type PromptResponse int

PromptResponse is the user's answer to the upgrade prompt.

const (
	PromptUpgrade PromptResponse = iota
	PromptDecline
	PromptSkip
)

Possible PromptResponse values.

func AskAndApply

func AskAndApply(ctx context.Context, rel *Release, currentVersion string, in io.Reader, out io.Writer) (PromptResponse, error)

AskAndApply prompts the user, then either upgrades, declines, or marks the version as skipped according to the response. Returns the chosen response.

type Release

type Release struct {
	TagName string  `json:"tag_name"`
	Name    string  `json:"name"`
	HTMLURL string  `json:"html_url"`
	Assets  []Asset `json:"assets"`
}

Release is the subset of the GitHub release payload that we care about.

func AutoCheck

func AutoCheck(ctx context.Context, current string) (*Release, error)

AutoCheck runs at most once per checkInterval. It returns the latest release if it is newer than `current` and the user has not skipped it, otherwise nil. Network failures are silent - we never want to block a command on the update check.

func LatestRelease

func LatestRelease(ctx context.Context) (*Release, error)

LatestRelease fetches the latest release tag from GitHub.

type State

type State struct {
	// NextCheckAt is the earliest UTC time at which we may run the
	// upgrade check again. Stored as an absolute timestamp (rather than
	// "last check + interval") so clock skew, NTP corrections, and
	// suspend/resume don't either suppress or fire the check unexpectedly.
	NextCheckAt        time.Time `json:"next_check_at,omitempty"`
	LatestKnownVersion string    `json:"latest_known_version,omitempty"`
	// CachedRelease holds the full release payload from the last
	// successful GitHub fetch. Caching the assets list means the prompt
	// path can install without a second LatestRelease call (which would
	// silently defeat the throttle).
	CachedRelease   *Release `json:"cached_release,omitempty"`
	SkippedVersions []string `json:"skipped_versions,omitempty"`
	// AutoCheck disables the periodic check entirely when false. Defaults
	// to true on first run; the user can flip it via `twoctl upgrade
	// --disable-autocheck`.
	AutoCheck *bool `json:"auto_check,omitempty"`

	// LastCheckAt is kept for backward-compat with older state files; new
	// writes populate NextCheckAt and leave this blank.
	LastCheckAt time.Time `json:"last_check_at,omitempty"`
}

State is the on-disk preference + check-cache file.

func LoadState

func LoadState() (*State, error)

LoadState reads the state file. A missing file returns an empty State, not an error.

func (*State) AddSkip

func (s *State) AddSkip(v string)

AddSkip adds v to the skip list if not already present.

func (*State) AutoCheckEnabled

func (s *State) AutoCheckEnabled() bool

AutoCheckEnabled returns whether autocheck should run. Defaults to true when the field has never been set.

func (*State) IsSkipped

func (s *State) IsSkipped(v string) bool

IsSkipped reports whether version v has been explicitly skipped by the user.

Jump to

Keyboard shortcuts

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