Skip to content

Sister HTTP API: serve a superset of XMRig's stats, enriched with RigForge-captured data #99

@VijitSingh97

Description

@VijitSingh97

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 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.
  • Efficiency / power — hashrate-per-watt and the live watt draw RigForge can already sample (RAPL / TUNE_POWER_CMD, tune: selectable optimization target — raw hashrate vs. hashrate-per-watt (efficiency) #79).
  • 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.

What XMRig exposes natively (and which to mirror)

In restricted mode XMRig serves read-only GETs:

Endpoint Content Sister API?
GET /1/summary, GET /2/summary hashrate (total/highest), shares/results, connection (pool, ping, diff), uptime, CPU info, hugepages, memory 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:
    {
      // ...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" } }
      }
    }
  • Same security posture as :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 :8080 Pithead already reads.
  • 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.
  • Same security posture as :8080: read-only, rig-name Bearer token (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.
  • 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.

Context

Metadata

Metadata

Assignees

No one assigned

    Labels

    dashboardMining dashboard web UIenhancementNew feature or requestsecuritySecurity-sensitive issue or hardeningsetuprigforge.sh, config.json, first-run setup

    Type

    No type
    No fields configured for issues without a type.

    Projects

    No projects

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions