Documentation
¶
Overview ¶
Package updater implements Etherpad-Go's self-update subsystem: a tiered (off / notify / manual / auto / autonomous) updater that checks GitHub releases, and — for writable single-binary installs — downloads the matching release binary, verifies it, atomically replaces the running executable and exits with code 75 so a process supervisor restarts into the new version.
It is a Go-appropriate port of Etherpad-lite's src/node/updater subsystem. Because Etherpad-Go ships as a single binary (rather than a git checkout updated with pnpm), the apply mechanism is binary self-replacement instead of git/pnpm, but the surrounding state machine, policy gates, scheduler, maintenance windows, session draining, rollback and crash-loop guard mirror the upstream design.
Index ¶
- Constants
- func IsValidTag(tag string) bool
- func SaveState(path string, s UpdateState) error
- type CheckKind
- type CheckResult
- type Config
- type DrainResult
- type Drainer
- type ExecResult
- type Execution
- type ExecutionStatus
- type Executor
- type InstallMethod
- type LastResult
- type MaintenanceWindow
- type PolicyInput
- type PolicyResult
- type PreflightDeps
- type PreflightResult
- type ReleaseInfo
- type ScheduleAction
- type ScheduleDecision
- type ScheduleInput
- type SignatureVerifier
- type Status
- type Tier
- type TriggerAction
- type TriggerDecision
- type TriggerInput
- type UpdateState
- type Updater
- func (u *Updater) Acknowledge() error
- func (u *Updater) ApplyNow() error
- func (u *Updater) CheckNow()
- func (u *Updater) CheckNowSync() Status
- func (u *Updater) IsAcceptingConnections() bool
- func (u *Updater) MarkBootHealthy()
- func (u *Updater) Start()
- func (u *Updater) Status() Status
- func (u *Updater) Stop()
- type VersionChecker
Constants ¶
const ExitCodeRestart = 75
ExitCodeRestart is returned to the OS after a successful apply or a rollback so that the supervisor (systemd, Docker, Windows service, ...) restarts the process into the swapped-in binary.
const SchemaVersion = 1
SchemaVersion is the persisted update-state schema version.
Variables ¶
This section is empty.
Functions ¶
func IsValidTag ¶
IsValidTag validates a release tag before it is interpolated into a URL or a filesystem path. It rejects empty/oversized tags, leading "-"/"." (option injection / hidden files), ".." (traversal), and control or shell-significant characters.
func SaveState ¶
func SaveState(path string, s UpdateState) error
SaveState writes the state atomically (write to a temp file, then rename) so a crash mid-write can never leave a truncated state file.
Types ¶
type CheckResult ¶
type CheckResult struct {
Kind CheckKind
Release *ReleaseInfo
ETag string
Status int
}
CheckResult is the outcome of polling for the latest release.
type Config ¶
type Config struct {
Tier Tier
Repo string
CurrentVersion string
CheckInterval time.Duration
GraceMinutes int
DrainSeconds int
HealthCheckSeconds int
InstallMethod InstallMethod // override; auto-detected when InstallAuto
Window *MaintenanceWindow
WindowConfigured bool
Verifier SignatureVerifier
StatePath string
LockPath string
}
Config is the fully-resolved updater configuration (built from settings by the server package, keeping this package settings-free and testable).
type DrainResult ¶
type DrainResult string
DrainResult is the outcome of a drain.
const ( DrainCompleted DrainResult = "completed" DrainCancelled DrainResult = "cancelled" )
type Drainer ¶
type Drainer struct {
// contains filtered or unexported fields
}
Drainer blocks new pad connections for a countdown window and announces the countdown to connected clients, giving them time to save before the binary is swapped and the process restarts.
func NewDrainer ¶
NewDrainer constructs a drainer. setAccept must not be nil; broadcast may be.
func (*Drainer) Start ¶
func (d *Drainer) Start() DrainResult
Start blocks until the drain window elapses or Cancel is called. New connections are refused for the duration and restored on return.
type ExecResult ¶
ExecResult is the outcome of an apply attempt's file operations. The orchestrator turns OK into a pending-verification state + exit(75), and a failure into a rollback.
type Execution ¶
type Execution struct {
Status ExecutionStatus `json:"status"`
TargetTag string `json:"targetTag,omitempty"`
FromVersion string `json:"fromVersion,omitempty"`
BackupPath string `json:"backupPath,omitempty"` // path to the backed-up previous binary
ScheduledFor string `json:"scheduledFor,omitempty"` // RFC3339
DrainEndsAt string `json:"drainEndsAt,omitempty"` // RFC3339
DeadlineAt string `json:"deadlineAt,omitempty"` // RFC3339, health-check deadline
Reason string `json:"reason,omitempty"`
At string `json:"at,omitempty"` // RFC3339, time the status was entered
}
Execution captures the current apply attempt. Fields are only meaningful for certain Status values.
type ExecutionStatus ¶
type ExecutionStatus string
ExecutionStatus is the persisted state-machine position of an apply attempt.
const ( StatusIdle ExecutionStatus = "idle" StatusScheduled ExecutionStatus = "scheduled" StatusPreflight ExecutionStatus = "preflight" StatusPreflightFailed ExecutionStatus = "preflight-failed" StatusDraining ExecutionStatus = "draining" StatusExecuting ExecutionStatus = "executing" StatusPendingVerification ExecutionStatus = "pending-verification" StatusVerified ExecutionStatus = "verified" StatusRollingBack ExecutionStatus = "rolling-back" StatusRolledBack ExecutionStatus = "rolled-back" StatusRollbackFailed ExecutionStatus = "rollback-failed" // terminal: blocks auto until acknowledged )
type Executor ¶
type Executor struct {
// contains filtered or unexported fields
}
Executor downloads, verifies and atomically swaps in the new release binary.
func NewExecutor ¶
func NewExecutor(checker *VersionChecker, exePath string, verifier SignatureVerifier, logger *zap.SugaredLogger) *Executor
NewExecutor builds an executor targeting exePath (the running executable).
func (*Executor) Apply ¶
func (e *Executor) Apply(target ReleaseInfo) ExecResult
Apply performs the full download → verify → replace sequence. On success the previous binary is preserved at the returned BackupPath for rollback.
type InstallMethod ¶
type InstallMethod string
InstallMethod describes how Etherpad-Go was installed, which determines whether an in-place update is possible.
const ( InstallAuto InstallMethod = "auto" // detect at runtime InstallBinary InstallMethod = "binary" // writable standalone executable InstallDocker InstallMethod = "docker" // container; rollout handled by orchestrator InstallManaged InstallMethod = "managed" // read-only / package-managed )
func DetectInstallMethod ¶
func DetectInstallMethod(override InstallMethod) InstallMethod
DetectInstallMethod determines whether an in-place update is possible. An explicit, non-auto override is returned verbatim.
Detection order: a Docker environment is never self-updated (the orchestrator owns the image), then a standalone executable whose directory is writable is updatable as a binary, otherwise the install is treated as read-only/managed.
type LastResult ¶
type LastResult struct {
TargetTag string `json:"targetTag"`
FromVersion string `json:"fromVersion"`
Outcome string `json:"outcome"`
Reason string `json:"reason,omitempty"`
At string `json:"at"`
}
LastResult records the outcome of the most recent completed apply attempt for display in the admin UI.
type MaintenanceWindow ¶
type MaintenanceWindow struct {
StartMin int // minutes since midnight, inclusive
EndMin int // minutes since midnight, exclusive
TZ string // "utc" or "local"
}
MaintenanceWindow is a daily wall-clock window during which autonomous updates may run.
func ParseWindow ¶
func ParseWindow(start, end, tz string) (*MaintenanceWindow, bool)
ParseWindow parses "HH:MM" start/end strings and a timezone ("utc"|"local"). It returns ok=false for malformed input or a zero-length window. When end <= start the window crosses midnight, e.g. 22:00–04:00.
func (*MaintenanceWindow) InWindow ¶
func (w *MaintenanceWindow) InWindow(now time.Time) bool
InWindow reports whether now falls within the window.
func (*MaintenanceWindow) NextWindowStart ¶
func (w *MaintenanceWindow) NextWindowStart(now time.Time) time.Time
NextWindowStart returns the earliest time at or after now whose wall-clock equals the window start.
type PolicyInput ¶
type PolicyInput struct {
Tier Tier
InstallMethod InstallMethod
CurrentVersion string
Latest *ReleaseInfo
ExecutionStatus ExecutionStatus
WindowConfigured bool // whether a maintenance window was configured at all
WindowValid bool // whether the configured window parsed successfully
}
PolicyInput is the pure input to EvaluatePolicy.
type PolicyResult ¶
type PolicyResult struct {
CanNotify bool // surface availability / send notifications
CanManual bool // admin may trigger an apply
CanAuto bool // scheduler may auto-apply (after a grace window)
CanAutonomous bool // scheduler may auto-apply respecting a maintenance window
Reason string // machine-readable explanation
}
PolicyResult is the set of permitted operations under the current config.
func EvaluatePolicy ¶
func EvaluatePolicy(in PolicyInput) PolicyResult
EvaluatePolicy decides what the updater is permitted to do. It is pure and deterministic so it can be unit-tested without any I/O.
type PreflightDeps ¶
type PreflightDeps struct {
InstallMethod InstallMethod
ExePath string
LockHeld func() bool
}
PreflightDeps are the inputs to RunPreflight. All checks run before any file is touched, so a failed preflight is fully reversible.
type PreflightResult ¶
PreflightResult reports whether an apply may proceed.
func RunPreflight ¶
func RunPreflight(d PreflightDeps) PreflightResult
RunPreflight validates the environment before an apply. Cheap, reversible checks first.
type ReleaseInfo ¶
type ReleaseInfo struct {
Version string `json:"version"` // tag without a leading "v"
Tag string `json:"tag"`
Body string `json:"body"`
PublishedAt string `json:"publishedAt"`
Prerelease bool `json:"prerelease"`
HTMLURL string `json:"htmlUrl"`
}
ReleaseInfo is the subset of a GitHub release the updater tracks.
type ScheduleAction ¶
type ScheduleAction string
ScheduleAction is the outcome of DecideSchedule.
const ( ScheduleNone ScheduleAction = "none" ScheduleArm ScheduleAction = "schedule" ScheduleCancel ScheduleAction = "cancel" )
type ScheduleDecision ¶
type ScheduleDecision struct {
Action ScheduleAction
TargetTag string
ScheduledFor time.Time
}
ScheduleDecision tells the runner what to do with its timer.
func DecideSchedule ¶
func DecideSchedule(in ScheduleInput) ScheduleDecision
DecideSchedule decides whether to arm, cancel or leave the auto-apply timer.
type ScheduleInput ¶
type ScheduleInput struct {
CanAuto bool
CanAutonomous bool
Tier Tier
Latest *ReleaseInfo
Execution Execution
Now time.Time
GraceMinutes int
Window *MaintenanceWindow
}
ScheduleInput is the pure input to DecideSchedule (called after each check).
type SignatureVerifier ¶
SignatureVerifier optionally verifies the release checksums file against a trusted ed25519 public key (base64-encoded, 32 bytes). When Require is false verification is a no-op.
func (SignatureVerifier) Verify ¶
func (s SignatureVerifier) Verify(message, signature []byte) error
Verify checks an ed25519 signature over message. It returns nil when signatures are not required.
type Status ¶
type Status struct {
Tier Tier `json:"tier"`
InstallMethod InstallMethod `json:"installMethod"`
CurrentVersion string `json:"currentVersion"`
Latest *ReleaseInfo `json:"latest,omitempty"`
UpdateAvailable bool `json:"updateAvailable"`
Execution Execution `json:"execution"`
LastResult *LastResult `json:"lastResult,omitempty"`
Policy PolicyResult `json:"policy"`
}
Status is a snapshot for the admin UI.
type Tier ¶
type Tier string
Tier controls how autonomous the updater is.
const ( TierOff Tier = "off" // do nothing TierNotify Tier = "notify" // check + surface availability only TierManual Tier = "manual" // admin can trigger an apply TierAuto Tier = "auto" // scheduler auto-applies after a grace window TierAutonomous Tier = "autonomous" // scheduler auto-applies within a maintenance window )
type TriggerAction ¶
type TriggerAction string
TriggerAction is the outcome of DecideTriggerApply.
const ( TriggerFire TriggerAction = "fire" // proceed with the apply TriggerDefer TriggerAction = "defer" // re-arm for NextAt (outside maintenance window) TriggerAbort TriggerAction = "abort" // state changed; do nothing TriggerClear TriggerAction = "clear" // policy revoked auto; clear the schedule )
type TriggerDecision ¶
type TriggerDecision struct {
Action TriggerAction
NextAt time.Time
}
TriggerDecision tells the runner what to do when its timer fires.
func DecideTriggerApply ¶
func DecideTriggerApply(in TriggerInput) TriggerDecision
DecideTriggerApply decides whether a fired auto-apply timer should proceed.
type TriggerInput ¶
type TriggerInput struct {
Execution Execution
TimerTargetTag string // tag the timer was armed for
Latest *ReleaseInfo
CanAuto bool
CanAutonomous bool
Tier Tier
Window *MaintenanceWindow
Now time.Time
}
TriggerInput is the pure input to DecideTriggerApply (called on timer fire).
type UpdateState ¶
type UpdateState struct {
SchemaVersion int `json:"schemaVersion"`
LastCheckAt string `json:"lastCheckAt,omitempty"`
LastETag string `json:"lastEtag,omitempty"`
Latest *ReleaseInfo `json:"latest,omitempty"`
Execution Execution `json:"execution"`
BootCount int `json:"bootCount"`
LastResult *LastResult `json:"lastResult,omitempty"`
}
UpdateState is the full persisted state, written atomically to a JSON file.
func EmptyState ¶
func EmptyState() UpdateState
EmptyState returns the initial state for a fresh install or unreadable file.
func LoadState ¶
func LoadState(path string) UpdateState
LoadState reads and validates the persisted update state. Any problem (missing file, parse error, schema mismatch) yields a fresh EmptyState so a corrupt file can never wedge the updater.
type Updater ¶
type Updater struct {
// contains filtered or unexported fields
}
Updater orchestrates checking, scheduling, applying and verifying updates.
func New ¶
func New(cfg Config, logger *zap.SugaredLogger, broadcast func(int)) *Updater
New builds an updater. broadcast may be nil.
func (*Updater) Acknowledge ¶
Acknowledge clears the terminal rollback-failed state so automatic applies can resume once an admin has resolved the underlying problem. It returns an error if there is nothing to acknowledge.
func (*Updater) ApplyNow ¶
ApplyNow manually triggers an apply of the latest known release. It returns an error if policy forbids it or no release is known.
func (*Updater) CheckNow ¶
func (u *Updater) CheckNow()
CheckNow triggers an immediate release check in the background.
func (*Updater) CheckNowSync ¶
CheckNowSync runs a release check synchronously and returns the new status. Used by the admin UI's "check for updates" action.
func (*Updater) IsAcceptingConnections ¶
IsAcceptingConnections reports whether new pad connections should be allowed. It is false only while draining for an update.
func (*Updater) MarkBootHealthy ¶
func (u *Updater) MarkBootHealthy()
MarkBootHealthy is called once the server is up and serving. It confirms a pending update as verified and cancels the rollback timer.
func (*Updater) Start ¶
func (u *Updater) Start()
Start detects the install method, loads persisted state, resolves any pending verification from a previous restart, and begins polling.
type VersionChecker ¶
type VersionChecker struct {
HTTPClient *http.Client
Repo string // e.g. "ether/etherpad-go"
// contains filtered or unexported fields
}
VersionChecker polls the GitHub releases API with ETag-based caching.
func NewVersionChecker ¶
func NewVersionChecker(repo string) *VersionChecker
NewVersionChecker builds a checker for the given repo.
func (*VersionChecker) CheckLatest ¶
func (v *VersionChecker) CheckLatest(prevETag string) CheckResult
CheckLatest fetches the latest (non-prerelease) release, sending the previous ETag so an unchanged release returns notmodified cheaply.