// DragonX Wallet - ImGui Edition // Copyright 2024-2026 The Hush Developers // Released under the GPLv3 // // DaemonUpdater — fetch + verify + install the latest dragonxd full node from the DragonX Gitea. // // Sibling of util::XmrigUpdater (the miner updater): same query → download → verify → extract → // install pipeline on a background thread, but for the full-node binaries instead of the miner. // Flow: query the Gitea releases API for the latest dragonx release, pick the archive matching this // platform (dragonx--linux-amd64.zip / -win64.zip / -macos.zip), download it, verify it, then // extract the daemon executables (dragonxd / dragonx-cli / dragonx-tx, flattening the versioned // subdir the archive nests them in) into the daemon directory and chmod them executable. // // Security: download-and-execute, so verification is mandatory. TLS is verified (libcurl defaults), // the host is the project's own Gitea over HTTPS, the archive's SHA-256 is checked against the // checksum table published in the release body, AND a detached ed25519 signature over the archive // bytes is verified against the key pinned below (kDaemonRequireSignature enforces it: an install // is refused when no valid .sig is published). The inner binaries are trusted by transitivity once // the whole archive is authenticated. The generic SHA-256 / ed25519 primitives are shared with the // miner updater (util::sha256Hex / util::verifyXmrigSignature in xmrig_updater_core.cpp). #pragma once #include #include #include #include #include #include #include namespace dragonx { namespace util { struct DaemonReleaseAsset { std::string name; std::string downloadUrl; long long size = 0; }; struct DaemonRelease { bool ok = false; std::string tag; // e.g. "v1.0.2" std::string name; // human title (e.g. "Dragonx v1.0.2") std::string body; // release notes markdown (holds the checksum table) bool prerelease = false; // marked pre-release on the Gitea release std::string publishedAt; // ISO-8601 publish timestamp (date shown in the UI) std::vector assets; std::string error; }; // ── Release signature (supply-chain hardening, mandatory for the daemon) ────── // // Pinned ed25519 public key (base64, 32 bytes) used to verify a detached signature over the // downloaded archive. With kDaemonRequireSignature=true an install is refused unless the release // publishes a ".sig" asset (base64 of, or raw, a 64-byte ed25519 signature over the // archive bytes) that verifies against this key — see scripts/sign-daemon-release.sh. To rotate the // key, regenerate with that script and replace the value below. If the key is set empty AND // kDaemonRequireSignature=false, verification falls back to TLS + the published SHA-256 only. inline constexpr const char* kDaemonSignaturePublicKeyBase64 = "d59hosHzh2LtHypgGvsMBlgrRGBhOfS0Xl40D/fEjzQ="; inline constexpr bool kDaemonRequireSignature = true; // enforced: refuse installs without a valid .sig // ── Pure helpers (no I/O; unit-tested) ─────────────────────────────────────── // Parse the Gitea GET /releases/latest JSON into a DaemonRelease (ok=false + error on failure). DaemonRelease parseDaemonRelease(const std::string& json); // Parse the Gitea GET /releases (array) JSON into the list of releases, newest first, skipping // drafts. Empty on parse failure. Lets the user browse + pin a specific (or pre-release) version. std::vector parseDaemonReleaseList(const std::string& json); // The asset-name token for the host platform: "linux-amd64", "win64", "macos", or "" if // unknown/unsupported (e.g. linux-arm64, for which no build is published -> Unavailable). std::string currentDaemonPlatformToken(); // Index of the asset whose name matches the platform token (e.g. ends with "-linux-amd64.zip"), // or -1 if none. int selectDaemonAsset(const DaemonRelease& release, const std::string& platformToken); // Parse the release-body markdown checksum table ( "| .zip | `` |" rows ) into // { archive-name -> lowercase-hex }. Header/separator/prose rows (no 64-hex token) are ignored. std::map parseDaemonChecksums(const std::string& body); // The binary file basenames to extract for a platform: {"dragonxd","dragonx-cli","dragonx-tx"} on // POSIX, the ".exe" variants on Windows. The first entry is always the daemon itself. std::vector daemonExtractBasenames(const std::string& platformToken); // Index of the detached-signature asset for a given archive (name ".sig" or // ".minisig"), or -1 if none is published. int selectDaemonSignatureAsset(const DaemonRelease& release, const std::string& archiveName); // Reduce a version string to its "vMAJOR.MINOR.PATCH" core, dropping any "-" suffix the // binary scanner appends, so an installed "v1.0.2-ddd851dc1" compares equal to a release "v1.0.2". // Returns the input unchanged if it holds no vN.N.N pattern. std::string daemonVersionCore(const std::string& version); // ── Background worker ──────────────────────────────────────────────────────── class DaemonUpdater { public: enum class State { Idle, Checking, UpToDate, UpdateAvailable, Unavailable, // no daemon build is published for this platform (terminal, not an error) Listing, // fetching the full release list (Browse all releases) ReleaseList, // release list fetched; awaiting the user's pick 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 dragonx full node. static constexpr const char* kApiUrl = "https://git.dragonx.is/api/v1/repos/DragonX/dragonx/releases/latest"; // Full release list (newest first, includes pre-releases) for the "Browse all releases" picker. static constexpr const char* kReleasesUrl = "https://git.dragonx.is/api/v1/repos/DragonX/dragonx/releases?limit=50"; DaemonUpdater() = default; ~DaemonUpdater(); DaemonUpdater(const DaemonUpdater&) = delete; DaemonUpdater& operator=(const DaemonUpdater&) = delete; // Query the latest release on a background thread. `installedVersion` (may be empty/unknown) is // compared (by vN.N.N core) to the API tag to set Progress.update_available. End state: // UpToDate / UpdateAvailable / Failed. void startCheck(const std::string& installedVersion); // Download → verify archive → extract (flatten) → install into `targetDir` on a background // thread. Re-fetches the latest release so it is self-contained. End state: Done / Failed. The // new binary takes effect on the next daemon start (the caller offers a restart). void startInstall(const std::string& targetDir); // Fetch the full release list on a background thread (for "Browse all releases"). End state: // ReleaseList (then getReleases() holds the list, newest first) / Failed. void startListReleases(); // Snapshot of the release list fetched by startListReleases(). std::vector getReleases() const; // Install a SPECIFIC release (chosen from the browse list) into `targetDir` — same verify/extract // path as startInstall, but pinned to `release` instead of latest. End state: Done / Failed. void startInstallRelease(const std::string& targetDir, DaemonRelease release); void cancel(); Progress getProgress() const; bool isDone() const; // true once the worker reached a terminal state (Done/Failed/Unavailable) // Internal: called by the libcurl progress callback. Publishes live download bytes and returns // false to ask curl to abort (when cancel() was requested). Public only so the C callback in the // .cpp can reach it without leaking curl types into this header. bool onDownloadProgress(double downloadedBytes, double totalBytes); private: void runCheck(std::string installedVersion); void runListReleases(); void runInstall(std::string targetDir); void installResolved(const std::string& targetDir, const DaemonRelease& rel); // shared install body 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::vector releases_; std::atomic cancel_requested_{false}; std::atomic worker_running_{false}; std::thread worker_; }; } // namespace util } // namespace dragonx