aarch64-native-webassets-overlay¶
Overlays
A nixpkgs overlay that sources arch-agnostic web packages (static HTML/JS/CSS bundles) from a freshly imported native x86_64 pkgs on an aarch64 builder, so the JS toolchain is never run under emulation.
The problem¶
You are building for aarch64-linux on an aarch64 machine (or cross/emulated
via qemu-user / binfmt). A web package such as element-web or jitsi-meet
fails to build with something like:
The build runs webpack, which spins up Node's V8 JIT. Under qemu-user
emulation the JIT generates host-native machine code that the emulator cannot
execute, and the build dies with SIGILL. This is not a bug in the package —
it is the JIT tripping over the emulator.
The insight¶
The output of these packages is platform-independent: minified JS, CSS, HTML, images. Nothing arch-specific ends up in the store path. So the x86_64-built derivation is a perfectly valid substitute on aarch64.
Instead of emulating webpack, import a fresh x86_64-linux pkgs from the same
nixpkgs source (prev.path) and take the package from there. On an x86_64
builder (or via a native/remote x86_64 build) the toolchain runs on real
hardware — the emulator never touches the JS build at all.
Usage¶
default.nix is a function returning an overlay. Call it with the list of
attribute names you want sourced natively, then add the result to your
overlays / nixpkgs.overlays:
nixpkgs.overlays = [
# defaults to [ "element-web" ]
(import ./aarch64-native-webassets-overlay { })
# or choose your own:
(import ./aarch64-native-webassets-overlay {
packages = [ "element-web" "jitsi-meet" ];
})
];
Option¶
| name | default | meaning |
|---|---|---|
packages |
[ "element-web" ] |
Attribute names in pkgs to replace with their natively-built x86_64 equivalents on aarch64-linux. |
The overlay is a no-op on any non-aarch64-linux system, so it is safe to apply
unconditionally in a shared config.
Traps and caveats¶
-
Apply it first. Add this overlay before any overlay that patches or depends on the listed packages. As soon as something references the emulated
element-web, that build is pinned and the substitution can no longer take effect. -
Only for arch-agnostic outputs. Every name you pass must produce output with no native binaries — pure static web assets. If you list a package whose closure contains compiled ELF, you will ship x86_64 binaries onto an aarch64 host and they will not run.
-
You still need an x86_64 build path. This overlay moves the build to x86_64; it does not make it appear from nowhere. Building on aarch64 alone, you need an x86_64 builder available (a remote builder, or an x86_64 host in your
nix.buildMachines). The point is to avoid emulating the JS toolchain, not to avoid building it. -
Same nixpkgs revision. Using
prev.path(not a separately pinned nixpkgs) guarantees the native pkgs is the exact revision backing your current pkgs, andconfigis carried across soallowUnfree/permittedInsecurePackages/ etc. still apply to the native instantiation. -
The proper fix is a patched qemu. If you want the emulated build to work (e.g. no x86_64 builder available), the underlying cure is a
qemu-userbinfmt that handles the JIT correctly. This overlay is the pragmatic workaround when the output is arch-independent anyway.
Source¶
overlays/aarch64-native-webassets-overlay/default.nix
# aarch64-native-webassets-overlay
#
# Source arch-agnostic web packages (static HTML/JS/CSS assets) from a freshly
# imported *native* x86_64 pkgs when their build itself SIGILLs under qemu-user
# on an aarch64 builder.
#
# The trap: packages like element-web / jitsi-meet emit platform-independent
# static assets, but the *build* runs webpack + the V8 JIT. Under qemu-user
# emulation the JIT emits host instructions the emulator can't execute, so the
# build dies with "uncaught target signal 4 (Illegal instruction)" (SIGILL).
# Because the OUTPUT is arch-agnostic, the natively-built x86_64 derivation is a
# byte-for-byte valid substitute on aarch64 — and building it natively means the
# emulator never runs the JS toolchain at all.
#
# Usage — this file is a *function* returning a nixpkgs overlay. Call it with
# the list of attribute names to source natively, then add the result to your
# `overlays`/`nixpkgs.overlays`:
#
# nixpkgs.overlays = [
# (import ./aarch64-native-webassets-overlay { }) # defaults below
# ];
#
# # or pick your own packages:
# nixpkgs.overlays = [
# (import ./aarch64-native-webassets-overlay {
# packages = [ "element-web" "jitsi-meet" ];
# })
# ];
#
# ORDERING TRAP: apply this overlay *first*, before any overlay that patches or
# depends on the listed packages. Once something references `element-web`, it
# pins the emulated build and this substitution can no longer take effect.
#
# `packages` : list of attribute names in pkgs to replace with their
# natively-built x86_64 equivalents on aarch64-linux. Each must be
# a package whose output is truly arch-independent (static web
# assets) — do NOT list anything with native binaries in its
# output, or you will ship x86_64 ELF onto an aarch64 host.
{ packages ? [ "element-web" ] }:
# Standard overlay signature. `final` is unused: we deliberately take the
# native derivations verbatim rather than letting them re-enter the aarch64
# fixpoint (which would just reintroduce the emulated build).
_final: prev:
prev.lib.optionalAttrs (prev.stdenv.hostPlatform.system == "aarch64-linux") (
let
# A fresh, independent x86_64 nixpkgs instantiation. `prev.path` is the
# nixpkgs source tree backing the current pkgs, so this reuses the exact
# same nixpkgs revision — only the target system differs. `config` is
# carried over so allowUnfree / permittedInsecurePackages / etc. still hold.
pkgsX86 = import prev.path {
system = "x86_64-linux";
config = prev.config;
};
in
prev.lib.genAttrs packages (name: pkgsX86.${name})
)