Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
113 changes: 82 additions & 31 deletions .github/workflows/beta.yml
Original file line number Diff line number Diff line change
Expand Up @@ -7,12 +7,17 @@ on:
paths:
- scripts/build-windows.ps1
- scripts/build-posix.sh
- scripts/build-linux-native.sh
- scripts/stage-linux-package.sh
- scripts/build-deb.sh
- scripts/linux-distro-lab.sh
- scripts/linux-desktop-smoke.py
- scripts/release-notes.js
- .github/workflows/beta.yml
workflow_dispatch:

permissions:
contents: write
contents: read
pull-requests: read

concurrency:
Expand All @@ -23,10 +28,8 @@ jobs:
windows:
runs-on: windows-2025
permissions:
contents: write
contents: read
pull-requests: read
id-token: write
attestations: write
steps:
- uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7
with:
Expand Down Expand Up @@ -55,13 +58,6 @@ jobs:
$name = $_.Name -replace '^nowplaying-v[0-9]+\.[0-9]+\.[0-9]+-', 'nowplaying-dev-'
if ($name -ne $_.Name) { Rename-Item $_.FullName $name }
}
- name: Attest development build provenance
if: ${{ github.event.repository.visibility == 'public' }}
uses: actions/attest-build-provenance@4d101475d8b20a2381f78447822ac1eab6504dd8 # v4
with:
subject-path: |
dist/windows/*.exe
dist/windows/*.zip
- name: Upload Windows artifacts
uses: actions/upload-artifact@ea165f8d65b6e75b540449e92b4886f43607fa02 # v4
with:
Expand All @@ -74,10 +70,8 @@ jobs:
macos:
runs-on: macos-latest
permissions:
contents: write
contents: read
pull-requests: read
id-token: write
attestations: write
steps:
- uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7
with:
Expand All @@ -90,11 +84,6 @@ jobs:
- name: Build macOS ZIP
shell: bash
run: ./scripts/build-posix.sh macos
- name: Attest development build provenance
if: ${{ github.event.repository.visibility == 'public' }}
uses: actions/attest-build-provenance@4d101475d8b20a2381f78447822ac1eab6504dd8 # v4
with:
subject-path: dist/macos/*.zip
- name: Upload macOS artifacts
uses: actions/upload-artifact@ea165f8d65b6e75b540449e92b4886f43607fa02 # v4
with:
Expand All @@ -105,10 +94,8 @@ jobs:
linux:
runs-on: ubuntu-latest
permissions:
contents: write
contents: read
pull-requests: read
id-token: write
attestations: write
steps:
- uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7
with:
Expand All @@ -121,24 +108,88 @@ jobs:
- name: Build Linux tarball
shell: bash
run: ./scripts/build-posix.sh linux
- name: Attest development build provenance
if: ${{ github.event.repository.visibility == 'public' }}
uses: actions/attest-build-provenance@4d101475d8b20a2381f78447822ac1eab6504dd8 # v4
with:
subject-path: dist/linux/*.tar.gz
- name: Upload Linux artifacts
uses: actions/upload-artifact@ea165f8d65b6e75b540449e92b4886f43607fa02 # v4
with:
name: linux
path: dist/linux/*.tar.gz
retention-days: 1

linux-native:
runs-on: ubuntu-latest
timeout-minutes: 25
permissions:
contents: read
strategy:
fail-fast: false
matrix:
include:
- distro: debian
image: debian:13
- distro: fedora
image: fedora:43
- distro: arch
image: archlinux:base
steps:
- uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7
with:
persist-credentials: false
- uses: actions/setup-node@49933ea5288caeca8642d1e84afbd3f7d6820020 # v4
with:
node-version: 22
- run: npm ci --omit=dev
- run: bash scripts/build-posix.sh linux
- name: Package, install and verify native desktop app
env:
DISTRO_IMAGE: ${{ matrix.image }}
DISTRO: ${{ matrix.distro }}
run: |
mkdir -p native-packages
package_version="$(node -p 'require("./package.json").version')+dev.${GITHUB_SHA:0:7}"
docker run --rm -e DISTRO -e "PACKAGE_VERSION=$package_version" -v "$PWD:/source:ro" -v "$PWD/native-packages:/artifacts" "$DISTRO_IMAGE" bash /source/scripts/linux-distro-lab.sh
- name: Upload native development packages
uses: actions/upload-artifact@ea165f8d65b6e75b540449e92b4886f43607fa02 # v4
with:
name: linux-native-${{ matrix.distro }}
path: |
native-packages/*.deb
native-packages/*.rpm
native-packages/*.pkg.tar.zst
if-no-files-found: error
retention-days: 1

attest:
needs: [windows, macos, linux, linux-native]
if: github.ref == 'refs/heads/main' && (github.event_name == 'push' || github.event_name == 'workflow_dispatch')
runs-on: ubuntu-latest
permissions:
contents: read
id-token: write
attestations: write
steps:
- uses: actions/download-artifact@3e5f45b2cfb9172054b4087a40e8e0b5a5461e7c # v8
with:
pattern: '*'
path: dist
merge-multiple: true
- name: Attest trusted development assets
if: ${{ github.event.repository.visibility == 'public' }}
uses: actions/attest-build-provenance@4d101475d8b20a2381f78447822ac1eab6504dd8 # v4
with:
subject-path: |
dist/*.exe
dist/*.zip
dist/*.tar.gz
dist/*.deb
dist/*.rpm
dist/*.pkg.tar.zst

# One moving pre-release: the "dev" tag and its release are updated in place
# on every push to main, so the releases page shows a single development
# build instead of one per merge. It is never marked Latest.
release:
needs: [windows, macos, linux]
if: github.event_name != 'pull_request'
needs: [windows, macos, linux, linux-native, attest]
if: github.ref == 'refs/heads/main' && (github.event_name == 'push' || github.event_name == 'workflow_dispatch')
runs-on: ubuntu-latest
permissions:
contents: write
Expand All @@ -161,7 +212,7 @@ jobs:
shell: bash
run: |
cd dist
find . -maxdepth 1 -type f \( -name '*.exe' -o -name '*.zip' -o -name '*.tar.gz' \) -printf '%f\n' | sort | while IFS= read -r f; do
find . -maxdepth 1 -type f \( -name '*.exe' -o -name '*.zip' -o -name '*.tar.gz' -o -name '*.deb' -o -name '*.rpm' -o -name '*.pkg.tar.zst' \) -printf '%f\n' | sort | while IFS= read -r f; do
sha256sum "$f"
done > SHA256SUMS
cat SHA256SUMS
Expand Down Expand Up @@ -217,7 +268,7 @@ jobs:
set -euo pipefail
title="nowplaying dev build #${GITHUB_RUN_NUMBER} (${DEV_VERSION})"
remote="https://x-access-token:${GH_TOKEN}@github.com/${GITHUB_REPOSITORY}.git"
mapfile -t files < <(find dist -maxdepth 1 -type f \( -name '*.exe' -o -name '*.zip' -o -name '*.tar.gz' -o -name SHA256SUMS \) | sort)
mapfile -t files < <(find dist -maxdepth 1 -type f \( -name '*.exe' -o -name '*.zip' -o -name '*.tar.gz' -o -name '*.deb' -o -name '*.rpm' -o -name '*.pkg.tar.zst' -o -name SHA256SUMS \) | sort)
# Only a 404 means there is no dev release yet. Any other lookup error
# (rate limit, outage) used to fall through to "create", which moved the
# tag and then failed with "a release with the same tag name already
Expand Down
8 changes: 8 additions & 0 deletions docs/linux-native-packages.md
Original file line number Diff line number Diff line change
Expand Up @@ -22,3 +22,11 @@ makepkg requires an unprivileged builder. The scripts build files only; they
do not install, publish, enable autostart or cut stable releases. Versions use
letters, digits, dot, plus and dash; RPM/Arch translate dash to underscore.
This slice rejects non-x86_64 hosts and bundles rather than mislabeling them.

## Development release identity

Rolling development packages carry `+dev.<headsha>` in package metadata so
upgrades can identify the commit. The installed `nowplaying --version` reports
the base version from the bundled package.json, not that package-manager suffix.
Use package metadata and the published checksum/attestation for exact build
identity; do not infer the commit from the CLI's base version alone.
8 changes: 7 additions & 1 deletion scripts/linux-distro-lab.sh
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,7 @@ esac
useradd -m builder
cp -a /source /home/builder/source
chown -R builder:builder /home/builder/source
runuser -u builder -- bash /home/builder/source/scripts/build-linux-native.sh "$format" /home/builder/source/dist/linux/nowplaying 0.2.1+dev /home/builder/packages
runuser -u builder -- bash /home/builder/source/scripts/build-linux-native.sh "$format" /home/builder/source/dist/linux/nowplaying "${PACKAGE_VERSION:-0.2.1+dev}" /home/builder/packages
case "$format" in
deb) apt-get install -y /home/builder/packages/*.deb ;;
rpm) dnf install -y /home/builder/packages/*.rpm ;;
Expand All @@ -27,3 +27,9 @@ esac
/usr/bin/nowplaying --version
# appPaths/first-run and real GTK helper run in a private user session.
runuser -u builder -- xvfb-run -a dbus-run-session -- python3 /home/builder/source/scripts/linux-desktop-smoke.py

# Export only after package installation and desktop protocol acceptance pass.
if [[ -d /artifacts ]]; then
find /home/builder/packages -maxdepth 1 -type f \( -name '*.deb' -o -name '*.rpm' -o -name '*.pkg.tar.zst' \) -exec cp {} /artifacts/ \;
find /artifacts -maxdepth 1 -type f -exec chmod 644 {} +
fi
21 changes: 21 additions & 0 deletions test/linux-dev-release.test.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
import test from "node:test";
import assert from "node:assert/strict";
import { readFileSync } from "node:fs";

const beta = readFileSync(new URL("../.github/workflows/beta.yml", import.meta.url), "utf8");
test("rolling dev release awaits native distro packages and covers them in checksums and upload", () => {
assert.match(beta, /linux-native:/);
assert.match(beta, /needs: \[windows, macos, linux, linux-native, attest\]/);
for (const extension of ["*.deb", "*.rpm", "*.pkg.tar.zst"]) {
assert.ok(beta.split(extension).length >= 4, `${extension} must cover upload, attestation, checksums, release files`);
}
assert.match(beta, /--prerelease --latest=false/);
});

test("PR build legs have read-only grants and release/attestation require trusted main", () => {
const build = beta.slice(beta.indexOf(" windows:"), beta.indexOf(" attest:"));
assert.doesNotMatch(build, /id-token: write|attestations: write|contents: write/);
assert.doesNotMatch(build, /actions\/attest-build-provenance/);
assert.equal((beta.match(/if: github.ref == 'refs\/heads\/main' && \(github.event_name == 'push' \|\| github.event_name == 'workflow_dispatch'\)/g) ?? []).length, 2);
assert.doesNotMatch(beta, /pull_request_target/);
});
Loading