Skip to content

release

release #23

Workflow file for this run

name: release
# Builds the end-user installer and attaches it to a GitHub release.
#
# Only the INSTALLER is built here. The device binaries it embeds (cinder-home, cinder-probe, the
# setuid helpers, the .UPG) are committed under cinder-home/dist/ because building them needs a
# glibc-2.23 + libc++-3.9.0 cross toolchain matched to the player's own runtime — see
# cinder-home/build.sh. Reproducing that on a hosted runner would be a lot of machinery to
# maintain for no gain, so the ARM side is built by a maintainer and committed.
#
# That means: BUILD AND COMMIT dist/ BEFORE TAGGING, or the release ships whatever was last
# committed. The installer's build.rs fails loudly if a payload file is missing, so a stale or
# incomplete dist/ breaks the build here rather than shipping a broken .exe.
on:
push:
tags: ["v*"]
workflow_dispatch:
inputs:
channel:
description: "Build channel to embed"
required: false
default: stable
type: choice
options: [stable, dev]
permissions:
contents: write
env:
CINDER_CHANNEL: ${{ github.event.inputs.channel || 'stable' }}
jobs:
build:
runs-on: windows-latest
steps:
- uses: actions/checkout@v7
# ── the payload must be the one tools/release.sh VERIFIED ────────────────────────────────
#
# This used to be an existence check, and that was the hole (docs/SHORTCOMINGS.md item 4 /
# D4: the integrity guard is "real, correct, and opt-in"). tools/release.sh rebuilds the
# stable channel from source and refuses to tag unless every committed byte matches — but
# `git tag && git push --tags` reaches this workflow directly without it, and a stale dist/
# then ships as an installer full of last week's binaries, green tick and all.
#
# The runner cannot repeat that comparison — it needs the glibc-2.23 cross toolchain, which
# is the whole reason dist/ is committed rather than built here. So release.sh leaves a
# manifest of what it verified and this checks the manifest still describes the tree AND was
# written for THIS tag; a tag cut without the script finds one naming the previous version.
#
# Via a SCRIPT, deliberately: the same rule the shellcheck gate is under. A check a
# contributor cannot run locally is one that fails for the first time during a release.
# The tag argument is passed ONLY on a tag push. On a manual workflow_dispatch GITHUB_REF_NAME
# is a BRANCH name, which can never equal the tag the manifest records — so this step failed
# every dispatch run, and the dispatch path exists precisely to build a test .exe without
# cutting a release. With no argument the script still verifies every payload hash; it only
# skips the "verified for THIS tag" assertion, which is meaningless off a tag.
- name: Payload matches the verified manifest
shell: bash
run: |
if [ "${GITHUB_REF_TYPE}" = tag ]; then
tools/verify_payload_manifest.sh "${GITHUB_REF_NAME}"
else
echo "manual run — checking payload hashes, skipping the tag assertion"
tools/verify_payload_manifest.sh
fi
- name: Install Rust
uses: dtolnay/rust-toolchain@stable
- name: Test
working-directory: installer
run: cargo test
- name: Build
working-directory: installer
run: cargo build --release
- name: Stage artifact
working-directory: installer
run: |
Copy-Item target/release/cinder-installer.exe cinder-installer-windows-x64.exe
Get-ChildItem cinder-installer-windows-x64.exe
- uses: actions/upload-artifact@v7
with:
name: cinder-installer-windows-x64.exe
path: installer/cinder-installer-windows-x64.exe
if-no-files-found: error
# The Linux build sends the same vendor SCSI command the Windows build does (SG_IO rather than
# SCSI pass-through), so both finish the install; it needs root, and says so when it does not
# have it (installer/src/console.rs, print_trigger_failed).
#
# It shipped in v0.1.2 and then quietly stopped: the v0.1.3 rework moved to the Sony-updater
# handoff and dropped this job, while the README went on promising a Linux build for six weeks.
# It is back rather than the promise being withdrawn, because ci.yml has been building AND
# testing it on every push the whole time — the only things ever missing were publishing it and
# an honest exit code.
build-linux:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v7
- uses: dtolnay/rust-toolchain@stable
- name: Test
working-directory: installer
run: cargo test --release
- name: Build
working-directory: installer
run: cargo build --release
- name: Stage artifact
working-directory: installer
run: |
cp target/release/cinder-installer cinder-installer-linux-x64
ls -l cinder-installer-linux-x64
- uses: actions/upload-artifact@v7
with:
name: cinder-installer-linux-x64
path: installer/cinder-installer-linux-x64
if-no-files-found: error
release:
needs: [build, build-linux]
runs-on: ubuntu-latest
if: startsWith(github.ref, 'refs/tags/')
# Job-level permissions REPLACE the workflow's, so contents: write is restated. id-token lets
# the job ask GitHub's OIDC issuer for the short-lived identity Sigstore signs with;
# attestations lets it store the result against this repository.
permissions:
contents: write
id-token: write
attestations: write
steps:
- uses: actions/checkout@v7
- uses: actions/download-artifact@v8
with:
merge-multiple: true
path: dist
- name: List what is being published, with checksums
run: |
cp cinder-home/dist/stable/cinder_home_install.upg dist/cinder-home-install.upg
cp cinder-home/dist/stable/cinder_home_uninstall.upg dist/cinder-home-uninstall.upg
ls -lR dist
# A published binary nobody can verify is a binary nobody should run.
( cd dist && sha256sum -- * | tee SHA256SUMS )
# BUILD PROVENANCE (docs/SHORTCOMINGS.md D7). SHA256SUMS proves a download matches what this
# job published; it cannot prove this workflow, from this commit, produced it — anyone able to
# replace a file could replace the sums beside it. An attestation is signed through Sigstore
# with the run's own OIDC identity (repository, workflow file, commit), so it can be checked
# with `gh attestation verify <file> -R superwilso/Cinder` and cannot be minted outside a run.
#
# One step over SHA256SUMS rather than one per build job: every published file, the two
# .UPGs included, is covered by the same list the release body inlines. It says nothing about
# where the committed ARM payload came from — that is D4's manifest, checked above.
- name: Attest build provenance for every published file
uses: actions/attest-build-provenance@v4
with:
subject-checksums: dist/SHA256SUMS
# The sums are INLINED INTO THE RELEASE PAGE, not merely attached. Verifying a download
# against a checksum file fetched from beside it is close to circular: whoever could swap
# one could swap the other. The release body is the rendered record of what the workflow
# built, so that is where the hashes belong. `SHA256SUMS` stays attached as a copy for
# `sha256sum -c`.
#
# tools/render_release_notes.sh is the same renderer this step runs, so the body can be
# previewed locally instead of being discovered after publication — the rule this repo
# keeps relearning (see tools/shell_check.sh).
- name: Compose the release notes, with the checksums inlined
run: tools/render_release_notes.sh dist/SHA256SUMS RELEASE_BODY.md
- uses: softprops/action-gh-release@v3
with:
files: dist/*
# NOT a draft. It was, and that was the bug: the workflow ran, built the installer,
# attached them — and left the release invisible until someone opened GitHub and pressed
# Publish. "Automated" has to mean the artefacts are actually reachable at the end.
# A tag with a suffix (v1.2.3-rc1) still ships, but marked pre-release.
draft: false
prerelease: ${{ contains(github.ref_name, '-') }}
generate_release_notes: true
fail_on_unmatched_files: true
body_path: RELEASE_BODY.md