update

package
v0.1.0 Latest Latest
Warning

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

Go to latest
Published: Aug 20, 2026 License: MIT Imports: 20 Imported by: 0

Documentation

Overview

Package update installs a newer dcode over the running one.

Nothing here ever runs on its own. An agent that replaces its own binary without being asked defeats every audit the rest of this program is built to support — so the passive notice never applies anything, and Apply is only reachable from an explicit `dcode update` (RN-3).

Spec: docs/specs/architecture/distribution/202608072352-*.

Index

Constants

View Source
const (
	DefaultIdentity = "https://github.com/aguinelo/dcode/.github/workflows/release.yml@refs/heads/main"
	DefaultIssuer   = "https://token.actions.githubusercontent.com"
)

DefaultIdentity is the workflow allowed to sign releases.

View Source
const DefaultAPIURL = "https://api.github.com/repos/aguinelo/dcode/releases"

DefaultAPIURL is where release metadata is read from.

View Source
const DefaultBaseURL = "https://github.com/aguinelo/dcode/releases"

DefaultBaseURL is the release origin.

View Source
const DefaultInstallerURL = "https://raw.githubusercontent.com/aguinelo/dcode/main/install.sh"

DefaultInstallerURL is the installer on the default branch. The pipeline rewrites its pinned block after publishing, so it carries the digests of the latest release — and it is in git history, which is what makes it a route independent of the release assets.

View Source
const DefaultInterval = 24 * time.Hour

DefaultInterval is the minimum gap between checks. Anything below an hour is network noise for no gain — the release cadence does not justify it.

View Source
const MinInterval = time.Hour

MinInterval is the floor a configured interval is clamped to.

View Source
const NoticeFileName = "update-check.json"

NoticeFileName is where the check result is cached.

Variables

View Source
var ErrLocalBuild = errors.New(
	"this is a local build, not a published release, so `update` would replace it " +
		"with something older. Rebuild with `make install`, or install a release " +
		"with the install script")

ErrLocalBuild is returned when the running binary was not published by the release pipeline.

Refusing is the useful answer rather than the cautious one: a build from the working tree is normally *ahead* of the last tag, so replacing it with the latest release is a downgrade wearing the word "update". And a binary we did not publish is one we cannot reason about at all.

View Source
var ErrNoVerifier = errors.New("the release signature could not be checked here")

ErrNoVerifier says the signature could not be checked on this machine.

It used to fail the update on its own, which made cosign a requirement for updating at all — a package nobody installs, demanded of a machine that already has a working dcode. What actually needs covering is a substituted release, and the digest committed to the installer covers it too. So this is now one route being unavailable rather than a verdict, and Apply refuses only when neither route held.

View Source
var SupportedPlatforms = []string{
	"darwin_amd64", "darwin_arm64",
	"linux_amd64", "linux_arm64",
}

SupportedPlatforms is the published matrix.

It is exactly the set that has a sandbox backend, and it has to be. Windows was in this list and in the release workflow, and internal/sandbox has no backend for it — so New fails closed there and the binary cannot create a session at all. Publishing an artefact that cannot run is worse than not publishing one: someone downloads it, it installs, it verifies, and then it refuses the first thing they ask, with an error about a sandbox they never chose.

The sandbox spec puts Windows out of the MVP by name. This list now says the same thing, and TestThePublishedMatrixIsExactlyWhatCanRun keeps the two from drifting apart again.

Functions

func ArtifactName

func ArtifactName(version, goos, goarch string) string

ArtifactName is the stable artifact filename. Third-party scripts depend on this format, which is why it carries the same stability promise as the binary name itself.

func CheckRuns

func CheckRuns(ctx context.Context, path, wantVersion string) error

CheckRuns executes the candidate binary and confirms it reports the version it claims to be.

This runs before the swap, which is what stops a working binary from being replaced by one that does not run on this machine — a wrong architecture, a missing library, a truncated download that still hashed correctly because it was the wrong file all along.

func ExtractBinary

func ExtractBinary(archiveName string, data []byte, dest string) error

ExtractBinary pulls the dcode binary out of a release archive.

Entry paths are checked rather than trusted: an archive is remote input, and a `../` in a member name is the oldest way there is to write outside the directory you were given.

func ParseBool

func ParseBool(s string, def bool) bool

ParseBool reads a boolean environment value, defaulting when unset or unparsable — a malformed value must not stop the program from starting.

func ParseChecksums

func ParseChecksums(data []byte) (map[string]string, error)

ParseChecksums reads a `sha256␠␠filename` list into a map keyed by filename.

The signature covers this one file rather than each artifact: one signature for the whole release, while verification stays per-artifact through the hashes it carries.

func ParseInterval

func ParseInterval(s string) time.Duration

ParseInterval reads DCODE_UPDATE_CHECK_INTERVAL, clamped to MinInterval.

func PinnedDigest added in v0.1.0

func PinnedDigest(script []byte, artifact string) string

PinnedDigest returns the SHA-256 an installer carries for one artifact, or empty when it carries none.

Only what sits between the markers counts. Reading a digest from anywhere in the file would let an unrelated line — a comment, an example, an error message — decide what gets installed.

func PlatformKey

func PlatformKey(goos, goarch string) string

PlatformKey is the map key inside a Release.

func SaveNotice

func SaveNotice(path string, n VersionNotice) error

SaveNotice writes the cache, creating the directory if needed.

func VerifySHA256

func VerifySHA256(data []byte, want string) error

VerifySHA256 compares a payload against an expected digest.

Types

type Artifact

type Artifact struct {
	URL    string `json:"url"`
	SHA256 string `json:"sha256"`
}

Artifact is one platform's build.

type Config

type Config struct {
	// APIURL and BaseURL exist for an internal mirror. Signature verification
	// is required against any origin — a mirror is not a reason to trust.
	APIURL   string
	BaseURL  string
	Channel  string
	HTTP     *http.Client
	Verifier Verifier
	// InstallerURL is where the installer that carries this release's digests
	// is read from. It is the second route to the expected SHA-256, and the
	// only one that does not travel with the artifact.
	InstallerURL string
	// TargetPath is the binary to replace. Empty means the running one.
	TargetPath string
	// Pin refuses to change version when set.
	Pin string
	// GOOS and GOARCH override the platform, for tests.
	GOOS, GOARCH string
	// AllowLocalOverwrite lets a caller replace a binary the release pipeline
	// did not publish. Only the explicit `--force` sets it: the default has to
	// be the refusal, or the guard is decoration.
	AllowLocalOverwrite bool
}

Config configures the updater.

type CosignVerifier

type CosignVerifier struct {
	// Identity and Issuer pin who may sign. Keyless OIDC signing removes the
	// long-lived private key, which would otherwise be the highest-value item
	// in the entire repository.
	Identity string
	Issuer   string
	// Path overrides the cosign binary, for tests.
	Path string
	// Look overrides binary lookup, for tests.
	Look func(string) (string, error)
}

CosignVerifier verifies with the cosign binary.

Shelling out rather than linking sigstore: the verification path is then the same one a user can run by hand to reproduce the result, and the trust root is a tool they can audit independently of this program.

func (CosignVerifier) Verify

func (v CosignVerifier) Verify(ctx context.Context, checksums, signature, certificate []byte) error

type GitHub

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

GitHub is the released-artifact updater.

func NewGitHub

func NewGitHub(cfg Config) *GitHub

NewGitHub builds the updater.

func (*GitHub) Apply

func (g *GitHub) Apply(ctx context.Context, r Release) error

Apply installs a release over the current binary.

func (*GitHub) Latest

func (g *GitHub) Latest(ctx context.Context) (Release, error)

Latest reports the newest release on the configured channel.

type Release

type Release struct {
	Version   string              `json:"version"`
	Artifacts map[string]Artifact `json:"artifacts"`
}

Release is one published version and everything needed to install it.

type Updater

type Updater interface {
	// Latest reports the most recent release. A network error is NOT fatal to
	// the passive notice (RN-4).
	Latest(ctx context.Context) (Release, error)

	// Apply downloads, VERIFIES and swaps the binary atomically (RN-5).
	Apply(ctx context.Context, r Release) error
}

Updater is the update surface. Two operations, deliberately: everything that looks is separate from the one thing that writes.

type Verifier

type Verifier interface {
	Verify(ctx context.Context, checksums, signature, certificate []byte) error
}

Verifier checks that a checksums file was signed by the project's release identity.

type VersionNotice

type VersionNotice struct {
	Current   string    `json:"current"`
	Latest    string    `json:"latest"`
	CheckedAt time.Time `json:"checked_at"`
}

VersionNotice is the cached result of a version check.

It is persisted to disk and NEVER placed in the model's context. A timestamp that changes between turns would break the append-only prefix, and the resulting cache miss would cost far more than the notice is worth (ADR-03).

func Check

func Check(ctx context.Context, u Updater, path, current string, now time.Time, interval time.Duration) VersionNotice

Check refreshes the notice at most once per interval.

A network failure is silent by contract: it is logged at debug level and the cached answer is returned unchanged. Checking for a version can never degrade the use of the tool, and it never changes an exit code (RN-4).

func LoadNotice

func LoadNotice(path string) (VersionNotice, bool)

LoadNotice reads the cached check. A missing or corrupt cache is not an error: the worst it costs is one extra request.

func (VersionNotice) Message

func (n VersionNotice) Message() string

Message is the one line a client shows. Empty when there is nothing to say.

func (VersionNotice) Outdated

func (n VersionNotice) Outdated() bool

Outdated reports whether a newer version exists.

func (VersionNotice) Stale

func (n VersionNotice) Stale(now time.Time, interval time.Duration) bool

Stale reports whether the cached check is old enough to redo.

Jump to

Keyboard shortcuts

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