Files
netris-nestri/.github/workflows/release-nesdoctor.yml
Wanjohi 786267f30a fix(nesdoctor): report storage properly, and stop being blind on Windows
A submission from a team machine with four drives and 22 TiB reported
`disk=8880`, and the field was not wrong so much as meaningless: it was the
free space on the single largest mount, with no capacity anywhere and no total.
A content store is sized against capacity.

Storage now reports four things, because they answer different questions and
one number could not:

  diskfree   total free across every real filesystem
  disksize   total capacity
  diskmax    the largest single filesystem, which is the real ceiling for any
             one store -- a dataset cannot be spread across drives
  disks      how many there are

The ambiguous `disk` key is gone rather than silently redefined, so old rows
stay readable as what they were. `Get-PSDrive` reports Free *and* Used and we
were reading only Free, hence no capacity on Windows at all.

Pseudo-filesystems are now excluded by *type* rather than by mount path. Path
filtering missed `/tmp` on a tmpfs, whose free space is RAM -- so 7 GiB of
memory was being added to a storage total, which is exactly the sort of number
a capacity plan gets built on.

## The real finding, which was not about disks

"We are working blind on Windows" is correct, and both Windows bugs this tool
has had prove it: a virtual display adapter reported as the GPU, and a URL
truncated at its first `&`. Both were in code that only runs on Windows, both
were found by a person reading the results channel, and neither could have been
found here -- the development machine is Linux and `xdg-open` never sees a
shell.

Two things about that, and the first is the one that generalises.

`OPENERS` is now a const with a test asserting the property that actually
matters: **never hand a URL to anything that will re-parse it.** No `cmd`, no
`sh`, no `powershell`, no `start` builtin, and no argument that looks like it
wants the URL interpolated into it. Unlike the bug, that is checkable on every
platform in a millisecond. Verified by reintroducing `cmd /C start "" <url>`
and confirming the test fails with the right message, then reverting.

And CI already runs a real Windows machine and a real macOS one -- we simply
were not looking at them. Each smoke-tested target now prints its full report
and JSON into a collapsed log group. Deliberately not `set -e`: this step is
for looking, and a probe that misbehaves on a runner must not fail a release.
It turns "working blind" into "looking at it once per release", which would
have shown the Parsec adapter problem the first time a Windows binary was ever
built.

Version to 0.2.2.
2026-09-02 16:10:05 +03:00

205 lines
8.4 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::"
- 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.