← Back · NextBSD research · relates to the library-resolution design (Lever B, #278) and the launchd PID 1 port
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.
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.
tools-make FilesystemLayout)/System & /Library today/private wrinklefile:line)tools-make FilesystemLayout, not the generic GNUstep modelGershwin 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:
| Domain | Root | Library | Apps | Users |
|---|---|---|---|---|
| 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:
Libraries path is NextBSD's rtld path. The layout sets GNUSTEP_SYSTEM_LIBRARIES=/System/Library/Libraries — the exact directory NextBSD compiled into rtld as Lever B (STANDARD_LIBRARY_PATH=…:/System/Library/Libraries:…, compat build.yml:84). This is not a stylistic overlap; it is one directory claimed by two projects for two different purposes — Gershwin's ObjC framework/`.so` install target vs NextBSD's ld-elf.so search path./System/Users, /Network/Users, /Local/Users are Gershwin-specific. The layout's own header states it exists to “support the Network computing model Gershwin provides.” The generic GNUstep layout has no */Users roots — this is precisely why the spike must reason about the gershwin layout, not GNUstep-in-general./Library is still not a Gershwin path. The Local domain is /Local/Library, not /Library. So NextBSD's /Library/LaunchDaemons and /Library/Preferences don't collide with Gershwin — that stays a POSIX-vs-Apple style question. And the launchd port already resolves /Local/Library and /System/Library in its NSSystemDirectories shim (NSSystemDirectories.h:83,86), so the “keep the domains” option is not foreign to the code.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:
| Directory | NextBSD puts here | Gershwin puts here |
|---|---|---|
/System/Library/Libraries | rtld STANDARD_LIBRARY_PATH search entry (Lever B) | GNUSTEP_SYSTEM_LIBRARIES install target (libdispatch et al., -DCMAKE_INSTALL_PREFIX=/System/Library) |
/System/Library/LaunchDaemons | 11 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.
/System & /Library todayThe 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.
| Path | What | Generated by | Consumer |
|---|---|---|---|
/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/LaunchDaemons | overlay (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 default | build (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.plist | SCPreferences store (incl. ComputerName — the hostname persistence, #358) | runtime (configd / hostnamed / IPConfiguration write it) | configd & friends SCPreferences.c:35 |
Two findings that shrink the job
HOST_KEXTD_PORT — the kernel opens no kext directory, reads no cache, hardcodes no path (iokit_catalogue.c:308). There is no prelinked kernel / mkext (prelink.h:3, OSKext.c:12828). So moving Extensions needs zero kernel changes.Caches/ doesn't need a new home — it just disappears./SystemNextBSD'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.
/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.rm -rf /Local/… — but only after that fork lands./System/Library/{Libraries,LaunchDaemons} stay contested until Gershwin actually moves.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.
/etc/launchd.d + /usr/local/etc/launchd.d/boot/kernel/extensions/var/db/systemconfiguration (or /etc)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.
| Item | Option 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 |
— | |
/System/Library erodes that. Docs, muscle memory, and any Apple tooling that assumes the domains drift./etc dir; kexts are Apple bundles under a FreeBSD /boot. Coherence cost.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.rm -rf /System (and the other domains) removes only Gershwin; NextBSD stands alone. That's the stated goal./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./var (SCPrefs, caches) instead of a nominally-immutable domain.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.
/private wrinklegetty.plist is still in the old one (or vice-versa), there's no login prompt and boot looks hung with no error.root:wheel and not group/other-writable (OSKext refuses otherwise). The existing chown 0:0 / chmod go-w steps must follow the path./private wrinkle: assemble-image.sh symlinks /etc, /var, /tmp → /private/*. So POSIX targets in those trees are authored at overlay/private/etc/launchd.d and written at runtime to /private/var/db/…. /usr/local and /boot are real top-level dirs (no /private)./System/Library/Frameworks, no dyld cache, no hardcoded ABI path. The single generator of the /System tree is assemble-image.sh./var/db/systemconfiguration (mutable-state semantics, recommended) vs /etc/systemconfiguration (config semantics)./etc/launchd.d (proposed) vs /etc/launchd/daemons; /boot/kernel/extensions (proposed) vs /boot/kexts.file:line)The load-bearing edits for Option B, from the three source sweeps. Everything else is comments or unbuilt mirror trees.
nextbsd-userland/src/launchd/src/launchd_plist_scan.c:264,265 — the two launchd_scan_one_dir() literals (the load-bearing scan). This is the tree build-userland.sh actually builds.src/launchd/support/launchctl.c:88 (AUDITD_PLIST_FILE, used :2448), :2726/2728 (strcat "/LaunchDaemons"), and freebsd-shims/NSSystemDirectories.h:83,86 (/Local/Library, /System/Library) — the bulk-load shim needs a small restructure, not a string swap.overlay/System/Library/LaunchDaemons/* → overlay/private/etc/launchd.d/*, in both nextbsd-userland/overlay/ and nextbsd-work/overlays/.overlay/usr/tests/freebsd-launchd-mach/run.sh:920; installer script src/syslog/syslogd.tproj/after_install.sh:33 (if live).src/kext_tools/kext.subproj/OSKextPrivate.h:55 (the authoritative _kOSKextSystemLibraryExtensionsFolder), + defaults kextd.c:349, kextload.c:52, kextdeps.c:49, macro bootfiles.h:67.-r /boot/kernel/extensions in overlay/System/Library/LaunchDaemons/com.apple.kextd.plist (currently -w -v only).ci/assemble-image.sh:89,96,151-152; nextbsd-work/build.sh (kext dir + chmod block); nextbsd-kernel-modules/.github/workflows/build.yml:542-551; nextbsd-kernel-modules/tests/kext-boot-test.sh:57,58.OSKextPrivate.h:82 repoint if kept.nextbsd-freebsd-compat/.github/workflows/build.yml:84 — drop :/System/Library/Libraries from -DSTANDARD_LIBRARY_PATH.src/libSystemConfiguration/SCPreferences.c:35 — PREFS_DEFAULT_DIR; bootfiles.h:73 (com.apple.Boot.plist); referenced by hostnameprefset.c:112, SCDynamicStoreKeys.c:62.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.