← Back · NextBSD Research

NextBSD: Linux support — the unified design

How a user runs any AppImage (and any Linux program) on NextBSD, end to end. This page unifies the existing Linux plans with what we learned running Cool Retro Term and 86Box on a real NextBSD box, and settles the open decisions — base distro, the /compat layout, and user identity (NSS).

2026-10-09. Synthesises the Linuxulator plan, the ABI-mounts plan, the debootstrap/apt plan, and a hands-on session on a NextBSD ThinkPad T460s (a live /compat/linux root). New in this doc — the single-emul_path constraint, the /lib64 requirement, the PT_INTERP→SIGABRT kernel defect, the messagebus provisioning gap, the D-Bus session story, and the NSS/identity design.

The three detailed plans already exist and are the source of truth for their layer; this document does not re-derive them. It adds the connective tissue, the real-box findings, and the decisions. Read top to bottom for the end-to-end story, or jump in:

  1. The three layers
  2. The rootfs — one root, Devuan, and the /compat layout
  3. The kernel — and the PT_INTERP→SIGABRT defect
  4. User identity (NSS) — so any user can run in it
  5. The AppImage runtime
  6. End to end: a user runs an AppImage
  7. Phased plan & ticket map
  8. Open decisions

1. The three layers

Everything stacks in three independent layers. A layer below is a prerequisite for the one above; a layer is inert (harmless, unmounted/absent) if the one below is missing.

LayerWhat it isWho provides itState
L1 KernelLinuxulator: linux64 + linux_common modules translate Linux syscalls; sets kern.features.linux64nextbsd-kernel (baked in)planned §4b
L2 Rootfs + mountsOne Linux root (Devuan minbase) at emul_path; the six ABI filesystems always mountednextbsd-linux bootstrap (debootstrap) + launchctl bootstrap linux_abi_mounts()planned §4, §8
L3 Identity + appsUser/group resolution (NSS) + the AppImage/GUI runtime (client libs, FUSE, D-Bus session, GPU)nextbsd-linux run / .app wrappers + curated packagesplanned §4, §5

2. The rootfs — one root, Devuan, and the /compat layout

2a. The base: Devuan

The debootstrap plan already made this choice; it holds. Devuan Excalibur is the default because it has the same Mesa 25.0.7 / libdrm 2.4.124 (built against 6.12) as Debian 13 trixie — an exact match for NextBSD’s drm-kmod 6.12-lts — but no systemd at all, and systemd’s postinst is the one thing that has actually failed under the Linuxulator.

BasesystemdglibcValid-UntilMesa/DRM matchNote
Devuan Excalibur (default)nonetracks its Debian base (≈ trixie-era)current6.12, exactNo systemd postinst; self-provisions messagebus (§4). Recommended.
Debian trixiepresent (inert)2.41 (newest)current6.12, exactWhat we used for the hands-on session; the messagebus provisioning gap bites here (§4).
Debian bookwormpresent2.36 (older)past (LTS)6.12Older glibc; a -o Acquire::Check-Valid-Until=false flag on every update. Oldest, most AppImage-compatible of the three by glibc age only.

2b. The /compat layout — one root, and why

The constraint is in the kernel, not in our layout. In sys/compat/linux/linux_util.c: linux_emul_path is one global string (:71, tunable as compat.linux.emul_path, CTLFLAG_RWTUN). On every Linux-ABI exec, linux_pwd_onexec() (:77) looks up that one global and calls pwd_altroot() to set the thread’s alt-root to it (:91; if the path is missing it falls back to the native root, :87). The alt-root is per-thread, but its value is that single global — there is no per-binary or per-process root selector. Hence one active Linux root: /compat/linux and /compat/devuan cannot both be live. Two simultaneous roots would require a kernel change to linux_pwd_onexec (a per-credential dispatch).

That one root serves three axes; only two of them force a choice of base:

You run…Depends on the root’s base?Why
Self-contained AppImages (FUSE or extracted)No — base-agnosticBrings its own runtime; needs only the compat mounts (§2d, under whatever emul_path is) + /dev/fuse + identity (§4). Rides any root, even a minimal one.
USES=linux / linux_base-rl9 ports (claude-code, chrome)Yes — pinned to rl9The wrapper execs the program by native path; its ld/libc then resolve under the alt-root, so the root must hold the rl9 base at /compat/linux.
Portable programs / portable AppImages (link system libs; 86Box’s libfluidsynth/librtmidi)Yes — a curated baseNeeds the specific system .sos present → the curated client-lib set (§5), hence a chosen base (Devuan).

2c. /lib64 — required, minimal, and why

Every unpatched x86-64 glibc binary carries PT_INTERP = /lib64/ld-linux-x86-64.so.2. The Linuxulator resolves that path under the alt root (a kernel lookup, not ld.so.conf), so the root must contain /lib64. Verified on the box: remove /compat/linux/lib64 and a bare chroot /compat/linux /bin/ls dies with SIGABRT (rc 134); restore it and rc=0. Minimal layout:

/compat/linux/lib64            -> usr/lib64          (symlink)
/compat/linux/usr/lib64/ld-linux-x86-64.so.2         (real file)
/compat/linux/usr/lib/x86_64-linux-gnu/ld-linux-x86-64.so.2   (real file, multiarch)

2d. The six ABI mounts (always present)

From the ABI-mounts plan, made by linux_abi_mounts() inside launchctl bootstrap at every boot (nextbsd-userland #244, fixing #190), even when no userland is installed:

MountWhereSupplies
devfs$emul/dev/dev/* (incl. dri), and devfs auto-creates dev/fd
devfs (shm)$emul/dev/shmby linux_dev_shm_create() at ABI init — Chromium’s shared-memory fallback
linprocfs$emul/procLinux-style /proc (debootstrap unmounts it; the apt wrapper remounts)
linsysfs$emul/sysLinux-style /sys
fdescfs$emul/dev/fdthe fd table
tmpfs + nullfs$emul/tmpa Linux /tmp (nullfs of the host /tmp)

$emul = compat.linux.emul_path. These are the compat instances; a path that is missing under $emul falls through to the native root (one extra negative lookup), which is how a self-contained AppImage runs with no userland.

3. The kernel — and the PT_INTERP→SIGABRT defect

The necessary kernel work is in the Linuxulator plan: bake linux_common + linux64 (sets kern.features.linux64), and build NVIDIA’s kext with the Linux-ioctl compat off. That is the whole of L1. What this section adds is a defect found on the real box that the plans did not anticipate.

4. User identity (NSS) — so any user can run in it

This is the layer that makes it any user, not just the one who built the root. Two distinct problems live here; conflating them is what muddied the earlier discussion.

4a. Resolution: “I am uid 5000” → “I am admin”

A host user (say admin, uid 5000, home /Local/Users/admin) execs a Linux ELF. It runs as uid 5000, and glibc calls getpwuid(5000) → NSS files → the root’s /etc/passwd. That file is the 20-line Devuan/Debian base — 5000 is not in it → NULL. The app cannot find its username or $HOME and misbehaves or fails.

The host resolves users with NextBSD’s own nss_directory_services module (/usr/lib/nss_directory_services.so.1, source in nextbsd-userland/src/nss_directory_services/; the host nsswitch is passwd: directory_services files). It is a FreeBSD nsdispatch(3) NSS source resolving from the DirectoryServices store.

4b. The options

OptionMechanismProsCons
v1 — generated identity (default)At bootstrap and on every nextbsd-linux run, copy the invoking host user’s passwd+group entries (uid/gid/home/shell) into the root’s /etc/passwd+/etc/group. Stock files NSS.Any user; no custom module; zero new code paths; exactly the FreeBSD Handbook’s “add your user to the root’s passwd” recipe — already the debootstrap plan’s recommendation.Per-user entries accumulate; the host home (/Local/Users/*) is outside the root, so $HOME needs a nullfs mount or a root-local dir.
v2 — glibc module + host bridge the “integrated” pathA Linux glibc NSS module in the root (nsswitch: passwd files <src>) that resolves by reaching the host. Two flavours: (a) nullfs-mount the host’s DirectoryServices store//etc into the root and read it; (b) a host-side daemon (FreeBSD libc — may use libds/nss_directory_services) answering uid/gid↔name over a unix socket; the glibc module is a thin client.The root sees the same live users/groups as native NextBSD — true integration; no per-user file churn.Real new code (a glibc NSS module +, for (b), a host daemon + socket protocol); the socket variant is the only one that survives chroot isolation cleanly.

4c. Provisioning: creating the accounts the base needs (the messagebus gap)

Distinct from resolution: some Linux packages need a system account to exist. The canonical one is messagebus. The dbus-system-bus-common postinst provisions it with a branch that keys on the base:

if command -v systemd-sysusers >/dev/null; then
    systemd-sysusers dbus.conf          # Debian: has systemd
else
    adduser --system --quiet --group messagebus   # Devuan: no systemd
fi

Verified on the box: after the three lines, dpkg --audit is clean and all five packages are ii; systemd is in the tree only because dbus-user-session Depends: … libpam-systemd, systemd, and it is inert (no PID-1) in a root. Devuan sidesteps all of it.

5. The AppImage runtime

AppImages are self-contained by design — the payload bundles its own runtime. So an AppImage needs only the compat mounts (under the single emul_path, §2d) + /dev/fuse (FUSE) or just the ld-fix (extracted, §3) + identity (§4). It does not need a curated base — it rides whichever root the mounts are under, coexisting with the rl9 ports (§2b). The “curated client-lib set” in §5b is therefore for portable (system-lib) software and the rare non-self-contained AppImage — not for ordinary self-contained ones.

5a. Delivery: FUSE vs extracted

FUSE (self-mount)Extracted (--appimage-extract)
Needsfuse + /dev/fuse + the compat mounts; the payload is self-contained in the squashfsthe compat mounts + the ld-fix (§3); a portable image additionally wants the root’s curated client libs (§5b)
Root userland?No (brings its own runtime)Self-contained: no. Portable (system-lib) image: yes — loads $emul’s libs
86Box caveatits relative interp still SIGABRTs unless the in-image ld symlink is a real filesame — needs the real-file ld workaround

5b. What a portable GUI program needs from the root

(A self-contained AppImage needs none of this — it brings its own libs. This list is for portable programs, and the rare non-self-contained AppImage that links system libraries.)

AppToolkitBase libsBusSpecialStatus
Cool Retro TermQtGL + X11 + fontconfig (bundles most)session (optional)—runs; verify on Devuan base
86BoxGTK3GL + X11 + libfluidsynth.so.2 + librtmidi.so.5 (§5b)session (optional)real-file ld fix (§3, §5a)runs after the ld fix; re-verify on Devuan

6. End to end: a user runs an AppImage

  1. admin (uid 5000) double-clicks 86Box.AppImage → the .app wrapper calls nextbsd-linux run ./86Box.AppImage.
  2. The kernel Linuxulator (L1) translates; linux_pwd_onexec() sets the alt root to emul_path = the Devuan root (L2).
  3. The six ABI mounts are already up (L2) — /proc, /sys, /dev/dri, /dev/shm, /dev/fd, /tmp.
  4. Identity (L3, §4a v1): the wrapper has ensured 5000 admin … is in the root’s passwd/group (and mounted /Local/Users/admin if the app wants its real home). getpwuid(5000) → admin.
  5. Runtime (L3, §5): curated libs load from the root; the relative-interp ld is a real file (so no SIGABRT); dbus-run-session gives a session bus; Mesa drives /dev/dri.
  6. The window appears on the NextBSD desktop.

7. Phased plan & ticket map

PhaseDeliverableTracks
P0This design doc — on pkgdemon.github.io, linked from NextBSD Research— (this page)
P1Bake linux64+linux_common into the kernel (kern.features.linux64); NVIDIA compat offLinuxulator plan §4 · nextbsd-kernel
P2 newKernel defect: unresolvable PT_INTERP → clean exec error / re-apply the compat prefix on symlink follow (kills 86Box today, §3)new nextbsd-kernel ticket → Epic #461
P3One Devuan root at emul_path + the six mounts + the /lib64 layout + curated client-lib set → CRT + 86Box run (fixed user)debootstrap plan (#241) · abi-mounts (#244/#190) · #526
P4User identity v1 — generated passwd/group on bootstrap+run → any user (Model A)debootstrap plan §5 · nextbsd-userland
P5AppImage delivery — FUSE (/dev/fuse) and/or extracted+real-file-ld; dbus-run-session; .app wrappersdebootstrap plan (§ AppImages, #242) · #460
P6 futureUser identity v2 (glibc module + host bridge); rl9-ports coexistence (Model B, emul_path-switch); NVIDIA Linux userlandnew tickets → Epic #461 / E13

8. Open decisions

DecisionRecommendationWhy / what closes it
Base distroDevuan Excalibur (confirms the debootstrap plan)No systemd (the Linuxulator failure) + exact 6.12 DRM match + self-provisions messagebus.
/compat layoutModel A — one curated rootemul_path is one global; two live roots is impossible. Closes the “/compat/linux vs /compat/devuan” question.
User identityv1 generated identity to ship; design v2 (bridge) nextv1 = Handbook recipe, any user, no new ABI. v2 = the integrated nss_directory_services bridge (over the §4a ABI wall).
AppImage deliveryextracted + real-file-ld now; FUSE once /dev/fuse is provenextracted is robust pre-kernel-fix; the P2 kernel fix then makes FUSE the clean “no root” path.
Home mappingnullfs-mount /Local/Users/<user> into the root (or a root-local $HOME)the host home is outside the alt root; pick the mount vs local-dir policy in P4.