Cross builds, packaging split, CI, fuzzing, supply-chain policy

build.sh builds the Rust targets with the Go-era names, refuses an OUT
that is /, $HOME or the tree, checks static linking and names the Go
targets Rust cannot build. packaging/package.sh builds and inspects
packages without signing or publishing; repo.sh reuses it. Seven fuzz
targets (one found a URL-parser panic, fixed here with a regression
test), cargo-deny policy, CI for lint, tests on Linux/macOS/Windows,
cross builds with qemu runs, supply chain and fuzzing. Compatibility
specification and security notes.

Co-Authored-By: Claude <noreply@anthropic.com>
This commit is contained in:
Andy Dixon
2026-10-08 11:44:23 +01:00
co-authored by Claude
parent a98931779a
commit cddde5ca52
27 changed files with 2347 additions and 114 deletions
+128 -33
View File
@@ -1,48 +1,143 @@
#!/usr/bin/env bash
# Build vault for every OS and CPU architecture Go can target without cgo.
# Build vault for the supported operating systems and CPU architectures.
#
# ./build.sh # everything, into ./dist
# ./build.sh # every target this host can build, into ./dist
# TARGETS="linux/amd64 darwin/arm64" ./build.sh
# OUT=/tmp/release ./build.sh
# OUT=/tmp/release JOBS=4 ./build.sh
#
# Binaries are static, stripped and reproducible (CGO_ENABLED=0, -trimpath).
# iOS and most Android targets are skipped (they need cgo); see below.
# 32-bit ARM Linux is built twice: armv6 (Raspberry Pi Zero/1) and armv7.
# SHA256SUMS is written next to the binaries.
# Binaries keep the Go-era names, vault-OS-ARCH[.exe], and are stripped
# release builds from the pinned toolchain (rust-toolchain.toml) and the
# locked dependencies (Cargo.lock), with build paths remapped so the output
# does not depend on where the tree lives. Linux binaries are static (musl)
# except s390x and ppc64, which link glibc dynamically; static linking is
# checked. SHA256SUMS is written next to the binaries.
#
# How each target is built, and what the host needs:
# zig cargo-zigbuild and zig on PATH (Linux, Windows GNU ABI)
# mac a Mac with Xcode (macOS)
# cross `cross` and Docker (FreeBSD, NetBSD, illumos, Solaris)
# cargo nothing extra (WASI)
# A requested target the host cannot build is reported, the others are still
# built, and the script exits non-zero. See docs/PLATFORMS.md for the Go
# targets with no Rust equivalent; asking for one is an error.
#
# JOBS builds several targets at once, each in its own target directory
# under target/build-sh (default 1: one at a time, cargo using every CPU).
set -euo pipefail
cd "$(dirname "$0")"
root=$PWD
table() {
# os/arch Rust target method
cat <<-'EOF'
linux/amd64 x86_64-unknown-linux-musl zig
linux/arm64 aarch64-unknown-linux-musl zig
linux/386 i686-unknown-linux-musl zig
linux/armv6 arm-unknown-linux-musleabihf zig
linux/armv7 armv7-unknown-linux-musleabihf zig
linux/riscv64 riscv64gc-unknown-linux-musl zig
linux/loong64 loongarch64-unknown-linux-musl zig
linux/ppc64le powerpc64le-unknown-linux-musl zig
linux/ppc64 powerpc64-unknown-linux-gnu zig
linux/s390x s390x-unknown-linux-gnu zig
android/arm64 aarch64-unknown-linux-musl zig
windows/amd64 x86_64-pc-windows-gnu zig
windows/386 i686-pc-windows-gnu zig
windows/arm64 aarch64-pc-windows-gnullvm zig
darwin/amd64 x86_64-apple-darwin mac
darwin/arm64 aarch64-apple-darwin mac
freebsd/amd64 x86_64-unknown-freebsd cross
freebsd/386 i686-unknown-freebsd cross
netbsd/amd64 x86_64-unknown-netbsd cross
illumos/amd64 x86_64-unknown-illumos cross
solaris/amd64 x86_64-pc-solaris cross
wasip1/wasm wasm32-wasip1 cargo
EOF
}
# Go targets that Rust cannot build with a stable toolchain (no prebuilt
# standard library, or no Rust target at all). See docs/PLATFORMS.md.
unsupported="aix/ppc64 dragonfly/amd64 freebsd/arm freebsd/arm64 freebsd/riscv64 js/wasm
linux/mips linux/mipsle linux/mips64 linux/mips64le netbsd/386 netbsd/arm netbsd/arm64
openbsd/386 openbsd/amd64 openbsd/arm openbsd/arm64 openbsd/ppc64 openbsd/riscv64
plan9/386 plan9/amd64 plan9/arm"
out=${OUT:-dist}
# iOS and 32-bit/x86 Android need cgo and a vendor SDK. android/arm64 builds
# a static binary that runs in Termux.
targets=${TARGETS:-$(go tool dist list | grep -vE '^ios/|^android/(386|amd64|arm)$')}
jobs=${JOBS:-$(getconf _NPROCESSORS_ONLN 2>/dev/null || echo 4)}
jobs=${JOBS:-1}
targets=${TARGETS:-$(table | awk '{print $1}')}
rm -rf "$out"
mkdir -p "$out"
# Never clean anything but our own output, and never in a directory that
# matters: OUT must be a dedicated directory, not /, $HOME or the tree.
mkdir -p -- "$out"
out=$(cd -- "$out" && pwd -P)
case $out in
/ | "$(cd ~ && pwd -P)" | "$root") echo "refusing OUT=$out: use a dedicated directory" >&2; exit 2 ;;
esac
rm -f -- "$out"/vault-* "$out"/SHA256SUMS "$out"/*.log
export CARGO_TARGET_DIR_BASE=$root/target/build-sh
export RUSTFLAGS="${RUSTFLAGS:-} --remap-path-prefix=$root=vault --remap-path-prefix=${CARGO_HOME:-$HOME/.cargo}=cargo"
export OUT_DIR=$out JOBS_N=$jobs HOST_OS=$(uname -s)
build_one() {
local os=${1%/*} arch=${1#*/} variants=("") name ext=""
[[ $os == windows ]] && ext=.exe
[[ $arch == wasm ]] && ext=.wasm
[[ $os/$arch == linux/arm ]] && variants=(6 7)
for v in "${variants[@]}"; do
name=vault-$os-$arch${v:+v$v}$ext
if ! GOOS=$os GOARCH=$arch GOARM=${v:-} CGO_ENABLED=0 \
go build -trimpath -buildvcs=false -ldflags='-s -w -buildid=' -o "$OUT_DIR/$name" . 2>"$OUT_DIR/$name.log"; then
echo "FAILED $name" >&2
cat "$OUT_DIR/$name.log" >&2
return 1
local want=$1 row os triple method name ext="" tdir bin
row=$(table | awk -v t="$want" '$1 == t')
if [[ -z $row ]]; then
if grep -qw -- "$want" <<<"$unsupported"; then
echo "UNSUPPORTED $want: no stable Rust target; see docs/PLATFORMS.md" >&2
else
echo "UNKNOWN $want" >&2
fi
rm "$OUT_DIR/$name.log"
echo "built $name"
done
return 1
fi
read -r _ triple method <<<"$row"
os=${want%/*}
[[ $os == windows ]] && ext=.exe
[[ $os == wasip1 ]] && ext=.wasm
name=vault-${want/\//-}$ext
tdir=$root/target
[[ $JOBS_N != 1 ]] && tdir=$CARGO_TARGET_DIR_BASE/$triple
case $method in
zig)
command -v cargo-zigbuild >/dev/null && command -v zig >/dev/null ||
{ echo "SKIPPED $want: needs cargo-zigbuild and zig" >&2; return 1; }
cmd=(cargo zigbuild) ;;
mac)
[[ $HOST_OS == Darwin ]] || { echo "SKIPPED $want: needs a Mac with Xcode" >&2; return 1; }
cmd=(cargo build) ;;
cross)
command -v cross >/dev/null && command -v docker >/dev/null ||
{ echo "SKIPPED $want: needs cross and docker" >&2; return 1; }
cmd=(cross build) ;;
cargo) cmd=(cargo build) ;;
esac
local flags=$RUSTFLAGS
# zig's musl getrandom() never reaches the kernel on riscv64 (it spins);
# make the getrandom crate issue the system call itself.
[[ $triple == riscv64*-musl ]] && flags+=' --cfg getrandom_backend="linux_raw"'
if ! RUSTFLAGS=$flags CARGO_TARGET_DIR=$tdir "${cmd[@]}" --release --locked --target "$triple" >"$OUT_DIR/$name.log" 2>&1; then
echo "FAILED $want ($triple)" >&2
tail -20 "$OUT_DIR/$name.log" >&2
return 1
fi
bin=$tdir/$triple/release/vault$ext
[[ $os == wasip1 ]] && bin=$tdir/$triple/release/vault.wasm
cp -- "$bin" "$OUT_DIR/$name"
rm -f -- "$OUT_DIR/$name.log"
if [[ $triple == *-musl* ]] && command -v file >/dev/null; then
file -L "$OUT_DIR/$name" | grep -q 'static' || { echo "FAILED $want: not statically linked" >&2; return 1; }
fi
echo "built $name ($triple)"
}
export -f build_one
export OUT_DIR=$out
export -f build_one table
export unsupported
printf '%s\n' $targets | xargs -P "$jobs" -I{} bash -c 'build_one {}'
status=0
printf '%s\n' $targets | xargs -P "$jobs" -I{} bash -c 'build_one {}' || status=1
cd "$out"
if command -v sha256sum >/dev/null; then sha256sum vault-* >SHA256SUMS; else shasum -a 256 vault-* >SHA256SUMS; fi
echo "$(ls vault-* | wc -l | tr -d ' ') binaries in $out"
cd -- "$out"
if ls vault-* >/dev/null 2>&1; then
if command -v sha256sum >/dev/null; then sha256sum vault-* >SHA256SUMS; else shasum -a 256 vault-* >SHA256SUMS; fi
fi
echo "$(ls vault-* 2>/dev/null | wc -l | tr -d ' ') binaries in $out"
exit $status