Help us shape the future of Go! Take the 2026 Go Developer Survey

updater

package
v1.0.2 Latest Latest
Warning

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

Go to latest
Published: Oct 28, 2026 License: Apache-2.0 Imports: 33 Imported by: 0

Documentation

Overview

Package updater is the on-box update agent: it reads the signed channel manifest, picks a target under policy, and stages it for the supervisor.

Index

Constants

View Source
const (
	ActionNone      = "none"
	ActionApply     = "apply"
	ActionRollback  = "rollback"
	ActionAvailable = "available" // a major release exists; never applied automatically
	ActionBlocked   = "blocked"
	ActionWait      = "wait"
)

Actions a Decision can ask for.

View Source
const (
	KindUpToDate       = "up_to_date"
	KindScheduled      = "scheduled"
	KindWaitingWindow  = "waiting_window"
	KindWaitingDefer   = "waiting_defer"
	KindBlocked        = "blocked"
	KindAvailableMajor = "available_major"
	KindApplying       = "applying"
	KindRolledBackYank = "rolled_back_yanked"
	KindRolledBack     = "rolled_back" // the health gate rolled the newest release back
	KindDev            = "dev"
	KindBackupFailed   = "backup_failed" // the pre-update backup failed; the update was not installed
)

Decision kinds (the Status.Decision vocabulary that Decide produces).

View Source
const (
	MaxManifestSize = 1 << 20
	MaxRecordSize   = 64 << 10
	MaxSigSize      = 4 << 10
)

Size caps for what the updater downloads.

View Source
const (
	KindStaged = "staged"
	KindOff    = "off"
	KindNotify = "notify"
	// KindRollbackPending: a check found the running release withdrawn and
	// changed nothing; the next scheduled run rolls the site back.
	KindRollbackPending = "rollback_pending"
	KindBusy            = "busy"
	// KindFailed is a managed apply that stopped with an error (a download or
	// signature failure, a withdrawn target): the owner's page shows it.
	KindFailed = "update_failed"
)

Further Status.Decision values, next to those Decide produces.

View Source
const (
	ResultOK      = "ok"
	ResultError   = "error"
	ResultSkipped = "skipped"
)

Status.LastResult values.

View Source
const (
	ColMode    = "mode"
	ColChannel = "channel"
	ColWindow  = "window"
	ColDefer   = "defer_days"
	ColPin     = "pin"
)

Policy columns SavePolicy can write.

View Source
const (
	ModeAuto   = "auto"
	ModeNotify = "notify"
	ModeOff    = "off"

	// DefaultChannel is the channel a box follows when nothing says otherwise.
	DefaultChannel = "stable"

	// MaxDeferDays is the longest a non-security release may be held back.
	MaxDeferDays = 14
)

Update modes.

View Source
const (
	EnvMode    = "HATAMOTO_UPDATES"
	EnvChannel = "HATAMOTO_UPDATE_CHANNEL"
)

Environment variables that override the settings row.

View Source
const DefaultBaseURL = "https://hatamoto.dev/releases"

DefaultBaseURL is where release channels and artifacts are published.

View Source
const DiskFactor = 3

DiskFactor is how many times the artifact size must be free under the data directory before an update starts (download plus staged copy plus slack).

View Source
const EnvBackup = "HATAMOTO_UPDATE_BACKUP"

EnvBackup set to "0" turns the pre-update backup off.

View Source
const InstallIDFile = "install_id"

InstallIDFile is the name of the install id file under the data directory.

View Source
const MaxRecordFetches = 5

MaxRecordFetches bounds how many records one decision may read.

Variables

View Source
var ErrInstallID = errors.New("install_id is unreadable")

ErrInstallID means the install id file exists but is not 32 bytes. It is never replaced silently: a new id would move the box to another bucket.

View Source
var ErrStalled = errors.New("download stalled")

ErrStalled means a download delivered no bytes for too long.

View Source
var ErrTooLarge = errors.New("response too large")

ErrTooLarge means a response exceeded its size cap.

Functions

func Bucket

func Bucket(id []byte, version string) int

Bucket places an install in 0..99 for one version: the first two bytes of sha256(id || version) as a big-endian number, modulo 100. An install is in a rollout of pct percent when Bucket < pct.

func DiskBlocker

func DiskBlocker(free func(dir string) (uint64, error), dir string, artifactSize int64) string

DiskBlocker returns a reason when dir has less than DiskFactor times artifactSize free, or "" when it has enough. free reads the free bytes (FreeDisk in production); a failure to read blocks.

func FreeDisk

func FreeDisk(dir string) (uint64, error)

FreeDisk returns the bytes an unprivileged process can still write under dir.

func InWindow

func InWindow(now time.Time, loc *time.Location, start, end *int) bool

InWindow reports whether now falls inside the maintenance window, hours 0-23 in loc (UTC when nil). Either bound nil, or start == end, means any time; start > end wraps midnight. The window is [start, end).

func InstallID

func InstallID(dataDir string) ([]byte, error)

InstallID returns the box's random id, creating the file (0600) on first use. The id picks the rollout bucket and never leaves the machine.

func ParseMode

func ParseMode(v string) (string, bool)

ParseMode reads a HATAMOTO_UPDATES value: off|false|0|no|disabled, on|auto|true|1, or notify, in any case.

func PinMatches

func PinMatches(pin, v string) bool

PinMatches reports whether v lies inside the pin ("vX.Y" allows vX.Y.*). An empty pin allows everything; an invalid pin or version allows nothing.

func RampPct

func RampPct(steps []release.RampStep, now time.Time) int

RampPct is the percentage of the last ramp step that has started by now (the step with the latest start time not after now). A step whose start time does not parse is ignored; no started step means 0. Step order does not matter.

func SDKBlockers

func SDKBlockers(rec *release.Record, ranges []NamedRange) []string

SDKBlockers returns one sentence per plugin or theme the target release would break: its range is met neither by rec.SDK nor by any version in rec.SDKCompatibleWith. An invalid range or version blocks (fail closed).

func SameMajor

func SameMajor(a, b string) bool

SameMajor reports whether a and b are valid versions of the same major.

Types

type Backuper

type Backuper interface {
	PreUpdate(ctx context.Context, target string) (note string, err error)
}

Backuper takes the backup that must exist before a switch. An error blocks the update; the note is added to the status text.

type Decision

type Decision struct {
	Action         string
	Kind           string
	Target         string
	Rec            *release.Record // set for apply and wait
	Reason         string
	AvailableMajor string
	FirstSeen      map[string]time.Time // input map plus newly seen candidates, pruned to the manifest
	// Fallback is the yank-back to use when this apply turns out not to be
	// installable (the preflight Decide cannot see). Set only for an apply that
	// replaces a withdrawn running release, and only when that yank-back is allowed.
	Fallback *Decision
}

Decision is what to do about the channel.

func Decide

func Decide(in Input) (Decision, error)

Decide picks a target. Order: a yank of the running version (move to its replacement or roll back), then candidates of the same major that are not yanked, bad or halted and whose bucket is inside the ramp, newest first; records are read lazily for min_from stepping, pin and kind; then defer and window. Security releases ignore defer, window and the pin (inside the major). Majors only ever yield ActionAvailable.

type Env

type Env struct {
	Data        string // absolute data dir
	Version     string // the running version
	Platform    string // "" = this build's platform
	Supervisor  supervisor.Config
	Store       *Store // nil: no stored policy or status (the offline CLI)
	Plugins     PluginSource
	Imports     ImportProbe
	Backups     Backuper // nil blocks the update unless the backup is skipped
	Site        func(ctx context.Context) *time.Location
	DatabaseURL string
	BaseURL     string // "" = DefaultBaseURL
	Log         *slog.Logger
	Now         func() time.Time
}

Env is what the updater needs from the process around it. The updater imports neither the app nor the server; they hand in these pieces.

type Fetcher

type Fetcher struct {
	// contains filtered or unexported fields
}

Fetcher makes the updater's only network requests: plain anonymous GETs. A request carries no headers of its own (Go adds User-Agent and Accept-Encoding), no query, no cookies and no identifier of the install.

func NewFetcher

func NewFetcher(baseURL string) (*Fetcher, error)

NewFetcher validates baseURL ("" = DefaultBaseURL). It must be https; plain http to a loopback host is accepted only in test builds.

func (*Fetcher) Download

func (f *Fetcher) Download(ctx context.Context, version, platform, dst string, max int64) (err error)

Download streams the binary for version and platform to dst, refusing more than max bytes. It stops after a stall (no bytes for two minutes) or 30 minutes in all. The clock is detached from ctx's deadline, since the scheduler's job cap is far shorter than a slow download. Cancelling ctx (the server shutting down) does stop it. A failed download leaves no file behind.

func (*Fetcher) Manifest

func (f *Fetcher) Manifest(ctx context.Context, channel string) ([]byte, error)

Manifest returns the raw signed channel manifest: one GET of <base>/channels/<channel>.json.

func (*Fetcher) Record

func (f *Fetcher) Record(ctx context.Context, version string) (rec, sig []byte, err error)

Record returns the release record and its detached signature for version.

type ImportProbe

type ImportProbe interface {
	ImportRunning(ctx context.Context) (bool, error)
}

ImportProbe reports whether a content import is running right now.

type Input

type Input struct {
	Running       string
	RunningRecord *release.Record // for rollback_floor; may be nil
	Manifest      *release.ChannelManifest
	Settings      Settings // effective: defaults and environment applied
	Now           time.Time
	Loc           *time.Location // site timezone for the window; nil = UTC
	Bucket        func(version string) int
	Bad           func(version string) bool   // versions that failed health gating; may be nil
	BadReason     func(version string) string // why a bad version failed; may be nil
	FirstSeen     map[string]time.Time
	// Previous is what a yank-back returns to: the supervisor's previous
	// release, or when PreviousIsBootstrap the bootstrap build's version
	// ("" or not semver when unknown). "" with PreviousIsBootstrap false: none.
	Previous            string
	PreviousIsBootstrap bool
	// Record reads and verifies one release record. Decide memoises it and
	// calls it at most MaxRecordFetches times.
	Record func(version string) (*release.Record, error)
}

Input is everything Decide looks at. It does no I/O of its own.

type NamedRange

type NamedRange struct {
	Kind  string // "plugin" or "theme"
	Name  string
	Range string
}

NamedRange is the SDK range one enabled plugin or the active theme declares.

type PluginSource

type PluginSource interface {
	EnabledSDKRanges(ctx context.Context) ([]NamedRange, error)
}

PluginSource lists the SDK ranges of the enabled plugins and the active theme. The adapter over the package store lives with the server wiring.

type Querier

type Querier interface {
	QueryRow(ctx context.Context, sql string, args ...any) pgx.Row
	Exec(ctx context.Context, sql string, args ...any) (pgconn.CommandTag, error)
}

Querier is the part of a pgx pool the Store needs.

type RunOpts

type RunOpts struct {
	DryRun bool   // decide only; nothing is stored or staged
	Apply  string // a named version: the explicit operator route
	Force  bool   // skip the plugin preflight
	// CheckOnly is a dry run whose outcome is stored like a scheduled check:
	// fetch, decide and preflight, never download, stage or switch.
	CheckOnly bool
	// Managed makes a named Apply behave like a scheduled install where it
	// matters to the owner: it waits for a running check instead of giving up,
	// takes the pre-update database backup and stores its outcome whatever it
	// is. The CLI leaves it off.
	Managed bool
	// SkipBackup skips the pre-update backup for this run only.
	SkipBackup bool
}

RunOpts shapes one Run.

type Settings

type Settings struct {
	Mode        string
	Channel     string
	WindowStart *int
	WindowEnd   *int
	Pin         string // "vX.Y" or ""
	DeferDays   int    // 0..MaxDeferDays
}

Settings is the update policy. Mode and Channel are empty when unset; Effective fills the defaults. A nil window hour means "any time".

func (Settings) Effective

func (s Settings) Effective(env func(string) string) Settings

Effective returns s with the defaults filled and the environment applied. Environment values win over the row. An unrecognised HATAMOTO_UPDATES fails closed to off (with a warning), since a mistyped "false" must not leave the box calling out and installing; an invalid channel is logged and ignored.

func (*Settings) Validate

func (s *Settings) Validate() error

Validate checks the policy values and clamps DeferDays into range.

type Status

type Status struct {
	LastCheckAt    *time.Time
	LastResult     string
	Decision       string
	Reason         string
	Target         string
	AvailableMajor string
	FirstSeen      map[string]time.Time
}

Status is the outcome of the last run. Decision is one of up_to_date, scheduled, waiting_window, waiting_defer, blocked, available_major, staged, applying, rolled_back_yanked, off, notify, busy, dev; LastResult is ok, error or skipped; Reason is human text.

type Store

type Store struct {
	// contains filtered or unexported fields
}

Store reads and writes the single hatamoto_update_settings row.

func NewStore

func NewStore(db Querier) *Store

NewStore returns a Store over db (a *pgxpool.Pool fits).

func (*Store) Load

func (s *Store) Load(ctx context.Context) (Settings, Status, error)

Load returns the stored policy and status; a missing row is all defaults.

func (*Store) SavePolicy

func (s *Store) SavePolicy(ctx context.Context, set Settings, cols ...string) error

SavePolicy validates set and writes only the named policy columns, so what was not named keeps its stored value: a column left NULL stays the built-in default, and a pin set meanwhile (the command line) is not overwritten.

func (*Store) SaveSettings

func (s *Store) SaveSettings(ctx context.Context, set Settings) error

SaveSettings validates and stores the policy columns, leaving the status alone.

func (*Store) SaveStatus

func (s *Store) SaveStatus(ctx context.Context, st Status) error

SaveStatus stores the status columns, leaving the policy alone.

func (*Store) ServerMajor

func (s *Store) ServerMajor(ctx context.Context) (int, error)

ServerMajor returns the major version of the database server.

type Updater

type Updater struct {
	// contains filtered or unexported fields
}

Updater runs update checks, one at a time.

func New

func New(env Env) (*Updater, error)

New validates the base URL and returns an Updater.

func (*Updater) Run

func (u *Updater) Run(ctx context.Context, o RunOpts) (Status, error)

Run does one check and, under policy, the update. It makes exactly one request before a target exists: the channel manifest.

func (*Updater) RunningVersion

func (u *Updater) RunningVersion() string

RunningVersion is the version the supervisor runs now, which after a self-update or a rollback is the current release and not the version this process was stamped with. It falls back to Version when the state cannot be read.

func (*Updater) Schedule

func (u *Updater) Schedule(s *worker.Scheduler) error

Schedule registers the periodic check: every six hours give or take half an hour, the first one a few minutes after start. The run detaches from the scheduler's 10 minute cap, which is shorter than a slow download, but not from its cancellation: the scheduler's context is the server's lifecycle context, so a shutdown stops the download and the pg_dump.

func (*Updater) Store

func (u *Updater) Store() *Store

Store returns the policy and status store the Updater was built with (nil for the offline CLI). The admin reads and saves settings through it.

func (*Updater) Version

func (u *Updater) Version() string

Version returns the version this Updater was built to run as.

Jump to

Keyboard shortcuts

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