Skip to content

upgrade --check: report when a newer RigForge release exists (on-demand, no phone-home) #148

Closed

Description

@VijitSingh97

Why

An operator running a fleet from release tags has no way to learn a new RigForge shipped short of watching the GitHub repo. The obvious fix — a periodic version ping — is off the table: SECURITY.md:10-11 and README.md:132 promise "no telemetry, ever… no version ping", and that promise is a feature. The compatible shape is on-demand only: upgrade --check asks GitHub's releases API exactly when the operator types it, prints the answer, and exits 0 no matter what the network did (an update hint must never break a script that calls it).

Scope

  • upgrade --check: compare the local VERSION file against the latest GitHub release tag via one unauthenticated curl -fsS --max-time 5 https://api.github.com/repos/p2pool-starter-stack/rigforge/releases/latest | jq -r .... No gh dependency, no token — 60 unauthenticated requests/hour is plenty for a human-invoked check.
  • Output, three cases: up to date → one ok line; newer available → the version, the release URL, and the upgrade recipe (git pull in $SCRIPT_DIR, or git fetch --tags && git checkout <tag> for tag-pinned fleets, then sudo $0 upgrade — matching usage()'s "redeploy after a 'git pull'" wording); local ahead of the latest release (a develop checkout) → "ahead of the latest release" ok line, not a downgrade prompt.
  • Offline / rate-limited / API-shape-changed → warn one line and exit 0. Never nonzero, never a hang past the 5s timeout.
  • Version compare, decided: sort -V. Verified: GNU coreutils has it, and macOS's shipped sort (2.3-Apple) supports -V and orders 1.9.0 < 1.10.0 correctly (checked on the dev Mac — the "BSD sort lacks -V" folklore does not apply to Apple's sort, and Linux+macOS are the only supported platforms per the script's OS_TYPE handling). No pure-bash comparator; equality is checked with = before sort ever runs, so sort only breaks ties in one direction.
  • doctor gets NO network call — decided: doctor is the deterministic offline health check (it runs in CI/tests and on rigs mid-incident; a 5s DNS stall inside a health check is worse than a stale hint). Instead, append one static offline line to doctor's existing all-clear closing summary ("doctor: all critical checks passed.", rigforge.sh:3028): Check for a newer RigForge any time: '$0 upgrade --check'. Text only, on the success path only (a failing doctor should not advertise upgrades).
  • Docs: SECURITY.md:10-11 and README.md:132 must gain one clarifying sentence — the "no version ping" promise stands because the check is explicit and on-demand, never scheduled or piggybacked on another verb. This is a hard requirement, not polish: shipping a GitHub call without touching that paragraph makes the security doc wrong.
  • No new config keys — config.minimal.json stays minimal.

Out of scope: auto-update, a check timer/cron, checking from setup/apply/doctor (all would violate the on-demand posture), checking XMRig upstream versions (RigForge pins XMRig deliberately — #18).

Acceptance criteria

  • ./rigforge.sh upgrade --check with local == latest prints one up-to-date line (including the version) and exits 0.
  • With latest > local: prints the new version, the release html_url, and the exact upgrade commands; exits 0.
  • With local > latest (develop checkout): says so, exits 0, does not suggest downgrading.
  • Offline / curl failure / rate-limit 403 / tag_name missing: one warn line ("couldn't reach GitHub — try again later"), exit 0.
  • Plain upgrade behavior is byte-for-byte unchanged (it makes no new network calls); unknown flags to upgrade error out with the standard "Run '$0 help'" message.
  • doctor makes zero network calls (assert via CURL_LOG in tests that doctor triggers no curl) and its all-clear output mentions upgrade --check.
  • Tag formats v1.1.0 and 1.1.0 both compare correctly against the bare-semver VERSION file (strip a leading v before comparing).
  • SECURITY.md + README.md "no version ping" paragraphs updated with the on-demand clarification; usage() upgrade line mentions --check. make lint + make test pass (macOS bash 3.2 and Linux).

Implementation notes

Numbered, mechanical. All design decisions above (sort -V, exit-0-always, doctor stays offline) are made — do not reopen them.

  1. Dispatch (rigforge.sh:3086): upgrade) upgrade ;; → upgrade) shift; upgrade "$@" ;; (upgrade currently takes no args — verified; the shift is safe because the verb matched, so $# ≥ 1).
  2. upgrade() (rigforge.sh:1038): prepend an arg loop (copy backup()'s shape, rigforge.sh:2368-2374): --check) _upgrade_check; return 0 ;; *) error "Unexpected argument for upgrade: $arg. Run '$0 help'." ;;. The existing pipeline (1039-1052) runs only with no args, unchanged.
  3. _upgrade_check() (place directly above upgrade()):
    • local local_v remote_v url body; local_v=$(tr -d '[:space:]' <"$SCRIPT_DIR/VERSION" 2>/dev/null) — same read as cmd_version (rigforge.sh:2661); empty → warn "no VERSION file" + return 0.
    • body=$(curl -fsS --max-time 5 "https://api.github.com/repos/p2pool-starter-stack/rigforge/releases/latest" 2>/dev/null) || { warn "…couldn't reach GitHub…"; return 0; } — curl -f turns the rate-limit 403 into exit 22, so that path funnels into the same warn.
    • remote_v=$(printf '%s' "$body" | jq -r '.tag_name // empty' 2>/dev/null); url=$(printf '%s' "$body" | jq -r '.html_url // empty' 2>/dev/null); remote_v="${remote_v#v}"; empty remote_v → the same warn + return 0 (covers API shape changes and rate-limit JSON bodies that slip past -f).
    • Compare: [ "$local_v" = "$remote_v" ] → log "RigForge $local_v is the latest release."; else highest=$(printf '%s\n%s\n' "$local_v" "$remote_v" | sort -V | tail -n1); highest = remote_v → newer: log the version + URL + "Update: cd $SCRIPT_DIR && git pull && sudo $0 upgrade (tag-pinned fleets: git fetch --tags && git checkout v$remote_v)"; else → log "RigForge $local_v is ahead of the latest release ($remote_v) — development build.". Always return 0.
    • No parse_config, no root, no other side effects — the function reads one file and one URL.
  4. doctor (rigforge.sh:3026-3031): inside the issues -eq 0 branch only, after the existing log "doctor: all critical checks passed.", add log "Check for a newer RigForge any time: '$0 upgrade --check'.". Static string — no curl, no version read needed.
  5. usage() (rigforge.sh:3036): extend the upgrade line: upgrade redeploy after a 'git pull': rebuild + restart if the pinned XMRig changed ('--check' just reports whether a newer RigForge release exists).
  6. Tests (tests/run.sh): the harness already has the pieces — the curl stub records every call to CURL_LOG (tests/run.sh:175-179) and the version assertion pattern lives at tests/run.sh:1037. Add a unit block that sources $SCRIPT in a subshell (the _RIGFORGE_SOURCED pattern used throughout) and drives _upgrade_check with a test-local curl stub, since the shared stub emits an XMRig summary body:
    • stub printing {"tag_name":"v9.9.9","html_url":"https://github.com/p2pool-starter-stack/rigforge/releases/tag/v9.9.9"} → output contains 9.9.9, the URL, and upgrade; rc 0.
    • stub printing {"tag_name":"v$(tr -d '[:space:]' <VERSION)"} → "latest" line; rc 0.
    • stub printing {"tag_name":"v0.0.1"} → "ahead" line, no downgrade suggestion; rc 0.
    • stub exit 22 (curl -f on 403/offline) and stub printing {} → one warn line each, rc 0 both.
    • Ordering sanity: assert printf '1.9.0\n1.10.0\n' | sort -V | tail -n1 is 1.10.0 — a canary that fails loudly if some exotic host's sort lacks -V, instead of silently mis-comparing.
    • Black-box: bash "$SCRIPT" upgrade --bogus → rc 1 + "Unexpected argument"; bash "$SCRIPT" doctor with CURL_LOG exported → CURL_LOG has no releases-API line (doctor stayed offline).
  7. Docs: SECURITY.md:10-11 — after "no version ping", add: "(upgrade --check queries the GitHub releases API, but only when you run it — nothing is scheduled and nothing else calls it)"; README.md:132 — same clause; docs/operations.md upgrade section — two lines showing --check before upgrade.

Pitfalls: bash 3.2 — no associative arrays, no ${var,,}; set -Eeuo pipefail — the curl/jq/tr failures are all handled with || { warn; return 0; } or 2>/dev/null so the check can NEVER kill the shell or exit nonzero; strip the leading v from tags before comparing (VERSION is bare semver — tests/run.sh:1037 asserts that format); no gh, no token, nothing written to disk; do not log the response body (keep output to the three decided lines); jq --arg if any JSON is ever written (none is here); make lint = pinned shellcheck 0.11 + shfmt -i 4; no new config keys.

Related

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    documentationImprovements or additions to documentationenhancementNew feature or requestsetupConfiguration and first-run setup

    Type

    No type

    Projects

    No projects

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions