Linux cross-compile from the Mac: cargo-zigbuild for x86_64 glibc 2.36, BIN_SOURCE=mac in provision.sh and stage-v4.sh

infra/cross/build-linux.sh builds igneumd and igneum-miner for x86_64-unknown-linux-gnu.2.36 (Debian 12) with
cargo-zigbuild 0.23.4 and zig 0.17.0 into vendor/igneum-node/target-linux: rocksdb, lz4, blst, secp256k1 and the
execution layer all link through zig, no crate failed (Docker is absent here, so cross was not needed). Cold build
1,856 s at 4 jobs, nice 19, on a loaded Mac; the seed's own build took 55 min and the builder VM 10 to 25.
Verified on igneum-seed-1 in /root/xbuild-test: igneumd --version, igneum-miner --help, and a 60-s private
network with real proof of work where the cross-compiled miner found 7 blocks that the cross-compiled node
accepted (0 rejected). /opt/igneum/v4/bin untouched.

BIN_SOURCE=mac: provision.sh skips the builder VM and takes build/bin from the cross-compile; stage-v4.sh
cross-compiles, uploads to /root/v4/out and installs exactly as a VM build would.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
This commit is contained in:
igneum-labs 2026-10-04 10:34:19 +00:00
parent 6b72c3b3e6
commit d29016d576
5 changed files with 89 additions and 7 deletions

View file

@ -4,13 +4,14 @@
# ./provision.sh source tarball -> builder VM -> build/bin/{igneumd,igneum-miner} -> every node (systemd units)
# ./provision.sh build only the build (creates the builder VM if build/builder.ip is missing, deletes it afterwards)
# ./provision.sh install only the install on the nodes (needs build/bin/igneumd)
# BIN_SOURCE=mac ./provision.sh [build|all] no builder VM: cross-compile on this Mac (infra/cross/build-linux.sh,
# cargo-zigbuild, x86_64-unknown-linux-gnu glibc 2.36) and take build/bin from its output
#
# Why a builder VM and not a cross-compile on the Mac: rustup here has only aarch64-apple-darwin and
# x86_64-pc-windows-gnu installed and there is no x86_64 Linux linker (no musl-cross, zig, cross or docker; checked
# 3 Oct 2026). A Linux cross-compile would need `rustup target add x86_64-unknown-linux-gnu` plus a linker
# (`brew install filosottile/musl-cross/musl-cross` and a musl target), and the rocksdb bindgen trick of the Windows
# cross-build again. The builder costs about EUR 0.05 for the hour (cpx41, approximate) and the Mac stays free.
# The binaries are cached in build/bin; REBUILD=1 forces a new build.
# Two ways to get the binaries. BIN_SOURCE=builder (the default, 3 Oct 2026): a builder VM compiles the source
# tarball (10 to 25 minutes on 8 vCPU, about EUR 0.05). BIN_SOURCE=mac (4 Oct 2026): cargo-zigbuild on this Mac
# cross-compiles for x86_64 Linux glibc 2.36 (zig is the C/C++ toolchain and linker; rocksdb, blst, secp256k1 and the
# execution layer link; infra/cross/build-linux.sh), no VM at all. The binaries are cached in build/bin; REBUILD=1
# forces a new build.
. "$(dirname "$0")/lib/common.sh"
@ -23,6 +24,13 @@ do_build() {
log "build/bin/igneumd exists ($(cat "$BIN_DIR/version.txt" 2>/dev/null | tr '\n' ' ')); REBUILD=1 to rebuild"
return
fi
if [ "${BIN_SOURCE:-builder}" = mac ]; then
log "BIN_SOURCE=mac: cross-compiling on this Mac (no builder VM) from $NODE_SRC"
NODE_SRC="$NODE_SRC" OUT_DIR="$BIN_DIR" "$HERE/../cross/build-linux.sh"
printf 'exported %s\nigneum-node (%s): HEAD %s (%s), cross-compiled on the Mac\n' "$(date -u +%Y-%m-%dT%H:%M:%SZ)" "$NODE_SRC" "$(git -C "$NODE_SRC" rev-parse --short HEAD)" "$(git -C "$NODE_SRC" rev-parse --abbrev-ref HEAD)" > "$BIN_DIR/src.stamp"
log "binaries in $BIN_DIR: $(cat "$BIN_DIR/version.txt" | tr '\n' ' ')"
return
fi
"$HERE/make-source.sh"
if [ ! -s "$BUILD_DIR/builder.ip" ]; then
YES="${YES:-0}" "$HERE/create.sh" builder

1
infra/cross/.gitignore vendored Normal file
View file

@ -0,0 +1 @@
out/

50
infra/cross/build-linux.sh Executable file
View file

@ -0,0 +1,50 @@
#!/usr/bin/env bash
# Cross-compile igneumd and igneum-miner for x86_64 Linux (Debian 12, glibc 2.36) on the Mac, so a server never
# builds. Uses cargo-zigbuild (zig as the C/C++ cross toolchain and linker); rocksdb (librocksdb-sys, C++), blst,
# secp256k1 and the execution layer's crates all link through it.
#
# infra/cross/build-linux.sh build from NODE_SRC (default vendor/igneum-node-v4) into TARGET_DIR
# NODE_SRC=... TARGET_DIR=... JOBS=4 infra/cross/build-linux.sh
#
# Output: $TARGET_DIR/x86_64-unknown-linux-gnu/release/{igneumd,igneum-miner} and $OUT_DIR/{igneumd,igneum-miner,version.txt}
# (OUT_DIR default infra/cross/out). First build: about 500 crates; later builds are incremental.
#
# One-off setup (4 Oct 2026, done on this Mac): brew install zig (0.17.0); cargo install cargo-zigbuild (0.23.4);
# rustup target add x86_64-unknown-linux-gnu. Docker is not installed here, so `cross` is not an option.
set -euo pipefail
HERE="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
REPO="$(cd "$HERE/../.." && pwd)"
NODE_SRC="${NODE_SRC:-$REPO/vendor/igneum-node-v4}"
TARGET_DIR="${TARGET_DIR:-$REPO/vendor/igneum-node/target-linux}"
OUT_DIR="${OUT_DIR:-$HERE/out}"
TARGET="${TARGET:-x86_64-unknown-linux-gnu}"
GLIBC="${GLIBC:-2.36}"
JOBS="${JOBS:-4}"
CARGO="${CARGO:-$HOME/.cargo/bin/cargo}"
log() { printf '%s %s\n' "$(date -u +%H:%M:%S)" "$*"; }
die() { log "ERROR: $*" >&2; exit 1; }
command -v zig >/dev/null 2>&1 || die "zig is not installed (brew install zig)"
[ -x "$HOME/.cargo/bin/cargo-zigbuild" ] || command -v cargo-zigbuild >/dev/null 2>&1 || die "cargo-zigbuild is not installed (cargo install cargo-zigbuild)"
"$HOME/.cargo/bin/rustup" target list --installed | grep -q "^$TARGET\$" || { log "adding rust target $TARGET"; "$HOME/.cargo/bin/rustup" target add "$TARGET"; }
[ -f "$NODE_SRC/Cargo.toml" ] || die "no node source at $NODE_SRC"
log "cross-compiling igneumd and igneum-miner for $TARGET.$GLIBC from $NODE_SRC ($(git -C "$NODE_SRC" rev-parse --short HEAD), $(git -C "$NODE_SRC" rev-parse --abbrev-ref HEAD)); target dir $TARGET_DIR; zig $(zig version); $JOBS jobs at nice 19"
start=$(date +%s)
( cd "$NODE_SRC" && CARGO_TARGET_DIR="$TARGET_DIR" nice -n 19 "$CARGO" zigbuild --release -j "$JOBS" \
--target "$TARGET.$GLIBC" -p kaspad -p igneum-miner --features kaspad/igneum-pow )
secs=$(( $(date +%s) - start ))
log "cross-compile finished in $secs s ($(( secs / 60 )) min)"
mkdir -p "$OUT_DIR"
cp "$TARGET_DIR/$TARGET/release/igneumd" "$TARGET_DIR/$TARGET/release/igneum-miner" "$OUT_DIR/"
chmod +x "$OUT_DIR/igneumd" "$OUT_DIR/igneum-miner"
ver=$(grep -m1 '^version' "$NODE_SRC/Cargo.toml" | sed 's/.*"\(.*\)".*/\1/')
{
printf 'igneumd %s\n' "$ver"
printf 'cross-compiled on %s from %s (%s) with cargo-zigbuild, target %s.%s, %s s\n' "$(date -u +%Y-%m-%dT%H:%M:%SZ)" "$(git -C "$NODE_SRC" rev-parse --short HEAD)" "$(git -C "$NODE_SRC" rev-parse --abbrev-ref HEAD)" "$TARGET" "$GLIBC" "$secs"
git -C "$NODE_SRC" rev-parse --short HEAD
} > "$OUT_DIR/version.txt"
file "$OUT_DIR/igneumd" "$OUT_DIR/igneum-miner" | sed 's/^/ /'
ls -la "$OUT_DIR" | sed 's/^/ /'
log "binaries in $OUT_DIR"

View file

@ -30,7 +30,10 @@ rollback. `health.sh` reports `unit=active-v4` once the v4 unit serves. The runb
Settings in `config.sh`: provider, name, type, location, image, ssh key (`~/.ssh/igneum_ed25519`), `SSH_SOURCE`
(`any`, `me` or a CIDR for port 22), `NETWORK_ARGS` (`--devnet`; a private network: `--devnet --devnet-suffix=20`),
`SEED_PEERS`, `BUILD_WHERE` (`seed` builds on the VM; `bin` reuses `../cloud-devnet/build/bin`). The Hetzner token is
`SEED_PEERS`, `BUILD_WHERE` (`seed` builds on the VM; `bin` reuses `../cloud-devnet/build/bin`). `BIN_SOURCE=mac
./stage-v4.sh` (4 Oct 2026) skips the VM build: `infra/cross/build-linux.sh` cross-compiles the devnet-v4 worktree
on the Mac with cargo-zigbuild (x86_64 Linux, glibc 2.36) in minutes, uploads the binaries to `/root/v4/out` and
installs them exactly as a VM build would (the seed's own build took 55 minutes on its 2 vCPU). The Hetzner token is
read from `~/.config/igneum/hetzner-token` and never printed.
Node flags (`node/run-seed.sh`): `--devnet --appdir=/var/lib/igneum --rpclisten=127.0.0.1:26610

View file

@ -9,6 +9,9 @@
# a finished build is not repeated), then wait and install; for a failed post-build step
# ./stage-v4.sh [seed-name] wait only wait for a build already started and install when it is done
# ./stage-v4.sh [seed-name] status one line: build state, installed version, unit states
# BIN_SOURCE=mac ./stage-v4.sh [seed-name] no build on the VM: cross-compile on this Mac (infra/cross/build-linux.sh,
# cargo-zigbuild, x86_64 Linux glibc 2.36, minutes instead of the VM's 40 to 60),
# upload the binaries to /root/v4/out and install exactly as the VM build would
# The build runs under `systemd-run` with MemoryHigh 1600M, MemoryMax 2400M, nice 19, idle IO, 1 cargo job (the VM has
# 2 vCPU and 4 GB; the v3 node uses about 1.5 GB; the 6 GB swap file from provision-seed.sh takes the overflow), so a
# dropped ssh session does not kill it and it cannot starve the running node. Expect 40 to 60 minutes (approximate: the
@ -73,8 +76,25 @@ wait_build() {
log "staged. Nothing was switched: $(remote_status)"
}
# BIN_SOURCE=mac (4 Oct 2026): cross-compile on the Mac, upload, install; the VM never builds.
if [ "${BIN_SOURCE:-seed}" = mac ] && [ "$mode" = start ]; then
for try in $(seq 1 10); do sssh "$ip" true >/dev/null 2>&1 && break; log "waiting for ssh ($try)"; sleep 10; done
sssh "$ip" true || die "ssh to $ip failed"
[ -f "$V4_SRC/Cargo.toml" ] || die "no devnet-v4 worktree at $V4_SRC"
log "BIN_SOURCE=mac: cross-compiling $V4_SRC ($(git -C "$V4_SRC" rev-parse --short HEAD), $(git -C "$V4_SRC" rev-parse --abbrev-ref HEAD)) on this Mac; nothing builds on $name"
NODE_SRC="$V4_SRC" OUT_DIR="$BUILD_DIR/cross-out" "$HERE/../cross/build-linux.sh" | tee -a "$LOG"
log "uploading the cross-compiled binaries to /root/v4/out on $name"
sssh "$ip" 'mkdir -p /root/v4/out && rm -f /root/v4/DONE /root/v4/FAILED'
sscp "$BUILD_DIR/cross-out/igneumd" "$BUILD_DIR/cross-out/igneum-miner" "$BUILD_DIR/cross-out/version.txt" "$SSH_USER@$ip:/root/v4/out/"
sssh "$ip" 'chmod +x /root/v4/out/igneumd /root/v4/out/igneum-miner && /root/v4/out/igneumd --version | head -1 && touch /root/v4/DONE' | tee -a "$LOG"
install_v4
log "staged from the Mac. Nothing was switched: $(remote_status)"
exit 0
fi
case "$mode" in
status) remote_status ;;
wait) wait_build ;;
resume)
sssh "$ip" "test -f /root/v4/src.tar.gz" || die "nothing to resume on $name (no /root/v4/src.tar.gz; run start)"