diff options
| author | Bitflash <btf01@proton.me> | 2026-07-29 16:33:02 -0300 |
|---|---|---|
| committer | GitHub <noreply@github.com> | 2026-07-29 16:33:02 -0300 |
| commit | c404e1c7f5d3f04d07e7e5b35a7a8ffcc998d855 (patch) | |
| tree | 314935e341d2e6fe0d6cd927e01efbb87b74df36 /src | |
| parent | ab24958f1470cb62a4637126345fdf14c2ec0bd4 (diff) | |
build: headless Linux node that does not need libGL (#37)
The GUI build requires libGL at load time even when started with -nogui. The
dynamic loader resolves every dependency before main() runs, so the flag arrives
far too late -- on a clean server the binary dies with
error while loading shared libraries: libGL.so.1: cannot open shared object file
and nothing suggests it would otherwise have worked. A server is where a node
most naturally lives, so this is the deployment that mattered most and the one
that failed. The AppImage additionally needs FUSE, which minimal servers lack.
Bundling libGL into the AppImage would be the wrong fix: GL has to come from the
host's driver stack, and shipping our own breaks machines that have a real GPU.
So: a second binary, built from the same node objects without ImGui, GLFW or
OpenGL. `make linux-node` produces bitflash-node-<version>-x86_64, 2.5 MB
stripped, with no graphics or FUSE dependency at all. `make linux` now builds it
alongside the AppImage. Anyone wanting a window keeps using the AppImage.
Two small moves were needed. MainFrameRepaint is called from main.cpp and
net.cpp but defined in gui.cpp, so headless.cpp provides an empty one rather
than putting ifdefs at six call sites. DateTimeStr also lived in gui.cpp while
main.cpp logs block times with it -- nothing about formatting a date is GUI
work, so it moved to util.cpp and both builds share one definition.
Verified on Debian 13:
ldd bitflash-node | grep -iE 'libGL|glfw|X11' -> nothing
ldd bitflash | grep -iE 'libGL|glfw' -> libglfw.so.3, libGL.so.1, libGLdispatch.so.0
and at runtime, from an empty datadir:
btfseed: reached ygffz37...btf via 90.156.222.107:8434
rendezvous: registered at 90.156.222.107:8434, waiting for a dial
self-resolve OK (meeting_node=90.156.222.107:8434)
btfpeers: kept 15 of 15 descriptor(s)
first block at ~30s, 3182 blocks accepted, synced to the tip
The GUI builds on Linux and Windows were rebuilt and are unaffected.
Diffstat (limited to 'src')
| -rw-r--r-- | src/Makefile | 32 | ||||
| -rw-r--r-- | src/gui.cpp | 10 | ||||
| -rw-r--r-- | src/headless.cpp | 20 | ||||
| -rw-r--r-- | src/main_gui.cpp | 13 | ||||
| -rw-r--r-- | src/util.cpp | 12 |
5 files changed, 78 insertions, 9 deletions
diff --git a/src/Makefile b/src/Makefile index c0b6e1d..9846d8e 100644 --- a/src/Makefile +++ b/src/Makefile | |||
| @@ -71,5 +71,35 @@ bitflash: $(IMGUI_OBJS) $(NODE_OBJS) $(GUI_OBJS) | |||
| 71 | $(CXX) $(CXXFLAGS) -o $@ $^ $(LIBS) | 71 | $(CXX) $(CXXFLAGS) -o $@ $^ $(LIBS) |
| 72 | @echo "Built: bitflash" | 72 | @echo "Built: bitflash" |
| 73 | 73 | ||
| 74 | # ---- headless node --------------------------------------------------------- | ||
| 75 | # | ||
| 76 | # Same node, built without ImGui, GLFW or OpenGL. Linking the GUI made the | ||
| 77 | # binary require libGL at load time even when started with -nogui, because the | ||
| 78 | # dynamic loader resolves everything before main() runs -- so on a clean server, | ||
| 79 | # which is where a node most naturally lives, it failed to start with an error | ||
| 80 | # about shared libraries and no hint that it would otherwise work. | ||
| 81 | # | ||
| 82 | # Objects go to obj-node/ because main_gui.cpp is compiled twice, with and | ||
| 83 | # without -DBITFLASH_NO_GUI, and the two must not overwrite each other. | ||
| 84 | |||
| 85 | NODE_LIBS = -L$(SECP_LIB) -lsecp256k1 \ | ||
| 86 | -L$(RANDOMX_DIR)/build -lrandomx \ | ||
| 87 | -lsodium -lssl -lcrypto -ldb_cxx -lpthread | ||
| 88 | |||
| 89 | NODE_ONLY_OBJS = $(patsubst obj/%,obj-node/%,$(NODE_OBJS)) \ | ||
| 90 | obj-node/main_gui.o obj-node/headless.o | ||
| 91 | |||
| 92 | obj-node/%.o: %.cpp $(HEADERS) | ||
| 93 | @mkdir -p obj-node | ||
| 94 | $(CXX) -c $(CXXFLAGS) -DBITFLASH_NO_GUI -o $@ $< | ||
| 95 | |||
| 96 | obj-node/sha.o: sha.cpp sha.h | ||
| 97 | @mkdir -p obj-node | ||
| 98 | $(CXX) -c $(CXXFLAGS) -DBITFLASH_NO_GUI -O3 -o $@ $< | ||
| 99 | |||
| 100 | bitflash-node: $(NODE_ONLY_OBJS) | ||
| 101 | $(CXX) $(CXXFLAGS) -o $@ $^ $(NODE_LIBS) | ||
| 102 | @echo "Built: bitflash-node (headless, no libGL needed)" | ||
| 103 | |||
| 74 | clean: | 104 | clean: |
| 75 | rm -rf obj bitflash | 105 | rm -rf obj obj-node bitflash bitflash-node |
diff --git a/src/gui.cpp b/src/gui.cpp index a1a0698..fc6f1e1 100644 --- a/src/gui.cpp +++ b/src/gui.cpp | |||
| @@ -171,14 +171,8 @@ static void RefreshWallet() | |||
| 171 | 171 | ||
| 172 | void MainFrameRepaint() { g_needRefresh = true; } | 172 | void MainFrameRepaint() { g_needRefresh = true; } |
| 173 | 173 | ||
| 174 | string DateTimeStr(int64 nTime) | 174 | // DateTimeStr moved to util.cpp: main.cpp logs block times with it, so it is |
| 175 | { | 175 | // needed by builds that have no GUI at all. |
| 176 | time_t t = (time_t)nTime; | ||
| 177 | struct tm* p = localtime(&t); | ||
| 178 | if (!p) return ""; | ||
| 179 | char buf[32]; strftime(buf, sizeof(buf), "%m/%d/%y %H:%M", p); | ||
| 180 | return buf; | ||
| 181 | } | ||
| 182 | 176 | ||
| 183 | // --------------------------------------------------------------------------- | 177 | // --------------------------------------------------------------------------- |
| 184 | // Status bar -- separate fullscreen-width overlay pinned to bottom edge. | 178 | // Status bar -- separate fullscreen-width overlay pinned to bottom edge. |
diff --git a/src/headless.cpp b/src/headless.cpp new file mode 100644 index 0000000..3372936 --- /dev/null +++ b/src/headless.cpp | |||
| @@ -0,0 +1,20 @@ | |||
| 1 | // Copyright (c) 2026 Bitflash developers | ||
| 2 | // Distributed under the MIT/X11 software license. | ||
| 3 | // | ||
| 4 | // Stand-ins for the handful of symbols the node core expects the GUI to | ||
| 5 | // provide, so a headless binary can be linked without ImGui, GLFW or OpenGL. | ||
| 6 | // | ||
| 7 | // This exists because linking the GUI made the node need libGL at load time | ||
| 8 | // even when started with -nogui: the dynamic loader resolves everything before | ||
| 9 | // main() runs, so the flag came far too late. On a clean server -- which is | ||
| 10 | // where a node most naturally lives -- that is a hard failure with an error | ||
| 11 | // message about shared libraries and no hint that the program would otherwise | ||
| 12 | // have worked fine. | ||
| 13 | |||
| 14 | #include "headers_core.h" | ||
| 15 | |||
| 16 | // The GUI uses this to mark itself dirty. With no window to repaint there is | ||
| 17 | // nothing to do, and the call sites in main.cpp and net.cpp stay unchanged. | ||
| 18 | void MainFrameRepaint() | ||
| 19 | { | ||
| 20 | } | ||
diff --git a/src/main_gui.cpp b/src/main_gui.cpp index 9f7e34e..99e63d4 100644 --- a/src/main_gui.cpp +++ b/src/main_gui.cpp | |||
| @@ -5,7 +5,11 @@ | |||
| 5 | #include <csignal> | 5 | #include <csignal> |
| 6 | #endif | 6 | #endif |
| 7 | 7 | ||
| 8 | // BITFLASH_NO_GUI builds the same entry point without ImGui, GLFW or OpenGL, | ||
| 9 | // so the binary does not need libGL present just to start. See headless.cpp. | ||
| 10 | #ifndef BITFLASH_NO_GUI | ||
| 8 | int RunGUI(int argc, char* argv[]); | 11 | int RunGUI(int argc, char* argv[]); |
| 12 | #endif | ||
| 9 | 13 | ||
| 10 | // Global definitions (were in ui.cpp, now here) | 14 | // Global definitions (were in ui.cpp, now here) |
| 11 | map<string,string> mapAddressBook; | 15 | map<string,string> mapAddressBook; |
| @@ -186,8 +190,13 @@ int main(int argc, char* argv[]) | |||
| 186 | if (_beginthread(ThreadBitcoinMiner, 0, NULL) == (uintptr_t)-1) | 190 | if (_beginthread(ThreadBitcoinMiner, 0, NULL) == (uintptr_t)-1) |
| 187 | printf("Error: _beginthread(ThreadBitcoinMiner) failed\n"); | 191 | printf("Error: _beginthread(ThreadBitcoinMiner) failed\n"); |
| 188 | 192 | ||
| 193 | #ifdef BITFLASH_NO_GUI | ||
| 194 | // Nothing else this binary can do; /nogui is accepted and redundant. | ||
| 195 | bool fHeadless = true; | ||
| 196 | #else | ||
| 189 | bool fHeadless = arg(argc,argv,"/nogui") || arg(argc,argv,"-nogui") || | 197 | bool fHeadless = arg(argc,argv,"/nogui") || arg(argc,argv,"-nogui") || |
| 190 | arg(argc,argv,"/daemon") || arg(argc,argv,"-daemon"); | 198 | arg(argc,argv,"/daemon") || arg(argc,argv,"-daemon"); |
| 199 | #endif | ||
| 191 | 200 | ||
| 192 | if (fHeadless) { | 201 | if (fHeadless) { |
| 193 | #ifndef _WIN32 | 202 | #ifndef _WIN32 |
| @@ -200,8 +209,12 @@ int main(int argc, char* argv[]) | |||
| 200 | return 0; | 209 | return 0; |
| 201 | } | 210 | } |
| 202 | 211 | ||
| 212 | #ifdef BITFLASH_NO_GUI | ||
| 213 | return 0; // unreachable: fHeadless is always true in this build | ||
| 214 | #else | ||
| 203 | int ret = RunGUI(argc, argv); | 215 | int ret = RunGUI(argc, argv); |
| 204 | fShutdown = true; | 216 | fShutdown = true; |
| 205 | StopNode(); | 217 | StopNode(); |
| 206 | return ret; | 218 | return ret; |
| 219 | #endif | ||
| 207 | } | 220 | } |
diff --git a/src/util.cpp b/src/util.cpp index 68bd18e..e4e314f 100644 --- a/src/util.cpp +++ b/src/util.cpp | |||
| @@ -490,3 +490,15 @@ void AddTimeData(unsigned int ip, int64 nTime) | |||
| 490 | printf("| nTimeOffset = %+lld (%+lld minutes)\n", nTimeOffset, nTimeOffset/60); | 490 | printf("| nTimeOffset = %+lld (%+lld minutes)\n", nTimeOffset, nTimeOffset/60); |
| 491 | } | 491 | } |
| 492 | } | 492 | } |
| 493 | |||
| 494 | // Formats a block or transaction timestamp for logs and for the wallet view. | ||
| 495 | // It lived in gui.cpp, but main.cpp logs block times with it, so a headless | ||
| 496 | // build needed it too -- and nothing about formatting a date is GUI work. | ||
| 497 | string DateTimeStr(int64 nTime) | ||
| 498 | { | ||
| 499 | time_t t = (time_t)nTime; | ||
| 500 | struct tm* p = localtime(&t); | ||
| 501 | if (!p) return ""; | ||
| 502 | char buf[32]; strftime(buf, sizeof(buf), "%m/%d/%y %H:%M", p); | ||
| 503 | return buf; | ||
| 504 | } | ||