android / android (push) Canceled after 36s
apple / swift (push) Canceled after 0s
apple / screenshots (push) Canceled after 0s
arch / build-publish (push) Canceled after 37s
ci / rust (push) Canceled after 37s
ci / rust-arm64 (push) Canceled after 36s
ci / web (push) Canceled after 34s
ci / docs-site (push) Canceled after 34s
deb / build-publish (push) Canceled after 19s
deb / build-publish-host (push) Canceled after 0s
deb / build-publish-client-arm64 (push) Canceled after 0s
docker / builders (--build-arg FEDORA_VERSION=44, ci/fedora-rpm.Dockerfile, punktfunk-fedora44-rpm, -f44) (push) Canceled after 0s
docker / builders (ci/android-ci.Dockerfile, punktfunk-android-ci) (push) Canceled after 0s
docker / builders (ci/arch-ci.Dockerfile, punktfunk-arch-ci) (push) Canceled after 0s
docker / builders (ci/fedora-rpm.Dockerfile, punktfunk-fedora-rpm) (push) Canceled after 0s
docker / builders (ci/rust-ci-noble.Dockerfile, punktfunk-rust-ci-noble) (push) Canceled after 0s
docker / builders (ci/rust-ci.Dockerfile, punktfunk-rust-ci) (push) Canceled after 0s
docker / builders-arm64cross (push) Canceled after 0s
docker / apps (., web/Dockerfile, punktfunk-web) (push) Canceled after 0s
docker / apps (docs-site, docs-site/Dockerfile, punktfunk-docs) (push) Canceled after 0s
docker / deploy-docs (push) Canceled after 0s
windows-host / canary-manifest (push) Canceled after 0s
rpm / build-publish (43, bazzite, punktfunk-fedora-rpm) (push) Canceled after 0s
rpm / build-publish (44, fedora-44, punktfunk-fedora44-rpm) (push) Canceled after 0s
windows-host / package (push) Canceled after 33s
windows-host / winget-source (push) Canceled after 0s
flatpak / build-publish (push) Successful in 5m43s
windows-msix / package (arm64, C:\Users\Public\ffmpeg-arm64, --no-default-features, aarch64-pc-windows-msvc, C:\t-a64) (push) Successful in 6m59s
windows-msix / package (x64, C:\Users\Public\ffmpeg, , x86_64-pc-windows-msvc, C:\t) (push) Successful in 4m9s
windows / build (aarch64-pc-windows-msvc) (push) Canceled after 2m19s
windows / build (x86_64-pc-windows-msvc) (push) Canceled after 0s
A channel nobody has published to answers `manifest.json` with a 404, and the check reported that the same way it reports a dead registry or a bad signature: "Last check failed: feed returned HTTP 404". Every host on the stable channel shows it today, because the stable manifest only publishes when someone dispatches `announce` for a release tag — so the first thing an operator sees from the new Updates card is a red failure caused by nothing being wrong. The shared checker now distinguishes the two. `feed::fetch_manifest_blocking` returns a typed `FeedError` instead of a string, and only a 404 on the manifest ITSELF becomes `NotPublished` — a 404 on the detached signature still fails loudly, because that is the half-published pair the manifest-then-signature upload order can produce, and it must stay fail-closed. The host carries that through as `UpdateStatus.not_published`, mutually exclusive with `last_error`. It is benign only while no manifest has ever been seen for the channel: once a check has succeeded, the same 404 means the feed LOST a document it used to serve, which stays an error. The console then shows a plain sentence naming the channel instead of the failure banner, and "None published yet" rather than "Not checked yet". The Linux client makes the same distinction but deliberately NOT the same choice: `--check-update` keeps exiting 1 and keeps `error` set, because its consumer is a shell script and an empty channel is the absence of evidence that this build is current — not a confirmation that it is. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
42 lines
2.1 KiB
Rust
42 lines
2.1 KiB
Rust
//! Shared update-**check** core for the punktfunk host and the Linux client.
|
|
//!
|
|
//! Both products answer the same question — *does a newer build exist for this box's
|
|
//! channel?* — from the same Ed25519-signed per-channel manifest. This crate owns the part
|
|
//! where being wrong is a security bug, so that it exists exactly once:
|
|
//!
|
|
//! * [`sig`] — detached Ed25519 verification against pinned keys.
|
|
//! * [`manifest`] — the signed document's schema and its fail-closed validation rules.
|
|
//! * [`feed`] — fetching the document (and verifying over the *final*, post-redirect bytes).
|
|
//! * [`version`] — channels, and the comparison that decides "newer" across packaging formats.
|
|
//! * [`detect`] — the install-kind ladder, parameterised by [`detect::Product`].
|
|
//!
|
|
//! There is deliberately **no apply code here**. Applying an update is privileged, per-product
|
|
//! and per-platform; it lives with the product that does it (`punktfunk-host::update`,
|
|
//! `pf-client-core::update`, and the root helper in `pf-update`).
|
|
|
|
/// The Ed25519 public keys trusted for update manifests — two slots, so a key rotation is
|
|
/// "sign with the new one, ship builds trusting both, retire the old" (the plugin-store
|
|
/// `OFFICIAL_KEYS` drill) rather than a flag day. The private half is the
|
|
/// `UPDATE_MANIFEST_KEY` CI secret and the operator's offline backup.
|
|
///
|
|
/// It lives here, not in either binary, because the host and the client consume the SAME
|
|
/// signed manifest: two pin lists could disagree about who may announce a release, and the
|
|
/// one that drifted would be the one nobody noticed. `scripts/ci/publish-update-manifest.sh`
|
|
/// cross-checks the signing key against this file before it signs anything.
|
|
pub const OFFICIAL_UPDATE_KEYS: [&str; 2] = [
|
|
"ed25519:6rmlLg1aQ55cgB6icpC5BEpbMJxwPKdGaDQtDcJ0yLI=",
|
|
"", // rotation slot
|
|
];
|
|
|
|
pub mod detect;
|
|
pub mod feed;
|
|
pub mod manifest;
|
|
pub mod sig;
|
|
pub mod version;
|
|
|
|
pub use detect::{InstallKind, Product};
|
|
pub use feed::FeedError;
|
|
pub use manifest::{Manifest, MAX_MANIFEST_BYTES, SCHEMA};
|
|
pub use sig::{verify_signature, PublicKey};
|
|
pub use version::{canary_run, is_newer, triple, Channel};
|