Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
18 changes: 11 additions & 7 deletions docs/configuration.md
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -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
Expand Down
10 changes: 6 additions & 4 deletions docs/operations.md
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
23 changes: 8 additions & 15 deletions rigforge.sh
Original file line number Diff line number Diff line change
Expand Up @@ -1143,7 +1143,6 @@ generate_xmrig_config() {
--argjson donation "$DONATION" \
--arg host "$HTTP_HOST" \
'{
autosave: true,
cpu: {
enabled: true,
"huge-pages": $huge_pages,
Expand Down Expand Up @@ -1177,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
Expand Down Expand Up @@ -1268,13 +1267,11 @@ 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
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
Expand Down Expand Up @@ -1812,8 +1809,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
# 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.
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
Expand Down Expand Up @@ -1926,7 +1921,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" ;;
Expand Down Expand Up @@ -1969,7 +1964,7 @@ _persist_appliance_mode() {
fi
}
main() {
local _arg _api_rc=0
local _arg _api_rc=0 config_before="" config_after="" XMRIG_CONFIG_CHANGED=false
for _arg in "$@"; do
case "$_arg" in
--dry-run)
Expand All @@ -1995,7 +1990,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
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
CURRENT_STEP="configuring limits"
Expand Down Expand Up @@ -2058,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
Expand Down
2 changes: 1 addition & 1 deletion tests/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
Loading