Skip to content

Precompiled Binaries

Mason Rowe edited this page Oct 31, 2026 · 1 revision

Precompiled Binaries

In short

YABS ships as one bash script with zero dependencies, but fio and iperf3 still have to come from somewhere. They are downloaded at runtime as fully static, musl-built binaries from the project's GitHub Releases — one per CPU architecture — and they run on essentially any Linux userland, old or new, glibc or not. If you already have fio/iperf3 installed, YABS uses yours instead (unless you pass -b).

For everyone: the trust model, honestly

Piping a script to bash is already an act of trust, and the bin/README has said so plainly since 2020: "there is a security risk to your machine and its contents by running this script since, after all, this is just a script on the internet. You'll simply have to have confidence that I don't have malicious intent and am semi-competent at writing a bash script." What you're trusting:

  • The maintainer — the script, the release assets, and the workflows are all public and reviewable.
  • GitHub Actions — the binaries are built by public CI workflows from public upstream source, not uploaded by hand. Each release attaches the exact upstream source tarball, SHA-256 checksums, and a VirusTotal scan table.
  • Your own packages, optionally — if fio/iperf3 are installed from your distro, YABS prefers them. -b does the opposite: forces the repo binaries even when local ones exist.

Mitigations worth knowing: the binary-download code path is ~20 readable lines; checksums and VT reports are published on every release; the workflow that produced each binary is inspectable end to end; and nothing is ever written outside your working directory. The residual trust — that the maintainer's GitHub account and CI secrets aren't compromised — is the same trust you extend to any project's release artifacts.

To eliminate the download entirely: install fio and iperf3 from your package manager first.

Why static, prebuilt binaries at all

The design goals (script header comment): "not require any dependencies - either compiled or installed - nor admin privileges to run." In the project's own words from the Jan 2020 LowEndSpirit thread, pre-requisites kill adoption: "much of bench.sh/nench.sh's success is that you can simply run the one-liner and not have to worry about anything being installed prior."

Static binaries specifically solve three problems:

  • Parsable, standardized output — same fio/iperf3 version and flags everywhere, so a terse field or an error string means the same thing on every host.
  • No libc version dependency — early fio builds kept failing: compiled on a 4.15 kernel they wouldn't run on 2.6, and vice versa ("Issues seem to be coming from the libaio and libc6 libraries"). Building statically on an old base (Debian 7 → then Holy Build Box → now musl) fixed it: "working on every vps+dedi I've tested so far (even an ancient cociu VM)".
  • 32-bit and ARM coverage — no reliance on the distro having packages at all.

What "static" and "musl" buy you

A normal Linux binary expects to find its libraries (libc.so, libaio.so, ...) on the system at runtime, and subtly different versions of glibc break binaries built elsewhere — exactly what killed the early fio attempts. A static binary has every library compiled in; it needs nothing from the host beyond the kernel's syscall interface. And musl (the alternative libc used here) is deliberately small and static-linking-friendly — glibc static linking is famously half-broken (NSS, locale, getaddrinfo), musl's just works. Result: the same fio_x64 runs on an old CentOS box, a current Debian, and an Alpine container with no glibc at all.

Local binaries have been preferred when present since Feb 2020 (v2020-02-10) to smooth over edge-case incompatibilities; -b (Nov 2021, #27) forces the repo ones for consistency.

Where the binaries live

Nothing binary is committed to the repo anymore (they were moved out in Sep 2026). Assets are GitHub Releases:

Release tag Assets Contents
fio-<ver> (e.g. fio-3.42) fio_x64, fio_x86, fio_aarch64, fio_arm, fio_riscv64, fio-checksums.sha256, upstream fio-<ver>.tar.gz Static fio + SHA-256 checksums + the exact source (fio is GPL-2.0 — distributing binaries requires offering source)
iperf3-<ver> (e.g. iperf3-3.21) iperf3_<arch> ×5, iperf3-checksums.sha256, iperf-<ver>.tar.gz Same shape; iperf3 is BSD-3-Clause
toolchains <triplet>-cross.tgz + NOTICE.txt Mirrors of the musl.cc cross toolchains — musl.cc blocks GitHub Actions IPs, so the builds fetch from the release; upstream URL kept as fallback

Each release body embeds the VirusTotal scan table (per-binary status, engine counts, and a link to the full VT report).

How yabs.sh finds them

At startup (only if fio or iperf tests aren't skipped):

  1. Fetch https://github.com/masonr/yet-another-bench-script/releases.atom.
  2. First fio-X.Y match → FIO_BIN_TAG; first iperf3-X.Y → IPERF_BIN_TAG.
  3. On failure (offline, rate-limited), fall back to the pins compiled into the script: FIO_BIN_TAG="fio-3.42", IPERF_BIN_TAG="iperf3-3.21".

Download then happens per-arch:

# curl path (wget -T 5 -t 5 equivalent otherwise)
curl -sfL --connect-timeout 5 --retry 5 --retry-delay 0 \
  "https://github.com/masonr/yet-another-bench-script/releases/download/$FIO_BIN_TAG/fio_$ARCH" \
  -o "$DISK_PATH/fio"

An empty/missing file triggers Fio binary download failed. Running dd test as fallback.... or iperf3 binary download failed. Skipping iperf network tests....

Which binary actually runs

Situation fio / iperf3 used
installed on PATH, no -b the local package
installed on PATH, -b given the repo binary
not installed the repo binary
repo download fails dd fallback (fio) / section skipped (iperf3)

The build recipe

Canonical path: the GitHub Actions workflows (see GitHub Actions Automation). Manual path: bin/cross-compile.sh inside phusion/holy-build-box-64:latest — the exact same steps, driven by ARCH/CROSS/HOST env vars:

# x86_64 (also plain bin/compile.sh for a native build)
docker run -t -i --rm -v `pwd`:/io phusion/holy-build-box-64:latest bash /io/compile.sh

# cross-compile, e.g. aarch64:
docker run -t -i --rm -v `pwd`:/io \
  --env ARCH=aarch64 --env CROSS=aarch64-linux-musl --env HOST=aarch64-linux-gnu \
  phusion/holy-build-box-64:latest bash /io/cross-compile.sh

bin/compile.sh is the same recipe minus the cross pieces — a native x86_64 build that produces fio_x64/iperf3_x64. bin/cross-compile.sh generalises it via the three env vars. The workflows embed the same steps in a heredoc rather than calling these files, so the two copies have to be kept in sync by hand (they are identical today) — and the workflow copy is what actually produces the published assets.

Matrix mapping (CROSS = musl.cc toolchain prefix, HOST = the --host triplet for autotools):

ARCH CROSS HOST
x64 x86_64-linux-musl x86_64-linux-musl
x86 i686-linux-musl i686-linux-musl
aarch64 aarch64-linux-musl aarch64-linux-gnu
arm arm-linux-musleabihf arm-linux-gnueabihf
riscv64 riscv64-linux-musl riscv64-linux-gnu

Step-by-step, with the "why"

Step What it does Why
source /hbb/activate Enter the Holy Build Box environment HBB (Phusion) is a CentOS-based container purpose-built for producing maximally-portable Linux binaries; historically the fix for the libc6/kernel compatibility saga.
Remove CentOS-*.repo files Deletes obsolete repo definitions The base image's repos point at dead URLs; yum must work to install tools.
yum install yum-plugin-ovl xz Overlay-FS fix + xz for tarballs yum-plugin-ovl makes yum work on Docker's overlay filesystem.
Download <CROSS>-cross.tgz musl cross toolchain from the repo's toolchains release (musl.cc fallback) musl.cc blocks GitHub Actions IP ranges. musl is a small libc designed for static linking — the resulting binaries need no system libraries at all, so glibc version differences vanish.
Build libaio 0.3.113, ENABLE_SHARED=0 Static libaio into /hbb_exe fio's libaio engine needs it; only the static lib is wanted for a fully static binary.
source /hbb_exe/activate Switch to HBB's exe-build env (picks up the just-installed libs)
sed -i '/#include <linux\/prctl.h>/d' backend.c Drop the kernel-header include from fio ≥3.42 fio 3.42+ includes both linux/prctl.h and sys/prctl.h, redefining struct prctl_mm_map under musl. Harmless on glibc too.
CC=<cross>-gcc ./configure --disable-native --build-static fio configure --disable-native: no -march=native — a native-built fio crashed with illegal instructions on QEMU CPUs in 2020 (issue #3); the binary must run on any CPU of the arch. --build-static: fully static link.
make EXTLIBS+=' -latomic -no-pie' fio link flags -latomic: 32-bit and some ARM targets lack native 64-bit atomics — GCC emits __atomic_* calls that need the runtime lib. -no-pie: see below.
libcheck fio HBB verification Fails the build if the result links any non-whitelisted shared library — the guarantee that "static" is really static.
LDFLAGS="$LDFLAGS -no-pie" ./configure --disable-shared --disable-profiling --build x86_64-pc-linux-gnu --host $HOST --with-openssl=no --enable-static-bin iperf3 configure --enable-static-bin + --disable-shared: static binary. --with-openssl=no: drops iperf3's authenticated-connection feature so OpenSSL isn't needed. --build x86_64... --host $HOST: cross-compile from x86_64 for the target. -no-pie via LDFLAGS at configure time — passing it to make instead would override automake's LDFLAGS and drop --static.
rm libatomic.la then make Remove libtool's .la file first Otherwise libtool links the shared libatomic.so into the "static" binary — this specifically broke arm32 builds.
libcheck src/iperf3 Same staticity check

The Holy Build Box two-phase trick

Notice the script activates two environments: source /hbb/activate for the library phase, then source /hbb_exe/activate for the executable phase. HBB separates "build libraries the old-glibc-compatible way" from "link executables that must stand alone" — libaio is compiled into /hbb_exe in phase one so the phase-two link can find it statically. The little chores at the top (deleting dead CentOS repo files, yum-plugin-ovl for Docker's overlayfs, xz for tarballs) exist because the HBB image is an old CentOS that has to be repaired before yum works at all.

The -no-pie story

The musl.cc riscv64-linux-musl toolchain (GCC 11.2.1) builds PIE executables by default. Combined with -static that produces static-PIE binaries — and on RISC-V that goes wrong two ways: fio fails to link outright (read-only segment has dynamic relocations), while iperf3 links but segfaults on its first network I/O. -no-pie forces a classic static executable and is a no-op on the other toolchains, which is why it's passed unconditionally.

Verifying a binary yourself

# fetch binary + checksums from the release page
sha256sum -c fio-checksums.sha256 --ignore-missing
# fio_x64: OK

file fio_x64
# fio_x64: ELF 64-bit LSB executable, x86-64, ... statically linked, stripped

ldd fio_x64
#         not a dynamic executable

ldd saying "not a dynamic executable" is the proof the binary carries no shared-library dependencies. Each release's VirusTotal table links the full per-engine report for the exact file hash, so you can cross-check what ~70 scanners said about that precise file.

The same release also carries the exact upstream source tarball the binary was built from (fio-<ver>.tar.gz / iperf-<ver>.tar.gz), the build scripts are bin/*.sh, and the workflow logs are public — the chain from source to binary is auditable end to end.

A short history of binary delivery

  • Oct 2019: iperf3 3.1.3 downloaded at runtime from iperf.fr — a dynamic binary plus a shared libiperf.so.0 dropped in /tmp; a static ioping binary shipped in-repo for the brief read-test experiment.
  • Jan 2020: the static-fio saga — builds failed across kernels/libcs until a Debian 7 build worked ("Deb 7 did the trick"); standardized on Holy Build Box. fio/iperf3 binaries committed to bin/ (Jan 13, 2020); dd replaced by fio in v2020-02-04 (fio 3.17, iperf3 3.7 at the time).
  • Feb 2020: local distro installs preferred when present; -b came later (Nov 2021) for the opposite preference.
  • Mar 2020: --disable-native after #3 crashes on QEMU.
  • Sep 2020: fio 3.23 OOM-killed on <1 GB CentOS boxes (issue #11) — temporarily reverted to 3.17; 3.25 shipped that December.
  • Jun 2021: ARM binaries added (#21) — aarch64 and arm builds have existed since.
  • May 2021 / Dec 2024: routine version bumps (iperf3 3.10, fio 3.27; later fio 3.38, iperf3 3.18) — historically manual rebuilds on the maintainer's machine.
  • Nov 2023: SHA-256 checksums + VirusTotal links added to bin/README.
  • Sep 2025: first build workflows — automation replaces by-hand rebuilds.
  • Sep 2026: binaries moved out of the repo into GitHub Releases; workflows rebuilt around musl cross toolchains + VT polling; yabs.sh learned to discover the newest release via the Atom feed.
  • Oct 2026: riscv64 assets added — the toolchain's default-PIE quirk required the -no-pie workaround described above.

The net effect: what began as "a static binary Mason built once and hoped for the best" is now a daily-checked, CI-built, checksum-verified, virus-scanned pipeline — while remaining, at heart, two small binaries on a releases page.

See also

Clone this wiki locally