Documentation
¶
Overview ¶
Package update implements the signed, fail-closed CodeSampleX binary updater. It deliberately uses only the Go standard library so the updater does not add another executable supply-chain dependency of its own.
Index ¶
- Constants
- Variables
- func AcknowledgeActivation(home, currentVersion string) error
- func AdoptStandalone(home, executable string) error
- func AutoEnabled(mode, policy string) bool
- func BootstrapLauncher(ctx context.Context, root, staged, legacy, currentVersion string) (launcher.Active, error)
- func CompareVersions(a, b string) (int, error)
- func EmbeddedPublicKey() (ed25519.PublicKey, error)
- func InstallPath(home string) string
- func IsCanonicalReleaseVersion(v string) bool
- func LockPidAlive(pid int) bool
- func OwnsExecutable(home, executable string) (bool, error)
- func ReleaseAssetURL(base, version, osName, arch string) (string, error)
- func Rollback(home, executable string) (string, error)
- func SaveState(home string, st State) error
- func StableExecutable(home, executable string) (string, error)
- func StatePath(home string) string
- func ValidateLauncherAssetForTest(a Asset, version string) error
- func VerifyReleaseDirectory(dist, version string, pub ed25519.PublicKey, now time.Time) error
- func WithLock(home string, fn func() error) error
- type Asset
- type Client
- type Envelope
- type Install
- type Manifest
- type RehydrateOptions
- type RehydrateReport
- type Result
- type State
Constants ¶
const ( ManifestSchema = 1 DefaultChannel = "stable" DefaultManifestURL = "https://github.com/r2cuerdame/CodeSampleX/releases/latest/download/csx-update-stable.json" )
const DefaultReleaseDownloadBase = "https://github.com/r2cuerdame/CodeSampleX/releases/download"
DefaultReleaseDownloadBase is the official release download root. Every rehydrated payload comes from underneath it, and the exact path is rebuilt from the version and target rather than taken from anywhere on this machine.
const RehydrateCooldown = 5 * time.Minute
RehydrateCooldown bounds how often a failing install repeats a repair it cannot complete. An editor or MCP host restarts a broken csx dozens of times a day; without this, every one of those would pay a full network timeout on a machine that is simply offline. An explicit operator repair ignores it.
Variables ¶
var ErrPolicyDisabled = errors.New("update: automatic update policy was disabled")
var ErrRehydrateCooldown = errors.New("update: payload repair was attempted too recently")
ErrRehydrateCooldown reports that a failed repair was attempted too recently.
var ErrRehydrateNotNeeded = errors.New("update: the current payload is already verified")
ErrRehydrateNotNeeded reports that the pointer's current payload already verifies, so there was nothing to repair.
var PublicKeyBase64 string
PublicKeyBase64 is stamped into release binaries from the protected release signing secret. Development builds intentionally have no update trust root.
Functions ¶
func AcknowledgeActivation ¶
AcknowledgeActivation clears a restart notice only from a newly launched binary that matches the installed target (or the first process after a rollback). It never removes the preserved previous executable.
func AdoptStandalone ¶
func AutoEnabled ¶
func BootstrapLauncher ¶
func BootstrapLauncher(ctx context.Context, root, staged, legacy, currentVersion string) (launcher.Active, error)
BootstrapLauncher is the one-time Windows installer bridge. It verifies the signed stable manifest with the key embedded in the payload, then lets the shared launcher commit code durably promote that running staged payload.
func CompareVersions ¶
CompareVersions compares strict numeric release versions, ignoring one leading v. It rejects ranges, aliases, and prerelease text.
func EmbeddedPublicKey ¶
func InstallPath ¶
func LockPidAlive ¶ added in v0.1.94
LockPidAlive reports whether pid names a live process, conservatively: it answers "alive" whenever it cannot prove otherwise, because every caller uses it to decide whether to take somebody else's single-instance lock away, and only a positive proof of death may do that.
Exported so the daemon can stop carrying its own version. That version read os.FindProcess succeeding as proof of life on Windows, which it is not -- FindProcess fails only for an INVALID pid, never for a dead one -- so no stale daemon lock was ever cleared there. Measured 2026-09-01 on a machine whose daemon had been unable to start for two days behind a lock naming a process that had exited.
func OwnsExecutable ¶
func ReleaseAssetURL ¶ added in v0.1.63
ReleaseAssetURL rebuilds the official download URL for one release target. The version and target decide the whole path; nothing on the local machine contributes to it beyond the version string the pointer already recorded.
func StableExecutable ¶
func ValidateLauncherAssetForTest ¶ added in v0.1.91
ValidateLauncherAssetForTest exposes validateLauncherAsset so the release signer's own test can check that what it writes is what every client will accept. The two live on opposite sides of a signature, and a disagreement between them is an outage that begins at the moment of release.
func VerifyReleaseDirectory ¶ added in v0.1.145
VerifyReleaseDirectory checks the complete payload and launcher set before publication or deployment makes its stable manifest visible to installers.
Types ¶
type Asset ¶
type Asset struct {
OS string `json:"os"`
Arch string `json:"arch"`
URL string `json:"url"`
Size int64 `json:"size"`
SHA256 string `json:"sha256"`
MinLauncherVersion string `json:"minLauncherVersion,omitempty"`
// LauncherURL, LauncherSize and LauncherSHA256 name the Windows launcher
// published with this release. Optional, and absent on every manifest
// signed before they existed.
//
// They live in the SIGNED manifest rather than being read from the
// release's SHA256SUMS.txt, because a launcher is the binary that runs
// before the payload does. SHA256SUMS.txt is a file served beside the
// release and nothing vouches for it; the manifest is the one artifact
// the updater's own key covers. Taking the first thing that executes from
// an unsigned list would hand it to whoever can write the download
// surface.
LauncherURL string `json:"launcherUrl,omitempty"`
LauncherSize int64 `json:"launcherSize,omitempty"`
LauncherSHA256 string `json:"launcherSha256,omitempty"`
}
func CurrentAsset ¶
type Client ¶
type Client struct {
Home string
CurrentVersion string
Executable string
ManifestURL string
Channel string
PublicKey ed25519.PublicKey
HTTP *http.Client
Now func() time.Time
SelfTest func(context.Context, string, string) error
Replace func(string, string, string) error
ValidateTarget func(string) error
SyncDir func(string) error
LoadState func(string) (State, error)
SaveState func(string, State) error
CheckApplySupport func() error
Preflight func() error
Automatic bool
// contains filtered or unexported fields
}
type Install ¶
type Install struct {
Schema int `json:"schema"`
Kind string `json:"kind"`
ExecutablePath string `json:"executablePath"`
InstallRoot string `json:"installRoot,omitempty"`
LauncherPath string `json:"launcherPath,omitempty"`
}
func LoadInstall ¶
func ResolveInstall ¶
ResolveInstall uses the per-profile marker when present, then falls back to the stable launcher's verified environment. The latter is install-scoped, so a second CSX_HOME/profile still resolves the same trusted launcher.
type Manifest ¶
type Manifest struct {
Schema int `json:"schema"`
Channel string `json:"channel"`
Sequence uint64 `json:"sequence"`
Version string `json:"version"`
PublishedAt time.Time `json:"publishedAt"`
ExpiresAt time.Time `json:"expiresAt"`
MinUpdaterVersion string `json:"minUpdaterVersion,omitempty"`
Assets []Asset `json:"assets"`
}
func VerifyBootstrapRelease ¶ added in v0.1.145
func VerifyBootstrapRelease(stableRaw, bootstrapRaw []byte, pub ed25519.PublicKey, now time.Time, version string) (Manifest, error)
VerifyBootstrapRelease binds the installer snapshot to its signed launcher descriptor. A separate envelope preserves the stable updater schema read by older clients, which reject launcher fields as unknown JSON fields.
type RehydrateOptions ¶ added in v0.1.63
type RehydrateOptions struct {
// HTTP is a test seam. Production leaves it nil, which is also what makes
// the official-release URL check mandatory: httptest addresses are
// deliberately not trusted download hosts.
HTTP *http.Client
// BaseURL overrides DefaultReleaseDownloadBase and is honored only
// alongside an injected HTTP client.
BaseURL string
// SelfTest defaults to running `<staged> version`, which is how a payload
// that downloads and hashes correctly but cannot execute is caught before
// it is promoted.
SelfTest func(ctx context.Context, path, version string) error
// Arch defaults to runtime.GOARCH; OS is always the running one, because a
// payload for another target could never have produced this pointer.
Arch string
// Force skips the cooldown. Explicit operator repair sets it.
Force bool
// StartFailed says the caller watched this payload fail to START, not to
// hash. The launcher declares payload-start-failed repairable and routes
// it here, but the bytes verify — so without this the repair answered
// ErrRehydrateNotNeeded and the one failure meaning "these bytes are
// correct and still will not run" was the one it refused to act on. An
// install whose payload is blocked from executing stayed dead behind a
// repair path reporting nothing to do.
//
// It replaces the current payload's bytes with the official artifact and
// self-tests them, which is the only thing this path can do about a file
// that hashes and does not execute.
StartFailed bool
Now func() time.Time
}
RehydrateOptions configures one repair.
type RehydrateReport ¶ added in v0.1.63
type RehydrateReport struct {
// ExhaustedVersion is the current payload the install could not run.
ExhaustedVersion string
// Restored names the versions whose bytes were refetched and verified.
Restored []string
// AlreadyVerified names candidates that turned out to be fine on disk.
AlreadyVerified []string
// FallbackVersion is the verified previous payload the install has after
// the repair, empty when the pointer records none.
FallbackVersion string
}
RehydrateReport says what one repair did. It is returned on success and on failure, because "current came back but the fallback did not" is a different operational state from "nothing came back".
func RehydrateInstall ¶ added in v0.1.63
func RehydrateInstall(ctx context.Context, root string, opts RehydrateOptions) (RehydrateReport, error)
RehydrateInstall refetches the payloads this install's pointer already records, from the official release path they were installed from.
It is the answer to the one failure the launcher's last-known-good recovery cannot handle. That recovery works by running a payload the pointer recorded earlier and that is still on disk; Defender quarantining `csx-payload.exe` as a false positive takes files, not pointers, and on 2026-08-29 it took the current payload and the recorded fallback on the same machine. The pointer was still exactly right. Every byte it named was gone, so there was nothing left on the machine to run — including the csx that owns `csx update`.
What makes this safe is that it adopts nothing. The descriptor's SHA-256 was recorded by this install from a signed manifest when the payload was committed, and it is the only thing the refetched bytes are accepted against: a hash mismatch, a truncated transfer, a substituted file and a redirect off the release path all end the same way, with the payload path untouched. A payload directory that merely happens to sit on this disk is still never adopted, exactly as launcher.Resolve refuses to. And the active pointer is not written here at all — this restores bytes the pointer already chose, and leaves every pointer change to the verified-only paths that own it.
It restores the recorded fallback as well as current, so a repaired install gets its minimum recovery set back rather than the single payload it needs to boot: the next quarantine is then handled locally, without the network.
type Result ¶
type Result struct {
CurrentVersion string
LatestVersion string
Available bool
Applied bool
RestartRequired bool
ManualInstallRequired bool
RollbackHeld bool
PreviousPath string
NextCheck time.Time
// LauncherReplaced and LauncherError report the launcher half of the
// update, which is separate because it is best effort: the payload has
// already committed by the time it runs, and it must not be able to fail
// an update that succeeded. An error here is worth SAYING -- a machine
// silently keeping an old launcher is how a fix reached a release page
// and stopped there for 26 releases -- but never worth failing over.
LauncherReplaced bool
LauncherError string
}
type State ¶
type State struct {
Schema int `json:"schema"`
HighestSequence uint64 `json:"highestSequence,omitempty"`
HighestVersion string `json:"highestVersion,omitempty"`
LastCheck time.Time `json:"lastCheck,omitempty"`
NextCheck time.Time `json:"nextCheck,omitempty"`
ConsecutiveFailures int `json:"consecutiveFailures,omitempty"`
PendingRestart string `json:"pendingRestart,omitempty"`
PreviousPath string `json:"previousPath,omitempty"`
LastError string `json:"lastError,omitempty"`
}