feat(mining): xmrig updater service — fetch/verify/install the latest miner from Gitea
Adds util/XmrigUpdater: a background-thread service (mirrors util/Bootstrap) that pulls the latest DRG-XMRig release from the project's Gitea, verifies it, and installs the miner binary into the daemon directory. Service layer only; the mining-tab UI hook comes next. Flow: GET /api/v1/repos/DragonX/drg-xmrig/releases/latest -> pick the asset matching this platform (…-linux-x64.zip / …-win-x64.zip; no macOS build -> graceful "unavailable") -> download (libcurl, TLS verified) -> verify the archive SHA-256 -> extract with miniz, flattening the versioned subdir the archive nests the binary in -> verify the extracted binary's SHA-256 in memory before writing it -> atomic install (+chmod +x on POSIX). On Windows also extracts WinRing0x64.sys; config.json/README.md are skipped. Security (download-and-execute): TLS is verified, and BOTH the archive and the inner binary are checked against the SHA-256 checksums published in the release body (parsed as "<hex> <name>" lines) — install is refused on a missing or mismatched checksum. Split into a pure core (xmrig_updater_core.cpp: release parse, asset/platform match, checksum parse, SHA-256) and the curl/miniz worker (xmrig_updater.cpp). The core is unit-tested against a real captured release fixture (tests/fixtures/xmrig/release_latest.json); an env-gated (DRAGONX_TEST_NETWORK=1) integration test exercises the worker live and was verified end-to-end on linux-x64 (inner binary SHA-256 matches the published value). Both variants build; suite passes. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
137
src/util/xmrig_updater.h
Normal file
137
src/util/xmrig_updater.h
Normal file
@@ -0,0 +1,137 @@
|
||||
// DragonX Wallet - ImGui Edition
|
||||
// Copyright 2024-2026 The Hush Developers
|
||||
// Released under the GPLv3
|
||||
//
|
||||
// XmrigUpdater — fetch + verify + install the latest DRG-XMRig miner from the DragonX Gitea.
|
||||
//
|
||||
// Flow (mirrors util/Bootstrap): query the Gitea releases API for the latest release, pick the
|
||||
// asset matching this platform (…-linux-x64.zip / …-win-x64.zip), download it, verify its
|
||||
// published SHA-256, then extract the miner binary (flattening the versioned subdir the archive
|
||||
// nests it in) into the target directory and verify the extracted binary's SHA-256 before it is
|
||||
// made executable. All network/disk work runs on a background thread; progress is polled
|
||||
// thread-safely from the UI thread.
|
||||
//
|
||||
// Security: download-and-execute, so verification is mandatory — TLS is verified (libcurl
|
||||
// defaults), the host is the project's own Gitea over HTTPS, and BOTH the archive and the inner
|
||||
// binary are checked against the SHA-256 checksums published in the release body. The checksums
|
||||
// live in the release body markdown (no SHA256SUMS asset), parsed as "<hex> <name>" lines.
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <atomic>
|
||||
#include <cstddef>
|
||||
#include <map>
|
||||
#include <mutex>
|
||||
#include <string>
|
||||
#include <thread>
|
||||
#include <vector>
|
||||
|
||||
namespace dragonx {
|
||||
namespace util {
|
||||
|
||||
struct XmrigReleaseAsset {
|
||||
std::string name;
|
||||
std::string downloadUrl;
|
||||
long long size = 0;
|
||||
};
|
||||
|
||||
struct XmrigRelease {
|
||||
bool ok = false;
|
||||
std::string tag; // e.g. "v1.0.0"
|
||||
std::string body; // release notes markdown (holds the checksum blocks)
|
||||
std::vector<XmrigReleaseAsset> assets;
|
||||
std::string error;
|
||||
};
|
||||
|
||||
// ── Pure helpers (no I/O; unit-tested) ───────────────────────────────────────
|
||||
|
||||
// Parse the Gitea GET /releases/latest JSON into an XmrigRelease (ok=false + error on failure).
|
||||
XmrigRelease parseXmrigRelease(const std::string& json);
|
||||
|
||||
// The asset-name token for the host platform: "linux-x64", "win-x64", "macos-x64",
|
||||
// "macos-arm64", or "" if unknown/unsupported.
|
||||
std::string currentXmrigPlatformToken();
|
||||
|
||||
// Index of the asset whose name matches the platform token (e.g. ends with "-linux-x64.zip"),
|
||||
// or -1 if none (e.g. no macOS build is published).
|
||||
int selectXmrigAsset(const XmrigRelease& release, const std::string& platformToken);
|
||||
|
||||
// Parse "<sha256hex> <name> …" lines from the release body into { name -> lowercase-hex }.
|
||||
// Keys are the first whitespace-delimited token after the hash, so this captures both the archive
|
||||
// checksums (keyed by zip filename) and the inner-binary checksums (keyed by "xmrig"/"xmrig.exe").
|
||||
std::map<std::string, std::string> parseXmrigChecksums(const std::string& body);
|
||||
|
||||
// Lowercase-hex SHA-256 of a buffer (libsodium). Empty string on failure.
|
||||
std::string sha256Hex(const void* data, std::size_t len);
|
||||
|
||||
// The binary file basenames to extract for a platform: {"xmrig"} on POSIX,
|
||||
// {"xmrig.exe", "WinRing0x64.sys"} on Windows. First entry is always the miner binary.
|
||||
std::vector<std::string> xmrigExtractBasenames(const std::string& platformToken);
|
||||
|
||||
// ── Background worker ────────────────────────────────────────────────────────
|
||||
|
||||
class XmrigUpdater {
|
||||
public:
|
||||
enum class State {
|
||||
Idle,
|
||||
Checking,
|
||||
UpToDate,
|
||||
UpdateAvailable,
|
||||
Downloading,
|
||||
Verifying,
|
||||
Extracting,
|
||||
Done,
|
||||
Failed
|
||||
};
|
||||
|
||||
struct Progress {
|
||||
State state = State::Idle;
|
||||
double downloaded_bytes = 0;
|
||||
double total_bytes = 0;
|
||||
float percent = 0.0f;
|
||||
std::string status_text;
|
||||
std::string error; // non-empty on Failed
|
||||
std::string latest_tag; // tag reported by the API (once checked)
|
||||
std::string installed_tag; // caller-supplied current install (for update detection)
|
||||
bool update_available = false;
|
||||
};
|
||||
|
||||
// Gitea releases API for the DRG-XMRig fork.
|
||||
static constexpr const char* kApiUrl =
|
||||
"https://git.dragonx.is/api/v1/repos/DragonX/drg-xmrig/releases/latest";
|
||||
|
||||
XmrigUpdater() = default;
|
||||
~XmrigUpdater();
|
||||
XmrigUpdater(const XmrigUpdater&) = delete;
|
||||
XmrigUpdater& operator=(const XmrigUpdater&) = delete;
|
||||
|
||||
// Query the latest release on a background thread. `installedTag` (may be empty/unknown) is
|
||||
// compared to the API tag to set Progress.update_available. End state: UpToDate / UpdateAvailable
|
||||
// / Failed.
|
||||
void startCheck(const std::string& installedTag);
|
||||
|
||||
// Download → verify archive → extract (flatten) → verify binary → install into `targetDir` on a
|
||||
// background thread. Re-fetches the release so it is self-contained. End state: Done / Failed.
|
||||
// On Done, getProgress().latest_tag is the version that should be persisted as the installed tag.
|
||||
void startInstall(const std::string& targetDir);
|
||||
|
||||
void cancel();
|
||||
Progress getProgress() const;
|
||||
bool isDone() const; // true when state is Done or Failed (worker finished)
|
||||
|
||||
private:
|
||||
void runCheck(std::string installedTag);
|
||||
void runInstall(std::string targetDir);
|
||||
void setProgress(State state, const std::string& text, double done = 0, double total = 0);
|
||||
bool downloadToFile(const std::string& url, const std::string& destPath);
|
||||
std::string httpGet(const std::string& url);
|
||||
|
||||
mutable std::mutex mutex_;
|
||||
Progress progress_;
|
||||
std::atomic<bool> cancel_requested_{false};
|
||||
std::atomic<bool> worker_running_{false};
|
||||
std::thread worker_;
|
||||
};
|
||||
|
||||
} // namespace util
|
||||
} // namespace dragonx
|
||||
Reference in New Issue
Block a user