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.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 new file mode 100644 index 0000000000..525769a2db --- /dev/null +++ b/flake.nix @@ -0,0 +1,18 @@ +{ + 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; + 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 new file mode 100644 index 0000000000..39265cc19c --- /dev/null +++ b/shell.nix @@ -0,0 +1,136 @@ +{ pkgs ? import (builtins.fetchTarball { + 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 (NixOS/nix#3539); copy it out impurely first + versionFile = pkgs.runCommand "impure-nvidia-version" { + 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)); + # "( 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 + 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" ]; + }; + + nixGLNvidia = (import (builtins.fetchTarball { + url = "https://github.com/nix-community/nixGL/archive/b6105297e6f0cd041670c3e8628394d4ee247ed5.tar.gz"; + sha256 = "1zv3bshk0l4hfh1s7s3jzwjxl0nqqcvc4a3kydd3d4lgh7651d3x"; + }) { inherit pkgs nvidiaVersion; }).nixGLNvidia; + + 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 + '' 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 + ''; + + tauriLinkLibs = (with pkgs; [ + glib + gtk3 + gdk-pixbuf + cairo + pango + atkmm + at-spi2-atk + libsoup_3 + webkitgtk_4_1 + librsvg + libayatana-appindicator + openssl + zlib + ]) ++ [ pkgs.stdenv.cc.cc.lib ]; + + tauriRuntimeLibs = with pkgs; [ + webkitgtk_4_1 + libsoup_3 + librsvg + libayatana-appindicator + gdk-pixbuf + ]; +in +pkgs.mkShell { + nativeBuildInputs = (with pkgs; [ + gcc + gnumake + cmake + autoconf + automake + libtool + pkg-config + binutils + ccache + gperf + lbzip2 + curl + git + python3 + nodejs_22 + just + typeshare + dprint + sqlx-cli + cargo-tauri + ]) ++ [ moneroDependsToolchain ]; + + buildInputs = tauriLinkLibs; + + CC = "${pkgs.gcc}/bin/gcc"; + CXX = "${pkgs.gcc}/bin/g++"; + + LD_LIBRARY_PATH = pkgs.lib.makeLibraryPath tauriRuntimeLibs; + + shellHook = '' + # 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 + + ${gpuShellHook} + 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 -m 700 "$_docker_shim" + printf '#!/bin/sh\nexec podman "$@"\n' > "$_docker_shim/docker" + chmod +x "$_docker_shim/docker" + export PATH="$PATH:$_docker_shim" + export TESTCONTAINERS_RYUK_DISABLED=true + unset _docker_shim + fi + + export PATH="$HOME/.cargo/bin:$PATH" + + 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" + + 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 403cc9407c..e0d5517563 100644 --- a/swap/tests/harness/electrs.rs +++ b/swap/tests/harness/electrs.rs @@ -42,7 +42,7 @@ impl Image for Electrs { impl Default for Electrs { fn default() -> Self { Electrs { - tag: "v0.16.0.3".into(), + tag: "latest".into(), args: ElectrsArgs::default(), entrypoint: Some("/build/electrs".into()), wait_for_message: "Running accept thread".to_string(),