From ff9052b5d7cd55fd20b2061893f1f5178ea59ad1 Mon Sep 17 00:00:00 2001 From: Adib Hanna Date: Fri, 19 Jun 2026 18:48:58 -0500 Subject: [PATCH] packaging(nix): ship desktop as a prebuilt binary (download, no compile) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit zennotes-desktop now wraps the official prebuilt ZenNotes--linux-x64.tar.gz release artifact (fetchurl + autoPatchelfHook + wrapGAppsHook3) instead of building Electron from source via buildNpmPackage. This makes `nix run github:ZenNotes/zennotes` download the same signed binary the AppImage/deb/AUR ship rather than compiling locally — the most-requested Nix ask. The tarball sha256 is pinned as release-data.json `desktopHash`. - package-desktop.nix: rewritten as a binaryNativeCode repackage; drops the SUID chrome-sandbox so Electron uses the userns sandbox; installs icons + desktop entry from the tarball's arch-extras tree. - release-data.json: add desktopHash (server still builds from source, so hash/npmDepsHash/vendorHash stay). - flake.nix: desktop is x86_64-linux only now (prebuilt x64); server is the default elsewhere, keeping eval clean on darwin/aarch64. - nix-build.yml: CI builds both packages on Nix-packaging changes (we have no local Nix machine to verify on). --- .github/workflows/nix-build.yml | 32 +++++++ flake.nix | 23 +++-- packaging/nix/package-desktop.nix | 145 ++++++++++++++++++++++-------- packaging/nix/release-data.json | 3 +- 4 files changed, 160 insertions(+), 43 deletions(-) create mode 100644 .github/workflows/nix-build.yml diff --git a/.github/workflows/nix-build.yml b/.github/workflows/nix-build.yml new file mode 100644 index 00000000..740ea409 --- /dev/null +++ b/.github/workflows/nix-build.yml @@ -0,0 +1,32 @@ +name: Nix build + +# Builds the flake packages on a Nix runner whenever the Nix packaging changes, +# so the prebuilt-desktop wrapper and the server package are verified in CI +# (the maintainers don't have a Nix machine to test on locally). + +on: + push: + branches: [main] + paths: ['flake.nix', 'flake.lock', 'packaging/nix/**'] + pull_request: + paths: ['flake.nix', 'flake.lock', 'packaging/nix/**'] + workflow_dispatch: + +permissions: + contents: read + +jobs: + build: + runs-on: ubuntu-latest + env: + # cache.nixos.org occasionally corrupts NAR streams over HTTP/2; dodge it. + NIX_CONFIG: | + http2 = false + download-attempts = 5 + connect-timeout = 20 + steps: + - uses: actions/checkout@v4 + - name: Install Nix + uses: DeterminateSystems/nix-installer-action@main + - name: Build packages + run: nix build --fallback --print-build-logs .#zennotes-desktop .#zennotes-server diff --git a/flake.nix b/flake.nix index 336b7f73..83a3b224 100644 --- a/flake.nix +++ b/flake.nix @@ -15,14 +15,23 @@ packages = forAllSystems (system: let pkgs = nixpkgs.legacyPackages.${system}; - in - rec { - zennotes-desktop = pkgs.callPackage ./packaging/nix/package-desktop.nix { }; - zennotes-server = pkgs.callPackage ./packaging/nix/package-server.nix { }; - - default = zennotes-desktop; - } + in + { inherit zennotes-server; } + # The desktop package wraps the prebuilt linux-x64 release tarball, so it + # only exists on x86_64-linux; elsewhere the server is the default. + // ( + if system == "x86_64-linux" then + let + zennotes-desktop = pkgs.callPackage ./packaging/nix/package-desktop.nix { }; + in + { + inherit zennotes-desktop; + default = zennotes-desktop; + } + else + { default = zennotes-server; } + ) ); devShell = forAllSystems (system: diff --git a/packaging/nix/package-desktop.nix b/packaging/nix/package-desktop.nix index df4b9cdd..71789482 100644 --- a/packaging/nix/package-desktop.nix +++ b/packaging/nix/package-desktop.nix @@ -1,65 +1,139 @@ { - stdenv, lib, - buildNpmPackage, - fetchFromGitHub, - makeDesktopItem, - copyDesktopItems, - electron, + stdenv, + fetchurl, + autoPatchelfHook, makeWrapper, + wrapGAppsHook3, + copyDesktopItems, + makeDesktopItem, + + # Electron / Chromium runtime libraries + alsa-lib, + at-spi2-atk, + at-spi2-core, + atk, + cairo, + cups, + dbus, + expat, + fontconfig, + freetype, + gdk-pixbuf, + glib, + gtk3, + libdrm, + libGL, + libgbm, + libnotify, + libpulseaudio, + libuuid, + libxkbcommon, + nspr, + nss, + pango, + systemd, + wayland, + xorg, - installCLI ? false, - CLIcommand ? "zen", commandLineArgs ? "", }: let releaseData = lib.importJSON ./release-data.json; in -buildNpmPackage (finalAttrs: { +# Packaged from the official prebuilt linux-x64 release tarball rather than built +# from source, so `nix run github:ZenNotes/zennotes` downloads the same signed +# binary the AppImage/deb/AUR ship instead of compiling Electron locally. Bump +# `version` + `desktopHash` in release-data.json per release (see README.md). +stdenv.mkDerivation (finalAttrs: { pname = "zennotes-desktop"; - inherit (releaseData) version npmDepsHash; + inherit (releaseData) version; - src = fetchFromGitHub { - owner = "ZenNotes"; - repo = "zennotes"; - tag = "v${finalAttrs.version}"; - inherit (releaseData) hash; + src = fetchurl { + url = "https://github.com/ZenNotes/zennotes/releases/download/v${finalAttrs.version}/ZenNotes-${finalAttrs.version}-linux-x64.tar.gz"; + hash = releaseData.desktopHash; }; - npmWorkspace = "apps/desktop"; - - env.ELECTRON_SKIP_BINARY_DOWNLOAD = "1"; + sourceRoot = "ZenNotes-${finalAttrs.version}-linux-x64"; nativeBuildInputs = [ + autoPatchelfHook makeWrapper - ] - ++ lib.optionals stdenv.hostPlatform.isLinux [ + wrapGAppsHook3 copyDesktopItems ]; + buildInputs = [ + alsa-lib + at-spi2-atk + at-spi2-core + atk + cairo + cups + dbus + expat + fontconfig + freetype + gdk-pixbuf + glib + gtk3 + libdrm + libgbm + libuuid + libxkbcommon + nspr + nss + pango + stdenv.cc.cc # libstdc++ + xorg.libX11 + xorg.libXcomposite + xorg.libXcursor + xorg.libXdamage + xorg.libXext + xorg.libXfixes + xorg.libXi + xorg.libXrandr + xorg.libXrender + xorg.libXScrnSaver + xorg.libXtst + xorg.libxcb + ]; + + # dlopen'd at runtime (not in DT_NEEDED), so keep them on the wrapper's path. + runtimeDependencies = [ + (lib.getLib systemd) + libGL + libnotify + libpulseaudio + wayland + ]; + + dontConfigure = true; + dontBuild = true; + # We invoke makeWrapper manually and splice in gappsWrapperArgs ourselves. + dontWrapGApps = true; + installPhase = '' runHook preInstall - mkdir -p $out/lib/node_modules/zennotes-monorepo - cp -r . $out/lib/node_modules/zennotes-monorepo/ + # The SUID chrome-sandbox can't be made setuid in the Nix store; drop it so + # Electron falls back to the user-namespace sandbox (same as the Flatpak). + rm -f chrome-sandbox + + mkdir -p $out/share/zennotes + cp -r . $out/share/zennotes - for icon in apps/desktop/build/icons/*.png; do + # Icons + desktop entry ship inside the tarball's arch-extras tree. + for icon in $out/share/zennotes/resources/arch-extras/icons/*.png; do size="$(basename "$icon" .png)" - install -Dm644 $icon $out/share/icons/hicolor/$size/apps/${finalAttrs.pname}.png + install -Dm644 "$icon" "$out/share/icons/hicolor/$size/apps/${finalAttrs.pname}.png" done - mkdir -p $out/bin - makeWrapper ${electron}/bin/electron $out/bin/${finalAttrs.pname} \ - --add-flags "$out/lib/node_modules/zennotes-monorepo/apps/desktop" \ + makeWrapper $out/share/zennotes/ZenNotes $out/bin/${finalAttrs.pname} \ + "''${gappsWrapperArgs[@]}" \ --add-flags "\''${NIXOS_OZONE_WL:+\''${WAYLAND_DISPLAY:+--ozone-platform-hint=auto}}" \ ${lib.optionalString (commandLineArgs != "") "--add-flags ${lib.escapeShellArg commandLineArgs}"} - ${lib.optionalString installCLI '' - makeWrapper ${electron}/libexec/electron/electron $out/bin/${CLIcommand} \ - --set ELECTRON_RUN_AS_NODE 1 \ - --add-flags "$out/lib/node_modules/zennotes-monorepo/apps/desktop/out/main/cli.js" - ''} - runHook postInstall ''; @@ -84,12 +158,13 @@ buildNpmPackage (finalAttrs: { ]; meta = { - description = "Keyboard-first local Markdown notes with Vim motions, diagrams, and MCP integration"; + description = "Keyboard-first local Markdown notes with Vim motions, diagrams, and MCP integration (prebuilt binary)"; homepage = "https://zennotes.org/"; changelog = "https://github.com/ZenNotes/zennotes/releases/tag/v${finalAttrs.version}"; license = lib.licenses.mit; maintainers = with lib.maintainers; [ justkrysteq ]; mainProgram = finalAttrs.pname; - inherit (electron.meta) platforms; + sourceProvenance = with lib.sourceTypes; [ binaryNativeCode ]; + platforms = [ "x86_64-linux" ]; }; }) diff --git a/packaging/nix/release-data.json b/packaging/nix/release-data.json index 0db34801..9c86345e 100644 --- a/packaging/nix/release-data.json +++ b/packaging/nix/release-data.json @@ -2,5 +2,6 @@ "version": "2.4.0", "hash": "sha256-v+0Iyxz0B5IQ6FnzD6WecIfrM1j4PUzizH63JMvkp8Y=", "npmDepsHash": "sha256-OgehLwGj216oBUJOuEmzxzzr/BlwoUaE5IAbT5rvgTQ=", - "vendorHash": "sha256-wYBF7CjM6AvoWMWql9hFmIaj6pCmli4vOef6POyGkfU=" + "vendorHash": "sha256-wYBF7CjM6AvoWMWql9hFmIaj6pCmli4vOef6POyGkfU=", + "desktopHash": "sha256-Y+l/bhP/l9bUesPAP+f9wHGEmMSU8gezgmlqppYVxas=" }