mirror of
https://github.com/nestriness/nestri.git
synced 2026-09-19 09:15:19 +03:00
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.
228 lines
9.5 KiB
YAML
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.
|