diff --git a/CLAUDE.md b/CLAUDE.md index 40fe941..609ea71 100644 --- a/CLAUDE.md +++ b/CLAUDE.md @@ -79,13 +79,13 @@ The detailed milestone plan and design history (the v2 plan, backend artifact/AB ## Miner updater (xmrig) -The mining tab's pool section has an **"Update miner…"** button that downloads/verifies/installs the latest DRG-XMRig from the project Gitea (`util/XmrigUpdater` + `ui/windows/xmrig_download_dialog.h`). Flow: query `git.dragonx.is/api/v1/repos/DragonX/drg-xmrig/releases/latest` → pick the asset for this platform (`linux-x64` / `win-x64` / `macos-x86_64`; no match → "Unavailable") → libcurl download (TLS verified) → verify the archive **SHA-256** (from the release body) **and** a detached **ed25519 signature** → miniz-extract the binary (flattening the versioned subdir) into `resources::getDaemonDirectory()`. The whole archive is verified, so extracted members are trusted by transitivity (no per-member hash check). The pure, no-I/O core is split into `xmrig_updater_core.cpp` for unit tests; an env-gated (`DRAGONX_TEST_NETWORK=1`) test exercises the worker live. A **"Browse all releases…"** button (the `/releases` list, newest first, pre-releases included) lets users pin an older or pre-release build — same verify/install path via `startInstallRelease()`; the picker UI is shared with the daemon updater (`ui/windows/release_list_view.h`). +The mining tab's pool section has an **"Update miner…"** button that downloads/verifies/installs the latest DRG-XMRig from the project Gitea (`util/XmrigUpdater` + `ui/windows/xmrig_download_dialog.h`). Flow: query `git.dragonx.is/api/v1/repos/DragonX/drg-xmrig/releases/latest` → pick the asset for this platform (`linux-x64` / `win-x64` / `macos-x86_64`; no match → "Unavailable") → libcurl download (TLS verified) → verify the archive **SHA-256** (from the release body) **and** a detached **ed25519 signature** → miniz-extract the binary (flattening the versioned subdir) into `resources::getDaemonDirectory()`. The whole archive is verified, so extracted members are trusted by transitivity (no per-member hash check). The pure, no-I/O core is split into `xmrig_updater_core.cpp` for unit tests; an env-gated (`DRAGONX_TEST_NETWORK=1`) test exercises the worker live. The dialog is a two-pane version picker (every `/releases` entry on the left, newest first, pre-releases included) so users can pin an older or pre-release build — same verify/install path via `startInstallRelease()`. It shares only the `ReleaseRow` row model with the daemon updater (`ui/windows/release_list_view.h`); each dialog renders its own tactile Material list. **Signature verification is enforced** (`kXmrigRequireSignature = true` in `src/util/xmrig_updater.h`), checked against the public key pinned in `kXmrigSignaturePublicKeyBase64`. **Consequence for releases:** every `drg-xmrig` release MUST ship a detached signature per archive or the in-app updater refuses it. To cut a release: build the archives, then `scripts/sign-xmrig-release.sh sign ...` (OpenSSL-based, no extra deps) and upload each `.sig` as a release asset alongside its `.zip`. The signing **secret key must stay offline** (it is gitignored: `*.ed25519.key`); only its base64 public key is pinned in the source. To rotate the key, regenerate (`scripts/sign-xmrig-release.sh keygen`) and update `kXmrigSignaturePublicKeyBase64`. An emergency env override is not provided — disabling verification means setting `kXmrigSignaturePublicKeyBase64` empty (and rebuilding). ## Daemon updater (dragonxd) -Settings → **NODE & SECURITY → DAEMON BINARY** has a **"Check for updates…"** button that downloads/verifies/installs the latest **dragonxd full node** from the project Gitea — the full-node sibling of the xmrig updater (`util/DaemonUpdater` + `ui/windows/daemon_download_dialog.h`, pure no-I/O core in `daemon_updater_core.cpp`; gated full-node-only via `supportsFullNodeLifecycleActions()`). Flow: query `git.dragonx.is/api/v1/repos/DragonX/dragonx/releases/latest` → pick the archive for this platform (`linux-amd64` / `macos` / `win64`; no match → "Unavailable") → libcurl download (TLS verified) → verify the archive **SHA-256** (parsed from the release body's markdown **checksum table**, not xmrig's ` ` lines) **and** a detached **ed25519 signature** → miniz-extract the three executables (`dragonxd`/`dragonx-cli`/`dragonx-tx`, flattening the versioned subdir) into `resources::getDaemonDirectory()`. The archive also bundles Sapling params/asmap, which the updater deliberately leaves to the wallet's own resource extraction. Install is **atomic and safe while the node runs** (POSIX `rename()` replaces the in-use binary; Windows moves the locked `.exe` aside to `.old`); the new binary takes effect on the **next daemon start**, so the Done screen offers **"Restart daemon now"** (`App::restartDaemon()`). A **"Browse all releases…"** button (shared `release_list_view.h` picker) lets users pin a specific/older/pre-release node build via `startInstallRelease()` — with a downgrade caution, since an older binary may not match current chain data. +Settings → **NODE & SECURITY → DAEMON BINARY** has a **"Check for updates…"** button that downloads/verifies/installs the latest **dragonxd full node** from the project Gitea — the full-node sibling of the xmrig updater (`util/DaemonUpdater` + `ui/windows/daemon_download_dialog.h`, pure no-I/O core in `daemon_updater_core.cpp`; gated full-node-only via `supportsFullNodeLifecycleActions()`). Flow: query `git.dragonx.is/api/v1/repos/DragonX/dragonx/releases/latest` → pick the archive for this platform (`linux-amd64` / `macos` / `win64`; no match → "Unavailable") → libcurl download (TLS verified) → verify the archive **SHA-256** (parsed from the release body's markdown **checksum table**, not xmrig's ` ` lines) **and** a detached **ed25519 signature** → miniz-extract the three executables (`dragonxd`/`dragonx-cli`/`dragonx-tx`, flattening the versioned subdir) into `resources::getDaemonDirectory()`. The archive also bundles Sapling params/asmap, which the updater deliberately leaves to the wallet's own resource extraction. Install is **atomic and safe while the node runs** (POSIX `rename()` replaces the in-use binary; Windows moves the locked `.exe` aside to `.old`); the new binary takes effect on the **next daemon start**, so the Done screen offers **"Restart daemon now"** (`App::restartDaemon()`). The dialog is a two-pane version picker (every `/releases` entry on the left) so users can pin a specific/older/pre-release node build via `startInstallRelease()` — with a downgrade caution, since an older binary may not match current chain data. It shares only the `ReleaseRow` row model (`ui/windows/release_list_view.h`) with the miner updater; each renders its own tactile Material list. **Signature verification is enforced** (`kDaemonRequireSignature = true` in `src/util/daemon_updater.h`), checked against `kDaemonSignaturePublicKeyBase64`. **Consequence for releases:** every `dragonx` release MUST ship a detached `.sig` per platform archive or the in-app updater refuses it (as of v1.0.2 the releases publish SHA-256 but **no** signatures yet — sign + upload them to enable in-app updates). To cut a release: `scripts/sign-daemon-release.sh sign dragonx--{linux-amd64,macos,win64}.zip` (OpenSSL-based) and upload each `.sig` next to its `.zip`. The signing **secret key stays offline** (gitignored `*.ed25519.key`; this repo's is `dragonx-daemon.ed25519.key`); only the base64 public key is pinned. To rotate: `scripts/sign-daemon-release.sh keygen` and update `kDaemonSignaturePublicKeyBase64`. The generic SHA-256 / ed25519 primitives are shared with the miner updater (`util::sha256Hex` / `util::verifyXmrigSignature`). diff --git a/src/ui/windows/release_list_view.h b/src/ui/windows/release_list_view.h index 391e999..933e8f4 100644 --- a/src/ui/windows/release_list_view.h +++ b/src/ui/windows/release_list_view.h @@ -2,21 +2,14 @@ // Copyright 2024-2026 The Hush Developers // Released under the GPLv3 // -// Shared "Browse all releases" picker, used by both the miner updater (XmrigDownloadDialog) and the -// node updater (DaemonUpdateDialog). Renders a scrollable list of releases (tag, title, date, with -// pre-release / installed badges) inside the current overlay dialog; the caller maps its updater's -// release list into ReleaseRow values and acts on the clicked index. +// Shared release-row model for the updater dialogs. Both the miner updater (XmrigDownloadDialog) and +// the node updater (DaemonUpdateDialog) map their updater's release list into ReleaseRow values to +// drive their two-pane version picker (tag + title + date, with pre-release / installed badges). The +// two-pane list is the "browse all releases" UI; each dialog renders its own tactile Material cards. #pragma once #include -#include - -#include "../../util/i18n.h" -#include "../material/colors.h" -#include "../material/draw_helpers.h" -#include "../material/type.h" -#include "imgui.h" namespace dragonx { namespace ui { @@ -30,58 +23,5 @@ struct ReleaseRow { bool installed = false; // matches the currently-installed version (Install -> Reinstall) }; -// Renders the picker. Returns the row index whose Install button was clicked this frame, or -1. -// Sets *back when the Back button is clicked. If `globalDisabledTooltip` is non-null, every Install -// button is disabled and shows that tooltip (e.g. "stop mining before updating the miner"). -inline int RenderReleaseList(const std::vector& rows, float dp, bool* back, - const char* globalDisabledTooltip = nullptr) { - using namespace material; - int clicked = -1; - - Type().text(TypeStyle::Subtitle2, TR("upd_select_version")); - ImGui::Spacing(); - - const float listH = 300.0f * dp; - if (ImGui::BeginChild("##release_list", ImVec2(0, listH), true)) { - const ImVec4 dim = ImGui::ColorConvertU32ToFloat4(OnSurfaceMedium()); - const ImVec4 warn = ImVec4(1.0f, 0.78f, 0.25f, 1.0f); - const ImVec4 succ = ImGui::ColorConvertU32ToFloat4(Success()); - for (int i = 0; i < static_cast(rows.size()); ++i) { - const ReleaseRow& r = rows[i]; - ImGui::PushID(i); - - ImGui::TextUnformatted(r.tag.c_str()); - if (r.prerelease) { ImGui::SameLine(); ImGui::TextColored(warn, "[%s]", TR("upd_prerelease")); } - if (r.installed) { ImGui::SameLine(); ImGui::TextColored(succ, "[%s]", TR("upd_installed_badge")); } - - if (!r.title.empty() || !r.date.empty()) { - std::string meta = r.title; - if (!r.title.empty() && !r.date.empty()) meta += " · "; - meta += r.date; - ImGui::TextColored(dim, "%s", meta.c_str()); - } - - const char* lbl = r.installed ? TR("upd_reinstall") : TR("upd_install"); - const bool disabled = !r.hasAsset || globalDisabledTooltip != nullptr; - ImGui::BeginDisabled(disabled); - if (TactileButton(lbl, ImVec2(140.0f * dp, 0))) clicked = i; - ImGui::EndDisabled(); - if (disabled && ImGui::IsItemHovered(ImGuiHoveredFlags_AllowWhenDisabled)) - material::Tooltip("%s", globalDisabledTooltip ? globalDisabledTooltip - : TR("upd_no_build_platform")); - - ImGui::Spacing(); - ImGui::Separator(); - ImGui::Spacing(); - ImGui::PopID(); - } - } - ImGui::EndChild(); - - ImGui::Spacing(); - if (TactileButton(TR("upd_back"), ImVec2(ImGui::GetContentRegionAvail().x, 0))) *back = true; - return clicked; -} - } // namespace ui } // namespace dragonx