fix(dist): kill the version-shadow + add build provenance (P0)
apple / swift (push) Successful in 53s
android / android (push) Failing after 2m8s
ci / web (push) Successful in 36s
ci / docs-site (push) Successful in 39s
ci / bench (push) Successful in 1m38s
ci / rust (push) Successful in 4m59s
decky / build-publish (push) Successful in 16s
docker / build-push (--build-arg FEDORA_VERSION=44, ci, ci/fedora-rpm.Dockerfile, punktfunk-fedora44-rpm) (push) Successful in 6s
docker / build-push (., web/Dockerfile, punktfunk-web) (push) Successful in 5s
docker / build-push (ci, ci/fedora-rpm.Dockerfile, punktfunk-fedora-rpm) (push) Successful in 3s
docker / build-push (ci, ci/rust-ci.Dockerfile, punktfunk-rust-ci) (push) Successful in 4s
docker / build-push (docs-site, docs-site/Dockerfile, punktfunk-docs) (push) Successful in 4s
flatpak / build-publish (push) Failing after 2s
deb / build-publish (push) Failing after 2m58s
rpm / build-publish (fedora-44, punktfunk-fedora44-rpm) (push) Has been cancelled
rpm / build-publish (bazzite, punktfunk-fedora-rpm) (push) Has been cancelled
docker / deploy-docs (push) Successful in 17s

The stale code a default install/upgrade got was a TAG LEAK: deb.yml/rpm.yml shared
`tags: ['v*']` with the Apple-client release.yml, so the v0.1.0/v0.1.1 tags cut to ship
the macOS app ALSO published host packages versioned 0.1.1 — which outranks every rolling
0.0.1~ciN / 0.0.1-0.ciN build in both registries (dpkg/rpm version compares confirm), so
`apt install`/`rpm-ostree install` silently fetched ~99-commits-stale code while the READMEs
claimed auto-tracking. Two fixes:

- Decouple host publishing from Apple `v*` tags: deb.yml/rpm.yml now trigger on `host-v*`
  only, so a client tag can never poison the host channel again.
- Bump the rolling base 0.0.1 -> 0.2.0 (deb `0.2.0~ciN`, rpm `0.2.0-0.ciN`): sits ABOVE the
  stray 0.1.1 yet BELOW a future 0.2.0 tag, and still climbs monotonically by run number — so
  `apt upgrade`/`rpm-ostree upgrade` genuinely move forward. Spec default + build scripts +
  PKGBUILD pkgver bumped to match.

Build provenance (so a stale/shadowed host is detectable): build.rs stamps PUNKTFUNK_BUILD_VERSION
(set by CI = the full package version, e.g. 0.2.0~ci120.g802e98d; falls back to the crate version
for a plain `cargo build`) into the binary via rustc-env. Surfaced in `punktfunk-host --version`,
the startup log, and the mgmt /health + /host `version` field (was a hardcoded CARGO_PKG_VERSION).
Deliberately env-driven, not git-derived — the RPM builds from a git-archive tarball with no .git.
Version computed BEFORE the build in deb.yml; the spec %build exports it from %{version}-%{release}
(and gains --locked for reproducibility parity with the .deb path). Validated: plain build reports
0.0.1, env-stamped build reports 0.2.0~ci999.gdeadbee.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
2026-06-15 10:30:21 +00:00
parent b0df291ffe
commit fe9921cc1c
9 changed files with 70 additions and 32 deletions
+20 -14
View File
@@ -13,7 +13,10 @@ name: deb
on: on:
push: push:
branches: [main] branches: [main]
tags: ['v*'] # HOST-scoped tags only. The Apple client uses `v*` (release.yml); those must NOT trigger a
# host publish — a `v0.1.1` client tag previously shipped a host package versioned 0.1.1 that
# outranked every rolling build (the version-shadow). Host releases use `host-v*`.
tags: ['host-v*']
workflow_dispatch: workflow_dispatch:
env: env:
@@ -31,6 +34,20 @@ jobs:
steps: steps:
- uses: actions/checkout@v4 - uses: actions/checkout@v4
- name: Version
# host-vX.Y.Z tag -> X.Y.Z (a real host release). A main push -> 0.2.0~ciN.g<sha>: the '~'
# sorts it BELOW the eventual 0.2.0 tag, it climbs monotonically by run number, AND it sits
# ABOVE the stray 0.1.1, so `apt upgrade` truly moves boxes forward. Computed BEFORE the
# build so it's stamped into the binary (PUNKTFUNK_BUILD_VERSION -> build.rs -> --version).
run: |
SHORT=$(echo "$GITHUB_SHA" | cut -c1-8)
case "$GITHUB_REF" in
refs/tags/host-v*) V="${GITHUB_REF_NAME#host-v}" ;;
*) V="0.2.0~ci${GITHUB_RUN_NUMBER}.g${SHORT}" ;;
esac
echo "VERSION=$V" >> "$GITHUB_ENV"
echo "package version $V"
# dpkg-shlibdeps (Depends resolution) + dpkg-deb live in dpkg-dev. The client's link # dpkg-shlibdeps (Depends resolution) + dpkg-deb live in dpkg-dev. The client's link
# deps are also baked into the rust-ci image, but this job runs against the image # deps are also baked into the rust-ci image, but this job runs against the image
# from the PREVIOUS push (docker.yml bootstrap note) — keep it green across image # from the PREVIOUS push (docker.yml bootstrap note) — keep it green across image
@@ -60,6 +77,8 @@ jobs:
restore-keys: cargo-target-v2-${{ env.rustc }}- restore-keys: cargo-target-v2-${{ env.rustc }}-
- name: Build release host + client - name: Build release host + client
env:
PUNKTFUNK_BUILD_VERSION: ${{ env.VERSION }} # stamped into the binary (build.rs)
run: | run: |
git config --global --add safe.directory "$PWD" git config --global --add safe.directory "$PWD"
cargo build --release -p punktfunk-host -p punktfunk-client-linux --locked cargo build --release -p punktfunk-host -p punktfunk-client-linux --locked
@@ -81,19 +100,6 @@ jobs:
echo "web console smoke: /login -> $code" echo "web console smoke: /login -> $code"
[ "$code" = 200 ] || { echo "ERROR: web console failed to boot under node"; exit 1; } [ "$code" = 200 ] || { echo "ERROR: web console failed to boot under node"; exit 1; }
- name: Version
# Tag v1.2.3 -> 1.2.3 (a real release); a main push -> 0.0.1~ciN.g<sha>, which sorts
# BEFORE 0.0.1 (the '~') yet monotonically increases by run number, so `apt upgrade`
# always moves the boxes to the newest main build.
run: |
SHORT=$(echo "$GITHUB_SHA" | cut -c1-8)
case "$GITHUB_REF" in
refs/tags/v*) V="${GITHUB_REF_NAME#v}" ;;
*) V="0.0.1~ci${GITHUB_RUN_NUMBER}.g${SHORT}" ;;
esac
echo "VERSION=$V" >> "$GITHUB_ENV"
echo "package version $V"
- name: Build .debs - name: Build .debs
run: | run: |
VERSION="$VERSION" bash packaging/debian/build-deb.sh VERSION="$VERSION" bash packaging/debian/build-deb.sh
+9 -6
View File
@@ -13,7 +13,9 @@ name: rpm
on: on:
push: push:
branches: [main] branches: [main]
tags: ['v*'] # HOST-scoped tags only — the Apple client's `v*` tags (release.yml) must NOT publish a host
# RPM (a `v0.1.1` client tag previously shipped a host 0.1.1 that shadowed every rolling build).
tags: ['host-v*']
workflow_dispatch: workflow_dispatch:
env: env:
@@ -65,14 +67,15 @@ jobs:
restore-keys: cargo-home- restore-keys: cargo-home-
- name: Version - name: Version
# Tag v1.2.3 -> 1.2.3-1 (release); main push -> 0.0.1-0.ciN.g<sha>, whose release "0." # host-vX.Y.Z tag -> X.Y.Z-1 (a real host release); main push -> 0.2.0-0.ciN.g<sha>, whose
# sorts BEFORE the eventual "1" yet increases by run number, so `rpm-ostree upgrade` # "0." release sorts BELOW the eventual 0.2.0-1 yet climbs by run number AND outranks the
# always moves to the newest main build. # stray 0.1.1, so `rpm-ostree upgrade` truly moves to the newest build. The spec %build
# stamps PUNKTFUNK_BUILD_VERSION from these macros into the binary (--version provenance).
run: | run: |
SHORT=$(echo "$GITHUB_SHA" | cut -c1-8) SHORT=$(echo "$GITHUB_SHA" | cut -c1-8)
case "$GITHUB_REF" in case "$GITHUB_REF" in
refs/tags/v*) V="${GITHUB_REF_NAME#v}"; R="1" ;; refs/tags/host-v*) V="${GITHUB_REF_NAME#host-v}"; R="1" ;;
*) V="0.0.1"; R="0.ci${GITHUB_RUN_NUMBER}.g${SHORT}" ;; *) V="0.2.0"; R="0.ci${GITHUB_RUN_NUMBER}.g${SHORT}" ;;
esac esac
echo "PF_VERSION=$V" >> "$GITHUB_ENV" echo "PF_VERSION=$V" >> "$GITHUB_ENV"
echo "PF_RELEASE=$R" >> "$GITHUB_ENV" echo "PF_RELEASE=$R" >> "$GITHUB_ENV"
+13
View File
@@ -6,6 +6,19 @@
//! Codec SDK, or an import lib generated from the driver's `nvEncodeAPI64.dll` //! Codec SDK, or an import lib generated from the driver's `nvEncodeAPI64.dll`
//! (`lib /def:nvenc.def /machine:x64 /out:nvencodeapi.lib` with the two exports above). //! (`lib /def:nvenc.def /machine:x64 /out:nvencodeapi.lib` with the two exports above).
fn main() { fn main() {
// Build provenance: stamp the exact package/build version into the binary so a running host
// can report what it is (mgmt /health, the startup log, `--version`) and a stale/shadowed
// install is detectable. CI (deb.yml / rpm.yml / the RPM spec / the PKGBUILD) sets
// PUNKTFUNK_BUILD_VERSION to the full package version (e.g. `0.2.0~ci120.g802e98d`); a plain
// `cargo build` falls back to the crate version. Deliberately NOT git-derived — the RPM builds
// from a `git archive` tarball with no .git, and a hard git dependency would break it.
let version = std::env::var("PUNKTFUNK_BUILD_VERSION")
.ok()
.filter(|v| !v.trim().is_empty())
.unwrap_or_else(|| std::env::var("CARGO_PKG_VERSION").unwrap_or_else(|_| "unknown".into()));
println!("cargo:rustc-env=PUNKTFUNK_VERSION={version}");
println!("cargo:rerun-if-env-changed=PUNKTFUNK_BUILD_VERSION");
if std::env::var_os("CARGO_FEATURE_NVENC").is_some() { if std::env::var_os("CARGO_FEATURE_NVENC").is_some() {
if let Some(dir) = std::env::var_os("PUNKTFUNK_NVENC_LIB_DIR") { if let Some(dir) = std::env::var_os("PUNKTFUNK_NVENC_LIB_DIR") {
println!("cargo:rustc-link-search=native={}", dir.to_string_lossy()); println!("cargo:rustc-link-search=native={}", dir.to_string_lossy());
+13 -2
View File
@@ -56,12 +56,23 @@ fn main() {
} }
fn real_main() -> Result<()> { fn real_main() -> Result<()> {
let args: Vec<String> = std::env::args().skip(1).collect();
// `--version` prints the build-stamped version (build.rs) to stdout and exits — no logging.
if matches!(
args.first().map(String::as_str),
Some("--version") | Some("-V") | Some("version")
) {
println!("punktfunk-host {}", env!("PUNKTFUNK_VERSION"));
return Ok(());
}
tracing::info!( tracing::info!(
"punktfunk-host (punktfunk_core ABI v{})", "punktfunk-host {} (punktfunk_core ABI v{})",
env!("PUNKTFUNK_VERSION"),
punktfunk_core::ABI_VERSION punktfunk_core::ABI_VERSION
); );
let args: Vec<String> = std::env::args().skip(1).collect();
match args.first().map(String::as_str) { match args.first().map(String::as_str) {
// GameStream host control plane (P1.1: mDNS + serverinfo) + management API, and (with // GameStream host control plane (P1.1: mDNS + serverinfo) + management API, and (with
// --native) the native punktfunk/1 host in the same process — the unified host. // --native) the native punktfunk/1 host in the same process — the unified host.
+2 -2
View File
@@ -571,7 +571,7 @@ fn token_eq(presented: &str, expected: &str) -> bool {
async fn get_health() -> Json<Health> { async fn get_health() -> Json<Health> {
Json(Health { Json(Health {
status: "ok".into(), status: "ok".into(),
version: env!("CARGO_PKG_VERSION").into(), version: env!("PUNKTFUNK_VERSION").into(),
abi_version: punktfunk_core::ABI_VERSION, abi_version: punktfunk_core::ABI_VERSION,
}) })
} }
@@ -593,7 +593,7 @@ async fn get_host_info(State(st): State<Arc<MgmtState>>) -> Json<HostInfo> {
hostname: h.hostname.clone(), hostname: h.hostname.clone(),
uniqueid: h.uniqueid.clone(), uniqueid: h.uniqueid.clone(),
local_ip: h.local_ip.to_string(), local_ip: h.local_ip.to_string(),
version: env!("CARGO_PKG_VERSION").into(), version: env!("PUNKTFUNK_VERSION").into(),
abi_version: punktfunk_core::ABI_VERSION, abi_version: punktfunk_core::ABI_VERSION,
app_version: APP_VERSION.into(), app_version: APP_VERSION.into(),
gfe_version: GFE_VERSION.into(), gfe_version: GFE_VERSION.into(),
+2 -1
View File
@@ -19,7 +19,7 @@ pkgbase=punktfunk
# the RPM spec's `%bcond_with web` (off by default). Set PF_WITH_WEB=1 to also build punktfunk-web # the RPM spec's `%bcond_with web` (off by default). Set PF_WITH_WEB=1 to also build punktfunk-web
# (appended to pkgname + bun to makedepends below). # (appended to pkgname + bun to makedepends below).
pkgname=('punktfunk-host' 'punktfunk-client') pkgname=('punktfunk-host' 'punktfunk-client')
pkgver=0.0.1 pkgver=0.2.0
pkgrel=1 pkgrel=1
arch=('x86_64') arch=('x86_64')
url="https://git.unom.io/unom/punktfunk" url="https://git.unom.io/unom/punktfunk"
@@ -46,6 +46,7 @@ _repo() { printf '%s' "${PF_SRCDIR:-$srcdir/punktfunk}"; }
build() { build() {
cd "$(_repo)" cd "$(_repo)"
export RUSTUP_TOOLCHAIN=stable CARGO_TARGET_DIR="$srcdir/target" export RUSTUP_TOOLCHAIN=stable CARGO_TARGET_DIR="$srcdir/target"
export PUNKTFUNK_BUILD_VERSION="${pkgver}-${pkgrel}" # stamp --version / mgmt /health (build.rs)
# The host's zero-copy FFI link-needs libcuda at build time; nvidia-utils provides it on an # The host's zero-copy FFI link-needs libcuda at build time; nvidia-utils provides it on an
# NVIDIA builder. On a GPU-less builder symlink the CUDA stub into the link path first (same # NVIDIA builder. On a GPU-less builder symlink the CUDA stub into the link path first (same
# caveat the RPM documents): ln -s "$(find / -name libcuda.so -path '*stubs*'|head -1)" /usr/lib/ # caveat the RPM documents): ln -s "$(find / -name libcuda.so -path '*stubs*'|head -1)" /usr/lib/
+1 -1
View File
@@ -26,7 +26,7 @@ cd "$ROOTDIR"
BIN="target/release/$PKG" BIN="target/release/$PKG"
if [ ! -x "$BIN" ]; then if [ ! -x "$BIN" ]; then
echo "==> building $PKG (release)" echo "==> building $PKG (release)"
cargo build --release -p "$PKG" --locked PUNKTFUNK_BUILD_VERSION="$VERSION" cargo build --release -p "$PKG" --locked # stamp --version (build.rs)
fi fi
STAGE="$(mktemp -d)" STAGE="$(mktemp -d)"
+1 -1
View File
@@ -9,7 +9,7 @@
# Output: dist/punktfunk-<version>-<release>.<arch>.rpm (+ the -debuginfo/-debugsource subpkgs) # Output: dist/punktfunk-<version>-<release>.<arch>.rpm (+ the -debuginfo/-debugsource subpkgs)
set -euo pipefail set -euo pipefail
PF_VERSION="${PF_VERSION:-0.0.1}" PF_VERSION="${PF_VERSION:-0.2.0}"
PF_RELEASE="${PF_RELEASE:-1}" PF_RELEASE="${PF_RELEASE:-1}"
# PF_WITH_WEB=1 builds the punktfunk-web subpackage too (needs `bun` on PATH — present in the CI # PF_WITH_WEB=1 builds the punktfunk-web subpackage too (needs `bun` on PATH — present in the CI
# builder image, not in a plain mock chroot). Default off so a bare `rpmbuild`/COPR still works. # builder image, not in a plain mock chroot). Default off so a bare `rpmbuild`/COPR still works.
+9 -5
View File
@@ -18,10 +18,11 @@
Name: punktfunk Name: punktfunk
# Version/Release are overridable so CI can stamp a rolling snapshot: a main build passes # Version/Release are overridable so CI can stamp a rolling snapshot: a main build passes
# --define "pf_version 0.0.1" --define "pf_release 0.ci42.gdeadbee" # --define "pf_version 0.2.0" --define "pf_release 0.ci42.gdeadbee"
# (Release starting "0." sorts BEFORE the eventual "1" release), a v* tag passes the clean # (Release starting "0." sorts BEFORE the eventual "1" release; base 0.2.0 sits ABOVE the stray
# version with "pf_release 1". A plain `rpmbuild` (or COPR) with no defines builds 0.0.1-1. # 0.1.1), a host-v* tag passes the clean version with "pf_release 1". A plain `rpmbuild` (or COPR)
Version: %{?pf_version}%{!?pf_version:0.0.1} # with no defines builds 0.2.0-1.
Version: %{?pf_version}%{!?pf_version:0.2.0}
Release: %{?pf_release}%{!?pf_release:1}%{?dist} Release: %{?pf_release}%{!?pf_release:1}%{?dist}
Summary: Low-latency desktop/game streaming host (Moonlight-compatible + punktfunk/1) Summary: Low-latency desktop/game streaming host (Moonlight-compatible + punktfunk/1)
@@ -149,7 +150,10 @@ editing. Enable with `systemctl --user enable --now punktfunk-web`.
# Release build of the host + client binaries (the workspace also has the core lib). # Release build of the host + client binaries (the workspace also has the core lib).
# cargo fetches crates over the network; COPR build hosts allow this. # cargo fetches crates over the network; COPR build hosts allow this.
export RUSTFLAGS="%{?build_rustflags}" export RUSTFLAGS="%{?build_rustflags}"
cargo build --release -p punktfunk-host -p punktfunk-client-linux # Stamp the exact NVR into the binary for --version / mgmt /health provenance (build.rs reads it).
export PUNKTFUNK_BUILD_VERSION="%{version}-%{release}"
# --locked: reproducible from (commit + Cargo.lock), matching the .deb build path.
cargo build --release --locked -p punktfunk-host -p punktfunk-client-linux
%if %{with web} %if %{with web}
# Management web console: build the Nitro/Node SSR bundle (node-server preset) with bun. The # Management web console: build the Nitro/Node SSR bundle (node-server preset) with bun. The