Skip to content

build: headless Linux node that does not need libGL - #37

Merged
Bitflash-sh merged 1 commit into
mainfrom
feat/headless-linux
Jul 29, 2026
Merged

build: headless Linux node that does not need libGL#37
Bitflash-sh merged 1 commit into
mainfrom
feat/headless-linux

Conversation

@Bitflash-sh

Copy link
Copy Markdown
Owner

The problem

The GUI build requires libGL at load time even with -nogui. The dynamic loader resolves every dependency before main() runs, so the flag arrives far too late:

error while loading shared libraries: libGL.so.1: cannot open shared object file

Nothing in that message suggests the program would otherwise have worked fine. A clean Linux server is where a node most naturally lives — so this was the deployment that mattered most and the one that failed. The AppImage also needs FUSE, which minimal servers do not have.

Found while setting up the bootstrap seed from #36: a fresh VPS could not run our own release.

Why not just bundle libGL

Because GL has to come from the host's driver stack. Shipping our own would fix the headless server and break every machine with a real GPU. It trades a loud failure for a quiet one.

The fix

A second binary from the same node objects, without ImGui, GLFW or OpenGL.

make linux-node produces bitflash-node-<version>-x86_642.5 MB stripped, no graphics or FUSE dependency. make linux now builds it beside 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. headless.cpp provides an empty one, rather than #ifdef 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 now share one definition.

Verified

ldd bitflash-node | grep -iE 'libGL|glfw|X11'   -> nothing
ldd bitflash      | grep -iE 'libGL|glfw'       -> libglfw.so.3, libGL.so.1, libGLdispatch.so.0

Runtime, empty datadir, Debian 13:

[net]   btfseed: reached ygffz37...btf via 90.156.222.107:8434
[net]   rendezvous: registered at 90.156.222.107:8434, waiting for a dial
[nostr] self-resolve OK (meeting_node=90.156.222.107:8434)
[net]   btfpeers: kept 15 of 15 descriptor(s)

First block at ~30s, 3182 blocks accepted, synced to the tip.

GUI builds on Linux and Windows rebuilt and unaffected.

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.
@Bitflash-sh
Bitflash-sh merged commit 7384f05 into main Jul 29, 2026
@Bitflash-sh Bitflash-sh mentioned this pull request Jul 29, 2026
Bitflash-sh added a commit that referenced this pull request Jul 29, 2026
Seeds (#36), headless node (#37), README (#35), wallet crash fix (#38).
Vic-Nas added a commit to Vic-Nas/bitflash-mod that referenced this pull request Jul 30, 2026
…sh#37); fix /gen not actually mining (port of Bitflash-sh#42)

Two real gaps, both pulled from upstream, both squarely in "the
command line has been neglected" territory.

## Headless build (Bitflash-sh#37)

The GUI binary needs libGL present at load time even when started
with -nogui/-daemon: the dynamic loader resolves every dependency
before main() runs, so the flag arrives far too late. A clean server
-- where a node most naturally lives -- fails outright with a shared-
library error that gives no hint the program would otherwise work
fine. The AppImage additionally needs FUSE, which minimal servers
lack.

`make linux-node-build` (wired into both `linux` and `linux-self`)
produces a second binary, `$(APPNAME)-node-<version>-x86_64`, from
the same node objects, without ImGui/GLFW/OpenGL at all -- no
graphics or FUSE dependency. Two small moves were needed: headless.cpp
provides an empty MainFrameRepaint() (declared in headers_core.h,
previously only ever defined in gui.cpp, which a headless build
doesn't compile), and DateTimeStr moved from gui.cpp to util.cpp
since it's not actually GUI-specific.

Adapted rather than copied: our src/Makefile carries extra objects
(nostr/compactblock/update) and the mandatory REPO/UPDATE_PUBKEY (or
NO_UPDATE=1) machinery upstream doesn't have. Caught one real bug
while wiring the root Makefile's linux-node-build target: forwarding
NO_UPDATE unconditionally (even as an empty value) would have made
`ifdef NO_UPDATE` true in the child invocation regardless -- GNU
Make's ifdef checks whether a variable was set at all, not whether
it's non-empty -- silently disabling the updater in every node build,
including regular (non-self) ones. Branched explicitly instead.

## /gen not mining (Bitflash-sh#42)

nMineMode defaults to MINE_RELAY and nothing in ParseStartupArguments
ever changed it for the plain /gen case (only /operator and
/participant set their own mode) -- so a node started with /gen alone
spawned ThreadBitcoinMiner, hit BitcoinMiner()'s relay guard, and
returned immediately. The node ran indefinitely looking completely
healthy -- synced, held peers, no errors -- while mining nothing, with
the only hint being nMineMode = 0 in a startup line that reads like a
status echo. This affects every headless miner, since the GUI was the
only other way to select a mining mode. /gen now implies MINE_SOLO
unless /operator or /participant asked for something else.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant