Skip to content
Open
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
5 changes: 5 additions & 0 deletions .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -168,6 +168,11 @@ jobs:
# /run/systemd/system, no package installs, no fstab/limits/logrotate writes, --runtime enables.
- name: Run end-to-end suite
run: make test-e2e
- name: Run API allowlist kernel test
run: >-
docker run --rm --privileged -v "$PWD:/src:ro"
alpine@sha256:14358309a308569c32bdc37e2e0e9694be33a9d99e68afb0f5ff33cc1f695dce
sh -c 'apk add --no-cache bash iproute2 nftables curl jq python3 >/dev/null && bash /src/tests/test-api-allowlist-linux.sh'

coverage:
name: Coverage (kcov + diff-cover)
Expand Down
8 changes: 7 additions & 1 deletion Makefile
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
# Local test entry points (mirror the GitHub Actions CI jobs).
.PHONY: help test test-suite test-e2e test-e2e-macos smoke coverage e2e-real e2e-pithead lint lint-topology lint-file-budget fmt lint-yaml lint-md lint-links lint-all lint-actions dev-setup ci
.PHONY: help test test-suite test-e2e test-e2e-macos smoke coverage e2e-real e2e-pithead e2e-docs-replay lint lint-topology lint-file-budget fmt lint-yaml lint-md lint-links lint-all lint-actions dev-setup ci

SHELL_FILES = $(shell git ls-files '*.sh')

Expand All @@ -21,11 +21,17 @@ test-suite: ## rigforge test suite: unit + black-box, every CPU/OS profile simul
bash tests/test-docs-clone-pin.sh
bash tests/test-staged-rebuild.sh
bash tests/test-tool-pin-watch.sh
PYTHONDONTWRITEBYTECODE=1 python3 tests/test-docs-replay.py
bash tests/test-api-allowlist-probe.sh
bash tests/test-e2e-docs-guest.sh
bash tests/test-revision-key.sh

test-e2e: ## Full end-to-end run in disposable Linux containers (needs Docker)
bash tests/e2e/linux.sh

e2e-docs-replay: ## Replay documented setup and two reboots in an explicitly supplied disposable KVM guest
python3 tests/docs_replay.py

test-e2e-macos: ## Native macOS e2e: real rigforge.sh (brew/git/cmake stubbed) — BSD sed, launchd, nohup (macOS only, deprecated, not run in CI)
bash tests/e2e/macos.sh

Expand Down
26 changes: 15 additions & 11 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -71,32 +71,35 @@ you like to your stack's single endpoint.

## 📊 Does it actually help?

Measured on a Ryzen **7800X3D**, mining live to a real pool (not a synthetic `--bench`):
RigForge automates upstream XMRig tuning. The benefit depends on the hardware and the starting
OS configuration.

| | Stock XMRig | RigForge | |
|---|--:|--:|:--|
| **Hashrate** | 10,416 H/s | **10,779 H/s** | **+3.5%** |
| **Power** | 86.8 W | **83.5 W** | **−3.8%** |
| **Efficiency** | 120.1 H/s/W | **129.2 H/s/W** | **+7.6%** |
The [historical benchmark report](docs/benchmarks.md) records live-mining observations on a Ryzen
7800X3D and an EPYC 7642. These are illustrative results, not measurements of the RigForge baked
into Pithead 2.0.0. Reported power and efficiency use **CPU-package RAPL**, not wall power; they
do not establish whole-system efficiency or temperature gains.

Without HugePages the CPU stalls on memory, so stock XMRig draws more power for less work. RigForge is
faster and runs cooler. On a 48-core EPYC the gap is +6.6%, and RigForge matched an expert's
hand-tuned config while avoiding a prefetch setting that halves RandomX on that chip. Full method,
both CPUs, and the caveats: [Benchmarks](docs/benchmarks.md).
No percentage uplift is established here for the current candidate. Numeric launch claims require
the [candidate comparison protocol](docs/benchmarks.md#candidate-comparison-protocol) and a dated
report for the exact baked revision.

---

## 🚀 Quick Start

On the machine you want to turn into a miner:

<!-- docs-replay:quick-start:begin -->

```bash
git clone --branch v1.18.0 https://github.com/p2pool-starter-stack/rigforge.git
cd rigforge
chmod +x rigforge.sh
sudo ./rigforge.sh
```

<!-- docs-replay:quick-start:end -->

The script needs root to install packages and tune the system. First-run setup asks for your pool URL
and optional password. For an `.onion` pool it offers a validated SOCKS proxy override, defaulting to
`127.0.0.1:9050`; install and run Tor yourself. It writes a minimal `config.json`. On Linux, reboot
Expand Down Expand Up @@ -129,8 +132,9 @@ doesn't prove — deploying from a git checkout at the release tag is the strong
|---|---|
| **[Getting Started](docs/getting-started.md)** | Prerequisites, install, first-run setup, the Linux reboot, and verification. |
| **[Hardware Requirements](docs/hardware.md)** | Worker CPU / RAM / HugePages requirements and the per-CPU tuning profiles. |
| **[Benchmarks](docs/benchmarks.md)** | Measured stock-vs-tuned hashrate and efficiency on real hardware, with the method and caveats. |
| **[Benchmarks](docs/benchmarks.md)** | Historical stock-vs-tuned observations, CPU-package (RAPL) power only, not wall power, and the candidate comparison protocol. |
| **[Configuration](docs/configuration.md)** | Every `config.json` key and default, and how the XMRig config is generated. |
| **[Shared-host profile](docs/shared-host.md)** | Opt-in CPU and memory bounds for co-resident workloads. |
| **[Operations & Maintenance](docs/operations.md)** | The full command reference, service management, logs, upgrades, and troubleshooting. |
| **[How It Works](docs/how-it-works.md)** | What the script actually does: compile, HugePages, MSR, NUMA, governor, service. |
| **[Pithead Integration](docs/pithead-integration.md)** | The worker ↔ dashboard contract: discovery via `:3333`, the read-only API on `:8080` (plus the optional enriched sister API on `:8081`), and the token rules. |
Expand Down
19 changes: 17 additions & 2 deletions config.reference.json
Original file line number Diff line number Diff line change
@@ -1,8 +1,22 @@
{
"_docs": "Reference for every config.json key, each shown with its default value. Copy ONLY the keys you actually want to change into config.json; any key you omit keeps the default shown here. The pool target is the native XMRig 'pools' array: each pool needs a 'url' (host:port); every other field falls back to a Pithead-friendly default. A pool's 'user' is the rig's label on the dashboard (defaults to the machine hostname); list multiple entries for failover. A pool's 'socks5' (default null) dials that pool through a SOCKS5 proxy given as host:port \u2014 e.g. \"127.0.0.1:9050\" for a local Tor client, which is how a rig reaches a stratum published as an onion service; XMRig sends the hostname to the proxy, so the proxy resolves it. RigForge points the miner at a proxy, it does not install or run one. An empty ACCESS_TOKEN (the default) leaves the rig's read-only HTTP API open (no token) — which matches Pithead's default no-auth stats probe; set a value only to require a Bearer token (then match it dashboard-side). 'ACCESS_TOKENS' (default {}) is an optional name -> token map, one token per Pithead stack that consumes this rig, so rotating the bench's token never touches production's: every entry is accepted on the sister API (:8081, raw or its own HMAC-derived read bearer) and the writable control path (:8082, raw), while XMRig's own :8080 and the control path's pools restore stay ACCESS_TOKEN's alone — a stack that probes :8080 directly must hold the master. 'autotune' is one of \"disabled\" (default — no scheduled tuning), \"performance\" (monthly tune for raw hashrate), or \"efficiency\" (monthly tune for hashrate-per-watt). 'watchdog' set to \"enabled\" installs a timer that health-checks the miner every 'watchdog_interval_min' minutes (default 5) and restarts it after two consecutive checks see 0 H/s or a dead API; 'max_temp_c' (empty = off) additionally stops the miner above that °C and starts it again 5°C below — check what your board's thermal_zone0 reports before setting it. 'miner_user' (empty = run as root, the default) runs the miner as that dedicated non-root system user — RigForge applies the MSR preset root-side; on CPU families without a known preset the ~10-15% MSR boost is skipped. 'api_allow_from' (empty = off) scopes the API port(s) — including the writable control port when 'control' is enabled — to one IPv4 or IPv6 address/CIDR source + loopback via an own nftables table (Linux, needs nft; for an IPv6 source the served port must bind a v6 address, so set 'api_bind'/'control_bind' to \"::\"). Set 'api' to enabled for the sister API: a second read-only port (default 8081) serving XMRig's summary enriched with RigForge tune/power/health/provenance data, gated by the same ACCESS_TOKEN; 'api_port'/'api_bind' place it. Set 'control' to \"enabled\" for the WRITABLE control path (#236): a SEPARATE authenticated port (default 8082, 'control_port'/'control_bind' place it) that lets a Pithead stack apply config changes through RigForge so config.json stays authoritative. Fail-closed: enabling it REQUIRES both ACCESS_TOKEN and api_allow_from (a writable API with no token or no pinned source is refused). Only pools, DONATION, autotune, watchdog(+interval), and max_temp_c are writable through it (and the REMOTE path refuses to disable watchdog or to unset/out-of-band max_temp_c — a rig's thermal protection can only be removed by a local rigforge.sh apply, #257); every change is validated, the old config is snapshotted to config-backups/, and a change that doesn't come back live is rolled back. Set 'control_upgrade' to \"enabled\" (a SECOND opt-in, only valid when 'control' is also enabled) to let the stack trigger this rig to upgrade its own RigForge to the latest release (#308, ADR 0002) — i.e. fetch and run new root code on a remote trigger. Default off, and never on just because 'control' is: the rig refuses any target that isn't a real release newer than the one running, verifies the release commit is reachable on main (the release branch), throttles repeat runs, and health-gates the swap with rollback. It rides the control path's own Bearer token + api_allow_from pin. Releases are checksummed, not signed — GitHub is the trust root (see SECURITY.md). For co-locating a miner on a busy box (e.g. a Pithead stack host), 'hugepages_reserve_extra_mb' (default 0) adds that many MB of HugePages to RigForge's computed mining reservation so the pool covers stack + miner — RigForge stays the sole writer of the reservation and, when the box's existing reservation already covers both, changes nothing and needs no reboot; 'hugepages_pool_ceiling_mb' (default 0 = no ceiling) is a HARD CAP on the runtime pool RigForge will grow to, in MB, distinct from hugepages_reserve_extra_mb (which adds declared headroom into the computed requirement rather than bounding the write) — set it on a RAM-constrained co-resident box so the grow-only write can never exceed the box's honest capacity no matter what the requirement/availability math computes (rigforge#398; an odd MB value floors to the 2MB page below, e.g. 5121 -> 5120MB effective, never rounds up past the declared ceiling). It bounds the PERSISTED reservation too: the 2MB `hugepages=` RigForge merges into the GRUB cmdline is capped the same way, so the pool does not come back up over the ceiling after a reboot, where the grow-only runtime write could never bring it down again (rigforge#410). The 1GB dataset reservation is a separate pool this key has never described and is not capped, so a ceiling below a box's 1GB reservation still leaves it holding more hugepage memory than the ceiling names; 'threads' (empty/null = auto) caps the RandomX thread count (a ceiling — min(auto, threads), e.g. nproc-2) so the miner leaves spare cores, and it sizes the reservation to match. A TLS pool can pin the server certificate with 'tls-fingerprint' (its SHA-256 as 64 hex chars; null = no pin) — XMRig does no CA verification for stratum, so the pin is the only server authentication TLS stratum has. Full descriptions: https://github.com/p2pool-starter-stack/rigforge/blob/main/docs/configuration.md#configuration-reference",
"_docs": "Reference for every config.json key, each shown with its default value. Copy ONLY the keys you actually want to change into config.json; any key you omit keeps the default shown here. The pool target is the native XMRig 'pools' array: each pool needs a 'url' (host:port); every other field falls back to a Pithead-friendly default. A pool's 'user' is the rig's label on the dashboard (defaults to the machine hostname); list multiple entries for failover. Optional pool fields: 'rig-id' is a separate worker label while 'user' remains the login; 'nicehash' enables the pool-assigned nonce protocol; 'algo' pins a supported rx/* algorithm; 'coin' selects a supported RandomX coin by case-insensitive name or code; and 'sni' sends the pool hostname during TLS setup. These five fields are omitted when absent or null; explicit false is preserved for the strict-boolean 'nicehash' and 'sni' fields. 'pass' is the Stratum password or worker name; 'keepalive' sends requests that prevent idle timeout; 'tls' enables encrypted Stratum; and 'enabled' controls whether XMRig may connect to the entry during failover. A pool's 'socks5' (default null) dials that pool through a SOCKS5 proxy given as host:port \u2014 e.g. \"127.0.0.1:9050\" for a local Tor client, which is how a rig reaches a stratum published as an onion service; XMRig sends the hostname to the proxy, so the proxy resolves it. RigForge points the miner at a proxy, it does not install or run one. An empty ACCESS_TOKEN (the default) leaves the rig's read-only HTTP API open (no token) — which matches Pithead's default no-auth stats probe; set a value only to require a Bearer token (then match it dashboard-side). 'ACCESS_TOKENS' (default {}) is an optional name -> token map, one token per Pithead stack that consumes this rig, so rotating the bench's token never touches production's: every entry is accepted on the sister API (:8081, raw or its own HMAC-derived read bearer) and the writable control path (:8082, raw), while XMRig's own :8080 and the control path's pools restore stay ACCESS_TOKEN's alone — a stack that probes :8080 directly must hold the master. 'autotune' is one of \"disabled\" (default — no scheduled tuning), \"performance\" (monthly tune for raw hashrate), or \"efficiency\" (monthly tune for hashrate-per-watt). 'watchdog' set to \"enabled\" installs a timer that health-checks the miner every 'watchdog_interval_min' minutes (default 5) and restarts it after two consecutive checks see 0 H/s or a dead API; 'max_temp_c' (empty = off) additionally stops the miner above that °C and starts it again 5°C below — check what your board's thermal_zone0 reports before setting it. 'miner_user' (empty = run as root, the default) runs the miner as that dedicated non-root system user — RigForge applies the MSR preset root-side; on CPU families without a known preset the ~10-15% MSR boost is skipped. 'api_allow_from' (empty = off) scopes the API port(s) — including the writable control port when 'control' is enabled — to one IPv4 or IPv6 address/CIDR source + loopback via an own nftables table (Linux, needs nft; for an IPv6 source the served port must bind a v6 address, so set 'api_bind'/'control_bind' to \"::\"). Set 'api' to enabled for the sister API: a second read-only port (default 8081) serving XMRig's summary enriched with RigForge tune/power/health/provenance data, gated by the same ACCESS_TOKEN; 'api_port'/'api_bind' place it. Set 'control' to \"enabled\" for the WRITABLE control path (#236): a SEPARATE authenticated port (default 8082, 'control_port'/'control_bind' place it) that lets a Pithead stack apply config changes through RigForge so config.json stays authoritative. Fail-closed: enabling it REQUIRES both ACCESS_TOKEN and api_allow_from (a writable API with no token or no pinned source is refused). Only pools, DONATION, autotune, watchdog(+interval), and max_temp_c are writable through it (and the REMOTE path refuses to disable watchdog or to unset/out-of-band max_temp_c — a rig's thermal protection can only be removed by a local rigforge.sh apply, #257); every change is validated, the old config is snapshotted to config-backups/, and a change that doesn't come back live is rolled back. Set 'control_upgrade' to \"enabled\" (a SECOND opt-in, only valid when 'control' is also enabled) to let the stack trigger this rig to upgrade its own RigForge to the latest release (#308, ADR 0002) — i.e. fetch and run new root code on a remote trigger. Default off, and never on just because 'control' is: the rig refuses any target that isn't a real release newer than the one running, verifies the release commit is reachable on main (the release branch), throttles repeat runs, and health-gates the swap with rollback. It rides the control path's own Bearer token + api_allow_from pin. Releases are checksummed, not signed — GitHub is the trust root (see SECURITY.md). For co-locating a miner on a busy box (e.g. a Pithead stack host), 'hugepages_reserve_extra_mb' (default 0) adds that many MB of HugePages to RigForge's computed mining reservation so the pool covers stack + miner — RigForge stays the sole writer of the reservation and, when the box's existing reservation already covers both, changes nothing and needs no reboot; 'hugepages_pool_ceiling_mb' (default 0 = no ceiling) is a HARD CAP on the runtime pool RigForge will grow to, in MB, distinct from hugepages_reserve_extra_mb (which adds declared headroom into the computed requirement rather than bounding the write) — set it on a RAM-constrained co-resident box so the grow-only write can never exceed the box's honest capacity no matter what the requirement/availability math computes (rigforge#398; an odd MB value floors to the 2MB page below, e.g. 5121 -> 5120MB effective, never rounds up past the declared ceiling). It bounds the PERSISTED reservation too: the 2MB `hugepages=` RigForge merges into the GRUB cmdline is capped the same way, so the pool does not come back up over the ceiling after a reboot, where the grow-only runtime write could never bring it down again (rigforge#410). The 1GB dataset reservation is a separate pool this key has never described and is not capped, so a ceiling below a box's 1GB reservation still leaves it holding more hugepage memory than the ceiling names; 'threads' (empty/null = auto) caps the RandomX thread count (a ceiling — min(auto, threads), e.g. nproc-2) so the miner leaves spare cores, and it sizes the reservation to match. A TLS pool can pin the server certificate with 'tls-fingerprint' (its SHA-256 as 64 hex chars; null = no pin) — XMRig does no CA verification for stratum, so the pin is the only server authentication TLS stratum has. The pool 'coin' lets XMRig choose the algorithm for that coin. Both 'algo' and 'coin' defer to pool algorithm negotiation; avoid 'coin' with NiceHash, where the pool controls the coin. Full descriptions: https://github.com/p2pool-starter-stack/rigforge/blob/main/docs/configuration.md#configuration-reference",

"pools": [
{ "url": "<YOUR_POOL_HOST>:3333", "user": "", "pass": "x", "keepalive": true, "tls": false, "tls-fingerprint": null, "socks5": null, "enabled": true }
{
"url": "<YOUR_POOL_HOST>:3333",
"user": "",
"pass": "x",
"rig-id": null,
"nicehash": false,
"algo": null,
"coin": null,
"keepalive": true,
"tls": false,
"tls-fingerprint": null,
"sni": false,
"socks5": null,
"enabled": true
}
],

"ACCESS_TOKEN": "",
Expand All @@ -29,6 +43,7 @@
"hugepages_reserve_extra_mb": 0,
"hugepages_pool_ceiling_mb": 0,
"threads": null,
"shared_host": false,

"HOME_DIR": "DYNAMIC_HOME"
}
Loading