Skip to content

fix: ship correct better-sqlite3 native module for arm64 Linux builds#1522

Draft
the-narwhal wants to merge 6 commits into
frappe:masterfrom
the-narwhal:aarch64-build-native-dependencies
Draft

fix: ship correct better-sqlite3 native module for arm64 Linux builds#1522
the-narwhal wants to merge 6 commits into
frappe:masterfrom
the-narwhal:aarch64-build-native-dependencies

Conversation

@the-narwhal

@the-narwhal the-narwhal commented Apr 13, 2026

Copy link
Copy Markdown
Contributor

Fixes #1513 and potentially fixes #1413

Bug: arm64 Linux packages contained an x86_64 better-sqlite3 native module

Root cause

The build-linux publish job ran on ubuntu-latest (x86_64). The postinstall hook (electron-rebuild) compiled better-sqlite3.node for the host machine before electron-builder ran. Because asarUnpack: '**/*.node' copies .node files verbatim into every output package, all arm64 .deb and .rpm artifacts ended up with an x86_64 native module inside — causing them to crash or fail to load on actual arm64 hardware.

A second smaller issue: AppImage was the only Linux target missing arm64 from its arch list, despite deb and rpm already including it.

What was discovered during testing

Two additional issues surfaced while validating the fix on a real arm64 Fedora machine:

  1. electron-builder's --arm64/--x64 CLI flags do not filter targets in the programmatic API. When both arches are listed in the config, electron-builder builds all of them regardless of the flag. It downloads the x64 better-sqlite3 prebuilt first, which then contaminates the arm64 package — both packages end up with the same x86-64 binary. The fix is to drive the target arch through the config directly via a TARGET_ARCH env var.

  2. electron-builder bundles an x86-only fpm binary. fpm is used to produce .deb and .rpm packages. On an arm64 runner, the bundled fpm-1.9.3-linux-x86 binary fails with Exec format error. The fix is to install fpm as a Ruby gem on the arm64 runner and set USE_SYSTEM_FPM=true.

Changes

electron-builder-config.mjs

  • Added arm64 to the AppImage target arch list so it is consistent with deb and rpm.
  • Added TARGET_ARCH env var support — when set, only that architecture is built. This is what ensures each CI job only ever packages its own arch, preventing cross-arch native module contamination.

.github/workflows/publish.yml

  • Renamed build-linuxbuild-linux-x64, running on ubuntu-latest with TARGET_ARCH: x64.
  • Added build-linux-arm64 job running on ubuntu-24.04-arm with TARGET_ARCH: arm64, USE_SYSTEM_FPM: 'true', and a step to install fpm via Ruby gem before building.
  • Added a Verify better-sqlite3 architecture step to both Linux jobs. It reads the ELF header of the unpacked .node file and hard-fails the job — before tarring or uploading — if the binary doesn't match the expected architecture. A regression can no longer produce a silently broken release.

Because yarn install (and therefore electron-rebuild) runs natively on each runner, better-sqlite3.node is compiled for the correct architecture automatically — no cross-compilation toolchain or Docker required.

Before / After

Artifact Before After
linux-x64.deb / .rpm / .AppImage ✅ correct ✅ correct
linux-arm64.deb / .rpm ❌ contained x64 .node ✅ correct arm64 .node — verified on Fedora aarch64
linux-arm64.AppImage ❌ never produced ✅ produced with correct arm64 .node
arch mismatch regression ❌ silent — bad release ships ✅ hard CI failure with diagnostic output

The linux publish job ran entirely on ubuntu-latest (x86_64). Because
`postinstall` runs `electron-rebuild`, better-sqlite3.node was compiled
for the host (x64) before electron-builder ever ran. With
`asarUnpack: '**/*.node'` copying the .node file verbatim, every arm64
.deb and .rpm package shipped an x86_64 native module and would crash
on real arm64 hardware.

Fix by splitting the single build-linux job into two jobs — one on
ubuntu-latest for x64 targets and one on ubuntu-24.04-arm for arm64
targets. Each job installs dependencies natively, so electron-rebuild
produces the correct .node binary for that architecture automatically.

Also add arm64 to the AppImage target arch list, which was missing
despite deb and rpm already including it.
… build

Add a verification step to both build-linux-x64 and build-linux-arm64
in publish.yml that inspects the ELF header of the unpacked .node file
before tarring or uploading artifacts. The job fails hard with the
actual arch string in the log if there is a mismatch, so a regression
can never produce a silently broken release.

Also add a temporary test-build-linux.yml (workflow_dispatch) to
produce downloadable artifacts for manual testing without a real
release. Remove before merging.
@the-narwhal the-narwhal reopened this Apr 14, 2026
… per job

The --arm64/--x64 CLI flags are not reliably filtered in electron-builder's
programmatic API. When both arches are listed in the config, electron-builder
builds all of them regardless of the flag, downloading the x64 better-sqlite3
prebuilt first which then contaminates the arm64 package with the wrong binary.

Drive the target arch entirely through TARGET_ARCH in electron-builder-config.mjs
so each CI job only ever builds for its own architecture, preventing cross-arch
native module contamination entirely.
@the-narwhal
the-narwhal marked this pull request as ready for review April 14, 2026 03:48
@the-narwhal
the-narwhal marked this pull request as draft April 14, 2026 04:14
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

🐛 [Bug] - v0.36.0 Linux aarch64 artifacts contain x86_64 native modules (better-sqlite3) [Bug] - AppImage stalls out on "Loading instance"

1 participant