feat(os): the rig boot leg — a role=rig box mines instead of coordinating (#797 R4) - #876
Merged
Merged
Conversation
…ting (#797 R4) R3 shipped the role select and left the pure-rig BOOT as an honest console stub. This makes it real: a machine carrying `machine-role: rig` starts RigForge from rig.json instead of the compose stack, and commits its A/B slot the same way a coordinator does. The fork lives in pithead-boot, before the container-image loader — a rig loads none. Its branch is two steps: `pithead local-miner`, which now reads the marker and takes the rig leg, and the same health-gated `mark-good`. The commit gate is deliberately pool-independent: a rig whose coordinator is late still starts, still retries, and must still commit, or a slow LAN would flap the A/B pair. The rig leg REUSES R2's path rather than growing a second one. provision_local_miner forks at the top, and both roles share one rigforge_setup_run — one invocation contract, one appliance flag, one first-run narration. Only the config source differs: rig.json (pool, worker name as RigForge's pools[].user, stratum password when set, and no HugePages headroom, because there is no stack here to leave room for). Prebuilt-first needed no new code, only proof: pithead-sync already seeds the image's baked XMRig into the workspace for both roles, so a rig's first boot re-renders instead of compiling and never reaches the network. Removable-root tolerance, for the run-from-USB rig whose root IS the stick it mines from: journald goes volatile (the image ships it persistent with a 200 MB cap, whose files land on that same medium), converged every boot because /etc and /run are both volatile here and no drop-in survives a reboot. Swap needed no code — the appliance declares no swap partition in any role. Unit conditions carry the other half of the fork, using systemd's `|` triggering prefix: pithead-boot admits either config.json or machine-role, pithead-firstboot is closed by either. The marker and not rig.json, deliberately — a fleet stick holds a rig's answers in flight while installing that rig onto a disk, and must stay an installer through it. repart: /data needs no rig-sized rule and could not have one. It is already sized to the MEDIUM rather than the role (a 16 GB stick leaves a rig ~6 GiB after the ESP and both slots), and systemd-repart runs at first boot, before the wizard has asked what the machine is. 40-data.conf carries the reasoning. Coverage: verify-image asserts both role paths in the shipped artifact; the stack suite covers the derived config, prebuilt-first, the volatile journal, the refusals and both unit conditions; the KVM battery gains a `rig` phase proving mining from the byte-identical baked binary, no containers at all, and an A/B install → uncommitted rollback → self-commit → persistence. The battery is not runnable from here — assertions added, not executed. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Phase R4 of #797 — the rig boot leg. R3 (#847) shipped the role select and left the pure-rig
BOOT as an honest console stub; this makes it real. Last code phase before the master-image epic
(#77) can close.
The boot leg
pithead-bootforks on the role marker before the container-image loader — a rig loads none.Its branch is two steps:
pithead local-miner, which now reads the marker and takes the rig leg:rig.json→RigForge's
config.json→ the same appliance-mode setup the Both role has run since R2.mark-good, on the miner running.The commit gate is deliberately pool-independent. A rig whose coordinator is late still
starts, still retries, and must still commit — rolling a slot back cannot fix a switch nobody
plugged in, and a gate that punished it would flap the A/B pair every boot the LAN was slow.
Reuse, not a second path.
provision_local_minerforks at the top and both roles share onerigforge_setup_run— one invocation contract, one appliance flag, one first-run narration. Onlythe config source differs:
rig.jsongives pool, worker name (RigForge'spools[].user) and thestratum password when one was set, with no
hugepages_reserve_extra_mb, because there is nostack on this machine to leave headroom for.
The R3 stub is gone from all three places that carried it:
pithead, the wizard server's/status, and the frontend's done view. A rig has no dashboard, so its last page now names wherethe operator will actually see it — the coordinator's Workers view.
Prebuilt-first
No new code, only proof.
pithead-syncalready seeds the image's baked XMRig into the workspacefor both roles, so a rig's first boot re-renders instead of compiling and never reaches the
network. A native rebuild stays the operator's option, cached on
/data, never a requirement.The KVM leg asserts it the only way that cannot be faked: the running binary is
cmp-identical to/opt/rigforge/prebuilt/xmrig/build/xmrig. A recompile would differ; a clone could not happen atall on a Tor-only box.
Removable root
For the run-from-USB rig, whose root IS the stick it mines from. What the boot path assumed about
a fixed disk was journald: the image ships
Storage=persistentwith a 200 MB cap, and thosefiles land on the
/varoverlay whose upper lives on that same medium. The rig leg flips it tovolatile — logs in memory, no rotating writes — converged every boot, because
/etcand/runare both volatile here and no drop-in survives a reboot.
Swap needed no code: the appliance declares no swap partition and creates none, in any role.
Unit conditions — the other half of the fork
pithead-boot.servicenever fired on a rig (ConditionPathExists=/data/pithead/config.json), andpithead-firstboot.servicefired on every rig boot. Both now use systemd's|triggeringprefix: boot is admitted by either
config.jsonormachine-role, firstboot is closed byeither.
The marker and not
rig.json, deliberately: a fleet stick holds a rig's answers in flightwhile installing that rig onto a disk, and must stay an installer through it. Only an accepted
role writes the marker, and only onto the machine that IS that role. A stack assertion pins this
so it cannot regress.
repart — the honest answer
/dataneeds no rig-sized rule, and could not have one.SizeMaxBytes,Weight=1000, so a 16 GB stick leaves a rig ~6 GiB after the ESP and both 4 GiB slots — plentywith no chains — while a 1 TB disk leaves a coordinator its chains.
systemd-repartruns at first boot, before/datais mounted and long before the wizard asks what the machine is. Making it role-awarewould mean carrying the answer from before first boot — a role staged on the ESP, read by a
repart drop-in generator ahead of the first pass — and even that could not cover the
run-from-USB rig, whose role is chosen on a medium repart already partitioned.
os/rootfs/repart.d/40-data.confcarries the full reasoning where the next person will look.Coverage
tests/stack/run.sh(+3 blocks): the rig boot leg's derived config, prebuilt-first, thevolatile journal and its idempotence, every refusal, both first-boot entry points, the boot
script's fork ordering, and both unit conditions.
tests/os/verify-image.sh(+13 checks): both role paths present in the shipped artifact —the fork, the conditions that admit each role, the baked prebuilt, no swap anywhere.
tests/os/run.sh --phase rig(new): the same page answeredRigForge→ rig card with nologin → mining from the byte-identical baked binary → no containers at all → reboot owned
by
pithead-bootwith the wizard closed → slot self-commit on an unanswered pool → A/B install,uncommitted rollback, self-commit, persistence.
The KVM battery was NOT run from this environment — the rig phase is assertions added, not
executed. It needs the bench.
make lintgreen;make testgreen (2052 stack assertions, 1733 dashboard, 304 frontend, 0failed); patch coverage 91%.
Also fixed in review: the marker read in
pithead-bootwas unguarded, which is harmless tocontrol flow (that script runs without
errexit) but would print a redirection error into thejournal of every coordinator boot. Guarded, and pinned by an assertion at both tiers.
🤖 Generated with Claude Code