From c34e7d584b3ee637dd0d205f7c2c888c385e9bef Mon Sep 17 00:00:00 2001 From: Sami Ansari <74448287+y0usaf@users.noreply.github.com> Date: Wed, 11 Feb 2026 00:47:39 +0000 Subject: [PATCH] fix(nix): build from source instead of wrapping AppImage (#778) * fix(nix): build from source instead of wrapping AppImage (#701) Replace the AppImage-based Nix package with a native from-source build using cargo-tauri.hook + rustPlatform.buildRustPackage. This eliminates the recurring hash mismatch failures when releases update the binary. - Build frontend (bun) and backend (cargo tauri) from source - Vendor JS deps as a fixed-output derivation - Patch libappindicator-sys for Nix store path - Patch ferrous-opencc to skip cbindgen (upstream removed in v0.3.1+) - Wrap binary with WEBKIT_DISABLE_DMABUF_RENDERER and LD_LIBRARY_PATH - Delete update-flake-hash.yml workflow (no longer needed) - devShell unchanged * add support for arm64 --------- Co-authored-by: CJ Pais --- .github/workflows/update-flake-hash.yml | 74 ----------- flake.nix | 168 ++++++++++++++++++++---- 2 files changed, 145 insertions(+), 97 deletions(-) delete mode 100644 .github/workflows/update-flake-hash.yml diff --git a/.github/workflows/update-flake-hash.yml b/.github/workflows/update-flake-hash.yml deleted file mode 100644 index 159e10c..0000000 --- a/.github/workflows/update-flake-hash.yml +++ /dev/null @@ -1,74 +0,0 @@ -name: "Update Flake Hash" - -on: - release: - types: [published] - -permissions: - contents: write - -jobs: - update-flake: - runs-on: ubuntu-latest - steps: - - name: Checkout repository - uses: actions/checkout@v4 - - - name: Install Nix - uses: DeterminateSystems/nix-installer-action@main - - - name: Get AppImage hash - id: hash - env: - RELEASE_VERSION: ${{ github.event.release.tag_name }} - run: | - # Remove 'v' prefix from tag if present - VERSION="${RELEASE_VERSION#v}" - APPIMAGE_URL="https://github.com/${{ github.repository }}/releases/download/v${VERSION}/Handy_${VERSION}_amd64.AppImage" - - echo "Computing hash for: $APPIMAGE_URL" - - # Retry logic in case AppImage isn't immediately available - for i in {1..30}; do - HASH=$(nix-prefetch-url --name "Handy_${VERSION}_amd64.AppImage" "$APPIMAGE_URL" 2>&1 | grep "^sha256-" || true) - if [ -n "$HASH" ]; then - echo "hash=$HASH" >> "$GITHUB_OUTPUT" - echo "Successfully computed hash: $HASH" - exit 0 - fi - echo "Attempt $i: AppImage not yet available, waiting..." - sleep 5 - done - - echo "Failed to fetch AppImage after retries" - exit 1 - - - name: Update flake.nix - env: - HASH: ${{ steps.hash.outputs.hash }} - run: | - sed -i "s|hash = \"sha256-[^\"]*\"|hash = \"$HASH\"|" flake.nix - - # Verify the hash was actually updated - if ! grep -q "hash = \"$HASH\"" flake.nix; then - echo "ERROR: Failed to update hash in flake.nix" - exit 1 - fi - echo "Successfully updated flake.nix with hash: $HASH" - - - name: Commit and push - env: - VERSION: ${{ github.event.release.tag_name }} - run: | - git config user.name "github-actions[bot]" - git config user.email "github-actions[bot]@users.noreply.github.com" - - # Only commit if flake.nix actually changed - if git diff --quiet flake.nix; then - echo "Hash already up-to-date, no commit needed" - else - git add flake.nix - git commit -m "chore(nix): update AppImage hash for ${VERSION}" - git push - echo "Successfully pushed hash update" - fi diff --git a/flake.nix b/flake.nix index 90b9fd4..7dd38d0 100644 --- a/flake.nix +++ b/flake.nix @@ -11,7 +11,7 @@ nixpkgs, }: let - supportedSystems = [ "x86_64-linux" ]; + supportedSystems = [ "x86_64-linux" "aarch64-linux" ]; forAllSystems = nixpkgs.lib.genAttrs supportedSystems; # Read version from Cargo.toml cargoToml = builtins.fromTOML (builtins.readFile ./src-tauri/Cargo.toml); @@ -22,30 +22,152 @@ system: let pkgs = nixpkgs.legacyPackages.${system}; - in - { - # AppImage-based package - handy-appimage = - let - appimage = pkgs.appimageTools.wrapType2 { - pname = "handy-appimage-unwrapped"; - inherit version; - src = pkgs.fetchurl { - url = "https://github.com/cjpais/Handy/releases/download/v${version}/Handy_${version}_amd64.AppImage"; - hash = "sha256-tTswFYLCPGtMbHAb2bQMsklRiRCVXLrtu4pQC8IHdqQ="; - }; - extraPkgs = - p: with p; [ - alsa-lib - ]; - }; - in - pkgs.writeShellScriptBin "handy" '' - export WEBKIT_DISABLE_DMABUF_RENDERER=1 - exec ${appimage}/bin/handy-appimage-unwrapped "$@" + lib = pkgs.lib; + + bunDeps = pkgs.stdenv.mkDerivation { + pname = "handy-bun-deps"; + inherit version; + src = self; + + nativeBuildInputs = [ + pkgs.bun + pkgs.cacert + ]; + + dontFixup = true; + + buildPhase = '' + export HOME=$TMPDIR + bun install --frozen-lockfile --no-progress ''; - default = self.packages.${system}.handy-appimage; + installPhase = '' + mkdir -p $out + cp -r node_modules $out/ + ''; + + outputHashAlgo = "sha256"; + outputHashMode = "recursive"; + outputHash = "sha256-+hUANv0w3qnK5d2+4JW3XMazLRDhWCbOxUXQyTGta/0="; + }; + in + { + handy = pkgs.rustPlatform.buildRustPackage { + pname = "handy"; + inherit version; + src = self; + + cargoRoot = "src-tauri"; + + cargoLock = { + lockFile = ./src-tauri/Cargo.lock; + outputHashes = { + "rdev-0.5.0-2" = "sha256-0F7EaPF8Oa1nnSCAjzEAkitWVpMldL3nCp3c5DVFMe0="; + "rodio-0.20.1" = "sha256-wq72awTvN4fXZ9qZc5KLYS9oMxtNDZ4YGxfqz8msofs="; + "tauri-nspanel-2.1.0" = "sha256-gotQQ1DOhavdXU8lTEux0vdY880LLetk7VLvSm6/8TI="; + "tauri-runtime-2.9.1" = "sha256-sdneSI2kfRMgTkuqQoFPgtYvkqMPSuoyrffFwOph+ZM="; + "vad-rs-0.1.5" = "sha256-Q9Dxq31npyUPY9wwi6OxqSJrEvFvG8/n0dbyT7XNcyI="; + }; + }; + + postPatch = '' + ${pkgs.jq}/bin/jq 'del(.build.beforeBuildCommand) | .bundle.createUpdaterArtifacts = false' \ + src-tauri/tauri.conf.json > $TMPDIR/tauri.conf.json + cp $TMPDIR/tauri.conf.json src-tauri/tauri.conf.json + + # Point libappindicator-sys to the Nix store path + substituteInPlace \ + $cargoDepsCopy/libappindicator-sys-*/src/lib.rs \ + --replace-fail \ + "libayatana-appindicator3.so.1" \ + "${pkgs.libayatana-appindicator}/lib/libayatana-appindicator3.so.1" + + # Disable cbindgen in ferrous-opencc (calls cargo metadata which fails in sandbox) + # Upstream removed this call in v0.3.1+ + substituteInPlace $cargoDepsCopy/ferrous-opencc-0.2.3/build.rs \ + --replace-fail '.expect("Unable to generate bindings")' '.ok();' + substituteInPlace $cargoDepsCopy/ferrous-opencc-0.2.3/build.rs \ + --replace-fail '.write_to_file("opencc.h");' '// skipped' + ''; + + preBuild = '' + cp -r ${bunDeps}/node_modules node_modules + chmod -R +w node_modules + patchShebangs node_modules + export HOME=$TMPDIR + ${pkgs.bun}/bin/bun run build + ''; + + doCheck = false; + + # The tauri hook's installPhase expects target/ in cwd, but our + # cargoRoot puts it under src-tauri/. Override to extract the DEB. + installPhase = '' + runHook preInstall + mkdir -p $out + cd src-tauri + mv target/${pkgs.stdenv.hostPlatform.rust.rustcTarget}/release/bundle/deb/*/data/usr/* $out/ + runHook postInstall + ''; + + nativeBuildInputs = with pkgs; [ + cargo-tauri.hook + pkg-config + wrapGAppsHook4 + bun + nodejs + jq + cmake + llvmPackages.libclang + shaderc + ]; + + buildInputs = with pkgs; [ + webkitgtk_4_1 + gtk3 + glib + glib-networking + libsoup_3 + alsa-lib + onnxruntime + libayatana-appindicator + libevdev + xorg.libX11 + xorg.libXtst + gtk-layer-shell + openssl + vulkan-loader + vulkan-headers + shaderc + ]; + + env = { + LIBCLANG_PATH = "${pkgs.llvmPackages.libclang.lib}/lib"; + BINDGEN_EXTRA_CLANG_ARGS = "-isystem ${pkgs.llvmPackages.libclang.lib}/lib/clang/${lib.getVersion pkgs.llvmPackages.libclang}/include -isystem ${pkgs.glibc.dev}/include"; + ORT_LIB_LOCATION = "${pkgs.onnxruntime}/lib"; + OPENSSL_NO_VENDOR = "1"; + }; + + preFixup = '' + gappsWrapperArgs+=( + --set WEBKIT_DISABLE_DMABUF_RENDERER 1 + --prefix LD_LIBRARY_PATH : "${lib.makeLibraryPath [ + pkgs.vulkan-loader + pkgs.onnxruntime + ]}" + ) + ''; + + meta = { + description = "A free, open source, and extensible speech-to-text application that works completely offline"; + homepage = "https://github.com/cjpais/Handy"; + license = lib.licenses.mit; + mainProgram = "handy"; + platforms = [ "x86_64-linux" ]; + }; + }; + + default = self.packages.${system}.handy; } );