Releases: p2pool-starter-stack/rigforge
Release list
RigForge v1.12.0
The pithead#597 producer release: the control-upgrade /status contract a one-click worker
upgrade can poll without string-matching, a fail-closed anti-beacon throttle, corrected
security docs, and the remote-upgrade chain codified as a repeatable real-hardware e2e phase.
Added
control-upgradestatus contract:started/noop/throttled+ version echo (#320).
Consumer findings from pithead's one-click worker upgrade (pithead#597): the oneshot now writes a
non-terminalstartedrecord the moment it claims a staged intent (so a poller can tell mid-build
from queued, and astartedthat outlives the oneshot means the run was lost); the already-on-target
refusal reportsnoopand the throttle window reportsthrottledinstead of collapsing into
failed; a successful upgrade'sreasonnames the landed version. Additive to the ADR 0002 D3/D6
model — D6 amended in place, contract documented in Operations › Writable control path.tests/e2e-real.shgrows anupgradephase (#322). Codifies the real-hardware
control-upgrade run that caught both real bugs in this chain (#308's dubious-ownership silent git
death, #318's origin/HEAD refusal) — invisible to the unit suite by design, since it stubs git. A
noop leg proves the wire → path unit → root oneshot →/statusround trip; a rollback leg forges
a local tag unreachable fromorigin/mainand asserts the D10 refusal rolls back with the tree,
VERSION, and miner intact and the D6 throttle stamped; an opt-inE2E_UPGRADE_TARGETleg drives
a real forward upgrade. Runs insideallbetweencontrolandperf, with the same
snapshot-and-revert guarantees. Also the producer half of pithead#597's cross-repo gate.
Fixed
- The anti-beacon throttle fails CLOSED on an unusable state dir (#321). It used to proceed
unthrottled (with a warn) when it couldn't take its lock — inverting the guard's whole purpose on
exactly the kind of degraded box an attacker might arrange. Now the upgrade is refused with a
failedstatus whose reason names the throttle state, distinct fromthrottledso a consumer
doesn't retry-later forever against a broken rig. - Disabling the control path re-stops the receiver explicitly. On the v1.12.0 pre-tag gate,
a loaded systemctl transiently dropped the stop half ofdisable --now: the unit files were
removed but the running receiver was left orphaned — a live write surface on a rig whose config
said control was off. The disable path now issues one explicit re-stop (no-op when the stop
landed). Thecontrole2e phase also gained the polled receiver-up gate theupgradephase
ships with — its single-shot check hit the same pinned-CPU cold-start race on the gate run. - Security-doc drift on the remote-upgrade trust model (#321). SECURITY.md claimed the remote
path is "verified by SHA256SUMS hash only" — it never was; the real anchor is the ADR 0002 D5
design (git checkout of the release tag over TLS,origin/mainancestry, immutable releases), and
the doc now says so. ADR 0002 D8's "a flock serializes concurrent triggers" corrected in place:
the only flock guards the throttle stamp; serialization isType=oneshot+ newest-wins spool
draining.
RigForge v1.11.2
Consistency-audit patch: CLI-surface and docs cleanups, plus a real fix to the remote-upgrade
reachability guard surfaced by the first real-hardware control-upgrade e2e (miner-0).
Fixed
control-upgrade's reachability guard pinsorigin/main(#318). The D10 guard checked the
target tag againstorigin/HEAD; on a fresh clone that resolves todevelop(the repo default),
and release tags are merge commits onmainthatdevelopdoesn't contain — so such a rig
refused every legitimate remote upgrade with a rollback. Releases are cut frommain, so the
guard now checks ancestry againstorigin/mainexplicitly (fail-closed when the ref is absent,
e.g. a single-branch clone). ADR 0002 D10 amended in place. Deployed rigs were unaffected (their
clones pointorigin/HEADatmain).control-upgradejoins the CLI surface (#312). It's now listed inhelpwith the other
internal verbs, rejects stray arguments like its siblings, and is documented in
Operations › Writable control path. The unknown-command hint no longer maintains a second,
drifting verb list (it had silently lostwatchdogand both control verbs) — it points at
helpinstead.- Unknown-option errors name the verb's valid flags (#314). Every
Unknown option for <verb>
message now both lists the flags that verb accepts and points athelp; previously some verbs
did one, some the other. New tests cover the previously untested backup/restore/support-bundle
error paths, plus a drift guard on the template. - Test suite: the
control_upgradeunit-test git stub no longer matches subcommands by position.
v1.11.1'ssafe.directoryfix shifted git's argument positions, silently breaking the stub's
$3dispatch — the two rollback tests failed ondevelopfrom that commit until now.
Changed
- README: "What it does" now covers day-2 operation. New-user feature tour extended past
provisioning:tune/autotune, the watchdog andmax_temp_c,doctor/status, thebios
walkthrough,backup/restore/support-bundle/completion, the opt-in Pithead control path,
and cleanuninstall.
RigForge v1.11.1
The first published v1.11 release. v1.11.0 was tagged but not published: its remote worker-upgrade
path (#308) was inert on real rigs — fixed here. Everything under [1.11.0] below (co-resident
HugePages sizing #305, the remote worker-upgrade path #308) ships in this release, with the fix.
Fixed
control-upgradenow works when triggered on a real rig (#308). The privileged upgrade oneshot
runs as root with no$HOME, sogitcouldn't read root'ssafe.directoryconfig and fatally
refused the operator-owned install as "dubious ownership" — every git call in the upgrade failed and
the upgrade silently died (rolling back to the same, unbuilt state). Eachgitinvocation in the
upgrade path now pins-c safe.directory="$SCRIPT_DIR", so fetch / reachability / checkout run
regardless of the unit's environment; a drift guard keeps a future git call from dropping it. Caught
by the real-hardware (miner-0) upgrade e2e — the stubbed unit suite can't reach it (it stubs git).
RigForge v1.10.0
Follow-ups from the post-v1.9.0 re-validation of the 2026-07 repo scan: one operator-facing check
added, three ERR-trap / state-durability edges closed. All four issues (#278, #290, #296, #301) were
filed during the scan and its regression hunt.
Added
doctorreports control-receiver health when the control path is enabled (#278). With
control: enabled,doctornow checks thatrigforge-controlis active and that its/status
endpoint answers (an authed probe over loopback;200/503both count as up —503just means no
change applied yet), so an operator (or Pithead) can't believe the writable path is live while the
service is dead, crash-looping, or firewalled. Enabled-but-down is a counted issue with a
journalctl -u rigforge-controlhint; the token is never echoed;doctorstays silent when control
is disabled.
Fixed
-
An unverifiable
numa_nps/smtBIOS item no longer vanishes from the resumable state (#296).
If a fresh probe comes backunknownat verify time (e.g.lscpucan't read the CPU model, or SMT
isn't exposed in sysfs) the item is now keptpending—memory_profile/power_boostalready
resurrected,numa_nps/smtdidn't — instead of silently dropping fromrigforge-bios.json. The
"can't verify" hint is also per-item now (the old text nameddmidecode, wrong for NPS's
lscpu/sysfs probe and SMT's sysfs probe). -
A host with no RAPL power sensors no longer spams the ERR trap during tuning (#290, #301). When
RAPL watts sampling is on but thepowercapsysfs is absent,_rapl_sumlegitimately returns
non-zero; underset -Eeuo pipefailthat fired the misleading "[ERROR] rigforge aborted while
starting up" line at every sample — the same class fixed for the hashrate grep in #277. Both the
offline bench path (_xmrig_bench, #290) and the live-tune path (_measure_live, #301) now guard
the sampling the way_autotune_samplealready did. Cosmetic on the fleet (all rigs have RAPL);
it only bit generic/older hardware.
RigForge v1.9.0
Fixes and hardening from the 2026-07 full-repo scan (five independent review passes over the whole
tree: security, shell correctness, util/tests/systemd, docs accuracy, and test gaps), plus a
post-merge re-validation pass over the result. All 13 scan issues (#265–#277) closed.
Fixed
- Explicit
falsepool booleans are honored (#265). jq's//operator treatsfalselike
null, so"enabled": false(a parked failover pool) and"keepalive": falsewere silently
rewritten to thetruedefault — the pool mined anyway. A single-line restore pass (same shape
as the #115 tls-fingerprint pass) re-attaches explicit falses after normalization, and the tune
seeds (_seed_g/_seed_wr) keep an explicit base-configfalseinstead of benchmarking with
the wrong MSR/1G-pages behavior. tune --benchno longer falsely rejects healthy candidates as "throttled to 0 MHz" (#266).
For an even sample count, awk printed large kHz medians in scientific notation (4.6275e+06),
which the shell floor mangled into4— ~97% of candidates per run were rejected on real
hardware and the search silently kept the seed baseline. The freq writer now floors to whole
kHz; a follow-up keeps an empty sample set (no readable cpufreq) as "no reading" instead of a
literal 0, which would have re-introduced the false trip from the other side.- The standalone
perfrelease gate exits non-zero on a regression (#267).perf()counted
failures but never calledsummary, sosudo bash tests/e2e-real.sh perfprinted a red
regression and exited 0 — a scripted release check would have passed a slower build. The #214
unit test now exercises the real exit plumbing instead of reimplementing it. - The guided
biosflow walks and verifies the EPYC NPS item (#268).numa_npswas detected
and persisted (#201) but never rendered in the checklist and never verified — a pending-NPS-only
state printed "Everything's already set" and a verify run silently dropped the item. It now
guides to NPS4 (mirroring doctor's recommendation) and counts applied/still-pending correctly. restorevalidates the backup's config before overwriting the live one (#274). A corrupted
or cross-host archive whoseconfig.jsonfailsparse_configis rejected with the live config
left byte-untouched, instead of bricking a working rig until the nextapply.control-applywrites a terminal status on every exit path (#276). An unreadable rollback
snapshot used to ERR-trap the root oneshot out with no status written, leaving the receiver
serving the stale previous outcome forever; it now recordsfailedwith the cause (additive
fourth outcome next toapplied/rejected/rolled_back). The two rollback outcomes are also
distinguishable now: "rolled back and live" vs "rollback re-apply also failed to restore
liveness".compute_build_jobscaps at 1 job on sub-1GB hosts (#277). Integer truncation made the RAM
cap vanish on exactly the hosts it protects, compiling with-j$(nproc)— the OOM scenario the
cap exists for. A zero-hashrate bench iteration also no longer spams the journal with misleading
[ERROR] rigforge abortedlines from the ERR trap (#277).e2e-pitheadcleans up the right rig-lock holder sidecar (#269). The cleanup still removed
the pre-#244/run/rig-e2e.holderpath, so every run left a stale "miner-0 busy" breadcrumb at
/var/lock/rig-e2e.lock.holderfor the next blocked arrival; a drift guard now fails the suite
if the old path ever reappears.- Docs accuracy (#271 + re-validation). Coverage-scope claims (kcov instruments
rigforge.sh+util/proposed-grub.sh, not all ofutil/), diff-cover's base branch, which
verbs self-elevate vs use inline sudo, the Linux-only command list, the ADR'scontrol-apply
command name, thefailedoutcome in the control-status enums, and the NPS item in the
guided-bios docs.
Security
config.jsonis created mode 600 from the first byte (#270). The fresh-config write went
through the process umask before itschmod 600, leaving a short window where another local
user could open the file the operator is about to fill with a wallet andACCESS_TOKEN. Now
created underumask 077like every other secrets-bearing file. (The 2026-07 security pass
found nothing else: token comparison, control-path fail-closed posture, unit hardening, and
secrets handling all came back clean.)
Added
controlphase in the release e2e (#272).tests/e2e-real.shnow drives the writable
control path (#236) against real systemd for the first time — enable, POST a change, watch the
path-unit → root-oneshot chain reachapplied, assert the config/revision/miner, then revert —
with an idempotent cleanup trap that guarantees the rig never stays control-enabled. Wired into
allbetweenverifyandperf, and into RELEASING.md's gate list.- Test-coverage batch from the scan (#273, #275, #276).
uninstallandapplynow have
coverage over the api/control units (a regression leaving the token-authed writable server
running after uninstall is caught); five can't-fail assertions actually fail now (stratum-user
label, dry-run drift guard, envsubstNFT_PATH, two-way reference-config key drift, the #reown
skip is explicit); five failure paths are pinned (rollback double-failure, tune-abort restart,
config-meta stamping onapply, changes-index prune,api_refreshpartial failure). The two
timing-sensitive tune black-box tests are Linux-gated (#292) — their plumbing is Linux sysfs,
and the deterministic unit-level halves still run everywhere.
RigForge v1.8.0
Added
-
Config revision + last-change provenance in the enriched feed (#254).
rigforge.config_meta
now ridesGET :api_port/1/summary:{revision, changed_at, source, last_change_id}.revisionis
a short content hash of the effective writable config — it changes iff that config changes, so a
polling consumer can tell "same as last time I looked" from "changed under me" (and invalidate a
stale editor prefill). It's over the unmasked config, so a pool-password change bumps it too. It's
stamped on every path that rewrites the writable config — localapply, control-apply, restore —
recordingsource(control/local/restore) and, for a control change, itslast_change_id; a
no-op apply or an autotune restart (autotune tunes runtime params, not the writable config) never
false-bumps it. Additive. -
Query a specific
change_idon the control API (#255).GET :control_port/status?change_id=<16hex>
returns that change's recorded outcome (or404), so a caller whose confirmation was stepped on by
a concurrent change can still record its own result — removing the accepted→poll race. The applier
indexes each outcome underchanges/<change_id>.json(last ~20). The no-argGET /statusstill
returns the most recent (backward compatible); auth unchanged (bearer). Additive. -
Effective writable config in the enriched feed (#253).
GET :api_port/1/summary(=/2/summary)
now carriesrigforge.config— the rig's current writable config (pools,DONATION,autotune,
watchdog,watchdog_interval_min,max_temp_c), exactly the keys the control path accepts, read
the same wayparse_configdoes (canonical strings). Lets Pithead's Worker Inspect prefill from a
live rig read and round-trip read→edit→POST /apply. Served even when the miner is down (read from
config.json). Pool secrets are masked:pools[].passand anytls-fingerprintare omitted, so a
round-trip that re-sendspoolsmust re-supply the pool password. Additive — no existing key changes. -
IPv6
api_allow_from(#243). The API firewall scoping now accepts an IPv6 address/CIDR
(rendering anip6 saddrrule in theinet rigforgetable) as well as IPv4, and the sister/control
servers bind dual-stack whenapi_bind/control_bindis set to::. On an IPv6-primary LAN you
can now pin the writable control path to the stack's v6 source instead of falling back to a broad
LAN CIDR. The per-family charset stays the injection guard; malformed addresses and a bad prefix
hard-error.
Fixed
-
rig-lock holder write can never drop the flock (#249). The shared
rig_lockholder-marker
write is now strictly display-only and best-effort (plain write → sudo -n tee → || true); under
set -Eeuo pipefaila failed write to a non-writable holder path used to abort the run after the
flock was acquired, dropping it and leaving the shared box UNRESERVED — the exact collision the
lock exists to prevent. The EXIT-trap cleanup gained asudo -n rmfallback for a root-written
marker. A new self-test probes the kernel flock directly (a rawflock -nfails while a run holds
it, succeeds after) so a "reports success but never held" regression can't return; this was also
re-confirmed live on the rig during the release gate (a rawflock -nfailed 91/91 probes across a
perf run). The read-open from #242 stays the canonical approach — it holds the flock on FD 9 for the
whole run and survivesfs.protected_regular=2, which the sibling pithead#484exec 9>does not;
the pithead mirror should adopt the read-open for the two to be byte-identical. Test-infra only. -
API firewall fails closed on a rejected ruleset (#248, pre-release scan).
install_api_firewallnow
checks thatnft -factually loaded the rules and hard-errors if it didn't, rather thanapply
swallowing the failure with|| trueand still logging "firewall active". A charset-valid but
structurally-invalid IPv6api_allow_fromcould otherwise pass parse_config, fail to load, and
leave the API ports open while the log claimed they were scoped. parse_config also now warns when
api_allow_fromis IPv6 butapi_bind/control_bindare still IPv4 — a silent no-op scope. -
rig-lock helper: portable timestamp + non-root holder default (#244). The shared
rig_lock
breadcrumb now defaults beside the lock (<lockfile>.holder) instead of root-owned
/run/rig-e2e.holder(a non-root box couldn't write there — noise, no breadcrumb), and stamps
date -u +%Y-%m-%dT%H:%M:%SZinstead of the GNU-onlydate -Iseconds(BSD/macOS rejects-I).
Mirrors the sibling pithead fix; both e2e helper copies stay identical. Also from a pre-release
scan: dropped thesudo chmodfallback and added a symlink-path guard on the lock/holder (a
planted symlink in world-writable/run/lockcould otherwise redirect a root-side write).
Test-infra only. -
rig-lock survives a non-root reserve (#242). The shared
rig_lockhelper now opens
/var/lock/rig-e2e.lockread-only (9<) instead of write (9>). A lock file first created by a
non-rootflock(a manual reserve after a reboot clears the/run/locktmpfs) is owned by that
user, andfs.protected_regular=2then blocked even root'sO_CREAT-write of it with EACCES,
aborting the release perf gate until someonermed the file. A read-open is never guarded and
flockworks fine on a read fd, so this sidesteps it without ever removing a possibly-held lock.
The~/README.mdbox contracts also now say to reserve as root. Test-infra only.
Security
-
The remote control path can no longer strip thermal protection (#257). A
POST :control_port/apply
that would disable thewatchdog, or unset / set an out-of-bandmax_temp_c(a rig's thermal
cutoff), is now refused with400at the receiver (util/control-server.py), with a mirrored
applier-side backstop in_control_commit. Before this, such a change passed the liveness rollback
gate (the miner keeps hashing) and was recorded as a normal successfulapplied— so Pithead's
one-click Worker Inspect edit could silently remove a rig's thermal cutoff. A localrigforge.sh applyon the box is unaffected (operator physically present). Defense-in-depth: any allowed change
touchingwatchdog/max_temp_cadds awarnings[]entry to the/statusoutcome (additive to the
/statusshape) so the dashboard can force an extra confirmation. -
Documented the write-capable control-token posture (#256). With the control path enabled, the
ACCESS_TOKENbearer is write-capable and travels in cleartext HTTP (no TLS);api_allow_fromscopes
the source but not the token in flight.SECURITY.md,docs/pithead-integration.md, and ADR 0001
now state this explicitly and prescribe LAN isolation; on-LAN TLS is deliberately deferred (reverse
proxy is the escape hatch). No code change — a conscious, documented decision.
RigForge v1.7.0
Added
- Writable control path (#236). An opt-in, fail-closed producer for pithead's Worker Inspect:
a separate authenticated endpoint (default:8082,control/control_port/control_bind)
that lets the stack apply config changes through RigForge, soconfig.jsonstays the source of
truth. Enabling it requires bothACCESS_TOKENandapi_allow_from(a writable API with no
token or no pinned source is refused with a hard error), and the nftables scoping extends to the
control port. An unprivileged receiver validates the shape of a change and stages it off the
request path (so a write never touches mining); a path-triggered root oneshot snapshots the old
config to a timestampedconfig-backups/, re-validates against RigForge's key allowlist (only
pools,DONATION,autotune,watchdog(+interval),max_temp_care writable — never
identity, trust, path, or auth keys), writes it durably (fsync + atomic rename), applies it, and
rolls back anything that doesn't come back to a live hashrate.GET /statusreports the outcome
withsource: "control". Design and decisions indocs/adr/0001. Linux-only; off by default.
RigForge v1.6.0
Added
- Thermal sensing works on hwmon-only boards (#208).
_read_tempfalls back to the CPU hwmon
(k10temp/coretemptemp1_input) when nothermal_zoneexists — the shape of every rig in
the production fleet, where the watchdog'smax_temp_cand tune's temperature sampling were
silently blind. A thermal zone still wins when present; nothing readable stays empty (a missing
sensor never stops a miner). Docs carry the Tctl caveat: k10temp reads a control temperature
that runs high by design, so damage-avoidance cutoffs belong well above the everyday reading. - Watchdog state on the wire (#212). The sister API's
rigforgeobject gains awatchdog
block — mode,thermal_hold,max_temp_c/resumes_below_c, livetemp_c, and the strike
count — and/healthcarries it too. A thermally-held miner now says "held, resumes below N"
on the one component still alive instead of looking mystery-dead; a wedge being counted is
visible before the restart lands. Absent or garbled state files degrade to defaults, never a
failed refresh. - EPYC NPS regression detection (#201). A multi-CCD EPYC reporting a single NUMA node is NPS1
— a BIOS update or CMOS reset silently eats the NPS4 setting and costs RandomX its per-node
datasets.doctor's firmware advisory and the guidedbioswalkthrough now flag it (with the
AMD CBS menu path) via one shared detector; desktop parts and unverifiable topologies are never
flagged. The genericpower_boostmenu text also gains the server-board spellings (cTDP /
Package Power Limit / Determinism).
Changed
- Per-rig benchmark recording is now also the per-rig perf gate (#214).
E2E_PERF_RECORD=1
judges against the committed baseline and best-ever history before writing; a regressed
measurement fails the phase and records nothing (E2E_PERF_FORCE=1is the conscious
re-record override). Previously the recording run — the only perf run most rigs ever got —
skipped the judgment entirely. - Perf recording tells you how to finish the job (#206). A recorded run prints the files to
collect and the post-merge rig reset (git checkout -- tests/perf-baselines/) that keeps the
next tag deploy from aborting on a dirty checkout; RELEASING.md carries the same step. - Tab completion caught up with the CLI (#207).
setup/apply --dry-run,upgrade --check,
andbios --perf/--efficiencynow complete, and a flag-level drift guard keeps future flags
from shipping uncompleted.
Security
- Release signing removed by decision; checksums stay (#205). The minisign machinery from
#137 never actually signed a release (no keypair existed until the 2026-07-11 audit), and its
protection only materializes for users who pin the public key outside the repo — nobody, for a
project deployed from git tags. Rather than carry key-custody forever for a hypothetical,
releases now shipSHA256SUMSonly, and SECURITY.md says plainly what that does and doesn't
prove. The signing machinery stays in git history if a download-based user base ever makes the
trade worth it. Dependabot now watches the SHA-pinned actions, and a weekly workflow opens
build-verified PRs when upstream XMRig releases (#225).
RigForge v1.5.1
Fixed
- watchdog: wedge recovery actually fires on an unreachable API (#210). In v1.5.0 the check
crashed (curl's exit rodepipefailout of the probe into an unguarded assignment under
set -e) before the strike logic ran — so the exact condition the watchdog exists for, a live
process with a dead API, was never recovered, and the failed oneshot spammed the journal every
tick. Caught by the #139 live wedge test on miner-0. The probe guard now sits inside the
command substitution (an outer||can't suppress the ERR trap firing in the subshell); the
same shape made offlineupgrade --checkprint ERR-trap noise ahead of its one warn line,
fixed identically. Regression tests run both paths through the real dispatch, where the trap
is live.
RigForge v1.5.0
Security
- Sister API hardening (2026-07-10 audit). The one network-facing process no longer runs as
root:rigforge-api.servicegetsDynamicUser=yes, reading the 0600config.jsonthrough a
systemdLoadCredentialinstead of owning it. The Bearer check is now constant-time
(hmac.compare_digest) and a 10 s request-arrival timeout closes the single-connection
slowloris hang on the deliberately single-threaded server.doctorgains one advisory line for
the open-AND-unscoped posture (noACCESS_TOKEN, noapi_allow_from) — correct on the designed
trusted LAN, loud enough to catch before a rig faces anything else.
Changed
- Consistency sweep (#149).
doctornow exits non-zero when critical issues are found
(cron-friendly, matching Pithead's health verb). Extra arguments to no-arg verbs (doctor foo,
Pithead'ssetup --skip-depsmuscle memory) error with a pointer to help instead of being
silently ignored.uninstallgains the same EOF-guarded prompt as every other prompt (a piped
run aborts cleanly on the default-No path) and the same strict flag loop asrestore. All
unknown-flag errors share one template (Unknown option for <verb>: '<arg>'), the four
"Aborting." suffixes are gone, and the reboot reminder speaks[WARN]like every other
instruction. Docs: a Privileges note (which verbs sudo inline, which self-elevate, and that
status/logs/backup/restorenever need root — the docs used to prescribesudofor
looking), and a written config-key naming policy (legacy SCREAMING keys frozen; all new keys
lowercase snake_case, matching Pithead). No key or verb was renamed.
Added
-
Miner watchdog + optional thermal cutoff (#139).
"watchdog": "enabled"installs a systemd
timer (everywatchdog_interval_minminutes, default 5) running one health check per tick: two
consecutive checks seeing 0 H/s or an unreachable worker API restart a wedged miner — the
process-alive-but-mining-nothing caseRestart=alwayscan't see — while a single blip never
does.max_temp_c(opt-in, empty by default) adds a runtime thermal cutoff: stop above it,
start again exactly once belowmax_temp_c - 5(fixed 5 °C hysteresis). A stopped miner is left
to systemd; an unreadable sensor never stops a healthy miner; the token never lands in unit
files or logs. Same timer machinery as periodic autotune; Linux-only; removed cleanly on
disable and byuninstall. -
upgrade --check(#148). On-demand "is there a newer RigForge?" — one unauthenticated query
of GitHub's releases API, compared against the localVERSIONwithsort -V, printing the new
version, release URL, and the upgrade recipe when you're behind (an "ahead of the latest
release" note on develop checkouts, never a downgrade prompt). Runs exactly when you type it —
nothing scheduled, no other verb calls it, so the "no version ping" promise in SECURITY.md
stands — and always exits 0, so it's safe inside operator scripts.doctorstays fully offline;
its all-clear summary now just mentions the command. -
support-bundle(#147). One verb collects everything a bug report needs — doctor output,
version, configs, miner-log tail, tuning + unit files, hardware basics — into a mode-600 local
tarball with secrets redacted structurally in jq (token/pass removed, wallet masked to
first-4…last-4; a file jq can't parse is skipped, never shipped unredacted). Collects the
minimum, uploads nothing, and says exactly what it did and didn't take. -
setup --dry-run/apply --dry-run(#146). A numbered, machine-specific plan of exactly
what would happen — missing packages by name, build-or-skip, the exact GRUB cmdline
before → after and whether a reboot follows, fstab/unit/timer actions — computed from read-only
probes, no sudo, nothing touched. A printer function, not a DRY_RUN flag threaded through
mutating code (a printer can't mutate by construction); a drift test pins the plan to every
step in the real pipeline. -
Shell completion (#145).
completion bash|zshprints a static, zero-dependency tab-completion
script (nothing auto-installs;source <(rigforge completion bash)opts in). Verbs and per-verb
flags complete; a suite drift-guard diffs the script's verb list against the dispatch case, so a
new verb can't ship without completion. zsh rides bash's script via bashcompinit — one list. -
Live status summary (#143).
statusnow opens with a one-glance block from a single
worker-API fetch — hashrate, pool, uptime (1d 2h 3m), accepted/rejected shares, huge pages —
then the unchanged platform detail. Facts only (no ✓/! judgment — that's doctor's), never sudo,
and a stopped miner or bad config degrades to one explanatory line plus the platform block. -
Shared-rig lock for the release gates (#183).
e2e-realande2e-pitheadtake a kernel
flockon/var/lock/rig-e2e.lockbefore their first service- or API-touching action, so
RigForge's rig-mutating gates and Pithead's e2e testing can't collide on the shared miner-0: a
second arrival exits 75 (EX_TEMPFAIL) naming the holder (project, suite, pid, start time, via
the/run/rig-e2e.holdersidecar), andRIG_LOCK_WAIT=1queues instead of failing. The lock
dies with the holding process — a killed run needs no cleanup. Pithead's harness carries the
same helper against the same path; the path is the whole contract. Test-harness only:
rigforge.shitself stays lock-free.