feat(recovery): bundle & embed the offline wallet-rebuild helper in every release

The dragonx-wallet-rebuild helper is the only thing that repairs a genuinely
BDB-inconsistent wallet.dat — plain "Restore" just re-triggers the daemon's
salvage cascade — yet it was silently dropped from every packaged build:

- Linux zip/AppImage copied a hand-picked file list that omitted it.
- Windows bundled it only behind a soft `[[ -f ]]` guard (silent skip).
- macOS never wired Berkeley DB, never built it, never bundled it.

build.sh now HARD-REQUIRES the helper for full-node releases (fails the build if
the vendored Berkeley DB depends are missing, rather than shipping recovery-less),
and ships it in the Linux zip + AppImage, the Windows zip, and the macOS .app.

It also compiles the helper standalone for Windows and INCBINs it, and
embedded_resources gains ensureWalletRebuildHelperExtracted() to extract it on
demand — so a self-contained ObsidianDragon.exe carries recovery exactly like the
embedded daemon, even on a machine where first-run param extraction already ran.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
2026-08-10 22:10:57 -05:00
co-authored by Claude Opus 4.8
parent ac49f44f84
commit 393f3d147e
3 changed files with 127 additions and 9 deletions
+47
View File
@@ -40,6 +40,9 @@ static const EmbeddedResource s_resources[] = {
{ g_dragonx_cli_exe_data, g_dragonx_cli_exe_size, RESOURCE_DRAGONX_CLI },
{ g_dragonx_tx_exe_data, g_dragonx_tx_exe_size, RESOURCE_DRAGONX_TX },
#endif
#ifdef HAS_EMBEDDED_WALLET_REBUILD
{ g_dragonx_wallet_rebuild_exe_data, g_dragonx_wallet_rebuild_exe_size, RESOURCE_DRAGONX_WALLET_REBUILD },
#endif
#ifdef HAS_EMBEDDED_XMRIG
{ g_xmrig_exe_data, g_xmrig_exe_size, RESOURCE_XMRIG },
#endif
@@ -436,6 +439,24 @@ bool extractEmbeddedResources()
}
#endif
#ifdef HAS_EMBEDDED_WALLET_REBUILD
// Offline wallet-rebuild recovery helper — extracted next to the daemon so a bare, self-extracting
// ObsidianDragon.exe still offers "Repair automatically" (findWalletRebuildHelper() checks this dir).
const EmbeddedResource* rebuildRes = getEmbeddedResource(RESOURCE_DRAGONX_WALLET_REBUILD);
if (rebuildRes) {
std::string dest = daemonDir + pathSep + RESOURCE_DRAGONX_WALLET_REBUILD;
if (!std::filesystem::exists(dest)) {
DEBUG_LOGF("[INFO] Extracting dragonx-wallet-rebuild (%zu MB)...\n", rebuildRes->size / (1024*1024));
if (!extractResource(rebuildRes, dest)) {
success = false;
}
#ifndef _WIN32
else { chmod(dest.c_str(), 0755); }
#endif
}
}
#endif
// Best-effort cleanup of any ".old" binaries left behind by a previous in-use replacement.
// Once the old daemon/xmrig process has exited, the file is no longer locked and removes cleanly;
// if it's still running, the remove fails harmlessly and we retry on the next startup.
@@ -450,6 +471,32 @@ bool extractEmbeddedResources()
return success;
}
std::string ensureWalletRebuildHelperExtracted()
{
#ifdef HAS_EMBEDDED_WALLET_REBUILD
const EmbeddedResource* res = getEmbeddedResource(RESOURCE_DRAGONX_WALLET_REBUILD);
if (!res || res->size == 0) return {};
#ifdef _WIN32
const char sep = '\\';
#else
const char sep = '/';
#endif
const std::string dir = getDaemonDirectory();
const std::string dest = dir + sep + RESOURCE_DRAGONX_WALLET_REBUILD;
std::error_code ec;
if (std::filesystem::exists(dest, ec)) return dest; // already extracted
std::filesystem::create_directories(dir, ec);
if (!extractResource(res, dest)) return {};
#ifndef _WIN32
chmod(dest.c_str(), 0755);
#endif
DEBUG_LOGF("[INFO] Extracted wallet-rebuild helper on demand: %s\n", dest.c_str());
return dest;
#else
return {};
#endif
}
std::string getDaemonDirectory()
{
// Daemon binaries live in %APPDATA%/ObsidianDragon/dragonx/ (Windows) or
+7
View File
@@ -55,6 +55,12 @@ BundledDaemonInfo getBundledDaemonInfo();
// caller should stop the daemon first. Returns true if all present resources were written.
bool reextractBundledDaemon();
// Ensure the embedded offline wallet-rebuild recovery helper is extracted to the daemon dir, and
// return its path ("" if not embedded in this build or extraction failed). Idempotent — extracts only
// when missing. Unlike the first-run extractEmbeddedResources() (gated on needsParamsExtraction()),
// this runs on demand so recovery works from a self-contained exe on ANY run, not just the first.
std::string ensureWalletRebuildHelperExtracted();
// Resource names
constexpr const char* RESOURCE_SAPLING_SPEND = "sapling-spend.params";
constexpr const char* RESOURCE_SAPLING_OUTPUT = "sapling-output.params";
@@ -62,6 +68,7 @@ constexpr const char* RESOURCE_ASMAP = "asmap.dat";
constexpr const char* RESOURCE_DRAGONXD = "dragonxd.exe";
constexpr const char* RESOURCE_DRAGONX_CLI = "dragonx-cli.exe";
constexpr const char* RESOURCE_DRAGONX_TX = "dragonx-tx.exe";
constexpr const char* RESOURCE_DRAGONX_WALLET_REBUILD = "dragonx-wallet-rebuild.exe";
constexpr const char* RESOURCE_XMRIG = "xmrig.exe";
constexpr const char* RESOURCE_DARK_GRADIENT = "dark_gradient.png";
constexpr const char* RESOURCE_LOGO = "logo_ObsidianDragon_dark.png";