Files
MeshTerm/.github/workflows/github-release.yml
T
Jean-Pierre MartineauandClaude Opus 5 c7c1770d96 The Windows binary is signed by SignPath Foundation
Windows greets an unsigned one-file PyInstaller build as a stranger every
release: SmartScreen warns, and antivirus engines flag it on sight. A
signature gives Windows a publisher to accumulate trust against instead.

SignPath Foundation issue the certificate free to open-source projects and
sign on their own infrastructure, so no key exists on the runner. Their
condition is that the artifact provably came from this repository's CI,
which is why the binary is handed over as a GitHub artifact id rather than
uploaded -- SignPath reads the workflow run behind it.

Signing sits between the build's smoke test and the naming step, so the
checksum describes the file people actually download. The unsigned draft
is the one artifact the release must not attach, so it is named outside
the meshterm-* pattern the release now downloads by.

Both steps skip wholesale until SIGNPATH_ORGANIZATION_ID is set, so the
Windows binary keeps building unsigned in the meantime and a fork with no
SignPath project of its own is unaffected.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-09-17 18:02:10 -04:00

170 lines
6.2 KiB
YAML

# Puts the downloadable builds on the repository's Releases page.
#
# Nothing here leaves GitHub: a release is exactly as visible as the repository it sits
# on. This is *not* the PyPI publish — that is `release.yml`, it is a separate act, and it
# does not run from here.
#
# Triggered by pushing a version tag, which is the moment a release exists in git anyway:
#
# git tag -a v0.3.0 -m "MeshTerm 0.3.0"
# git push origin v0.3.0
#
# The binaries are built by `installers.yml`, called rather than copied, so the files
# people download are made exactly the way the ones CI checks on every push are.
#
# Once the release exists, `website` puts it on meshterm.net: it rebuilds that site from
# the tagged tree — the landing and About pages carry the version — and pushes the result
# to jpmartineau/meshterm.net, which GitHub Pages serves. It writes with a deploy key
# scoped to that one repository (secret SITE_DEPLOY_KEY); that repo's README makes one.
name: GitHub release
on:
push:
tags: ["v*"]
workflow_dispatch:
inputs:
tag:
description: "Existing tag to build a release from (e.g. v0.2.0)"
required: true
type: string
prerelease:
description: "Mark as a pre-release (keeps it out of 'Latest')"
type: boolean
default: false
permissions:
contents: read
jobs:
build:
uses: ./.github/workflows/installers.yml
publish:
name: create the release
needs: build
runs-on: ubuntu-latest
timeout-minutes: 10
permissions:
# The one job that needs it: creating a release writes to the repository.
contents: write
steps:
- uses: actions/checkout@v4
with:
ref: ${{ inputs.tag || github.ref }}
# Every platform's artifact, flattened into one directory of files to attach.
- uses: actions/download-artifact@v4
with:
# The build uploads one artifact nobody should ever download: the unsigned
# Windows binary it hands to SignPath. Everything meant for the release page is
# named `meshterm-…`, so the pattern is the filter.
pattern: meshterm-*
path: artifacts
merge-multiple: true
- name: Gather the binaries and their checksums
id: gather
shell: bash
run: |
mkdir -p release
find artifacts -type f -exec mv {} release/ \;
ls -lh release/
# One combined checksum file is easier to verify against than five loose ones.
(cd release && cat *.sha256 > SHA256SUMS && rm -f *.sha256)
tag="${{ inputs.tag || github.ref_name }}"
echo "tag=$tag" >> "$GITHUB_OUTPUT"
echo "version=${tag#v}" >> "$GITHUB_OUTPUT"
- name: Take the release notes from the changelog
id: notes
shell: bash
env:
VERSION: ${{ steps.gather.outputs.version }}
run: |
# The section for this version, between its own heading and the next one. If the
# changelog has nothing for it, say so plainly rather than inventing notes.
#
# Extracted once, into the file, and the *file* is what gets tested. It used to
# run twice — once down a `| sed | head -1 | grep -q .` pipeline to decide, then
# again for real — and this step runs under `bash -e -o pipefail`, where that
# pipeline is a race it can lose. `head -1` exits the moment it has its line and
# closes the pipe; if `sed` still has bytes to write it dies on SIGPIPE, pipefail
# promotes that to a failed condition, and a version with a perfectly good
# changelog section publishes "No changelog entry" instead. It only bites once a
# section outgrows one buffered write, which is why every entry under 4 KB was
# fine and 0.3.1 was not — while 0.3.0, the same size, happened to win.
awk -v v="$VERSION" '
$0 ~ "^## \\[" v "\\]" {found=1; next}
found && /^## \[/ {exit}
found {print}
' CHANGELOG.md > notes.md
if ! grep -q '[^[:space:]]' notes.md; then
echo "No changelog entry for $VERSION." > notes.md
fi
echo "--- notes ---"; cat notes.md
- name: Create it
env:
GH_TOKEN: ${{ github.token }}
TAG: ${{ steps.gather.outputs.tag }}
PRERELEASE: ${{ inputs.prerelease && '--prerelease' || '' }}
run: |
gh release create "$TAG" release/* \
--title "MeshTerm ${TAG#v}" \
--notes-file notes.md \
--verify-tag \
$PRERELEASE
echo "https://github.com/${{ github.repository }}/releases/tag/$TAG"
website:
name: put the version on meshterm.net
needs: publish
# A pre-release stays out of "Latest", so it stays off the landing page too.
if: ${{ !inputs.prerelease }}
runs-on: ubuntu-latest
timeout-minutes: 10
steps:
- name: Check for the deploy key
env:
SITE_DEPLOY_KEY: ${{ secrets.SITE_DEPLOY_KEY }}
run: |
if [ -z "$SITE_DEPLOY_KEY" ]; then
echo "::error::SITE_DEPLOY_KEY is not set; the meshterm.net README says how to make it."
exit 1
fi
- uses: actions/checkout@v4
with:
ref: ${{ inputs.tag || github.ref }}
path: meshterm
- uses: actions/checkout@v4
with:
repository: jpmartineau/meshterm.net
ssh-key: ${{ secrets.SITE_DEPLOY_KEY }}
path: site
- uses: actions/setup-python@v5
with:
python-version: "3.12"
- name: Rebuild the site from this release
run: |
python -m pip install --quiet -r site/requirements.txt
python site/build.py --meshterm meshterm
- name: Publish it
working-directory: site
env:
TAG: ${{ inputs.tag || github.ref_name }}
run: |
git add -A
if git diff --cached --quiet; then
echo "meshterm.net already shows ${TAG#v}."
exit 0
fi
git config user.name "github-actions[bot]"
git config user.email "41898341+github-actions[bot]@users.noreply.github.com"
git commit -m "MeshTerm ${TAG#v}"
git push