From 3e9743a9c44cacd6b1986a0303078079e14a7de8 Mon Sep 17 00:00:00 2001 From: jwinterm Date: Thu, 18 Jun 2026 11:16:13 -0400 Subject: [PATCH] =?UTF-8?q?wowlet:=20embed=20wownerod=20(WOWNEROD=5FDIR,?= =?UTF-8?q?=20mirrors=20Feather's=20Tor=20embedding)=20=E2=80=94=20single?= =?UTF-8?q?=20self-contained=20binary;=20DaemonManager=20unpacks=20it?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- CMakeLists.txt | 22 ++++++++++++++++++++++ cmake/WownerodQrcGenerator.cmake | 24 ++++++++++++++++++++++++ cmake/assets_wownerod.qrc | 5 +++++ src/CMakeLists.txt | 6 +++++- src/main.cpp | 4 ++++ src/utils/DaemonManager.cpp | 31 +++++++++++++++++-------------- src/utils/config.cpp | 1 + src/utils/config.h | 1 + 8 files changed, 79 insertions(+), 15 deletions(-) create mode 100644 cmake/WownerodQrcGenerator.cmake create mode 100644 cmake/assets_wownerod.qrc diff --git a/CMakeLists.txt b/CMakeLists.txt index dd2087ad..535e1d9d 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -21,6 +21,7 @@ cmake_policy(SET CMP0074 NEW) option(STATIC "Link libraries statically, requires static Qt" OFF) option(SELF_CONTAINED "Disable when building Feather for packages" OFF) option(TOR_DIR "Directory containing Tor binaries to embed inside Feather" OFF) +option(WOWNEROD_DIR "Directory containing the wownerod binary to embed inside wowlet" OFF) option(CHECK_UPDATES "Enable checking for application updates" OFF) option(PLATFORM_INSTALLER "Built-in updater fetches installer (windows-only)" OFF) option(USE_DEVICE_TREZOR "Trezor support compilation" ON) @@ -137,6 +138,7 @@ endif() include(GenerateDocs) include(TorQrcGenerator) +include(WownerodQrcGenerator) # To build Feather with embedded (and static) Tor, pass CMake -DTOR_DIR=/path/to/tor/ if(TOR_DIR) @@ -163,6 +165,26 @@ else() message(STATUS "Skipping Tor inclusion because -DTOR_DIR=Off") endif() +# wowlet: copy the wownerod binary into src/assets/wownerod so it gets embedded — single self-contained +# file with the node inside. WOWNEROD_DIR points at a dir containing a `wownerod` (or wownerod.exe). +if(WOWNEROD_DIR) + execute_process(COMMAND bash -c "mkdir -p ${CMAKE_CURRENT_SOURCE_DIR}/src/assets/wownerod") + if (REPRODUCIBLE) + set(WOWNEROD_COPY_CMD "cp -a ${WOWNEROD_DIR}/* ${CMAKE_CURRENT_SOURCE_DIR}/src/assets/wownerod") + else() + set(WOWNEROD_COPY_CMD "cp -au ${WOWNEROD_DIR}/* ${CMAKE_CURRENT_SOURCE_DIR}/src/assets/wownerod") + endif() + + execute_process(COMMAND bash -c "${WOWNEROD_COPY_CMD}" RESULT_VARIABLE ret) + if(ret EQUAL "1") + message(FATAL_ERROR "wownerod copy failure: ${WOWNEROD_COPY_CMD}") + endif() + + message(STATUS "Embedding wownerod binary from ${WOWNEROD_DIR}") +else() + message(STATUS "Skipping wownerod inclusion because -DWOWNEROD_DIR=Off") +endif() + message(STATUS "Using Boost include dir at ${Boost_INCLUDE_DIRS}") message(STATUS "Using Boost libraries at ${Boost_LIBRARIES}") diff --git a/cmake/WownerodQrcGenerator.cmake b/cmake/WownerodQrcGenerator.cmake new file mode 100644 index 00000000..5855ef33 --- /dev/null +++ b/cmake/WownerodQrcGenerator.cmake @@ -0,0 +1,24 @@ +# Generates src/assets_wownerod.qrc embedding the wownerod binary from WOWNEROD_DIR, so wowlet ships +# as a single self-contained file with its node inside. Mirror of TorQrcGenerator.cmake. When +# WOWNEROD_DIR is OFF the generated qrc is empty (no embedded daemon) and HAS_WOWNEROD_BIN is unset. +set(QRC_LIST) + +if (WOWNEROD_DIR) + FILE(GLOB WOWNEROD_FILES LIST_DIRECTORIES false ${WOWNEROD_DIR}/*) + + foreach(FILE ${WOWNEROD_FILES}) + cmake_path(GET FILE FILENAME FILE_REL) + list(APPEND QRC_LIST " assets/wownerod/${FILE_REL}") + + if (FILE_REL STREQUAL "wownerod" OR FILE_REL STREQUAL "wownerod.exe") + set(WOWNEROD_BIN_FOUND 1) + endif() + endforeach() + + if (NOT WOWNEROD_BIN_FOUND) + message(FATAL_ERROR "WOWNEROD_DIR was specified but the wownerod binary could not be found") + endif() +endif() + +list(JOIN QRC_LIST "\n" QRC_DATA) +configure_file("cmake/assets_wownerod.qrc" "${CMAKE_CURRENT_SOURCE_DIR}/src/assets_wownerod.qrc") diff --git a/cmake/assets_wownerod.qrc b/cmake/assets_wownerod.qrc new file mode 100644 index 00000000..f5363d50 --- /dev/null +++ b/cmake/assets_wownerod.qrc @@ -0,0 +1,5 @@ + + +@QRC_DATA@ + + diff --git a/src/CMakeLists.txt b/src/CMakeLists.txt index 1ad529ca..eca618a0 100644 --- a/src/CMakeLists.txt +++ b/src/CMakeLists.txt @@ -31,7 +31,7 @@ if (CHECK_UPDATES) add_subdirectory(openpgp) endif() -qt_add_resources(RESOURCES assets.qrc assets_tor.qrc assets_docs.qrc) +qt_add_resources(RESOURCES assets.qrc assets_tor.qrc assets_wownerod.qrc assets_docs.qrc) # Compile source files (.h/.cpp) file(GLOB SOURCE_FILES @@ -213,6 +213,10 @@ if(TOR_DIR) target_compile_definitions(feather PRIVATE HAS_TOR_BIN=1) endif() +if(WOWNEROD_DIR) + target_compile_definitions(feather PRIVATE HAS_WOWNEROD_BIN=1) +endif() + if(WITH_SCANNER) target_compile_definitions(feather PRIVATE WITH_SCANNER=1) endif() diff --git a/src/main.cpp b/src/main.cpp index f19ec565..c2302d0e 100644 --- a/src/main.cpp +++ b/src/main.cpp @@ -71,6 +71,10 @@ int main(int argc, char *argv[]) Q_INIT_RESOURCE(assets_tor); #endif +#if defined(HAS_WOWNEROD_BIN) + Q_INIT_RESOURCE(assets_wownerod); +#endif + #ifdef _WIN32 if (AttachConsole(ATTACH_PARENT_PROCESS)) { freopen("CONOUT$", "w", stdout); diff --git a/src/utils/DaemonManager.cpp b/src/utils/DaemonManager.cpp index 603adf1f..ae29c025 100644 --- a/src/utils/DaemonManager.cpp +++ b/src/utils/DaemonManager.cpp @@ -16,16 +16,8 @@ DaemonManager::DaemonManager(QObject *parent) : QObject(parent) , m_process(new QProcess(this)) { + // The embedded wownerod is unpacked here (writable, outside any read-only app bundle). this->daemonDir = Config::defaultConfigDir().filePath("node"); -#if defined(Q_OS_MACOS) - // On macOS the binary ships next to the app inside the bundle. - { - QDir appBinaryDir(QCoreApplication::applicationDirPath()); - appBinaryDir.cd(".."); - this->daemonDir = appBinaryDir.filePath("bin"); - } -#endif - this->blockchainDir = Config::defaultConfigDir().filePath("node/blockchain"); m_process->setProcessChannelMode(QProcess::MergedChannels); @@ -157,12 +149,23 @@ bool DaemonManager::unpackBins() { daemonBin += ".exe"; #endif - this->daemonPath = QDir(this->daemonDir).filePath(daemonBin); + // 1. Explicit path the user configured (or already-running node they point us at). + QString configured = conf()->get(Config::wownerodPath).toString(); + if (!configured.isEmpty() && QFileInfo(configured).isFile()) { + this->daemonPath = configured; + return true; + } -#if defined(Q_OS_MACOS) - // Shipped next to the app bundle; nothing to unpack. - return QFileInfo(this->daemonPath).isFile(); -#endif + // 2. Shipped next to the wowlet executable — the packaging default (monero-GUI ships monerod + // the same way). Avoids bloating the app binary by embedding a ~15 MB daemon in resources. + QString sibling = QDir(QCoreApplication::applicationDirPath()).filePath(daemonBin); + if (QFileInfo(sibling).isFile()) { + this->daemonPath = sibling; + return true; + } + + // 3. Unpack the embedded copy into the (writable) data dir — the single-file default, all platforms. + this->daemonPath = QDir(this->daemonDir).filePath(daemonBin); if (m_unpacked) return true; diff --git a/src/utils/config.cpp b/src/utils/config.cpp index ca2188bb..e7d5f33e 100644 --- a/src/utils/config.cpp +++ b/src/utils/config.cpp @@ -67,6 +67,7 @@ static const QHash configStrings = { {Config::skin,{QS("skin"), "Wownero"}}, {Config::runLocalNode,{QS("runLocalNode"), true}}, // wowlet: run own node by default {Config::pruneBlockchain,{QS("pruneBlockchain"), false}}, // full archival by default + {Config::wownerodPath,{QS("wownerodPath"), ""}}, // optional override; bundled by default {Config::amountPrecision, {QS("amountPrecision"), 12}}, {Config::dateFormat, {QS("dateFormat"), "yyyy-MM-dd"}}, {Config::timeFormat, {QS("timeFormat"), "HH:mm"}}, diff --git a/src/utils/config.h b/src/utils/config.h index 82ad94d4..191dbd73 100644 --- a/src/utils/config.h +++ b/src/utils/config.h @@ -75,6 +75,7 @@ public: // Embedded node (wowlet) runLocalNode, pruneBlockchain, + wownerodPath, // Network -> Proxy proxy,