fix(nix): replace manual hash management with bun2nix (#1021)
* fix(nix): replace manual hash management with bun2nix Eliminate the recurring problem of Nix build hashes breaking whenever bun dependencies change or the bun version in nixpkgs updates. Changes: - Add bun2nix flake input (pinned to v2.0.1) for per-package fetchurl expressions from bun.lock, replacing the single FOD hash approach - Use allowBuiltinFetchGit for cargo git dependencies, removing manual outputHashes that required updates on every git dep change - Add scripts/check-nix-deps.ts (cross-platform, runs via bun) that auto-regenerates .nix/bun.nix when bun.lock changes, triggered by the postinstall hook in package.json - Update CI workflow to verify bun.nix is in sync and evaluate flake - Remove scripts/update-nix-hashes.sh (no longer needed) * style: format check-nix-deps.ts with prettier * ci(nix): make nix-check job a required check * fix(nix): address review feedback - Fix bun2nix input pinning: use path syntax (github:owner/repo/tag) instead of ?tag= query parameter so flake.lock records the ref and `nix flake update` respects the pin - Make check-nix-deps.ts exit with 0 on bun2nix failure so that `bun install` is not blocked for non-Nix developers (CI validates bun.nix independently) - Fix stale reference to check-nix-deps.sh in flake.nix comment * ci(nix): add full nix build step to CI workflow Add `nix build .#handy -L --show-trace` after flake evaluation to catch runtime build errors (broken dependencies, sandbox issues, compilation failures) that flake eval alone cannot detect. * fix(nix): update bun2nix pin from 2.0.1 to 2.0.8 bun2nix 2.0.1 has a bug in cache-entry-creator that causes "ln: failed to create symbolic link '/p': Permission denied" during the build. Version 2.0.8 fixes this.
This commit is contained in:
parent
01b8b05bdc
commit
7056edce28
8 changed files with 1695 additions and 265 deletions
63
.github/workflows/nix-check.yml
vendored
63
.github/workflows/nix-check.yml
vendored
|
|
@ -1,18 +1,62 @@
|
||||||
|
# Verify that Nix dependency files (.nix/bun.nix) are in sync with lockfiles
|
||||||
|
# and that the flake evaluates successfully.
|
||||||
|
#
|
||||||
|
# This catches cases where a developer updated bun.lock but forgot to
|
||||||
|
# regenerate .nix/bun.nix (e.g., committed without running bun install,
|
||||||
|
# which triggers the postinstall hook).
|
||||||
|
|
||||||
name: "nix build check"
|
name: "nix build check"
|
||||||
on: [pull_request]
|
on: [pull_request]
|
||||||
|
|
||||||
jobs:
|
jobs:
|
||||||
nix-build:
|
nix-build:
|
||||||
runs-on: ubuntu-24.04
|
runs-on: ubuntu-24.04
|
||||||
continue-on-error: true
|
continue-on-error: false
|
||||||
steps:
|
steps:
|
||||||
- uses: actions/checkout@v4
|
- uses: actions/checkout@v4
|
||||||
|
|
||||||
|
- uses: oven-sh/setup-bun@v2
|
||||||
|
|
||||||
- uses: cachix/install-nix-action@v30
|
- uses: cachix/install-nix-action@v30
|
||||||
with:
|
with:
|
||||||
nix_path: nixpkgs=channel:nixos-unstable
|
nix_path: nixpkgs=channel:nixos-unstable
|
||||||
|
|
||||||
|
# Regenerate .nix/bun.nix from bun.lock and check if it matches
|
||||||
|
# what's committed. A diff means the developer forgot to run
|
||||||
|
# bun scripts/check-nix-deps.ts or bun install (which triggers it).
|
||||||
|
- name: Check bun.nix is up to date
|
||||||
|
id: bun-check
|
||||||
|
run: |
|
||||||
|
bunx bun2nix -o .nix/bun.nix
|
||||||
|
if ! git diff --quiet .nix/bun.nix; then
|
||||||
|
echo "outdated=true" >> "$GITHUB_OUTPUT"
|
||||||
|
fi
|
||||||
|
|
||||||
|
- name: Hint on outdated bun.nix
|
||||||
|
if: steps.bun-check.outputs.outdated == 'true'
|
||||||
|
run: |
|
||||||
|
echo ""
|
||||||
|
echo "::warning::.nix/bun.nix is out of sync with bun.lock"
|
||||||
|
echo ""
|
||||||
|
echo "┌──────────────────────────────────────────────────────────────┐"
|
||||||
|
echo "│ .nix/bun.nix is outdated. To fix, run: │"
|
||||||
|
echo "│ │"
|
||||||
|
echo "│ bun scripts/check-nix-deps.ts │"
|
||||||
|
echo "│ │"
|
||||||
|
echo "│ Or simply run 'bun install' — the postinstall hook will │"
|
||||||
|
echo "│ regenerate it automatically. Commit the resulting changes. │"
|
||||||
|
echo "└──────────────────────────────────────────────────────────────┘"
|
||||||
|
echo ""
|
||||||
|
echo "Diff:"
|
||||||
|
git diff .nix/bun.nix
|
||||||
|
exit 1
|
||||||
|
|
||||||
|
# Evaluate the flake to catch issues with cargo git dependency hashes,
|
||||||
|
# missing inputs, or other Nix expression errors.
|
||||||
|
# Skip if bun.nix is already outdated — nix eval would fail with a
|
||||||
|
# cryptic error, and the bun-check step already printed a clear message.
|
||||||
- name: Check flake evaluation
|
- name: Check flake evaluation
|
||||||
|
if: steps.bun-check.outputs.outdated != 'true'
|
||||||
id: eval
|
id: eval
|
||||||
run: |
|
run: |
|
||||||
if ! nix eval .#packages.x86_64-linux.handy.drvPath 2>eval_err.log; then
|
if ! nix eval .#packages.x86_64-linux.handy.drvPath 2>eval_err.log; then
|
||||||
|
|
@ -24,16 +68,13 @@ jobs:
|
||||||
if: steps.eval.outputs.failed == 'true'
|
if: steps.eval.outputs.failed == 'true'
|
||||||
run: |
|
run: |
|
||||||
echo ""
|
echo ""
|
||||||
echo "::warning::flake.nix evaluation failed — likely outdated outputHashes or bunDeps hash."
|
echo "::warning::flake.nix evaluation failed"
|
||||||
echo ""
|
|
||||||
echo "┌─────────────────────────────────────────────────────────────────┐"
|
|
||||||
echo "│ To fix, run on NixOS, Ubuntu/Debian, macOS with nix installed: │"
|
|
||||||
echo "│ │"
|
|
||||||
echo "│ ./scripts/update-nix-hashes.sh │"
|
|
||||||
echo "│ │"
|
|
||||||
echo "│ The script will update version keys and hashes in flake.nix │"
|
|
||||||
echo "│ automatically. Commit the resulting changes. │"
|
|
||||||
echo "└─────────────────────────────────────────────────────────────────┘"
|
|
||||||
echo ""
|
echo ""
|
||||||
cat eval_err.log
|
cat eval_err.log
|
||||||
exit 1
|
exit 1
|
||||||
|
|
||||||
|
# Full build — catches runtime build errors (broken dependencies,
|
||||||
|
# sandbox issues, compilation failures) that flake eval alone misses.
|
||||||
|
- name: Build handy
|
||||||
|
if: steps.bun-check.outputs.outdated != 'true' && steps.eval.outputs.failed != 'true'
|
||||||
|
run: nix build .#handy -L --show-trace
|
||||||
|
|
|
||||||
1
.nix/bun-lock-hash
Normal file
1
.nix/bun-lock-hash
Normal file
|
|
@ -0,0 +1 @@
|
||||||
|
e00b12c719a762004194cec01f2ad0b78ae483c41452bcca8537179d28e704b1
|
||||||
1400
.nix/bun.nix
Normal file
1400
.nix/bun.nix
Normal file
File diff suppressed because it is too large
Load diff
110
flake.lock
110
flake.lock
|
|
@ -1,5 +1,63 @@
|
||||||
{
|
{
|
||||||
"nodes": {
|
"nodes": {
|
||||||
|
"bun2nix": {
|
||||||
|
"inputs": {
|
||||||
|
"flake-parts": "flake-parts",
|
||||||
|
"import-tree": "import-tree",
|
||||||
|
"nixpkgs": [
|
||||||
|
"nixpkgs"
|
||||||
|
],
|
||||||
|
"systems": "systems",
|
||||||
|
"treefmt-nix": "treefmt-nix"
|
||||||
|
},
|
||||||
|
"locked": {
|
||||||
|
"lastModified": 1770895533,
|
||||||
|
"narHash": "sha256-v3QaK9ugy9bN9RXDnjw0i2OifKmz2NnKM82agtqm/UY=",
|
||||||
|
"owner": "nix-community",
|
||||||
|
"repo": "bun2nix",
|
||||||
|
"rev": "c843f477b15f51151f8c6bcc886954699440a6e1",
|
||||||
|
"type": "github"
|
||||||
|
},
|
||||||
|
"original": {
|
||||||
|
"owner": "nix-community",
|
||||||
|
"ref": "2.0.8",
|
||||||
|
"repo": "bun2nix",
|
||||||
|
"type": "github"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"flake-parts": {
|
||||||
|
"inputs": {
|
||||||
|
"nixpkgs-lib": "nixpkgs-lib"
|
||||||
|
},
|
||||||
|
"locked": {
|
||||||
|
"lastModified": 1769996383,
|
||||||
|
"narHash": "sha256-AnYjnFWgS49RlqX7LrC4uA+sCCDBj0Ry/WOJ5XWAsa0=",
|
||||||
|
"owner": "hercules-ci",
|
||||||
|
"repo": "flake-parts",
|
||||||
|
"rev": "57928607ea566b5db3ad13af0e57e921e6b12381",
|
||||||
|
"type": "github"
|
||||||
|
},
|
||||||
|
"original": {
|
||||||
|
"owner": "hercules-ci",
|
||||||
|
"repo": "flake-parts",
|
||||||
|
"type": "github"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"import-tree": {
|
||||||
|
"locked": {
|
||||||
|
"lastModified": 1763762820,
|
||||||
|
"narHash": "sha256-ZvYKbFib3AEwiNMLsejb/CWs/OL/srFQ8AogkebEPF0=",
|
||||||
|
"owner": "vic",
|
||||||
|
"repo": "import-tree",
|
||||||
|
"rev": "3c23749d8013ec6daa1d7255057590e9ca726646",
|
||||||
|
"type": "github"
|
||||||
|
},
|
||||||
|
"original": {
|
||||||
|
"owner": "vic",
|
||||||
|
"repo": "import-tree",
|
||||||
|
"type": "github"
|
||||||
|
}
|
||||||
|
},
|
||||||
"nixpkgs": {
|
"nixpkgs": {
|
||||||
"locked": {
|
"locked": {
|
||||||
"lastModified": 1770562336,
|
"lastModified": 1770562336,
|
||||||
|
|
@ -16,10 +74,62 @@
|
||||||
"type": "github"
|
"type": "github"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"nixpkgs-lib": {
|
||||||
|
"locked": {
|
||||||
|
"lastModified": 1769909678,
|
||||||
|
"narHash": "sha256-cBEymOf4/o3FD5AZnzC3J9hLbiZ+QDT/KDuyHXVJOpM=",
|
||||||
|
"owner": "nix-community",
|
||||||
|
"repo": "nixpkgs.lib",
|
||||||
|
"rev": "72716169fe93074c333e8d0173151350670b824c",
|
||||||
|
"type": "github"
|
||||||
|
},
|
||||||
|
"original": {
|
||||||
|
"owner": "nix-community",
|
||||||
|
"repo": "nixpkgs.lib",
|
||||||
|
"type": "github"
|
||||||
|
}
|
||||||
|
},
|
||||||
"root": {
|
"root": {
|
||||||
"inputs": {
|
"inputs": {
|
||||||
|
"bun2nix": "bun2nix",
|
||||||
"nixpkgs": "nixpkgs"
|
"nixpkgs": "nixpkgs"
|
||||||
}
|
}
|
||||||
|
},
|
||||||
|
"systems": {
|
||||||
|
"locked": {
|
||||||
|
"lastModified": 1681028828,
|
||||||
|
"narHash": "sha256-Vy1rq5AaRuLzOxct8nz4T6wlgyUR7zLU309k9mBC768=",
|
||||||
|
"owner": "nix-systems",
|
||||||
|
"repo": "default",
|
||||||
|
"rev": "da67096a3b9bf56a91d16901293e51ba5b49a27e",
|
||||||
|
"type": "github"
|
||||||
|
},
|
||||||
|
"original": {
|
||||||
|
"owner": "nix-systems",
|
||||||
|
"repo": "default",
|
||||||
|
"type": "github"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"treefmt-nix": {
|
||||||
|
"inputs": {
|
||||||
|
"nixpkgs": [
|
||||||
|
"bun2nix",
|
||||||
|
"nixpkgs"
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"locked": {
|
||||||
|
"lastModified": 1770228511,
|
||||||
|
"narHash": "sha256-wQ6NJSuFqAEmIg2VMnLdCnUc0b7vslUohqqGGD+Fyxk=",
|
||||||
|
"owner": "numtide",
|
||||||
|
"repo": "treefmt-nix",
|
||||||
|
"rev": "337a4fe074be1042a35086f15481d763b8ddc0e7",
|
||||||
|
"type": "github"
|
||||||
|
},
|
||||||
|
"original": {
|
||||||
|
"owner": "numtide",
|
||||||
|
"repo": "treefmt-nix",
|
||||||
|
"type": "github"
|
||||||
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"root": "root",
|
"root": "root",
|
||||||
|
|
|
||||||
99
flake.nix
99
flake.nix
|
|
@ -3,12 +3,22 @@
|
||||||
|
|
||||||
inputs = {
|
inputs = {
|
||||||
nixpkgs.url = "github:NixOS/nixpkgs/nixos-unstable";
|
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 =
|
outputs =
|
||||||
{
|
{
|
||||||
self,
|
self,
|
||||||
nixpkgs,
|
nixpkgs,
|
||||||
|
bun2nix,
|
||||||
}:
|
}:
|
||||||
let
|
let
|
||||||
supportedSystems = [
|
supportedSystems = [
|
||||||
|
|
@ -24,35 +34,11 @@
|
||||||
packages = forAllSystems (
|
packages = forAllSystems (
|
||||||
system:
|
system:
|
||||||
let
|
let
|
||||||
pkgs = nixpkgs.legacyPackages.${system};
|
pkgs = import nixpkgs {
|
||||||
lib = pkgs.lib;
|
inherit system;
|
||||||
|
overlays = [ bun2nix.overlays.default ];
|
||||||
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
|
|
||||||
'';
|
|
||||||
|
|
||||||
installPhase = ''
|
|
||||||
mkdir -p $out
|
|
||||||
cp -r node_modules $out/
|
|
||||||
'';
|
|
||||||
|
|
||||||
outputHashAlgo = "sha256";
|
|
||||||
outputHashMode = "recursive";
|
|
||||||
outputHash = "sha256-84Aw9E2+fEZT+lIb9k1bodessoex+YFr0im2GMVAPnw=";
|
|
||||||
};
|
};
|
||||||
|
lib = pkgs.lib;
|
||||||
in
|
in
|
||||||
{
|
{
|
||||||
handy = pkgs.rustPlatform.buildRustPackage {
|
handy = pkgs.rustPlatform.buildRustPackage {
|
||||||
|
|
@ -64,13 +50,11 @@
|
||||||
|
|
||||||
cargoLock = {
|
cargoLock = {
|
||||||
lockFile = ./src-tauri/Cargo.lock;
|
lockFile = ./src-tauri/Cargo.lock;
|
||||||
outputHashes = {
|
# Automatically fetch git dependencies using builtins.fetchGit.
|
||||||
"rdev-0.5.0-2" = "sha256-0F7EaPF8Oa1nnSCAjzEAkitWVpMldL3nCp3c5DVFMe0=";
|
# This eliminates the need for manual outputHashes that had to be
|
||||||
"rodio-0.20.1" = "sha256-wq72awTvN4fXZ9qZc5KLYS9oMxtNDZ4YGxfqz8msofs=";
|
# updated every time a git dependency changed in Cargo.lock.
|
||||||
"tauri-nspanel-2.1.0" = "sha256-gotQQ1DOhavdXU8lTEux0vdY880LLetk7VLvSm6/8TI=";
|
# Safe for standalone flakes (not allowed in nixpkgs, it is needed something like crate2nix).
|
||||||
"tauri-runtime-2.10.0" = "sha256-s1IBM9hOY+HRdl/E5r7BsRTE7aLaFCCMK/DdS+bvZRc=";
|
allowBuiltinFetchGit = true;
|
||||||
"vad-rs-0.1.5" = "sha256-Q9Dxq31npyUPY9wwi6OxqSJrEvFvG8/n0dbyT7XNcyI=";
|
|
||||||
};
|
|
||||||
};
|
};
|
||||||
|
|
||||||
postPatch = ''
|
postPatch = ''
|
||||||
|
|
@ -78,6 +62,12 @@
|
||||||
src-tauri/tauri.conf.json > $TMPDIR/tauri.conf.json
|
src-tauri/tauri.conf.json > $TMPDIR/tauri.conf.json
|
||||||
cp $TMPDIR/tauri.conf.json src-tauri/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
|
# Point libappindicator-sys to the Nix store path
|
||||||
substituteInPlace \
|
substituteInPlace \
|
||||||
$cargoDepsCopy/libappindicator-sys-*/src/lib.rs \
|
$cargoDepsCopy/libappindicator-sys-*/src/lib.rs \
|
||||||
|
|
@ -93,11 +83,31 @@
|
||||||
--replace-fail '.write_to_file("opencc.h");' '// skipped'
|
--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
|
||||||
|
llvmPackages.libclang
|
||||||
|
shaderc
|
||||||
|
];
|
||||||
|
|
||||||
preBuild = ''
|
preBuild = ''
|
||||||
cp -r ${bunDeps}/node_modules node_modules
|
# bun2nix.hook has already set up node_modules from pre-fetched cache.
|
||||||
chmod -R +w node_modules
|
# Build the frontend with bun (tsc + vite).
|
||||||
substituteInPlace node_modules/.bin/{tsc,vite} \
|
|
||||||
--replace-fail "/usr/bin/env node" "${lib.getExe pkgs.bun}"
|
|
||||||
export HOME=$TMPDIR
|
export HOME=$TMPDIR
|
||||||
bun run build
|
bun run build
|
||||||
'';
|
'';
|
||||||
|
|
@ -116,17 +126,6 @@
|
||||||
runHook postInstall
|
runHook postInstall
|
||||||
'';
|
'';
|
||||||
|
|
||||||
nativeBuildInputs = with pkgs; [
|
|
||||||
cargo-tauri.hook
|
|
||||||
pkg-config
|
|
||||||
wrapGAppsHook4
|
|
||||||
bun
|
|
||||||
jq
|
|
||||||
cmake
|
|
||||||
llvmPackages.libclang
|
|
||||||
shaderc
|
|
||||||
];
|
|
||||||
|
|
||||||
buildInputs = with pkgs; [
|
buildInputs = with pkgs; [
|
||||||
webkitgtk_4_1
|
webkitgtk_4_1
|
||||||
gtk3
|
gtk3
|
||||||
|
|
|
||||||
|
|
@ -16,7 +16,8 @@
|
||||||
"format:backend": "cd src-tauri && cargo fmt",
|
"format:backend": "cd src-tauri && cargo fmt",
|
||||||
"test:playwright": "playwright test",
|
"test:playwright": "playwright test",
|
||||||
"test:playwright:ui": "playwright test --ui",
|
"test:playwright:ui": "playwright test --ui",
|
||||||
"check:translations": "bun scripts/check-translations.ts"
|
"check:translations": "bun scripts/check-translations.ts",
|
||||||
|
"postinstall": "bun scripts/check-nix-deps.ts"
|
||||||
},
|
},
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@tailwindcss/vite": "^4.1.16",
|
"@tailwindcss/vite": "^4.1.16",
|
||||||
|
|
|
||||||
81
scripts/check-nix-deps.ts
Normal file
81
scripts/check-nix-deps.ts
Normal file
|
|
@ -0,0 +1,81 @@
|
||||||
|
// scripts/check-nix-deps.ts — Keep .nix/bun.nix in sync with bun.lock
|
||||||
|
//
|
||||||
|
// Handy uses bun2nix to generate per-package Nix fetchurl expressions from
|
||||||
|
// bun.lock. This replaces the old FOD (Fixed-Output Derivation) approach
|
||||||
|
// where a single hash covered the entire node_modules — that hash would
|
||||||
|
// break whenever the bun version in nixpkgs changed, even without any
|
||||||
|
// dependency updates.
|
||||||
|
//
|
||||||
|
// How it works:
|
||||||
|
// 1. Computes sha256 of bun.lock
|
||||||
|
// 2. Compares with stored hash in .nix/bun-lock-hash
|
||||||
|
// 3. If they match — nothing to do (~2ms)
|
||||||
|
// 4. If they differ — runs `bunx bun2nix` to regenerate .nix/bun.nix
|
||||||
|
//
|
||||||
|
// When it runs:
|
||||||
|
// - Automatically via "postinstall" in package.json — triggers after every
|
||||||
|
// bun install / bun add / bun remove / bun update
|
||||||
|
// - Can also be run manually: bun scripts/check-nix-deps.ts
|
||||||
|
//
|
||||||
|
// What to commit:
|
||||||
|
// If the script regenerated .nix/bun.nix, commit it together with bun.lock:
|
||||||
|
// git add bun.lock .nix/bun.nix .nix/bun-lock-hash
|
||||||
|
|
||||||
|
import { existsSync, mkdirSync, readFileSync, writeFileSync } from "fs";
|
||||||
|
import { join, resolve } from "path";
|
||||||
|
|
||||||
|
const root = resolve(import.meta.dirname, "..");
|
||||||
|
const nixDir = join(root, ".nix");
|
||||||
|
const lockFile = join(root, "bun.lock");
|
||||||
|
const hashFile = join(nixDir, "bun-lock-hash");
|
||||||
|
const nixFile = join(nixDir, "bun.nix");
|
||||||
|
|
||||||
|
// No bun.lock — nothing to do
|
||||||
|
if (!existsSync(lockFile)) process.exit(0);
|
||||||
|
|
||||||
|
// Ensure .nix directory exists
|
||||||
|
mkdirSync(nixDir, { recursive: true });
|
||||||
|
|
||||||
|
// Compute sha256 of the current bun.lock
|
||||||
|
const currentHash = new Bun.CryptoHasher("sha256")
|
||||||
|
.update(readFileSync(lockFile))
|
||||||
|
.digest("hex");
|
||||||
|
|
||||||
|
// Read the previously stored hash (empty if first run)
|
||||||
|
const storedHash = existsSync(hashFile)
|
||||||
|
? readFileSync(hashFile, "utf-8").trim()
|
||||||
|
: "";
|
||||||
|
|
||||||
|
// If hashes match, bun.nix is up to date — nothing to do
|
||||||
|
if (currentHash === storedHash) process.exit(0);
|
||||||
|
|
||||||
|
// bun.lock has changed — regenerate the Nix dependency file
|
||||||
|
console.log(
|
||||||
|
`[check-nix-deps] bun.lock has changed, regenerating ${nixFile}...`,
|
||||||
|
);
|
||||||
|
|
||||||
|
const result = Bun.spawnSync(["bunx", "bun2nix", "-o", nixFile], {
|
||||||
|
cwd: root,
|
||||||
|
stdio: ["inherit", "inherit", "inherit"],
|
||||||
|
});
|
||||||
|
|
||||||
|
if (result.exitCode !== 0) {
|
||||||
|
console.warn(
|
||||||
|
"[check-nix-deps] Warning: bunx bun2nix failed. .nix/bun.nix may be outdated.",
|
||||||
|
);
|
||||||
|
console.warn(
|
||||||
|
"[check-nix-deps] Nix users: run `bunx bun2nix -o .nix/bun.nix` manually.",
|
||||||
|
);
|
||||||
|
console.warn(
|
||||||
|
"[check-nix-deps] Non-Nix users: this is safe to ignore, CI will catch it.",
|
||||||
|
);
|
||||||
|
// Exit 0 so that `bun install` is not blocked for non-Nix developers.
|
||||||
|
// CI validates bun.nix independently.
|
||||||
|
process.exit(0);
|
||||||
|
}
|
||||||
|
|
||||||
|
writeFileSync(hashFile, currentHash + "\n");
|
||||||
|
console.log(`[check-nix-deps] Updated ${nixFile}`);
|
||||||
|
console.log(
|
||||||
|
"[check-nix-deps] Don't forget to commit: .nix/bun.nix .nix/bun-lock-hash",
|
||||||
|
);
|
||||||
|
|
@ -1,203 +0,0 @@
|
||||||
#!/usr/bin/env bash
|
|
||||||
# Updates outputHashes and bunDeps hash in flake.nix
|
|
||||||
# when Cargo or JS dependencies change.
|
|
||||||
#
|
|
||||||
# Usage: ./scripts/update-nix-hashes.sh
|
|
||||||
#
|
|
||||||
# Handles:
|
|
||||||
# - Version changes in git dependencies (Cargo.lock → outputHashes)
|
|
||||||
# - bun.lock changes (→ bunDeps outputHash)
|
|
||||||
#
|
|
||||||
# Requires: nix, awk, sed
|
|
||||||
# Works on: NixOS, Ubuntu/Debian, macOS
|
|
||||||
|
|
||||||
set -euo pipefail
|
|
||||||
|
|
||||||
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
|
|
||||||
PROJECT_DIR="$(cd "$SCRIPT_DIR/.." && pwd)"
|
|
||||||
|
|
||||||
FLAKE_NIX="$PROJECT_DIR/flake.nix"
|
|
||||||
CARGO_LOCK="$PROJECT_DIR/src-tauri/Cargo.lock"
|
|
||||||
|
|
||||||
FAKE_HASH="sha256-AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA="
|
|
||||||
|
|
||||||
# Portable sed -i (macOS requires -i '', GNU sed requires just -i)
|
|
||||||
sedi() {
|
|
||||||
if sed --version >/dev/null 2>&1; then
|
|
||||||
sed -i "$@"
|
|
||||||
else
|
|
||||||
sed -i '' "$@"
|
|
||||||
fi
|
|
||||||
}
|
|
||||||
|
|
||||||
if ! command -v nix >/dev/null 2>&1; then
|
|
||||||
echo "error: nix is not installed. Install it from https://nixos.org/download/" >&2
|
|
||||||
exit 1
|
|
||||||
fi
|
|
||||||
if [ ! -f "$FLAKE_NIX" ]; then
|
|
||||||
echo "error: flake.nix not found at $FLAKE_NIX" >&2
|
|
||||||
exit 1
|
|
||||||
fi
|
|
||||||
if [ ! -f "$CARGO_LOCK" ]; then
|
|
||||||
echo "error: Cargo.lock not found at $CARGO_LOCK" >&2
|
|
||||||
exit 1
|
|
||||||
fi
|
|
||||||
|
|
||||||
# ---------------------------------------------------------------------------
|
|
||||||
# Step 1: Extract git dependency representative keys from Cargo.lock
|
|
||||||
#
|
|
||||||
# Cargo.lock format (consecutive lines per package):
|
|
||||||
# [[package]]
|
|
||||||
# name = "foo"
|
|
||||||
# version = "1.2.3"
|
|
||||||
# source = "git+https://...#commit"
|
|
||||||
#
|
|
||||||
# Multiple packages from the same git URL share one outputHash entry keyed
|
|
||||||
# by the alphabetically first "name-version" from that URL.
|
|
||||||
# ---------------------------------------------------------------------------
|
|
||||||
|
|
||||||
extract_cargo_git_keys() {
|
|
||||||
awk '
|
|
||||||
/^name = / { name = substr($3, 2, length($3) - 2) }
|
|
||||||
/^version = / { version = substr($3, 2, length($3) - 2) }
|
|
||||||
/^source = "git\+/ {
|
|
||||||
src = $3
|
|
||||||
gsub(/^"git\+/, "", src)
|
|
||||||
sub(/#.*/, "", src)
|
|
||||||
key = name "-" version
|
|
||||||
if (!(src in best) || key < best[src])
|
|
||||||
best[src] = key
|
|
||||||
}
|
|
||||||
END { for (s in best) print best[s] }
|
|
||||||
' "$CARGO_LOCK" | sort
|
|
||||||
}
|
|
||||||
|
|
||||||
# ---------------------------------------------------------------------------
|
|
||||||
# Step 2: Extract current outputHashes keys from flake.nix
|
|
||||||
# ---------------------------------------------------------------------------
|
|
||||||
|
|
||||||
extract_flake_keys() {
|
|
||||||
# Portable: no grep -P, use awk instead
|
|
||||||
sed -n '/outputHashes/,/};/p' "$FLAKE_NIX" \
|
|
||||||
| awk -F'"' '/sha256-/ { print $2 }' \
|
|
||||||
| sort
|
|
||||||
}
|
|
||||||
|
|
||||||
# ---------------------------------------------------------------------------
|
|
||||||
# Step 3: Compare keys and update flake.nix where needed
|
|
||||||
# ---------------------------------------------------------------------------
|
|
||||||
|
|
||||||
update_output_hash_keys() {
|
|
||||||
local cargo_keys flake_keys
|
|
||||||
cargo_keys=$(extract_cargo_git_keys)
|
|
||||||
flake_keys=$(extract_flake_keys)
|
|
||||||
|
|
||||||
local changed=0
|
|
||||||
|
|
||||||
# For each flake key, check if it still matches a Cargo.lock git dep.
|
|
||||||
# If the package name matches but version differs -> update.
|
|
||||||
echo "$flake_keys" | while IFS= read -r fk; do
|
|
||||||
[ -z "$fk" ] && continue
|
|
||||||
|
|
||||||
# Extract the package name prefix (everything before the version)
|
|
||||||
fname=$(echo "$fk" | sed 's/-[0-9][0-9.]*[-0-9]*$//')
|
|
||||||
|
|
||||||
if echo "$cargo_keys" | grep -qxF "$fk"; then
|
|
||||||
continue
|
|
||||||
fi
|
|
||||||
|
|
||||||
# Key not found in Cargo.lock — look for a replacement with the same name
|
|
||||||
replacement=$(echo "$cargo_keys" | while IFS= read -r ck; do
|
|
||||||
cname=$(echo "$ck" | sed 's/-[0-9][0-9.]*[-0-9]*$//')
|
|
||||||
if [ "$cname" = "$fname" ]; then
|
|
||||||
echo "$ck"
|
|
||||||
break
|
|
||||||
fi
|
|
||||||
done)
|
|
||||||
|
|
||||||
if [ -n "$replacement" ]; then
|
|
||||||
echo "outputHashes: $fk -> $replacement"
|
|
||||||
sedi "s|\"$fk\" = \"sha256-[^\"]*\"|\"$replacement\" = \"$FAKE_HASH\"|" "$FLAKE_NIX"
|
|
||||||
changed=1
|
|
||||||
else
|
|
||||||
echo "warning: $fk not found in Cargo.lock git deps and no replacement detected" >&2
|
|
||||||
echo " This entry may need to be removed or added manually." >&2
|
|
||||||
fi
|
|
||||||
done
|
|
||||||
|
|
||||||
# Check for new git deps not yet in flake.nix
|
|
||||||
echo "$cargo_keys" | while IFS= read -r ck; do
|
|
||||||
[ -z "$ck" ] && continue
|
|
||||||
if ! echo "$flake_keys" | grep -qxF "$ck" && ! grep -q "\"$ck\"" "$FLAKE_NIX"; then
|
|
||||||
echo "warning: git dep $ck exists in Cargo.lock but not in flake.nix outputHashes" >&2
|
|
||||||
echo " You may need to add it manually." >&2
|
|
||||||
fi
|
|
||||||
done
|
|
||||||
|
|
||||||
return $changed
|
|
||||||
}
|
|
||||||
|
|
||||||
# ---------------------------------------------------------------------------
|
|
||||||
# Step 4: Iteratively fix hashes by running nix build and parsing errors
|
|
||||||
# ---------------------------------------------------------------------------
|
|
||||||
|
|
||||||
fix_hashes() {
|
|
||||||
local max_attempts=10
|
|
||||||
local attempt=0
|
|
||||||
|
|
||||||
while [ "$attempt" -lt "$max_attempts" ]; do
|
|
||||||
attempt=$((attempt + 1))
|
|
||||||
echo ""
|
|
||||||
echo "=== nix build attempt $attempt/$max_attempts ==="
|
|
||||||
|
|
||||||
local output
|
|
||||||
if output=$(nix build .#handy 2>&1); then
|
|
||||||
echo "Build successful!"
|
|
||||||
return 0
|
|
||||||
fi
|
|
||||||
|
|
||||||
# Check for hash mismatch
|
|
||||||
if echo "$output" | grep -q "hash mismatch in fixed-output derivation"; then
|
|
||||||
local specified got
|
|
||||||
specified=$(echo "$output" | grep "specified:" | awk '{print $2}')
|
|
||||||
got=$(echo "$output" | grep "got:" | awk '{print $2}')
|
|
||||||
|
|
||||||
if [ -n "$specified" ] && [ -n "$got" ]; then
|
|
||||||
echo "Hash mismatch: $specified -> $got"
|
|
||||||
sedi "s|$specified|$got|" "$FLAKE_NIX"
|
|
||||||
continue
|
|
||||||
fi
|
|
||||||
fi
|
|
||||||
|
|
||||||
# If we can't parse the error, show it and bail out
|
|
||||||
echo ""
|
|
||||||
echo "Build failed with an error that cannot be fixed automatically:" >&2
|
|
||||||
echo "$output" | tail -20 >&2
|
|
||||||
return 1
|
|
||||||
done
|
|
||||||
|
|
||||||
echo "error: exceeded max attempts ($max_attempts)" >&2
|
|
||||||
return 1
|
|
||||||
}
|
|
||||||
|
|
||||||
# ---------------------------------------------------------------------------
|
|
||||||
# Main
|
|
||||||
# ---------------------------------------------------------------------------
|
|
||||||
|
|
||||||
cd "$PROJECT_DIR"
|
|
||||||
|
|
||||||
echo "=== Nix flake hash updater ==="
|
|
||||||
echo ""
|
|
||||||
echo "Checking outputHashes keys against Cargo.lock..."
|
|
||||||
|
|
||||||
if update_output_hash_keys; then
|
|
||||||
echo "All outputHashes keys are up to date."
|
|
||||||
fi
|
|
||||||
|
|
||||||
echo ""
|
|
||||||
echo "Running nix build to verify/fix hashes..."
|
|
||||||
fix_hashes
|
|
||||||
|
|
||||||
echo ""
|
|
||||||
echo "Done. Changes in flake.nix:"
|
|
||||||
git diff --stat -- flake.nix 2>/dev/null || true
|
|
||||||
Loading…
Reference in a new issue