Skip to content

release: v1.15.0 - #363

Closed
VijitSingh97 wants to merge 35 commits into
mainfrom
develop
Closed

release: v1.15.0#363
VijitSingh97 wants to merge 35 commits into
mainfrom
develop

Conversation

@VijitSingh97

Copy link
Copy Markdown
Contributor

Promote develop to main for the v1.15.0 release. Gates: e2e-real all six phases PASS on miner-2 (2026-08-15, /opt checkout), e2e-pithead worker↔stack PASS against gouda's live stack (stratum-auth + dashboard phases env-skipped, recorded), full suite 1668/0, CI green. Contents: the v2-readiness stability wave — status abort fix (#341), TLS doc reality (#342), pool-reachability in doctor/apply (#343), control status mirror (#346), tune-interrupt restore (#347), appliance-mode CI pass (#348), ADR-0002 language (#352).

🤖 Generated with Claude Code

dependabot Bot and others added 30 commits July 23, 2026 20:11
Bumps [actions/checkout](https://github.com/actions/checkout) from 7.0.0 to 7.0.1.
- [Release notes](https://github.com/actions/checkout/releases)
- [Changelog](https://github.com/actions/checkout/blob/main/CHANGELOG.md)
- [Commits](actions/checkout@9c091bb...3d3c42e)

---
updated-dependencies:
- dependency-name: actions/checkout
  dependency-version: 7.0.1
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
* fix(tune): runtime HugePages reservation is grow-only (#328)

tune_kernel wrote the miner's computed requirement to vm.nr_hugepages
absolutely, which shrinks a pool another consumer already reserved (a
co-hosted pithead stack's p2pool/monerod share the same 2MB pool) down
to its in-use floor — zero free pages on both sides, measured live on
the pithead#797 appliance bench.

The write is now availability-based: pages the miner can draw on =
HugePages_Free + whatever a running miner already holds (it re-uses its
pages across a restart). The pool grows by the shortfall only and never
shrinks. Fresh single-purpose rigs see the same reservation as before;
re-runs that find enough available write nothing. This is the runtime
half of #305's co-resident keep-existing guard. The setup --dry-run
plan previews the same decision from the same availability check.

Closes #328

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>

* test(tune): cover the held-pages probe, fallback, and dry-run no-change branches

Three lines diff-cover flagged: the real-MainPID HugetlbPages read (now
exercised via a stub systemctl reporting a live pid), the no-proposed-grub
3072 fallback (now asserted grow-only too), and the dry-run plan's
covered-pool wording. The probe also always prints a number now — a status
file without a HugetlbPages line previously produced empty output.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>

---------

Co-authored-by: Claude Fable 5 <noreply@anthropic.com>
…ead#797 R1) (#330)

* feat(setup): appliance mode — baked deps, /run units, GRUB skip (pithead#797 R1)

One opt-in env flag, RIGFORGE_APPLIANCE=1, for running setup on the Pithead
appliance image: read-only root, volatile /etc overlay, and a boot leg that
re-runs setup every boot instead of relying on persisted state. Under the flag:

- dependencies are verified (command -v), never installed — a missing tool
  fails loudly naming the image-build gap; a prebuilt tree needs envsubst only
  (the R0 bench re-ran from cache with a half-broken compiler)
- the GRUB leg takes the skip branch deliberately: the kernel cmdline, incl.
  any 1GB-hugepage reservation, is image-owned
- units render into /run/systemd/system (SYSTEMD_DIR preset) and enable with
  systemctl enable --runtime
- hugetlbfs mounts at runtime; no fstab or limits.conf appends (the unit
  already sets LimitMEMLOCK=infinity)
- runtime tuning unchanged: modprobe msr, grow-only HugePages sysctl (#328),
  performance governor

setup --dry-run previews the same decisions through the shared helpers, never
a second copy. Every decision was proven on the pithead#797 R0 bench.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>

* fix(appliance): close the two review gaps — jq install and the logrotate drop-in

Adversarial review of the appliance flag found two writes that survived
the R1 contract: check_prerequisites still apt/brew-installed a missing
jq (appliance mode never installs — hard-fail naming the baked-image
fix instead), and generate_xmrig_config still wrote /etc/logrotate.d/
xmrig onto the volatile overlay of an image that runs no logrotate (log
policy belongs to the integration layer, pithead#797 R2). Tests cover
both; the missing-jq case clears bash's command hash table first, since
sourcing the script hashes jq's real path and command -v would ignore
the emptied PATH.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>

---------

Co-authored-by: Claude Fable 5 <noreply@anthropic.com>
The appliance full-run asserted --runtime for xmrig.service only; the
other enable sites (timers, api, control) share the same
${ENABLE_RUNTIME:+...} mechanism, but a future call site that forgets
the expansion would persist its enable into the volatile /etc overlay
and silently vanish on reboot. One guard closes the class: no
[systemctl] enable line in the appliance run's call log may lack
--runtime.

Co-authored-by: Claude Fable 5 <noreply@anthropic.com>
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
…from MSR failure (#333) (#334)

* fix(doctor): detect kernel lockdown directly instead of inferring it from MSR failure (#333)

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>

* fix(docs): rewrap line so markdownlint doesn't parse '+ Curve Optimizer' as a list (#333)

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>

---------

Co-authored-by: Claude Fable 5 <noreply@anthropic.com>
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
…ls don't fail (#327) (#337)

The apt dependency list named linux-tools-common unconditionally, but that
package is Ubuntu-only — Debian ships cpupower as linux-cpupower — and apt's
install transaction is all-or-nothing, so the one unknown name failed the
ENTIRE dependency install: git, build-essential and cmake never arrived
either. Probe with apt-cache show (the same guard the kernel-versioned
linux-tools-$(uname -r) package already had) and add whichever name the
distro actually ships; when neither exists, warn and carry on — cpupower is
a tuning aid, never worth losing the compiler toolchain over.

Closes #327

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-authored-by: Claude Fable 5 <noreply@anthropic.com>
…y slow (#339)

* feat(doctor): surface missing AES-NI / AVX2 instead of mining silently slow (#338)

RandomX without AES-NI falls back to XMRig's soft-AES path, roughly 4x
slower, and nothing anywhere said why — the last undelivered acceptance
criterion from #1 ("unsupported hardware is surfaced rather than silently
failing"). setup/apply now warn at configure time when the CPU flags lack
aes (and, advisory, avx2 — that one only slows dataset init), and doctor
counts a missing AES-NI as an issue. Judged only when an x86-style flags
line exists in /proc/cpuinfo (same CPUINFO override util/proposed-grub.sh
already uses); macOS, ARM and stubbed sandboxes read as unknown, and
unknown never manufactures an issue — the #333 lockdown stance. Never
aborts: a knowingly-old rig is a valid choice.

Closes #338

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>

* refactor: collapse one-arm case blocks to [[ ]] guards (ponytail-review)

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>

---------

Co-authored-by: Claude Fable 5 <noreply@anthropic.com>
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Four comments/docs still described the pre-acceptance design — "the rig
re-derives latest and refuses anything else" — contradicting accepted D4:
the staged body IS the target (the dashboard re-derives latest host-side,
over Tor); the rig makes no version check of its own and only BOUNDS the
supplied target with the monotonic anti-rollback + reachable-from-main
guards. Reworded util/control-server.py (_handle_upgrade comment),
systemd/rigforge-control-upgrade.service.template, the ADR 0002
Alternatives bullet, and the control_upgrade() header in rigforge.sh.
Comments and docs only; no behavior change.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
…lity (#342)

pithead#261 shipped: the stack serves TLS on the SAME :3333 (per-connection
detect, mixed fleet migrates one rig at a time), generates a self-signed cert
on first apply, and prints the lowercase 64-hex SHA-256 fingerprint (pithead
status repeats it). Rotation is delete the proxy-tls files + re-pin. Rewrite
docs/pithead-integration.md "Stratum over TLS" accordingly: drop the
unshipped claim, the :3334 example port, and the "once pithead#261 fixes its
port model" caveat; state that the rig side is config-file-not-prompt; point
the troubleshooting row at pithead status as the canonical pin source.

Ride-along audit one-liners (2026-08-14):
- docs/README.md: ADR 0002 status Proposed -> Accepted (D-OPEN-1 signed off);
  integration row mentions the optional :8081 sister API like the root README
- README.md: platform badge Ubuntu 22.04+ -> Ubuntu 22.04+ / Debian 12
  (Debian shipped in v1.14.0, #327)
- docs/operations.md: drop the word "simply"

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Pithead's host runner caps its synchronous /status poll after a
worker-apply at 20s, but control-apply's auto-rollback can take minutes —
the change-history row froze at 'accepted' with no way to catch up short
of a new authenticated dial to the rig's control port (a token the
dashboard container never holds).

The rig now mirrors its own last outcome into the already-open read feed:
_api_control_json reads {change_id, status, reason} from the status.json
_control_status already writes 0644, and _api_rigforge_block serves it as
rigforge.control. Missing, unreadable, or malformed status.json degrades
to control: null — the mirror can never break the feed — and pithead's
parse_worker_control_status filters non-terminal records itself.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
…341)

XMRig's /2/summary reports "hugepages" as a [loaded, total] pages array;
jq's @TSV refuses nested arrays (exit 5), so _status_api_summary's single
stats-row fork died on every healthy rig — the || true swallowed the exit
but the ERR trap still printed an abort, and the Hashrate/Pool/Uptime/
Shares block never rendered. Serialize the array to "loaded/total" before
@TSV; scalar and absent shapes pass through unchanged.

Tests: the shared curl stub now emits the real array shape (the old
bool-shaped fixture is how this slipped past the suite), the #143
assertion tracks it, and a new #341 section renders status against
array, scalar, and absent hugepages bodies.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
…iner (#348)

tests/e2e/linux.sh now runs TWO passes, a fresh disposable container each:
the standard deploy, then RIGFORGE_APPLIANCE=1. The appliance branch in
in-container.sh reuses the shared harness setup (real apt prereqs, /etc
seed, stubs, config) and asserts the mode's contracts on the container's
real filesystem across a first run and an every-boot re-run:

- units render into /run/systemd/system (real envsubst|tee), and nothing
  lands under /etc/systemd/system;
- no package installs: install_dependencies reads the toolchain as baked
  (new cc stub feeds the command -v probe) and a logging apt-get stub
  proves apt is never invoked;
- /etc/fstab, /etc/security/limits.conf and /etc/default/grub stay
  byte-identical (grepping for "memlock" would false-fail: the stock
  ubuntu limits.conf documents it in comments), no modules-load or
  logrotate drop-ins;
- every systemctl enable carries --runtime, pinned non-vacuous by the
  xmrig enable line and a zero count of enables without it;
- hugetlbfs mounted at runtime (argument-level: mount is stubbed, the
  real mountpoint probe reports not-mounted in a fresh container).

Honesty boundary stated in the harness comment: systemctl/mount stay
stubbed (no pid-1 systemd, no privileges in the container), so those
contracts are proven at the argument level; the filesystem effects are
real. To make argument-level assertions readable at all, the container
stubs now log "[cmd] args" to $CALL_LOG (run.sh's idiom) — this also
turns run 3's previously vacuous "no git clone" assert into real
evidence, since the git stub never logged before.

CI wiring is the existing e2e job's existing step: make test-e2e runs
both passes, so local runs and CI cover appliance identically with no
matrix plumbing.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
…ed (#347)

An interrupted tune --live (or autotune / tune --now) persisted whatever
mid-sweep candidate was in flight: _measure_live writes each candidate into
tune-overrides.json and restarts the miner on it, but the EXIT trap only
cleaned the temp dir and restarted a bench-stopped service — the candidate
stayed saved AND running, silently costing hashrate until the next re-tune.

Mechanism: tune() snapshots the pre-sweep overrides and marks the file dirty
for the duration of a live sweep; the existing EXIT trap (which bash runs on
INT/TERM/HUP and on errexit) now restores that snapshot FIRST — through
_restore_overrides, the exact restore+apply step the --confirm revert leg
already used, factored out so the abort path cannot diverge from the
completion path — before any other service handling, so every later restart
re-reads the restored file. The winner write clears the flag: a completed
decision stands down the abort-restore.

autotune gets the same protection via its own EXIT trap: the pre-sweep
prefetch mode is re-merged with _autotune_set_prefetch (the sweep's own
write path, shared via _autotune_settle with the clean-completion tail), so
knobs pinned by an offline tune survive the abort exactly as they survive a
completed run.

Tests: two black-box sections kill the real process mid-iteration (the fake
API flags the candidate window, then blocks; exec makes the backgrounded PID
the trap-owning shell) and assert the overrides come back byte-identical
(tune --live) / mode-restored with merged knobs intact (autotune), plus a
post-kill service restart. All six behavior asserts fail on the unfixed
script.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
… of assuming it (#343)

doctor and apply gave a clean bill of health while the rig mined nothing:
with pools[0].url unresolvable, xmrig loops on DNS errors every ~5s, yet
doctor printed "all critical checks passed" (no pool check existed) and
apply reported "Applied config and restarted xmrig." with no warning.

Mechanism: the miner's local API already knows the truth — /2/summary's
connection.uptime is positive exactly while a stratum connection is live
and 0 while it retries — so both verbs read that rather than dialing the
pool from the script (a second dial can disagree with the miner's
proxied/TLS view; the miner is the party that has to be connected).

- doctor gains a named pool-connection check right after the service
  check: connected -> ok line (pool, connection age, accepted shares);
  running-but-disconnected -> counted issue (exit 1 per #149); silent
  API -> advisory only (the miner may still be starting). Service
  stopped -> one guarded TCP dial of pools[0] stands in (host/port as
  positional args, never interpolated into the -c string; timeout-bound).
- apply's post-reconcile summary polls the API briefly (APPLY_POOL_TRIES
  x APPLY_POOL_IVL, default 5x3s, early exit once connected) and WARNS
  when no live connection appears — warn, never refuse: the pool may be
  legitimately down at apply time, so the exit code stays 0. tune and
  autotune are untouched (they restart via _apply_runtime and already
  verify liveness with _wait_miner_live).

The TSV parse places its "?" placeholder jq-side: a disconnected xmrig
reports pool "", and since tab is IFS whitespace, a leading empty field
would be swallowed by read and shift every later field one left —
"failures: 2" would have parsed as a live connection.

Tests: a doctor section (connected / disconnected / empty-pool fallback
to pools[0] / api-down / stopped+reachable / stopped+unreachable / real
closed-port dial) and a black-box apply section (connected report;
disconnected warn with rc 0; api-down warn with rc 0); the shared curl
stub's /2/summary body gains realistic connection.uptime/failures fields.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
The rig has no non-latest check — a newer-but-not-latest target is accepted;
the refusal category is a target outside the allowed bound.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
…ring continuations

diff-cover graded the reshaped filter's two continuation lines as 0% — bash
coverage marks only the command line, and a quoted string's continuation
lines can never execute. Same filter, one physical line; the tests already
prove all three hugepages shapes.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
…pages-array

fix(status): render XMRig's array-valued hugepages instead of aborting (#341)
docs: pithead stratum TLS is shipped, same-port — rewrite the stale section (#342)
…nguage

docs: stop claiming the rig re-derives latest — five sites, accepted D4 semantics (#352)
…container-pass

test(e2e): a second in-container pass with RIGFORGE_APPLIANCE=1 (#348)
…ability

# Conflicts:
#	CHANGELOG.md
#	tests/run.sh
…bility

fix(doctor,apply): read the miner's own pool-connection state (#343)
VijitSingh97 and others added 5 commits August 14, 2026 23:07
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
…atus-mirror

feat(api): mirror the control status into the enriched feed (#346)
…estore

fix(tune): an interrupted live sweep restores the pre-sweep config (#347)
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
@VijitSingh97

Copy link
Copy Markdown
Contributor Author

Promoted via the local rebase-twin flow (GitHub's rebase-merge could not create the twins cleanly): main bfc4be2cfd92fa, final tree verified byte-identical to develop's release commit (ab69af7) before tagging, full suite 1668/0 on the promotion tip. v1.15.0 tagged on cfd92fa.

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