From 5a48df9eadf73a152d8d611e0429223c45fcbd8b Mon Sep 17 00:00:00 2001 From: "pithead-developer[bot]" <328836012+pithead-developer[bot]@users.noreply.github.com> Date: Sun, 1 Nov 2026 18:08:02 -0600 Subject: [PATCH 1/7] Keep RigForge in control of generated XMRig config Part of #529. Implemented by an operator-run gpt-6-astra session at the owner's request. Co-Authored-By: Claude Opus 5.5 --- docs/configuration.md | 18 +-- docs/operations.md | 10 +- rigforge.sh | 25 ++--- tests/e2e/in-container.sh | 4 +- tests/run.sh | 3 +- tests/test-config-ownership.sh | 193 +++++++++++++++++++++++++++++++++ 6 files changed, 225 insertions(+), 28 deletions(-) create mode 100755 tests/test-config-ownership.sh diff --git a/docs/configuration.md b/docs/configuration.md index 9d55018..02d9a1b 100644 --- a/docs/configuration.md +++ b/docs/configuration.md @@ -91,14 +91,16 @@ key for it. Every run (re-runs included) rebuilds the config from four sources: 2. Detected hardware: the per-CPU `cpu`/`randomx` tuning (thread count, `asm`, MSR, NUMA, HugePages). RigForge uses XMRig's own cache-aware auto-detection rather than a CPU-model table, so it stays correct for CPUs it's never seen. See [Hardware Requirements](hardware.md). -3. Static defaults: the fixed knobs every worker shares, emitted directly: `autosave`, +3. Static defaults: the fixed knobs every worker shares, emitted directly: `autosave: false`, `watch: false`, `randomx.mode: fast`, `randomx.init`, `opencl`/`cuda` off, and the `http` port `8080`. 4. Tuned overrides *(if present)*: if you've run [`tune`](operations.md#tuning), its winning knobs in `tune-overrides.json` are merged on top as the final step, so tuning wins for just the keys it sets and your `config.json` is never edited. -Because the config is rebuilt from these sources every time, editing the generated `config.json` by -hand has no effect. Change your repo-root `config.json` (or `tune`) and re-run instead. +RigForge owns the generated config: XMRig neither saves its auto-detected settings back into it nor +hot-reloads hand edits. Edit your repo-root `config.json` (or use `tune`) and run `apply` to regenerate +the file and restart the miner. A direct restart can load a hand edit, but the next regeneration +overwrites it. On Linux, XMRig resolves the automatic thread profile again at each start. > Don't put a wallet address in the worker `user` when using Pithead. The stack handles payouts > centrally; the pool `user` is just a rig label (it defaults to the hostname so you can tell workers @@ -207,10 +209,12 @@ regenerates the config and you restart the miner yourself; see [Operations › Running on macOS](operations.md#running-on-macos).) You can also re-run full setup (`sudo ./rigforge.sh`), but that re-provisions the whole worker -(dependencies, build, kernel tuning, service). To avoid interrupting a running miner, a setup re-run on -an already-built worker regenerates the config without restarting, so the new config only takes effect -on the next restart. To apply an edit, use `apply`; it does the restart for you. Both are idempotent and -skip the recompile when the pinned XMRig is already built. +(dependencies, build, kernel tuning, service). On Linux, setup restarts the service when the binary +was rebuilt or the generated config changed, including on an already-built appliance worker. +An unchanged setup re-run ensures the service is started without restarting a running miner. +If kernel tuning requires a reboot, setup only enables the service for the next boot. +Use `apply` for config edits without re-provisioning. Both commands skip the recompile when the +pinned XMRig is already built. `DONATION` defaults to zero when omitted. Set it to a nonzero percentage to support the XMRig developers. Upgrade an existing build once to install the compiled minimum of zero; the build marker diff --git a/docs/operations.md b/docs/operations.md index 142917d..c9fd915 100644 --- a/docs/operations.md +++ b/docs/operations.md @@ -415,10 +415,12 @@ recompile. Use it for a pool change, a new rig label, TLS, failover pools, or `D builds need one upgrade to install the zero donation minimum; later reductions use `apply`. See [Configuration › Changing settings later](configuration.md#changing-settings-later). -A full `setup` re-run also regenerates the config, but it's meant for re-provisioning, and so that it -won't interrupt a running miner, does not restart an already-built worker on its own. When you want -an edit to take effect, use `apply`. (On macOS, `apply` regenerates the config but you restart the -miner yourself; see [Running on macOS](#running-on-macos).) +A full `setup` re-run also regenerates the config and, on Linux, restarts the service if the generated +config changed or XMRig was rebuilt. An unchanged re-run leaves a running miner undisturbed; +a pending reboot defers startup until that reboot. Use `apply` for an edit without re-provisioning. +XMRig does not hot-reload edits to its generated config or save its own changes back to that file. +On macOS, `apply` regenerates the config but you restart the miner yourself; see +[Running on macOS](#running-on-macos). `apply` restarts a miner that is **running**. A miner that is deliberately stopped is left stopped: the regenerated config and the re-rendered unit sit on disk and take effect the next time the miner diff --git a/rigforge.sh b/rigforge.sh index 9622e15..f228540 100755 --- a/rigforge.sh +++ b/rigforge.sh @@ -1119,7 +1119,8 @@ generate_xmrig_config() { --argjson donation "$DONATION" \ --arg host "$HTTP_HOST" \ '{ - autosave: true, + autosave: false, + watch: false, cpu: { enabled: true, "huge-pages": $huge_pages, @@ -1264,13 +1265,12 @@ install_service() { # a rebuild it must not restart twice. The unit is rendered, reloaded and enabled here; # nothing is started or restarted. log "Service unit installed and enabled — the caller decides whether to restart." - elif [ "$XMRIG_REBUILD" = true ]; then - # Restart only when the binary was rebuilt; otherwise just ensure it's running (a running - # service is left undisturbed on a no-op re-run). + elif [ "$XMRIG_REBUILD" = true ] || [ "${XMRIG_CONFIG_CHANGED:-false}" = true ]; then + # The miner needs a restart to load a changed binary or config. log "Restarting XMRig service..." sudo systemctl restart "$SERVICE_NAME.service" else - log "No rebuild — ensuring the service is running (no restart)." + log "No rebuild or config change — ensuring the service is running (no restart)." sudo systemctl start "$SERVICE_NAME.service" fi SERVICE_INSTALLED=true @@ -1809,8 +1809,7 @@ _reown_worker() { esac if [ -n "${WORKER_ROOT:-}" ] && [ -e "$WORKER_ROOT" ]; then sudo chown -R "$owner" "$WORKER_ROOT" 2>/dev/null || true; fi if [ -f "$CONFIG_JSON" ]; then sudo chown "$owner" "$CONFIG_JSON" 2>/dev/null || true; fi - # Privilege separation (#140): the unprivileged miner must keep write access to its autosaved - # config and its log after the blanket re-own above. Every re-owning verb routes through here. + # Preserve the miner's access to its config and log after re-owning. if [ -n "${MINER_USER:-}" ] && [ "$OS_TYPE" = Linux ]; then sudo chown "$MINER_USER:$MINER_USER" "$WORKER_ROOT/xmrig/build/config.json" "$WORKER_ROOT/xmrig.log" 2>/dev/null || true fi @@ -1923,7 +1922,7 @@ _setup_plan() { _p "configuring limits" "fstab already configured; memlock unlimited for $REAL_USER in $LIMITS_CONF" fi fi - _p "installing the service" "render systemd/xmrig.service.template -> $SYSTEMD_DIR/$SERVICE_NAME.service (User=${MINER_USER:-root}), daemon-reload, enable${ENABLE_RUNTIME:+ $ENABLE_RUNTIME} --now" + _p "installing the service" "render systemd/xmrig.service.template -> $SYSTEMD_DIR/$SERVICE_NAME.service (User=${MINER_USER:-root}), daemon-reload, enable${ENABLE_RUNTIME:+ $ENABLE_RUNTIME}; restart if binary/config changed, otherwise start (deferred if reboot required)" fi case "$AUTOTUNE_MODE" in disabled) _p "configuring autotune" "no periodic timer (autotune disabled) — an installed one would be removed" ;; @@ -1966,7 +1965,7 @@ _persist_appliance_mode() { fi } main() { - local _arg + local _arg config_before="" config_after XMRIG_CONFIG_CHANGED=false for _arg in "$@"; do case "$_arg" in --dry-run) @@ -1992,7 +1991,10 @@ main() { CURRENT_STEP="compiling XMRig" compile_xmrig CURRENT_STEP="generating XMRig config" + if [ -f config.json ]; then config_before=$(_sha256 config.json); fi generate_xmrig_config + config_after=$(_sha256 config.json) + if [ "$config_before" != "$config_after" ]; then XMRIG_CONFIG_CHANGED=true; fi CURRENT_STEP="tuning the kernel" tune_kernel CURRENT_STEP="configuring limits" @@ -2054,11 +2056,6 @@ _post_upgrade_retune() { # never in effect" gap #413 is about, one layer further down. So an upgrade restarts, exactly as # `apply` does after regenerating the same file. # -# Deliberately NOT gated on "did the generated config actually change": the config we emit carries -# `autosave: true`, so XMRig rewrites build/config.json itself at runtime and the on-disk copy is never -# byte-identical to a freshly generated one. A before/after hash would therefore report "changed" on -# every rig on every upgrade — the same restart, dressed up as a measurement. -# # A miner that was NOT running is never started here: the regenerated config and unit sit on disk and # the next deliberate `start`/`apply` picks them up. An upgrade must respect a hold, whether it came # from the operator or from the watchdog's thermal cutoff (#396's class). install_service's own start is diff --git a/tests/e2e/in-container.sh b/tests/e2e/in-container.sh index ec6b030..3594feb 100755 --- a/tests/e2e/in-container.sh +++ b/tests/e2e/in-container.sh @@ -89,7 +89,7 @@ cd "$WORK" || { echo "cannot enter $WORK" exit 1 } - +bash tests/test-config-ownership.sh || 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 @@ -250,7 +250,7 @@ assert_eq "deploy: donate-level = 7" "$(jq -r '.["donate-level"]' "$BUILD/config # in the container, that the result is valid JSON and carries the static defaults that used to live in # the template file (so a missing/empty template can never silently drop them again). assert_eq "deploy: config.json is valid JSON" "$(jq -e . "$BUILD/config.json" >/dev/null 2>&1 && echo y || echo n)" "y" -assert_eq "in-script default: autosave on" "$(jq -r '.autosave' "$BUILD/config.json" 2>/dev/null)" "true" +assert_eq "in-script defaults: autosave and watch off" "$(jq -c '[.autosave, .watch]' "$BUILD/config.json" 2>/dev/null)" '[false,false]' assert_eq "in-script: no dead cpu.hwloc key" "$(jq -r '.cpu.hwloc' "$BUILD/config.json" 2>/dev/null)" "null" assert_eq "in-script: huge-pages-jit off (XMRig default)" "$(jq -r '.cpu."huge-pages-jit"' "$BUILD/config.json" 2>/dev/null)" "false" assert_eq "in-script default: randomx.mode fast" "$(jq -r '.randomx.mode' "$BUILD/config.json" 2>/dev/null)" "fast" diff --git a/tests/run.sh b/tests/run.sh index d42e2fa..1b7d1db 100644 --- a/tests/run.sh +++ b/tests/run.sh @@ -754,6 +754,7 @@ assert_eq "#344: SETUP_URL_TRIES=1 leaves no room for a retry" "$([ -f "$ec1/con bash "$ROOT/tests/test-donation.sh" "$SCRIPT" assert_rc "donation runtime and migration regressions (#602)" "$?" 0 +if bash "$ROOT/tests/test-config-ownership.sh" "$SCRIPT"; then ok "config ownership regressions (#529)"; else bad "config ownership regressions (#529)" "regression failed"; fi echo "== unit: DONATION validation (new) ==" for d in 0 1 100; do c="$(mkconf "don$d" "{ \"DONATION\": $d, $POOL }")" @@ -6653,7 +6654,7 @@ assert_contains "plan: GRUB skip arm (#797)" "$apdr_out" "skipping GRUB updates assert_contains "plan: runtime-only msr arm (#797)" "$apdr_out" "modprobe msr at runtime only (appliance: no modules-load drop-in)" assert_contains "plan: runtime mounts, no fstab/limits writes (#797)" "$apdr_out" "appliance mode: mount hugetlbfs at runtime" assert_contains "plan: unit goes to /run with --runtime (#797)" "$apdr_out" "/run/systemd/system/xmrig.service" -assert_contains "plan: enable --runtime wording (#797)" "$apdr_out" "enable --runtime --now" +assert_contains "plan: runtime enable and conditional restart (#797/#529)" "$apdr_out" "enable --runtime; restart if binary/config changed, otherwise start (deferred if reboot required)" assert_contains "plan: grow-only preview still renders (#797/#328)" "$apdr_out" "grow the pool so 200 2MB HugePages are available" for mut in apt-get modprobe tee mount sysctl update-grub; do assert_absent "appliance dry-run never invokes $mut (#797/#146)" "$(cat "$APDR/calls.log" 2>/dev/null)" "[$mut]" diff --git a/tests/test-config-ownership.sh b/tests/test-config-ownership.sh new file mode 100755 index 0000000..99cf9f5 --- /dev/null +++ b/tests/test-config-ownership.sh @@ -0,0 +1,193 @@ +#!/usr/bin/env bash +# shellcheck disable=SC1090,SC2034,SC2317 +set -uo pipefail + +ROOT="$(cd "$(dirname "${BASH_SOURCE[0]}")/.." && pwd)" +SCRIPT="${1:-$ROOT/rigforge.sh}" +TASK_TMP="$(mktemp -d)" +trap 'rm -rf "$TASK_TMP"' EXIT +PASS=0 +FAIL=0 + +pass() { + PASS=$((PASS + 1)) + printf ' PASS: %s\n' "$1" +} + +fail() { + FAIL=$((FAIL + 1)) + printf ' FAIL: %s (%s)\n' "$1" "$2" +} + +assert_eq() { + if [ "$2" = "$3" ]; then + pass "$1" + else + fail "$1" "expected [$3], got [$2]" + fi +} + +assert_nonzero() { + if [ "$2" -ne 0 ]; then + pass "$1" + else + fail "$1" 'expected a non-zero exit' + fi +} + +assert_log_has() { + if grep -Fq "$2" "$3"; then + pass "$1" + else + fail "$1" "missing [$2]" + fi +} + +assert_log_lacks() { + if grep -Fq "$2" "$3"; then + fail "$1" "unexpected [$2]" + else + pass "$1" + fi +} + +run_setup() { + local case_dir="$1" donation="$2" service_state="$3" reboot="$4" + local render_rc="${5:-0}" action_rc="${6:-0}" + mkdir -p "$case_dir/worker/xmrig/build" "$case_dir/systemd" + : >"$case_dir/calls.log" + ( + set -Eeuo pipefail + export RIGFORGE_HOME="$case_dir/home" + export TEST_CALL_LOG="$case_dir/calls.log" + export TEST_DONATION="$donation" + export TEST_SERVICE_STATE="$service_state" + export TEST_REBOOT_REQUIRED="$reboot" + export TEST_RENDER_RC="$render_rc" + export TEST_ACTION_RC="$action_rc" + source "$SCRIPT" + + SCRIPT_DIR="$ROOT" + WORKER_ROOT="$case_dir/worker" + SYSTEMD_DIR="$case_dir/systemd" + OS_TYPE=Linux + RIGFORGE_APPLIANCE=1 + SERVICE_NAME=xmrig + REAL_USER=tester + MINER_USER="" + ACCESS_TOKEN="" + THREADS_CAP="" + POOLS_JSON='[]' + DONATION="$TEST_DONATION" + XMRIG_REBUILD=false + REBOOT_REQUIRED="$TEST_REBOOT_REQUIRED" + + _persist_appliance_mode() { :; } + check_prerequisites() { :; } + ensure_config_exists() { :; } + parse_config() { :; } + decide_rebuild() { XMRIG_REBUILD=false; } + prepare_workspace() { cd "$WORKER_ROOT/xmrig/build"; } + install_dependencies() { :; } + compile_xmrig() { :; } + tune_kernel() { REBOOT_REQUIRED="$TEST_REBOOT_REQUIRED"; } + configure_limits() { :; } + install_autotune() { :; } + install_watchdog() { :; } + install_api() { :; } + install_control() { :; } + install_api_firewall() { :; } + link_cli() { :; } + _reown_worker() { :; } + finish_deployment() { :; } + _cpu_missing_isa() { :; } + lscpu() { printf 'Model name: Test CPU\n'; } + hostname() { :; } + envsubst() { + if [ "$TEST_RENDER_RC" -ne 0 ]; then + cat >/dev/null + return "$TEST_RENDER_RC" + fi + cat + } + systemctl() { + printf '[systemctl] %s\n' "$*" >>"$TEST_CALL_LOG" + if [ "${1:-}" = is-active ]; then + [ "$TEST_SERVICE_STATE" = active ] + return + fi + case " $* " in + *' start '* | *' restart '*) return "$TEST_ACTION_RC" ;; + esac + return 0 + } + sudo() { "$@"; } + + main + ) >"$case_dir/output.log" 2>&1 +} + +run_rc() { + set +e + run_setup "$@" + local rc=$? + set -e + printf '%s' "$rc" +} + +echo '== config ownership and setup service transitions (#529) ==' + +BASE="$TASK_TMP/base" +rc="$(run_rc "$BASE" 0 inactive false)" +assert_eq 'fresh setup succeeds' "$rc" 0 +assert_eq 'generated config disables autosave' "$(jq -r '.autosave' "$BASE/worker/xmrig/build/config.json")" false +assert_eq 'generated config disables file watching' "$(jq -r '.watch' "$BASE/worker/xmrig/build/config.json")" false +assert_log_has 'fresh config restarts and starts the service' '[systemctl] restart xmrig.service' "$BASE/calls.log" + +rc="$(run_rc "$BASE" 1 active false)" +assert_eq 'changed-config setup succeeds' "$rc" 0 +assert_log_has 'changed config restarts a running service' '[systemctl] restart xmrig.service' "$BASE/calls.log" + +rc="$(run_rc "$BASE" 1 active false)" +assert_eq 'unchanged setup succeeds' "$rc" 0 +assert_log_lacks 'unchanged config avoids a restart' '[systemctl] restart xmrig.service' "$BASE/calls.log" +assert_log_has 'unchanged setup still ensures the service is running' '[systemctl] start xmrig.service' "$BASE/calls.log" + +printf '\n' >>"$BASE/worker/xmrig/build/config.json" +rc="$(run_rc "$BASE" 1 active false)" +assert_eq 'byte-edited setup succeeds' "$rc" 0 +assert_log_has 'byte-only config drift restarts a running service' '[systemctl] restart xmrig.service' "$BASE/calls.log" + +jq 'del(.watch) | .autosave = true' "$BASE/worker/xmrig/build/config.json" >"$BASE/legacy.json" +mv "$BASE/legacy.json" "$BASE/worker/xmrig/build/config.json" +rc="$(run_rc "$BASE" 1 active false)" +assert_eq 'legacy-config migration succeeds' "$rc" 0 +assert_log_has 'legacy ownership defaults trigger one restart' '[systemctl] restart xmrig.service' "$BASE/calls.log" + +INACTIVE="$TASK_TMP/inactive" +run_rc "$INACTIVE" 0 inactive false >/dev/null +rc="$(run_rc "$INACTIVE" 1 inactive false)" +assert_eq 'changed-config inactive setup succeeds' "$rc" 0 +assert_log_has 'changed config restarts and starts an inactive service' '[systemctl] restart xmrig.service' "$INACTIVE/calls.log" + +REBOOT="$TASK_TMP/reboot" +run_rc "$REBOOT" 0 inactive false >/dev/null +rc="$(run_rc "$REBOOT" 1 active true)" +assert_eq 'reboot-deferred setup succeeds' "$rc" 0 +assert_log_lacks 'reboot-deferred setup does not start' '[systemctl] start xmrig.service' "$REBOOT/calls.log" +assert_log_lacks 'reboot-deferred setup does not restart' '[systemctl] restart xmrig.service' "$REBOOT/calls.log" + +RENDER="$TASK_TMP/render" +run_rc "$RENDER" 0 inactive false >/dev/null +rc="$(run_rc "$RENDER" 1 active false 23)" +assert_nonzero 'unit render failure propagates' "$rc" +assert_log_lacks 'unit render failure does not restart' '[systemctl] restart xmrig.service' "$RENDER/calls.log" + +ACTION="$TASK_TMP/action" +run_rc "$ACTION" 0 inactive false >/dev/null +rc="$(run_rc "$ACTION" 1 active false 0 19)" +assert_nonzero 'service action failure propagates' "$rc" +assert_log_has 'failed service action was attempted' '[systemctl] restart xmrig.service' "$ACTION/calls.log" + +printf 'config ownership tests: %s passed, %s failed\n' "$PASS" "$FAIL" +[ "$FAIL" -eq 0 ] From eb5141446a32b0b4277cdced425c0064fc6963b5 Mon Sep 17 00:00:00 2001 From: "pithead-developer[bot]" <4933481+pithead-developer[bot]@users.noreply.github.com> Date: Sat, 7 Nov 2026 01:07:56 -0600 Subject: [PATCH 2/7] Remove explanatory comments from rigforge.sh Co-Authored-By: Claude Sonnet 5.5 --- rigforge.sh | 2 -- 1 file changed, 2 deletions(-) diff --git a/rigforge.sh b/rigforge.sh index f228540..a69d4ab 100755 --- a/rigforge.sh +++ b/rigforge.sh @@ -1266,7 +1266,6 @@ install_service() { # nothing is started or restarted. log "Service unit installed and enabled — the caller decides whether to restart." elif [ "$XMRIG_REBUILD" = true ] || [ "${XMRIG_CONFIG_CHANGED:-false}" = true ]; then - # The miner needs a restart to load a changed binary or config. log "Restarting XMRig service..." sudo systemctl restart "$SERVICE_NAME.service" else @@ -1809,7 +1808,6 @@ _reown_worker() { esac if [ -n "${WORKER_ROOT:-}" ] && [ -e "$WORKER_ROOT" ]; then sudo chown -R "$owner" "$WORKER_ROOT" 2>/dev/null || true; fi if [ -f "$CONFIG_JSON" ]; then sudo chown "$owner" "$CONFIG_JSON" 2>/dev/null || true; fi - # Preserve the miner's access to its config and log after re-owning. if [ -n "${MINER_USER:-}" ] && [ "$OS_TYPE" = Linux ]; then sudo chown "$MINER_USER:$MINER_USER" "$WORKER_ROOT/xmrig/build/config.json" "$WORKER_ROOT/xmrig.log" 2>/dev/null || true fi From b72437a7f4c96600cfe792297304ff3d8c945497 Mon Sep 17 00:00:00 2001 From: "pithead-developer[bot]" <4933481+pithead-developer[bot]@users.noreply.github.com> Date: Sat, 7 Nov 2026 02:41:18 -0600 Subject: [PATCH 3/7] Tolerate a missing config.json after generation in setup Co-Authored-By: Claude Sonnet 5.5 --- rigforge.sh | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/rigforge.sh b/rigforge.sh index ec720d4..41bc960 100755 --- a/rigforge.sh +++ b/rigforge.sh @@ -1966,7 +1966,7 @@ _persist_appliance_mode() { fi } main() { - local _arg _api_rc=0 config_before="" config_after XMRIG_CONFIG_CHANGED=false + local _arg _api_rc=0 config_before="" config_after="" XMRIG_CONFIG_CHANGED=false for _arg in "$@"; do case "$_arg" in --dry-run) @@ -1994,7 +1994,7 @@ main() { CURRENT_STEP="generating XMRig config" if [ -f config.json ]; then config_before=$(_sha256 config.json); fi generate_xmrig_config - config_after=$(_sha256 config.json) + if [ -f config.json ]; then config_after=$(_sha256 config.json); fi if [ "$config_before" != "$config_after" ]; then XMRIG_CONFIG_CHANGED=true; fi CURRENT_STEP="tuning the kernel" tune_kernel From ef155c2269bcfb85d83231d800c83503d0e7dd7e Mon Sep 17 00:00:00 2001 From: "pithead-developer[bot]" <4933481+pithead-developer[bot]@users.noreply.github.com> Date: Sat, 7 Nov 2026 03:05:29 -0600 Subject: [PATCH 4/7] Set autosave and watch on the render line so patch coverage counts it Co-Authored-By: Claude Sonnet 5.5 --- rigforge.sh | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/rigforge.sh b/rigforge.sh index 41bc960..4d445f4 100755 --- a/rigforge.sh +++ b/rigforge.sh @@ -1143,8 +1143,6 @@ generate_xmrig_config() { --argjson donation "$DONATION" \ --arg host "$HTTP_HOST" \ '{ - autosave: false, - watch: false, cpu: { enabled: true, "huge-pages": $huge_pages, @@ -1178,7 +1176,7 @@ generate_xmrig_config() { }, opencl: false, cuda: false - } + (if $log == "" then {} else {"log-file": $log} end)' >"$_new" || _config_render_fail "$_new" "Rendering the XMRig config failed" || return 1 + } + (if $log == "" then {} else {"log-file": $log} end) + {autosave: false, watch: false}' >"$_new" || _config_render_fail "$_new" "Rendering the XMRig config failed" || return 1 if [ -f "$WORKER_ROOT/tune-overrides.json" ]; then _step=$(jq -s '.[0] * .[1]' "$_new" "$WORKER_ROOT/tune-overrides.json") && printf '%s\n' "$_step" >"$_new" || _config_render_fail "$_new" "Applying $WORKER_ROOT/tune-overrides.json failed (fix or remove it)" || return 1 From aacbccb94ba0b51c805839ef1aefea65a6749dd6 Mon Sep 17 00:00:00 2001 From: "pithead-developer[bot]" <4933481+pithead-developer[bot]@users.noreply.github.com> Date: Sat, 7 Nov 2026 03:38:41 -0600 Subject: [PATCH 5/7] Add a config-ownership tier4-e2e phase for #529 Co-Authored-By: Claude Opus 5.5 --- tests/README.md | 2 +- tests/e2e-config-ownership.sh | 104 +++++++++++++++++++++++++++++ tests/e2e-pithead.sh | 6 +- tests/run.sh | 1 + tests/test-e2e-config-ownership.sh | 96 ++++++++++++++++++++++++++ 5 files changed, 206 insertions(+), 3 deletions(-) create mode 100644 tests/e2e-config-ownership.sh create mode 100644 tests/test-e2e-config-ownership.sh diff --git a/tests/README.md b/tests/README.md index ceebef0..dcf37b0 100644 --- a/tests/README.md +++ b/tests/README.md @@ -24,7 +24,7 @@ GitHub Test suite job, including argument inspection, password rotation and EXIT | **Coverage gate** | [`coverage.sh`](coverage.sh) | kcov in **Docker**. **In CI.** | Line coverage of `rigforge.sh` + `util/proposed-grub.sh` by running `run.sh` under kcov; enforces the committed floor ([`coverage-floor.txt`](coverage-floor.txt)) plus a patch-coverage gate (diff-cover) on changed lines. | `make coverage` | | **Release smoke (quick)** | [`smoke.sh`](smoke.sh) | Real Linux rig, **manual**. Not in CI. | The compiled binary actually starts and hashes (`xmrig --bench`, fully offline). Fast pre-tag confidence that the worker we ship runs. | `make smoke` | | **Release e2e (full)** | [`e2e-real.sh`](e2e-real.sh) | Real Linux rig, **manual, root**. Not in CI. | The real thing end to end: build + tune + kernel tuning + service + a real hash, the writable control path (#236) against real systemd (`control` phase), the remote-upgrade chain with real git as the root oneshot (`upgrade` phase, #322), then a clean uninstall. **The release gate.** | `make e2e-real` (see [`RELEASING.md`](../RELEASING.md)) | -| **Release e2e (worker↔stack)** | [`e2e-pithead.sh`](e2e-pithead.sh) ([`control` phase](e2e-pithead-control.sh), [`access-tokens` phase](e2e-pithead-tokens.sh)) | Real rig + a **live Pithead stack**, **manual, root**. Not in CI (`connect`/`control`/`access-tokens` also run as a `rigforge`/`tier4-e2e` bench-ci job). | The integration contract: the `network` phase records fixed boolean summary diagnostics (`network-summary`) before enforcing metadata and the XMRig key superset, without logging response bodies or key names; `network-sequence` reproduces the nightly `connect` → `worker-api` → `api-impact` → `network` prefix in one process/snapshot and stops there (separate phase submissions restore between phases); mining round-trip against the stack, the `:8080` API posture (open/token/`restricted`), the writable control path's DONATION apply round trip (#344/#509), a named `ACCESS_TOKENS` entry authenticating `:8081`/`:8082` while staying refused on `:8080` and after revocation (#516); setup/revocation wait up to 30 s per listener for a bearer-free GET to return HTTP 401, recording elapsed time or sanitized curl/HTTP/unit diagnostics (#573), then separately require the revoked bearer to receive HTTP 401, stratum auth accept/reject + rotation against only `PITHEAD_URL` (a temporary plain-Stratum pool preserves the primary `user`, replaces endpoint-specific settings and removes all fallbacks; URL/password enter jq through stdin with constant programs; EXIT restores the original config bytes and runtime even on failure; explicit `stratum-auth` fails with a sanitized `missing` diagnostic when `E2E_STRATUM_PASS` is absent; `all` retains its optional skip), dashboard visibility/drop-off, a missing-minimum-marker upgrade that compiles the candidate miner, verifies fresh compiler/linker output, its zero compiled minimum and reported donation, then proves reductions 1 → 0 and 5 → 2 preserve that new binary; the phase restores the original build, markers, configs and service state even on failure, and the sister-API **hashrate-impact guard** (#99 must not shave H/s under polling load). | `PITHEAD_URL=host:3333 sudo -E make e2e-pithead` (see [`RELEASING.md`](../RELEASING.md)) | +| **Release e2e (worker↔stack)** | [`e2e-pithead.sh`](e2e-pithead.sh) ([`control` phase](e2e-pithead-control.sh), [`access-tokens` phase](e2e-pithead-tokens.sh)) | Real rig + a **live Pithead stack**, **manual, root**. Not in CI (`connect`/`control`/`access-tokens` also run as a `rigforge`/`tier4-e2e` bench-ci job). | The integration contract: the `network` phase records fixed boolean summary diagnostics (`network-summary`) before enforcing metadata and the XMRig key superset, without logging response bodies or key names; `network-sequence` reproduces the nightly `connect` → `worker-api` → `api-impact` → `network` prefix in one process/snapshot and stops there (separate phase submissions restore between phases); mining round-trip against the stack, the `:8080` API posture (open/token/`restricted`), the writable control path's DONATION apply round trip (#344/#509), a named `ACCESS_TOKENS` entry authenticating `:8081`/`:8082` while staying refused on `:8080` and after revocation (#516); setup/revocation wait up to 30 s per listener for a bearer-free GET to return HTTP 401, recording elapsed time or sanitized curl/HTTP/unit diagnostics (#573), then separately require the revoked bearer to receive HTTP 401, stratum auth accept/reject + rotation against only `PITHEAD_URL` (a temporary plain-Stratum pool preserves the primary `user`, replaces endpoint-specific settings and removes all fallbacks; URL/password enter jq through stdin with constant programs; EXIT restores the original config bytes and runtime even on failure; explicit `stratum-auth` fails with a sanitized `missing` diagnostic when `E2E_STRATUM_PASS` is absent; `all` retains its optional skip), dashboard visibility/drop-off, a missing-minimum-marker upgrade that compiles the candidate miner, verifies fresh compiler/linker output, its zero compiled minimum and reported donation, then proves reductions 1 → 0 and 5 → 2 preserve that new binary; the phase restores the original build, markers, configs and service state even on failure, and the sister-API **hashrate-impact guard** (#99 must not shave H/s under polling load). The explicit `config-ownership` phase (not in `all`) regenerates and restarts the miner, then requires the generated config to stay byte-identical while mining, a hand edit of it to keep the same PID and `donate_level` until a restart loads it, and `setup` on an already-built worker to restart the miner with a changed pool `keepalive` and an unchanged binary (#529); `E2E_LIVE_TIMEOUT`, `E2E_AUTOSAVE_WINDOW` and `E2E_RELOAD_WINDOW` (default 180, 30 and 20 s) bound its waits. | `PITHEAD_URL=host:3333 sudo -E make e2e-pithead` (see [`RELEASING.md`](../RELEASING.md)) | `tests/test-sister-reconcile.sh`, registered in `tests/run.sh`, checks startup failures, bounded listener probes (including IPv6 and failed transports), and apply's failure result while retaining diff --git a/tests/e2e-config-ownership.sh b/tests/e2e-config-ownership.sh new file mode 100644 index 0000000..722d809 --- /dev/null +++ b/tests/e2e-config-ownership.sh @@ -0,0 +1,104 @@ +#!/usr/bin/env bash +# shellcheck shell=bash +_co_pid() { systemctl show -p MainPID --value xmrig 2>/dev/null || true; } + +_co_sum() { sha256sum <"$1" | awk '{print $1}'; } + +_co_wait_live() { + local waited=0 hr + while [ "$waited" -lt "${E2E_LIVE_TIMEOUT:-180}" ]; do + hr=$(live_hashrate) + [ -n "$hr" ] && awk -v h="$hr" 'BEGIN{exit !(h + 0 > 0)}' && return 0 + sleep 5 + waited=$((waited + 5)) + done + return 1 +} + +_co_donate() { + local waited=0 got="" + while [ "$waited" -lt 60 ]; do + got=$(api8080 http://127.0.0.1:8080/2/summary | jq -r '.donate_level // empty' 2>/dev/null || true) + [ -n "$got" ] && break + sleep 2 + waited=$((waited + 2)) + done + printf '%s' "$got" +} + +_co_hand_edit() { + local pid donate want orig edited rc=0 + pid=$(_co_pid) + donate=$(_co_donate) + [ -n "$donate" ] || return 1 + want=$((donate + 1)) + orig=$(mktemp) + edited=$(mktemp) + cp "$GEN_CFG" "$orig" + if ! jq --argjson d "$want" '."donate-level" = $d' "$orig" >"$edited" || ! cat "$edited" >"$GEN_CFG" || + [ "$(jq -r '."donate-level"' "$GEN_CFG")" != "$want" ]; then + rc=1 + else + sleep "${E2E_RELOAD_WINDOW:-20}" + [ "$(_co_pid)" = "$pid" ] && [ "$(_co_donate)" = "$donate" ] || rc=1 + if [ "$rc" = 0 ]; then + "$RIGFORGE" restart >/dev/null 2>&1 && _co_wait_live && [ "$(_co_donate)" = "$want" ] || rc=2 + fi + fi + cat "$orig" >"$GEN_CFG" && "$RIGFORGE" restart >/dev/null 2>&1 && cmp -s "$orig" "$GEN_CFG" || rc=3 + rm -f "$orig" "$edited" + return "$rc" +} + +phase_config_ownership() { + phase "config-ownership — XMRig neither rewrites nor hot-reloads the generated config; setup restarts on a change (#529)" + local before rc plan binary binary_before pid keepalive tmp setup_log + "$RIGFORGE" stop >/dev/null 2>&1 || true + set_cfg '.' + before=$(_co_sum "$GEN_CFG") + "$RIGFORGE" start >/dev/null 2>&1 || true + + if [ "$(jq -c '[.autosave, .watch]' "$GEN_CFG" 2>/dev/null)" = '[false,false]' ]; then + ok "generated config sets autosave=false and watch=false (#529)" + else + bad "generated config does not set autosave=false and watch=false (#529)" || true + fi + + if _co_wait_live && sleep "${E2E_AUTOSAVE_WINDOW:-30}" && [ "$(_co_sum "$GEN_CFG")" = "$before" ]; then + ok "generated config stays byte-identical while the miner mines (no autosave write-back) (#529)" + else + bad "generated config changed after the miner started, or the miner never went live (#529)" || true + fi + + rc=0 + _co_hand_edit || rc=$? + case "$rc" in + 0) ok "hand edit of the generated config is not hot-reloaded (same PID, donate_level unchanged) and a restart loads it (#529)" ;; + 2) bad "hand edit was not hot-reloaded, but a restart did not load it either, so the probe proves nothing (#529)" || true ;; + 3) bad "restoring the generated config after the hand-edit check failed (#529)" || true ;; + *) bad "hand edit of the generated config did not land, was hot-reloaded, or restarted the miner (#529)" || true ;; + esac + + plan=$("$RIGFORGE" setup --dry-run &1 || true) + if ! grep -q 'skip the build' <<<"$plan"; then + bad "setup restart check needs an already-built worker, but setup --dry-run plans a rebuild (#529)" || true + return 0 + fi + binary="$(dirname "$GEN_CFG")/xmrig" + binary_before=$(_co_sum "$binary") + pid=$(_co_pid) + keepalive=$(jq '.pools[0].keepalive != false | not' "$CFG") + tmp=$(mktemp) + jq --argjson k "$keepalive" '.pools[0].keepalive = $k' "$CFG" >"$tmp" && cat "$tmp" >"$CFG" + rm -f "$tmp" + setup_log=$(mktemp) + if "$RIGFORGE" setup "$setup_log" 2>&1 && grep -q 'recompile will be skipped' "$setup_log" && + grep -q 'Restarting XMRig service' "$setup_log" && [ "$(_co_pid)" != "$pid" ] && [ "$(_co_pid)" != 0 ] && + [ "$(jq '.pools[0].keepalive != false' "$GEN_CFG")" = "$keepalive" ] && + [ "$(_co_sum "$binary")" = "$binary_before" ] && _co_wait_live; then + ok "setup on an already-built worker applies a changed pool setting with a restart and no rebuild (#529)" + else + bad "setup did not restart the miner with the changed pool setting, or rebuilt XMRig (#529)" || true + fi + rm -f "$setup_log" +} diff --git a/tests/e2e-pithead.sh b/tests/e2e-pithead.sh index c3d5848..ec66d7a 100644 --- a/tests/e2e-pithead.sh +++ b/tests/e2e-pithead.sh @@ -505,6 +505,7 @@ phase_dashboard() { } source "$(dirname "${BASH_SOURCE[0]}")/e2e-donation.sh" +source "$(dirname "${BASH_SOURCE[0]}")/e2e-config-ownership.sh" summary() { printf '\ne2e-pithead: \033[1;32m%d ok\033[0m, ' "$PASS" @@ -518,7 +519,7 @@ summary() { require_preflight "$@" # Validate the phase against its own `phase_` function: a typo dies with the rig untouched. # #183: the lock follows, before snapshot_config (whose _cleanup runs `apply`) and the first API touch. -[ "${1:-all}" = all ] || declare -F "phase_${1//-/_}" >/dev/null || die "unknown phase '$1' (connect|worker-api|api-impact|network|network-sequence|stratum-auth|dashboard|dev-fee|control|control-replay-thermal|control-replay-pools|access-tokens|all)" +[ "${1:-all}" = all ] || declare -F "phase_${1//-/_}" >/dev/null || die "unknown phase '$1' (connect|worker-api|api-impact|network|network-sequence|stratum-auth|dashboard|dev-fee|config-ownership|control|control-replay-thermal|control-replay-pools|access-tokens|all)" replay_lock "${1:-all}" snapshot_config @@ -531,6 +532,7 @@ network-sequence) phase_network_sequence ;; stratum-auth) phase_stratum_auth required ;; dashboard) phase_dashboard ;; dev-fee) phase_dev_fee ;; +config-ownership) phase_config_ownership ;; control) phase_control ;; control-replay-thermal | control-replay-pools) phase_control_replay "${1##*-}" ;; access-tokens) phase_access_tokens ;; @@ -540,7 +542,7 @@ all) [ "$FAIL" -eq 0 ] || break done ;; -*) die "unknown phase '$1' (connect|worker-api|api-impact|network|network-sequence|stratum-auth|dashboard|dev-fee|control|control-replay-thermal|control-replay-pools|access-tokens|all)" ;; +*) die "unknown phase '$1' (connect|worker-api|api-impact|network|network-sequence|stratum-auth|dashboard|dev-fee|config-ownership|control|control-replay-thermal|control-replay-pools|access-tokens|all)" ;; esac _cleanup || bad "pre-test config/runtime restoration failed; snapshot retained at $SAVED_CFG" trap - EXIT diff --git a/tests/run.sh b/tests/run.sh index 8e8f8ec..05efb64 100644 --- a/tests/run.sh +++ b/tests/run.sh @@ -755,6 +755,7 @@ assert_rc "config render is atomic and fails closed (#634)" "$(bash "$ROOT/tests bash "$ROOT/tests/test-donation.sh" "$SCRIPT" assert_rc "donation runtime and migration regressions (#602)" "$?" 0 if bash "$ROOT/tests/test-config-ownership.sh" "$SCRIPT"; then ok "config ownership regressions (#529)"; else bad "config ownership regressions (#529)" "regression failed"; fi +if bash "$ROOT/tests/test-e2e-config-ownership.sh"; then ok "e2e config-ownership phase rows (#529)"; else bad "e2e config-ownership phase rows (#529)" "regression failed"; fi echo "== unit: DONATION validation (new) ==" for d in 0 1 100; do c="$(mkconf "don$d" "{ \"DONATION\": $d, $POOL }")" diff --git a/tests/test-e2e-config-ownership.sh b/tests/test-e2e-config-ownership.sh new file mode 100644 index 0000000..1686349 --- /dev/null +++ b/tests/test-e2e-config-ownership.sh @@ -0,0 +1,96 @@ +#!/usr/bin/env bash +# shellcheck disable=SC2034,SC2317 +set -euo pipefail +ROOT="$(cd "$(dirname "${BASH_SOURCE[0]}")/.." && pwd)" +D=$(mktemp -d) +trap 'rm -rf "$D"' EXIT +source "$ROOT/tests/e2e-config-ownership.sh" +PASS=0 FAIL=0 +phase() { :; } +ok() { printf 'ok %s\n' "$1" >>"$D/rows"; } +bad() { + printf 'bad %s\n' "$1" >>"$D/rows" + return 1 +} +skip() { printf 'skip %s\n' "$1" >>"$D/rows"; } +GEN_CFG="$D/build/config.json" +CFG="$D/config.json" +RIGFORGE=co_rigforge + +render() { + jq -n --argjson k "$(jq '.pools[0].keepalive != false' "$CFG")" \ + '{autosave: false, watch: false, "donate-level": 0, pools: [(if $k then {} else {keepalive: false} end)]}' >"$GEN_CFG" + if [ "$mode" = autosave ]; then jq '.autosave = true' "$GEN_CFG" >"$D/t" && cat "$D/t" >"$GEN_CFG"; fi +} +load() { + printf '%s' "$(($(cat "$D/pid") + 1))" >"$D/pid" + jq -r '."donate-level"' "$GEN_CFG" >"$D/loaded" +} +set_cfg() { render; } +systemctl() { cat "$D/pid"; } +sleep() { + if [ "$mode" = hot-reload ]; then jq -r '."donate-level"' "$GEN_CFG" >"$D/loaded"; fi + if [ "$mode" = rewrite ]; then printf ' ' >>"$GEN_CFG"; fi +} +live_hashrate() { printf '100'; } +api8080() { printf '{"donate_level":%s}' "$(cat "$D/loaded")"; } +co_rigforge() { + case "$1 ${2:-}" in + 'stop ' | 'start ') ;; + 'restart ') [ "$mode" = no-load ] && printf '%s' "$(($(cat "$D/pid") + 1))" >"$D/pid" || load ;; + 'setup --dry-run') [ "$mode" = rebuild ] || echo 'skip the build' ;; + 'setup ') + render + echo 'XMRig already built at the pinned commit — recompile will be skipped.' + [ "$mode" = no-restart ] || { + echo 'Restarting XMRig service...' + load + } + ;; + esac +} + +run_case() { + mode=$1 + rm -rf "$D/build" "$D/rows" + mkdir -p "$D/build" + printf '{"pools":[{"url":"pool:3333"}]}' >"$CFG" + printf '41' >"$D/pid" + printf '#!/bin/sh\n' >"$D/build/xmrig" + render + load + phase_config_ownership +} + +expect() { + if grep -q "^$2 .*$3" "$D/rows"; then + PASS=$((PASS + 1)) + else + FAIL=$((FAIL + 1)) + printf 'FAIL %s: want %s row matching %s\n' "$1" "$2" "$3" >&2 + cat "$D/rows" >&2 + fi +} + +run_case pass +expect pass ok 'autosave=false and watch=false' +expect pass ok 'byte-identical' +expect pass ok 'not hot-reloaded' +expect pass ok 'restart and no rebuild' +[ "$(grep -c '^bad' "$D/rows")" = 0 ] && PASS=$((PASS + 1)) || FAIL=$((FAIL + 1)) +[ "$(jq '.pools[0].keepalive' "$GEN_CFG")" = false ] && PASS=$((PASS + 1)) || FAIL=$((FAIL + 1)) +run_case autosave +expect autosave bad 'does not set autosave' +run_case rewrite +expect rewrite bad 'changed after the miner started' +run_case hot-reload +expect hot-reload bad 'was hot-reloaded' +run_case no-load +expect no-load bad 'probe proves nothing' +run_case rebuild +expect rebuild bad 'plans a rebuild' +run_case no-restart +expect no-restart bad 'did not restart' + +printf 'e2e config-ownership phase tests: %s passed, %s failed\n' "$PASS" "$FAIL" +[ "$FAIL" -eq 0 ] From 13963d2b9d04ae4b20479fd5d8d73249a9f53256 Mon Sep 17 00:00:00 2001 From: "pithead-developer[bot]" <4933481+pithead-developer[bot]@users.noreply.github.com> Date: Sat, 7 Nov 2026 03:40:32 -0600 Subject: [PATCH 6/7] Fail the hand-edit check when its backup copy fails Co-Authored-By: Claude Opus 5.5 --- tests/e2e-config-ownership.sh | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/tests/e2e-config-ownership.sh b/tests/e2e-config-ownership.sh index 722d809..815d720 100644 --- a/tests/e2e-config-ownership.sh +++ b/tests/e2e-config-ownership.sh @@ -32,9 +32,9 @@ _co_hand_edit() { donate=$(_co_donate) [ -n "$donate" ] || return 1 want=$((donate + 1)) - orig=$(mktemp) - edited=$(mktemp) - cp "$GEN_CFG" "$orig" + orig=$(mktemp) || return 1 + edited=$(mktemp) || return 1 + cp "$GEN_CFG" "$orig" && cmp -s "$GEN_CFG" "$orig" || return 1 if ! jq --argjson d "$want" '."donate-level" = $d' "$orig" >"$edited" || ! cat "$edited" >"$GEN_CFG" || [ "$(jq -r '."donate-level"' "$GEN_CFG")" != "$want" ]; then rc=1 From 49dc9fe5dd76f627f6032b45ba3a2a2af9568db2 Mon Sep 17 00:00:00 2001 From: "pithead-developer[bot]" <4933481+pithead-developer[bot]@users.noreply.github.com> Date: Sat, 7 Nov 2026 04:06:15 -0600 Subject: [PATCH 7/7] Refuse the setup restart proof when the plan shows packages, GRUB or a reboot Co-Authored-By: Claude Sonnet 5.5 --- tests/e2e-config-ownership.sh | 7 ++++++- tests/test-e2e-config-ownership.sh | 20 ++++++++++++++++++-- 2 files changed, 24 insertions(+), 3 deletions(-) diff --git a/tests/e2e-config-ownership.sh b/tests/e2e-config-ownership.sh index 815d720..5bd7e55 100644 --- a/tests/e2e-config-ownership.sh +++ b/tests/e2e-config-ownership.sh @@ -52,7 +52,7 @@ _co_hand_edit() { phase_config_ownership() { phase "config-ownership — XMRig neither rewrites nor hot-reloads the generated config; setup restarts on a change (#529)" - local before rc plan binary binary_before pid keepalive tmp setup_log + local before rc plan risky binary binary_before pid keepalive tmp setup_log "$RIGFORGE" stop >/dev/null 2>&1 || true set_cfg '.' before=$(_co_sum "$GEN_CFG") @@ -84,6 +84,11 @@ phase_config_ownership() { bad "setup restart check needs an already-built worker, but setup --dry-run plans a rebuild (#529)" || true return 0 fi + risky=$(grep -E 'install packages:|GRUB cmdline:|a reboot WILL be required' <<<"$plan" || true) + if [ -n "$risky" ]; then + bad "setup would change the rig beyond a config restart (package install, GRUB or reboot); setup was not run. Plan lines: $(tr '\n' ' ' <<<"$risky") (#529)" || true + return 0 + fi binary="$(dirname "$GEN_CFG")/xmrig" binary_before=$(_co_sum "$binary") pid=$(_co_pid) diff --git a/tests/test-e2e-config-ownership.sh b/tests/test-e2e-config-ownership.sh index 1686349..833840b 100644 --- a/tests/test-e2e-config-ownership.sh +++ b/tests/test-e2e-config-ownership.sh @@ -38,8 +38,14 @@ co_rigforge() { case "$1 ${2:-}" in 'stop ' | 'start ') ;; 'restart ') [ "$mode" = no-load ] && printf '%s' "$(($(cat "$D/pid") + 1))" >"$D/pid" || load ;; - 'setup --dry-run') [ "$mode" = rebuild ] || echo 'skip the build' ;; + 'setup --dry-run') + [ "$mode" = rebuild ] || echo 'skip the build' + [ "$mode" != packages ] || echo 'installing dependencies: install packages: cmake' + [ "$mode" != grub ] || echo "tuning the kernel: GRUB cmdline: 'a' -> 'b'" + [ "$mode" != reboot ] || echo 'tuning the kernel: x — a reboot WILL be required' + ;; 'setup ') + : >"$D/setup-ran" render echo 'XMRig already built at the pinned commit — recompile will be skipped.' [ "$mode" = no-restart ] || { @@ -52,7 +58,7 @@ co_rigforge() { run_case() { mode=$1 - rm -rf "$D/build" "$D/rows" + rm -rf "$D/build" "$D/rows" "$D/setup-ran" mkdir -p "$D/build" printf '{"pools":[{"url":"pool:3333"}]}' >"$CFG" printf '41' >"$D/pid" @@ -89,6 +95,16 @@ run_case no-load expect no-load bad 'probe proves nothing' run_case rebuild expect rebuild bad 'plans a rebuild' +run_case packages +expect packages bad 'install packages' +run_case grub +expect grub bad 'GRUB cmdline' +run_case reboot +expect reboot bad 'reboot WILL be required' +for _m in packages grub reboot; do + run_case "$_m" + [ ! -f "$D/setup-ran" ] && PASS=$((PASS + 1)) || FAIL=$((FAIL + 1)) +done run_case no-restart expect no-restart bad 'did not restart'