Skip to content

feat(os): the rig boot leg — a role=rig box mines instead of coordinating (#797 R4) - #876

Merged
VijitSingh97 merged 1 commit into
develop-v2from
feat/797-r4-rig-boot
Aug 3, 2026
Merged

feat(os): the rig boot leg — a role=rig box mines instead of coordinating (#797 R4)#876
VijitSingh97 merged 1 commit into
develop-v2from
feat/797-r4-rig-boot

Conversation

@VijitSingh97

Copy link
Copy Markdown
Collaborator

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-boot forks on the role marker before the container-image loader — a rig loads none.
Its branch is two steps:

  1. 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.
  2. The same health-gated 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_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 gives pool, worker name (RigForge's pools[].user) and the
stratum password when one was set, with no hugepages_reserve_extra_mb, because there is no
stack 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 where
the operator will actually see it — the coordinator's Workers view.

Prebuilt-first

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. 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 at
all 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=persistent with a 200 MB cap, and those
files land on the /var overlay whose upper lives on that same medium. The rig leg flips it to
volatile — logs in memory, no rotating writes — 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 and creates none, in any role.

Unit conditions — the other half of the fork

pithead-boot.service never fired on a rig (ConditionPathExists=/data/pithead/config.json), and
pithead-firstboot.service fired on every rig boot. Both now use systemd's | triggering
prefix: boot is admitted by either config.json or machine-role, 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. 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

/data needs no rig-sized rule, and could not have one.

  • It is already sized to the medium, not the role and not the image: no SizeMaxBytes,
    Weight=1000, so a 16 GB stick leaves a rig ~6 GiB after the ESP and both 4 GiB slots — plenty
    with no chains — while a 1 TB disk leaves a coordinator its chains.
  • It cannot be role-conditional at image build: systemd-repart runs at first boot, before
    /data is mounted and long before the wizard asks what the machine is. Making it role-aware
    would 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.conf carries 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, the
    volatile 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 answered RigForge → rig card with no
    login
    → mining from the byte-identical baked binary → no containers at all → reboot owned
    by pithead-boot with 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 lint green; make test green (2052 stack assertions, 1733 dashboard, 304 frontend, 0
failed); patch coverage 91%.

Also fixed in review: the marker read in pithead-boot was unguarded, which is harmless to
control flow (that script runs without errexit) but would print a redirection error into the
journal of every coordinator boot. Guarded, and pinned by an assertion at both tiers.

🤖 Generated with Claude Code

…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>
@VijitSingh97
VijitSingh97 merged commit 465a396 into develop-v2 Aug 3, 2026
17 checks passed
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