Files
Joseph MattielloandClaude Fable 5.1 313a18d70b ci(release): attest, sign and gate every release asset (#1151, #1152)
One `stage` job in release.yml now flattens the artifacts, writes
SHA256SUMS.txt, signs it with the project minisign key
(SHA256SUMS.txt.minisig; public key docs/release-signing.pub once the
maintainer generates it) and attests build provenance for every file with
actions/attest-build-provenance.  A `verify-signatures` gate on a macOS
runner then checks each asset against its expected signing state
(scripts/release/verify.sh: macOS Developer ID + notarized, Windows
`unsigned` until #1150 flips one line, AppImage and checksum signatures once
their public keys are committed, attestation on everything), strict on tags
and release/**, warn-only on develop so an outage cannot withhold the core
nightly.  Its per-artifact table (signed, notarized, attested) replaces the
signing prose in the release body and the job summary.  The macOS and
AppImage signing steps write signing-status sidecars for the facts the gate
cannot re-derive from a file (Apple's ticket on a bare Mach-O).

AppImages: make-appimage.sh passes --sign --sign-key to appimagetool when
VJ_APPIMAGE_SIGN_KEY is set (app-package.yml imports APPIMAGE_GPG_PRIVATE_KEY
into a throwaway GNUPGHOME; the passphrase goes through
APPIMAGETOOL_SIGN_PASSPHRASE).  appimagetool 1.9 has no verifier, so
app/packaging/linux/verify-appimage.py re-hashes the image with the
signature sections zeroed and runs gpg --verify.

Installers: tools/install.sh and install.ps1 verify SHA256SUMS.txt's
minisign signature (pinned key) or, with gh, its attestation before
trusting it; a bad one aborts, a release that predates signing warns;
--no-verify / -NoVerify skips the step.  Both hermetic suites cover it with
fake minisign/gh.  New release-scripts.yml tests the stage/sign/verify
scripts (Linux also signs a real AppImage).  A `dry_run` dispatch input on
release.yml runs everything up to the gate on any branch and publishes
nothing.

Docs: key generation and secret commands in app/packaging/README.md
(SignPath "dormant" text removed), release-process.md, SECURITY.md, the
site's Download page (public key once committed), the nightly tracker text,
and the stale "signing service" known-issue line in the v3.11.0 notes.

Closes #1151
Closes #1152

Co-Authored-By: Claude Fable 5.1 <[email protected]>
2026-11-02 21:36:57 -05:00

65 lines
2.1 KiB
YAML

name: Release scripts
# The release signing tooling behind release.yml's stage / verify-signatures
# jobs (issues #1151 / #1152): scripts/release/*.sh and
# app/packaging/linux/verify-appimage.py, tested hermetically by
# test/release/test_release_scripts.sh (fake artifact tree, throwaway minisign
# and GPG keys, no GitHub calls). Linux additionally downloads the pinned
# appimagetool and signs a real AppImage, which proves the gpgme passphrase
# path app-package.yml relies on; macOS covers the codesign rows.
on:
push:
branches: [ master ]
paths:
- 'scripts/release/**'
- 'app/packaging/linux/verify-appimage.py'
- 'app/packaging/linux/make-appimage.sh'
- 'test/release/**'
- '.github/workflows/release-scripts.yml'
pull_request:
branches: [ develop, master, 'release/**' ]
paths:
- 'scripts/release/**'
- 'app/packaging/linux/verify-appimage.py'
- 'app/packaging/linux/make-appimage.sh'
- 'test/release/**'
- '.github/workflows/release-scripts.yml'
workflow_dispatch:
permissions:
contents: read
concurrency:
group: ${{ github.workflow }}-${{ github.ref }}
cancel-in-progress: ${{ github.event_name == 'pull_request' }}
jobs:
test:
name: ${{ matrix.os }}
runs-on: ${{ matrix.os }}
strategy:
fail-fast: false
matrix:
os: [ ubuntu-latest, macos-latest ]
steps:
- uses: actions/checkout@v7
- name: Tools (Linux)
if: runner.os == 'Linux'
run: |
sudo apt-get update -qq
sudo apt-get install -y -qq minisign gnupg shellcheck
- name: Tools (macOS)
if: runner.os == 'macOS'
run: brew install minisign shellcheck
- name: shellcheck
run: shellcheck -S warning scripts/release/*.sh app/packaging/linux/make-appimage.sh test/release/test_release_scripts.sh
- name: Release script suite
env:
# Linux: also sign a real AppImage with the pinned appimagetool (network).
VJ_TEST_REAL_APPIMAGE: ${{ runner.os == 'Linux' && '1' || '0' }}
run: bash test/release/test_release_scripts.sh