diff --git a/infra/build-server/provision.sh b/infra/build-server/provision.sh new file mode 100644 index 000000000..f74e00178 --- /dev/null +++ b/infra/build-server/provision.sh @@ -0,0 +1,398 @@ +#!/usr/bin/env bash +# Provision igneum-build-1, the Hetzner dedicated build server (AX162-1-LTD: EPYC 9454P 48 cores / 96 threads, 128 GB, +# 2x 3.84 TB NVMe, Falkenstein; ordered 6 October 2026). Idempotent: every step checks before it changes anything and +# says "ok" (nothing to do) or "changed". Run it over ssh as root; nothing here reads a secret. +# +# infra/build-server/run-from-mac.sh the usual way (ships this file, fills WORKTREES, writes the host file) +# ssh root@ 'bash -s' < infra/build-server/provision.sh the bare way +# ssh root@ 'MODE=install bash -s' < infra/build-server/provision.sh force the rescue-system path +# +# Two modes, chosen by MODE (auto, install, provision; default auto): +# install the box booted into Hetzner's rescue system (installimage present, hostname rescue*): run installimage in +# batch mode for Ubuntu 24.04 with software RAID 1 over the two NVMe drives, no swap, the rescue system's +# authorized_keys taken over, the image signature checked, then reboot. Run the script again after the reboot. +# Ran on igneum-build-1 on 6 October 2026 at 17:16 to 17:20 UTC (16 steps, no prompt). +# provision a running Ubuntu 24.04: user `build` with root's key, the compiler and cross toolchains, rustup pinned to +# RUST_TOOLCHAIN with the x86_64-pc-windows-gnu target, sccache with a 100 GB disk cache, Node 22, git, tmux, +# a swap-free tuned sysctl, the two bare mirrors (/srv/igneum.git, /srv/igneum-node.git), /srv/builds with one +# directory per agent worktree, sshd key-only, ufw with 22 and the seed p2p ports. +# +# Settings (environment, all optional): +# RUST_TOOLCHAIN 1.99.0 the Mac's `rustc --version` on 6 October 2026. Neither the repo nor the fork carries a +# rust-toolchain file (checked 6 October 2026), so the pin lives here; the fork's Cargo.toml says +# rust-version 1.91.0. tools/build-remote.sh compares the two sides and refuses a mismatch. +# SCCACHE_GB 100 the local disk cache at /srv/sccache +# SCCACHE_VERSION (unset) a `cargo install sccache --version` pin; unset = the newest on crates.io +# NODE_MAJOR 22 +# WORKTREES "" space-separated agent worktree names, one /srv/builds/ each (run-from-mac.sh fills it from +# `git worktree list` on the Mac; tools/build-remote.sh creates a missing one on first use) +# SLOTS 1 remote build slots (tools/build-remote.sh takes one; with 1 slot every build gets the box) +# P2P_PORTS "26611 26811" TCP ports ufw opens beside 22: the devnet seed's p2p (infra/seed-nodes/config.sh devnet +# P2P_PORT=26611) and the testnet seed's (26811). A suffixed devnet (Devnet 2, the fleet's staging +# chain) listens on the same 26611 (infra/cloud-devnet/config.sh P2P_PORT=26611). RPC ports +# (26610, 28610, 26790 and the 268xx set) stay on loopback as on every seed, so they are not opened. +# BOX_HOSTNAME igneum-build-1 +# SSH_PUBKEY (unset) a public key line for the build user when root has no authorized_keys (installimage installs it) +# +# Mirrors: the Mac pushes to them (never a clone from GitHub; the fork vendor/igneum-node exists only on the Mac): +# git -C /Users/joshm/Projects/igneum remote add build build@:/srv/igneum.git +# git -C /Users/joshm/Projects/igneum/vendor/igneum-node remote add build build@:/srv/igneum-node.git +# git push build --all (tools/build-remote.sh pushes the branch it builds before every build) +# +# What this script does NOT do: install zig or cargo-zigbuild (the Mac's glibc 2.36 Linux cross-build, infra/cross/build-linux.sh, +# stays on the Mac until the box is proven; a native build here links glibc 2.39, which Debian 13 seeds accept and HiveOS +# does not), start any node, or copy a secret. The installimage flags and the image name were read from the live rescue system +# on 6 October 2026 (`installimage -h`, /root/.oldroot/nfs/images); the script still reads the image list instead of hard-coding a name. +set -euo pipefail + +MODE="${MODE:-auto}" +RUST_TOOLCHAIN="${RUST_TOOLCHAIN:-1.99.0}" +SCCACHE_GB="${SCCACHE_GB:-100}" +SCCACHE_VERSION="${SCCACHE_VERSION:-}" +NODE_MAJOR="${NODE_MAJOR:-22}" +WORKTREES="${WORKTREES:-}" +SLOTS="${SLOTS:-1}" +P2P_PORTS="${P2P_PORTS:-26611 26811}" +BOX_HOSTNAME="${BOX_HOSTNAME:-igneum-build-1}" +SSH_PUBKEY="${SSH_PUBKEY:-}" +BUILD_USER=build +BUILD_HOME=/home/$BUILD_USER + +log() { printf '%s provision: %s\n' "$(date -u +%H:%M:%S)" "$*"; } +die() { log "ERROR: $*" >&2; exit 1; } +changed() { log "$1: changed${2:+ ($2)}"; } +ok() { log "$1: ok${2:+ ($2)}"; } +as_build() { su - "$BUILD_USER" -c "$*"; } + +[ "$(id -u)" = 0 ] || die "run as root" + +# ---------------------------------------------------------------------------------------------------------------------- +# install mode: the rescue system +# ---------------------------------------------------------------------------------------------------------------------- +INSTALLIMAGE=/root/.oldroot/nfs/install/installimage # not on PATH in a non-interactive ssh shell (read 6 Oct 2026) + +in_rescue() { + [ -x "$INSTALLIMAGE" ] || return 1 + case "$(hostname)" in rescue*) return 0 ;; esac + [ -d /root/.oldroot/nfs/images ] +} + +do_install() { + local images drives image parts + images=/root/.oldroot/nfs/images + [ -d "$images" ] || die "no image directory at $images: not the Hetzner rescue system" + # the Ubuntu 24.04 (noble) amd64 base image the rescue system offers (read on 6 October 2026: Ubuntu-2404-noble-amd64-base.tar.zst + # with a detached .sig; read, not hard-coded, because Hetzner refreshes the names) + image=$(find "$images" -maxdepth 1 -type f -iregex '.*/ubuntu-2404.*amd64.*\.tar\.\(zst\|gz\|xz\)' -printf '%f\n' | sort | tail -1 || true) + [ -n "$image" ] || die "no Ubuntu 24.04 amd64 image under $images: $(find "$images" -maxdepth 1 -type f -printf '%f ' )" + mapfile -t drives < <(lsblk -dn -o NAME,TYPE | awk '$2 == "disk" && $1 ~ /^nvme/ { print $1 }' | sort) + [ "${#drives[@]}" = 2 ] || die "expected exactly two NVMe drives for RAID 1, found ${#drives[@]}: ${drives[*]:-none}" + [ -s /root/.ssh/authorized_keys ] || die "/root/.ssh/authorized_keys is empty in the rescue system; -t yes would carry nothing into the image" + [ -d /sys/firmware/efi ] || log "WARNING: no /sys/firmware/efi, the box booted in BIOS mode; the esp partition is harmless but grub goes to the MBR" + # no swap partition: 128 GB of RAM and a swap-free sysctl (the provision mode checks no swap is active) + parts="/boot/efi:esp:512M,/boot:ext4:1G,/:ext4:all" + log "installimage: image $image, drives ${drives[*]} as software RAID 1, partitions $parts, hostname $BOX_HOSTNAME, rescue ssh keys taken over (-t yes), image signature checked (-g)" + log "this WIPES ${drives[*]}" + # flag form, read from `installimage -h` on 6 October 2026: -a batch, -n hostname, -r raid, -l level, -i image, -g verify + # the detached signature, -p partitions mount:fs:size, -d drives, -t yes take over the rescue system's ssh keys (root's + # authorized_keys), -G yes new host keys. The -c config form forbids every other flag, so the keys could not travel with it. + TERM="${TERM:-xterm}" "$INSTALLIMAGE" -a -n "$BOX_HOSTNAME" -r yes -l 1 -i "$images/$image" -g -p "$parts" -d "$(IFS=,; echo "${drives[*]}")" -t yes -G yes + log "installimage finished; rebooting into Ubuntu. Run this script again (MODE=provision or auto) once ssh answers (the host key is new: -G yes)." + sync; reboot +} + +# ---------------------------------------------------------------------------------------------------------------------- +# provision mode: the installed Ubuntu +# ---------------------------------------------------------------------------------------------------------------------- +step_hostname() { + if [ "$(hostnamectl --static 2>/dev/null || hostname)" = "$BOX_HOSTNAME" ]; then ok hostname "$BOX_HOSTNAME"; return; fi + hostnamectl set-hostname "$BOX_HOSTNAME"; grep -q "$BOX_HOSTNAME" /etc/hosts || printf '127.0.1.1 %s\n' "$BOX_HOSTNAME" >> /etc/hosts + changed hostname "$BOX_HOSTNAME" +} + +step_os_check() { + . /etc/os-release + [ "${ID:-}" = ubuntu ] && [ "${VERSION_ID:-}" = 24.04 ] || die "this is ${PRETTY_NAME:-unknown}, not Ubuntu 24.04" + ok os "$PRETTY_NAME, $(nproc) threads, $(awk '/MemTotal/ { printf "%d GB", $2 / 1024 / 1024 }' /proc/meminfo)" +} + +# the proven Ubuntu 24.04 set: the PC build job's APT lists (app/igneum-app/src/jobbuild.rs: mingw-w64 posix threads so +# libstdc++ has std::thread for rocksdb, clang for librocksdb-sys's bindgen, protoc for the node's proto crates) plus the +# task's list (build-essential, clang, lld, pkg-config, libssl-dev, cmake, git, tmux) and what the scripts here call +APT_PACKAGES=( + build-essential clang lld llvm libclang-dev pkg-config libssl-dev cmake protobuf-compiler + gcc-mingw-w64-x86-64 g++-mingw-w64-x86-64 binutils-mingw-w64-x86-64 mingw-w64-x86-64-dev mingw-w64-tools + git tmux curl ca-certificates xz-utils zstd unzip rsync jq python3 ufw htop file +) +step_apt() { + local need=() p + for p in "${APT_PACKAGES[@]}"; do dpkg -s "$p" >/dev/null 2>&1 || need+=("$p"); done + if [ "${#need[@]}" = 0 ]; then ok apt "${#APT_PACKAGES[@]} packages present"; return; fi + export DEBIAN_FRONTEND=noninteractive + apt-get update -qq + apt-get install -y -qq --no-install-recommends "${need[@]}" + changed apt "installed ${need[*]}" +} + +step_mingw_alternatives() { + # Ubuntu ships -posix and -win32 variants behind update-alternatives; the Windows exes want posix threads (jobbuild.rs) + local tool want cur any=0 + for tool in gcc g++; do + want="/usr/bin/x86_64-w64-mingw32-$tool-posix" + cur=$(readlink -f "/etc/alternatives/x86_64-w64-mingw32-$tool" 2>/dev/null || true) + [ -x "$want" ] || die "no $want after apt" + if [ "$cur" != "$want" ]; then update-alternatives --set "x86_64-w64-mingw32-$tool" "$want" >/dev/null; any=1; fi + done + [ "$any" = 1 ] && changed mingw-alternatives "posix threads" || ok mingw-alternatives "posix threads" +} + +step_no_swap() { + local any=0 + if [ -n "$(swapon --noheadings --show 2>/dev/null)" ]; then swapoff -a; any=1; fi + if grep -qE '^[^#].*\sswap\s' /etc/fstab; then sed -i -E 's/^([^#].*\sswap\s.*)$/# \1 (disabled by infra\/build-server\/provision.sh)/' /etc/fstab; any=1; fi + [ "$any" = 1 ] && changed swap "off, fstab entry commented" || ok swap "none" +} + +step_sysctl() { + local f=/etc/sysctl.d/90-igneum-build.conf tmp + tmp=$(mktemp) + cat > "$tmp" <<'EOF' +# igneum-build-1: a compile box with no swap (infra/build-server/provision.sh) +vm.swappiness = 0 +vm.overcommit_memory = 0 +vm.dirty_ratio = 20 +vm.dirty_background_ratio = 5 +vm.max_map_count = 1048576 +fs.file-max = 4194304 +fs.inotify.max_user_watches = 1048576 +fs.inotify.max_user_instances = 8192 +kernel.pid_max = 4194304 +kernel.threads-max = 1048576 +net.core.somaxconn = 4096 +net.ipv4.tcp_fin_timeout = 15 +EOF + if [ -f "$f" ] && cmp -s "$tmp" "$f"; then rm -f "$tmp"; ok sysctl "$f"; return; fi + install -m 644 "$tmp" "$f"; rm -f "$tmp"; sysctl --system >/dev/null + changed sysctl "$f applied" +} + +step_limits() { + local f=/etc/security/limits.d/90-igneum-build.conf + if [ -f "$f" ]; then ok limits; return; fi + printf '%s soft nofile 1048576\n%s hard nofile 1048576\n%s soft nproc unlimited\n' "$BUILD_USER" "$BUILD_USER" "$BUILD_USER" > "$f" + changed limits "$f" +} + +step_user() { + local keys + if ! id -u "$BUILD_USER" >/dev/null 2>&1; then useradd -m -s /bin/bash -G users "$BUILD_USER"; changed user "$BUILD_USER created"; else ok user "$BUILD_USER"; fi + install -d -m 700 -o "$BUILD_USER" -g "$BUILD_USER" "$BUILD_HOME/.ssh" + if [ -n "$SSH_PUBKEY" ]; then keys="$SSH_PUBKEY"; elif [ -s /root/.ssh/authorized_keys ]; then keys=$(cat /root/.ssh/authorized_keys); else die "no key for $BUILD_USER: root has no authorized_keys and SSH_PUBKEY is unset"; fi + if [ -f "$BUILD_HOME/.ssh/authorized_keys" ] && [ "$(cat "$BUILD_HOME/.ssh/authorized_keys")" = "$keys" ]; then ok authorized_keys; else + printf '%s\n' "$keys" > "$BUILD_HOME/.ssh/authorized_keys"; chmod 600 "$BUILD_HOME/.ssh/authorized_keys"; chown "$BUILD_USER:$BUILD_USER" "$BUILD_HOME/.ssh/authorized_keys" + changed authorized_keys "$(printf '%s\n' "$keys" | grep -c .) key(s) from root" + fi +} + +worktree_count() { find /srv/builds -mindepth 1 -maxdepth 1 -type d -not -name '_*' | wc -l | tr -d ' '; } + +step_dirs() { + local d any=0 + for d in /srv/builds /srv/builds/_locks /srv/sccache /srv/artefacts; do + if [ ! -d "$d" ]; then install -d -m 755 -o "$BUILD_USER" -g "$BUILD_USER" "$d"; any=1; fi + done + if [ ! -f /srv/builds/_locks/slots ] || [ "$(cat /srv/builds/_locks/slots)" != "$SLOTS" ]; then printf '%s\n' "$SLOTS" > /srv/builds/_locks/slots; chown "$BUILD_USER:$BUILD_USER" /srv/builds/_locks/slots; any=1; fi + for d in $WORKTREES; do + case "$d" in */*|.*|_*) die "worktree name '$d' is not a plain directory name" ;; esac + if [ ! -d "/srv/builds/$d" ]; then install -d -m 755 -o "$BUILD_USER" -g "$BUILD_USER" "/srv/builds/$d"; any=1; fi + done + [ "$any" = 1 ] && changed dirs "/srv/builds ($(worktree_count) worktree dirs), /srv/sccache, slots=$SLOTS" || ok dirs "$(worktree_count) worktree dirs, slots=$SLOTS" +} + +step_mirrors() { + local r any=0 + for r in /srv/igneum.git /srv/igneum-node.git; do + if [ ! -d "$r" ]; then install -d -m 755 -o "$BUILD_USER" -g "$BUILD_USER" "$r"; as_build "git init -q --bare -b master $r"; any=1; fi + done + as_build "git config --global --get safe.directory >/dev/null 2>&1 || git config --global --add safe.directory '*'" + as_build "git config --global init.defaultBranch master; git config --global gc.auto 0" + [ "$any" = 1 ] && changed mirrors "bare /srv/igneum.git and /srv/igneum-node.git (push from the Mac, see the header)" || ok mirrors +} + +step_rustup() { + local cargo="$BUILD_HOME/.cargo/bin/cargo" rustup="$BUILD_HOME/.cargo/bin/rustup" any=0 t + if [ ! -x "$rustup" ]; then + as_build "curl -fsSL https://sh.rustup.rs | sh -s -- -y --profile minimal --no-modify-path --default-toolchain $RUST_TOOLCHAIN" >/dev/null + any=1 + fi + if ! as_build "$rustup toolchain list" | grep -q "^$RUST_TOOLCHAIN-"; then as_build "$rustup toolchain install $RUST_TOOLCHAIN --profile minimal" >/dev/null; any=1; fi + if [ "$(as_build "$rustup default" | cut -d- -f1)" != "$RUST_TOOLCHAIN" ]; then as_build "$rustup default $RUST_TOOLCHAIN" >/dev/null; any=1; fi + for t in x86_64-pc-windows-gnu x86_64-unknown-linux-gnu; do + as_build "$rustup target list --installed --toolchain $RUST_TOOLCHAIN" | grep -qx "$t" || { as_build "$rustup target add $t --toolchain $RUST_TOOLCHAIN" >/dev/null; any=1; } + done + as_build "$rustup component list --installed --toolchain $RUST_TOOLCHAIN" | grep -q '^clippy' || { as_build "$rustup component add clippy rustfmt --toolchain $RUST_TOOLCHAIN" >/dev/null; any=1; } + [ "$any" = 1 ] && changed rustup "$(as_build "$cargo --version"), targets: $(as_build "$rustup target list --installed" | tr '\n' ' ')" || ok rustup "$(as_build "$cargo --version"), targets: $(as_build "$rustup target list --installed" | tr '\n' ' ')" +} + +step_sccache() { + local cargo="$BUILD_HOME/.cargo/bin/cargo" bin="$BUILD_HOME/.cargo/bin/sccache" cfgdir="$BUILD_HOME/.config/sccache" any=0 bytes tmp + if [ ! -x "$bin" ] || { [ -n "$SCCACHE_VERSION" ] && ! "$bin" --version | grep -q " $SCCACHE_VERSION\$"; }; then + as_build "$cargo install sccache --locked ${SCCACHE_VERSION:+--version $SCCACHE_VERSION}" >/dev/null 2>&1 || as_build "$cargo install sccache ${SCCACHE_VERSION:+--version $SCCACHE_VERSION}" >/dev/null + any=1 + fi + bytes=$(( SCCACHE_GB * 1024 * 1024 * 1024 )) + install -d -m 755 -o "$BUILD_USER" -g "$BUILD_USER" "$cfgdir" + tmp=$(mktemp) + printf '[cache.disk]\ndir = "/srv/sccache"\nsize = %s\n' "$bytes" > "$tmp" + if ! cmp -s "$tmp" "$cfgdir/config"; then install -m 644 -o "$BUILD_USER" -g "$BUILD_USER" "$tmp" "$cfgdir/config"; any=1; fi + rm -f "$tmp" + [ "$any" = 1 ] && changed sccache "$(as_build "$bin --version"), disk cache /srv/sccache, $SCCACHE_GB GB" || ok sccache "$(as_build "$bin --version"), /srv/sccache $SCCACHE_GB GB" +} + +step_cargo_config() { + # the build user's cargo defaults: sccache in front of rustc, 90 jobs (96 threads, 6 left for ssh, rsync and the + # system), lld for the native target through clang. The Windows target's compilers and flags are NOT here: they are + # set per build by tools/cross-remote.sh, the same variables as the Mac's proto-cuda/windows-node/cross-build.sh and + # the PC's jobbuild.rs, so a build's flags are visible in the script that runs it. + local f="$BUILD_HOME/.cargo/config.toml" tmp + tmp=$(mktemp) + cat > "$tmp" <<'EOF' +# igneum-build-1 (infra/build-server/provision.sh) +[build] +rustc-wrapper = "/home/build/.cargo/bin/sccache" +jobs = 90 + +[target.x86_64-unknown-linux-gnu] +linker = "clang" +rustflags = ["-C", "link-arg=-fuse-ld=lld"] + +[net] +git-fetch-with-cli = true +EOF + if cmp -s "$tmp" "$f"; then rm -f "$tmp"; ok cargo-config "$f"; return; fi + install -m 644 -o "$BUILD_USER" -g "$BUILD_USER" "$tmp" "$f"; rm -f "$tmp" + changed cargo-config "$f" +} + +step_profile() { + # sourced by tools/build-remote.sh's remote script (a non-login ssh shell reads no profile) and by login shells + local f=/etc/profile.d/igneum-build.sh tmp + tmp=$(mktemp) + cat > "$tmp" </dev/null || true) + case "$have" in v$NODE_MAJOR.*) ok node "$have"; return ;; esac + # the newest $NODE_MAJOR release from nodejs.org, checked against its SHASUMS256.txt (https, the official host) + shasums=$(curl -fsSL "https://nodejs.org/dist/latest-v$NODE_MAJOR.x/SHASUMS256.txt") + tarball=$(printf '%s\n' "$shasums" | awk '$2 ~ /linux-x64\.tar\.xz$/ { print $2 }' | head -1) + [ -n "$tarball" ] || die "no linux-x64 tarball in the Node $NODE_MAJOR SHASUMS" + ver=${tarball#node-}; ver=${ver%-linux-x64.tar.xz} + dir=/usr/local/lib/nodejs + install -d "$dir" + ( cd "$dir" && curl -fsSLO "https://nodejs.org/dist/latest-v$NODE_MAJOR.x/$tarball" && printf '%s\n' "$shasums" | grep " $tarball\$" | sha256sum -c --quiet - && tar -xJf "$tarball" && rm -f "$tarball" ) + ln -sfn "$dir/node-$ver-linux-x64/bin/node" /usr/local/bin/node + ln -sfn "$dir/node-$ver-linux-x64/bin/npm" /usr/local/bin/npm + ln -sfn "$dir/node-$ver-linux-x64/bin/npx" /usr/local/bin/npx + changed node "$(/usr/local/bin/node --version) from nodejs.org (sha256 checked)" +} + +step_sshd() { + local f=/etc/ssh/sshd_config.d/10-igneum-build.conf tmp + tmp=$(mktemp) + cat > "$tmp" <<'EOF' +# igneum-build-1 (infra/build-server/provision.sh): keys only +PasswordAuthentication no +KbdInteractiveAuthentication no +ChallengeResponseAuthentication no +PubkeyAuthentication yes +PermitRootLogin prohibit-password +PermitEmptyPasswords no +X11Forwarding no +MaxAuthTries 4 +ClientAliveInterval 60 +ClientAliveCountMax 10 +EOF + if [ -f "$f" ] && cmp -s "$tmp" "$f"; then rm -f "$tmp"; ok sshd "$f"; return; fi + install -m 644 "$tmp" "$f"; rm -f "$tmp" + # Hetzner's installimage may leave a cloud-init drop-in that sets PasswordAuthentication yes; the lowest-numbered file wins + if [ -f /etc/ssh/sshd_config.d/50-cloud-init.conf ] && grep -qi '^PasswordAuthentication yes' /etc/ssh/sshd_config.d/50-cloud-init.conf; then + sed -i 's/^PasswordAuthentication yes/PasswordAuthentication no/' /etc/ssh/sshd_config.d/50-cloud-init.conf + fi + sshd -t || die "sshd -t rejected the configuration; the drop-in $f was NOT activated" + systemctl reload ssh 2>/dev/null || systemctl reload sshd + changed sshd "key-only, root prohibit-password" +} + +step_ufw() { + local p want=() any=0 status + status=$(ufw status verbose 2>/dev/null || true) + grep -q 'Default: deny (incoming), allow (outgoing)' <<<"$status" || { ufw --force default deny incoming >/dev/null; ufw --force default allow outgoing >/dev/null; any=1; } + want=(22) + for p in $P2P_PORTS; do want+=("$p"); done + for p in "${want[@]}"; do + grep -qE "^$p/tcp +ALLOW IN +Anywhere *$" <<<"$status" || { ufw allow "$p/tcp" >/dev/null; any=1; } + done + grep -q '^Status: active' <<<"$status" || { ufw --force enable >/dev/null; any=1; } + [ "$any" = 1 ] && changed ufw "22 and ${P2P_PORTS} open, everything else denied" || ok ufw "22 and ${P2P_PORTS}" +} + +step_summary() { + log "summary:" + { + printf 'host %s, %s threads, %s RAM, root fs %s free\n' "$(hostname)" "$(nproc)" "$(awk '/MemTotal/ { printf "%d GB", $2 / 1024 / 1024 }' /proc/meminfo)" "$(df -h / | awk 'NR == 2 { print $4 }')" + printf 'raid: %s\n' "$(grep -E '^md' /proc/mdstat 2>/dev/null | tr '\n' ';' || echo none)" + printf 'rust: %s | %s | targets %s\n' "$(as_build "$BUILD_HOME/.cargo/bin/rustc --version")" "$(as_build "$BUILD_HOME/.cargo/bin/cargo --version")" "$(as_build "$BUILD_HOME/.cargo/bin/rustup target list --installed" | tr '\n' ' ')" + printf 'sccache: %s, %s\n' "$(as_build "$BUILD_HOME/.cargo/bin/sccache --version")" "$(cat "$BUILD_HOME/.config/sccache/config" | tr '\n' ' ')" + printf 'mingw: %s\n' "$(x86_64-w64-mingw32-gcc-posix --version | head -1)" + printf 'clang: %s | lld: %s\n' "$(clang --version | head -1)" "$(ld.lld --version | head -1)" + printf 'node: %s | git: %s | tmux: %s\n' "$(/usr/local/bin/node --version)" "$(git --version)" "$(tmux -V)" + printf 'swap: %s\n' "$(swapon --noheadings --show 2>/dev/null | wc -l | awk '{ print ($1 == 0) ? "none" : $1 " device(s) ACTIVE" }')" + printf 'mirrors: /srv/igneum.git (%s) /srv/igneum-node.git (%s)\n' "$(as_build 'git -C /srv/igneum.git branch --list | wc -l') branches" "$(as_build 'git -C /srv/igneum-node.git branch --list | wc -l') branches" + printf 'builds: %s worktree dirs under /srv/builds, %s slot(s)\n' "$(worktree_count)" "$(cat /srv/builds/_locks/slots)" + printf 'ufw: %s\n' "$(ufw status | grep -E 'ALLOW' | awk '{ print $1 }' | tr '\n' ' ')" + printf 'ssh line: ssh -i ~/.ssh/igneum_ed25519 build@%s\n' "$(hostname -I 2>/dev/null | awk '{ print $1 }')" + } | sed 's/^/ /' +} + +do_provision() { + step_os_check + step_hostname + step_apt + step_mingw_alternatives + step_no_swap + step_sysctl + step_limits + step_user + step_dirs + step_mirrors + step_rustup + step_sccache + step_cargo_config + step_profile + step_node + step_sshd + step_ufw + step_summary + log "done" +} + +case "$MODE" in + install) do_install ;; + provision) do_provision ;; + auto) if in_rescue; then log "rescue system detected: install mode"; do_install; else do_provision; fi ;; + *) die "MODE must be auto, install or provision" ;; +esac