From 0304717e4dbb8c66f56292f545f6aff696553bb7 Mon Sep 17 00:00:00 2001 From: einliterflasche Date: Fri, 8 May 2026 12:16:51 +0200 Subject: [PATCH 1/6] build: setup nix dependencies --- .envrc | 1 + .gitignore | 2 + flake.nix | 15 +++++++ shell.nix | 120 +++++++++++++++++++++++++++++++++++++++++++++++++++++ 4 files changed, 138 insertions(+) create mode 100644 .envrc create mode 100644 flake.nix create mode 100644 shell.nix diff --git a/.envrc b/.envrc new file mode 100644 index 0000000000..1d953f4bd7 --- /dev/null +++ b/.envrc @@ -0,0 +1 @@ +use nix diff --git a/.gitignore b/.gitignore index d499b9c3f3..fb47aac4d9 100644 --- a/.gitignore +++ b/.gitignore @@ -24,3 +24,5 @@ libp2p-rendezvous-node/rendezvous-data rust-electrum-client/ rust-libp2p/ bdk/ + +.direnv/ diff --git a/flake.nix b/flake.nix new file mode 100644 index 0000000000..65055b1a0e --- /dev/null +++ b/flake.nix @@ -0,0 +1,15 @@ +{ + description = "xmr-btc-swap / eigenwallet build environment"; + + inputs = { + nixpkgs.url = "github:NixOS/nixpkgs/nixos-25.05"; + flake-utils.url = "github:numtide/flake-utils"; + }; + + outputs = { self, nixpkgs, flake-utils }: + flake-utils.lib.eachDefaultSystem (system: + let pkgs = import nixpkgs { inherit system; }; + in { + devShells.default = import ./shell.nix { inherit pkgs; }; + }); +} diff --git a/shell.nix b/shell.nix new file mode 100644 index 0000000000..c16e3c3f8f --- /dev/null +++ b/shell.nix @@ -0,0 +1,120 @@ +{ pkgs ? import (builtins.fetchTarball { + url = "https://github.com/NixOS/nixpkgs/archive/refs/heads/nixos-25.05.tar.gz"; + }) { } +}: + +let + # monero-depends/hosts/linux.mk hardcodes the Debian-style triple + # `x86_64-linux-gnu-` when the build host is x86. Nixpkgs ships + # those tools under `x86_64-unknown-linux-gnu-*` (or unprefixed), so + # without these wrappers `configure` reports "C compiler cannot create + # executables" even though the toolchain is fine. + prefixWrapper = from: tool: pkgs.writeShellScriptBin "x86_64-linux-gnu-${tool}" + ''exec ${from}/bin/${tool} "$@"''; + + moneroDependsToolchain = pkgs.symlinkJoin { + name = "monero-depends-toolchain"; + paths = + map (prefixWrapper pkgs.gcc) [ "gcc" "g++" "cpp" "cc" ] + ++ map (prefixWrapper pkgs.binutils) + [ "ar" "ranlib" "nm" "strip" "ld" "as" "objcopy" "objdump" "readelf" ]; + }; + + # Link-time deps for src-tauri and upstream Rust crates. These are found + # via pkg-config + nix-managed RPATH; do NOT add them to LD_LIBRARY_PATH + # because that overrides RPATH and can shadow binaries built against a + # newer openssl (e.g. curl's ngtcp2 module). + tauriLinkLibs = with pkgs; [ + glib + gtk3 + gdk-pixbuf + cairo + pango + atkmm + at-spi2-atk + libsoup_3 + webkitgtk_4_1 + librsvg + libayatana-appindicator + openssl + zlib + ]; + + # Subset that WebKitGTK / GTK dlopen at runtime (tray icon, SVG pixbuf + # loaders, webview plugins). These must be reachable via LD_LIBRARY_PATH. + tauriRuntimeLibs = with pkgs; [ + webkitgtk_4_1 + libsoup_3 + librsvg + libayatana-appindicator + gdk-pixbuf + ]; +in +pkgs.mkShell { + # Tools invoked during the build (compilers, codegen, fetchers). + # Mirrors DEPS_BUILD_LINUX from .github/actions/set-monero-env/action.yml, + # minus cross-compilation bits (mingw-w64, nsis) that we don't need here. + nativeBuildInputs = (with pkgs; [ + # C/C++ toolchain for monero-sys (CMake + monero-depends/make) + gcc + gnumake + cmake + autoconf + automake + libtool + pkg-config + binutils + ccache + gperf + lbzip2 + curl + git + python3 + + # Node for src-gui. The project pins `yarn@4.x` via `packageManager` in + # src-gui/package.json, so we let corepack (bundled with nodejs) fetch + # that exact version instead of using nixpkgs' yarn 1.x. + nodejs_22 + + # Cargo workspace helpers used by `just` recipes and the CI workflow. + just + typeshare + dprint + sqlx-cli + # `just tauri` runs `cargo tauri dev`, which needs the Rust tauri CLI + # on PATH (provides the `cargo-tauri` subcommand shim). The GitHub CI + # uses the yarn-based `@tauri-apps/cli` instead, so it doesn't need this. + cargo-tauri + ]) ++ [ moneroDependsToolchain ]; + + # Native libraries linked by src-tauri and its webkit-based webview. + buildInputs = tauriLinkLibs; + + # monero-sys build.rs picks up $CC/$CXX; be explicit so we don't accidentally + # pull in a host toolchain that isn't in $PATH under nix-shell. + CC = "${pkgs.gcc}/bin/gcc"; + CXX = "${pkgs.gcc}/bin/g++"; + + # WebKitGTK dlopens some of its plugins at runtime, so `tauri dev` + # needs them reachable via LD_LIBRARY_PATH in addition to being linked. + LD_LIBRARY_PATH = pkgs.lib.makeLibraryPath tauriRuntimeLibs; + + shellHook = '' + # Rustup-managed toolchain lives in ~/.cargo/bin; nix-shell resets PATH + # for its own deps, so re-prepend it. rust-toolchain.toml pins the version. + export PATH="$HOME/.cargo/bin:$PATH" + + # Let corepack materialise the project-pinned yarn version into a user- + # writable dir (nodejs bin in /nix/store is read-only, so the default + # `corepack enable` location fails). + export COREPACK_HOME="$HOME/.cache/corepack" + export COREPACK_ENABLE_DOWNLOAD_PROMPT=0 + corepack_bin="$HOME/.cache/corepack/bin" + mkdir -p "$corepack_bin" + ${pkgs.nodejs_22}/bin/corepack enable --install-directory "$corepack_bin" + export PATH="$corepack_bin:$PATH" + + # GSettings/GIO schemas for GTK apps (otherwise Tauri dialogs warn/crash). + export XDG_DATA_DIRS="${pkgs.gsettings-desktop-schemas}/share/gsettings-schemas/${pkgs.gsettings-desktop-schemas.name}:${pkgs.gtk3}/share/gsettings-schemas/${pkgs.gtk3.name}:$XDG_DATA_DIRS" + ''; +} From 646729f317aa67aa75c833745fd96fa339445552 Mon Sep 17 00:00:00 2001 From: einliterflasche Date: Fri, 8 May 2026 13:46:29 +0200 Subject: [PATCH 2/6] build(nix-shell): fix tauri dev whitescreen and missing build-script libs MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit `just tauri-mainnet` couldn't get past the swap build script (libz.so.1 not found) and, once that was bypassed, the tauri webview rendered as a blank whitescreen with `EGL_BAD_PARAMETER. Aborting...` from WebKit. Two underlying issues: - cc-wrapper's add-flags.sh prepends `-rpath $out/lib` to NIX_LDFLAGS, but in nix-shell `$out` is a phantom path that never exists. Every binary cargo links here ends up with a dead RUNPATH, so dynamic deps (libgit2-sys → libz, webkitgtk → libgdk/libgtk/libstdc++) can't be resolved at runtime. Rewrite the rpath in shellHook to point at the link-time inputs. - WebKitGTK uses libglvnd for EGL/GLX dispatch, but nixpkgs' libglvnd has no vendor ICD installed. On NixOS the system populates `/run/opengl-driver/lib`; on non-NixOS hosts there's nothing to dispatch to. Point libglvnd at nixpkgs' mesa (which ships libEGL_mesa.so.0 + swrast_dri.so + the matching egl_vendor.d JSON) and force LIBGL_ALWAYS_SOFTWARE so we don't try to talk to the host NVIDIA/Mesa drivers (whose ABIs clash with the nix-built lib stack). This unblocks `cargo tauri dev` end-to-end with the CPU-side renderer. GPU-accelerated rendering on non-NixOS hosts (nixGL or equivalent) will follow in a separate change. Co-Authored-By: Claude Opus 4.7 (1M context) --- shell.nix | 58 +++++++++++++++++++++++++++++++++++++++++++++++++------ 1 file changed, 52 insertions(+), 6 deletions(-) diff --git a/shell.nix b/shell.nix index c16e3c3f8f..6ab7e757ed 100644 --- a/shell.nix +++ b/shell.nix @@ -21,10 +21,14 @@ let }; # Link-time deps for src-tauri and upstream Rust crates. These are found - # via pkg-config + nix-managed RPATH; do NOT add them to LD_LIBRARY_PATH - # because that overrides RPATH and can shadow binaries built against a - # newer openssl (e.g. curl's ngtcp2 module). - tauriLinkLibs = with pkgs; [ + # via pkg-config and the RPATH baked in by NIX_LDFLAGS (rewritten in + # shellHook below — see the comment there for why); we deliberately keep + # them off LD_LIBRARY_PATH so they can't shadow nix-built tools like curl + # whose ngtcp2 module is pinned to a specific openssl ABI. + # + # `stdenv.cc.cc.lib` is included so RUNPATH covers libstdc++.so.6, which + # the tauri binary pulls in via the C++ webkitgtk/cxx bindings. + tauriLinkLibs = (with pkgs; [ glib gtk3 gdk-pixbuf @@ -38,16 +42,28 @@ let libayatana-appindicator openssl zlib - ]; + ]) ++ [ pkgs.stdenv.cc.cc.lib ]; # Subset that WebKitGTK / GTK dlopen at runtime (tray icon, SVG pixbuf - # loaders, webview plugins). These must be reachable via LD_LIBRARY_PATH. + # loaders, webview plugins). dlopen of a bare soname only consults + # LD_LIBRARY_PATH and the system cache, not the calling binary's RUNPATH, + # so these have to be on LD_LIBRARY_PATH even though they're also linked. + # + # zlib is here as a safety net for cargo build-script binaries (e.g. + # the vergen-git2 -> libgit2-sys path used by swap, swap-asb, + # swap-orchestrator). The rpath rewrite in shellHook fixes their + # RUNPATH for fresh links, but build-script binaries linked before + # the fix went in still carry a dead RUNPATH and would fail to + # resolve libz when cargo re-runs them after a `rerun-if-changed` + # trigger (e.g. a new git commit). Keeping libz on LD_LIBRARY_PATH + # avoids forcing users to `cargo clean` after picking up shell.nix. tauriRuntimeLibs = with pkgs; [ webkitgtk_4_1 libsoup_3 librsvg libayatana-appindicator gdk-pixbuf + zlib ]; in pkgs.mkShell { @@ -99,7 +115,37 @@ pkgs.mkShell { # needs them reachable via LD_LIBRARY_PATH in addition to being linked. LD_LIBRARY_PATH = pkgs.lib.makeLibraryPath tauriRuntimeLibs; + # WebKitGTK uses libglvnd (from nixpkgs) for EGL/GLX dispatch, but the + # nix-shipped libglvnd has no vendor ICD installed by default. On NixOS + # the system populates `/run/opengl-driver/lib`; on a non-NixOS host + # there's nothing for libglvnd to dispatch to, so `eglGetDisplay` returns + # EGL_NO_DISPLAY, WebKit prints `Could not create default EGL display: + # EGL_BAD_PARAMETER. Aborting...`, and the WebProcess crashes — leaving + # the tauri window as a blank whitescreen. Point libglvnd at nixpkgs' + # mesa (which ships `libEGL_mesa.so.0`, `swrast_dri.so` and the matching + # `egl_vendor.d` JSON) and force software rendering so we don't try to + # talk to the host's NVIDIA/Mesa drivers (whose ABIs would clash with + # the nix-built lib stack). + WEBKIT_DISABLE_DMABUF_RENDERER = "1"; + LIBGL_ALWAYS_SOFTWARE = "1"; + __EGL_VENDOR_LIBRARY_DIRS = "${pkgs.mesa}/share/glvnd/egl_vendor.d"; + LIBGL_DRIVERS_PATH = "${pkgs.mesa}/lib/dri"; + shellHook = '' + # cc-wrapper's add-flags.sh prepends `-rpath $out/lib` to NIX_LDFLAGS so + # mkDerivation builds get a working RUNPATH at install time. In a + # nix-shell, however, $out resolves to /outputs/out — a path that + # never exists — so every binary cargo links here (build scripts and + # the tauri app itself) ends up with a dead RUNPATH and can't load its + # dynamic deps at runtime. Rewrite the bogus rpath to point at the + # link-time inputs so RUNPATH actually resolves and we don't have to + # leak everything onto LD_LIBRARY_PATH. + if [ -n "$out" ] && [[ "$NIX_LDFLAGS" == *"-rpath $out/lib"* ]]; then + _rpath_real=${pkgs.lib.makeLibraryPath tauriLinkLibs} + export NIX_LDFLAGS="''${NIX_LDFLAGS//-rpath $out\/lib/-rpath $_rpath_real}" + unset _rpath_real + fi + # Rustup-managed toolchain lives in ~/.cargo/bin; nix-shell resets PATH # for its own deps, so re-prepend it. rust-toolchain.toml pins the version. export PATH="$HOME/.cargo/bin:$PATH" From 1f115d79fb349c6a81eec24328c6e0b30faa3b90 Mon Sep 17 00:00:00 2001 From: einliterflasche Date: Fri, 8 May 2026 13:53:13 +0200 Subject: [PATCH 3/6] build(nix-shell): use host NVIDIA driver for tauri webview when available MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Software rendering (the previous fix) is functional but caps the webview at a few fps on a modern GPU. Detect the proprietary NVIDIA driver on the host and surface its libnvidia-*/libEGL_nvidia/libGLX_nvidia files via a focused symlink directory on LD_LIBRARY_PATH so nix's libglvnd can dispatch into the host's driver and render on the GPU. We can't put /usr/lib64 on LD_LIBRARY_PATH directly — that would shadow nix's libstdc++/libc and segfault the process — so the symlink dir contains only the libnvidia/libEGL_nvidia/libGLX_nvidia files, and the rest of /usr/lib64 stays out of the search path. The NVIDIA libs themselves only depend on glibc + each other, and they pick up nix's glibc-2.40 cleanly. When NVIDIA isn't present we keep the nixpkgs mesa software rasteriser fallback so non-NVIDIA hosts still launch (just slower). Co-Authored-By: Claude Opus 4.7 (1M context) --- shell.nix | 54 ++++++++++++++++++++++++++++++++++++++++-------------- 1 file changed, 40 insertions(+), 14 deletions(-) diff --git a/shell.nix b/shell.nix index 6ab7e757ed..5374345b52 100644 --- a/shell.nix +++ b/shell.nix @@ -115,21 +115,13 @@ pkgs.mkShell { # needs them reachable via LD_LIBRARY_PATH in addition to being linked. LD_LIBRARY_PATH = pkgs.lib.makeLibraryPath tauriRuntimeLibs; - # WebKitGTK uses libglvnd (from nixpkgs) for EGL/GLX dispatch, but the - # nix-shipped libglvnd has no vendor ICD installed by default. On NixOS - # the system populates `/run/opengl-driver/lib`; on a non-NixOS host - # there's nothing for libglvnd to dispatch to, so `eglGetDisplay` returns - # EGL_NO_DISPLAY, WebKit prints `Could not create default EGL display: - # EGL_BAD_PARAMETER. Aborting...`, and the WebProcess crashes — leaving - # the tauri window as a blank whitescreen. Point libglvnd at nixpkgs' - # mesa (which ships `libEGL_mesa.so.0`, `swrast_dri.so` and the matching - # `egl_vendor.d` JSON) and force software rendering so we don't try to - # talk to the host's NVIDIA/Mesa drivers (whose ABIs would clash with - # the nix-built lib stack). + # WebKit's DMA-BUF renderer needs an EGL backend that can negotiate + # buffer sharing with the compositor; the host driver path below covers + # the EGL initialisation, but DMA-BUF still tends to fail on non-NixOS + # hosts (mismatch between the nix-built webkitgtk and the host's + # wayland/drm stack). Disable it so WebKit falls back to a glReadPixels- + # style upload that works with any GL backend. WEBKIT_DISABLE_DMABUF_RENDERER = "1"; - LIBGL_ALWAYS_SOFTWARE = "1"; - __EGL_VENDOR_LIBRARY_DIRS = "${pkgs.mesa}/share/glvnd/egl_vendor.d"; - LIBGL_DRIVERS_PATH = "${pkgs.mesa}/lib/dri"; shellHook = '' # cc-wrapper's add-flags.sh prepends `-rpath $out/lib` to NIX_LDFLAGS so @@ -146,6 +138,40 @@ pkgs.mkShell { unset _rpath_real fi + # WebKitGTK uses nixpkgs' libglvnd for EGL/GLX dispatch, but nixpkgs + # ships libglvnd without any vendor ICD installed — on NixOS the system + # populates /run/opengl-driver/lib, on a non-NixOS host there's nothing + # for libglvnd to dispatch to. Result: WebKit aborts with `Could not + # create default EGL display: EGL_BAD_PARAMETER` and the tauri webview + # renders as a blank whitescreen. + # + # If the host has the proprietary NVIDIA driver, surface its + # libnvidia-*/libEGL_nvidia/libGLX_nvidia files via a focused symlink + # dir on LD_LIBRARY_PATH (we can NOT put /usr/lib64 on LD_LIBRARY_PATH + # directly — that shadows nix's libstdc++/libc and segfaults the + # process). Combined with the host's /usr/share/glvnd/egl_vendor.d/ + # JSON, nix's libglvnd dispatches into the host driver and gets real + # GPU rendering. + # + # Otherwise fall back to nixpkgs' mesa software rasteriser. Slow but + # works without any host driver and without ABI mixing. + if [ -e /usr/lib64/libEGL_nvidia.so.0 ] && [ -d /usr/share/glvnd/egl_vendor.d ]; then + _nv_dir="''${XDG_RUNTIME_DIR:-/tmp}/eigenwallet-nvidia-libs" + rm -rf "$_nv_dir" + mkdir -p "$_nv_dir" + for _f in /usr/lib64/libnvidia-*.so.* /usr/lib64/libEGL_nvidia.so.* /usr/lib64/libGLX_nvidia.so.*; do + [ -e "$_f" ] && ln -s "$_f" "$_nv_dir/" + done + export LD_LIBRARY_PATH="''${LD_LIBRARY_PATH}:$_nv_dir" + export __EGL_VENDOR_LIBRARY_DIRS=/usr/share/glvnd/egl_vendor.d + export __GLX_VENDOR_LIBRARY_NAME=nvidia + unset _nv_dir _f + else + export __EGL_VENDOR_LIBRARY_DIRS=${pkgs.mesa}/share/glvnd/egl_vendor.d + export LIBGL_DRIVERS_PATH=${pkgs.mesa}/lib/dri + export LIBGL_ALWAYS_SOFTWARE=1 + fi + # Rustup-managed toolchain lives in ~/.cargo/bin; nix-shell resets PATH # for its own deps, so re-prepend it. rust-toolchain.toml pins the version. export PATH="$HOME/.cargo/bin:$PATH" From 2f802409d59f8544f95933d526c40b1a092f3df7 Mon Sep 17 00:00:00 2001 From: einliterflasche Date: Fri, 8 May 2026 14:38:11 +0200 Subject: [PATCH 4/6] build(nix-shell): use nixGL for NVIDIA GPU rendering on non-NixOS hosts MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The previous attempt symlinked the host's NVIDIA libs from /usr/lib64 into a focused dir on LD_LIBRARY_PATH. That got nix's libglvnd to load the libs, but glXChooseFBConfig still returned NULL ("No available configurations for the given pixel format"), GTK disabled hardware acceleration, and WebKit fell back to CPU rasterisation — capping the GUI at single-digit fps even though the NVIDIA libs were resident. nixGL builds nix-managed copies of the host's NVIDIA user-space driver (matching the running kernel module's version) and a wrapper that points libglvnd at them. Sourcing the wrapper's env-var setup into the nix-shell at entry gives the WebProcess a working EGL vendor without needing to wrap every cargo invocation. Combined with GDK_BACKEND=x11 (NVIDIA + native Wayland + nix's webkitgtk hits a Wayland EPROTO during DMA-BUF setup; XWayland sidesteps that path), WebKit now renders on the GPU end-to-end: - nvidia-smi shows WebKitWebProcess holding ~200 MiB of GPU memory - WebProcess CPU drops from ~98% to ~40% under the same workload - GPU utilisation jumps from idle to 40-50% `config.allowUnfree = true` is required on the nixpkgs import for the NVIDIA runfile fetch. The driver version defaults to 580.142 (matches the user's current kernel module) and is overridable via `--argstr nvidiaVersion `. The shellHook compares the pin against `/proc/driver/nvidia/version` at entry and warns on mismatch so a kernel-driver upgrade produces a clear diagnostic rather than silent GPU breakage. Hosts without the NVIDIA driver continue to use the nixpkgs mesa software rasteriser fallback. Co-Authored-By: Claude Opus 4.7 (1M context) --- shell.nix | 87 +++++++++++++++++++++++++++++++++---------------------- 1 file changed, 52 insertions(+), 35 deletions(-) diff --git a/shell.nix b/shell.nix index 5374345b52..6a8e2a024b 100644 --- a/shell.nix +++ b/shell.nix @@ -1,6 +1,14 @@ { pkgs ? import (builtins.fetchTarball { url = "https://github.com/NixOS/nixpkgs/archive/refs/heads/nixos-25.05.tar.gz"; - }) { } + # config.allowUnfree is required for the proprietary NVIDIA user-space + # driver that nixGLNvidia (below) builds against; falls back to mesa + # software rendering on hosts without the NVIDIA driver. + }) { config.allowUnfree = true; } +, # Override to match `cat /proc/driver/nvidia/version` when your host's + # NVIDIA kernel module differs from the default. nixpkgs fixed-output- + # fetches the matching user-space runfile, so a wrong value produces a + # clear hash-mismatch error rather than silent corruption. + nvidiaVersion ? "580.142" }: let @@ -20,6 +28,22 @@ let [ "ar" "ranlib" "nm" "strip" "ld" "as" "objcopy" "objdump" "readelf" ]; }; + # nixGLNvidia is a thin wrapper script that sets `LD_LIBRARY_PATH` and + # `__EGL_VENDOR_LIBRARY_FILENAMES` to nix-managed copies of the host's + # proprietary NVIDIA driver, so nix-built libglvnd can dispatch into a + # working EGL/GLX vendor. We don't invoke the wrapper directly — its + # `export` statements are sourced into the shell below so `cargo tauri + # dev` picks up the right env without further wrapping. + # + # Without this, nix's libglvnd has no vendor ICD installed (NixOS + # populates /run/opengl-driver/lib; non-NixOS hosts have nothing), so + # WebKit's WebProcess can't initialise EGL and either aborts with + # `EGL_BAD_PARAMETER` or silently falls back to CPU rasterisation, + # capping the GUI at single-digit fps even on a discrete GPU. + nixGLNvidia = (import (builtins.fetchTarball { + url = "https://github.com/nix-community/nixGL/archive/refs/heads/main.tar.gz"; + }) { inherit pkgs nvidiaVersion; }).nixGLNvidia; + # Link-time deps for src-tauri and upstream Rust crates. These are found # via pkg-config and the RPATH baked in by NIX_LDFLAGS (rewritten in # shellHook below — see the comment there for why); we deliberately keep @@ -115,14 +139,6 @@ pkgs.mkShell { # needs them reachable via LD_LIBRARY_PATH in addition to being linked. LD_LIBRARY_PATH = pkgs.lib.makeLibraryPath tauriRuntimeLibs; - # WebKit's DMA-BUF renderer needs an EGL backend that can negotiate - # buffer sharing with the compositor; the host driver path below covers - # the EGL initialisation, but DMA-BUF still tends to fail on non-NixOS - # hosts (mismatch between the nix-built webkitgtk and the host's - # wayland/drm stack). Disable it so WebKit falls back to a glReadPixels- - # style upload that works with any GL backend. - WEBKIT_DISABLE_DMABUF_RENDERER = "1"; - shellHook = '' # cc-wrapper's add-flags.sh prepends `-rpath $out/lib` to NIX_LDFLAGS so # mkDerivation builds get a working RUNPATH at install time. In a @@ -138,38 +154,39 @@ pkgs.mkShell { unset _rpath_real fi - # WebKitGTK uses nixpkgs' libglvnd for EGL/GLX dispatch, but nixpkgs - # ships libglvnd without any vendor ICD installed — on NixOS the system - # populates /run/opengl-driver/lib, on a non-NixOS host there's nothing - # for libglvnd to dispatch to. Result: WebKit aborts with `Could not - # create default EGL display: EGL_BAD_PARAMETER` and the tauri webview - # renders as a blank whitescreen. + # GPU rendering setup. WebKitGTK uses libglvnd for EGL/GLX dispatch; + # nix's libglvnd has no vendor ICD installed, so on a non-NixOS host + # WebKit can't reach the GPU. Two paths: # - # If the host has the proprietary NVIDIA driver, surface its - # libnvidia-*/libEGL_nvidia/libGLX_nvidia files via a focused symlink - # dir on LD_LIBRARY_PATH (we can NOT put /usr/lib64 on LD_LIBRARY_PATH - # directly — that shadows nix's libstdc++/libc and segfaults the - # process). Combined with the host's /usr/share/glvnd/egl_vendor.d/ - # JSON, nix's libglvnd dispatches into the host driver and gets real - # GPU rendering. + # - host has the proprietary NVIDIA driver: source the env vars from + # a pre-built nixGLNvidia wrapper, which points libglvnd at nix- + # managed copies of the host's NVIDIA libs (avoids ABI-mixing + # /usr/lib64 with the nix lib stack). We also force GDK_BACKEND=x11 + # because NVIDIA + native Wayland + nix's webkitgtk hits a Wayland + # EPROTO during DMA-BUF setup; XWayland sidesteps that path. # - # Otherwise fall back to nixpkgs' mesa software rasteriser. Slow but - # works without any host driver and without ABI mixing. - if [ -e /usr/lib64/libEGL_nvidia.so.0 ] && [ -d /usr/share/glvnd/egl_vendor.d ]; then - _nv_dir="''${XDG_RUNTIME_DIR:-/tmp}/eigenwallet-nvidia-libs" - rm -rf "$_nv_dir" - mkdir -p "$_nv_dir" - for _f in /usr/lib64/libnvidia-*.so.* /usr/lib64/libEGL_nvidia.so.* /usr/lib64/libGLX_nvidia.so.*; do - [ -e "$_f" ] && ln -s "$_f" "$_nv_dir/" - done - export LD_LIBRARY_PATH="''${LD_LIBRARY_PATH}:$_nv_dir" - export __EGL_VENDOR_LIBRARY_DIRS=/usr/share/glvnd/egl_vendor.d - export __GLX_VENDOR_LIBRARY_NAME=nvidia - unset _nv_dir _f + # - no NVIDIA: fall back to nixpkgs' mesa software rasteriser. Slow + # (single-digit fps) but works on any host without ABI mixing, and + # WEBKIT_DISABLE_DMABUF_RENDERER=1 keeps the WebProcess from + # trying a hardware path it can't satisfy. + if [ -e /proc/driver/nvidia/version ]; then + _host_nv=$(awk '/Module/ { for(i=1;i<=NF;i++) if($i ~ /^[0-9]+\.[0-9]+/) { print $i; exit } }' /proc/driver/nvidia/version 2>/dev/null) + if [ -n "$_host_nv" ] && [ "$_host_nv" != "${nvidiaVersion}" ]; then + echo "[shell.nix] WARNING: nvidia kernel module is $_host_nv but shell.nix is pinned to ${nvidiaVersion}." >&2 + echo "[shell.nix] GPU rendering may break — pass --argstr nvidiaVersion $_host_nv or update shell.nix." >&2 + fi + unset _host_nv + + # Source the nixGLNvidia wrapper's env-var setup. The wrapper ends + # in `exec "$@"`; strip that line so sourcing doesn't try to exec + # an empty argv. Bash arrays (NVIDIA_JSON*=) are evaluated too. + eval "$(${pkgs.gnused}/bin/sed '/^[[:space:]]*exec /d' ${nixGLNvidia}/bin/nixGLNvidia-${nvidiaVersion})" + export GDK_BACKEND=x11 else export __EGL_VENDOR_LIBRARY_DIRS=${pkgs.mesa}/share/glvnd/egl_vendor.d export LIBGL_DRIVERS_PATH=${pkgs.mesa}/lib/dri export LIBGL_ALWAYS_SOFTWARE=1 + export WEBKIT_DISABLE_DMABUF_RENDERER=1 fi # Rustup-managed toolchain lives in ~/.cargo/bin; nix-shell resets PATH From 423ca94c85bedbfe234889d4c72357a475d14e5a Mon Sep 17 00:00:00 2001 From: einliterflasche Date: Sun, 24 May 2026 21:05:45 +0200 Subject: [PATCH 5/6] progress --- flake.nix | 11 ++- shell.nix | 147 ++++++++++++++++++++++------------ swap/tests/harness/electrs.rs | 5 +- 3 files changed, 109 insertions(+), 54 deletions(-) diff --git a/flake.nix b/flake.nix index 65055b1a0e..131de08c85 100644 --- a/flake.nix +++ b/flake.nix @@ -10,6 +10,15 @@ flake-utils.lib.eachDefaultSystem (system: let pkgs = import nixpkgs { inherit system; }; in { - devShells.default = import ./shell.nix { inherit pkgs; }; + # nvidiaVersion is pinned to null here because flake evaluation is pure: + # shell.nix's default auto-detection reads /proc and uses builtins.currentTime, + # both of which a pure `nix develop` rejects. So the flake gives a working + # shell with mesa software rendering. For GPU acceleration use `nix-shell` + # (auto-detects the host driver), or `nix develop --impure` with an explicit + # `nvidiaVersion`. + devShells.default = import ./shell.nix { + inherit pkgs; + nvidiaVersion = null; + }; }); } diff --git a/shell.nix b/shell.nix index 6a8e2a024b..98c208659e 100644 --- a/shell.nix +++ b/shell.nix @@ -1,14 +1,32 @@ { pkgs ? import (builtins.fetchTarball { url = "https://github.com/NixOS/nixpkgs/archive/refs/heads/nixos-25.05.tar.gz"; - # config.allowUnfree is required for the proprietary NVIDIA user-space - # driver that nixGLNvidia (below) builds against; falls back to mesa - # software rendering on hosts without the NVIDIA driver. + # allowUnfree lets nixGL build the proprietary NVIDIA user-space driver for + # the GPU path below. Harmless on hosts without NVIDIA: nothing unfree is + # pulled in unless that path is actually taken. }) { config.allowUnfree = true; } -, # Override to match `cat /proc/driver/nvidia/version` when your host's - # NVIDIA kernel module differs from the default. nixpkgs fixed-output- - # fetches the matching user-space runfile, so a wrong value produces a - # clear hash-mismatch error rather than silent corruption. - nvidiaVersion ? "580.142" +, # NVIDIA user-space driver version for GPU rendering in the Tauri webview. + # Defaults to auto-detecting the running kernel module from /proc, so it + # tracks the host across driver upgrades instead of pinning one version. + # Detection is impure (reads /proc, rebuilds each eval) which is fine under + # `nix-shell`; pure callers like the flake's `nix develop` must pass this + # explicitly (e.g. `nvidiaVersion = "580.159.03"`) or `null` to skip the GPU + # path and use mesa software rendering. + nvidiaVersion ? + let + # nix can't `readFile` /proc directly (zero-sized files, NixOS/nix#3539), + # so copy it out in an impure derivation first — same trick as nixGL's + # own `auto`. `|| touch` yields an empty file on non-NVIDIA hosts. + versionFile = pkgs.runCommand "impure-nvidia-version" { + time = builtins.currentTime; # rebuild every eval; the host driver can change + preferLocalBuild = true; + allowSubstitutes = false; + } ''cp /proc/driver/nvidia/version "$out" 2>/dev/null || touch "$out"''; + firstLine = builtins.head (pkgs.lib.splitString "\n" (builtins.readFile versionFile)); + # Match both "...Kernel Module ..." (proprietary) and + # "...Open Kernel Module for x86_64 ..." (open module). nixGL's + # built-in detector only handles the former, so we parse it ourselves. + m = builtins.match ".*Kernel Module( for [^ ]+)? ([0-9.]+) .*" firstLine; + in if m == null then null else builtins.elemAt m 1 }: let @@ -28,22 +46,62 @@ let [ "ar" "ranlib" "nm" "strip" "ld" "as" "objcopy" "objdump" "readelf" ]; }; - # nixGLNvidia is a thin wrapper script that sets `LD_LIBRARY_PATH` and - # `__EGL_VENDOR_LIBRARY_FILENAMES` to nix-managed copies of the host's - # proprietary NVIDIA driver, so nix-built libglvnd can dispatch into a - # working EGL/GLX vendor. We don't invoke the wrapper directly — its - # `export` statements are sourced into the shell below so `cargo tauri - # dev` picks up the right env without further wrapping. + # GPU rendering for the Tauri webview. WebKitGTK dispatches OpenGL/EGL through + # nix's libglvnd, which ships no vendor ICD, so on a non-NixOS host the + # WebProcess can't reach the GPU and either aborts with `EGL_BAD_PARAMETER` + # or silently drops to CPU rasterisation (single-digit fps even on a discrete + # GPU). # - # Without this, nix's libglvnd has no vendor ICD installed (NixOS - # populates /run/opengl-driver/lib; non-NixOS hosts have nothing), so - # WebKit's WebProcess can't initialise EGL and either aborts with - # `EGL_BAD_PARAMETER` or silently falls back to CPU rasterisation, - # capping the GUI at single-digit fps even on a discrete GPU. + # nixGL fixes this by building the NVIDIA user-space driver *as a nix + # derivation* and exposing it via LD_LIBRARY_PATH + a glvnd vendor ICD. The + # crucial property — versus copying the host's driver libs, e.g. nix-gl-host + # — is that the nix-built driver links nix's own libX11/libxcb/libffi, so it + # never drags mismatched host libraries into the nix process. Pulling host + # libxcb/libffi in alongside nix's copies crashes the WebProcess in their + # `_init`, which is exactly what a host-driver bridge does here. + # + # The driver must match the running kernel module *exactly* (a mismatch falls + # back to software or fails), which is why `nvidiaVersion` auto-detects rather + # than pins. This is built only when a driver is present: the shellHook + # references it solely inside the `nvidiaVersion != null` branch, and nix is + # lazy, so a host without NVIDIA never fetches nixGL or the driver runfile. nixGLNvidia = (import (builtins.fetchTarball { url = "https://github.com/nix-community/nixGL/archive/refs/heads/main.tar.gz"; }) { inherit pkgs nvidiaVersion; }).nixGLNvidia; + # The GL environment, chosen at eval time so `just tauri-mainnet` runs + # unwrapped. NVIDIA: source the nixGL wrapper's env-setup — strip its trailing + # `exec "$@"` so sourcing doesn't exec an empty argv; the bash NVIDIA_JSON* + # arrays it defines are evaluated too. The wrapper *appends* to the existing + # LD_LIBRARY_PATH (the link-time libs set on the shell), so both survive. + # GDK_BACKEND=x11 is forced because NVIDIA + native Wayland + nix's webkitgtk + # hits a Wayland EPROTO during DMA-BUF setup; XWayland renders fine via the + # GLX/EGL paths the wrapper wires up. + gpuShellHook = + if nvidiaVersion != null then '' + eval "$(${pkgs.gnused}/bin/sed '/^[[:space:]]*exec /d' ${nixGLNvidia}/bin/nixGLNvidia-${nvidiaVersion})" + export GDK_BACKEND=x11 + + # Under GNOME fractional scaling + XWayland, app-set cursors (the hand + # over a button, the I-beam over a text box) render tiny because GTK + # ignores the X server's scaled Xcursor.size. Re-export that size as + # XCURSOR_SIZE so they match the correctly-sized default cursor. No-op + # when there's no X server or the resource is absent (non-HiDPI). + if [ -z "''${XCURSOR_SIZE:-}" ] && [ -n "''${DISPLAY:-}" ]; then + _xcursor_size=$(${pkgs.xorg.xrdb}/bin/xrdb -query 2>/dev/null | ${pkgs.gnused}/bin/sed -n 's/^Xcursor\.size:[[:space:]]*//p') + [ -n "$_xcursor_size" ] && export XCURSOR_SIZE="$_xcursor_size" + unset _xcursor_size + fi + '' else '' + # No NVIDIA driver detected: fall back to nixpkgs' mesa software + # rasteriser. Slow but portable; WEBKIT_DISABLE_DMABUF_RENDERER=1 stops + # the WebProcess from attempting a hardware path it can't satisfy. + export __EGL_VENDOR_LIBRARY_DIRS=${pkgs.mesa}/share/glvnd/egl_vendor.d + export LIBGL_DRIVERS_PATH=${pkgs.mesa}/lib/dri + export LIBGL_ALWAYS_SOFTWARE=1 + export WEBKIT_DISABLE_DMABUF_RENDERER=1 + ''; + # Link-time deps for src-tauri and upstream Rust crates. These are found # via pkg-config and the RPATH baked in by NIX_LDFLAGS (rewritten in # shellHook below — see the comment there for why); we deliberately keep @@ -154,39 +212,24 @@ pkgs.mkShell { unset _rpath_real fi - # GPU rendering setup. WebKitGTK uses libglvnd for EGL/GLX dispatch; - # nix's libglvnd has no vendor ICD installed, so on a non-NixOS host - # WebKit can't reach the GPU. Two paths: - # - # - host has the proprietary NVIDIA driver: source the env vars from - # a pre-built nixGLNvidia wrapper, which points libglvnd at nix- - # managed copies of the host's NVIDIA libs (avoids ABI-mixing - # /usr/lib64 with the nix lib stack). We also force GDK_BACKEND=x11 - # because NVIDIA + native Wayland + nix's webkitgtk hits a Wayland - # EPROTO during DMA-BUF setup; XWayland sidesteps that path. - # - # - no NVIDIA: fall back to nixpkgs' mesa software rasteriser. Slow - # (single-digit fps) but works on any host without ABI mixing, and - # WEBKIT_DISABLE_DMABUF_RENDERER=1 keeps the WebProcess from - # trying a hardware path it can't satisfy. - if [ -e /proc/driver/nvidia/version ]; then - _host_nv=$(awk '/Module/ { for(i=1;i<=NF;i++) if($i ~ /^[0-9]+\.[0-9]+/) { print $i; exit } }' /proc/driver/nvidia/version 2>/dev/null) - if [ -n "$_host_nv" ] && [ "$_host_nv" != "${nvidiaVersion}" ]; then - echo "[shell.nix] WARNING: nvidia kernel module is $_host_nv but shell.nix is pinned to ${nvidiaVersion}." >&2 - echo "[shell.nix] GPU rendering may break — pass --argstr nvidiaVersion $_host_nv or update shell.nix." >&2 - fi - unset _host_nv - - # Source the nixGLNvidia wrapper's env-var setup. The wrapper ends - # in `exec "$@"`; strip that line so sourcing doesn't try to exec - # an empty argv. Bash arrays (NVIDIA_JSON*=) are evaluated too. - eval "$(${pkgs.gnused}/bin/sed '/^[[:space:]]*exec /d' ${nixGLNvidia}/bin/nixGLNvidia-${nvidiaVersion})" - export GDK_BACKEND=x11 - else - export __EGL_VENDOR_LIBRARY_DIRS=${pkgs.mesa}/share/glvnd/egl_vendor.d - export LIBGL_DRIVERS_PATH=${pkgs.mesa}/lib/dri - export LIBGL_ALWAYS_SOFTWARE=1 - export WEBKIT_DISABLE_DMABUF_RENDERER=1 + # GPU rendering setup (see the nixGL / nvidiaVersion bindings above): NVIDIA + # via a nix-built driver, otherwise mesa software rendering. + ${gpuShellHook} + # `just docker_test` runs the testcontainers-based integration tests, whose + # 0.15 Cli client shells out to a `docker` binary. On a host with rootless + # podman but no docker (e.g. Fedora), bridge `docker` -> `podman` so the + # tests run without a docker daemon or root. Guarded so a real docker + # install — or CI's preinstalled docker — is left untouched. + if ! command -v docker >/dev/null 2>&1 && command -v podman >/dev/null 2>&1; then + _docker_shim="$HOME/.cache/eigenwallet-docker-shim" + mkdir -p "$_docker_shim" + printf '#!/bin/sh\nexec podman "$@"\n' > "$_docker_shim/docker" + chmod +x "$_docker_shim/docker" + export PATH="$_docker_shim:$PATH" + # testcontainers 0.15's Cli client cleans up its own containers; Ryuk + # (its reaper container) isn't needed and trips on rootless podman. + export TESTCONTAINERS_RYUK_DISABLED=true + unset _docker_shim fi # Rustup-managed toolchain lives in ~/.cargo/bin; nix-shell resets PATH diff --git a/swap/tests/harness/electrs.rs b/swap/tests/harness/electrs.rs index 403cc9407c..fa236d5bb8 100644 --- a/swap/tests/harness/electrs.rs +++ b/swap/tests/harness/electrs.rs @@ -42,7 +42,10 @@ impl Image for Electrs { impl Default for Electrs { fn default() -> Self { Electrs { - tag: "v0.16.0.3".into(), + // Docker Hub dropped the old version tags for this image; only + // `latest` (still the 2020 build with the same /build/electrs + // entrypoint) remains, so the previous "v0.16.0.3" now 404s. + tag: "latest".into(), args: ElectrsArgs::default(), entrypoint: Some("/build/electrs".into()), wait_for_message: "Running accept thread".to_string(), From 513c1176de48b1e21f6c7991be67c9cd55b9ce2f Mon Sep 17 00:00:00 2001 From: einliterflasche Date: Mon, 1 Jun 2026 23:41:04 +0200 Subject: [PATCH 6/6] build(nix-shell): pin inputs, scope unfree, move rationale to nix/README - pin nixpkgs + nixGL fetchTarballs to immutable rev+sha256 and commit flake.lock so both the nix-shell and nix develop paths are reproducible - scope allowUnfree to the NVIDIA driver via allowUnfreePredicate - append (not prepend) the docker->podman shim so a real docker always wins; create its cache dir mode 700 - drop the XCURSOR_SIZE cursor tweak and the zlib LD_LIBRARY_PATH safety net (both scope creep) - trim shell.nix to 4 trap-preventing comments; the remaining rationale now lives in nix/README.md - electrs Default tag stays "latest" (Docker Hub dropped v0.16.0.3; the live test path already used latest) Co-Authored-By: Claude Opus 4.8 (1M context) --- flake.lock | 61 ++++++++++++++ flake.nix | 6 -- nix/README.md | 88 ++++++++++++++++++++ shell.nix | 148 ++++------------------------------ swap/tests/harness/electrs.rs | 3 - 5 files changed, 165 insertions(+), 141 deletions(-) create mode 100644 flake.lock create mode 100644 nix/README.md diff --git a/flake.lock b/flake.lock new file mode 100644 index 0000000000..db686c56b7 --- /dev/null +++ b/flake.lock @@ -0,0 +1,61 @@ +{ + "nodes": { + "flake-utils": { + "inputs": { + "systems": "systems" + }, + "locked": { + "lastModified": 1731533236, + "narHash": "sha256-l0KFg5HjrsfsO/JpG+r7fRrqm12kzFHyUHqHCVpMMbI=", + "owner": "numtide", + "repo": "flake-utils", + "rev": "11707dc2f618dd54ca8739b309ec4fc024de578b", + "type": "github" + }, + "original": { + "owner": "numtide", + "repo": "flake-utils", + "type": "github" + } + }, + "nixpkgs": { + "locked": { + "lastModified": 1767313136, + "narHash": "sha256-16KkgfdYqjaeRGBaYsNrhPRRENs0qzkQVUooNHtoy2w=", + "owner": "NixOS", + "repo": "nixpkgs", + "rev": "ac62194c3917d5f474c1a844b6fd6da2db95077d", + "type": "github" + }, + "original": { + "owner": "NixOS", + "ref": "nixos-25.05", + "repo": "nixpkgs", + "type": "github" + } + }, + "root": { + "inputs": { + "flake-utils": "flake-utils", + "nixpkgs": "nixpkgs" + } + }, + "systems": { + "locked": { + "lastModified": 1681028828, + "narHash": "sha256-Vy1rq5AaRuLzOxct8nz4T6wlgyUR7zLU309k9mBC768=", + "owner": "nix-systems", + "repo": "default", + "rev": "da67096a3b9bf56a91d16901293e51ba5b49a27e", + "type": "github" + }, + "original": { + "owner": "nix-systems", + "repo": "default", + "type": "github" + } + } + }, + "root": "root", + "version": 7 +} diff --git a/flake.nix b/flake.nix index 131de08c85..525769a2db 100644 --- a/flake.nix +++ b/flake.nix @@ -10,12 +10,6 @@ flake-utils.lib.eachDefaultSystem (system: let pkgs = import nixpkgs { inherit system; }; in { - # nvidiaVersion is pinned to null here because flake evaluation is pure: - # shell.nix's default auto-detection reads /proc and uses builtins.currentTime, - # both of which a pure `nix develop` rejects. So the flake gives a working - # shell with mesa software rendering. For GPU acceleration use `nix-shell` - # (auto-detects the host driver), or `nix develop --impure` with an explicit - # `nvidiaVersion`. devShells.default = import ./shell.nix { inherit pkgs; nvidiaVersion = null; diff --git a/nix/README.md b/nix/README.md new file mode 100644 index 0000000000..42df01fef0 --- /dev/null +++ b/nix/README.md @@ -0,0 +1,88 @@ +# Nix dev environment + +A `nix-shell` / `nix develop` environment providing the full toolchain to build and +run eigenwallet (monero-sys, the Tauri GUI, and the `just` recipes) on non-NixOS hosts. +This file documents the *why* behind `shell.nix` and `flake.nix`; the code itself is +kept comment-free. + +## Usage + +- `nix-shell` — impure shell; auto-detects the host NVIDIA driver for GPU rendering. +- `direnv` — `.envrc` runs `use nix`, so the shell loads automatically on `cd`. +- `nix develop` — pure flake shell; uses mesa software rendering (pure eval can't read + `/proc`). For GPU acceleration use `nix-shell`, or `nix develop --impure` with an + explicit `nvidiaVersion` (e.g. `"580.159.03"`). + +Inputs are pinned: `flake.lock` for the flake path, and an explicit rev + `sha256` on +each `fetchTarball` in `shell.nix` for the `nix-shell` path. Bump both together. + +## GPU rendering (Tauri webview) + +WebKitGTK dispatches OpenGL/EGL through nix's libglvnd, which ships no vendor ICD. On a +non-NixOS host the WebProcess can't reach the GPU and either aborts with +`EGL_BAD_PARAMETER` or silently drops to CPU rasterisation (single-digit fps even on a +discrete GPU). + +We use [nixGL](https://github.com/nix-community/nixGL) to build the NVIDIA user-space +driver *as a nix derivation* and expose it via `LD_LIBRARY_PATH` + a glvnd vendor ICD. +Building it — rather than bridging the host's driver libs (e.g. nix-gl-host) — is what +keeps it working: the nix-built driver links nix's own libX11/libxcb/libffi, so it never +drags mismatched host libraries into the nix process. Pulling host libxcb/libffi in +alongside nix's copies crashes the WebProcess in their `_init`. + +The driver must match the running kernel module *exactly*, so `nvidiaVersion` +auto-detects from `/proc/driver/nvidia/version` rather than pinning a version (`nix` can't +`readFile` `/proc` directly — zero-sized files, NixOS/nix#3539 — so it's copied out in an +impure derivation first; `builtins.currentTime` makes it re-detect each eval). The regex +matches both the proprietary and the open kernel module. nixGL is only fetched/built when +a driver is present — the shellHook references it solely in the `nvidiaVersion != null` +branch, and nix is lazy — so a host without NVIDIA never fetches it. + +`GDK_BACKEND=x11` is forced because NVIDIA + native Wayland + nix's webkitgtk hits a +Wayland `EPROTO` during DMA-BUF setup; XWayland renders fine via the GLX/EGL paths nixGL +wires up. The wrapper's env-setup is sourced (with its trailing `exec` stripped so +sourcing doesn't exec an empty argv). Without a driver we fall back to mesa software +rendering and `WEBKIT_DISABLE_DMABUF_RENDERER=1`. + +## monero-depends toolchain wrappers + +`monero-depends/hosts/linux.mk` hardcodes the Debian-style triple `x86_64-linux-gnu-` +on x86 build hosts. Nixpkgs ships those tools unprefixed (or under +`x86_64-unknown-linux-gnu-*`), so without the `prefixWrapper` shims `configure` reports +"C compiler cannot create executables" even though the toolchain is fine. + +## Linking model + +Link-time deps (`tauriLinkLibs`) are found via pkg-config and the RPATH baked in by +`NIX_LDFLAGS`. We deliberately keep them off `LD_LIBRARY_PATH` so they can't shadow +nix-built tools like curl, whose ngtcp2 module is pinned to a specific openssl ABI. +`stdenv.cc.cc.lib` is included so RUNPATH covers `libstdc++.so.6`. + +Only the subset WebKitGTK/GTK `dlopen` at runtime (`tauriRuntimeLibs`) goes on +`LD_LIBRARY_PATH`, because `dlopen` of a bare soname consults `LD_LIBRARY_PATH` and the +system cache, not the calling binary's RUNPATH. + +In a `nix-shell`, cc-wrapper sets `-rpath $out/lib` in `NIX_LDFLAGS`, but `$out` resolves +to `/outputs/out` — a path that never exists — so every binary cargo links gets a +dead RUNPATH. The shellHook rewrites that rpath to the real link-time inputs. + +## docker → podman shim + +`just docker_test` runs testcontainers-based integration tests whose 0.15 Cli client +shells out to a `docker` binary. On a host with rootless podman but no docker (e.g. +Fedora), the shellHook bridges `docker` → `podman` (appended to `PATH`, so a real docker +always wins) and sets `TESTCONTAINERS_RYUK_DISABLED=true` — the reaper isn't needed and +trips on rootless podman. Guarded so a real docker install is left untouched. + +## Rust & yarn + +The Rust toolchain comes from host rustup (`~/.cargo/bin`, re-prepended to `PATH`); +`rust-toolchain.toml` pins the version. `src-gui` pins `yarn@4.x` via `packageManager`, so +corepack (bundled with nodejs) materialises that exact version into a user-writable dir +instead of using nixpkgs' yarn 1.x. + +## electrs test image + +`swap/tests/harness` pulls `vulpemventures/electrs:latest`: Docker Hub dropped the old +`v0.16.0.3` tag (it now 404s), and `latest` is still the same 2020 build with the same +`/build/electrs` entrypoint. The live test path already used `latest`. diff --git a/shell.nix b/shell.nix index 98c208659e..39265cc19c 100644 --- a/shell.nix +++ b/shell.nix @@ -1,40 +1,25 @@ { pkgs ? import (builtins.fetchTarball { - url = "https://github.com/NixOS/nixpkgs/archive/refs/heads/nixos-25.05.tar.gz"; - # allowUnfree lets nixGL build the proprietary NVIDIA user-space driver for - # the GPU path below. Harmless on hosts without NVIDIA: nothing unfree is - # pulled in unless that path is actually taken. - }) { config.allowUnfree = true; } -, # NVIDIA user-space driver version for GPU rendering in the Tauri webview. - # Defaults to auto-detecting the running kernel module from /proc, so it - # tracks the host across driver upgrades instead of pinning one version. - # Detection is impure (reads /proc, rebuilds each eval) which is fine under - # `nix-shell`; pure callers like the flake's `nix develop` must pass this - # explicitly (e.g. `nvidiaVersion = "580.159.03"`) or `null` to skip the GPU - # path and use mesa software rendering. - nvidiaVersion ? + url = "https://github.com/NixOS/nixpkgs/archive/ac62194c3917d5f474c1a844b6fd6da2db95077d.tar.gz"; + sha256 = "0v6bd1xk8a2aal83karlvc853x44dg1n4nk08jg3dajqyy0s98np"; + }) { + config.allowUnfreePredicate = p: + builtins.match "nvidia.*" (builtins.parseDrvName p.name).name != null; + } +, nvidiaVersion ? let - # nix can't `readFile` /proc directly (zero-sized files, NixOS/nix#3539), - # so copy it out in an impure derivation first — same trick as nixGL's - # own `auto`. `|| touch` yields an empty file on non-NVIDIA hosts. + # nix can't readFile /proc directly (NixOS/nix#3539); copy it out impurely first versionFile = pkgs.runCommand "impure-nvidia-version" { - time = builtins.currentTime; # rebuild every eval; the host driver can change + time = builtins.currentTime; preferLocalBuild = true; allowSubstitutes = false; } ''cp /proc/driver/nvidia/version "$out" 2>/dev/null || touch "$out"''; firstLine = builtins.head (pkgs.lib.splitString "\n" (builtins.readFile versionFile)); - # Match both "...Kernel Module ..." (proprietary) and - # "...Open Kernel Module for x86_64 ..." (open module). nixGL's - # built-in detector only handles the former, so we parse it ourselves. + # "( for )?" also matches the NVIDIA open kernel module, not just proprietary m = builtins.match ".*Kernel Module( for [^ ]+)? ([0-9.]+) .*" firstLine; in if m == null then null else builtins.elemAt m 1 }: let - # monero-depends/hosts/linux.mk hardcodes the Debian-style triple - # `x86_64-linux-gnu-` when the build host is x86. Nixpkgs ships - # those tools under `x86_64-unknown-linux-gnu-*` (or unprefixed), so - # without these wrappers `configure` reports "C compiler cannot create - # executables" even though the toolchain is fine. prefixWrapper = from: tool: pkgs.writeShellScriptBin "x86_64-linux-gnu-${tool}" ''exec ${from}/bin/${tool} "$@"''; @@ -46,70 +31,23 @@ let [ "ar" "ranlib" "nm" "strip" "ld" "as" "objcopy" "objdump" "readelf" ]; }; - # GPU rendering for the Tauri webview. WebKitGTK dispatches OpenGL/EGL through - # nix's libglvnd, which ships no vendor ICD, so on a non-NixOS host the - # WebProcess can't reach the GPU and either aborts with `EGL_BAD_PARAMETER` - # or silently drops to CPU rasterisation (single-digit fps even on a discrete - # GPU). - # - # nixGL fixes this by building the NVIDIA user-space driver *as a nix - # derivation* and exposing it via LD_LIBRARY_PATH + a glvnd vendor ICD. The - # crucial property — versus copying the host's driver libs, e.g. nix-gl-host - # — is that the nix-built driver links nix's own libX11/libxcb/libffi, so it - # never drags mismatched host libraries into the nix process. Pulling host - # libxcb/libffi in alongside nix's copies crashes the WebProcess in their - # `_init`, which is exactly what a host-driver bridge does here. - # - # The driver must match the running kernel module *exactly* (a mismatch falls - # back to software or fails), which is why `nvidiaVersion` auto-detects rather - # than pins. This is built only when a driver is present: the shellHook - # references it solely inside the `nvidiaVersion != null` branch, and nix is - # lazy, so a host without NVIDIA never fetches nixGL or the driver runfile. nixGLNvidia = (import (builtins.fetchTarball { - url = "https://github.com/nix-community/nixGL/archive/refs/heads/main.tar.gz"; + url = "https://github.com/nix-community/nixGL/archive/b6105297e6f0cd041670c3e8628394d4ee247ed5.tar.gz"; + sha256 = "1zv3bshk0l4hfh1s7s3jzwjxl0nqqcvc4a3kydd3d4lgh7651d3x"; }) { inherit pkgs nvidiaVersion; }).nixGLNvidia; - # The GL environment, chosen at eval time so `just tauri-mainnet` runs - # unwrapped. NVIDIA: source the nixGL wrapper's env-setup — strip its trailing - # `exec "$@"` so sourcing doesn't exec an empty argv; the bash NVIDIA_JSON* - # arrays it defines are evaluated too. The wrapper *appends* to the existing - # LD_LIBRARY_PATH (the link-time libs set on the shell), so both survive. - # GDK_BACKEND=x11 is forced because NVIDIA + native Wayland + nix's webkitgtk - # hits a Wayland EPROTO during DMA-BUF setup; XWayland renders fine via the - # GLX/EGL paths the wrapper wires up. gpuShellHook = if nvidiaVersion != null then '' + # strip the trailing exec so sourcing the nixGL wrapper doesn't exec an empty argv eval "$(${pkgs.gnused}/bin/sed '/^[[:space:]]*exec /d' ${nixGLNvidia}/bin/nixGLNvidia-${nvidiaVersion})" export GDK_BACKEND=x11 - - # Under GNOME fractional scaling + XWayland, app-set cursors (the hand - # over a button, the I-beam over a text box) render tiny because GTK - # ignores the X server's scaled Xcursor.size. Re-export that size as - # XCURSOR_SIZE so they match the correctly-sized default cursor. No-op - # when there's no X server or the resource is absent (non-HiDPI). - if [ -z "''${XCURSOR_SIZE:-}" ] && [ -n "''${DISPLAY:-}" ]; then - _xcursor_size=$(${pkgs.xorg.xrdb}/bin/xrdb -query 2>/dev/null | ${pkgs.gnused}/bin/sed -n 's/^Xcursor\.size:[[:space:]]*//p') - [ -n "$_xcursor_size" ] && export XCURSOR_SIZE="$_xcursor_size" - unset _xcursor_size - fi '' else '' - # No NVIDIA driver detected: fall back to nixpkgs' mesa software - # rasteriser. Slow but portable; WEBKIT_DISABLE_DMABUF_RENDERER=1 stops - # the WebProcess from attempting a hardware path it can't satisfy. export __EGL_VENDOR_LIBRARY_DIRS=${pkgs.mesa}/share/glvnd/egl_vendor.d export LIBGL_DRIVERS_PATH=${pkgs.mesa}/lib/dri export LIBGL_ALWAYS_SOFTWARE=1 export WEBKIT_DISABLE_DMABUF_RENDERER=1 ''; - # Link-time deps for src-tauri and upstream Rust crates. These are found - # via pkg-config and the RPATH baked in by NIX_LDFLAGS (rewritten in - # shellHook below — see the comment there for why); we deliberately keep - # them off LD_LIBRARY_PATH so they can't shadow nix-built tools like curl - # whose ngtcp2 module is pinned to a specific openssl ABI. - # - # `stdenv.cc.cc.lib` is included so RUNPATH covers libstdc++.so.6, which - # the tauri binary pulls in via the C++ webkitgtk/cxx bindings. tauriLinkLibs = (with pkgs; [ glib gtk3 @@ -126,34 +64,16 @@ let zlib ]) ++ [ pkgs.stdenv.cc.cc.lib ]; - # Subset that WebKitGTK / GTK dlopen at runtime (tray icon, SVG pixbuf - # loaders, webview plugins). dlopen of a bare soname only consults - # LD_LIBRARY_PATH and the system cache, not the calling binary's RUNPATH, - # so these have to be on LD_LIBRARY_PATH even though they're also linked. - # - # zlib is here as a safety net for cargo build-script binaries (e.g. - # the vergen-git2 -> libgit2-sys path used by swap, swap-asb, - # swap-orchestrator). The rpath rewrite in shellHook fixes their - # RUNPATH for fresh links, but build-script binaries linked before - # the fix went in still carry a dead RUNPATH and would fail to - # resolve libz when cargo re-runs them after a `rerun-if-changed` - # trigger (e.g. a new git commit). Keeping libz on LD_LIBRARY_PATH - # avoids forcing users to `cargo clean` after picking up shell.nix. tauriRuntimeLibs = with pkgs; [ webkitgtk_4_1 libsoup_3 librsvg libayatana-appindicator gdk-pixbuf - zlib ]; in pkgs.mkShell { - # Tools invoked during the build (compilers, codegen, fetchers). - # Mirrors DEPS_BUILD_LINUX from .github/actions/set-monero-env/action.yml, - # minus cross-compilation bits (mingw-w64, nsis) that we don't need here. nativeBuildInputs = (with pkgs; [ - # C/C++ toolchain for monero-sys (CMake + monero-depends/make) gcc gnumake cmake @@ -168,77 +88,42 @@ pkgs.mkShell { curl git python3 - - # Node for src-gui. The project pins `yarn@4.x` via `packageManager` in - # src-gui/package.json, so we let corepack (bundled with nodejs) fetch - # that exact version instead of using nixpkgs' yarn 1.x. nodejs_22 - - # Cargo workspace helpers used by `just` recipes and the CI workflow. just typeshare dprint sqlx-cli - # `just tauri` runs `cargo tauri dev`, which needs the Rust tauri CLI - # on PATH (provides the `cargo-tauri` subcommand shim). The GitHub CI - # uses the yarn-based `@tauri-apps/cli` instead, so it doesn't need this. cargo-tauri ]) ++ [ moneroDependsToolchain ]; - # Native libraries linked by src-tauri and its webkit-based webview. buildInputs = tauriLinkLibs; - # monero-sys build.rs picks up $CC/$CXX; be explicit so we don't accidentally - # pull in a host toolchain that isn't in $PATH under nix-shell. CC = "${pkgs.gcc}/bin/gcc"; CXX = "${pkgs.gcc}/bin/g++"; - # WebKitGTK dlopens some of its plugins at runtime, so `tauri dev` - # needs them reachable via LD_LIBRARY_PATH in addition to being linked. LD_LIBRARY_PATH = pkgs.lib.makeLibraryPath tauriRuntimeLibs; shellHook = '' - # cc-wrapper's add-flags.sh prepends `-rpath $out/lib` to NIX_LDFLAGS so - # mkDerivation builds get a working RUNPATH at install time. In a - # nix-shell, however, $out resolves to /outputs/out — a path that - # never exists — so every binary cargo links here (build scripts and - # the tauri app itself) ends up with a dead RUNPATH and can't load its - # dynamic deps at runtime. Rewrite the bogus rpath to point at the - # link-time inputs so RUNPATH actually resolves and we don't have to - # leak everything onto LD_LIBRARY_PATH. + # nix-shell's default -rpath is $out/lib which never exists — repoint it or every cargo-linked binary gets a dead RUNPATH if [ -n "$out" ] && [[ "$NIX_LDFLAGS" == *"-rpath $out/lib"* ]]; then _rpath_real=${pkgs.lib.makeLibraryPath tauriLinkLibs} export NIX_LDFLAGS="''${NIX_LDFLAGS//-rpath $out\/lib/-rpath $_rpath_real}" unset _rpath_real fi - # GPU rendering setup (see the nixGL / nvidiaVersion bindings above): NVIDIA - # via a nix-built driver, otherwise mesa software rendering. ${gpuShellHook} - # `just docker_test` runs the testcontainers-based integration tests, whose - # 0.15 Cli client shells out to a `docker` binary. On a host with rootless - # podman but no docker (e.g. Fedora), bridge `docker` -> `podman` so the - # tests run without a docker daemon or root. Guarded so a real docker - # install — or CI's preinstalled docker — is left untouched. if ! command -v docker >/dev/null 2>&1 && command -v podman >/dev/null 2>&1; then _docker_shim="$HOME/.cache/eigenwallet-docker-shim" - mkdir -p "$_docker_shim" + mkdir -p -m 700 "$_docker_shim" printf '#!/bin/sh\nexec podman "$@"\n' > "$_docker_shim/docker" chmod +x "$_docker_shim/docker" - export PATH="$_docker_shim:$PATH" - # testcontainers 0.15's Cli client cleans up its own containers; Ryuk - # (its reaper container) isn't needed and trips on rootless podman. + export PATH="$PATH:$_docker_shim" export TESTCONTAINERS_RYUK_DISABLED=true unset _docker_shim fi - # Rustup-managed toolchain lives in ~/.cargo/bin; nix-shell resets PATH - # for its own deps, so re-prepend it. rust-toolchain.toml pins the version. export PATH="$HOME/.cargo/bin:$PATH" - # Let corepack materialise the project-pinned yarn version into a user- - # writable dir (nodejs bin in /nix/store is read-only, so the default - # `corepack enable` location fails). export COREPACK_HOME="$HOME/.cache/corepack" export COREPACK_ENABLE_DOWNLOAD_PROMPT=0 corepack_bin="$HOME/.cache/corepack/bin" @@ -246,7 +131,6 @@ pkgs.mkShell { ${pkgs.nodejs_22}/bin/corepack enable --install-directory "$corepack_bin" export PATH="$corepack_bin:$PATH" - # GSettings/GIO schemas for GTK apps (otherwise Tauri dialogs warn/crash). export XDG_DATA_DIRS="${pkgs.gsettings-desktop-schemas}/share/gsettings-schemas/${pkgs.gsettings-desktop-schemas.name}:${pkgs.gtk3}/share/gsettings-schemas/${pkgs.gtk3.name}:$XDG_DATA_DIRS" ''; } diff --git a/swap/tests/harness/electrs.rs b/swap/tests/harness/electrs.rs index fa236d5bb8..e0d5517563 100644 --- a/swap/tests/harness/electrs.rs +++ b/swap/tests/harness/electrs.rs @@ -42,9 +42,6 @@ impl Image for Electrs { impl Default for Electrs { fn default() -> Self { Electrs { - // Docker Hub dropped the old version tags for this image; only - // `latest` (still the 2020 build with the same /build/electrs - // entrypoint) remains, so the previous "v0.16.0.3" now 404s. tag: "latest".into(), args: ElectrsArgs::default(), entrypoint: Some("/build/electrs".into()),