Gershwin installer backends Plan ready

Fix the Linux backend (both install methods are broken), add a first-class NextBSD backend, and pull the FreeBSD backend out of the /System/Library/Scripts junk drawer into a properly-named, dispatcher-driven layout. For gershwin-desktop/gershwin-componentsAssistants/InstallationAssistant.

TL;DR

The InstallationAssistant offers two methods — Clone running system to disk and Image based installation (from external media). On Linux both fail; on FreeBSD they mostly work. The two "methods" are really one pipeline (partition → format → rsync → bootloader → fstab) that differs by a single variable: the rsync source (/ for clone, the detected media for image).

Linux is broken for three reasons: (1) the deployed installer-linux.sh drifted from source and lost its --check-image-source handler, so the GUI never even offers the image radio; (2) image detection only matches iso9660, missing Debian's /run/live/medium; (3) the clone path installs the bootloader via chroot grub-install/update-grub, which needs grub + initramfs inside the copied tree — absent when cloning a live/squashfs system — and it overwrites /etc/fstab. FreeBSD works because it installs the loader by copying loader.efi + efibootmgr (no target tooling, no chroot).

NextBSD has a latent bug: the ostype rebrand flips uname -s/kern.ostype to NextBSD, so the current boolean IAIsFreeBSD() misclassifies it as Linux and would run the broken Linux backend. It needs its own backend (launchd, label-based UFS root ROOTFS, kextd autoload, hostname via configd/hostnamed — never rc.conf).

Plan: a thin dispatcher + per-OS modules with proper names, an explicit --method flag, a 3-way platform resolver, a home outside /System/Library/Scripts, and a shared first-boot identity reset (machine-id, SSH host keys, stable fstab). All in gershwin-components; no GUI redesign.

1. Symptom & scope

Gershwin InstallationAssistant showing the two installation methods
The Installation Type step: Clone running system to disk and Image based installation (from external media) (source detected: /run/live/medium). Captured on Debian. On Linux neither method produces a bootable install; on FreeBSD they mostly work.

Flow: Welcome → License → Installation Type → Select Destination → Confirmation → Installing → Finished. We want both methods working on Linux, FreeBSD, and NextBSD, with the backends organized and named sanely.

2. What the two methods mean for an installed system

Mechanically the method is one variable: SRC. Both methods then run the identical partition/format/rsync/bootloader/fstab pipeline. There is no separate image extractor — the image is just a different rsync source dir.

MethodSRCWhat it produces
Clone running system (default; no --source)/ (the live root)Copies the live system's flattened union view (squashfs/uzip + tmpfs overlay merged). Captures in-session changes (pkgs installed live) — but also drags in live cruft and stale runtime state unless excluded/reset.
Image based (--source <path>)detected mediaOn Linux: loop-mounts the .squashfs inside the medium and copies the pristine rootfs (explicit flatten). On FreeBSD: copies the da0 mountpoint as-is. Cleaner identity (no live drift), but ships fixed SSH keys / machine-id unless reset.

The deep correctness consequences (duplicate host keys, stale /var/run, fstab targets, flattening the live overlay) are in §8 Identity & correctness. This builds on the union-flatten analysis in the live-ISO unionfs plan and the future Copier.framework in the native installer-backend plan.

3. How it works today: the GUI↔backend contract

The GUI (InstallationSteps.m) selects a backend script and drives it via NSTask. The contract has three modes and a stdout line-protocol:

ModeInvocationReturns
Probescript --check-image-source (InstallationSteps.m:85-87)one line IMAGE_SOURCE:<path> (empty = none). Gates the image radio.
Enumeratescript --list-disks --debug (:646-647)JSON array of {devicePath,name,description,sizeBytes,formattedSize}
Install[sudo] script --noninteractive --disk <dev> --debug [--source <path>] (:1094-1113)progress lines; exit code = success

4. Why both Linux methods fail

4a. Image method is dead before it starts

4b. Clone method can't produce a bootable disk

Why FreeBSD works: it installs the loader by copying /boot/loader.efiBOOTX64.EFI + efibootmgr -c (installer-FreeBSD.sh:457-478), or gpart bootcode for BIOS — no chroot, no target-side packages — and writes a fresh complete fstab. It succeeds against a blank formatted root.

5. The FreeBSD backend (works) & the installed system

Pipeline: gpart GPT layout (efi 512M FAT32 + freebsd-ufs, or freebsd-boot 512k + ufs for BIOS) → newfs -U → rsync from SRC → copy loader + efibootmgr → write loader.conf (vfs.root.mountfrom) and fstab (installer-FreeBSD.sh:362-502). The deployed copy adds a Directory-Services identity reset for clone installs: wipe /Local, chroot dscli init (creates admin/admin), restart dshelper (/tmp/gersh_installer.sh:430-440).

Known trap: FreeBSD writes device-path fstab (/dev/da1p2, :486) not UUID/label — if disk enumeration shifts at boot, root can fail to mount. (Linux correctly uses blkid UUIDs.)

6. NextBSD backend

NextBSD is FreeBSD-derived, so the FreeBSD backend is the starting point — but an installed NextBSD differs in ways the FreeBSD path doesn't handle. From the live system's boot config and overlays:

ConcernFreeBSD backend doesNextBSD requires
Platform detectionIAIsFreeBSD() booleanlatent bug rebranded kern.ostype=NextBSD → classified as Linux. Needs 3-way resolver, NextBSD first.
init / servicesrc.conf, service, sysrcINIT_PATH=/sbin/launchd; launchd-only, no rc.conf. Service enablement = LaunchDaemons under /System/Library/LaunchDaemons.
root device / fstabdevice-path /dev/da1p2ROOTDEVNAME="ufs:/dev/ufs/ROOTFS" — plain UFS root labeled ROOTFS; label-based fstab (stable across enumeration).
kernel modulesn/akextd autoload from /System/Library/Extensions — ensure the dir + any boot kexts land and autoload is wired.
hostname / identitysysrc hostname= / /etc/rc.confhostname published to SCDynamicStore (configd) / hostnamedno file/sysrc path. Post-install must set it the NextBSD-native way.
loader.confvanilla FreeBSD knobsNextBSD INIT_PATH/ROOTDEVNAME knobs; verify against the rebranded kernel.
Reuse vs fork: a NextBSD module should reuse the FreeBSD core (gpart/newfs/loader copy/efibootmgr) and override the post-install hooks (fstab by label, launchd service enablement, kextd dir, configd/hostnamed identity, loader.conf knobs). That argues for the dispatcher + per-OS modules design in §7 rather than a third monolith.

7. Reorganization: naming, placement, selection

7a. Stop the /System/Library/Scripts drift

The GNUmakefile installs the scripts into the app bundle (InstallationAssistant.app/Resources/, GNUmakefile:12-16), which is what the GUI loads. The flat installer.sh/installer-linux.sh in /System/Library/Scripts (next to Gershwin.sh, LoginWindow.sh, doit) are an unmanaged, renamed, stale manual copy. Drop that copy and the rename.

7b. Dispatcher + per-OS modules

install-backend.sh # dispatcher: owns the contract (flags, PROGRESS:/IMAGE_SOURCE:/JSON, exit codes) install-backend-common.sh # report_progress, JSON emit, fstab/identity helpers modules/freebsd.sh # os_list_disks / os_check_image_source / os_install_clone / os_install_image modules/linux.sh modules/nextbsd.sh # reuses freebsd core, overrides post-install hooks

Today all three scripts duplicate the arg parser, report_progress, --list-disks JSON and --check-image-source — which is exactly how the deployed copy drifted and lost a flag. A dispatcher owning the contract makes drift impossible and lets NextBSD be a thin override of FreeBSD.

7c. Better names & home

7d. 3-way platform resolver + explicit method

8. Identity & correctness (shared, all OSes)

The biggest cross-cutting gap: installs don't reset machine identity. Both methods need a shared first-boot/post-install reset:

9. Phased plan (PR-gated, all in gershwin-components)

Phase 1 — Stop the bleeding on Linux

Restore --check-image-source in the Linux backend and broaden detection beyond iso9660 (re-enables the image radio); replace chroot-grub with a dependency-checked bootloader step; merge/append fstab instead of overwriting; relax set -e around rsync. Gate: a Debian live image installs and boots via both methods.

Phase 2 — Dispatcher + modules + names

Refactor the three monoliths into install-backend.sh + common + modules/{freebsd,linux}.sh; rename per the scheme; delete the /System/Library/Scripts manual copies. GUI: IAPlatformToken() 3-way resolver + explicit --method. Gate: FreeBSD + Linux unchanged behavior through the new dispatcher.

Phase 3 — NextBSD module

modules/nextbsd.sh reusing the FreeBSD core, overriding: label-based UFS root ROOTFS + label fstab, launchd service enablement, kextd Extensions dir, configd/hostnamed identity, NextBSD loader.conf knobs. Gate: NextBSD installs and boots to launchd on the thinkpad-t460s target.

Phase 4 — Shared identity reset

First-boot regeneration of machine-id + SSH host keys; medium-exclude + /var/run scrub in the clone path; per-OS default method. Gate: two installs from one image have distinct host keys/machine-id and both log in.

10. References


Filed 2026-06-21. Root-caused from a 4-agent investigation (Linux failure analysis, FreeBSD/NextBSD backend mechanics, GUI↔backend contract + organization, install-method semantics) plus live capture of the InstallationAssistant on Debian. Target repo: gershwin-desktop/gershwin-components (Assistants/InstallationAssistant, branch feat/nextbsd).