Files
netris-nestri/.github/workflows/release-nesdoctor.yml
Wanjohi 09b9472134 ci(nesdoctor): cross-compile the Intel Mac target, and tag to a draft
Two things a dispatch on 2026-09-02 exposed.

`macos-13` is being retired and the x86_64-apple-darwin job sat queued
indefinitely waiting for a runner, while the other three targets built and
smoke-tested in under three minutes. A release should not have that as a
dependency, so Intel macOS is now cross-compiled from the arm64 runner.

The cost is real and is stated rather than hidden: an x86_64 binary cannot be
executed on an arm64 runner without Rosetta, which these images do not carry,
so it is the one target whose smoke test cannot run. The matrix carries an
explicit `smoke` flag, the step is gated on it, and the generated release notes
say which binary is unexercised. Fabricating a pass for it would have been
easy and worse.

And a tag now produces a **draft** release rather than a published one. The
binaries get attached and the notes get written, then a person reads both and
presses publish -- which is the only step in this pipeline that cannot be
undone in public.

For the record, from the successful three: the musl smoke test measured the
network from inside the static binary -- `up=1635Mbps rtt=2ms bloat=+0ms
grade=A` -- so `ring` and the platform verifier do resolve root certificates in
a fully static build. That was the one thing about this release nobody could
have known without running it.
2026-09-02 13:09:03 +03:00

178 lines
7.1 KiB
YAML

# Builds the binaries people actually download.
#
# `nesdoctor` is handed to strangers and asked to be trusted, so the release
# artefacts are built here and nowhere else: a binary someone produced on their
# laptop and uploaded is not auditable, however honest they are.
#
# Tag `nesdoctor-v0.1.0` to cut a release, or run it by hand to check the
# matrix still builds.
name: release nesdoctor
on:
push:
tags: ["nesdoctor-v*"]
workflow_dispatch:
permissions:
contents: write
jobs:
build:
name: ${{ matrix.target }}
runs-on: ${{ matrix.os }}
strategy:
# One broken target must not suppress the others: a Windows failure
# should still leave the Linux binary available to look at.
fail-fast: false
matrix:
include:
# musl and not glibc, so one Linux binary runs on every distro
# regardless of its glibc version. Static linking is the whole reason
# this target is here.
- os: ubuntu-latest
target: x86_64-unknown-linux-musl
bin: nesdoctor
smoke: true
- os: windows-latest
target: x86_64-pc-windows-msvc
bin: nesdoctor.exe
smoke: true
- os: macos-latest
target: aarch64-apple-darwin
bin: nesdoctor
smoke: true
# Intel Macs are still a large installed base and they are clients,
# which is a category we want answers from. Cross-compiled from the
# arm64 runner rather than built on `macos-13`: that runner is being
# retired and a dispatch on 2026-09-02 sat queued indefinitely
# waiting for one, which is not a dependency a release should have.
#
# The cost is honest and stated: an x86_64 binary cannot be executed
# on an arm64 runner without Rosetta, which these images do not
# carry, so this is the one target whose smoke test cannot run. It
# ships cross-compiled and unexercised, and the release notes say so.
- os: macos-latest
target: x86_64-apple-darwin
bin: nesdoctor
smoke: false
steps:
- uses: actions/checkout@v4
- name: Rust toolchain
run: |
rustup toolchain install stable --profile minimal --no-self-update
rustup target add ${{ matrix.target }}
# `ring`, under rustls, compiles C. On musl that needs the musl C
# toolchain present or the build fails at link time with an error that
# does not mention TLS at all.
- name: musl toolchain
if: matrix.target == 'x86_64-unknown-linux-musl'
run: sudo apt-get update && sudo apt-get install -y musl-tools
- name: Build
run: cargo build --release -p nesdoctor --target ${{ matrix.target }}
# The binary is run here on purpose, and this step is the reason this
# workflow is worth having rather than a `cargo build` someone trusts.
#
# A static musl build resolves root certificates through the host trust
# store, so TLS can compile perfectly and then fail on the machine it is
# shipped to -- which would break the network test, the one feature
# anybody runs this for, silently and only for other people. Running the
# real thing here catches that class of failure before a tag exists.
- name: Smoke test — the whole run, network included
if: matrix.smoke
shell: bash
run: |
set -euo pipefail
BIN="target/${{ matrix.target }}/release/${{ matrix.bin }}"
test -x "$BIN"
# --no-steam because a runner has no Steam and the consent prompt
# would block; stdin is closed so any prompt reads as a skip.
OUT="$("$BIN" --quiet --no-steam --json "$RUNNER_TEMP/nd.json" < /dev/null)"
echo "$OUT"
# The summary line must exist and must not have fallen back to
# "net=unmeasured", which is what a TLS or upload failure looks like.
grep -q "nesdoctor " <<<"$OUT"
if grep -q "net=unmeasured" <<<"$OUT"; then
echo "::error::network test did not run in the built binary — \
TLS or the upload sink failed at runtime, which is exactly the \
failure this step exists to catch"
exit 1
fi
test -s "$RUNNER_TEMP/nd.json"
- name: Package
shell: bash
run: |
set -euo pipefail
mkdir -p dist
NAME="nesdoctor-${{ matrix.target }}"
cp "target/${{ matrix.target }}/release/${{ matrix.bin }}" "dist/$NAME${{ matrix.bin == 'nesdoctor.exe' && '.exe' || '' }}"
cd dist
# Checksums beside the binary, because "download this and run it" is
# only a reasonable request if the file can be verified.
if command -v sha256sum >/dev/null; then
sha256sum * > "$NAME.sha256"
else
shasum -a 256 * > "$NAME.sha256"
fi
cat *.sha256
- uses: actions/upload-artifact@v4
with:
name: nesdoctor-${{ matrix.target }}
path: dist/*
if-no-files-found: error
release:
# Only on a tag. A manual run builds and smoke-tests the matrix without
# publishing anything, which is what you want while iterating.
if: startsWith(github.ref, 'refs/tags/nesdoctor-v')
needs: build
runs-on: ubuntu-latest
steps:
- uses: actions/download-artifact@v4
with:
path: dist
merge-multiple: true
- name: Collect checksums
run: |
cd dist
cat *.sha256 | sort -k2 > SHA256SUMS
rm -f nesdoctor-*.sha256
ls -la
cat SHA256SUMS
- uses: softprops/action-gh-release@v2
with:
# A draft, always. The binaries are attached and the notes are
# written, and a person reads both and presses publish -- which is
# the only step in this pipeline that is irreversible in public.
draft: true
files: dist/*
generate_release_notes: true
body: |
**nesdoctor** — checks whether this machine can host a Nestri box,
and measures what your connection actually does under load.
Nothing is uploaded. There is no server to upload to: the network
test talks to Cloudflare's public speed-test sink and to `1.1.1.1`,
neither of which is ours. The output is a line on your terminal that
you may choose to paste somewhere.
Download the file for your platform, verify it against
`SHA256SUMS`, and run it. On macOS and Linux you will need
`chmod +x` first. Source is in `apps/nesdoctor`.
The Intel macOS binary is cross-compiled and is the one target CI
cannot execute to test. Every other binary here was run by the
workflow that built it.
The number worth running it for is **added latency under load**.
Everybody knows their download speed; almost nobody has seen this
one, and for anything interactive it is the figure that decides it.