Compare commits

..

3 commits

Author SHA1 Message Date
c20d053cd1 Merge pull request 'fix(nix): build bcrypt's native binding again under pnpm 10 — unbreaks nsecbunkerd on aio-demo' (#54) from fix/pnpm10-bcrypt-native-build into dev
Some checks failed
Docker image / build-and-push-image (push) Has been cancelled
Reviewed-on: #54
2026-09-05 19:00:01 +00:00
e05e184785 fix(nix): give the launcher a shell and coreutils/gnused on PATH
Some checks failed
Docker image / build-and-push-image (push) Has been cancelled
scripts/start.js shells out to `npm run prisma:migrate`, but the wrapper
only put nodejs and openssl on PATH, so npm could not spawn a shell at
all and every boot logged

  npm error syscall spawn sh
  npm error enoent spawn sh ENOENT

This was never fatal — the systemd unit's ExecStartPre already applies
migrations — but it is pure noise in the journal and it made the real
bcrypt crash harder to spot.

A shell alone is not enough: pnpm's generated node_modules/.bin/prisma
is itself a /bin/sh script that resolves its basedir with `dirname` and
`sed`. Ship bash, coreutils and gnused so the launcher stands on its own
under any caller's environment — the systemd unit's PATH, docker
compose, or a bare shell — rather than depending on what the caller
happens to export.

Verified: with PATH set to /nonexistent, the wrapper now applies all 25
migrations and starts the daemon cleanly.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01QBjd9Rw4ct134JH3CnLVaw
2026-09-05 20:58:26 +02:00
d2ec84a18e fix(nix): build bcrypt's native binding again under pnpm 10
0d8c436 (nodejs_20/pnpm_9 -> nodejs_24/pnpm_10) shipped a package with
no compiled bcrypt, and nsecbunkerd has been crashlooping on aio-demo
ever since: dist/daemon/index.js requires bcrypt at load, the store path
has only binding.gyp and the C++ sources under bcrypt@5.1.1, and the
daemon dies instantly with

  Cannot find module '.../bcrypt/lib/binding/napi-v3/bcrypt_lib.node'

buildPhase re-runs `pnpm install --force --offline` specifically to fire
bcrypt's node-gyp postinstall, because configHook installs with
--ignore-scripts. pnpm 10 changed that contract: it refuses to run *any*
dependency lifecycle script unless the package is allow-listed, and it
skips them silently — the install still reports success. So the bump
turned that line into a no-op, the build kept passing, and the failure
only surfaced at boot on the deployed host.

Pass --config.dangerouslyAllowAllBuilds=true to restore the pnpm 9
semantics this build has always relied on. We run inside the nix sandbox
against a store-seeded offline cache, so "all builds" is the same closed
set of scripts pnpm 9 already ran.

Add an installCheckPhase that requires bcrypt from the *installed* $out
tree, the same way the daemon does. This failure mode is invisible at
build time and fatal at boot, so it has to break the build instead of
the host.

Verified against the nixpkgs the deploy uses (da5ad661):
lib/binding/napi-v3/bcrypt_lib.node is produced, installCheck prints
"bcrypt native binding loads OK", and the daemon reaches
"nsecBunker ready to serve requests." pnpmDeps hash is unchanged.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01QBjd9Rw4ct134JH3CnLVaw
2026-09-05 20:58:17 +02:00

View file

@ -9,6 +9,9 @@
# 7.x in nixpkgs which doesn't ship libquery_engine.node, so we'd fail
# at postinstall.
prisma-engines_6,
bash,
coreutils,
gnused,
openssl,
sqlite,
python311,
@ -80,7 +83,21 @@ stdenv.mkDerivation (finalAttrs: {
# configHook ran with --ignore-scripts; re-run install to trigger
# native-module postinstall (bcrypt). --offline keeps it inside the
# store seeded by configHook.
pnpm install --force --offline --frozen-lockfile --reporter=append-only
#
# `dangerouslyAllowAllBuilds` is load-bearing under pnpm 10: unlike
# pnpm 9, pnpm 10 refuses to run *any* dependency lifecycle script
# unless the package is allow-listed (`onlyBuiltDependencies` /
# `pnpm approve-builds`), and it skips them **silently** — the install
# still reports success. Without this, bcrypt's node-gyp postinstall
# never runs, `lib/binding/napi-v3/bcrypt_lib.node` is never produced,
# and the daemon dies at boot with MODULE_NOT_FOUND. That is exactly
# what shipped in 0d8c436 (the nodejs_20/pnpm_9 -> nodejs_24/pnpm_10
# bump) and took down nsecbunkerd on aio-demo. The flag restores the
# pnpm 9 semantics this build has always relied on; we are inside the
# nix sandbox against a store-seeded offline cache, so "all builds"
# is the same closed set of scripts pnpm 9 ran.
pnpm install --force --offline --frozen-lockfile --reporter=append-only \
--config.dangerouslyAllowAllBuilds=true
pnpm prisma generate
pnpm build
@ -117,7 +134,22 @@ stdenv.mkDerivation (finalAttrs: {
makeWrapper ${lib.getExe nodejs_24} $out/bin/nsecbunkerd \
--add-flags $out/share/nsecbunkerd/scripts/start.js \
--set NODE_ENV production \
--prefix PATH : ${lib.makeBinPath [ openssl nodejs_24 ]} \
--prefix PATH : ${
lib.makeBinPath [
openssl
nodejs_24
# scripts/start.js shells out to `npm run prisma:migrate`, and
# npm needs a shell to spawn at all. The pnpm-generated
# `node_modules/.bin/prisma` shim is itself a /bin/sh script
# that resolves its basedir with `dirname` + `sed`. Ship all
# three so the launcher works under any caller's environment
# (the systemd unit's PATH, docker compose, a bare shell)
# rather than depending on what the caller happens to export.
bash
coreutils
gnused
]
} \
${
lib.concatStringsSep " \\\n " (
lib.mapAttrsToList (n: v: "--set ${n} ${lib.escapeShellArg v}") prismaEnv
@ -132,6 +164,22 @@ stdenv.mkDerivation (finalAttrs: {
runHook postInstall
'';
doInstallCheck = true;
# The bcrypt failure mode is silent at build time and fatal at boot, so
# assert the native binding loads from the *installed* tree exactly the
# way `dist/daemon/index.js` loads it. A build that can't require bcrypt
# must fail here rather than on the deployed host.
installCheckPhase = ''
runHook preInstallCheck
${lib.getExe nodejs_24} -e \
"require('$out/share/nsecbunkerd/node_modules/bcrypt'); \
console.log('bcrypt native binding loads OK')"
runHook postInstallCheck
'';
passthru = {
inherit prisma-engines;
};