← Back · NextBSD research · relates to the library-resolution design (Lever B, #278) and the launchd PID 1 port

NextBSD system path layout — the Gershwin layout vs a POSIX layout

NextBSD (the OS: launchd PID 1, Apple userland, no rc.d) and Gershwin (the GNUstep desktop, which ships its own named tools-make layout) both descend from the NeXT/Apple filesystem model, and today both reach into the same /System tree — they even collide on one exact directory, /System/Library/Libraries. The ask: relocate NextBSD's system machinery so that /System belongs entirely to Gershwin — a clean rm -rf /System removes only Gershwin and breaks nothing in NextBSD. This spike lays out two coherent target layouts — keep the Apple/Gershwin-domain shape, or move NextBSD to a POSIX shape — with the full per-path inventory, the exact change surface, and an honest accounting of what we lose either way. Decision doc, not a runbook — no code changed.

2026-07-02. Synthesized from three parallel source investigations across nextbsd-userland, nextbsd-freebsd-compat, nextbsd-kernel, nextbsd-kernel-modules, and nextbsd-work (launchd LaunchDaemons scan; kext_tools/OSKext Extensions + caches; a full /System + /Library reference sweep). Citations are file:line.

The one-sentence question Should NextBSD's system layer stay in the Apple/GNUstep domains (and Gershwin move to a different domain), or move to POSIX paths (and Gershwin keep the domains)? The research says either is mechanically cheap — the interesting part is the philosophy and the small blast radius, both mapped below.

Contents

  1. The Gershwin layout (a named tools-make FilesystemLayout)
  2. What NextBSD actually puts under /System & /Library today
  3. The two layouts, side by side
  4. Per-path proposals
  5. What we lose (and gain) by going POSIX
  6. Blast radius, atomicity & the /private wrinkle
  7. Open decisions
  8. Appendix: change surface (file:line)

1. The Gershwin layout — a named tools-make FilesystemLayout, not the generic GNUstep model

Gershwin does not merely “follow the GNUstep domain convention.” It ships an official, named filesystem layout inside tools-make: FilesystemLayouts/gershwin, selectable at gnustep-make configure time alongside the stock gnustep, gnustep-with-network, fhs, fhs-system, apple, mac, next, and standalone layouts. It pins every GNUSTEP_* root explicitly — so the paths below are not convention, they are the checked-in configuration NextBSD must design against. Verbatim from tools-make/FilesystemLayouts/gershwin:

DomainRootLibraryAppsUsers
System/System/System/Library/System/Applications/System/Users
Network/Network/Network/Library/Network/Applications/Network/Users
Local/Local/Local/Library/Local/Applications/Local/Users
User~~/Library~/Applications

with GNUSTEP_DEFAULT_PREFIX=/, GNUstep makefiles at /System/Library/Makefiles, and per-domain Libraries/, Headers/, Tools/, WebApplications/ and Documentation/ subtrees. Three properties of this specific layout drive the rest of the doc:

The collision is already real — and already coded around Gershwin's own installer is the proof. gershwin-developer's make install refuses when /System/Applications already exists, and — the tell — its make uninstall would run rm -rf /System but explicitly bails out on NextBSD: “NextBSD system detected (/usr/lib/system exists)… Cannot uninstall /System as it may contain system libraries.” (gershwin-developer/Makefile). That guard is the failure this spike exists to remove: right now rm -rf /System is unsafe because NextBSD-the-OS and Gershwin-the-desktop share the /System tree.

What Gershwin actually claims under /System The gershwin-system repo is cloned directly onto /System and records a receipt at /System/Library/Receipts/System.stamp (gershwin-system/GNUmakefile:9) — Gershwin treats the whole tree as its own. Its /System/Library subtree spans Makefiles, Headers, Frameworks, Bundles, Tools, CoreServices, Scripts, Menu, Sounds, WebApplications, Documentation, Preferences (its GNUstep.conf lives at /System/Library/Preferences/GNUstep.conf), Libraries — and LaunchDaemons. So there are two concrete NextBSD↔Gershwin collisions inside /System/Library, not one:

DirectoryNextBSD puts hereGershwin puts here
/System/Library/Librariesrtld STANDARD_LIBRARY_PATH search entry (Lever B)GNUSTEP_SYSTEM_LIBRARIES install target (libdispatch et al., -DCMAKE_INSTALL_PREFIX=/System/Library)
/System/Library/LaunchDaemons11 boot daemon plists (getty, configd, syslogd…)session-service plists (e.g. loginwindow)

Even with distinct filenames, the LaunchDaemons directory can't be cleanly owned or wiped by either side once they interleave. Gershwin additionally claims /Developer as its from-source build root (a fifth root beyond the four domains), and at runtime LoginWindow overrides the user domain to ~/GNUstep rather than the layout's ~/Library.

2. What NextBSD puts under /System & /Library today

The good news from the sweep: the footprint is small, NextBSD-authored, and editable — and there is no /System/Library/Frameworks. NextBSD's Apple libraries all live in /usr/lib/system (rtld -rpath), so there is no dyld shared cache, no framework install_name, no ABI path to untangle — the part that is usually the hard part simply does not exist here.

PathWhatGenerated byConsumer
/System/Library/LaunchDaemons/
/Library/LaunchDaemons/
11 daemon plists (getty, configd, syslogd, notifyd, mDNSResponder, IPConfiguration, DiskArbitration, hostnamed, kextd, aslmanager, sshd). Contested: Gershwin also drops session plists (e.g. loginwindow) into /System/Library/LaunchDaemonsoverlay (authored files)launchd PID 1 scans both at boot launchd_plist_scan.c:264-265
/System/Library/Extensions/the kext bundles (if_em, i915, IntelWiFi, …)build (assemble-image extracts the modules tarball here)kextd default repo kextd.c:349, OSKextPrivate.h:55. Kernel never reads it (see below)
/System/Library/Libraries/— nothing installed — it's only a search-path entry in rtld's compiled defaultbuild (baked into STANDARD_LIBRARY_PATH, Lever B)rtld compat build.yml:84; already flagged “move away from gershwin-developer's” PORTING.md:86
/System/Library/Caches/com.apple.kext.caches/kext directory-scan speedup (id→URL, personalities)runtime (kextd, when the repo is a “system folder”)kextd only; never the kernel, never at boot
/Library/Preferences/SystemConfiguration/preferences.plistSCPreferences store (incl. ComputerName — the hostname persistence, #358)runtime (configd / hostnamed / IPConfiguration write it)configd & friends SCPreferences.c:35

Two findings that shrink the job

3. The two layouts, side by side

Option A — Keep the domains; move Gershwin off /System

NextBSD's system layer stays under /System/Library; to separate the two, Gershwin would move to another domain (e.g. /Local), leaving /System to the OS.

  • NextBSD stays maximally “Mac-shaped” — the property that motivates the whole Apple-userland effort; launchd/kext code changes little.
  • But the burden lands on Gershwin, and it's heavy. Gershwin's /System rooting isn't a convention we can flip — it's a checked-in --with-layout=gershwin tools-make layout, hard-coded runtime setenv GNUSTEP_SYSTEM_ROOT=/System (UIBridge/Server/main.m:1101), and a ports Uses/gershwin.mk default. Relocating it means forking Gershwin's layout — not NextBSD's call to make, and against the grain of the whole project.
  • “Remove Gershwin” would become rm -rf /Local/… — but only after that fork lands.
  • Still muddier philosophically: both the OS and the desktop are “system,” and /System/Library/{Libraries,LaunchDaemons} stay contested until Gershwin actually moves.

Option B — Move NextBSD to POSIX

NextBSD's system machinery moves to FreeBSD-idiomatic paths, vacating /System entirely. Gershwin then owns the GNUstep domains outright and rm -rf /System removes only Gershwin.

  • LaunchDaemons → /etc/launchd.d + /usr/local/etc/launchd.d
  • Extensions → /boot/kernel/extensions
  • Libraries → dropped from the rtld path
  • SCPreferences → /var/db/systemconfiguration (or /etc)
  • Caches → gone (self-disables)
  • Clean OS/desktop separation; pairs with service(8)'s /usr/local/etc/rc.d. Cost: the system layer stops looking like macOS.

Both are internally coherent. Option A optimises for Apple fidelity; Option B optimises for clean separation + POSIX idiom. A viable hybrid also exists (below): move only the things that genuinely collide or are already flagged (Libraries, and whichever of LaunchDaemons/Extensions you consider “OS, not desktop”), and leave the rest.

4. Per-path proposals

ItemOption A (domains)Option B (POSIX)Move cost
LaunchDaemons
system + third-party
stay /System/Library/LaunchDaemons + /Local/Library/LaunchDaemons /etc/launchd.d (phys /private/etc/launchd.d) + /usr/local/etc/launchd.d 2 scan literals + move 11 plists + rework the NSSystemDirectories “append /LaunchDaemons” shim. Atomic (getty = login).
Extensions (kexts) stay /System/Library/Extensions /boot/kernel/extensions (real dir) 1 constant + 3 tool defaults + build/CI/test placement (keep root:wheel + go-w). Kernel unchanged. Caches self-disable.
Libraries (rtld path) keep /System/Library/Libraries in STANDARD_LIBRARY_PATH remove it from the define (libs already in /usr/lib/system) One string in compat build.yml:84 + base rebuild. Already an intended move (PORTING.md:86).
Caches (kext) /System/Library/Caches gone (auto-disable) — or /var/db/kext.caches if ever wanted Zero (falls out of the Extensions move).
SCPreferences store /Local/Library/Preferences/… /var/db/systemconfiguration/ (phys /private/var/db) or /etc/systemconfiguration/ One #define PREFS_DEFAULT_DIR SCPreferences.c:35 (+ com.apple.Boot.plist). Dovetails with #358 hostname persistence.
Frameworks n/a — NextBSD has none; system libs live in /usr/lib/system

5. What we lose (and gain) by going POSIX

Costs of Option B

  • Apple-shape fidelity. The whole point of the Apple-userland port is that a Mac dev feels at home; LaunchDaemons/Extensions leaving /System/Library erodes that. Docs, muscle memory, and any Apple tooling that assumes the domains drift.
  • Two conventions in one system. launchd is Apple-native but its jobs now live in a FreeBSD /etc dir; kexts are Apple bundles under a FreeBSD /boot. Coherence cost.
  • The NSSystemDirectories shim gets a special case. Its <domain-prefix> + "/LaunchDaemons" shape doesn't fit a flat /etc/launchd.d; the abstraction that made domains uniform gets a carve-out.
  • Divergence from Gershwin-developer's assumptions wherever they expect the shared domain layout.

Gains of Option B

  • Clean separabilityrm -rf /System (and the other domains) removes only Gershwin; NextBSD stands alone. That's the stated goal.
  • POSIX idiom/etc, /usr/local/etc, /boot/kernel, /var/db are where a BSD admin looks; pairs with the just-landed service(8) + /usr/local/etc/rc.d.
  • Mutable state lands in /var (SCPrefs, caches) instead of a nominally-immutable domain.
  • No shared ownership of a directory tree between two independent projects — fewer “who owns this plist” questions.

The honest summary: the two options are not symmetric in who pays. Option A keeps NextBSD Mac-shaped but exports the work to Gershwin — forking its checked-in gershwin tools-make layout, runtime env, and ports Mk off /System — which NextBSD can't do unilaterally. Option B is nearly free mechanically (no kernel work, no frameworks, small edit surface), stays entirely inside NextBSD's own repos, and delivers exactly the requested property. What Option B costs is identity — NextBSD's system layer stops being a Mac clone and becomes “a BSD that runs launchd.” Whether that's a loss or the actual goal is the decision for wider review.

6. Blast radius, atomicity & the /private wrinkle

7. Open decisions

  1. A vs B vs hybrid — the philosophical call. Hybrid = move only the flagged/colliding items (Libraries for sure; then choose whether LaunchDaemons/Extensions are “OS” that should be POSIX or “Apple-shape” that should stay).
  2. SCPreferences target (if moving): /var/db/systemconfiguration (mutable-state semantics, recommended) vs /etc/systemconfiguration (config semantics).
  3. Names: /etc/launchd.d (proposed) vs /etc/launchd/daemons; /boot/kernel/extensions (proposed) vs /boot/kexts.
  4. Sequencing (if we implement): phased per-move PRs (safest — contains the getty-atomicity risk) vs one coordinated set. Deferred — planning only for now.

8. Appendix — change surface (file:line)

The load-bearing edits for Option B, from the three source sweeps. Everything else is comments or unbuilt mirror trees.

LaunchDaemons

Extensions + Caches

Libraries + SCPreferences

Kernel: no changes (kext autoload is a Mach-port present-scan, filesystem-blind). Frameworks/dyld: n/a (system libs in /usr/lib/system). Unbuilt mirror trees (launchd/nextbsd-pr, launchd/freebsd-launchd-mach, nextbsd-work/src/launchd, worktrees) carry identical literals and should be re-synced to avoid regressions but are not load-bearing.