{ description = "Donut Browser development environment and quick-start commands"; inputs = { nixpkgs.url = "github:nixos/nixpkgs/nixos-unstable"; flake-utils.url = "github:numtide/flake-utils"; }; outputs = { self, nixpkgs, flake-utils, ... }: flake-utils.lib.eachDefaultSystem (system: let pkgs = import nixpkgs { inherit system; config.allowUnfree = true; }; lib = pkgs.lib; # The version in .node-version; 22 only where 24 is not packaged. nodejs = pkgs.nodejs_24 or pkgs.nodejs_22; rustPackages = with pkgs; [ cargo clippy rust-analyzer rustc rustfmt ]; commonLibs = with pkgs; [ webkitgtk_4_1 libsoup_3 glib gtk3 libayatana-appindicator cairo gdk-pixbuf pango atk at-spi2-atk at-spi2-core dbus nss nspr libdrm libgbm libxkbcommon libx11 libxcomposite libxdamage libxext libxfixes libxrandr libxcb libxshmfence libxtst libxi xdotool libxrender libxinerama libxcursor libxscrnsaver fontconfig freetype fribidi harfbuzz expat libglvnd libgpg-error e2fsprogs gmp zlib stdenv.cc.cc.lib ]; runtimeLibPath = lib.makeLibraryPath commonLibs; nixLd = pkgs.stdenv.cc.bintools.dynamicLinker; pkgConfigLibs = [ pkgs.at-spi2-atk pkgs.at-spi2-core pkgs.cairo pkgs.dbus pkgs.gdk-pixbuf pkgs.glib pkgs.gtk3 pkgs.libayatana-appindicator pkgs.libsoup_3 pkgs.libxkbcommon pkgs.openssl pkgs.pango pkgs.harfbuzz pkgs.webkitgtk_4_1 pkgs.zlib ]; # The .pc files these name in Requires have to resolve too: gdk-3.0 # requires zlib, and a list of direct dependencies alone left it out, # so `nix run .#build` failed in gdk-sys. The propagated closure brings # in everything the listed libraries themselves depend on. pkgConfigClosure = lib.closePropagation (pkgConfigLibs ++ map lib.getDev pkgConfigLibs); pkgConfigPath = lib.concatStringsSep ":" [ (lib.makeSearchPath "lib/pkgconfig" (pkgConfigClosure ++ map lib.getDev pkgConfigClosure)) (lib.makeSearchPath "share/pkgconfig" pkgConfigClosure) ]; releaseVersion = "0.31.2"; releaseAppImage = if system == "x86_64-linux" then pkgs.fetchurl { url = "https://github.com/zhom/donutbrowser/releases/download/v0.31.2/Donut_0.31.2_amd64.AppImage"; hash = "sha256-KQgvuKnb9B+V+uQysK7Av+rMutyv7uV7JqhzlM1Iu+0="; } else if system == "aarch64-linux" then pkgs.fetchurl { url = "https://github.com/zhom/donutbrowser/releases/download/v0.31.2/Donut_0.31.2_aarch64.AppImage"; hash = "sha256-K0UCdXzOMOKQeXWFH9bhMXcxtQ93K42gf7OvhSxdtyc="; } else null; releaseUnpacked = if releaseAppImage != null then pkgs.stdenvNoCC.mkDerivation { pname = "donut-release-unpacked"; version = releaseVersion; src = releaseAppImage; dontUnpack = true; nativeBuildInputs = [ pkgs.xz ]; installPhase = '' runHook preInstall cp "$src" ./donut.AppImage chmod +x ./donut.AppImage ./donut.AppImage --appimage-extract >/dev/null mkdir -p "$out" cp -a ./squashfs-root "$out/" runHook postInstall ''; } else null; releaseWrapped = if releaseAppImage != null then pkgs.appimageTools.wrapType2 { pname = "donut"; version = releaseVersion; src = releaseAppImage; extraPkgs = _: commonLibs; extraInstallCommands = '' for bin in "$out"/bin/*; do if [ -f "$bin" ]; then mv "$bin" "$out/bin/donut-release" break fi done ''; } else null; releaseLauncher = if releaseUnpacked != null then pkgs.writeShellApplication { name = "donut-release-start"; runtimeInputs = with pkgs; [ coreutils xdg-utils ]; text = '' set -euo pipefail if [ -x "${releaseWrapped}/bin/donut-release" ]; then if "${releaseWrapped}/bin/donut-release" "$@"; then exit 0 fi echo "Wrapped AppImage failed, retrying with direct AppRun..." >&2 fi export LD_LIBRARY_PATH="${releaseUnpacked}/squashfs-root/usr/lib:${releaseUnpacked}/squashfs-root/usr/lib64:${runtimeLibPath}:''${LD_LIBRARY_PATH:-}" export NIX_LD_LIBRARY_PATH="$LD_LIBRARY_PATH" export LIBRARY_PATH="$LD_LIBRARY_PATH" export XDG_DATA_DIRS="${releaseUnpacked}/squashfs-root/usr/share:''${XDG_DATA_DIRS:-}" exec "${releaseUnpacked}/squashfs-root/AppRun" "$@" ''; } else pkgs.writeShellApplication { name = "donut-release-start"; text = '' echo "Release launcher is supported only on Linux (x86_64/aarch64)." exit 1 ''; }; mkApp = name: text: let app = pkgs.writeShellApplication { inherit name; runtimeInputs = with pkgs; [ bash coreutils findutils git gnugrep gnused curl gcc pkg-config openssl cargo clippy rustc rustfmt nodejs pnpm cargo-tauri ]; text = '' export NODE_ENV=development export NIX_LD="${nixLd}" export NIX_LD_LIBRARY_PATH="${runtimeLibPath}:''${NIX_LD_LIBRARY_PATH:-}" export LD_LIBRARY_PATH="${runtimeLibPath}:''${LD_LIBRARY_PATH:-}" export LIBRARY_PATH="${runtimeLibPath}:''${LIBRARY_PATH:-}" export PKG_CONFIG_PATH="${pkgConfigPath}:''${PKG_CONFIG_PATH:-}" export RUST_SRC_PATH="${pkgs.rustPlatform.rustLibSrc}" ${text} ''; }; in { type = "app"; program = "${app}/bin/${name}"; }; in { devShells.default = pkgs.mkShell { packages = with pkgs; [ nodejs pnpm cargo-tauri pkg-config openssl git bashInteractive gnumake clang llvmPackages.bintools python3 curl wget unzip zip xz biome docker ] ++ rustPackages ++ commonLibs; shellHook = '' export NODE_ENV=development export NIX_LD="${nixLd}" export NIX_LD_LIBRARY_PATH="${runtimeLibPath}:''${NIX_LD_LIBRARY_PATH:-}" export LD_LIBRARY_PATH="${runtimeLibPath}:''${LD_LIBRARY_PATH:-}" export LIBRARY_PATH="${runtimeLibPath}:''${LIBRARY_PATH:-}" export PKG_CONFIG_PATH="${pkgConfigPath}:''${PKG_CONFIG_PATH:-}" export RUST_SRC_PATH="${pkgs.rustPlatform.rustLibSrc}" export XDG_DATA_DIRS="${pkgs.gsettings-desktop-schemas}/share:${pkgs.gtk3}/share:''${XDG_DATA_DIRS:-}" echo "Donut Browser dev shell ready." echo "Quick start:" echo " nix run .#setup" echo " nix run .#tauri-dev" echo " nix run .#full-dev" echo " nix run .#build" echo " nix run .#test" echo " nix run .#release-start" ''; }; apps.info = mkApp "donut-info" '' set -euo pipefail echo "Node: $(node --version)" echo "pnpm: $(pnpm --version)" echo "Rust: $(rustc --version)" echo "Cargo: $(cargo --version)" echo "Tauri CLI: $(cargo-tauri --version)" # Every library the Rust build links through pkg-config, with the # packages their .pc files require. A gap fails here in seconds # instead of minutes into `nix run .#build`. for module in gdk-3.0 webkit2gtk-4.1 javascriptcoregtk-4.1 libsoup-3.0 ayatana-appindicator3-0.1 openssl; do pkg-config --cflags --libs "$module" > /dev/null echo "$module: $(pkg-config --modversion "$module")" done ''; apps.deps = mkApp "donut-deps" '' set -euo pipefail pnpm install ''; apps.dev = mkApp "donut-dev" '' set -euo pipefail pnpm dev ''; apps."tauri-dev" = mkApp "donut-tauri-dev" '' set -euo pipefail pnpm tauri dev ''; apps."full-dev" = mkApp "donut-full-dev" '' set -euo pipefail chmod +x ./scripts/dev.sh ./scripts/dev.sh ''; apps.build = mkApp "donut-build" '' set -euo pipefail pnpm build (cd src-tauri && cargo build) ''; apps.start = mkApp "donut-start" '' set -euo pipefail pnpm start ''; apps.test = mkApp "donut-test" '' set -euo pipefail pnpm format && pnpm lint && pnpm test ''; apps.setup = mkApp "donut-setup" '' set -euo pipefail if [ ! -f "package.json" ]; then echo "package.json not found. Run this from the donutbrowser repo root." exit 1 fi pnpm install pnpm copy-proxy-binary echo "Setup complete." echo "Run the app with:" echo " nix run .#tauri-dev" echo "Or run full local stack (sync + minio + tauri):" echo " nix run .#full-dev" ''; apps."release-start" = { type = "app"; program = "${releaseLauncher}/bin/donut-release-start"; }; apps.default = self.apps.${system}.setup; }); }