feat(fullnode): manage the daemon binary in Settings; stop auto-overwriting it
Previously the wallet re-extracted the bundled dragonxd on startup whenever the
installed binary's size differed from the bundle ("stale" overwrite), which could
replace a node a user had deliberately placed in dragonx/.
Now dragonx binaries (dragonxd/cli/tx) are auto-placed ONLY when missing — never
auto-overwritten on a size mismatch (needsParamsExtraction + extractEmbeddedResources).
Params/asmap keep their size-based refresh; a daemon dropped next to the wallet exe
still takes priority and is never touched.
Replacing the daemon is now an explicit action: Settings → "Daemon binary" reports the
installed binary's version (scanned from the file), size and modified date, compares it
to the version bundled in this build, and offers an "Install bundled daemon" button.
That stops the node, overwrites dragonxd/cli/tx with the bundled copies (waiting for the
process to release the file lock), and restarts — wallet/keys/chain data untouched.
Adds resources::{getInstalledDaemonInfo,getBundledDaemonInfo,reextractBundledDaemon}
(+ a version-string scanner) and App::reinstallBundledDaemon().
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
39
src/app.cpp
39
src/app.cpp
@@ -3035,6 +3035,45 @@ void App::repairWallet()
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void App::reinstallBundledDaemon()
|
||||||
|
{
|
||||||
|
if (!supportsFullNodeLifecycleActions()) {
|
||||||
|
ui::Notifications::instance().warning("Full-node lifecycle actions are unavailable in lite build");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if (!resources::getBundledDaemonInfo().available) {
|
||||||
|
ui::Notifications::instance().warning("This build has no bundled daemon to install");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if (!daemon_controller_ || !isUsingEmbeddedDaemon()) {
|
||||||
|
ui::Notifications::instance().warning("Reinstalling the daemon requires the embedded daemon");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
DEBUG_LOGF("[App] Reinstalling bundled daemon binary — stopping daemon first\n");
|
||||||
|
ui::Notifications::instance().info("Installing bundled daemon — the node will stop, update, and restart...");
|
||||||
|
|
||||||
|
async_tasks_.submit("reinstall-daemon", [this](const util::AsyncTaskManager::Token& token) {
|
||||||
|
AppDaemonLifecycleRuntime runtime(*this);
|
||||||
|
daemon::AsyncLifecycleTaskContext ctx(token, shutting_down_);
|
||||||
|
// Stop the daemon so its binary is no longer locked (Windows) / in use (ETXTBSY on Linux).
|
||||||
|
runtime.stopDaemonWithPolicy();
|
||||||
|
// Wait (bounded, ~12s) for the process to exit and release the binary before overwriting.
|
||||||
|
for (int i = 0; i < 120 && daemon::EmbeddedDaemon::isRpcPortInUse()
|
||||||
|
&& !ctx.cancelled() && !ctx.shuttingDown(); ++i) {
|
||||||
|
ctx.sleepForMs(100);
|
||||||
|
}
|
||||||
|
// Overwrite the installed dragonx binaries (dragonxd/cli/tx) with the bundled ones.
|
||||||
|
const bool ok = resources::reextractBundledDaemon();
|
||||||
|
DEBUG_LOGF("[App] Reinstall bundled daemon: %s\n", ok ? "ok" : "FAILED");
|
||||||
|
// Restart on the freshly-installed binary.
|
||||||
|
runtime.resetOutputOffset();
|
||||||
|
if (!ctx.cancelled() && !ctx.shuttingDown()) {
|
||||||
|
runtime.startDaemon();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
void App::deleteBlockchainData()
|
void App::deleteBlockchainData()
|
||||||
{
|
{
|
||||||
if (!supportsFullNodeLifecycleActions()) {
|
if (!supportsFullNodeLifecycleActions()) {
|
||||||
|
|||||||
@@ -307,6 +307,7 @@ public:
|
|||||||
void setUseEmbeddedDaemon(bool use) { use_embedded_daemon_ = use && supportsEmbeddedDaemon(); }
|
void setUseEmbeddedDaemon(bool use) { use_embedded_daemon_ = use && supportsEmbeddedDaemon(); }
|
||||||
void rescanBlockchain(); // restart daemon with -rescan flag
|
void rescanBlockchain(); // restart daemon with -rescan flag
|
||||||
void repairWallet(); // restart daemon with -zapwallettxes=2 (wipe & rebuild wallet tx records)
|
void repairWallet(); // restart daemon with -zapwallettxes=2 (wipe & rebuild wallet tx records)
|
||||||
|
void reinstallBundledDaemon(); // stop daemon, overwrite installed binary with the bundled one, restart
|
||||||
void deleteBlockchainData(); // stop daemon, delete chain data, restart fresh
|
void deleteBlockchainData(); // stop daemon, delete chain data, restart fresh
|
||||||
bool stopDaemonForBootstrap(); // stop daemon + disconnect for bootstrap, returns true if was running
|
bool stopDaemonForBootstrap(); // stop daemon + disconnect for bootstrap, returns true if was running
|
||||||
bool isBootstrapDownloading() const { return bootstrap_downloading_; }
|
bool isBootstrapDownloading() const { return bootstrap_downloading_; }
|
||||||
|
|||||||
@@ -5,6 +5,8 @@
|
|||||||
#include <filesystem>
|
#include <filesystem>
|
||||||
#include <vector>
|
#include <vector>
|
||||||
#include <cstdio>
|
#include <cstdio>
|
||||||
|
#include <cctype>
|
||||||
|
#include <chrono>
|
||||||
|
|
||||||
#ifdef _WIN32
|
#ifdef _WIN32
|
||||||
#include <windows.h>
|
#include <windows.h>
|
||||||
@@ -225,11 +227,13 @@ bool needsParamsExtraction()
|
|||||||
if (spendRes && resourceNeedsUpdate(spendRes, spendPath)) return true;
|
if (spendRes && resourceNeedsUpdate(spendRes, spendPath)) return true;
|
||||||
if (outputRes && resourceNeedsUpdate(outputRes, outputPath)) return true;
|
if (outputRes && resourceNeedsUpdate(outputRes, outputPath)) return true;
|
||||||
|
|
||||||
// Also check if daemon binaries need updating
|
// Daemon binaries are only auto-placed when MISSING (never auto-overwritten on a size
|
||||||
|
// mismatch) — the user may be running a specific dragonxd. Replacing the bundled daemon is
|
||||||
|
// an explicit action via Settings → daemon binary. So only trigger extraction if it's absent.
|
||||||
#ifdef HAS_EMBEDDED_DAEMON
|
#ifdef HAS_EMBEDDED_DAEMON
|
||||||
const auto* daemonRes = getEmbeddedResource(RESOURCE_DRAGONXD);
|
const auto* daemonRes = getEmbeddedResource(RESOURCE_DRAGONXD);
|
||||||
std::string daemonPath = daemonDir + pathSep + RESOURCE_DRAGONXD;
|
std::string daemonPath = daemonDir + pathSep + RESOURCE_DRAGONXD;
|
||||||
if (daemonRes && resourceNeedsUpdate(daemonRes, daemonPath)) return true;
|
if (daemonRes && !std::filesystem::exists(daemonPath)) return true;
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
#ifdef HAS_EMBEDDED_XMRIG
|
#ifdef HAS_EMBEDDED_XMRIG
|
||||||
@@ -369,12 +373,12 @@ bool extractEmbeddedResources()
|
|||||||
#ifdef HAS_EMBEDDED_DAEMON
|
#ifdef HAS_EMBEDDED_DAEMON
|
||||||
DEBUG_LOGF("[INFO] Daemon extraction directory: %s\n", daemonDir.c_str());
|
DEBUG_LOGF("[INFO] Daemon extraction directory: %s\n", daemonDir.c_str());
|
||||||
|
|
||||||
|
// Daemon binaries are placed ONLY when missing — never auto-overwritten on a size mismatch
|
||||||
|
// (the user may run a specific dragonxd; replacing it is an explicit Settings action).
|
||||||
const EmbeddedResource* daemonRes = getEmbeddedResource(RESOURCE_DRAGONXD);
|
const EmbeddedResource* daemonRes = getEmbeddedResource(RESOURCE_DRAGONXD);
|
||||||
if (daemonRes) {
|
if (daemonRes) {
|
||||||
std::string dest = daemonDir + pathSep + RESOURCE_DRAGONXD;
|
std::string dest = daemonDir + pathSep + RESOURCE_DRAGONXD;
|
||||||
if (resourceNeedsUpdate(daemonRes, dest)) {
|
if (!std::filesystem::exists(dest)) {
|
||||||
if (std::filesystem::exists(dest))
|
|
||||||
DEBUG_LOGF("[INFO] Updating stale dragonxd (size mismatch)...\n");
|
|
||||||
DEBUG_LOGF("[INFO] Extracting dragonxd (%zu MB)...\n", daemonRes->size / (1024*1024));
|
DEBUG_LOGF("[INFO] Extracting dragonxd (%zu MB)...\n", daemonRes->size / (1024*1024));
|
||||||
if (!extractResource(daemonRes, dest)) {
|
if (!extractResource(daemonRes, dest)) {
|
||||||
success = false;
|
success = false;
|
||||||
@@ -384,13 +388,11 @@ bool extractEmbeddedResources()
|
|||||||
#endif
|
#endif
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
const EmbeddedResource* cliRes = getEmbeddedResource(RESOURCE_DRAGONX_CLI);
|
const EmbeddedResource* cliRes = getEmbeddedResource(RESOURCE_DRAGONX_CLI);
|
||||||
if (cliRes) {
|
if (cliRes) {
|
||||||
std::string dest = daemonDir + pathSep + RESOURCE_DRAGONX_CLI;
|
std::string dest = daemonDir + pathSep + RESOURCE_DRAGONX_CLI;
|
||||||
if (resourceNeedsUpdate(cliRes, dest)) {
|
if (!std::filesystem::exists(dest)) {
|
||||||
if (std::filesystem::exists(dest))
|
|
||||||
DEBUG_LOGF("[INFO] Updating stale dragonx-cli (size mismatch)...\n");
|
|
||||||
DEBUG_LOGF("[INFO] Extracting dragonx-cli (%zu MB)...\n", cliRes->size / (1024*1024));
|
DEBUG_LOGF("[INFO] Extracting dragonx-cli (%zu MB)...\n", cliRes->size / (1024*1024));
|
||||||
if (!extractResource(cliRes, dest)) {
|
if (!extractResource(cliRes, dest)) {
|
||||||
success = false;
|
success = false;
|
||||||
@@ -400,13 +402,11 @@ bool extractEmbeddedResources()
|
|||||||
#endif
|
#endif
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
const EmbeddedResource* txRes = getEmbeddedResource(RESOURCE_DRAGONX_TX);
|
const EmbeddedResource* txRes = getEmbeddedResource(RESOURCE_DRAGONX_TX);
|
||||||
if (txRes) {
|
if (txRes) {
|
||||||
std::string dest = daemonDir + pathSep + RESOURCE_DRAGONX_TX;
|
std::string dest = daemonDir + pathSep + RESOURCE_DRAGONX_TX;
|
||||||
if (resourceNeedsUpdate(txRes, dest)) {
|
if (!std::filesystem::exists(dest)) {
|
||||||
if (std::filesystem::exists(dest))
|
|
||||||
DEBUG_LOGF("[INFO] Updating stale dragonx-tx (size mismatch)...\n");
|
|
||||||
DEBUG_LOGF("[INFO] Extracting dragonx-tx (%zu MB)...\n", txRes->size / (1024*1024));
|
DEBUG_LOGF("[INFO] Extracting dragonx-tx (%zu MB)...\n", txRes->size / (1024*1024));
|
||||||
if (!extractResource(txRes, dest)) {
|
if (!extractResource(txRes, dest)) {
|
||||||
success = false;
|
success = false;
|
||||||
@@ -590,6 +590,120 @@ bool forceExtractXmrig()
|
|||||||
#endif
|
#endif
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Scan a binary blob for the daemon's version stamp: 'v' <maj>.<min>.<rev> optionally followed by
|
||||||
|
// '-' <commit hash (>=6 hex)>, e.g. "v1.0.2-ddd851dc1". Returns the first match, or "" if none.
|
||||||
|
static std::string scanBinaryVersion(const uint8_t* data, std::size_t size)
|
||||||
|
{
|
||||||
|
if (!data || size < 6) return "";
|
||||||
|
auto isdig = [](uint8_t c) { return std::isdigit(static_cast<unsigned char>(c)) != 0; };
|
||||||
|
auto isxd = [](uint8_t c) { return std::isxdigit(static_cast<unsigned char>(c)) != 0; };
|
||||||
|
for (std::size_t i = 0; i + 5 < size; ++i) {
|
||||||
|
if (data[i] != 'v') continue;
|
||||||
|
std::size_t k = i + 1, s;
|
||||||
|
s = k; while (k < size && isdig(data[k])) ++k; if (k == s) continue; // major
|
||||||
|
if (k >= size || data[k] != '.') continue; ++k;
|
||||||
|
s = k; while (k < size && isdig(data[k])) ++k; if (k == s) continue; // minor
|
||||||
|
if (k >= size || data[k] != '.') continue; ++k;
|
||||||
|
s = k; while (k < size && isdig(data[k])) ++k; if (k == s) continue; // revision
|
||||||
|
std::size_t end = k;
|
||||||
|
if (k < size && data[k] == '-') { // optional -<commit>
|
||||||
|
std::size_t h = k + 1, hs = h;
|
||||||
|
while (h < size && isxd(data[h])) ++h;
|
||||||
|
if (h - hs >= 6) end = h;
|
||||||
|
}
|
||||||
|
return std::string(reinterpret_cast<const char*>(data) + i, end - i);
|
||||||
|
}
|
||||||
|
return "";
|
||||||
|
}
|
||||||
|
|
||||||
|
DaemonBinaryInfo getInstalledDaemonInfo()
|
||||||
|
{
|
||||||
|
DaemonBinaryInfo info;
|
||||||
|
std::string daemonDir = getDaemonDirectory();
|
||||||
|
#ifdef _WIN32
|
||||||
|
info.path = daemonDir + "\\" + RESOURCE_DRAGONXD;
|
||||||
|
#else
|
||||||
|
info.path = daemonDir + "/" + RESOURCE_DRAGONXD;
|
||||||
|
#endif
|
||||||
|
std::error_code ec;
|
||||||
|
if (!std::filesystem::exists(info.path, ec)) return info; // exists stays false
|
||||||
|
info.exists = true;
|
||||||
|
info.size = std::filesystem::file_size(info.path, ec);
|
||||||
|
if (ec) info.size = 0;
|
||||||
|
|
||||||
|
auto ftime = std::filesystem::last_write_time(info.path, ec);
|
||||||
|
if (!ec) {
|
||||||
|
// Convert filesystem clock → system_clock epoch (pre-C++20 portable approximation).
|
||||||
|
auto sysTime = std::chrono::time_point_cast<std::chrono::system_clock::duration>(
|
||||||
|
ftime - decltype(ftime)::clock::now() + std::chrono::system_clock::now());
|
||||||
|
info.modifiedEpoch =
|
||||||
|
static_cast<std::int64_t>(std::chrono::system_clock::to_time_t(sysTime));
|
||||||
|
}
|
||||||
|
|
||||||
|
// Read the binary and scan for its version stamp (one-off; caller caches the result).
|
||||||
|
std::ifstream f(info.path, std::ios::binary);
|
||||||
|
if (f) {
|
||||||
|
f.seekg(0, std::ios::end);
|
||||||
|
std::streamoff len = f.tellg();
|
||||||
|
f.seekg(0, std::ios::beg);
|
||||||
|
if (len > 0) {
|
||||||
|
std::vector<uint8_t> buf(static_cast<std::size_t>(len));
|
||||||
|
f.read(reinterpret_cast<char*>(buf.data()), len);
|
||||||
|
info.version = scanBinaryVersion(buf.data(), static_cast<std::size_t>(f.gcount()));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return info;
|
||||||
|
}
|
||||||
|
|
||||||
|
BundledDaemonInfo getBundledDaemonInfo()
|
||||||
|
{
|
||||||
|
BundledDaemonInfo info;
|
||||||
|
#ifdef HAS_EMBEDDED_DAEMON
|
||||||
|
const EmbeddedResource* res = getEmbeddedResource(RESOURCE_DRAGONXD);
|
||||||
|
if (res && res->data && res->size > 0) {
|
||||||
|
info.available = true;
|
||||||
|
info.size = res->size;
|
||||||
|
// The embedded bytes are constant for this build — scan once.
|
||||||
|
static const std::string cachedVersion = scanBinaryVersion(res->data, res->size);
|
||||||
|
info.version = cachedVersion;
|
||||||
|
}
|
||||||
|
#endif
|
||||||
|
return info;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool reextractBundledDaemon()
|
||||||
|
{
|
||||||
|
#ifdef HAS_EMBEDDED_DAEMON
|
||||||
|
std::string daemonDir = getDaemonDirectory();
|
||||||
|
#ifdef _WIN32
|
||||||
|
const char pathSep = '\\';
|
||||||
|
#else
|
||||||
|
const char pathSep = '/';
|
||||||
|
#endif
|
||||||
|
bool ok = true;
|
||||||
|
bool wroteAny = false;
|
||||||
|
const char* names[] = { RESOURCE_DRAGONXD, RESOURCE_DRAGONX_CLI, RESOURCE_DRAGONX_TX };
|
||||||
|
for (const char* name : names) {
|
||||||
|
const EmbeddedResource* res = getEmbeddedResource(name);
|
||||||
|
if (!res) continue;
|
||||||
|
std::string dest = daemonDir + pathSep + name;
|
||||||
|
DEBUG_LOGF("[INFO] reextractBundledDaemon: writing %s (%zu MB)\n", name, res->size / (1024*1024));
|
||||||
|
if (!extractResource(res, dest)) {
|
||||||
|
DEBUG_LOGF("[ERROR] reextractBundledDaemon: failed to write %s\n", name);
|
||||||
|
ok = false;
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
wroteAny = true;
|
||||||
|
#ifndef _WIN32
|
||||||
|
chmod(dest.c_str(), 0755);
|
||||||
|
#endif
|
||||||
|
}
|
||||||
|
return ok && wroteAny;
|
||||||
|
#else
|
||||||
|
return false;
|
||||||
|
#endif
|
||||||
|
}
|
||||||
|
|
||||||
std::string getXmrigPath()
|
std::string getXmrigPath()
|
||||||
{
|
{
|
||||||
std::string daemonDir = getDaemonDirectory();
|
std::string daemonDir = getDaemonDirectory();
|
||||||
|
|||||||
@@ -30,6 +30,31 @@ bool needsParamsExtraction();
|
|||||||
// Get the params directory path
|
// Get the params directory path
|
||||||
std::string getParamsDirectory();
|
std::string getParamsDirectory();
|
||||||
|
|
||||||
|
// --- Daemon binary management (Settings → daemon binary panel) ------------------------------
|
||||||
|
// Info about the dragonxd binary currently installed in the dragonx/ extraction directory.
|
||||||
|
struct DaemonBinaryInfo {
|
||||||
|
bool exists = false;
|
||||||
|
std::string path;
|
||||||
|
std::uintmax_t size = 0;
|
||||||
|
std::string version; // scanned from the binary ("vX.Y.Z-<commit>"), empty if not found
|
||||||
|
std::int64_t modifiedEpoch = 0; // last-write time as unix epoch seconds, 0 if unknown
|
||||||
|
};
|
||||||
|
|
||||||
|
// Info about the dragonxd binary bundled inside this wallet build.
|
||||||
|
struct BundledDaemonInfo {
|
||||||
|
bool available = false; // a daemon resource is embedded in this build
|
||||||
|
std::uintmax_t size = 0;
|
||||||
|
std::string version;
|
||||||
|
};
|
||||||
|
|
||||||
|
// Read + scan the installed dragonxd (reads the file; call off the UI thread or cache the result).
|
||||||
|
DaemonBinaryInfo getInstalledDaemonInfo();
|
||||||
|
// Info about the bundled daemon (scans the embedded bytes once, cached).
|
||||||
|
BundledDaemonInfo getBundledDaemonInfo();
|
||||||
|
// Force-overwrite the installed dragonx binaries (dragonxd/cli/tx) with the bundled ones. The
|
||||||
|
// caller should stop the daemon first. Returns true if all present resources were written.
|
||||||
|
bool reextractBundledDaemon();
|
||||||
|
|
||||||
// Resource names
|
// Resource names
|
||||||
constexpr const char* RESOURCE_SAPLING_SPEND = "sapling-spend.params";
|
constexpr const char* RESOURCE_SAPLING_SPEND = "sapling-spend.params";
|
||||||
constexpr const char* RESOURCE_SAPLING_OUTPUT = "sapling-output.params";
|
constexpr const char* RESOURCE_SAPLING_OUTPUT = "sapling-output.params";
|
||||||
|
|||||||
@@ -15,6 +15,8 @@
|
|||||||
#include "../windows/console_tab.h"
|
#include "../windows/console_tab.h"
|
||||||
#include "../../util/i18n.h"
|
#include "../../util/i18n.h"
|
||||||
#include "../../util/platform.h"
|
#include "../../util/platform.h"
|
||||||
|
#include "../../resources/embedded_resources.h"
|
||||||
|
#include <ctime>
|
||||||
#include "../../rpc/rpc_client.h"
|
#include "../../rpc/rpc_client.h"
|
||||||
#include "../../rpc/connection.h"
|
#include "../../rpc/connection.h"
|
||||||
#include "../../rpc/rpc_worker.h"
|
#include "../../rpc/rpc_worker.h"
|
||||||
@@ -147,6 +149,12 @@ struct SettingsPageState {
|
|||||||
bool confirm_delete_blockchain = false;
|
bool confirm_delete_blockchain = false;
|
||||||
bool confirm_rescan = false;
|
bool confirm_rescan = false;
|
||||||
bool confirm_repair_wallet = false;
|
bool confirm_repair_wallet = false;
|
||||||
|
bool confirm_reinstall_daemon = false;
|
||||||
|
// Cached daemon-binary status for the "daemon binary" panel (loaded once / on Refresh,
|
||||||
|
// since reading the installed binary to scan its version is a one-off disk read).
|
||||||
|
bool daemon_info_loaded = false;
|
||||||
|
dragonx::resources::DaemonBinaryInfo installed_daemon;
|
||||||
|
dragonx::resources::BundledDaemonInfo bundled_daemon;
|
||||||
bool confirm_restart_daemon = false;
|
bool confirm_restart_daemon = false;
|
||||||
bool confirm_lite_redownload = false;
|
bool confirm_lite_redownload = false;
|
||||||
effects::ScrollFadeShader fade_shader;
|
effects::ScrollFadeShader fade_shader;
|
||||||
@@ -2055,6 +2063,78 @@ void RenderSettingsPage(App* app) {
|
|||||||
}
|
}
|
||||||
if (ImGui::IsItemHovered(ImGuiHoveredFlags_AllowWhenDisabled)) ImGui::SetTooltip("%s", TR("tt_repair_wallet"));
|
if (ImGui::IsItemHovered(ImGuiHoveredFlags_AllowWhenDisabled)) ImGui::SetTooltip("%s", TR("tt_repair_wallet"));
|
||||||
ImGui::EndDisabled();
|
ImGui::EndDisabled();
|
||||||
|
|
||||||
|
// ---- Daemon binary: installed vs bundled, with explicit reinstall ----
|
||||||
|
// The wallet only auto-places dragonxd when it's missing (never overwrites a
|
||||||
|
// size-mismatched one), so this panel reports the installed binary and lets the
|
||||||
|
// user deliberately replace it with the version bundled in this wallet build.
|
||||||
|
if (!s_settingsState.daemon_info_loaded) {
|
||||||
|
s_settingsState.installed_daemon = dragonx::resources::getInstalledDaemonInfo();
|
||||||
|
s_settingsState.bundled_daemon = dragonx::resources::getBundledDaemonInfo();
|
||||||
|
s_settingsState.daemon_info_loaded = true;
|
||||||
|
}
|
||||||
|
const auto& inst = s_settingsState.installed_daemon;
|
||||||
|
const auto& bun = s_settingsState.bundled_daemon;
|
||||||
|
|
||||||
|
auto fmtDate = [](std::int64_t epoch) -> std::string {
|
||||||
|
if (epoch <= 0) return "—";
|
||||||
|
std::time_t t = static_cast<std::time_t>(epoch);
|
||||||
|
std::tm tmv{};
|
||||||
|
#ifdef _WIN32
|
||||||
|
localtime_s(&tmv, &t);
|
||||||
|
#else
|
||||||
|
localtime_r(&t, &tmv);
|
||||||
|
#endif
|
||||||
|
char buf[32];
|
||||||
|
std::strftime(buf, sizeof(buf), "%Y-%m-%d %H:%M", &tmv);
|
||||||
|
return std::string(buf);
|
||||||
|
};
|
||||||
|
|
||||||
|
ImGui::Dummy(ImVec2(0, Layout::spacingMd()));
|
||||||
|
Type().textColored(TypeStyle::Overline, OnSurfaceMedium(), TR("daemon_binary"));
|
||||||
|
ImGui::Dummy(ImVec2(0, Layout::spacingXs()));
|
||||||
|
|
||||||
|
if (inst.exists) {
|
||||||
|
ImGui::Text("%s %s", TR("daemon_installed"),
|
||||||
|
inst.version.empty() ? TR("unknown") : inst.version.c_str());
|
||||||
|
ImGui::TextColored(ImGui::ColorConvertU32ToFloat4(OnSurfaceMedium()), " %s · %s",
|
||||||
|
util::Platform::formatFileSize(inst.size).c_str(),
|
||||||
|
fmtDate(inst.modifiedEpoch).c_str());
|
||||||
|
} else {
|
||||||
|
ImGui::Text("%s %s", TR("daemon_installed"), TR("daemon_not_installed"));
|
||||||
|
}
|
||||||
|
|
||||||
|
if (bun.available) {
|
||||||
|
ImGui::Text("%s %s", TR("daemon_bundled"),
|
||||||
|
bun.version.empty() ? TR("unknown") : bun.version.c_str());
|
||||||
|
ImGui::TextColored(ImGui::ColorConvertU32ToFloat4(OnSurfaceMedium()), " %s",
|
||||||
|
util::Platform::formatFileSize(bun.size).c_str());
|
||||||
|
} else {
|
||||||
|
ImGui::Text("%s %s", TR("daemon_bundled"), TR("daemon_none_bundled"));
|
||||||
|
}
|
||||||
|
|
||||||
|
if (bun.available) {
|
||||||
|
const bool sameSize = inst.exists && inst.size == bun.size;
|
||||||
|
if (!inst.exists)
|
||||||
|
ImGui::TextColored(ImVec4(1.0f, 0.8f, 0.2f, 1.0f), "%s", TR("daemon_status_missing"));
|
||||||
|
else if (sameSize)
|
||||||
|
ImGui::TextColored(ImVec4(0.3f, 0.8f, 0.3f, 1.0f), "%s", TR("daemon_status_match"));
|
||||||
|
else
|
||||||
|
ImGui::TextColored(ImVec4(1.0f, 0.8f, 0.2f, 1.0f), "%s", TR("daemon_status_differ"));
|
||||||
|
}
|
||||||
|
|
||||||
|
ImGui::Dummy(ImVec2(0, Layout::spacingXs()));
|
||||||
|
ImGui::SetCursorScreenPos(ImVec2(leftX, ImGui::GetCursorScreenPos().y));
|
||||||
|
ImGui::BeginDisabled(!app->isUsingEmbeddedDaemon() || !bun.available);
|
||||||
|
if (TactileButton(TR("daemon_install_bundled"), ImVec2(0, 0), btnFont)) {
|
||||||
|
s_settingsState.confirm_reinstall_daemon = true;
|
||||||
|
}
|
||||||
|
if (ImGui::IsItemHovered(ImGuiHoveredFlags_AllowWhenDisabled)) ImGui::SetTooltip("%s", TR("tt_daemon_install_bundled"));
|
||||||
|
ImGui::EndDisabled();
|
||||||
|
ImGui::SameLine(0, Layout::spacingMd());
|
||||||
|
if (TactileButton(TR("refresh"), ImVec2(0, 0), btnFont)) {
|
||||||
|
s_settingsState.daemon_info_loaded = false; // recompute next frame
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
@@ -2688,6 +2768,37 @@ void RenderSettingsPage(App* app) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Confirm: reinstall the bundled daemon binary (stop → overwrite → restart)
|
||||||
|
if (s_settingsState.confirm_reinstall_daemon) {
|
||||||
|
if (BeginOverlayDialog(TR("confirm_reinstall_daemon_title"), &s_settingsState.confirm_reinstall_daemon, 500.0f, 0.94f)) {
|
||||||
|
ImGui::PushFont(Type().iconLarge());
|
||||||
|
ImGui::TextColored(ImVec4(1.0f, 0.8f, 0.2f, 1.0f), ICON_MD_WARNING);
|
||||||
|
ImGui::PopFont();
|
||||||
|
ImGui::SameLine();
|
||||||
|
ImGui::TextColored(ImVec4(1.0f, 0.8f, 0.2f, 1.0f), "%s", TR("warning"));
|
||||||
|
|
||||||
|
ImGui::Spacing();
|
||||||
|
ImGui::TextWrapped("%s", TR("confirm_reinstall_daemon_msg"));
|
||||||
|
ImGui::Spacing();
|
||||||
|
ImGui::TextColored(ImVec4(0.3f, 0.8f, 0.3f, 1.0f), "%s", TR("confirm_reinstall_daemon_safe"));
|
||||||
|
ImGui::Spacing();
|
||||||
|
ImGui::Separator();
|
||||||
|
ImGui::Spacing();
|
||||||
|
|
||||||
|
float btnW = (ImGui::GetContentRegionAvail().x - ImGui::GetStyle().ItemSpacing.x) * 0.5f;
|
||||||
|
if (ImGui::Button(TrId("cancel", "reinstall_daemon_cancel").c_str(), ImVec2(btnW, 40))) {
|
||||||
|
s_settingsState.confirm_reinstall_daemon = false;
|
||||||
|
}
|
||||||
|
ImGui::SameLine();
|
||||||
|
if (ImGui::Button(TrId("daemon_install_bundled", "reinstall_daemon_confirm").c_str(), ImVec2(btnW, 40))) {
|
||||||
|
app->reinstallBundledDaemon();
|
||||||
|
s_settingsState.daemon_info_loaded = false; // refresh the panel after the swap
|
||||||
|
s_settingsState.confirm_reinstall_daemon = false;
|
||||||
|
}
|
||||||
|
EndOverlayDialog();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// Confirm: restart daemon (briefly drops the connection to apply changed options)
|
// Confirm: restart daemon (briefly drops the connection to apply changed options)
|
||||||
if (s_settingsState.confirm_restart_daemon) {
|
if (s_settingsState.confirm_restart_daemon) {
|
||||||
if (BeginOverlayDialog(TR("confirm_restart_daemon_title"), &s_settingsState.confirm_restart_daemon, 500.0f, 0.94f)) {
|
if (BeginOverlayDialog(TR("confirm_restart_daemon_title"), &s_settingsState.confirm_restart_daemon, 500.0f, 0.94f)) {
|
||||||
|
|||||||
@@ -423,6 +423,19 @@ void I18n::loadBuiltinEnglish()
|
|||||||
strings_["confirm_repair_wallet_title"] = "Repair Wallet";
|
strings_["confirm_repair_wallet_title"] = "Repair Wallet";
|
||||||
strings_["confirm_repair_wallet_msg"] = "This restarts the daemon with -zapwallettxes=2: it deletes all of the wallet's transaction and note records, then rebuilds them from the blockchain. Use this when transactions fail to build (\"Invalid sapling spend proof\" / \"shielded requirements not met\") even after a full rescan. It takes a long time and the wallet stays offline until it finishes.";
|
strings_["confirm_repair_wallet_msg"] = "This restarts the daemon with -zapwallettxes=2: it deletes all of the wallet's transaction and note records, then rebuilds them from the blockchain. Use this when transactions fail to build (\"Invalid sapling spend proof\" / \"shielded requirements not met\") even after a full rescan. It takes a long time and the wallet stays offline until it finishes.";
|
||||||
strings_["confirm_repair_wallet_safe"] = "Your keys, addresses and balance are preserved — only the cached transaction records are rebuilt.";
|
strings_["confirm_repair_wallet_safe"] = "Your keys, addresses and balance are preserved — only the cached transaction records are rebuilt.";
|
||||||
|
strings_["daemon_binary"] = "Daemon binary";
|
||||||
|
strings_["daemon_installed"] = "Installed:";
|
||||||
|
strings_["daemon_bundled"] = "Bundled:";
|
||||||
|
strings_["daemon_not_installed"] = "not installed";
|
||||||
|
strings_["daemon_none_bundled"] = "none in this build";
|
||||||
|
strings_["daemon_status_match"] = "Installed binary matches the bundled version.";
|
||||||
|
strings_["daemon_status_differ"] = "Installed binary differs from the bundled version.";
|
||||||
|
strings_["daemon_status_missing"] = "No daemon installed — install the bundled version.";
|
||||||
|
strings_["daemon_install_bundled"] = "Install bundled daemon";
|
||||||
|
strings_["tt_daemon_install_bundled"] = "Stop the node, overwrite the installed dragonxd with the version bundled in this wallet build, then restart";
|
||||||
|
strings_["confirm_reinstall_daemon_title"] = "Install Bundled Daemon";
|
||||||
|
strings_["confirm_reinstall_daemon_msg"] = "This stops the daemon, overwrites the installed dragonxd (and dragonx-cli/dragonx-tx) with the versions bundled in this wallet build, then restarts the node. Use this to recover or update the node binary.";
|
||||||
|
strings_["confirm_reinstall_daemon_safe"] = "Your wallet, keys and blockchain data are not touched — only the daemon program files are replaced.";
|
||||||
strings_["confirm_restart_daemon_title"] = "Restart Daemon";
|
strings_["confirm_restart_daemon_title"] = "Restart Daemon";
|
||||||
strings_["confirm_restart_daemon_msg"] = "This stops and restarts the daemon to apply the changed options. The wallet will briefly disconnect and reconnect.";
|
strings_["confirm_restart_daemon_msg"] = "This stops and restarts the daemon to apply the changed options. The wallet will briefly disconnect and reconnect.";
|
||||||
strings_["lite_maintenance"] = "Maintenance";
|
strings_["lite_maintenance"] = "Maintenance";
|
||||||
|
|||||||
Reference in New Issue
Block a user