| 1 | // Package update defines the desktop auto-updater's shared types and signature |
| 2 | // verification — the single source of truth for the latest.json manifest format, |
| 3 | // the platform-asset lookup, and minisign verification. Both the CI signing tool |
| 4 | // (desktop/cmd/sign) and the running updater (desktop/updater.go) import this |
| 5 | // package, so the sign path and the verify path can never drift apart. |
| 6 | package update |
| 7 | |
| 8 | import "runtime" |
| 9 | |
| 10 | // Manifest is the latest.json published alongside a desktop release. The updater |
| 11 | // fetches it from the R2 mirror (primary) or GitHub releases (fallback), compares |
| 12 | // Version against the running build, and looks up the running platform's artifact |
| 13 | // via Asset / NativePackage. |
| 14 | type Manifest struct { |
| 15 | Version string `json:"version"` // release version, e.g. "v1.1.0" |
| 16 | Notes string `json:"notes"` // markdown release notes |
| 17 | PubDate string `json:"pub_date"` // RFC3339, optional |
| 18 | DownloadPage string `json:"download_page"` // human-facing download page (macOS manual-update fallback) |
| 19 | ReleaseNotesURL string `json:"release_notes_url,omitempty"` // exact version history page; older manifests omit it |
| 20 | Platforms map[string]Asset `json:"platforms"` // keyed by PlatformKey, e.g. "darwin-arm64" |
| 21 | NativePackages map[string]Asset `json:"native_packages,omitempty"` // optional OS package assets, e.g. linux-amd64 → .deb |
| 22 | Downloads map[string]Asset `json:"downloads,omitempty"` // optional signed human-download assets keyed by exact filename |
| 23 | } |
| 24 | |
| 25 | // Asset is one platform's downloadable artifact plus the metadata the updater |
| 26 | // needs to verify and report on it. |
| 27 | type Asset struct { |
| 28 | URL string `json:"url"` // direct download URL for the artifact |
| 29 | Sig string `json:"sig"` // URL of the detached minisign (.minisig) signature |
| 30 | Size int64 `json:"size"` // artifact size in bytes (download-progress denominator) |
| 31 | SHA256 string `json:"sha256"` // lowercase hex digest, for a second integrity check after verify |
| 32 | // InstallLayout identifies the on-disk layout the artifact expects after |
| 33 | // install. Empty means the pre-v1.20 flat layout (ignored by old clients). |
| 34 | // New clients require "versioned-v1" for self-update and reject unknown |
| 35 | // values without changing the active install. |
| 36 | InstallLayout string `json:"install_layout,omitempty"` |
| 37 | } |
| 38 | |
| 39 | // PlatformKey is the map key used in Manifest.Platforms for the given OS/arch. |
| 40 | // The updater calls CurrentPlatform; the manifest generator builds keys the same |
| 41 | // way, so lookups always agree. |
| 42 | func PlatformKey(goos, goarch string) string { return goos + "-" + goarch } |
| 43 | |
| 44 | // CurrentPlatform is PlatformKey for the running binary. |
| 45 | func CurrentPlatform() string { return PlatformKey(runtime.GOOS, runtime.GOARCH) } |
| 46 | |
| 47 | // Asset returns the portable/tarball artifact for the running platform, if listed. |
| 48 | func (m Manifest) Asset() (Asset, bool) { |
| 49 | a, ok := m.Platforms[CurrentPlatform()] |
| 50 | return a, ok |
| 51 | } |
| 52 | |
| 53 | // NativePackage returns the OS package artifact for the running platform, if listed. |
| 54 | // Older manifests omit native_packages; callers must treat absence as "no package". |
| 55 | func (m Manifest) NativePackage() (Asset, bool) { |
| 56 | if m.NativePackages == nil { |
| 57 | return Asset{}, false |
| 58 | } |
| 59 | a, ok := m.NativePackages[CurrentPlatform()] |
| 60 | return a, ok |
| 61 | } |
| 62 |