From 7a2e805aa017d6657cf3df68fb81f9ddc9373fbd Mon Sep 17 00:00:00 2001 From: jrtxio Date: Thu, 15 Oct 2026 15:14:20 +0800 Subject: [PATCH] distribution: centralize release target identity --- CHANGELOG.md | 4 ++ rivet-cli/release-layout.rkt | 12 ++--- rivet-cli/release.rkt | 15 +------ rivet/distribution.rkt | 2 + rivet/distribution/identity.rkt | 59 +++++++++++++++++++++++++ rivet/distribution/managed-updater.rkt | 19 ++++---- rivet/distribution/platform-adapter.rkt | 10 +---- rivet/scribblings/rivet.scrbl | 27 +++++++++++ rivet/tests/distribution.rkt | 18 ++++++++ rivet/tests/public-api.rkt | 10 +++-- 10 files changed, 134 insertions(+), 42 deletions(-) create mode 100644 rivet/distribution/identity.rkt diff --git a/CHANGELOG.md b/CHANGELOG.md index 740e0ec7..89250148 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -2,6 +2,10 @@ ## Unreleased +- Add leaf-loadable distribution identity helpers for the canonical manifest + platform, architecture, installer-extension, and UTC RFC 3339 vocabulary. + Release tooling and the managed updater now consume the same implementation + instead of maintaining private mappings. - Add the Windows device-gateway transport. A CNG-persisted P-256 self-signed identity (rotatable by deletion), an SChannel TLS server over any accepted socket with the `rivet-device/1` ALPN requirement, the diff --git a/rivet-cli/release-layout.rkt b/rivet-cli/release-layout.rkt index d55c77cd..fcf05d11 100644 --- a/rivet-cli/release-layout.rkt +++ b/rivet-cli/release-layout.rkt @@ -1,21 +1,17 @@ #lang racket/base -(require "project.rkt") +(require "../rivet/distribution/identity.rkt" + "project.rkt") (provide release-platform release-architecture portable-zip-path) (define (release-platform) - (case (system-type 'os) - [(windows) 'windows] - [(macosx) 'macos] - [(unix) 'linux])) + (platform-symbol)) (define (release-architecture) - (case (system-type 'arch) - [(aarch64 arm64) 'arm64] - [else 'x64])) + (architecture-symbol)) (define (portable-zip-path project) (project-path diff --git a/rivet-cli/release.rkt b/rivet-cli/release.rkt index f101fb9e..88839f9c 100644 --- a/rivet-cli/release.rkt +++ b/rivet-cli/release.rkt @@ -1,12 +1,11 @@ #lang racket/base -(require racket/date - file/zip +(require file/zip racket/file - racket/format racket/path racket/string "../rivet/distribution/crypto.rkt" + "../rivet/distribution/identity.rkt" "../rivet/distribution/manifest.rkt" "appimage.rkt" "compliance.rkt" @@ -37,16 +36,6 @@ (string->path (required-environment "RIVET_UPDATE_PRIVATE_KEY")) (required-environment "RIVET_UPDATE_KEY_ID")))) -(define (rfc3339-now) - (define d (seconds->date (current-seconds) #t)) - (format "~a-~a-~aT~a:~a:~aZ" - (~r (date-year d) #:min-width 4 #:pad-string "0") - (~r (date-month d) #:min-width 2 #:pad-string "0") - (~r (date-day d) #:min-width 2 #:pad-string "0") - (~r (date-hour d) #:min-width 2 #:pad-string "0") - (~r (date-minute d) #:min-width 2 #:pad-string "0") - (~r (date-second d) #:min-width 2 #:pad-string "0"))) - ;; Portable zip beside the installer: the family update feed consumes ;; it and it runs on locked-down machines. Deterministic input tree, ;; family naming ---.zip, sha256 alongside. diff --git a/rivet/distribution.rkt b/rivet/distribution.rkt index f818f840..c23bde2a 100644 --- a/rivet/distribution.rkt +++ b/rivet/distribution.rkt @@ -1,6 +1,7 @@ #lang racket/base (require "distribution/crypto.rkt" + "distribution/identity.rkt" "distribution/managed-updater.rkt" "distribution/manifest.rkt" "distribution/platform-adapter.rkt" @@ -8,6 +9,7 @@ "distribution/version.rkt") (provide (all-from-out "distribution/crypto.rkt" + "distribution/identity.rkt" "distribution/managed-updater.rkt" "distribution/manifest.rkt" "distribution/platform-adapter.rkt" diff --git a/rivet/distribution/identity.rkt b/rivet/distribution/identity.rkt new file mode 100644 index 00000000..8987eda4 --- /dev/null +++ b/rivet/distribution/identity.rkt @@ -0,0 +1,59 @@ +#lang racket/base + +;; Release-target vocabulary shared by the publisher and update clients. Keep +;; this module leaf-loadable: callers that only report platform identity must +;; not initialize crypto factories or native update adapters. + +(require racket/date + racket/format) + +(provide platform-symbol + architecture-symbol + installer-extension + rfc3339-now) + +(define (platform-symbol [os (system-type 'os)]) + (case os + [(windows) 'windows] + [(macosx) 'macos] + [(unix) 'linux] + [else + (raise-arguments-error 'platform-symbol + "unsupported operating system" + "system-type" os)])) + +(define (architecture-symbol [machine (system-type 'arch)]) + (case machine + [(x86_64 amd64) 'x64] + [(aarch64 arm64) 'arm64] + [else + (raise-arguments-error 'architecture-symbol + "unsupported architecture" + "system-type" machine)])) + +(define (installer-extension installer) + (case installer + [(zip) ".zip"] + [(targz) ".tar.gz"] + [(msi) ".msi"] + [(msix) ".msix"] + [(exe) ".exe"] + [(dmg) ".dmg"] + [(pkg) ".pkg"] + [(deb) ".deb"] + [(rpm) ".rpm"] + [(appimage) ".AppImage"] + [else + (raise-arguments-error 'installer-extension + "unsupported installer kind" + "installer" installer)])) + +(define (rfc3339-now [seconds (current-seconds)]) + (define d (seconds->date seconds #f)) + (format "~a-~a-~aT~a:~a:~aZ" + (~r (date-year d) #:min-width 4 #:pad-string "0") + (~r (date-month d) #:min-width 2 #:pad-string "0") + (~r (date-day d) #:min-width 2 #:pad-string "0") + (~r (date-hour d) #:min-width 2 #:pad-string "0") + (~r (date-minute d) #:min-width 2 #:pad-string "0") + (~r (date-second d) #:min-width 2 #:pad-string "0"))) diff --git a/rivet/distribution/managed-updater.rkt b/rivet/distribution/managed-updater.rkt index 76eb4246..0aed9605 100644 --- a/rivet/distribution/managed-updater.rkt +++ b/rivet/distribution/managed-updater.rkt @@ -10,6 +10,7 @@ racket/path racket/string "crypto.rkt" + "identity.rkt" "manifest.rkt" "platform-adapter.rkt" "updater.rkt" @@ -37,13 +38,13 @@ (define current-managed-download (make-parameter download-update)) (define (update-architecture-for machine) - (case machine - [(x86_64 amd64) 'x64] - [(aarch64 arm64) 'arm64] - [else - (raise-arguments-error 'current-update-architecture - "unsupported update architecture" - "architecture" machine)])) + (with-handlers ([exn:fail:contract? + (lambda (_) + (raise-arguments-error + 'current-update-architecture + "unsupported update architecture" + "architecture" machine))]) + (architecture-symbol machine))) (define (current-update-architecture) (update-architecture-for (system-type 'arch))) @@ -70,12 +71,12 @@ (build-path data-directory "updates" - (format "~a-~a-~a-~a.~a" + (format "~a-~a-~a-~a~a" (safe-component application-id) (update-manifest-version manifest) (update-artifact-platform artifact) (update-artifact-architecture artifact) - (update-artifact-installer artifact)))) + (installer-extension (update-artifact-installer artifact))))) (define (initial-state current-version) (hasheq 'phase "idle" diff --git a/rivet/distribution/platform-adapter.rkt b/rivet/distribution/platform-adapter.rkt index 30a2ef8a..1f9aec9f 100644 --- a/rivet/distribution/platform-adapter.rkt +++ b/rivet/distribution/platform-adapter.rkt @@ -10,6 +10,7 @@ racket/path racket/string racket/system + "identity.rkt" "manifest.rkt" "updater.rkt") @@ -25,14 +26,7 @@ (struct platform-installation (plan health-check commit transaction-lock)) (define (current-update-platform) - (case (system-type 'os) - [(windows) 'windows] - [(macosx) 'macos] - [(unix) 'linux] - [else - (raise-arguments-error 'current-update-platform - "unsupported operating system" - "system-type" (system-type 'os))])) + (platform-symbol)) (define (detect-install-kind platform executable appimage) (cond diff --git a/rivet/scribblings/rivet.scrbl b/rivet/scribblings/rivet.scrbl index aae92519..f67a3972 100644 --- a/rivet/scribblings/rivet.scrbl +++ b/rivet/scribblings/rivet.scrbl @@ -614,6 +614,33 @@ Reports SemVer precedence; equivalent to a negative @defproc[(channel-accepts-version? [channel symbol?] [version string?]) boolean?]{ Checks whether a version is allowed by a release channel.} +@subsection{Release Target Metadata} + +These helpers live in the leaf module @racketmodname[rivet/distribution/identity] +and are also re-exported by @racketmodname[rivet/distribution]. Requiring the +leaf module does not initialize cryptographic factories or platform adapters. + +@defproc[(platform-symbol [os symbol? (system-type 'os)]) + (or/c 'windows 'macos 'linux)]{ +Maps Racket's @racket['windows], @racket['macosx], and @racket['unix] +operating-system symbols to the exact platform vocabulary used by release +manifests. Unsupported values fail instead of being classified as Linux.} + +@defproc[(architecture-symbol [machine symbol? (system-type 'arch)]) + (or/c 'x64 'arm64)]{ +Maps @racket['x86_64]/@racket['amd64] and +@racket['aarch64]/@racket['arm64] to the manifest architecture vocabulary. +Unsupported architectures fail instead of being classified as x64.} + +@defproc[(installer-extension [installer symbol?]) string?]{ +Returns the canonical filename extension for @racket['zip], @racket['targz], +@racket['msi], @racket['msix], @racket['exe], @racket['dmg], @racket['pkg], +@racket['deb], @racket['rpm], or @racket['appimage].} + +@defproc[(rfc3339-now [seconds real? (current-seconds)]) string?]{ +Formats @racket[seconds] in UTC at second precision, using the RFC 3339 form +stored in a manifest's @racket[published-at] field.} + @subsection{Signed Manifests} @racket[update-artifact] values describe one platform artifact using the fields diff --git a/rivet/tests/distribution.rkt b/rivet/tests/distribution.rkt index 6ff5b5f9..48ee16cf 100644 --- a/rivet/tests/distribution.rkt +++ b/rivet/tests/distribution.rkt @@ -24,6 +24,24 @@ (check-true (channel-accepts-version? 'beta "1.0.0-beta.1")) (check-true (channel-accepts-version? 'dev "1.0.0-nightly.5")) +(check-equal? (platform-symbol 'windows) 'windows) +(check-equal? (platform-symbol 'macosx) 'macos) +(check-equal? (platform-symbol 'unix) 'linux) +(check-exn #rx"unsupported operating system" + (lambda () (platform-symbol 'plan9))) +(check-equal? (architecture-symbol 'x86_64) 'x64) +(check-equal? (architecture-symbol 'amd64) 'x64) +(check-equal? (architecture-symbol 'aarch64) 'arm64) +(check-equal? (architecture-symbol 'arm64) 'arm64) +(check-exn #rx"unsupported architecture" + (lambda () (architecture-symbol 'riscv64))) +(check-equal? (installer-extension 'zip) ".zip") +(check-equal? (installer-extension 'targz) ".tar.gz") +(check-equal? (installer-extension 'appimage) ".AppImage") +(check-exn #rx"unsupported installer kind" + (lambda () (installer-extension 'unknown))) +(check-equal? (rfc3339-now 0) "1970-01-01T00:00:00Z") + (define ed25519-impl (get-pk 'eddsa (list libcrypto-factory sodium-factory decaf-factory))) (define private-key diff --git a/rivet/tests/public-api.rkt b/rivet/tests/public-api.rkt index ff1d18e6..e91750b1 100644 --- a/rivet/tests/public-api.rkt +++ b/rivet/tests/public-api.rkt @@ -60,17 +60,17 @@ system-adapter-set-tray-menu system-adapter-show-notification system-adapter? system-capabilities)) (rivet/distribution - (base64-string->bytes bytes->base64-string bytes->ed25519-public-key + (architecture-symbol base64-string->bytes bytes->base64-string bytes->ed25519-public-key channel-accepts-version? current-install-kind current-update-platform current-update-architecture download-update ed25519-sign ed25519-verify execute-install-plan! execute-platform-installation! fetch-update-manifest install-plan install-plan-backup-path install-plan-candidate install-plan-downloaded-path install-plan-install install-plan-restart - install-plan-rollback install-plan? make-install-plan + install-plan-rollback install-plan? installer-extension make-install-plan manifest->payload-bytes manifest-schema-version payload-bytes->manifest make-updater platform-installation? platform-installer-policy - prepare-platform-installation read-ed25519-private-key + platform-symbol prepare-platform-installation read-ed25519-private-key read-ed25519-public-key read-signed-manifest recover-install-plan! recover-platform-installation! select-update sha256-bytes sha256-file sha256-file/hex struct:install-plan @@ -91,10 +91,12 @@ updater-config-current-version updater-config-expected-key-id updater-config-maximum-download-bytes updater-config-platform updater-config-public-key updater-config-rollout-bucket updater-config? - updater-wait updater? + updater-wait updater? rfc3339-now valid-channel? verify-platform-payload! verify-signed-manifest verify-update-artifact! version-compare version<=? version=? version>? version? write-signed-manifest)) + (rivet/distribution/identity + (architecture-symbol installer-extension platform-symbol rfc3339-now)) (rivet/testing (ephemeral-http-listener fake-request fake-request-body fake-request-headers fake-request-method fake-request-path fake-request?