Build server: infra/build-server/provision.sh (installimage mode run on igneum-build-1, provision mode for Ubuntu 24.04)
Idempotent provisioning of the Hetzner AX162 build box: install mode (rescue system, Ubuntu 24.04, software RAID 1 over the two NVMe drives, no swap, rescue keys taken over, run 6 October 2026 17:16 to 17:20 UTC) and provision mode (user build, compilers, mingw-w64 posix, rustup 1.99.0 with the Windows target, sccache 100 GB, Node 22, bare mirrors, /srv/builds, sshd key-only, ufw 22 + seed p2p ports). shellcheck clean. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
This commit is contained in:
parent
0be505b8e2
commit
f83f8df1c7
1 changed files with 398 additions and 0 deletions
398
infra/build-server/provision.sh
Normal file
398
infra/build-server/provision.sh
Normal file
|
|
@ -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 <ip> the usual way (ships this file, fills WORKTREES, writes the host file)
|
||||
# ssh root@<ip> 'bash -s' < infra/build-server/provision.sh the bare way
|
||||
# ssh root@<ip> '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/<name> 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@<ip>:/srv/igneum.git
|
||||
# git -C /Users/joshm/Projects/igneum/vendor/igneum-node remote add build build@<ip>:/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" <<EOF
|
||||
# igneum-build-1 (infra/build-server/provision.sh)
|
||||
export PATH="/home/build/.cargo/bin:/usr/local/bin:\$PATH"
|
||||
export SCCACHE_DIR=/srv/sccache
|
||||
export SCCACHE_CACHE_SIZE=${SCCACHE_GB}G
|
||||
export CARGO_INCREMENTAL=0
|
||||
export IGNEUM_BUILD_SLOTS_DIR=/srv/builds/_locks
|
||||
export IGNEUM_BUILD_ROOT=/srv/builds
|
||||
export IGNEUM_RUST_TOOLCHAIN=$RUST_TOOLCHAIN
|
||||
EOF
|
||||
if [ -f "$f" ] && cmp -s "$tmp" "$f"; then rm -f "$tmp"; ok profile "$f"; return; fi
|
||||
install -m 644 "$tmp" "$f"; rm -f "$tmp"; changed profile "$f"
|
||||
}
|
||||
|
||||
step_node() {
|
||||
local want have shasums tarball ver dir
|
||||
have=$(/usr/local/bin/node --version 2>/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
|
||||
Loading…
Reference in a new issue