# use:
# - nix develop
# - mkdir cache-yarn ; rm -rf ~/.cache/yarn ; ln -s $(pwd)/cache-yarn ~/.cache/yarn
# - mkdir cache-node-gyp ; rm -rf ~/.cache/node-gyp ; ln -s $(pwd)/cache-node-gyp ~/.cache/node-gyp
# - mkdir cache-electron ; rm -rf ~/.cache/electron ; ln -s $(pwd)/cache-electron ~/.cache/electron
# - yarn install --frozen-lockfile --ignore-engines
# - yarn generate
# - yarn start  (or, `electron .`)
# - patch-signal release/linux-unpacked/signal-desktop
#
# contributing.md recommends:
# > npm install --global yarn      # Make sure you have have `yarn`
# > yarn install --frozen-lockfile # Install and build dependencies (this will take a while)
# > yarn generate                  # Generate final JS and CSS assets
# > yarn test                      # A good idea to make sure tests run first
# > yarn start                     # Start Signal!
#
# a build process which creates `release/linux-unpacked/signal-desktop`:
# - yarn generate
# - yarn build:esbuild:prod
# - yarn build:release
#
# .github/workflows/ci.yml linux build is:
# - yarn generate
# - yarn prepare-beta-build
# - yarn build:esbuild:prod
# - DISABLE_INSPECT_FUSE=on yarn build:release -- --publish=never
# - DISABLE_INSPECT_FUSE=on yarn build:release --linux dir
# - yarn test-node
# - ARTIFACTS_DIR=artifacts/linux LANG=en_US LANGUAGE=en_US yarn test-electron
# - NODE_ENV=production yarn test-release
#
# fix for specific problems:
# - linux cannot execute required file not found
#   - patchelf --set-interpreter /nix/store/gqghjch4p1s69sv4mcjksb2kb65rwqjy-glibc-2.38-23/lib/ld-linux-x86-64.so.2 /home/colin/ref/repos/signalapp/Signal-Desktop/node_modules/electron/dist/electron
# - /home/colin/.cache/electron-builder/fpm/fpm-1.9.3-2.3.1-linux-x86_64/fpm: no such file or directory
#   - change fpm's interpreter from #!/bin/bash to #!/bin/sh
#   - do the same with the `ruby` packaged in fpm
#
# electron flags to consider
# - --no-sandbox
# - --no-zygote   (fixes Zygote could not fork: process_type gpu-process numfds 3 child_pid -1)
# - --disable-gpu
#
#
# debugging notes
# `./node_modules/.bin/electron .` is reading `./package.json`.
#  - dependent on:   "main": "app/main.js"
#    app/main.js is generated (from app/main.ts, checked into git)
# - yarn install --ignore-scripts:
#   - ringrtc uses a script, to DL https://build-artifacts.signal.org/libraries/ringrtc-desktop-build-v2.33.0.tar.gz
#   - it's in package.scripts.install
#   - node scripts/fetch-prebuild.js
#   - wants it to exist as `prebuild.tar.gz`  (i could provide that for it)
{
  inputs = {
    nixpkgs.url = "nixpkgs/nixos-unstable";
    flake-utils.url = github:numtide/flake-utils;
  };
  outputs = { self, nixpkgs, flake-utils }:
    with flake-utils.lib; eachSystem allSystems (system:
      let
        pkgs = import nixpkgs { inherit system; };
        ringrtcPrebuild = builtins.fetchurl {
          url = "https://build-artifacts.signal.org/libraries/ringrtc-desktop-build-v2.33.0.tar.gz";
          sha256 = "sha256:0zmgjax0ycjkvhqz1hybfi799lzss52s1cd8hzbqnm4ka3b1lhsf";
        };
        sqlcipher = builtins.fetchurl {
          url = "https://build-artifacts.signal.org/desktop/sqlcipher-4.5.5-fts5-fix--3.0.7--0.2.1-ef53ea45ed92b928ecfd33c552d8d405263e86e63dec38e1ec63e1b0193b630b.tar.gz";
          sha256 = "sha256:02v37ccv1qb3xkhkiv1xws33w9h5skc55i9kzpn2ifcjxm2yllzg";
        };
        nodejs_18_15_0 = pkgs.nodejs.overrideAttrs (upstream:
        let
          # 18.15.0 matches the version in package.json
          version = "18.15.0";
          sha256 = "sha256-jkTWUBj/lzKEGVwjGGRpoOpAgul+xCAOX1cG1VhNqjc=";
        in {
          inherit version;
          src = builtins.fetchurl {
            url = "https://nodejs.org/dist/v${version}/node-v${version}.tar.xz";
            inherit sha256;
          };
        });
        yarn_18_15_0 = (pkgs.yarn.override { nodejs = nodejs_18_15_0; }).overrideAttrs (upstream: {
          # patches = (upstream.patches or []) ++ [
          #   ./yarn-allow-extraneous.patch
          # ];
          # force yarn to never delete files; that way i can install the ringrtc prebuild w/o yarn deleting it.
          # postPatch = (upstream.postPatch or "") + ''
          #   substituteInPlace lib/cli.js \
          #     --replace 'yield (_fs || _load_fs()).unlink' '// yield (_fs || _load_fs()).unlink'
          # '';
          # preFixup = (upstream.preFixup or "") + ''
          #   substituteInPlace $out/libexec/yarn/lib/cli.js --replace \
          #     "this.reporter.verbose(this.reporter.lang('verboseFileRemoveExtraneous', _loc6));" \
          #     "if (false)"
          # '';
        });
        electronDeps = with pkgs; [
          # packages which electron dynamically links against
          alsa-lib
          atk
          cairo
          cups
          dbus
          expat
          glib
          gtk3
          libdrm
          libxkbcommon
          mesa
          nss
          nspr
          pango
          xorg.libX11
          xorg.libXfixes
          xorg.libXext
          xorg.libXcomposite
          xorg.libXdamage
          xorg.libXrandr
          xorg.libxcb

          systemd  # fixes "Zygote could not fork: process_type gpu-process" when launching signal-desktop
        ];
        _7zaDeps = with pkgs; [
          libgcc.lib
        ];
        fpmDeps = with pkgs; [
          libxcrypt-legacy
        ];
        patch-interpreter = with pkgs; writeShellScriptBin "patch-interpreter" ''
          patchelf --set-interpreter ${glibc}/lib/ld-linux-x86-64.so.2 $1
        '';
        patch-signal = with pkgs; writeShellScriptBin "patch-signal" ''
          patchelf --add-needed ${nss}/lib/libnss3.so $1
          patchelf --add-needed ${nss}/lib/libnssutil3.so $1
          patchelf --add-needed ${nss}/lib/libsmime3.so $1
          patchelf --add-needed ${lib.getLib cups}/lib/libcups.so.2 $1
          patchelf --add-needed ${cairo}/lib/libcairo.so.2 $1
          patchelf --add-needed ${lib.getLib pango}/lib/libpango-1.0.so.0 $1
          patchelf --add-needed ${at-spi2-core}/lib/libatspi.so.0 $1
          patchelf --add-needed ${alsa-lib}/lib/libasound.so.2 $1
          patchelf --add-needed ${atk}/lib/libatk-1.0.so.0 $1
          patchelf --add-needed ${atk}/lib/libatk-bridge-2.0.so.0 $1
          patchelf --add-needed ${lib.getLib glib}/lib/libgobject-2.0.so.0 $1
          patchelf --add-needed ${lib.getLib glib}/lib/libglib-2.0.so.0 $1
          patchelf --add-needed ${lib.getLib glib}/lib/libgio-2.0.so.0 $1
          patchelf --add-needed ${gtk3}/lib/libgtk-3.so.0 $1
          patchelf --add-needed ${libdrm}/lib/libdrm.so.2 $1
          patchelf --add-needed ${nspr}/lib/libnspr4.so $1
          patchelf --add-needed ${xorg.libXfixes}/lib/libXfixes.so.3 $1
          patchelf --add-needed ${xorg.libXext}/lib/libXext.so.6 $1
          patchelf --add-needed ${xorg.libXcomposite}/lib/libXcomposite.so.1 $1
          patchelf --add-needed ${xorg.libXdamage}/lib/libXdamage.so.1 $1
          patchelf --add-needed ${xorg.libXrandr}/lib/libXrandr.so.2 $1
          patchelf --add-needed ${xorg.libxcb}/lib/libxcb.so.1 $1
          patchelf --add-needed ${xorg.libX11}/lib/libX11.so.6 $1
          patchelf --add-needed ${libxkbcommon}/lib/libxkbcommon.so.0 $1
          patchelf --add-needed ${expat}/lib/libexpat.so.1 $1
          patchelf --add-needed ${lib.getLib dbus}/lib/libdbus-1.so.3 $1
          patchelf --add-needed ${lib.getLib mesa}/lib/libgbm.so.1 $1
        '';
        clean = with pkgs; writeShellScriptBin "clean" ''
          rm -rf \
            cache-* \
            node_modules \
            preload.bundle.js \
            release/ \
            .eslintcache \
            bundles \
            ts/protobuf/compiled.d.ts \
        '';
        link-caches = with pkgs; writeShellScriptBin "link-caches" ''
          mkdir cache-yarn ; rm -rf ~/.cache/yarn ; ln -s $(pwd)/cache-yarn ~/.cache/yarn
          mkdir cache-node-gyp ; rm -rf ~/.cache/node-gyp ; ln -s $(pwd)/cache-node-gyp ~/.cache/node-gyp
          mkdir cache-electron ; rm -rf ~/.cache/electron ; ln -s $(pwd)/cache-electron ~/.cache/electron
          mkdir cache-ms-playwright ; rm -rf ~/.cache/ms-playwright ; ln -s $(pwd)/cache-ms-playwright ~/.cache/ms-playwright
        '';

        # run this to clean state, and then build + run Signal in a way which i know WORKS.
        # you should land at the Signal pairing screen at the end of the command.
        reproducible0 = with pkgs; writeShellScriptBin "reproducible0" ''
          clean
          link-caches
          yarn install --frozen-lockfile
          yarn generate
          yarn build:esbuild:prod
          patch-interpreter node_modules/electron/dist/electron
          # yarn start
          ./node_modules/.bin/electron .  # equiv to `yarn start`
        '';
        reproducible1 = with pkgs; writeShellScriptBin "reproducible1" ''
          clean
          link-caches
          yarn install --frozen-lockfile
          yarn generate
          yarn build:esbuild:prod
          patch-interpreter node_modules/electron/dist/electron
          # this fails because it tries to also build the deb -- but we don't need that!
          yarn build:release || true
          patch-interpreter release/linux-unpacked/signal-desktop

          echo "launch signal with: ./release/linux-unpacked/signal-desktop"
        '';
        reproducible2 = with pkgs; writeShellScriptBin "reproducible2" ''
          clean
          link-caches
          yarn install --frozen-lockfile
          yarn generate
          yarn build:esbuild:prod
          # this fails during the "fuse" process, which is ok to skip
          yarn build:release \
            -c.electronDist=${electron}/libexec/electron \
            -c.electronVersion=${electron.version} \
            --dir \
            || true

          patch-interpreter release/linux-unpacked/signal-desktop

          # nixpkgs electron doesn't seem to support zygote/GPU.
          # not electron (27); not electron_25
          echo "launch signal with: ./release/linux-unpacked/signal-desktop --no-sandbox --no-zygote"
        '';
        reproducible3 = with pkgs; writeShellScriptBin "reproducible3" ''
          clean
          link-caches
          yarn install --frozen-lockfile
          yarn generate
          yarn build:esbuild:prod
          # this fails during the "fuse" process, which is ok to skip
          yarn build:release \
            -c.electronDist=${electron-bin}/libexec/electron \
            -c.electronVersion=${electron-bin.version} \
            --dir \
            || true

          # patch-interpreter release/linux-unpacked/signal-desktop

          # electron-bin (26.3.0) DOES support default flags (i.e. zygote)
          # echo "launch signal with: ${electron-bin}/libexec/electron/electron ./release/linux-unpacked/resources/app.asar"
          echo "launch signal with: ${electron-bin}/bin/electron ./release/linux-unpacked/resources/app.asar"  # same as libexec
        '';
        reproducible4 = with pkgs; writeShellScriptBin "reproducible4" ''
          clean
          link-caches

          yarn install --frozen-lockfile
          # yarn generate --offline --frozen-lockfile
          # apparently this isn't equivalent to `yarn generate --offline --frozen-lockfile`!
          yarn build-module-protobuf --offline --frozen-lockfile
          yarn build:esbuild --offline --frozen-lockfile
          yarn sass
          yarn get-expire-time
          yarn copy-components

          yarn build:esbuild:prod --offline --frozen-lockfile
          # this fails during the "fuse" process, which is ok to skip
          yarn build:release \
            -c.electronDist=${electron-bin}/libexec/electron \
            -c.electronVersion=${electron-bin.version} \
            --dir \
            || true

          # electron-bin (26.3.0) DOES support default flags (i.e. zygote)
          # echo "launch signal with: ${electron-bin}/libexec/electron/electron ./release/linux-unpacked/resources/app.asar"
          echo "launch signal with: ${electron-bin}/bin/electron ./release/linux-unpacked/resources/app.asar"  # same as libexec
        '';
        reproducible5 = with pkgs; writeShellScriptBin "reproducible5" ''
          set -x
          clean
          link-caches

          # mkdir -p "$HOME/.node-gyp/${nodejs_18_15_0.version}"
          # echo 9 > "$HOME/.node-gyp/${nodejs_18_15_0.version}/installVersion"
          # ln -sfv "$(pwd)/include" "$HOME/.node-gyp/${nodejs_18_15_0.version}"
          # export npm_config_nodedir=${nodejs_18_15_0}

          yarn install --frozen-lockfile --ignore-scripts
          cp ${ringrtcPrebuild} node_modules/@signalapp/ringrtc/scripts/prebuild.tar.gz
          cp ${sqlcipher} node_modules/@signalapp/better-sqlite3/deps/sqlcipher.tar.gz
          # N.B.: without patched yarn, this deletes the vendored dependencies i just installed
          yarn install --frozen-lockfile

          # N.B.: `yarn install` ships node_modules/@signalapp/better-sqlite3/build/Release/better_sqlite3.node
          # also build/Release/obj.target/better_sqlite3.noe
          # also libcrypto.a, libsignal_tokenizer.a
          # - both of these for x64 and aarch64
          # also sqlite3.a
          # remove build/Release to remove allof those prebuilts
          rm -rf node_modules/@signalapp/better-sqlite3/build/Release

          npm rebuild @signalapp/better-sqlite3  --offline

          # yarn generate --offline --frozen-lockfile
          # apparently this isn't equivalent to `yarn generate --offline --frozen-lockfile`!
          yarn build-module-protobuf --offline --frozen-lockfile
          yarn build:esbuild --offline --frozen-lockfile
          yarn sass
          yarn get-expire-time
          yarn copy-components

          yarn build:esbuild:prod --offline --frozen-lockfile
          # this fails during the "fuse" process, which is ok to skip
          yarn build:release \
            -c.electronDist=${electron-bin}/libexec/electron \
            -c.electronVersion=${electron-bin.version} \
            --dir \
            || true

          # patch-interpreter release/linux-unpacked/signal-desktop

          # electron-bin (26.3.0) DOES support default flags (i.e. zygote)
          # echo "launch signal with: ${electron-bin}/libexec/electron/electron ./release/linux-unpacked/resources/app.asar"
          echo "launch signal with: ${electron-bin}/bin/electron ./release/linux-unpacked/resources/app.asar"  # same as libexec
        '';

        reproducible6 = with pkgs; writeShellScriptBin "reproducible6" ''
          set -x
          clean
          link-caches

          yarn install --frozen-lockfile --ignore-scripts
          # these packages have build scripts:
          # - [x] node_modules/@signalapp/ringrtc/package.json
          #   - 22:    "install": "node scripts/fetch-prebuild.js",
          # - [x] node_modules/@signalapp/mock-server/node_modules/@signalapp/libsignal-client/package.json
          #   - 19:    "install": "node-gyp-build",
          # - [x] node_modules/@signalapp/better-sqlite3/package.json
          #   - 37:    "install": "npm run download && npm run build-release",
          # - [x] node_modules/@signalapp/libsignal-client/package.json
          #   - 19:    "install": "node-gyp-build",
          #
          # - [x] node_modules/bufferutil/package.json
          #   - 10:    "install": "node-gyp-build",
          # - [x] node_modules/mac-screen-capture-permissions/package.json
          #   - 18:    "install": "prebuild-install || node install.js"
          # - [x] node_modules/playwright/package.json
          #   - 28:    "install": "node install.js"
          # - [x] node_modules/utf-8-validate/package.json
          #   - 10:   "install": "node-gyp-build",
          #
          # several packages also have a postinstall in their package.json
          # - [x] node_modules/@babel/runtime-corejs3/node_modules/core-js-pure/package.json
          #   - 55:    "postinstall": "node -e \"try{require('./postinstall')}catch(e){}\""
          # - [x] node_modules/@storybook/builder-manager/node_modules/esbuild/package.json
          #   - 7:    "postinstall": "node install.js"
          # - [x] node_modules/@storybook/core-common/node_modules/esbuild/package.json
          #   - 7:    "postinstall": "node install.js"
          # - [x] node_modules/@swc/core/package.json
          #   - 51:    "postinstall": "node postinstall.js",
          # - [x] node_modules/core-js-pure/package.json
          #   - 71:    "postinstall": "node -e \"try{require('./postinstall')}catch(e){}\""
          # - [x] node_modules/core-js/package.json
          #   - 41:    "postinstall": "node scripts/postinstall || echo \"ignore\""
          # - [x] node_modules/danger/node_modules/core-js/package.json
          #   - 55:    "postinstall": "node -e \"try{require('./postinstall')}catch(e){}\""
          # - [x] node_modules/electron/package.json
          #   - 8:    "postinstall": "node install.js"
          # - [x] node_modules/esbuild/package.json
          #   - 7:    "postinstall": "node install.js"
          # - [x] node_modules/protobufjs/package.json
          #   - 43:    "postinstall": "node scripts/postinstall",
          # - [x] node_modules/websocket/node_modules/es5-ext/package.json
          #   - 115:   "postinstall": " node -e \"try{require('./_postinstall')}catch(e){}\" || exit 0",

          cp ${ringrtcPrebuild} node_modules/@signalapp/ringrtc/scripts/prebuild.tar.gz
          pushd node_modules/@signalapp/ringrtc/
            tar -xzf ./scripts/prebuild.tar.gz
          popd
          cp ${sqlcipher} node_modules/@signalapp/better-sqlite3/deps/sqlcipher.tar.gz

          # pushd node_modules/@signalapp/libsignal-client
          #   yarn node-gyp-build
          # popd
          # pushd node_modules/@signalapp/mock-server/node_modules/@signalapp/libsignal-client
          #   yarn node-gyp-build
          # popd
          # pushd node_modules/bufferutil
          #   yarn node-gyp-build
          # popd
          # pushd node_modules/bufferutil
          #   yarn node-gyp-build
          # popd
          # pushd node_modules/mac-screen-capture-permissions
          #   yarn prebuild-install || node install.js
          # popd
          # pushd node_modules/playwright
          #   node install.js
          # popd
          # pushd node_modules/utf-8-validate
          #   yarn node-gyp-build
          # popd

          # pushd node_modules/@babel/runtime-corejs3/node_modules/core-js-pure
          #   node -e "try{require('./postinstall')}catch(e){}"
          # popd
          # pushd node_modules/@swc/core
          #   node postinstall.js
          # popd
          # pushd node_modules/@storybook/builder-manager/node_modules/esbuild
          #   node install.js
          # popd
          # pushd node_modules/@storybook/core-common/node_modules/esbuild
          #   node install.js
          # popd
          # pushd node_modules/core-js-pure
          #   node -e "try{require('./postinstall')}catch(e){}"
          # popd
          # pushd node_modules/core-js
          #   node scripts/postinstall || echo "ignore"
          # popd
          # pushd node_modules/danger/node_modules/core-js
          #   node -e "try{require('./postinstall')}catch(e){}"
          # popd
          # pushd node_modules/electron
          #   node install.js
          # popd
          # pushd node_modules/esbuild
          #   node install.js
          # popd
          # pushd node_modules/protobufjs
          #   node scripts/postinstall
          # popd
          # pushd node_modules/websocket/node_modules/es5-ext
          #   node -e "try{require('./_postinstall')}catch(e){}"
          # popd

          # run signal's own `postinstall`:
          yarn build:acknowledgments
          yarn patch-package
          # yarn electron:install-app-deps

          # N.B.: `yarn install` ships node_modules/@signalapp/better-sqlite3/build/Release/better_sqlite3.node
          # specifically, better-sqlite3's package.json install script does that
          # also build/Release/obj.target/better_sqlite3.node
          # also libcrypto.a, libsignal_tokenizer.a
          # - both of these for x64 and aarch64
          # also sqlite3.a
          # remove build/Release to remove all of those prebuilts
          # rm -rf node_modules/@signalapp/better-sqlite3/build/Release

          # npm rebuild @signalapp/better-sqlite3  --offline

          # yarn generate --offline --frozen-lockfile
          # apparently this isn't equivalent to `yarn generate --offline --frozen-lockfile`!
          yarn build-module-protobuf --offline --frozen-lockfile
          yarn build:esbuild --offline --frozen-lockfile
          yarn sass
          yarn get-expire-time
          yarn copy-components

          yarn build:esbuild:prod --offline --frozen-lockfile
          # this fails during the "fuse" process, which is ok to skip
          yarn build:release \
            -c.electronDist=${electron-bin}/libexec/electron \
            -c.electronVersion=${electron-bin.version} \
            --dir \
            || true

          # patch-interpreter release/linux-unpacked/signal-desktop

          # electron-bin (26.3.0) DOES support default flags (i.e. zygote)
          # echo "launch signal with: ${electron-bin}/libexec/electron/electron ./release/linux-unpacked/resources/app.asar"
          echo "launch signal with: ${electron-bin}/bin/electron ./release/linux-unpacked/resources/app.asar"  # same as libexec
        '';

        reproducible7 = with pkgs; writeShellScriptBin "reproducible7" ''
          set -x
          clean
          link-caches

          # prevent node-gyp from trying to download nodejs headers
          # mkdir -p "$HOME/.node-gyp/${nodejs_18_15_0.version}"
          # echo 9 > "$HOME/.node-gyp/${nodejs_18_15_0.version}/installVersion"
          # ln -sfv "$(pwd)/include" "$HOME/.node-gyp/${nodejs_18_15_0.version}"
          # export npm_config_nodedir=${nodejs_18_15_0}

          # export npm_config_nodedir=${nodejs_18_15_0}

          tar xzf ${electron-bin.headers}
          export npm_config_nodedir=$(pwd)/node_headers

          yarn install --frozen-lockfile --ignore-scripts

          cp ${ringrtcPrebuild} node_modules/@signalapp/ringrtc/scripts/prebuild.tar.gz
          pushd node_modules/@signalapp/ringrtc/
            tar -xzf ./scripts/prebuild.tar.gz
          popd
          cp ${sqlcipher} node_modules/@signalapp/better-sqlite3/deps/sqlcipher.tar.gz
          pushd node_modules/@signalapp/better-sqlite3
            yarn --offline build-release
          popd

          # run signal's own `postinstall`:
          yarn build:acknowledgments
          yarn patch-package
          # yarn electron:install-app-deps

          # yarn generate --offline --frozen-lockfile
          # apparently this isn't equivalent to `yarn generate --offline --frozen-lockfile`!
          yarn build-module-protobuf --offline --frozen-lockfile
          yarn build:esbuild --offline --frozen-lockfile
          yarn sass
          yarn get-expire-time
          yarn copy-components

          yarn build:esbuild:prod --offline --frozen-lockfile
          # this fails during the "fuse" process, which is ok to skip
          yarn build:release \
            -c.electronDist=${electron-bin}/libexec/electron \
            -c.electronVersion=${electron-bin.version} \
            --dir \
            || true

          # patch-interpreter release/linux-unpacked/signal-desktop

          # electron-bin (26.3.0) DOES support default flags (i.e. zygote)
          echo "launch signal with: ${electron-bin}/bin/electron ./release/linux-unpacked/resources/app.asar"
        '';

        LD_LIBRARY_PATH = builtins.concatStringsSep ":" (builtins.map
          (pkg: "${pkgs.lib.getLib pkg}/lib")
          (electronDeps ++ _7zaDeps ++ fpmDeps)
        );
      in rec {
        devShells.default = with pkgs; mkShell {
          env = { inherit LD_LIBRARY_PATH; };
          buildInputs = [
            clean
            link-caches
            patch-interpreter
            patch-signal
            reproducible0
            reproducible1
            reproducible2
            reproducible3
            reproducible4
            reproducible5
            reproducible6
            reproducible7

            # yarn
            # nodejs
            # nodejs 18.18.2 causes signal to hang on launch.
            # 18.15.0 (matching package.json) works!
            yarn_18_15_0
            nodejs_18_15_0  # needed for `yarn install` bettersqlite step

            # electron

            # ruby  # needed by fpm (?)

            # this seems to all not be needed, thanks to LD_LIBRARY_PATH above
            # alsa-lib  # necessary?
            # at-spi2-atk
            # at-spi2-core
            # atk
            # cups
            # # electron
            # flac
            # gdk-pixbuf
            # gtk3
            # libpulseaudio
            # libwebp
            # libxslt
            # mesa # for libgbm
            # nspr
            # nss
            # pango
          ];
        };

        packages.yarn_18_15_0 = yarn_18_15_0;
        packages.sqlcipher = sqlcipher;
        packages.ringrtcPrebuild = ringrtcPrebuild;
        packages.signal-desktop-from-src = with pkgs; stdenv.mkDerivation {
          pname = "signal-desktop-from-src";
          version = "6.36.0";
          src = ./.;

          env = {
            inherit LD_LIBRARY_PATH;
            yarnOfflineCache = fetchYarnDeps {
              # this might be IFD: if `nix run '.#check.nur'` fails then inline the lock: `yarnLock = ./yarn.lock`
              yarnLock = ./yarn.lock;
              hash = "sha256-AXT6p5lgF0M9ckoxiAvT1HaJhUWVtwEOadY4otdeB0Q=";
            };
            # ELECTRON_SKIP_BINARY_DOWNLOAD = "1";
          };
          nativeBuildInputs = with pkgs; [
            fixup_yarn_lock
            makeWrapper
            nodejs_18_15_0  # needed for `yarn install` bettersqlite step
            python3
            yarn_18_15_0
          ];

          configurePhase = ''
            runHook preConfigure
            set -x

            export HOME=$NIX_BUILD_TOP
            yarn config --offline set yarn-offline-mirror $yarnOfflineCache
            fixup_yarn_lock yarn.lock

            # prevent node-gyp from trying to download nodejs headers
            # THIS BREAKS better-sqlite ABI
            # mkdir -p "$HOME/.node-gyp/${nodejs_18_15_0.version}"
            # echo 9 > "$HOME/.node-gyp/${nodejs_18_15_0.version}/installVersion"
            # ln -sfv "$(pwd)/include" "$HOME/.node-gyp/${nodejs_18_15_0.version}"
            # export npm_config_nodedir=${nodejs_18_15_0}

            tar xzf ${electron-bin.headers}
            export npm_config_nodedir=$(pwd)/node_headers

            # optional flags:  --no-progress --non-interactive
            # yarn install creates the node_modules/ directory
            yarn install --offline --frozen-lockfile --ignore-scripts
            patchShebangs node_modules/

            # manually emulate install scripts for key dependencies
            cp ${ringrtcPrebuild} node_modules/@signalapp/ringrtc/scripts/prebuild.tar.gz
            pushd node_modules/@signalapp/ringrtc/
              tar -xzf ./scripts/prebuild.tar.gz
            popd
            cp ${sqlcipher} node_modules/@signalapp/better-sqlite3/deps/sqlcipher.tar.gz
            pushd node_modules/@signalapp/better-sqlite3
              yarn --offline build-release
            popd
            pushd node_modules/@signalapp/libsignal-client
              yarn node-gyp-build
            popd
            pushd node_modules/@signalapp/mock-server/node_modules/@signalapp/libsignal-client
              yarn node-gyp-build
            popd
            pushd node_modules/bufferutil
              yarn node-gyp-build
            popd
            # pushd node_modules/mac-screen-capture-permissions
            #   yarn prebuild-install || node install.js
            # popd
            # pushd node_modules/playwright
            #   node install.js
            # popd
            pushd node_modules/utf-8-validate
              yarn node-gyp-build
            popd

            pushd node_modules/@babel/runtime-corejs3/node_modules/core-js-pure
              node -e "try{require('./postinstall')}catch(e){}"
            popd
            pushd node_modules/@swc/core
              node postinstall.js
            popd
            pushd node_modules/@storybook/builder-manager/node_modules/esbuild
              node install.js
            popd
            pushd node_modules/@storybook/core-common/node_modules/esbuild
              node install.js
            popd
            pushd node_modules/core-js-pure
              node -e "try{require('./postinstall')}catch(e){}"
            popd
            pushd node_modules/core-js
              node scripts/postinstall || echo "ignore"
            popd
            pushd node_modules/danger/node_modules/core-js
              node -e "try{require('./postinstall')}catch(e){}"
            popd
            # pushd node_modules/electron
            #   node install.js
            # popd
            pushd node_modules/esbuild
              node install.js
            popd
            pushd node_modules/protobufjs
              node scripts/postinstall
            popd
            pushd node_modules/websocket/node_modules/es5-ext
              node -e "try{require('./_postinstall')}catch(e){}"
            popd

            # run signal's own `postinstall`:
            yarn build:acknowledgments
            yarn patch-package
            yarn electron:install-app-deps

            runHook postConfigure
          '';

          buildPhase = ''
            runHook preBuild

            set -x

            # rebuild better-sqlite to avoid using prebuilts
            # - experiment to fix `_ZN2v812api_internal18GlobalizeReferenceEPNS_8internal7IsolateEPm` undefined symbol (doesn't fix)
            # rm -rf node_modules/@signalapp/better-sqlite3/build/Release
            # npm rebuild @signalapp/better-sqlite3  --offline

            # yarn generate
            yarn build-module-protobuf --offline --frozen-lockfile
            yarn build:esbuild --offline --frozen-lockfile
            yarn sass
            yarn get-expire-time
            yarn copy-components

            yarn build:esbuild:prod --offline --frozen-lockfile
            # this fails during the "fuse" process, which is ok to skip
            yarn build:release \
              -c.electronDist=${electron-bin}/libexec/electron \
              -c.electronVersion=${electron-bin.version} \
              --dir \
              || true

            runHook postBuild
          '';

          installPhase = ''
            runHook preInstall

            # cp -R ./release $out

            # directory structure follows the original `signal-desktop` nix package
            mkdir -p $out/lib/Signal
            cp -R release/linux-unpacked/* $out/lib/Signal
            mkdir $out/bin
            makeWrapper ${electron-bin}/bin/electron $out/bin/signal-desktop \
              --add-flags $out/lib/Signal/resources/app.asar \
              --add-flags "\''${NIXOS_OZONE_WL:+\''${WAYLAND_DISPLAY:+--ozone-platform-hint=auto --enable-features=WaylandWindowDecorations}}" \
              --inherit-argv0

            runHook postInstall
          '';
        };
        defaultPackage = packages.signal-desktop-from-src;
      }
    )
  ;
}

