Handy/flake.nix
Evgeny Khudoba a385371c32
Some checks failed
Main Branch Build / build (, windows-latest, x86_64-pc-windows-msvc) (push) Has been cancelled
Main Branch Build / build (--bundles appimage,deb,rpm, ubuntu-24.04-arm, aarch64-unknown-linux-gnu) (push) Has been cancelled
Main Branch Build / build (--bundles appimage,rpm, ubuntu-24.04, x86_64-unknown-linux-gnu) (push) Has been cancelled
Main Branch Build / build (--bundles deb, ubuntu-22.04, x86_64-unknown-linux-gnu) (push) Has been cancelled
Main Branch Build / build (--target aarch64-apple-darwin, macos-26, aarch64-apple-darwin) (push) Has been cancelled
Main Branch Build / build (--target aarch64-pc-windows-msvc, windows-11-arm, aarch64-pc-windows-msvc) (push) Has been cancelled
Main Branch Build / build (--target x86_64-apple-darwin, macos-latest, x86_64-apple-darwin) (push) Has been cancelled
nix build check / nix-build (push) Has been cancelled
refactor(nix): rely on cargo-tauri.hook standard phases (#1335)
Set buildAndTestSubdir + tauriBundleType so cargo-tauri.hook handles
build/install correctly, and drop the manual overrides:

- installPhase override (was needed because hook didn't know to pushd
  into src-tauri without buildAndTestSubdir; hook's install script now
  finds bundle/deb/.../data/usr/* on its own)
- preBuild with `bun run build` (hook runs beforeBuildCommand from
  tauri.conf.json itself; bun2nix.hook has node_modules ready before
  buildPhase)
- jq del(.build.beforeBuildCommand) in postPatch (no longer needed)

Net change: +3/-18. End-to-end verified on NixOS x86_64-linux:
GigaAM v3 ASR + Whisper large-v3-turbo on Vulkan both work.
2026-04-30 08:46:23 +08:00

255 lines
8.5 KiB
Nix

{
description = "Handy - A free, open source, and extensible speech-to-text application that works completely offline";
inputs = {
nixpkgs.url = "github:NixOS/nixpkgs/nixos-unstable";
# bun2nix: generates per-package Nix fetchurl expressions from bun.lock,
# replacing the old FOD approach where a single hash covered the entire
# node_modules directory (that hash would break on bun version changes).
# See: https://github.com/nix-community/bun2nix
bun2nix = {
url = "github:nix-community/bun2nix/2.0.8";
inputs.nixpkgs.follows = "nixpkgs";
};
};
outputs =
{
self,
nixpkgs,
bun2nix,
}:
let
supportedSystems = [
"x86_64-linux"
"aarch64-linux"
];
forAllSystems = nixpkgs.lib.genAttrs supportedSystems;
# Read version from Cargo.toml
cargoToml = fromTOML (builtins.readFile ./src-tauri/Cargo.toml);
version = cargoToml.package.version;
# Shared native library dependencies for both package build and dev shell.
# Keep in sync: if a native dep is needed for compilation, add it here.
commonNativeDeps = pkgs: with pkgs; [
webkitgtk_4_1
gtk3
glib
libsoup_3
alsa-lib
onnxruntime
libayatana-appindicator
libevdev
libxtst
gtk-layer-shell
openssl
vulkan-loader
vulkan-headers
shaderc
];
# GStreamer plugins for WebKitGTK audio/video
gstPlugins = pkgs: with pkgs.gst_all_1; [
gstreamer
gst-plugins-base
gst-plugins-good
gst-plugins-bad
gst-plugins-ugly
];
# Shared environment variables for Rust/native builds
commonEnv = pkgs: let lib = pkgs.lib; in {
ORT_LIB_LOCATION = "${pkgs.onnxruntime}/lib";
ORT_PREFER_DYNAMIC_LINK = "1";
GST_PLUGIN_SYSTEM_PATH_1_0 = "${lib.makeSearchPathOutput "lib" "lib/gstreamer-1.0" (gstPlugins pkgs)}";
};
in
{
packages = forAllSystems (
system:
let
pkgs = import nixpkgs {
inherit system;
overlays = [
bun2nix.overlays.default
];
};
lib = pkgs.lib;
combinedAlsaPlugins = pkgs.symlinkJoin {
name = "combined-alsa-plugins";
paths = [
"${pkgs.pipewire}/lib/alsa-lib"
"${pkgs.alsa-plugins}/lib/alsa-lib"
];
};
in
{
handy = pkgs.rustPlatform.buildRustPackage {
pname = "handy";
inherit version;
src = self;
cargoRoot = "src-tauri";
buildAndTestSubdir = "src-tauri";
tauriBundleType = "deb";
cargoLock = {
lockFile = ./src-tauri/Cargo.lock;
# Automatically fetch git dependencies using builtins.fetchGit.
# This eliminates the need for manual outputHashes that had to be
# updated every time a git dependency changed in Cargo.lock.
# Safe for standalone flakes (not allowed in nixpkgs, it is needed something like crate2nix).
allowBuiltinFetchGit = true;
};
postPatch = ''
${pkgs.jq}/bin/jq '.bundle.createUpdaterArtifacts = false' \
src-tauri/tauri.conf.json > $TMPDIR/tauri.conf.json
cp $TMPDIR/tauri.conf.json src-tauri/tauri.conf.json
# Strip postinstall hook it runs check-nix-deps.ts which is only
# needed during local development, not inside the Nix sandbox.
${pkgs.jq}/bin/jq 'del(.scripts.postinstall)' \
package.json > $TMPDIR/package.json
cp $TMPDIR/package.json package.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'
'';
# Bun dependencies: fetched per-package using hashes from .nix/bun.nix.
# This file is auto-generated by `bunx bun2nix -o .nix/bun.nix` and
# kept in sync via the postinstall hook in package.json.
# To regenerate manually: bun scripts/check-nix-deps.ts
bunDeps = pkgs.bun2nix.fetchBunDeps {
bunNix = ./.nix/bun.nix;
};
nativeBuildInputs = with pkgs; [
cargo-tauri.hook
pkg-config
wrapGAppsHook4
bun
# pkgs.bun2nix (from overlay), not the flake input — `with pkgs;`
# doesn't shadow function arguments in Nix.
pkgs.bun2nix.hook # Sets up node_modules from pre-fetched bun cache
jq
cmake
rustPlatform.bindgenHook
shaderc
];
# Tests require runtime resources (audio devices, model files, GPU/Vulkan)
# not available in the Nix build sandbox
doCheck = false;
buildInputs = commonNativeDeps pkgs ++ (with pkgs; [
glib-networking
libx11
]) ++ gstPlugins pkgs;
env = commonEnv pkgs // {
OPENSSL_NO_VENDOR = "1";
};
preFixup = ''
gappsWrapperArgs+=(
--set WEBKIT_DISABLE_DMABUF_RENDERER 1
--set ALSA_PLUGIN_DIR "${combinedAlsaPlugins}"
--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 = supportedSystems;
};
};
default = self.packages.${system}.handy;
}
);
# NixOS module for system-level integration (udev, input group)
nixosModules.default =
{ lib, pkgs, ... }:
{
imports = [ ./nix/module.nix ];
programs.handy.package = lib.mkDefault self.packages.${pkgs.stdenv.hostPlatform.system}.handy;
};
# Home-manager module for per-user service
homeManagerModules.default =
{ lib, pkgs, ... }:
{
imports = [ ./nix/hm-module.nix ];
services.handy.package = lib.mkDefault self.packages.${pkgs.stdenv.hostPlatform.system}.handy;
};
# Development shell for building from source
devShells = forAllSystems (
system:
let
pkgs = import nixpkgs {
inherit system;
};
in
{
default = pkgs.mkShell {
buildInputs = commonNativeDeps pkgs ++ (with pkgs; [
# Rust toolchain
rustc
cargo
rust-analyzer
clippy
# Frontend
nodejs
bun
# Build tools
cargo-tauri
pkg-config
rustPlatform.bindgenHook
cmake
]);
inherit (commonEnv pkgs)
ORT_LIB_LOCATION
ORT_PREFER_DYNAMIC_LINK
GST_PLUGIN_SYSTEM_PATH_1_0;
LD_LIBRARY_PATH = "${pkgs.lib.makeLibraryPath [ pkgs.libayatana-appindicator pkgs.onnxruntime pkgs.vulkan-loader ]}";
# Same as wrapGAppsHook4
XDG_DATA_DIRS = "${pkgs.gsettings-desktop-schemas}/share/gsettings-schemas/${pkgs.gsettings-desktop-schemas.name}:${pkgs.gtk3}/share/gsettings-schemas/${pkgs.gtk3.name}:${pkgs.hicolor-icon-theme}/share";
shellHook = ''
echo "Handy development environment"
bun install
echo "Run 'bun run tauri dev' to start"
'';
};
}
);
};
}