Skip to content

feat(appliance): the built-in RigForge miner — 'Both' on the current image (#797 R2) - #812

Merged
VijitSingh97 merged 2 commits into
feat/phase2-bakery-imagefrom
feat/796-rigforge-on-appliance
Aug 1, 2026
Merged

feat(appliance): the built-in RigForge miner — 'Both' on the current image (#797 R2)#812
VijitSingh97 merged 2 commits into
feat/phase2-bakery-imagefrom
feat/796-rigforge-on-appliance

Conversation

@VijitSingh97

Copy link
Copy Markdown
Collaborator

Phase R2 of #797: the appliance honours "Mine on this machine too?" itself. The image bakes a pinned RigForge tree and a prebuilt XMRig, pithead-sync delivers them to /data, render derives the miner's config, and the boot path runs RigForge's appliance mode (rigforge#330, RIGFORGE_APPLIANCE=1) after the stack is up. The wizard's Yes answer becomes true — no more "install RigForge on it afterwards" on a machine with no shell.

Closes #796.

What's baked at image build vs synced at boot

Baked (os/rootfs/Containerfile):

  • RigForge's Debian toolchain + runtime tools: git build-essential cmake libuv1-dev libssl-dev libhwloc-dev gettext-base python3 linux-cpupower msr-tools (trixie ships cpupower as linux-cpupower, not Ubuntu's linux-tools-common — rigforge#327; jq was already baked). R0 proved a post-ship install can never work: apt fails on the RO root, and even a rw-remount install leaves alternatives/ld.so state on the volatile /etc overlay.
  • A pinned RigForge tree at /opt/rigforge, fetched as a GitHub tarball at ARG RIGFORGE_REF and recorded in /opt/rigforge/RIGFORGE_REF. The pin is a commit on rigforge develop (32be3a4, the merge of rigforge#330 — the first ref with appliance mode); no tagged rigforge release carries it yet (latest is v1.12.0, 2026-07-19). Move the default to the next rigforge tag when it cuts.
  • A prebuilt XMRig at RigForge's own XMRIG_VERSION/XMRIG_COMMIT pin (read from the baked rigforge.sh via sed, so they cannot drift), staged under /opt/rigforge/prebuilt/xmrig with the exact .rigforge-commit/.rigforge-sha256 markers rigforge.sh writes. This answers R0 finding 8: RigForge's build step is a github clone over clearnet, unreachable from a Tor-only appliance — so the clone happens on the build host, and the appliance never compiles on first enable. The donate-level source patch is skipped (default 1% == XMRig's own default; the runtime config carries the value regardless). On-box upgrade/recompile against a changed pin would still need clearnet, but a new pin only ever arrives with a new image that carries its new prebuilt.

Synced every boot (pithead-sync): the rigforge program tree is replaced wholesale into /data/rigforge (RigForge's DYNAMIC_HOME roots its workspace under the script dir — R0 finding 6 — so tree placement alone puts the build on writable storage); config.json and the data/ workspace are state and survive by construction. The prebuilt is seeded into data/worker/xmrig only when the commit marker differs — a native recompile of the same pin is left alone, a new pin's prebuilt replaces the stale cache.

Boot order (the ordering decision)

pithead-boot runs the miner leg last, after the health-gated rauc mark-good:

load-images → render → up → health gate → slot commit → ./pithead local-miner || true

The stack serving is the product's health and must gate the A/B commit; the miner is a passenger that needs the stratum listening and must never delay or block the commit (first-enable work, a wedged setup). || true — a miner that cannot start does not fail the boot unit. The same provision_local_miner also runs at the end of setup (first provisioning — the miner starts without waiting for a reboot) and at the end of apply (a dashboard toggle converges immediately: on → setup, off → systemctl stop xmrig).

HugePages ownership (as implemented)

Per the #797 follow-up measurements (sum, not max — both sides hold pages concurrently):

  • RigForge is the single pool sizer. render writes hugepages_reserve_extra_mb: 6144 (pithead's own 3072 × 2 MiB budget) into the miner's config — rigforge#305's designed hand-off. RigForge's grow-only sysctl (rigforge#328) then sizes the whole pool as miner need + declared headroom.
  • pithead's own write became grow-only (optimize_kernel): it reads the live pool and only writes vm.nr_hugepages=3072 when the pool is smaller — it never shrinks the merged budget back. The budget now has ONE definition (PITHEAD_HUGEPAGES), feeding the sysctl, the GRUB params, and the declared headroom.
  • The image's boot-time sysctl.d 3072 is unchanged: it lands on a fresh pool before anything holds pages, and RigForge grows from there.

The render leg

RigForge's config.json is a derived file on the appliance, rebuilt by render/apply/setup like the Caddyfile: pools[0].url = 127.0.0.1:<stratum port>, pass only when a stratum password is set, plus the headroom above. Disabled ⇒ the file is removed (derived means derived; no stale secret on /data). DIY hosts are untouched everywhere — announce_local_miner still hands DIY operators the two values, and gets an appliance variant that promises the built-in worker instead of instructing an install.

Tests

  • tests/stack/run.sh1878 passed, 0 failed locally (macOS). New: render leg (loopback url, pass only-when-set, 6144 headroom, disabled-removes, DIY untouched), boot leg against a stub rigforge.sh (RIGFORGE_APPLIANCE=1, run from the tree, config rendered first, disabled → stop, failure contained), pithead-sync rigforge leg run for real against a sandbox (wholesale program replace, state preserved, seed-on-pin-change, same-pin native rebuild kept), grow-only optimize_kernel, appliance announce text, and the pithead-boot ordering guard (miner leg after mark-good).
  • Frontend: node --test292 passed incl. a new probe pinning "Yes" to the built-in-miner promise (no "install RigForge" instruction).
  • make lint green (shellcheck, shfmt, ruff, biome, markdownlint, docs voice, operator strings).
  • tests/os/verify-image.sh — new checks: rigforge tree + recorded ref, prebuilt xmrig + commit-marker-matches-pin + sha record, compiler chain, envsubst/cpupower/rdmsr.
  • tests/os/run.sh --phase provision — the submit now carries local_miner=true, and the phase gains the Master installer image: one stick provisions a Pithead coordinator, a RigForge rig, or both #797 leg: enable → xmrig unit active + process up → a share logged accepted by xmrig-proxy (i.e. taken by p2pool) → after the unaided reboot, the miner is back (its /run unit died with the boot; only pithead-boot's leg can have re-created it). I could not run the KVM battery from this environment — it needs the KVM build host.
  • The new Containerfile layers were validated statically, not by a full image build from this machine: the tarball URL fetches (HTTP 200), the XMRIG_VERSION/XMRIG_COMMIT sed extraction was proven against the real rigforge.sh at the pinned ref, the marker formats are copied from what rigforge.sh itself writes, and linux-cpupower is the trixie package name rigforge#327 established. An isolated docker build of just these layers was still running (network-bound) when this PR was opened — the real amd64 image build is item 1 of the bench list below and is the authoritative check.

What the bench session must verify (operator's HP box, over SSH)

  1. Build the image with the new layers on the amd64 build host; tests/os/verify-image.sh green (the new built-in-miner section in particular).
  2. KVM battery: tests/os/run.sh --phase provision end to end (miner up, share accepted, miner back after reboot) plus the untouched phases.
  3. On the HP box (the designated Pithead + RigForge machine): flash/A-B-update to this image, enable local_miner — first boot must start the miner from the prebuilt (no compile, no clearnet), rdmsr confirms the MSR preset, and the hugepage pool must equal the merged budget (miner need + 6144 MB) with neither side shrinking it across a stack restart or a pithead apply.
  4. Toggle local_miner.enabled off via the dashboard → miner stops without a reboot; on again → returns.
  5. A reboot cycle: miner returns via pithead-boot's leg after the slot commit; systemctl --failed clean.
  6. rigforge#330's own bench list (runtime hugetlbfs mounts, enable --runtime end-to-end) — first real-hardware exercise of that mode.

Follow-ups (deliberately out of scope)

🤖 Generated with Claude Code

…image

The appliance now honours "Mine on this machine too?" itself instead of
telling a shell-less machine's operator to install RigForge by hand:

- image bake: rigforge's Debian toolchain (trixie: linux-cpupower), a
  pinned rigforge tree (ARG RIGFORGE_REF, recorded in the image) and a
  prebuilt XMRig at rigforge's own pin — a Tor-only box can never run
  rigforge's github clone, so the clone happens at build time;
- pithead-sync: rigforge program tree replaced wholesale on /data,
  config.json + workspace preserved, the prebuilt seeded only when the
  pinned commit changed (a same-pin native rebuild is left alone);
- render: rigforge's config.json becomes a derived file — loopback pool
  url, stratum password when set, and the stack's HugePages budget
  declared as hugepages_reserve_extra_mb so rigforge's grow-only sysctl
  sizes the shared pool as the single writer; pithead's own
  vm.nr_hugepages write is now grow-only for the same reason;
- boot path: pithead-boot runs `pithead local-miner` LAST, after the
  health-gated slot commit — the miner needs the stratum listening and
  must never delay or block the commit; setup and apply run the same
  leg so first provisioning and dashboard toggles converge without a
  reboot;
- wizard: the Yes answer promises the built-in worker, with no
  install-it-yourself instruction anywhere.

Coverage: stack tests for the render/boot/sync legs + grow-only sysctl
(1878 pass), a frontend probe pinning the wizard promise (292 pass),
verify-image checks for everything baked, and the KVM provision phase
gains the enable -> xmrig up -> share accepted -> back-after-reboot leg
(needs the KVM host to run).

Closes #796

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Adversarial review: pithead-boot runs with TimeoutStartSec=infinity, so
'|| true' alone cannot save the unit from a HUNG miner setup — a
stalled native rebuild would leave the boot unit activating forever.
The leg now runs under timeout 1800 (clears a worst-case first native
recompile; the prebuilt path finishes in seconds). Tests pin the
timeout's presence beside the ordering assert, and the provision leg
gains the second-boot re-run case.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant