wowlet: embed wownerod (WOWNEROD_DIR, mirrors Feather's Tor embedding) — single self-contained binary; DaemonManager unpacks it

This commit is contained in:
jwinterm
2026-06-18 11:16:13 -04:00
parent d03fe4994d
commit 3e9743a9c4
8 changed files with 79 additions and 15 deletions
+22
View File
@@ -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}")
+24
View File
@@ -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 " <file>assets/wownerod/${FILE_REL}</file>")
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")
+5
View File
@@ -0,0 +1,5 @@
<!DOCTYPE RCC><RCC version="1.0">
<qresource prefix="/">
@QRC_DATA@
</qresource>
</RCC>
+5 -1
View File
@@ -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()
+4
View File
@@ -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);
+17 -14
View File
@@ -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;
+1
View File
@@ -67,6 +67,7 @@ static const QHash<Config::ConfigKey, ConfigDirective> 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"}},
+1
View File
@@ -75,6 +75,7 @@ public:
// Embedded node (wowlet)
runLocalNode,
pruneBlockchain,
wownerodPath,
// Network -> Proxy
proxy,