You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
Each worker already exposes XMRig's HTTP API on :8080 (read-only / restricted, token = rig name), and that's what Pithead's dashboard reads (GET /1/summary) — see pithead-integration.md and the locked-down contract from #7 / #24. The block that configures this is at rigforge.sh:658, with the read-only posture set at rigforge.sh:560.
But that API only reports what XMRig itself knows. RigForge captures a lot of operationally valuable data that never makes it onto the wire:
Tuning state — the winning knobs applied now (tune-overrides.json), the last full tune run (target, best H/s, candidates tried — rigforge-tune.json, summarized by _tune_history at rigforge.sh:1730), and the periodic auto-tune timer's schedule + recent decisions.
Health / firmware — everything doctor probes (rigforge.sh:2615): HugePages reserved (2M + 1G), MSR mod applied + register read-back, CPU governor, RAM channels / speed / rated speed, effective clock vs max boost (throttling), and BIOS/firmware identity + XMP/EXPO + SMT state.
Provenance — the pinned XMRig version/commit RigForge built, and the RigForge version itself.
Today the only way to see any of this is to SSH in and run doctor / tune --history. A dashboard (or pithead) can't surface "this rig is throttling / running single-channel RAM / hasn't been tuned / is 8% below its last tuned best" because that data isn't exposed anywhere.
XMRig's native API can't be extended to carry it either — RigForge deliberately compiles stock upstream XMRig (see the README "not a custom miner" note), and the API is intentionally restricted (read-only). So enriching it means a RigForge-owned sidecar, not a fork.
Yes — the main one. Mirror its shape, add a rigforge block.
GET /2/backends (/1/threads)
per-thread / per-backend hashrate
Maybe — pass through, annotate with the tuned rx thread layout. Lower value.
GET /1/config
full effective config
No — blocked in restricted by design; at most a read-only "what RigForge tuned vs XMRig auto" view, which really belongs in summary.
POST /1/config, POST /json_rpc (pause/resume/stop)
control
No — explicitly out of scope. Preserves the read-only posture from #7.
So: the data endpoints (summary, optionally backends) get sister versions; the control endpoints stay off, on purpose — which matches the "the data related ones mostly" instinct.
How XMRig serves it (and the low-footprint takeaway)
Worth copying XMRig's restraint here. Its HTTP API is embedded in the miner process — it rides the libuv event loop the miner already runs, parses requests with a bundled llhttp (src/3rdparty/llhttp), and pulls in OpenSSL only when TLS is enabled. There's no libmicrohttpd, no HTTP framework, and no separate process or thread (confirmed in src/base/api/Httpd.cpp / src/base/net/http/HttpServer.cpp at the pinned v6.26.0). restricted mode is just route-gating inside onHttpData. Net marginal footprint: a few KB of code on a loop that's already spinning — effectively zero extra runtime.
RigForge can't embed into the stock XMRig binary (the whole point — we ship upstream XMRig unmodified). But the property to copy isn't "use libuv," it's "no extra always-on component." systemd is already PID 1 on every worker, so the analog is socket activation: let systemd hold the listening port and spawn a short-lived handler only when a client connects. At idle that's zero RigForge processes — just a socket FD held by the init system that's running anyway — versus a persistent Python/socat daemon sitting resident 24/7 to answer an occasional dashboard poll. This also keeps us to tools RigForge already depends on (systemd + curl + jq); no new language runtime or package.
Proposal
A read-only, socket-activated handler (call it rigforge-api) that, per request, reads XMRig's local /2/summary (+ optionally /2/backends), merges in RigForge state (the on-disk tune files, cached doctor probes, a power sample) and serves the union:
Superset shape: mirror XMRig's summary fields verbatim so any existing consumer works unchanged, and add a namespaced rigforge object for the extras:
New endpoints with no XMRig equivalent are natural here — e.g. GET /health (the doctor result as JSON) and GET /tune (the tune --history JSON) — so doctor / tune --history get a machine-readable sibling, not just human text.
Wire it into setup + service management the systemd-native way: a rigforge-api.socket (ListenStream=0.0.0.0:8081, Accept=yes) plus a templated rigforge-api@.service (Type=oneshot, StandardInput=socket) that execs a new internal rigforge.sh api-serve handler — reading the request from stdin, writing the HTTP response to stdout. This reuses the existing $VAR-substituted unit-template pattern in systemd/ (see systemd/xmrig.service.template, systemd/rigforge-autotune.service.template) and the same oneshot shape autotune already uses. Opt-in via a config key. Reuse doctor's probe functions and _tune_history's readers rather than duplicating them.
Feasibility / trade-offs to settle
No persistent daemon — socket-activated, per the footprint note above. RigForge is a bash script with no long-running HTTP server, and that's fine: the handler is a short-lived rigforge.sh api-serve invocation that curls XMRig's local /2/summary and merges the on-disk state. Decide whether the slower health/power probes are sampled per-request or cached on a short interval (some doctor checks shell out to dmidecode / rdmsr and want root — likely cache them to a file the handler just reads, so per-connection work stays trivial). A per-connection fork on each dashboard poll is cheaper than a resident interpreter, and matches XMRig's "nothing extra always running" property.
macOS has no systemd (so no socket activation) and runs the worker under screen (light-use/dev), and doctor's health checks are already Linux-only (rigforge.sh:2617). Simplest is to make the API Linux-only too, mirroring doctor — decide and document. (launchd has equivalent socket activation if it's ever wanted, but light-use macOS probably doesn't need it.)
Relationship to Pithead: this is the RigForge-side richer feed a dashboard would consume; the cross-side coordination (custom port/token discovery) is the same Pithead-side work tracked in Pithead #171 / #172. Decide whether Pithead reads the new port, or :8080 stays the canonical summary and :8081 is the "extras."
Acceptance criteria
A read-only sidecar serves a /summary that is a strict superset of XMRig's /2/summary (XMRig fields passed through unchanged + a namespaced rigforge block).
The rigforge block carries tuning state, power/efficiency, and the doctor health/firmware data — sourced by reusing the existing readers, not re-implemented.
No resident daemon: served via systemd socket activation (rigforge-api.socket + rigforge-api@.service, Accept=yes), so idle footprint is zero RigForge processes — only curl/jq/systemd, which are already dependencies. Opt-in config key; Linux-only (parity with doctor), documented on macOS.
Docs (docs/pithead-integration.md, docs/operations.md) and tests cover the merge/shape and the read-only/token enforcement.
Problem
Each worker already exposes XMRig's HTTP API on
:8080(read-only /restricted, token = rig name), and that's what Pithead's dashboard reads (GET /1/summary) — see pithead-integration.md and the locked-down contract from #7 / #24. The block that configures this is atrigforge.sh:658, with the read-only posture set atrigforge.sh:560.But that API only reports what XMRig itself knows. RigForge captures a lot of operationally valuable data that never makes it onto the wire:
tune-overrides.json), the last fulltunerun (target, best H/s, candidates tried —rigforge-tune.json, summarized by_tune_historyatrigforge.sh:1730), and the periodic auto-tune timer's schedule + recent decisions.TUNE_POWER_CMD, tune: selectable optimization target — raw hashrate vs. hashrate-per-watt (efficiency) #79).doctorprobes (rigforge.sh:2615): HugePages reserved (2M + 1G), MSR mod applied + register read-back, CPU governor, RAM channels / speed / rated speed, effective clock vs max boost (throttling), and BIOS/firmware identity + XMP/EXPO + SMT state.Today the only way to see any of this is to SSH in and run
doctor/tune --history. A dashboard (orpithead) can't surface "this rig is throttling / running single-channel RAM / hasn't been tuned / is 8% below its last tuned best" because that data isn't exposed anywhere.XMRig's native API can't be extended to carry it either — RigForge deliberately compiles stock upstream XMRig (see the README "not a custom miner" note), and the API is intentionally
restricted(read-only). So enriching it means a RigForge-owned sidecar, not a fork.What XMRig exposes natively (and which to mirror)
In
restrictedmode XMRig serves read-only GETs:GET /1/summary,GET /2/summaryrigforgeblock.GET /2/backends(/1/threads)rxthread layout. Lower value.GET /1/configrestrictedby design; at most a read-only "what RigForge tuned vs XMRig auto" view, which really belongs insummary.POST /1/config,POST /json_rpc(pause/resume/stop)So: the data endpoints (
summary, optionallybackends) get sister versions; the control endpoints stay off, on purpose — which matches the "the data related ones mostly" instinct.How XMRig serves it (and the low-footprint takeaway)
Worth copying XMRig's restraint here. Its HTTP API is embedded in the miner process — it rides the libuv event loop the miner already runs, parses requests with a bundled llhttp (
src/3rdparty/llhttp), and pulls in OpenSSL only when TLS is enabled. There's nolibmicrohttpd, no HTTP framework, and no separate process or thread (confirmed insrc/base/api/Httpd.cpp/src/base/net/http/HttpServer.cppat the pinnedv6.26.0).restrictedmode is just route-gating insideonHttpData. Net marginal footprint: a few KB of code on a loop that's already spinning — effectively zero extra runtime.RigForge can't embed into the stock XMRig binary (the whole point — we ship upstream XMRig unmodified). But the property to copy isn't "use libuv," it's "no extra always-on component." systemd is already PID 1 on every worker, so the analog is socket activation: let systemd hold the listening port and spawn a short-lived handler only when a client connects. At idle that's zero RigForge processes — just a socket FD held by the init system that's running anyway — versus a persistent Python/
socatdaemon sitting resident 24/7 to answer an occasional dashboard poll. This also keeps us to tools RigForge already depends on (systemd +curl+jq); no new language runtime or package.Proposal
A read-only, socket-activated handler (call it
rigforge-api) that, per request, reads XMRig's local/2/summary(+ optionally/2/backends), merges in RigForge state (the on-disk tune files, cacheddoctorprobes, a power sample) and serves the union:summaryfields verbatim so any existing consumer works unchanged, and add a namespacedrigforgeobject for the extras:{ // ...all of XMRig's /2/summary, passed through unchanged... "rigforge": { "version": "…", "xmrig_commit": "…", "tune": { "applied": { /* winning knobs */ }, "target": "perf", "last_best_hs": 12345, "autotune": { "enabled": true, "next": "…", "recent": [/* decisions */] } }, "power": { "watts": 142.0, "hs_per_watt": 86.9 }, "health": { "hugepages_pct": 100, "msr": "applied", "governor": "performance", "ram": { "channels": 2, "mts": 6000, "rated_mts": 6000 }, "clock_pct_of_boost": 96, "throttling": false, "firmware": { "board": "…", "bios": "…", "xmp": true, "smt": "on" } } } }:8080(Lock down the XMRig HTTP API on Linux (currently 0.0.0.0 + unrestricted + guessable token) #7 / Honor & document the Pithead worker-API contract (port 8080 · token = rig name · restricted=true) #24): read-only,Bearer <rig name>token (the same token rule — never randomized — per the Pithead contract), LAN-bound. Run it on a separate port (e.g.:8081) so it never collides with the:8080Pithead already reads.GET /health(thedoctorresult as JSON) andGET /tune(thetune --historyJSON) — sodoctor/tune --historyget a machine-readable sibling, not just human text.rigforge-api.socket(ListenStream=0.0.0.0:8081,Accept=yes) plus a templatedrigforge-api@.service(Type=oneshot,StandardInput=socket) that execs a new internalrigforge.sh api-servehandler — reading the request from stdin, writing the HTTP response to stdout. This reuses the existing$VAR-substituted unit-template pattern insystemd/(seesystemd/xmrig.service.template,systemd/rigforge-autotune.service.template) and the sameoneshotshape autotune already uses. Opt-in via a config key. Reusedoctor's probe functions and_tune_history's readers rather than duplicating them.Feasibility / trade-offs to settle
rigforge.sh api-serveinvocation thatcurls XMRig's local/2/summaryand merges the on-disk state. Decide whether the slower health/power probes are sampled per-request or cached on a short interval (somedoctorchecks shell out todmidecode/rdmsrand want root — likely cache them to a file the handler just reads, so per-connection work stays trivial). A per-connection fork on each dashboard poll is cheaper than a resident interpreter, and matches XMRig's "nothing extra always running" property.screen(light-use/dev), anddoctor's health checks are already Linux-only (rigforge.sh:2617). Simplest is to make the API Linux-only too, mirroringdoctor— decide and document. (launchdhas equivalent socket activation if it's ever wanted, but light-use macOS probably doesn't need it.):8080stays the canonical summary and:8081is the "extras."Acceptance criteria
/summarythat is a strict superset of XMRig's/2/summary(XMRig fields passed through unchanged + a namespacedrigforgeblock).rigforgeblock carries tuning state, power/efficiency, and thedoctorhealth/firmware data — sourced by reusing the existing readers, not re-implemented.:8080: read-only, rig-nameBearertoken (the Honor & document the Pithead worker-API contract (port 8080 · token = rig name · restricted=true) #24 rule), LAN-bound, on a separate port; no control/config endpoints.rigforge-api.socket+rigforge-api@.service,Accept=yes), so idle footprint is zero RigForge processes — onlycurl/jq/systemd, which are already dependencies. Opt-in config key; Linux-only (parity withdoctor), documented on macOS.docs/pithead-integration.md,docs/operations.md) and tests cover the merge/shape and the read-only/token enforcement.Context
doctor(rigforge.sh:2615),_tune_history(rigforge.sh:1730), the tune state files (tune-overrides.json,rigforge-tune.json), power/efficiency (tune: selectable optimization target — raw hashrate vs. hashrate-per-watt (efficiency) #79)./2/summaryitself for the tune loop (rigforge.sh:2131), so the plumbing to talk to XMRig's API locally exists.