Files
netris-nestri/.github/workflows/release-nesdoctor.yml
Wanjohi 730739a5c0 fix(nesdoctor): fall back to the SoC name on Apple Silicon, and print raw probes
The macOS arm added in the previous commit did not change anything -- the
runner still reported `gpu=unknown`. Checked rather than assumed, which is the
only reason it is known.

Two possible causes and no way to choose between them from here: either the
`system_profiler SPDisplaysDataType` parsing is wrong, or that machine is a
headless virtual Mac with no display adapter to enumerate at all, in which case
`unknown` was the correct answer and there is nothing to fix. The second is
likely and the first is not ruled out.

So, rather than guessing again: on an arm64 Mac the GPU *is* the SoC, so the
chip name is a true and useful answer even with no display attached.
`sysctl -n machdep.cpu.brand_string` works headless and yields
"Apple M1 (integrated)". Intel Macs get no fallback, because there the GPU may
be integrated or discrete and a guess would be wrong rather than coarse.

And the CI step now dumps the **raw** output of each platform's probes --
`system_profiler`, `Get-CimInstance Win32_VideoController`, `Get-PSDrive`,
`df -Pk`, `/sys/class/drm` -- into its own log group. A field that comes back
empty can then be told apart from a parser that is wrong, which is exactly the
distinction that cost this round trip. All of it is `|| true`: the step exists
for looking, and a probe that misbehaves on a runner must never fail a release.
2026-09-02 16:29:03 +03:00

228 lines
9.5 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"
# Eyes on the platforms the developer machine is not.
#
# Every Windows bug this tool has had was found by a person reading the
# results channel: a virtual display adapter reported as the GPU, and a
# URL truncated at its first `&`. Both were in code that only runs on
# Windows, and the development machine is Linux -- so nobody had ever
# seen what these probes return on the platform most of the audience
# uses.
#
# CI already runs a real Windows machine and a real macOS one. Printing
# the full report from each is nearly free and turns "we are working
# blind" into "we are looking at it once per release".
- name: Show what the probes actually return here
if: matrix.smoke
shell: bash
run: |
set -uo pipefail
BIN="target/${{ matrix.target }}/release/${{ matrix.bin }}"
echo "::group::${{ matrix.target }} — full report"
# Not `set -e`: this step is for looking, and a probe that fails on a
# runner must not fail the release.
"$BIN" --no-net --no-steam --json "$RUNNER_TEMP/probe.json" < /dev/null || true
echo "::endgroup::"
echo "::group::${{ matrix.target }} — JSON"
cat "$RUNNER_TEMP/probe.json" 2>/dev/null || echo "(no json written)"
echo "::endgroup::"
# The raw output of the platform probes, so a field that comes back
# empty can be told apart from a parser that is wrong. `gpu=unknown`
# on the macOS runner could be either, and guessing which cost a
# round trip.
echo "::group::${{ matrix.target }} — raw platform probes"
case "${{ runner.os }}" in
macOS)
system_profiler SPDisplaysDataType 2>&1 | head -40 || true
sysctl -n machdep.cpu.brand_string 2>&1 || true
df -Pk 2>&1 | head -20 || true
;;
Windows)
powershell -NoProfile -Command \
"Get-CimInstance Win32_VideoController | Format-List Name,AdapterCompatibility,AdapterRAM" 2>&1 | head -30 || true
powershell -NoProfile -Command \
"Get-PSDrive -PSProvider FileSystem | Format-Table Name,Free,Used" 2>&1 | head -20 || true
;;
Linux)
df -Pk 2>&1 | head -20 || true
ls -l /sys/class/drm/ 2>&1 | head -20 || true
;;
esac
echo "::endgroup::"
- 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.