From 7b70556e5e52936d531866952600b94a068e47b3 Mon Sep 17 00:00:00 2001 From: Vijit Singh Date: Sun, 7 Jun 2026 02:35:54 -0500 Subject: [PATCH 1/3] ci: pin a checksum-verified shellcheck (#6) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The lint job used the runner's preinstalled shellcheck, so results could drift with the runner image. Install a pinned shellcheck v0.10.0 from the upstream static release with a sha256 check (no apt-get update — avoids the mirror flakiness pithead hit), then lint with it. Part of #6. The test-suite job already runs in CI (added in #26); an shfmt formatting check is still outstanding (deferred — needs a one-time formatting pass) so #6 stays open. Co-Authored-By: Claude Opus 4.8 --- .github/workflows/ci.yml | 20 +++++++++++++++++--- 1 file changed, 17 insertions(+), 3 deletions(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 7bbde53..17cf8ee 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -7,12 +7,26 @@ on: jobs: lint: - name: ShellCheck + name: ShellCheck (pinned) runs-on: ubuntu-24.04 + env: + SHELLCHECK_VERSION: "0.10.0" + SHELLCHECK_SHA256: "6c881ab0698e4e6ea235245f22832860544f17ba386442fe7e9d629f8cbedf87" steps: - uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2 - # shellcheck is preinstalled on ubuntu runners; invoke it directly (avoid `apt-get update`, - # which intermittently fails when an unrelated third-party mirror is briefly out of sync). + # Install a PINNED, checksum-verified shellcheck instead of the runner's preinstalled build, so + # lint results are reproducible and don't drift with the runner image (#6). Downloaded straight + # from the upstream static release — no `apt-get update`, avoiding the mirror flakiness pithead + # hit in its #64. + - name: Install pinned ShellCheck + run: | + set -euo pipefail + tarball="shellcheck-v${SHELLCHECK_VERSION}.linux.x86_64.tar.xz" + curl -fsSL "https://github.com/koalaman/shellcheck/releases/download/v${SHELLCHECK_VERSION}/${tarball}" -o "$tarball" + echo "${SHELLCHECK_SHA256} ${tarball}" | sha256sum -c - + tar -xJf "$tarball" + sudo install "shellcheck-v${SHELLCHECK_VERSION}/shellcheck" /usr/local/bin/shellcheck + shellcheck --version - name: Run ShellCheck run: shellcheck --severity=warning rigforge.sh util/proposed-grub.sh tests/run.sh tests/e2e/run.sh tests/e2e/in-container.sh From b4206d1618015d8972c0429599b1e74d0a6fe085 Mon Sep 17 00:00:00 2001 From: Vijit Singh Date: Sun, 7 Jun 2026 08:23:36 -0500 Subject: [PATCH 2/3] style: format shell scripts with shfmt -i 4 Mechanical reformat only (no behaviour change) so the codebase passes the shfmt check added alongside. Verified: shellcheck clean, shfmt clean, full test suite green before and after. Co-Authored-By: Claude Opus 4.8 --- rigforge.sh | 109 +++++----- tests/e2e/in-container.sh | 88 ++++---- tests/run.sh | 425 +++++++++++++++++++++++--------------- util/proposed-grub.sh | 2 +- 4 files changed, 363 insertions(+), 261 deletions(-) diff --git a/rigforge.sh b/rigforge.sh index cb20ddf..f551b1d 100755 --- a/rigforge.sh +++ b/rigforge.sh @@ -15,11 +15,14 @@ readonly C_RED='\033[1;31m' log() { echo -e "${C_GREEN}[INFO]${C_RESET} $1"; } warn() { echo -e "${C_YELLOW}[WARN]${C_RESET} $1"; } -error() { echo -e "${C_RED}[ERROR]${C_RESET} $1"; exit 1; } +error() { + echo -e "${C_RED}[ERROR]${C_RESET} $1" + exit 1 +} # --- Global Variables --- OS_TYPE="$(uname -s)" -SCRIPT_DIR="$( cd "$( dirname "${BASH_SOURCE[0]}" )" &> /dev/null && pwd )" +SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" &>/dev/null && pwd)" REAL_USER="${SUDO_USER:-${USER:-$(id -un)}}" CONFIG_JSON="$SCRIPT_DIR/config.json" TEMPLATE_JSON="$SCRIPT_DIR/config.json.template" @@ -53,14 +56,14 @@ if [ "${BASH_SOURCE[0]}" != "${0}" ]; then _RIGFORGE_SOURCED=1; fi # Uses sudo so it works on root-owned system files; harmless when the file is user-writable. append_once() { local file="$1" line="$2" - grep -qFx "$line" "$file" 2>/dev/null || echo "$line" | sudo tee -a "$file" > /dev/null + grep -qFx "$line" "$file" 2>/dev/null || echo "$line" | sudo tee -a "$file" >/dev/null } check_prerequisites() { log "Verifying system prerequisites..." - if ! command -v jq &> /dev/null; then + if ! command -v jq &>/dev/null; then if [ "$OS_TYPE" == "Darwin" ]; then - if command -v brew &> /dev/null; then + if command -v brew &>/dev/null; then log "Installing prerequisite: jq..." brew install jq else @@ -68,11 +71,11 @@ check_prerequisites() { fi else log "Installing prerequisite: jq..." - if command -v apt-get &> /dev/null; then + if command -v apt-get &>/dev/null; then sudo apt-get update -qq && sudo apt-get install -y -qq jq - elif command -v dnf &> /dev/null; then + elif command -v dnf &>/dev/null; then sudo dnf install -y -q jq - elif command -v pacman &> /dev/null; then + elif command -v pacman &>/dev/null; then sudo pacman -Sy --noconfirm jq else error "jq is required and no supported package manager was found. Please install jq manually." @@ -87,12 +90,12 @@ ensure_config_exists() { read -r -p "Create a minimal configuration now? (y/N): " CREATE_CONF if [[ "$CREATE_CONF" =~ ^[Yy] ]]; then log "Starting interactive setup..." - + # Load defaults from template if available local default_home="DYNAMIC_HOME" local default_donation=1 local default_config_file="./worker-config/example-config.json.template" - + if [ -f "$TEMPLATE_JSON" ]; then default_home=$(jq -r '.HOME_DIR // "DYNAMIC_HOME"' "$TEMPLATE_JSON") default_donation=$(jq -r '.DONATION // 1' "$TEMPLATE_JSON") @@ -100,12 +103,12 @@ ensure_config_exists() { fi read -r -p "Enter P2Pool Node Hostname/IP: " IN_HOSTNAME - + if [ -z "$IN_HOSTNAME" ]; then error "Hostname is required. Aborting." fi - cat < "$CONFIG_JSON" + cat <"$CONFIG_JSON" { "HOME_DIR": "$default_home", "DONATION": $default_donation, @@ -164,7 +167,7 @@ parse_config() { prepare_workspace() { log "Preparing workspace at $WORKER_ROOT..." - + if [ ! -d "$WORKER_ROOT" ]; then mkdir -p "$WORKER_ROOT" 2>/dev/null || sudo mkdir -p "$WORKER_ROOT" fi @@ -193,7 +196,7 @@ prepare_workspace() { install_dependencies() { if [ "$OS_TYPE" == "Darwin" ]; then log "Installing macOS dependencies..." - if command -v brew &> /dev/null; then + if command -v brew &>/dev/null; then if [ "${EUID:-$(id -u)}" -eq 0 ]; then # Drop privileges for Homebrew if running as root sudo -u "$REAL_USER" brew install cmake libuv openssl hwloc @@ -208,21 +211,21 @@ install_dependencies() { local install_cmd="" local check_cmd="" - if command -v apt-get &> /dev/null; then + if command -v apt-get &>/dev/null; then dependencies="git build-essential cmake libuv1-dev libssl-dev libhwloc-dev gettext-base" if [ "$OS_TYPE" == "Linux" ]; then dependencies="$dependencies linux-tools-common" - if apt-cache show "linux-tools-$(uname -r)" &> /dev/null; then + if apt-cache show "linux-tools-$(uname -r)" &>/dev/null; then dependencies="$dependencies linux-tools-$(uname -r)" fi fi install_cmd="sudo apt-get update -qq && sudo DEBIAN_FRONTEND=noninteractive apt-get install -y -qq -o Dpkg::Options::='--force-confdef' -o Dpkg::Options::='--force-confold'" check_cmd="dpkg -s" - elif command -v dnf &> /dev/null; then + elif command -v dnf &>/dev/null; then dependencies="git cmake libuv-devel openssl-devel hwloc-devel gettext gcc gcc-c++ make automake kernel-devel" install_cmd="sudo dnf install -y" check_cmd="rpm -q" - elif command -v pacman &> /dev/null; then + elif command -v pacman &>/dev/null; then dependencies="git cmake libuv openssl hwloc gettext base-devel" install_cmd="sudo pacman -Sy --noconfirm --needed" check_cmd="pacman -Qi" @@ -233,7 +236,7 @@ install_dependencies() { local missing_deps="" for dep in $dependencies; do - if ! command -v "$dep" &> /dev/null && ! $check_cmd "$dep" &> /dev/null; then + if ! command -v "$dep" &>/dev/null && ! $check_cmd "$dep" &>/dev/null; then missing_deps="$missing_deps $dep" fi done @@ -270,16 +273,16 @@ compile_xmrig() { log "Compiling binary (Concurrency: $CORES threads)..." mkdir -p xmrig/build && cd xmrig/build # macOS often needs explicit OpenSSL root for cmake if installed via brew - cmake .. -DWITH_HWLOC=ON -DOPENSSL_ROOT_DIR="$(brew --prefix openssl)" &> /dev/null + cmake .. -DWITH_HWLOC=ON -DOPENSSL_ROOT_DIR="$(brew --prefix openssl)" &>/dev/null else sed -i "s/DonateLevel = 1;/DonateLevel = $DONATION;/g" xmrig/src/donate.h CORES=$(nproc) log "Compiling binary (Concurrency: $CORES threads)..." mkdir -p xmrig/build && cd xmrig/build - cmake .. -DWITH_HWLOC=ON &> /dev/null + cmake .. -DWITH_HWLOC=ON &>/dev/null fi - make -j$CORES &> /dev/null + make -j$CORES &>/dev/null } generate_xmrig_config() { @@ -329,13 +332,13 @@ generate_xmrig_config() { HTTP_RESTRICTED="true" HTTP_HOST="::" JIT="false" - + # Generate rx array [-1, -1, ...] based on core count CORES=$(sysctl -n hw.ncpu) THREADS="[" - for ((i=0; i config.json + "$TEMPLATE_CONFIG" >config.json if [ "$OS_TYPE" == "Linux" ]; then log "Configuring log rotation policy..." # Install logrotate configuration - sudo tee "$LOGROTATE_DIR/xmrig" > /dev/null </dev/null < /dev/null + envsubst '$BUILD_DIR $CPUPOWER_PATH' <"$SCRIPT_DIR/systemd/xmrig.service.template" | sudo tee "$SYSTEMD_DIR/xmrig.service" >/dev/null # Reload systemd daemon sudo systemctl daemon-reload @@ -476,7 +479,7 @@ tune_kernel() { log "Enabling MSR module for hardware prefetcher tuning..." sudo modprobe msr 2>/dev/null || true if [ -d "$MODULES_LOAD_DIR" ]; then - echo "msr" | sudo tee "$MODULES_LOAD_DIR/msr.conf" > /dev/null + echo "msr" | sudo tee "$MODULES_LOAD_DIR/msr.conf" >/dev/null elif [ -f "$MODULES_FILE" ]; then append_once "$MODULES_FILE" "msr" fi @@ -572,4 +575,4 @@ main() { if [ "$_RIGFORGE_SOURCED" = "0" ]; then main "$@" -fi \ No newline at end of file +fi diff --git a/tests/e2e/in-container.sh b/tests/e2e/in-container.sh index 05b4c8a..396540b 100644 --- a/tests/e2e/in-container.sh +++ b/tests/e2e/in-container.sh @@ -8,11 +8,17 @@ set -uo pipefail PASS=0 FAIL=0 -ok() { PASS=$((PASS + 1)); printf ' \033[1;32m✓\033[0m %s\n' "$1"; } -bad() { FAIL=$((FAIL + 1)); printf ' \033[1;31m✗\033[0m %s\n %s\n' "$1" "$2"; } -assert_rc() { if [ "$2" = "$3" ]; then ok "$1"; else bad "$1" "expected rc $3, got $2"; fi; } -assert_eq() { if [ "$2" = "$3" ]; then ok "$1"; else bad "$1" "expected [$3], got [$2]"; fi; } -assert_contains() { case "$2" in *"$3"*) ok "$1" ;; *) bad "$1" "[$2] missing [$3]" ;; esac; } +ok() { + PASS=$((PASS + 1)) + printf ' \033[1;32m✓\033[0m %s\n' "$1" +} +bad() { + FAIL=$((FAIL + 1)) + printf ' \033[1;31m✗\033[0m %s\n %s\n' "$1" "$2" +} +assert_rc() { if [ "$2" = "$3" ]; then ok "$1"; else bad "$1" "expected rc $3, got $2"; fi; } +assert_eq() { if [ "$2" = "$3" ]; then ok "$1"; else bad "$1" "expected [$3], got [$2]"; fi; } +assert_contains() { case "$2" in *"$3"*) ok "$1" ;; *) bad "$1" "[$2] missing [$3]" ;; esac } # 1. Real prerequisites: jq + envsubst (gettext). Installed with the REAL apt before stubs go on # PATH, so the script's own dependency step is the only thing we stub out. @@ -25,22 +31,29 @@ apt-get update -qq >/dev/null && apt-get install -y -qq jq gettext-base >/dev/nu # 2. Writable copy of the repo (/src is mounted read-only). WORK=/work cp -a /src "$WORK" -cd "$WORK" || { echo "cannot enter $WORK"; exit 1; } +cd "$WORK" || { + echo "cannot enter $WORK" + exit 1 +} # 3. Seed the system files the deploy expects to edit (base images ship none of these). mkdir -p /etc/modules-load.d /etc/default /etc/systemd/system -: > /etc/fstab -printf 'GRUB_CMDLINE_LINUX_DEFAULT="quiet splash"\n' > /etc/default/grub -[ -f /etc/security/limits.conf ] || { mkdir -p /etc/security; : > /etc/security/limits.conf; } +: >/etc/fstab +printf 'GRUB_CMDLINE_LINUX_DEFAULT="quiet splash"\n' >/etc/default/grub +[ -f /etc/security/limits.conf ] || { + mkdir -p /etc/security + : >/etc/security/limits.conf +} # 4. Stubs for the heavy/privileged/hardware bits. Passthrough sudo so real writes land in /etc. # Hardware detection is stubbed for a deterministic EPYC profile. -STUBS="$WORK/.stubs"; mkdir -p "$STUBS" -cat > "$STUBS/sudo" <<'X' +STUBS="$WORK/.stubs" +mkdir -p "$STUBS" +cat >"$STUBS/sudo" <<'X' #!/usr/bin/env bash exec "$@" X -cat > "$STUBS/git" <<'X' +cat >"$STUBS/git" <<'X' #!/usr/bin/env bash case "$*" in *rev-parse*) echo "${XMRIG_COMMIT:-}" ;; # #18 verifies the cloned commit @@ -48,17 +61,17 @@ case "$*" in esac exit 0 X -cat > "$STUBS/lscpu" <<'X' +cat >"$STUBS/lscpu" <<'X' #!/usr/bin/env bash echo "Model name: AMD EPYC 7763 64-Core Processor" echo "L3 cache: 256 MiB" echo "Socket(s): 2" X -printf '#!/usr/bin/env bash\necho 8\n' > "$STUBS/nproc" -printf '#!/usr/bin/env bash\necho poolbox\n' > "$STUBS/hostname" +printf '#!/usr/bin/env bash\necho 8\n' >"$STUBS/nproc" +printf '#!/usr/bin/env bash\necho poolbox\n' >"$STUBS/hostname" # No-op the rest (sysctl -w / mount / systemctl etc. cannot run unprivileged in a container). for c in cmake make systemctl modprobe mount cpupower update-grub sysctl dpkg; do - printf '#!/usr/bin/env bash\nexit 0\n' > "$STUBS/$c" + printf '#!/usr/bin/env bash\nexit 0\n' >"$STUBS/$c" done chmod +x "$STUBS"/* export PATH="$STUBS:$PATH" @@ -68,7 +81,7 @@ export XMRIG_VERSION="vTEST" XMRIG_COMMIT="testcommit000000000000000000000000000 # 5. Seed config.json (writable HOME_DIR; DONATION 7). Use an explicit (dotted) host so this doesn't # depend on the .local/mDNS appending that PR #15 removes. -cat > "$WORK/config.json" <"$WORK/config.json" </dev/null)" "DonateLevel = 7;" -assert_contains "build: verified pinned commit" "$out1" "Verified XMRig" -assert_eq "deploy: pool url from hostname" "$(jq -r '.pools[0].url' "$BUILD/config.json" 2>/dev/null)" "poolbox.lan:3333" -assert_eq "deploy: EPYC numa applied" "$(jq -r '.randomx.numa' "$BUILD/config.json" 2>/dev/null)" "true" -assert_eq "deploy: donate-level = 7" "$(jq -r '.["donate-level"]' "$BUILD/config.json" 2>/dev/null)" "7" +assert_contains "donate.h patched by real sed" "$(cat "$WORK/data-home/worker/xmrig/src/donate.h" 2>/dev/null)" "DonateLevel = 7;" +assert_contains "build: verified pinned commit" "$out1" "Verified XMRig" +assert_eq "deploy: pool url from hostname" "$(jq -r '.pools[0].url' "$BUILD/config.json" 2>/dev/null)" "poolbox.lan:3333" +assert_eq "deploy: EPYC numa applied" "$(jq -r '.randomx.numa' "$BUILD/config.json" 2>/dev/null)" "true" +assert_eq "deploy: donate-level = 7" "$(jq -r '.["donate-level"]' "$BUILD/config.json" 2>/dev/null)" "7" assert_contains "service rendered by real envsubst" "$(cat /etc/systemd/system/xmrig.service 2>/dev/null)" "$BUILD" -assert_contains "limits: fstab hugepages written" "$(cat /etc/fstab)" "hugetlbfs /dev/hugepages" -assert_contains "limits: memlock written" "$(cat /etc/security/limits.conf)" "soft memlock unlimited" -assert_contains "grub: hugepages configured" "$(cat /etc/default/grub)" "hugepages" +assert_contains "limits: fstab hugepages written" "$(cat /etc/fstab)" "hugetlbfs /dev/hugepages" +assert_contains "limits: memlock written" "$(cat /etc/security/limits.conf)" "soft memlock unlimited" +assert_contains "grub: hugepages configured" "$(cat /etc/default/grub)" "hugepages" if [ "$ARCH" = x86_64 ]; then assert_contains "kernel: msr module enabled (x86)" "$(cat /etc/modules-load.d/msr.conf 2>/dev/null)" "msr" else @@ -96,15 +110,16 @@ fi cp "$BUILD/config.json" "$WORK/config-run1.json" echo "== second run (idempotency) ==" -out2="$(./rigforge.sh &1)"; rc2=$? -assert_rc "second run exits 0" "$rc2" "0" +out2="$(./rigforge.sh &1)" +rc2=$? +assert_rc "second run exits 0" "$rc2" "0" [ "$rc2" = 0 ] || printf '%s\n' "$out2" | tail -20 -assert_eq "fstab: hugepages line not doubled" "$(grep -c 'hugetlbfs /dev/hugepages ' /etc/fstab)" "1" -assert_eq "fstab: 1G line not doubled" "$(grep -c 'hugetlbfs_1g ' /etc/fstab)" "1" -assert_eq "limits: soft line not doubled" "$(grep -c 'soft memlock unlimited' /etc/security/limits.conf)" "1" -assert_eq "grub: single cmdline entry" "$(grep -c '^GRUB_CMDLINE_LINUX_DEFAULT=' /etc/default/grub)" "1" +assert_eq "fstab: hugepages line not doubled" "$(grep -c 'hugetlbfs /dev/hugepages ' /etc/fstab)" "1" +assert_eq "fstab: 1G line not doubled" "$(grep -c 'hugetlbfs_1g ' /etc/fstab)" "1" +assert_eq "limits: soft line not doubled" "$(grep -c 'soft memlock unlimited' /etc/security/limits.conf)" "1" +assert_eq "grub: single cmdline entry" "$(grep -c '^GRUB_CMDLINE_LINUX_DEFAULT=' /etc/default/grub)" "1" assert_contains "grub: detected already-configured" "$out2" "already configured" -assert_eq "workspace: prior install archived" "$(find "$WORK/data-home/worker" -maxdepth 1 -name 'xmrig-*' | wc -l | tr -d ' ')" "1" +assert_eq "workspace: prior install archived" "$(find "$WORK/data-home/worker" -maxdepth 1 -name 'xmrig-*' | wc -l | tr -d ' ')" "1" # Shell string compare (portable; no dependency on cmp/diff being present). if [ "$(cat "$WORK/config-run1.json" 2>/dev/null)" = "$(cat "$BUILD/config.json" 2>/dev/null)" ] && [ -s "$BUILD/config.json" ]; then ok "deploy: config.json stable across runs" @@ -114,5 +129,8 @@ fi echo "" printf 'in-container: \033[1;32m%d passed\033[0m, ' "$PASS" -if [ "$FAIL" -gt 0 ]; then printf '\033[1;31m%d failed\033[0m\n' "$FAIL"; exit 1; fi +if [ "$FAIL" -gt 0 ]; then + printf '\033[1;31m%d failed\033[0m\n' "$FAIL" + exit 1 +fi printf '0 failed\n' diff --git a/tests/run.sh b/tests/run.sh index 47132b4..deaf862 100644 --- a/tests/run.sh +++ b/tests/run.sh @@ -22,20 +22,26 @@ TEMPLATE="$ROOT/worker-config/example-config.json.template" PASS=0 FAIL=0 -ok() { PASS=$((PASS + 1)); printf ' \033[1;32m✓\033[0m %s\n' "$1"; } -bad() { FAIL=$((FAIL + 1)); printf ' \033[1;31m✗\033[0m %s\n %s\n' "$1" "$2"; } +ok() { + PASS=$((PASS + 1)) + printf ' \033[1;32m✓\033[0m %s\n' "$1" +} +bad() { + FAIL=$((FAIL + 1)) + printf ' \033[1;31m✗\033[0m %s\n %s\n' "$1" "$2" +} -assert_eq() { if [ "$2" = "$3" ]; then ok "$1"; else bad "$1" "expected [$3], got [$2]"; fi; } -assert_contains() { case "$2" in *"$3"*) ok "$1" ;; *) bad "$1" "[$2] missing [$3]" ;; esac; } -assert_absent() { case "$2" in *"$3"*) bad "$1" "[$2] unexpectedly contains [$3]" ;; *) ok "$1" ;; esac; } -assert_rc() { if [ "$2" = "$3" ]; then ok "$1"; else bad "$1" "expected rc $3, got $2"; fi; } +assert_eq() { if [ "$2" = "$3" ]; then ok "$1"; else bad "$1" "expected [$3], got [$2]"; fi; } +assert_contains() { case "$2" in *"$3"*) ok "$1" ;; *) bad "$1" "[$2] missing [$3]" ;; esac } +assert_absent() { case "$2" in *"$3"*) bad "$1" "[$2] unexpectedly contains [$3]" ;; *) ok "$1" ;; esac } +assert_rc() { if [ "$2" = "$3" ]; then ok "$1"; else bad "$1" "expected rc $3, got $2"; fi; } # A throwaway sandbox, cleaned on exit. SANDBOX="$(mktemp -d)" trap 'rm -rf "$SANDBOX"' EXIT # jq helpers: J = raw scalar, JC = compact (for arrays). -J() { jq -r "$2" "$1"; } +J() { jq -r "$2" "$1"; } JC() { jq -c "$2" "$1"; } # --------------------------------------------------------------------------- @@ -46,17 +52,17 @@ make_stubs() { local bin="$1" mkdir -p "$bin" - cat > "$bin/sudo" <<'EOF' + cat >"$bin/sudo" <<'EOF' #!/usr/bin/env bash exec "$@" EOF - cat > "$bin/lscpu" <<'EOF' + cat >"$bin/lscpu" <<'EOF' #!/usr/bin/env bash echo "Model name: ${STUB_CPU_MODEL:-Generic CPU}" echo "L3 cache: ${STUB_L3:-8 MiB}" echo "Socket(s): ${STUB_SOCKETS:-1}" EOF - cat > "$bin/sysctl" <<'EOF' + cat >"$bin/sysctl" <<'EOF' #!/usr/bin/env bash case "$*" in "-n hw.ncpu") echo "${STUB_NCPU:-4}" ;; @@ -64,15 +70,15 @@ case "$*" in *) exit 0 ;; # e.g. `sudo sysctl -w vm.nr_hugepages=...` esac EOF - cat > "$bin/nproc" <<'EOF' + cat >"$bin/nproc" <<'EOF' #!/usr/bin/env bash echo "${STUB_NPROC:-4}" EOF - cat > "$bin/hostname" <<'EOF' + cat >"$bin/hostname" <<'EOF' #!/usr/bin/env bash echo "${STUB_HOSTNAME:-rigbox}" EOF - cat > "$bin/uname" <<'EOF' + cat >"$bin/uname" <<'EOF' #!/usr/bin/env bash case "${1:-}" in -s) echo "${STUB_UNAME_S:-Linux}" ;; @@ -84,7 +90,7 @@ EOF # git stub: `clone` fabricates a minimal xmrig tree (so the donate.h sed patch has a target); # `rev-parse` reports the pinned commit so #18's commit verification passes. A test can force a # mismatch by exporting STUB_GIT_HEAD. - cat > "$bin/git" <<'EOF' + cat >"$bin/git" <<'EOF' #!/usr/bin/env bash echo "[git] $*" >> "${CALL_LOG:-/dev/null}" case "$*" in @@ -94,14 +100,14 @@ esac exit 0 EOF # envsubst stub: substitute exactly the two vars the systemd template uses (gettext may be absent on macOS). - cat > "$bin/envsubst" <<'EOF' + cat >"$bin/envsubst" <<'EOF' #!/usr/bin/env bash sed -e "s|\$BUILD_DIR|${BUILD_DIR:-}|g" -e "s|\$CPUPOWER_PATH|${CPUPOWER_PATH:-}|g" EOF # No-op recorders / package managers. dpkg/rpm/pacman exit 0 so "is this dep installed?" is always yes. local cmd for cmd in make cmake systemctl modprobe mount update-grub apt-get apt-cache dpkg dnf rpm pacman brew cpupower; do - cat > "$bin/$cmd" <"$bin/$cmd" <> "\${CALL_LOG:-/dev/null}" exit 0 @@ -116,23 +122,32 @@ make_stubs "$STUBS" # Source rigforge with the given config + script dir, run parse_config, print one resulting variable. parse_and_print() { # - ( source "$SCRIPT" - CONFIG_JSON="$1"; SCRIPT_DIR="$2"; local var="$3" - set +eu - PATH="$STUBS:$PATH" parse_config >/dev/null 2>&1 - eval "printf '%s' \"\${$var}\"" ) + ( + source "$SCRIPT" + CONFIG_JSON="$1" + SCRIPT_DIR="$2" + local var="$3" + set +eu + PATH="$STUBS:$PATH" parse_config >/dev/null 2>&1 + eval "printf '%s' \"\${$var}\"" + ) } # Same, but we only care about parse_config's exit code. parse_rc() { # - ( source "$SCRIPT" - CONFIG_JSON="$1"; SCRIPT_DIR="$2" - set +e - PATH="$STUBS:$PATH" parse_config >/dev/null 2>&1 ) + ( + source "$SCRIPT" + CONFIG_JSON="$1" + SCRIPT_DIR="$2" + set +e + PATH="$STUBS:$PATH" parse_config >/dev/null 2>&1 + ) } # Write a config.json into the sandbox and echo its path. mkconf() { # - local f="$SANDBOX/$1.json"; printf '%s\n' "$2" > "$f"; echo "$f" + local f="$SANDBOX/$1.json" + printf '%s\n' "$2" >"$f" + echo "$f" } CFG_TPL='"WORKER_CONFIG_FILE": "./worker-config/example-config.json.template"' @@ -145,36 +160,40 @@ echo "== unit: parse_config — pool address used verbatim (#15) ==" c="$(mkconf dotless "{ \"P2POOL_NODE_HOSTNAME\": \"box\", $CFG_TPL }")" assert_eq "short host used as-is (no .local)" "$(parse_and_print "$c" "$ROOT" P2POOL_NODE_ADDRESS)" "box" c="$(mkconf fqdn "{ \"P2POOL_NODE_HOSTNAME\": \"box.lan\", $CFG_TPL }")" -assert_eq "FQDN passed through" "$(parse_and_print "$c" "$ROOT" P2POOL_NODE_ADDRESS)" "box.lan" +assert_eq "FQDN passed through" "$(parse_and_print "$c" "$ROOT" P2POOL_NODE_ADDRESS)" "box.lan" c="$(mkconf ip "{ \"P2POOL_NODE_HOSTNAME\": \"10.0.0.5\", $CFG_TPL }")" -assert_eq "IPv4 host passed through" "$(parse_and_print "$c" "$ROOT" P2POOL_NODE_ADDRESS)" "10.0.0.5" +assert_eq "IPv4 host passed through" "$(parse_and_print "$c" "$ROOT" P2POOL_NODE_ADDRESS)" "10.0.0.5" echo "== unit: parse_config — workspace + token + template resolution ==" c="$(mkconf dyn "{ \"HOME_DIR\": \"DYNAMIC_HOME\", \"P2POOL_NODE_HOSTNAME\": \"h\", $CFG_TPL }")" assert_eq "DYNAMIC_HOME -> script data dir" "$(parse_and_print "$c" "$ROOT" WORKER_ROOT)" "$ROOT/data/worker" c="$(mkconf home "{ \"HOME_DIR\": \"/opt/rig\", \"P2POOL_NODE_HOSTNAME\": \"h\", $CFG_TPL }")" -assert_eq "custom HOME_DIR -> HOME/worker" "$(parse_and_print "$c" "$ROOT" WORKER_ROOT)" "/opt/rig/worker" +assert_eq "custom HOME_DIR -> HOME/worker" "$(parse_and_print "$c" "$ROOT" WORKER_ROOT)" "/opt/rig/worker" c="$(mkconf tok "{ \"ACCESS_TOKEN\": \"tok123\", \"P2POOL_NODE_HOSTNAME\": \"h\", $CFG_TPL }")" -assert_eq "ACCESS_TOKEN honoured" "$(parse_and_print "$c" "$ROOT" ACCESS_TOKEN)" "tok123" +assert_eq "ACCESS_TOKEN honoured" "$(parse_and_print "$c" "$ROOT" ACCESS_TOKEN)" "tok123" c="$(mkconf notok "{ \"P2POOL_NODE_HOSTNAME\": \"h\", $CFG_TPL }")" assert_eq "ACCESS_TOKEN falls back to hostname" "$(parse_and_print "$c" "$ROOT" ACCESS_TOKEN)" "rigbox" c="$(mkconf rel "{ \"P2POOL_NODE_HOSTNAME\": \"h\", $CFG_TPL }")" assert_eq "relative template resolved vs SCRIPT_DIR" "$(parse_and_print "$c" "$ROOT" TEMPLATE_CONFIG)" "$ROOT/./worker-config/example-config.json.template" c="$(mkconf abs "{ \"P2POOL_NODE_HOSTNAME\": \"h\", \"WORKER_CONFIG_FILE\": \"$TEMPLATE\" }")" -assert_eq "absolute template kept as-is" "$(parse_and_print "$c" "$ROOT" TEMPLATE_CONFIG)" "$TEMPLATE" +assert_eq "absolute template kept as-is" "$(parse_and_print "$c" "$ROOT" TEMPLATE_CONFIG)" "$TEMPLATE" echo "== unit: parse_config — error paths ==" -printf '{ not json ' > "$SANDBOX/bad.json" -parse_rc "$SANDBOX/bad.json" "$ROOT"; assert_rc "invalid JSON rejected" "$?" "1" +printf '{ not json ' >"$SANDBOX/bad.json" +parse_rc "$SANDBOX/bad.json" "$ROOT" +assert_rc "invalid JSON rejected" "$?" "1" c="$(mkconf noworker "{ \"P2POOL_NODE_HOSTNAME\": \"h\" }")" -parse_rc "$c" "$ROOT"; assert_rc "missing WORKER_CONFIG_FILE rejected" "$?" "1" +parse_rc "$c" "$ROOT" +assert_rc "missing WORKER_CONFIG_FILE rejected" "$?" "1" c="$(mkconf notmpl "{ \"P2POOL_NODE_HOSTNAME\": \"h\", \"WORKER_CONFIG_FILE\": \"./nope/missing.json\" }")" -parse_rc "$c" "$ROOT"; assert_rc "missing template file rejected" "$?" "1" +parse_rc "$c" "$ROOT" +assert_rc "missing template file rejected" "$?" "1" echo "== unit: DONATION validation (new) ==" for d in 0 1 100; do c="$(mkconf "don$d" "{ \"DONATION\": $d, \"P2POOL_NODE_HOSTNAME\": \"h\", $CFG_TPL }")" - parse_rc "$c" "$ROOT"; assert_rc "DONATION $d accepted" "$?" "0" + parse_rc "$c" "$ROOT" + assert_rc "DONATION $d accepted" "$?" "0" done c="$(mkconf d0 "{ \"DONATION\": 0, \"P2POOL_NODE_HOSTNAME\": \"h\", $CFG_TPL }")" assert_eq "DONATION 0 parsed as 0" "$(parse_and_print "$c" "$ROOT" DONATION)" "0" @@ -182,139 +201,184 @@ c="$(mkconf dmiss "{ \"P2POOL_NODE_HOSTNAME\": \"h\", $CFG_TPL }")" assert_eq "DONATION defaults to 1 when absent" "$(parse_and_print "$c" "$ROOT" DONATION)" "1" for d in 101 -1 1.5 abc; do c="$(mkconf "donbad" "{ \"DONATION\": \"$d\", \"P2POOL_NODE_HOSTNAME\": \"h\", $CFG_TPL }")" - parse_rc "$c" "$ROOT"; assert_rc "DONATION '$d' rejected" "$?" "1" + parse_rc "$c" "$ROOT" + assert_rc "DONATION '$d' rejected" "$?" "1" done echo "== unit: append_once idempotency ==" -F="$SANDBOX/append.txt"; : > "$F" -( source "$SCRIPT"; set +e - PATH="$STUBS:$PATH" - append_once "$F" "alpha"; append_once "$F" "alpha"; append_once "$F" "beta" ) +F="$SANDBOX/append.txt" +: >"$F" +( + source "$SCRIPT" + set +e + PATH="$STUBS:$PATH" + append_once "$F" "alpha" + append_once "$F" "alpha" + append_once "$F" "beta" +) assert_eq "duplicate line appended once" "$(grep -c '^alpha$' "$F")" "1" -assert_eq "distinct line also present" "$(grep -c '^beta$' "$F")" "1" -assert_eq "exactly two lines total" "$(wc -l < "$F" | tr -d ' ')" "2" +assert_eq "distinct line also present" "$(grep -c '^beta$' "$F")" "1" +assert_eq "exactly two lines total" "$(wc -l <"$F" | tr -d ' ')" "2" # --------------------------------------------------------------------------- # Config-generation matrix. Each profile sets STUB_* (the simulated hardware) + globals, runs # generate_xmrig_config in a fresh dir, and we assert the emitted config.json with jq. This is where # the per-CPU "optimizations" are proven to fire. gen_config() { # echoes path to the dir containing config.json - local d; d="$(mktemp -d "$SANDBOX/gen.XXXXXX")" - ( cd "$d" || exit 1 - source "$SCRIPT" - OS_TYPE="$SIM_OS"; WORKER_ROOT="$d"; TEMPLATE_CONFIG="$TEMPLATE" - P2POOL_NODE_ADDRESS="${SIM_ADDR:-myrig.local}" - ACCESS_TOKEN="${SIM_TOK:-tok123}" - DONATION="${SIM_DON:-1}" - LOGROTATE_DIR="$d" - set +e - PATH="$STUBS:$PATH" generate_xmrig_config >/dev/null 2>&1 ) + local d + d="$(mktemp -d "$SANDBOX/gen.XXXXXX")" + ( + cd "$d" || exit 1 + source "$SCRIPT" + OS_TYPE="$SIM_OS" + WORKER_ROOT="$d" + TEMPLATE_CONFIG="$TEMPLATE" + P2POOL_NODE_ADDRESS="${SIM_ADDR:-myrig.local}" + ACCESS_TOKEN="${SIM_TOK:-tok123}" + DONATION="${SIM_DON:-1}" + LOGROTATE_DIR="$d" + set +e + PATH="$STUBS:$PATH" generate_xmrig_config >/dev/null 2>&1 + ) echo "$d" } echo "== config-gen: generic Linux (default profile) ==" export STUB_CPU_MODEL="Intel(R) Xeon(R) Silver 4310" STUB_NPROC=8 STUB_HOSTNAME=rigbox SIM_OS=Linux SIM_DON=5 SIM_TOK=tok123 SIM_ADDR=myrig.local -d="$(gen_config)"; cfg="$d/config.json" -assert_eq "generic: rx auto (-1)" "$(J "$cfg" '.cpu.rx')" "-1" -assert_eq "generic: asm auto" "$(J "$cfg" '.cpu.asm')" "auto" -assert_eq "generic: numa off" "$(J "$cfg" '.randomx.numa')" "false" -assert_eq "generic: huge-pages on" "$(J "$cfg" '.cpu."huge-pages"')" "true" -assert_eq "generic: msr on" "$(J "$cfg" '.cpu.msr')" "true" -assert_eq "generic: priority default" "$(J "$cfg" '.cpu.priority')" "null" +d="$(gen_config)" +cfg="$d/config.json" +assert_eq "generic: rx auto (-1)" "$(J "$cfg" '.cpu.rx')" "-1" +assert_eq "generic: asm auto" "$(J "$cfg" '.cpu.asm')" "auto" +assert_eq "generic: numa off" "$(J "$cfg" '.randomx.numa')" "false" +assert_eq "generic: huge-pages on" "$(J "$cfg" '.cpu."huge-pages"')" "true" +assert_eq "generic: msr on" "$(J "$cfg" '.cpu.msr')" "true" +assert_eq "generic: priority default" "$(J "$cfg" '.cpu.priority')" "null" # HTTP API locked down on Linux (#7 / #17): made READ-ONLY (restricted) so it can't control the # miner remotely. It stays bound to 0.0.0.0 (NOT localhost) on purpose: Pithead reads per-rig stats # from the stack host at http://:8080 (read-only, token = rig name) — localhost would break that # integration (issue #24). The access-token assertion below is the auth half of the lockdown. -assert_eq "generic: http restricted" "$(J "$cfg" '.http.restricted')" "true" -assert_eq "generic: http reachable (LAN)" "$(J "$cfg" '.http.host')" "0.0.0.0" +assert_eq "generic: http restricted" "$(J "$cfg" '.http.restricted')" "true" +assert_eq "generic: http reachable (LAN)" "$(J "$cfg" '.http.host')" "0.0.0.0" # Shared invariants (assert once, here): -assert_eq "pools collapsed to one" "$(J "$cfg" '.pools | length')" "1" -assert_eq "pool url = addr:3333" "$(J "$cfg" '.pools[0].url')" "myrig.local:3333" -assert_eq "pool enabled" "$(J "$cfg" '.pools[0].enabled')" "true" -assert_eq "pool user = hostname" "$(J "$cfg" '.pools[0].user')" "rigbox" -assert_eq "access-token applied" "$(J "$cfg" '.http."access-token"')" "tok123" -assert_eq "donate-level = DONATION" "$(J "$cfg" '.["donate-level"]')" "5" -assert_eq "donate-over-proxy = DONATION" "$(J "$cfg" '.["donate-over-proxy"]')" "5" +assert_eq "pools collapsed to one" "$(J "$cfg" '.pools | length')" "1" +assert_eq "pool url = addr:3333" "$(J "$cfg" '.pools[0].url')" "myrig.local:3333" +assert_eq "pool enabled" "$(J "$cfg" '.pools[0].enabled')" "true" +assert_eq "pool user = hostname" "$(J "$cfg" '.pools[0].user')" "rigbox" +assert_eq "access-token applied" "$(J "$cfg" '.http."access-token"')" "tok123" +assert_eq "donate-level = DONATION" "$(J "$cfg" '.["donate-level"]')" "5" +assert_eq "donate-over-proxy = DONATION" "$(J "$cfg" '.["donate-over-proxy"]')" "5" echo "== config-gen: AMD EPYC (server) ==" # Run directly (not via gen_config) so we can also capture the profile log line from stdout. export STUB_CPU_MODEL="AMD EPYC 7763 64-Core Processor" STUB_NPROC=8 STUB_HOSTNAME=rigbox d="$(mktemp -d "$SANDBOX/epyc.XXXXXX")" -log_out="$( cd "$d" || exit 1; source "$SCRIPT"; OS_TYPE=Linux; WORKER_ROOT="$d"; TEMPLATE_CONFIG="$TEMPLATE" - P2POOL_NODE_ADDRESS=myrig.local; ACCESS_TOKEN=tok123; DONATION=1; LOGROTATE_DIR="$d" - set +e; PATH="$STUBS:$PATH" generate_xmrig_config 2>&1 )" +log_out="$( + cd "$d" || exit 1 + source "$SCRIPT" + OS_TYPE=Linux + WORKER_ROOT="$d" + TEMPLATE_CONFIG="$TEMPLATE" + P2POOL_NODE_ADDRESS=myrig.local + ACCESS_TOKEN=tok123 + DONATION=1 + LOGROTATE_DIR="$d" + set +e + PATH="$STUBS:$PATH" generate_xmrig_config 2>&1 +)" cfg="$d/config.json" -assert_eq "epyc: numa on" "$(J "$cfg" '.randomx.numa')" "true" -assert_eq "epyc: rx auto (-1)" "$(J "$cfg" '.cpu.rx')" "-1" -assert_eq "epyc: asm auto" "$(J "$cfg" '.cpu.asm')" "auto" -assert_eq "epyc: msr on" "$(J "$cfg" '.cpu.msr')" "true" -assert_eq "epyc: http stays restricted" "$(J "$cfg" '.http.restricted')" "true" -assert_eq "epyc: http reachable (LAN)" "$(J "$cfg" '.http.host')" "0.0.0.0" -assert_contains "epyc: profile logged" "$log_out" "AMD EPYC" +assert_eq "epyc: numa on" "$(J "$cfg" '.randomx.numa')" "true" +assert_eq "epyc: rx auto (-1)" "$(J "$cfg" '.cpu.rx')" "-1" +assert_eq "epyc: asm auto" "$(J "$cfg" '.cpu.asm')" "auto" +assert_eq "epyc: msr on" "$(J "$cfg" '.cpu.msr')" "true" +assert_eq "epyc: http stays restricted" "$(J "$cfg" '.http.restricted')" "true" +assert_eq "epyc: http reachable (LAN)" "$(J "$cfg" '.http.host')" "0.0.0.0" +assert_contains "epyc: profile logged" "$log_out" "AMD EPYC" echo "== config-gen: AMD Ryzen X3D (desktop) ==" export STUB_CPU_MODEL="AMD Ryzen 9 7950X3D 16-Core Processor" STUB_NPROC=4 STUB_HOSTNAME=rigbox SIM_OS=Linux SIM_DON=1 -d="$(gen_config)"; cfg="$d/config.json" -assert_eq "x3d: rx pins cores [0..3]" "$(JC "$cfg" '.cpu.rx')" "[0,1,2,3]" -assert_eq "x3d: asm ryzen" "$(J "$cfg" '.cpu.asm')" "ryzen" -assert_eq "x3d: priority 4" "$(J "$cfg" '.cpu.priority')" "4" -assert_eq "x3d: yield off" "$(J "$cfg" '.cpu.yield')" "false" -assert_eq "x3d: init-avx2 on" "$(J "$cfg" '.randomx."init-avx2"')" "1" -assert_eq "x3d: msr on" "$(J "$cfg" '.cpu.msr')" "true" +d="$(gen_config)" +cfg="$d/config.json" +assert_eq "x3d: rx pins cores [0..3]" "$(JC "$cfg" '.cpu.rx')" "[0,1,2,3]" +assert_eq "x3d: asm ryzen" "$(J "$cfg" '.cpu.asm')" "ryzen" +assert_eq "x3d: priority 4" "$(J "$cfg" '.cpu.priority')" "4" +assert_eq "x3d: yield off" "$(J "$cfg" '.cpu.yield')" "false" +assert_eq "x3d: init-avx2 on" "$(J "$cfg" '.randomx."init-avx2"')" "1" +assert_eq "x3d: msr on" "$(J "$cfg" '.cpu.msr')" "true" echo "== config-gen: macOS overrides ==" export STUB_CPU_MODEL="Apple M2" STUB_NCPU=4 STUB_HOSTNAME=rigbox SIM_OS=Darwin SIM_DON=1 -d="$(gen_config)"; cfg="$d/config.json" -assert_eq "macos: huge-pages off" "$(J "$cfg" '.cpu."huge-pages"')" "false" -assert_eq "macos: memory-pool off" "$(J "$cfg" '.cpu."memory-pool"')" "false" -assert_eq "macos: asm boolean true" "$(J "$cfg" '.cpu.asm')" "true" -assert_eq "macos: priority 5" "$(J "$cfg" '.cpu.priority')" "5" -assert_eq "macos: rx [-1] per core" "$(JC "$cfg" '.cpu.rx')" "[-1,-1,-1,-1]" -assert_eq "macos: 1gb-pages off" "$(J "$cfg" '.randomx."1gb-pages"')" "false" -assert_eq "macos: http host all v6" "$(J "$cfg" '.http.host')" "::" -assert_eq "macos: http restricted" "$(J "$cfg" '.http.restricted')" "true" -assert_eq "macos: yield off" "$(J "$cfg" '.cpu.yield')" "false" +d="$(gen_config)" +cfg="$d/config.json" +assert_eq "macos: huge-pages off" "$(J "$cfg" '.cpu."huge-pages"')" "false" +assert_eq "macos: memory-pool off" "$(J "$cfg" '.cpu."memory-pool"')" "false" +assert_eq "macos: asm boolean true" "$(J "$cfg" '.cpu.asm')" "true" +assert_eq "macos: priority 5" "$(J "$cfg" '.cpu.priority')" "5" +assert_eq "macos: rx [-1] per core" "$(JC "$cfg" '.cpu.rx')" "[-1,-1,-1,-1]" +assert_eq "macos: 1gb-pages off" "$(J "$cfg" '.randomx."1gb-pages"')" "false" +assert_eq "macos: http host all v6" "$(J "$cfg" '.http.host')" "::" +assert_eq "macos: http restricted" "$(J "$cfg" '.http.restricted')" "true" +assert_eq "macos: yield off" "$(J "$cfg" '.cpu.yield')" "false" unset STUB_CPU_MODEL STUB_NPROC STUB_NCPU STUB_HOSTNAME STUB_L3 STUB_SOCKETS echo "== config-gen: idempotent (same inputs -> identical output) ==" export STUB_CPU_MODEL="Intel(R) Xeon(R)" STUB_NPROC=8 STUB_HOSTNAME=rigbox SIM_OS=Linux SIM_DON=1 d="$(mktemp -d "$SANDBOX/idem.XXXXXX")" -( cd "$d" || exit 1; source "$SCRIPT"; OS_TYPE=Linux; WORKER_ROOT="$d"; TEMPLATE_CONFIG="$TEMPLATE" - P2POOL_NODE_ADDRESS=myrig.local; ACCESS_TOKEN=tok123; DONATION=1; LOGROTATE_DIR="$d" - set +e; PATH="$STUBS:$PATH" generate_xmrig_config >/dev/null 2>&1 ) +( + cd "$d" || exit 1 + source "$SCRIPT" + OS_TYPE=Linux + WORKER_ROOT="$d" + TEMPLATE_CONFIG="$TEMPLATE" + P2POOL_NODE_ADDRESS=myrig.local + ACCESS_TOKEN=tok123 + DONATION=1 + LOGROTATE_DIR="$d" + set +e + PATH="$STUBS:$PATH" generate_xmrig_config >/dev/null 2>&1 +) cp "$d/config.json" "$d/first.json" -( cd "$d" || exit 1; source "$SCRIPT"; OS_TYPE=Linux; WORKER_ROOT="$d"; TEMPLATE_CONFIG="$TEMPLATE" - P2POOL_NODE_ADDRESS=myrig.local; ACCESS_TOKEN=tok123; DONATION=1; LOGROTATE_DIR="$d" - set +e; PATH="$STUBS:$PATH" generate_xmrig_config >/dev/null 2>&1 ) +( + cd "$d" || exit 1 + source "$SCRIPT" + OS_TYPE=Linux + WORKER_ROOT="$d" + TEMPLATE_CONFIG="$TEMPLATE" + P2POOL_NODE_ADDRESS=myrig.local + ACCESS_TOKEN=tok123 + DONATION=1 + LOGROTATE_DIR="$d" + set +e + PATH="$STUBS:$PATH" generate_xmrig_config >/dev/null 2>&1 +) if cmp -s "$d/first.json" "$d/config.json"; then ok "config.json byte-identical on re-run"; else bad "config.json byte-identical on re-run" "differs"; fi unset STUB_CPU_MODEL STUB_NPROC STUB_HOSTNAME # --------------------------------------------------------------------------- echo "== unit: util/proposed-grub.sh hardware math ==" PG="$ROOT/util/proposed-grub.sh" -printf 'flags : fpu pdpe1gb\n' > "$SANDBOX/cpuinfo_1g" -: > "$SANDBOX/cpuinfo_no1g" +printf 'flags : fpu pdpe1gb\n' >"$SANDBOX/cpuinfo_1g" +: >"$SANDBOX/cpuinfo_no1g" # 32 MiB L3 / 2 sockets, with 1G support: threads=16; 1G pages=3*2=6; 2M=128+16+10=154. out="$(PATH="$STUBS:$PATH" STUB_L3="32 MiB" STUB_SOCKETS=2 CPUINFO="$SANDBOX/cpuinfo_1g" bash "$PG" -q)" -assert_contains "grub: 1G dataset pages" "$out" "hugepagesz=1G hugepages=6" -assert_contains "grub: 2M jit pages" "$out" "hugepagesz=2M hugepages=154" +assert_contains "grub: 1G dataset pages" "$out" "hugepagesz=1G hugepages=6" +assert_contains "grub: 2M jit pages" "$out" "hugepagesz=2M hugepages=154" # K->M normalization: 32768K == 32M -> threads 16. out="$(PATH="$STUBS:$PATH" STUB_L3="32768K" STUB_SOCKETS=1 CPUINFO="$SANDBOX/cpuinfo_1g" bash "$PG" -q)" -assert_contains "grub: K normalized to M" "$out" "hugepagesz=1G hugepages=3" +assert_contains "grub: K normalized to M" "$out" "hugepagesz=1G hugepages=3" # No pdpe1gb -> pure-2M fallback: 1168*1 + 16 + 50 = 1234, and no 1G stanza. out="$(PATH="$STUBS:$PATH" STUB_L3="32 MiB" STUB_SOCKETS=1 CPUINFO="$SANDBOX/cpuinfo_no1g" bash "$PG" -q)" -assert_contains "grub: 2M fallback total" "$out" "hugepages=1234" -assert_absent "grub: no 1G stanza" "$out" "hugepagesz=1G" +assert_contains "grub: 2M fallback total" "$out" "hugepages=1234" +assert_absent "grub: no 1G stanza" "$out" "hugepagesz=1G" # --runtime: fallback when no 1G pages allocated, smaller set once they are. out="$(PATH="$STUBS:$PATH" STUB_L3="32 MiB" STUB_SOCKETS=1 HUGEPAGES_1G_NR="$SANDBOX/nr_none" bash "$PG" --runtime)" -assert_eq "grub --runtime: 2M fallback" "$out" "1234" -printf '4\n' > "$SANDBOX/nr_4" +assert_eq "grub --runtime: 2M fallback" "$out" "1234" +printf '4\n' >"$SANDBOX/nr_4" out="$(PATH="$STUBS:$PATH" STUB_L3="32 MiB" STUB_SOCKETS=1 HUGEPAGES_1G_NR="$SANDBOX/nr_4" bash "$PG" --runtime)" -assert_eq "grub --runtime: 1G allocated" "$out" "154" +assert_eq "grub --runtime: 1G allocated" "$out" "154" # --------------------------------------------------------------------------- # Pinned-build verification (#18): compile_xmrig clones the pinned XMRIG_VERSION and aborts if the @@ -322,21 +386,27 @@ assert_eq "grub --runtime: 1G allocated" "$out" "154" # so we can prove the supply-chain check rejects it (and passes when they match). echo "== unit: compile_xmrig pinned-commit verification (#18) ==" pin_compile() { # ; runs compile_xmrig in a sandbox, prints its output, returns rc - local d; d="$(mktemp -d "$SANDBOX/pin.XXXXXX")" - ( cd "$d" || exit 1 - source "$SCRIPT" - OS_TYPE="$(uname -s)"; DONATION=1 - export XMRIG_COMMIT="pinnedsha000000000000000000000000000000" - [ -n "$1" ] && export STUB_GIT_HEAD="$1" - set +e - PATH="$STUBS:$PATH" compile_xmrig 2>&1 ) + local d + d="$(mktemp -d "$SANDBOX/pin.XXXXXX")" + ( + cd "$d" || exit 1 + source "$SCRIPT" + OS_TYPE="$(uname -s)" + DONATION=1 + export XMRIG_COMMIT="pinnedsha000000000000000000000000000000" + [ -n "$1" ] && export STUB_GIT_HEAD="$1" + set +e + PATH="$STUBS:$PATH" compile_xmrig 2>&1 + ) } -out="$(pin_compile "")"; rc=$? -assert_rc "matching commit builds" "$rc" "0" -assert_contains "matching commit is verified" "$out" "Verified XMRig" -out="$(pin_compile "tamperedsha1111111111111111111111111111")"; rc=$? -assert_rc "tampered commit fails build" "$rc" "1" -assert_contains "tampered commit is reported" "$out" "commit mismatch" +out="$(pin_compile "")" +rc=$? +assert_rc "matching commit builds" "$rc" "0" +assert_contains "matching commit is verified" "$out" "Verified XMRig" +out="$(pin_compile "tamperedsha1111111111111111111111111111")" +rc=$? +assert_rc "tampered commit fails build" "$rc" "1" +assert_contains "tampered commit is reported" "$out" "commit mismatch" # --------------------------------------------------------------------------- # Full end-to-end run of the REAL script with everything stubbed, executed TWICE to prove idempotency. @@ -349,18 +419,19 @@ assert_contains "tampered commit is reported" "$out" "commit mismatch" HOST_OS="$(uname -s)" e2e_setup() { # echoes the work dir - local W; W="$(mktemp -d "$SANDBOX/e2e.XXXXXX")" + local W + W="$(mktemp -d "$SANDBOX/e2e.XXXXXX")" cp "$SCRIPT" "$W/rigforge.sh" cp -R "$ROOT/worker-config" "$ROOT/systemd" "$ROOT/util" "$W/" mkdir -p "$W/etc/logrotate.d" "$W/etc/modules-load.d" "$W/etc/systemd" \ - "$W/etc/security" "$W/etc/default" "$W/home" "$W/proc" "$W/sys" - : > "$W/etc/fstab" - : > "$W/etc/security/limits.conf" - printf 'GRUB_CMDLINE_LINUX_DEFAULT="quiet splash"\n' > "$W/etc/default/grub" - printf 'flags : fpu pdpe1gb\n' > "$W/proc/cpuinfo" + "$W/etc/security" "$W/etc/default" "$W/home" "$W/proc" "$W/sys" + : >"$W/etc/fstab" + : >"$W/etc/security/limits.conf" + printf 'GRUB_CMDLINE_LINUX_DEFAULT="quiet splash"\n' >"$W/etc/default/grub" + printf 'flags : fpu pdpe1gb\n' >"$W/proc/cpuinfo" # Use an explicit (dotted) host so this E2E doesn't depend on the .local/mDNS appending that # PR #15 removes — "poolbox.lan" -> "poolbox.lan:3333" holds before and after that change. - cat > "$W/config.json" <"$W/config.json" < ; sets E2E_OUT, returns the script's exit code local W="$1" os="$2" cpu uname_m - if [ "$os" = Darwin ]; then cpu="Apple M2"; uname_m=arm64 - else cpu="AMD EPYC 7763 64-Core Processor"; uname_m=x86_64; fi - E2E_OUT="$( cd "$W" && \ + if [ "$os" = Darwin ]; then + cpu="Apple M2" + uname_m=arm64 + else + cpu="AMD EPYC 7763 64-Core Processor" + uname_m=x86_64 + fi + E2E_OUT="$(cd "$W" && PATH="$STUBS:$PATH" \ - STUB_UNAME_S="$os" STUB_UNAME_M="$uname_m" STUB_UNAME_R=6.0.0-test \ - STUB_CPU_MODEL="$cpu" STUB_NPROC=4 STUB_NCPU=4 STUB_HOSTNAME=poolbox \ - STUB_L3="32 MiB" STUB_SOCKETS=2 \ - LOGROTATE_DIR="$W/etc/logrotate.d" GRUB_DEFAULT="$W/etc/default/grub" \ - FSTAB="$W/etc/fstab" LIMITS_CONF="$W/etc/security/limits.conf" \ - MODULES_LOAD_DIR="$W/etc/modules-load.d" MODULES_FILE="$W/etc/modules" \ - SYSTEMD_DIR="$W/etc/systemd" HUGEPAGES_1G_DIR="$W/dev/hugepages1G" \ - CPUINFO="$W/proc/cpuinfo" HUGEPAGES_1G_NR="$W/sys/none" \ - CALL_LOG="$W/calls.log" \ - XMRIG_VERSION="vTEST" XMRIG_COMMIT="testcommit0000000000000000000000000000" \ - bash "$W/rigforge.sh" &1 )" + STUB_UNAME_S="$os" STUB_UNAME_M="$uname_m" STUB_UNAME_R=6.0.0-test \ + STUB_CPU_MODEL="$cpu" STUB_NPROC=4 STUB_NCPU=4 STUB_HOSTNAME=poolbox \ + STUB_L3="32 MiB" STUB_SOCKETS=2 \ + LOGROTATE_DIR="$W/etc/logrotate.d" GRUB_DEFAULT="$W/etc/default/grub" \ + FSTAB="$W/etc/fstab" LIMITS_CONF="$W/etc/security/limits.conf" \ + MODULES_LOAD_DIR="$W/etc/modules-load.d" MODULES_FILE="$W/etc/modules" \ + SYSTEMD_DIR="$W/etc/systemd" HUGEPAGES_1G_DIR="$W/dev/hugepages1G" \ + CPUINFO="$W/proc/cpuinfo" HUGEPAGES_1G_NR="$W/sys/none" \ + CALL_LOG="$W/calls.log" \ + XMRIG_VERSION="vTEST" XMRIG_COMMIT="testcommit0000000000000000000000000000" \ + bash "$W/rigforge.sh" &1)" } echo "== black-box: full deployment run (stubbed, native $HOST_OS path) ==" W="$(e2e_setup)" -e2e_run "$W" "$HOST_OS"; rc=$? +e2e_run "$W" "$HOST_OS" +rc=$? BUILD="$W/home/worker/xmrig/build" -assert_rc "first run exits 0" "$rc" "0" -assert_contains "build: cloned xmrig" "$(cat "$W/calls.log")" "[git] clone" -assert_contains "build: ran cmake" "$(cat "$W/calls.log")" "[cmake]" -assert_contains "build: ran make" "$(cat "$W/calls.log")" "[make]" -assert_contains "build: donate.h patched to 7" "$(cat "$W/home/worker/xmrig/src/donate.h")" "DonateLevel = 7;" -assert_contains "build: verified pinned commit" "$E2E_OUT" "Verified XMRig" -assert_eq "deploy: pool url from hostname" "$(J "$BUILD/config.json" '.pools[0].url')" "poolbox.lan:3333" -assert_eq "deploy: donate-level = 7" "$(J "$BUILD/config.json" '.["donate-level"]')" "7" +assert_rc "first run exits 0" "$rc" "0" +assert_contains "build: cloned xmrig" "$(cat "$W/calls.log")" "[git] clone" +assert_contains "build: ran cmake" "$(cat "$W/calls.log")" "[cmake]" +assert_contains "build: ran make" "$(cat "$W/calls.log")" "[make]" +assert_contains "build: donate.h patched to 7" "$(cat "$W/home/worker/xmrig/src/donate.h")" "DonateLevel = 7;" +assert_contains "build: verified pinned commit" "$E2E_OUT" "Verified XMRig" +assert_eq "deploy: pool url from hostname" "$(J "$BUILD/config.json" '.pools[0].url')" "poolbox.lan:3333" +assert_eq "deploy: donate-level = 7" "$(J "$BUILD/config.json" '.["donate-level"]')" "7" if [ "$HOST_OS" = Linux ]; then - assert_eq "deploy: EPYC numa applied" "$(J "$BUILD/config.json" '.randomx.numa')" "true" - assert_contains "service: rendered with build dir" "$(cat "$W/etc/systemd/xmrig.service")" "$BUILD" - assert_contains "kernel: msr module enabled" "$(cat "$W/etc/modules-load.d/msr.conf")" "msr" - assert_contains "limits: fstab 2M mount written" "$(cat "$W/etc/fstab")" "hugetlbfs /dev/hugepages" + assert_eq "deploy: EPYC numa applied" "$(J "$BUILD/config.json" '.randomx.numa')" "true" + assert_contains "service: rendered with build dir" "$(cat "$W/etc/systemd/xmrig.service")" "$BUILD" + assert_contains "kernel: msr module enabled" "$(cat "$W/etc/modules-load.d/msr.conf")" "msr" + assert_contains "limits: fstab 2M mount written" "$(cat "$W/etc/fstab")" "hugetlbfs /dev/hugepages" assert_contains "limits: memlock unlimited written" "$(cat "$W/etc/security/limits.conf")" "soft memlock unlimited" - assert_contains "grub: hugepages params written" "$(cat "$W/etc/default/grub")" "default_hugepagesz=2M" + assert_contains "grub: hugepages params written" "$(cat "$W/etc/default/grub")" "default_hugepagesz=2M" else - assert_eq "deploy: macOS huge-pages off" "$(J "$BUILD/config.json" '.cpu."huge-pages"')" "false" - assert_eq "deploy: macOS http host all v6" "$(J "$BUILD/config.json" '.http.host')" "::" - assert_contains "service: unsupported on macOS" "$E2E_OUT" "not supported" + assert_eq "deploy: macOS huge-pages off" "$(J "$BUILD/config.json" '.cpu."huge-pages"')" "false" + assert_eq "deploy: macOS http host all v6" "$(J "$BUILD/config.json" '.http.host')" "::" + assert_contains "service: unsupported on macOS" "$E2E_OUT" "not supported" fi cp "$BUILD/config.json" "$W/config-after-run1.json" echo "== black-box: re-run is idempotent (#5) ==" -e2e_run "$W" "$HOST_OS"; rc=$? -assert_rc "second run exits 0" "$rc" "0" -assert_eq "workspace: prior install archived" "$(find "$W/home/worker" -maxdepth 1 -name 'xmrig-*' | wc -l | tr -d ' ')" "1" +e2e_run "$W" "$HOST_OS" +rc=$? +assert_rc "second run exits 0" "$rc" "0" +assert_eq "workspace: prior install archived" "$(find "$W/home/worker" -maxdepth 1 -name 'xmrig-*' | wc -l | tr -d ' ')" "1" if cmp -s "$W/config-after-run1.json" "$BUILD/config.json"; then ok "deploy: config.json stable across runs"; else bad "deploy: config.json stable across runs" "differs"; fi if [ "$HOST_OS" = Linux ]; then - assert_eq "fstab: hugepages line not doubled" "$(grep -c 'hugetlbfs /dev/hugepages ' "$W/etc/fstab")" "1" - assert_eq "fstab: 1G line not doubled" "$(grep -c 'hugetlbfs_1g ' "$W/etc/fstab")" "1" - assert_eq "limits: soft line not doubled" "$(grep -c 'soft memlock unlimited' "$W/etc/security/limits.conf")" "1" - assert_eq "grub: single cmdline entry" "$(grep -c '^GRUB_CMDLINE_LINUX_DEFAULT=' "$W/etc/default/grub")" "1" + assert_eq "fstab: hugepages line not doubled" "$(grep -c 'hugetlbfs /dev/hugepages ' "$W/etc/fstab")" "1" + assert_eq "fstab: 1G line not doubled" "$(grep -c 'hugetlbfs_1g ' "$W/etc/fstab")" "1" + assert_eq "limits: soft line not doubled" "$(grep -c 'soft memlock unlimited' "$W/etc/security/limits.conf")" "1" + assert_eq "grub: single cmdline entry" "$(grep -c '^GRUB_CMDLINE_LINUX_DEFAULT=' "$W/etc/default/grub")" "1" assert_contains "grub: detected already-configured" "$E2E_OUT" "already configured" else echo " • macOS host: Linux /etc idempotency (fstab/limits/grub) is covered by the Docker E2E" @@ -431,5 +509,8 @@ fi # --------------------------------------------------------------------------- echo "" printf 'rigforge tests: \033[1;32m%d passed\033[0m, ' "$PASS" -if [ "$FAIL" -gt 0 ]; then printf '\033[1;31m%d failed\033[0m\n' "$FAIL"; exit 1; fi +if [ "$FAIL" -gt 0 ]; then + printf '\033[1;31m%d failed\033[0m\n' "$FAIL" + exit 1 +fi printf '0 failed\n' diff --git a/util/proposed-grub.sh b/util/proposed-grub.sh index 412d673..fbaa38a 100755 --- a/util/proposed-grub.sh +++ b/util/proposed-grub.sh @@ -92,4 +92,4 @@ else echo "-------------------------" echo "Proposed GRUB Configuration:" echo "GRUB_CMDLINE_LINUX_DEFAULT=\"$NEW_GRUB\"" -fi \ No newline at end of file +fi From d4353d02a45f63217cbb4c4067ec3f1c2a8bc84a Mon Sep 17 00:00:00 2001 From: Vijit Singh Date: Sun, 7 Jun 2026 08:23:36 -0500 Subject: [PATCH 3/3] ci: enforce pinned shfmt formatting; add make fmt (closes #6) Install a pinned, checksum-verified shfmt v3.13.1 in the lint job and run `shfmt -i 4 -d`, alongside the pinned shellcheck. Add `make fmt` (shfmt -w) and fold shfmt into `make lint`. With the test suite (already in CI from #26) and the pinned shellcheck, this completes #6. Co-Authored-By: Claude Opus 4.8 --- .github/workflows/ci.yml | 21 ++++++++++++++++----- Makefile | 12 +++++++++--- README.md | 2 +- 3 files changed, 26 insertions(+), 9 deletions(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 17cf8ee..824cdae 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -7,17 +7,19 @@ on: jobs: lint: - name: ShellCheck (pinned) + name: Lint (shellcheck + shfmt) runs-on: ubuntu-24.04 env: SHELLCHECK_VERSION: "0.10.0" SHELLCHECK_SHA256: "6c881ab0698e4e6ea235245f22832860544f17ba386442fe7e9d629f8cbedf87" + SHFMT_VERSION: "3.13.1" + SHFMT_SHA256: "fb096c5d1ac6beabbdbaa2874d025badb03ee07929f0c9ff67563ce8c75398b1" steps: - uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2 - # Install a PINNED, checksum-verified shellcheck instead of the runner's preinstalled build, so - # lint results are reproducible and don't drift with the runner image (#6). Downloaded straight - # from the upstream static release — no `apt-get update`, avoiding the mirror flakiness pithead - # hit in its #64. + # Install PINNED, checksum-verified shellcheck + shfmt instead of the runner's preinstalled + # builds, so lint/format results are reproducible and don't drift with the runner image (#6). + # Downloaded straight from the upstream releases — no `apt-get update`, avoiding the mirror + # flakiness pithead hit in its #64. - name: Install pinned ShellCheck run: | set -euo pipefail @@ -27,8 +29,17 @@ jobs: tar -xJf "$tarball" sudo install "shellcheck-v${SHELLCHECK_VERSION}/shellcheck" /usr/local/bin/shellcheck shellcheck --version + - name: Install pinned shfmt + run: | + set -euo pipefail + curl -fsSL "https://github.com/mvdan/sh/releases/download/v${SHFMT_VERSION}/shfmt_v${SHFMT_VERSION}_linux_amd64" -o shfmt + echo "${SHFMT_SHA256} shfmt" | sha256sum -c - + sudo install shfmt /usr/local/bin/shfmt + shfmt --version - name: Run ShellCheck run: shellcheck --severity=warning rigforge.sh util/proposed-grub.sh tests/run.sh tests/e2e/run.sh tests/e2e/in-container.sh + - name: Run shfmt (format check) + run: shfmt -i 4 -d rigforge.sh util/proposed-grub.sh tests/run.sh tests/e2e/run.sh tests/e2e/in-container.sh test: name: Test suite diff --git a/Makefile b/Makefile index 7695018..cc169ab 100644 --- a/Makefile +++ b/Makefile @@ -1,5 +1,7 @@ # Local test entry points (mirror the GitHub Actions CI jobs). -.PHONY: test test-stack test-e2e lint +.PHONY: test test-stack test-e2e lint fmt + +SHELL_FILES = rigforge.sh util/proposed-grub.sh tests/run.sh tests/e2e/run.sh tests/e2e/in-container.sh test: lint test-stack ## Lint + the dependency-free suite (runs on macOS or Linux, no Docker) @@ -9,5 +11,9 @@ test-stack: ## rigforge test suite: unit + black-box, every CPU/OS profile simul test-e2e: ## Full end-to-end run in disposable Linux containers (needs Docker) bash tests/e2e/run.sh -lint: ## shellcheck the script, utilities, and test scripts - shellcheck --severity=warning rigforge.sh util/proposed-grub.sh tests/run.sh tests/e2e/run.sh tests/e2e/in-container.sh +lint: ## shellcheck + shfmt (check) the script, utilities, and test scripts + shellcheck --severity=warning $(SHELL_FILES) + shfmt -i 4 -d $(SHELL_FILES) + +fmt: ## auto-format all shell scripts with shfmt (resolves shfmt lint failures) + shfmt -i 4 -w $(SHELL_FILES) diff --git a/README.md b/README.md index 24e819b..4537526 100644 --- a/README.md +++ b/README.md @@ -180,7 +180,7 @@ RigForge ships a dependency-free test suite plus an opt-in container end-to-end CI jobs, so `make test` locally is what CI checks. ```bash -make lint # shellcheck the script, utilities, and test scripts +make lint # shellcheck + shfmt the script, utilities, and test scripts make test # lint + the dependency-free suite (runs on macOS or Linux, no Docker) make test-e2e # full end-to-end in disposable Linux containers (needs Docker) ```