Today I got a project from a friend that asked me to make some changes to their Task Management app. It was a simple Electron app, and as much as I hated working with electron, I decided to work on it as a favour. To give some context, I had recently installed NixOS on my main laptop, and until now didn't face any major issues installing packages or configuring it for my needs. So, I cloned the repo and ran npm install to build the project:

> taskmgr@1.0.0 postinstall
> electron-builder install-app-deps

  • electron-builder  version=24.13.3
  • loaded configuration  file=package.json ("build" field)
  • rebuilding native dependencies  dependencies=bcrypt@5.1.1, sqlite3@5.1.7 platform=linux arch=x64
  • install prebuilt binary  name=sqlite3 version=5.1.7 platform=linux arch=x64 napi=
  • build native dependency from sources  name=sqlite3
                                          version=5.1.7
                                          platform=linux
                                          arch=x64
                                          napi=
reason=prebuild-install failed with error (run with env DEBUG=electron-builder to get more information)
                                          error=prebuild-install info begin Prebuild-install version 7.1.3
    prebuild-install warn This package does not support N-API version 36
    prebuild-install warn install prebuilt binaries enforced with --force!
    prebuild-install warn install prebuilt binaries may be out of date!
    prebuild-install info looking for local prebuild @ prebuilds/sqlite3-v5.1.7-napi-v36-linux-x64.tar.gz
    prebuild-install info looking for cached prebuild @ /home/huzaifa/.npm/_prebuilds/c8064a-sqlite3-v5.1.7-napi-v36-linux-x64.tar.gz
    prebuild-install http request GET https://github.com/TryGhost/node-sqlite3/releases/download/v5.1.7/sqlite3-v5.1.7-napi-v36-linux-x64.tar.gz
    prebuild-install http 404 https://github.com/TryGhost/node-sqlite3/releases/download/v5.1.7/sqlite3-v5.1.7-napi-v36-linux-x64.tar.gz
    prebuild-install warn install No prebuilt binaries found (target=36 runtime=napi arch=x64 libc= platform=linux)
    ...

With my lackluster abilities working with Node and NPM, I started to search for a solution for this seemingly innocuous error. After a little back and forth, I found that the prebuild-install library (likely a dependency of electron) was not able to find a suitable version of sqlite on my machine, and as I had nodejs_24 in my configuration.nix, it for some reason wasn't able to find a prebuilt version of it on GitHub.

  ...
  ⨯ cannot execute  cause=exit status 1
                    errorOut=npm error code 1
    npm error path /home/huzaifa/dev/taskmgr/node_modules/sqlite3
    npm error command failed
    npm error command sh -c prebuild-install -r napi || node-gyp rebuild
    npm error prebuild-install warn This package does not support N-API version 28.3.3
    npm error prebuild-install warn install No prebuilt binaries found (target=28.3.3 runtime=napi arch=x64 libc= platform=linux)
    npm error gyp info it worked if it ends with ok
    npm error gyp info using node-gyp@8.4.1
    npm error gyp info using node@22.17.1 | linux | x64
    npm error (node:26962) [DEP0060] DeprecationWarning: The `util._extend` API is deprecated. Please use Object.assign() instead.
    npm error (Use `node --trace-deprecation ...` to show where the warning was created)
    npm error gyp info find Python using Python version 3.12.11 found at "/usr/bin/python3"
    npm error gyp info spawn /usr/bin/python3
    npm error gyp info spawn args [
    npm error gyp info spawn args   '/home/huzaifa/dev/taskmgr/node_modules/sqlite3/node_modules/node-gyp/gyp/gyp_main.py',
    npm error gyp info spawn args   'binding.gyp',
    npm error gyp info spawn args   '-f',
    npm error gyp info spawn args   'make',
    npm error gyp info spawn args   '-I',
    npm error gyp info spawn args   '/home/huzaifa/dev/taskmgr/node_modules/sqlite3/build/config.gypi',
    npm error gyp info spawn args   '-I',
    npm error gyp info spawn args   '/home/huzaifa/dev/taskmgr/node_modules/sqlite3/node_modules/node-gyp/addon.gypi',
    npm error gyp info spawn args   '-I',
    npm error gyp info spawn args   '/home/huzaifa/.electron-gyp/28.3.3/include/node/common.gypi',
    npm error gyp info spawn args   '-Dlibrary=shared_library',
    npm error gyp info spawn args   '-Dvisibility=default',
    npm error gyp info spawn args   '-Dnode_root_dir=/home/huzaifa/.electron-gyp/28.3.3',
    npm error gyp info spawn args   '-Dnode_gyp_dir=/home/huzaifa/dev/taskmgr/node_modules/sqlite3/node_modules/node-gyp',
    npm error gyp info spawn args   '-Dnode_lib_file=/home/huzaifa/.electron-gyp/28.3.3/<(target_arch)/node.lib',
    npm error gyp info spawn args   '-Dmodule_root_dir=/home/huzaifa/dev/taskmgr/node_modules/sqlite3',
    npm error gyp info spawn args   '-Dnode_engine=v8',
    npm error gyp info spawn args   '--depth=.',
    npm error gyp info spawn args   '--no-parallel',
    npm error gyp info spawn args   '--generator-output',
    npm error gyp info spawn args   'build',
    npm error gyp info spawn args   '-Goutput_dir=.'
    npm error gyp info spawn args ]
    npm error Traceback (most recent call last):
    npm error   File "/home/huzaifa/dev/taskmgr/node_modules/sqlite3/node_modules/node-gyp/gyp/gyp_main.py", line 42, in <module>
    npm error     import gyp  # noqa: E402
    npm error     ^^^^^^^^^^
    npm error   File "/home/huzaifa/dev/taskmgr/node_modules/sqlite3/node_modules/node-gyp/gyp/pylib/gyp/__init__.py", line 9, in <module>
    npm error     import gyp.input
    npm error   File "/home/huzaifa/dev/taskmgr/node_modules/sqlite3/node_modules/node-gyp/gyp/pylib/gyp/input.py", line 19, in <module>
    npm error     from distutils.version import StrictVersion
    npm error ModuleNotFoundError: No module named 'distutils'
    npm error gyp ERR! configure error
    npm error gyp ERR! stack Error: `gyp` failed with exit code: 1
    npm error gyp ERR! stack     at ChildProcess.onCpExit (/home/huzaifa/dev/taskmgr/node_modules/sqlite3/node_modules/node-gyp/lib/configure.js:259:16)
    npm error gyp ERR! stack     at ChildProcess.emit (node:events:518:28)
    npm error gyp ERR! stack     at ChildProcess._handle.onexit (node:internal/child_process:293:12)
    npm error gyp ERR! System Linux 6.12.41
    npm error gyp ERR! command "/nix/store/ijsy113yzy0mpcr1sf0773nz9v0r7hff-nodejs-22.17.1/bin/node" "/home/huzaifa/dev/taskmgr/node_modules/sqlite3/node_modules/.bin/node-gyp" "rebuild"
    npm error gyp ERR! cwd /home/huzaifa/dev/taskmgr/node_modules/sqlite3
    npm error gyp ERR! node -v v22.17.1
    npm error gyp ERR! node-gyp -v v8.4.1
    npm error gyp ERR! not ok
    npm error A complete log of this run can be found in: /home/huzaifa/.npm/_logs/2025-10-27T18_14_32_797Z-debug-0.log

                    command=/nix/store/ijsy113yzy0mpcr1sf0773nz9v0r7hff-nodejs-22.17.1/bin/node /nix/store/ijsy113yzy0mpcr1sf0773nz9v0r7hff-nodejs-22.17.1/lib/node_modules/npm/bin/npm-cli.js rebuild bcrypt@5.1.1 sqlite3@5.1.7
                    workingDir=
npm error code 1
npm error path /home/huzaifa/dev/taskmgr
npm error command failed
npm error command sh -c electron-builder install-app-deps
npm error A complete log of this run can be found in: /home/huzaifa/.npm/_logs/2025-10-27T18_14_26_543Z-debug-0.log

Unable to find it online too, it then tries to build sqlite3 from source locally. I now faced another issue: even building it locally resulted in a failed compilation. Specifically:

    npm error ModuleNotFoundError: No module named 'distutils'

I guess the python version I got from nixpkgs didn't come bundled with distutils. Searching online, I saw that distutlils was a depreciated module (I guess) and wasn't being shipped in newer python versions. As my experience with Nix was somewhat new, installing a specific python version with a specific python module was difficult, as I didn't fully "get" the Nix language just yet.

Instead of fixing this and wasting time fixing something I won't use again, I searched for a better solution. better-sqlite3 has prebuilt binaries for many more ABIs than sqlite3. So after a quick look at their GitHub, I decided to go for it. I changed package.json:

  "dependencies": {
-    "sqlite": "^5.1.1",
-    "sqlite3": "^5.1.7",
+    "better-sqlite3": "^9.6.0",

To move to better-sqlite3, I also had to change all the source files to use its API rather than sqlite3's.

I also deleted node_modules/, ~/.electron-gyp for a fresh start. Doing npm install:

npm verbose pkgid better-sqlite3@9.6.0
npm error code 1
npm error path /home/huzaifa/dev/taskmgr/node_modules/better-sqlite3
npm error command failed
npm error command sh -c prebuild-install || node-gyp rebuild --release
npm error prebuild-install info begin Prebuild-install version 7.1.3
npm error prebuild-install info looking for local prebuild @ prebuilds/better-sqlite3-v9.6.0-node-v127-linux-x64.tar.gz
npm error prebuild-install info looking for cached prebuild @ /home/huzaifa/.npm/_prebuilds/26e7c7-better-sqlite3-v9.6.0-node-v127-linux-x64.tar.gz
npm error prebuild-install http request GET https://github.com/WiseLibs/better-sqlite3/releases/download/v9.6.0/better-sqlite3-v9.6.0-node-v127-linux-x64.tar.gz
npm error prebuild-install http 404 https://github.com/WiseLibs/better-sqlite3/releases/download/v9.6.0/better-sqlite3-v9.6.0-node-v127-linux-x64.tar.gz
npm error prebuild-install warn install No prebuilt binaries found (target=22.17.1 runtime=node arch=x64 libc= platform=linux)

Ohh. Scrolling up:

...
npm info using npm@10.9.2
npm info using node@v22.17.1
npm verbose title npm install
...

After fiddling around for a bit, I found out that while better-sqlite has prebuilt binaries for more targets that sqlite3, it still doesn't suppport the latest node version. I figured the only way to solve this issue would be to downgrade node itself. So I did just that. configuration.nix got a nodejs_20 and after nrs (nixos-rebuild switch), I ran npm install again:

npm info using npm@10.8.2
npm info using node@v20.19.4
npm verbose title npm install
...
npm info run better-sqlite3@9.6.0 install { code: 0, signal: null }
npm info run electron@28.3.3 postinstall node_modules/electron node install.js
npm info run electron@28.3.3 postinstall { code: 0, signal: null }

> taskmgr@1.0.0 postinstall
> electron-builder install-app-deps

  • electron-builder  version=24.13.3
  • loaded configuration  file=package.json ("build" field)
  • rebuilding native dependencies  dependencies=bcrypt@5.1.1, better-sqlite3@9.6.0 platform=linux arch=x64
  • install prebuilt binary  name=better-sqlite3 version=9.6.0 platform=linux arch=x64 napi=

changed 120 packages, and audited 533 packages in 19s

Nice! Now it correctly finds a suitable prebuilt version of better-sqlite3. Thanks prebuild-install!

Let's start the app by running npm start:

$ npm start

> taskmgr@1.0.0 start
> electron .

/home/huzaifa/dev/taskmgr/node_modules/electron/dist/electron: error while loading shared libraries: libgobject-2.0.so.0: cannot open shared object file: No such file or directory

Ohoh. Another Nix battle. Looks like electron relies on a number of shared libraries that are missing from my system. Let's check which libraries are needed:

$ ldd node_modules/electron/dist/electron
        linux-vdso.so.1 (0x00007fab3e510000)
        libffmpeg.so => /home/huzaifa/dev/taskmgr/node_modules/electron/dist/libffmpeg.so (0x00007fab33400000)
        libdl.so.2 => /nix/store/g8zyryr9cr6540xsyg4avqkwgxpnwj2a-glibc-2.40-66/lib/libdl.so.2 (0x00007fab3e505000)
        libpthread.so.0 => /nix/store/g8zyryr9cr6540xsyg4avqkwgxpnwj2a-glibc-2.40-66/lib/libpthread.so.0 (0x00007fab3e500000)
        libgobject-2.0.so.0 => not found
        libglib-2.0.so.0 => not found
        libgio-2.0.so.0 => not found
        libnss3.so => not found
        libnssutil3.so => not found
        libsmime3.so => not found
        libnspr4.so => not found
        libdbus-1.so.3 => not found
        libatk-1.0.so.0 => not found
        libatk-bridge-2.0.so.0 => not found
        libcups.so.2 => not found
        libdrm.so.2 => not found
        libgtk-3.so.0 => not found
        libpango-1.0.so.0 => not found
        libcairo.so.2 => not found
        libX11.so.6 => not found
        libXcomposite.so.1 => not found
        libXdamage.so.1 => not found
        libXext.so.6 => not found
        libXfixes.so.3 => not found
        libXrandr.so.2 => not found
        libgbm.so.1 => not found
        libexpat.so.1 => not found
        libxcb.so.1 => not found
        libxkbcommon.so.0 => not found
        libasound.so.2 => not found
        libatspi.so.0 => not found
        libm.so.6 => /nix/store/g8zyryr9cr6540xsyg4avqkwgxpnwj2a-glibc-2.40-66/lib/libm.so.6 (0x00007fab33317000)
        libgcc_s.so.1 => /nix/store/16hvpw4b3r05girazh4rnwbw0jgjkb4l-xgcc-14.3.0-libgcc/lib/libgcc_s.so.1 (0x00007fab3e4c6000)
        libc.so.6 => /nix/store/g8zyryr9cr6540xsyg4avqkwgxpnwj2a-glibc-2.40-66/lib/libc.so.6 (0x00007fab33000000)
        /lib64/ld-linux-x86-64.so.2 => /nix/store/g8zyryr9cr6540xsyg4avqkwgxpnwj2a-glibc-2.40-66/lib64/ld-linux-x86-64.so.2 (0x00007fab3e512000)

Too many. Now, in a typical distro, you'd have the FHS consisting of /usr with subdirectories bin, lib, include, share, etc. In Nix, there is no typical FHS. Instead packages are stored in /nix/store so that there can't be any unintended dependency on a package that is not explicitly installed by the application. The way you'd add dependencies in Nix is by either creating a derivation with a .nix file that specifies the inputs/outputs that the derivation requires/builds. Another way is to create a shell with the dependencies included explicitly.

nix-shell -p [packages] will create a new shell with those packages included in the environment. But there's a problem. electron, which is downloaded through npm is not patched, and requires the dependent libraries be in FHS-compatible directories, not /nix/store, as we saw above. If we had installed electron through nixpkgs, then there would be no problem as all the dependencies would be preinstalled beforehand by Nix and electron would correctly look for them in /nix/store.

A crude and hardcoded way to solve this problem would be to add the path to the different libraries in /nix/store to LD_LIBRARY_PATH. But each library is in a separate directory inside /nix/store and the path changes version to version. It will be just like fighting with Nix just to make it work.

After doing what I know best (googling) I found a good enough solution for our problem. What we need to do, is to create a sandbox FHS-compliant shell to run electron in, where it can find those libraries in /usr/lib, but in reality they would be symlinked to /nix/store. We need to use pkgs.buildFHSEnv for this:

{ pkgs ? import <nixpkgs> {} }:

(pkgs.buildFHSEnv {
  name = "electron-env";
  targetPkgs = pkgs: (with pkgs;
    [
      nodejs python3 libcxx systemd libpulseaudio libdrm mesa stdenv.cc.cc
      alsa-lib atk at-spi2-atk at-spi2-core cairo cups dbus expat fontconfig
      freetype gdk-pixbuf glib gtk3 libnotify libuuid nspr nss pango systemd
      libappindicator-gtk3 libdbusmenu libgbm libxkbcommon zlib
    ]
  ) ++ (with pkgs.xorg;
    [
      libXScrnSaver libXrender libXcursor libXdamage libXext libXfixes libXi
      libXrandr libX11 libXcomposite libxshmfence libXtst libxcb
    ]
  );
}).env
shell.nix

Now running nix-shell in the directory of shell.nix will create a new shell with the FHS environment with the libraries specified residing in /usr/lib. Running npm start now works!