diff --git a/desktop/.gitignore b/desktop/.gitignore index ec66c1d4739..127c85effdd 100644 --- a/desktop/.gitignore +++ b/desktop/.gitignore @@ -58,6 +58,7 @@ scripts/ **/python_reference/ Desktop/Sources/Resources/ffmpeg Desktop/Sources/Resources/node +Desktop/Sources/Resources/libnode.*.dylib # Local Rust helper build output codex-proxy/target/ diff --git a/desktop/run.sh b/desktop/run.sh index a7d3bf8e375..e2a3c3bfb8f 100755 --- a/desktop/run.sh +++ b/desktop/run.sh @@ -587,8 +587,17 @@ if [ -n "$SIGN_IDENTITY" ]; then fi # Sign the bundled node binary with developer identity + Node.entitlements # (macOS requires executables inside app bundles to be properly signed) - NODE_BIN="$APP_BUNDLE/Contents/Resources/Omi Computer_Omi Computer.bundle/node" + NODE_BUNDLE_DIR="$APP_BUNDLE/Contents/Resources/Omi Computer_Omi Computer.bundle" + NODE_BIN="$NODE_BUNDLE_DIR/node" if [ -f "$NODE_BIN" ]; then + # Sign any libnode dylib staged alongside the binary (Homebrew dynamic builds). + # Dylibs don't carry entitlements at runtime (macOS reads them from the main executable only); + # sign with --options runtime alone, no --entitlements. + for libnode_dylib in "$NODE_BUNDLE_DIR"/libnode.*.dylib; do + [ -f "$libnode_dylib" ] || continue + substep "Signing $(basename "$libnode_dylib")" + codesign --force --options runtime --sign "$SIGN_IDENTITY" "$libnode_dylib" + done substep "Signing bundled node binary" codesign --force --options runtime --entitlements Desktop/Node.entitlements --sign "$SIGN_IDENTITY" "$NODE_BIN" fi diff --git a/desktop/scripts/prepare-agent-runtime.sh b/desktop/scripts/prepare-agent-runtime.sh index 9c934bf5434..f52b27eefe1 100755 --- a/desktop/scripts/prepare-agent-runtime.sh +++ b/desktop/scripts/prepare-agent-runtime.sh @@ -125,6 +125,35 @@ stage_local_node() { cp -f "$node_bin" "$NODE_RESOURCE" chmod +x "$NODE_RESOURCE" xattr -cr "$NODE_RESOURCE" 2>/dev/null || true + + # Homebrew node is a small stub that dlopen()s libnode.X.dylib via @loader_path. + # Copy the dylib alongside the binary so it resolves at both validation and runtime. + local libnode_name + libnode_name=$(otool -L "$node_bin" 2>/dev/null \ + | awk 'match($0, /libnode\.[0-9]+\.dylib/) {print substr($0, RSTART, RLENGTH); exit}') + if [ -n "$libnode_name" ]; then + local libnode_src="" + local node_bin_dir + node_bin_dir="$(dirname "$(realpath "$node_bin")")" + for candidate in \ + "$node_bin_dir/../lib/$libnode_name" \ + "$node_bin_dir/$libnode_name" \ + "$(brew --prefix 2>/dev/null)/lib/$libnode_name"; do + if [ -f "$candidate" ]; then + libnode_src="$(realpath "$candidate")" + break + fi + done + if [ -z "$libnode_src" ]; then + echo "ERROR: node requires $libnode_name but it was not found near $node_bin or in Homebrew lib." >&2 + exit 1 + fi + cp -f "$libnode_src" "$(dirname "$NODE_RESOURCE")/$libnode_name" + chmod u+w "$(dirname "$NODE_RESOURCE")/$libnode_name" + xattr -cr "$(dirname "$NODE_RESOURCE")/$libnode_name" 2>/dev/null || true + log "Staged $libnode_name alongside node (Homebrew dynamic build)" + fi + log "Staged local Node $("$NODE_RESOURCE" --version) from $node_bin" }