Files
fruix/docs/reports/phase18-installer-iso-freebsd.md

9.8 KiB

Phase 18.3: bootable Fruix installer ISO on FreeBSD

Date: 2026-04-04

Goal

Phase 18.3 extends the Phase 18.2 installer-environment work from a disk-image-style installer into a UEFI-bootable ISO artifact.

The intended first ISO is deliberately narrow:

  • UEFI only
  • serial-console-friendly
  • non-interactive install flow reused from Phase 18.1/18.2
  • target disk installation still performed by the same Fruix-managed in-guest installer logic

Implementation

New API

Added in modules/fruix/system/freebsd.scm:

  • operating-system-installer-iso-spec
  • materialize-installer-iso

The system module split done immediately before this phase was also exercised during this work.

New CLI action

Added in scripts/fruix.scm:

  • fruix system installer-iso

This action emits metadata for:

  • ISO store path
  • ISO image path
  • EFI boot image path
  • installer root image path
  • installer and target closure paths
  • installer state/log paths
  • declared/materialized FreeBSD source metadata
  • store closure counts

ISO boot model

The ISO does not try to run the Fruix installer directly from a read-only cd9660 root.

Instead it uses a small UEFI El Torito boot image plus an in-memory installer root image:

  1. a small FAT EFI boot image contains EFI/BOOT/BOOTX64.EFI
  2. the ISO root contains real boot assets under /boot
  3. the ISO root also contains /boot/root.img
  4. loader.conf on the ISO is augmented with:
    • mdroot_load="YES"
    • mdroot_type="mfs_root"
    • mdroot_name="/boot/root.img"
    • vfs.root.mountfrom="ufs:/dev/md0"
    • vfs.root.mountfrom.options="rw"

A practical loader detail surfaced during validation:

  • setting rootdev or currdev to md0: in the ISO loader path is wrong for this loader configuration and caused an early EFI-loader crash before kernel handoff
  • the reliable ISO path is to let loader keep its current device on the CD media, preload /boot/root.img, and pass only vfs.root.mountfrom=ufs:/dev/md0

This preserves the existing Fruix installer environment semantics while avoiding the need to make the whole installer operate directly from a read-only ISO root.

Installer root image contents

materialize-installer-iso stages the same installer payload model already validated in Phase 18.2:

  • installer closure
  • target closure
  • target runtime store closure needed for installation/boot
  • staged target rootfs under /var/lib/fruix/installer/target-rootfs
  • installer plan and state files under /var/lib/fruix/installer
  • installer helper scripts:
    • /usr/local/libexec/fruix-installer-run
    • /usr/local/etc/rc.d/fruix-installer

The ISO root image is then built as a UFS image and embedded as /boot/root.img.

Split-regression fixes found during this work

While exercising the refactored split modules, two issues surfaced and were fixed:

  1. string-hash name-clash warnings
    • the old helper name collided with Guile/SRFI bindings
    • it was renamed to sha256-string
  2. missing prefix-materializer-version
    • this constant was accidentally omitted when modules/fruix/system/freebsd.scm was split
    • the missing definition was restored in modules/fruix/system/freebsd/build.scm

Validation

Completed smoke validation

A host-side smoke build was completed successfully for the new ISO builder using a host-staged operating-system definition:

  • command pattern:
    • fruix system installer-iso ...
  • result:
    • successful ISO materialization in a temporary store
  • artifact checks performed:
    • etdump reports an EFI El Torito boot entry
    • the ISO contains:
      • boot/kernel/kernel
      • boot/kernel/linker.hints
      • boot/loader.conf
      • boot/loader.efi
      • boot/root.img
    • boot/loader.conf inside the ISO contains the expected mdroot_* and vfs.root.mountfrom entries

Example smoke-build metadata:

action=installer-iso
iso_volume_label=FRUIX_INSTALLER
iso_store_path=/tmp/...-fruix-installer-iso-fruix-freebsd-installer
iso_image=/tmp/...-fruix-installer-iso-fruix-freebsd-installer/installer.iso
boot_efi_image=/tmp/...-fruix-installer-iso-fruix-freebsd-installer/efiboot.img
root_image=/tmp/...-fruix-installer-iso-fruix-freebsd-installer/root.img
installer_closure_path=/tmp/...-fruix-system-fruix-freebsd-installer
target_closure_path=/tmp/...-fruix-system-fruix-freebsd

End-to-end harness validation

Added:

  • tests/system/run-phase18-installer-iso.sh

This harness validates the full Phase 18.3 flow:

  1. build installer ISO
  2. boot it under QEMU/UEFI/TCG
  3. install onto a target disk from inside the booted ISO environment
  4. boot the installed target

Passing validation:

  • PASS phase18-installer-iso

Validated result summary:

installer_iso_store_path=/frx/store/...-fruix-installer-iso-fruix-freebsd-installer
installer_iso_image=/frx/store/...-fruix-installer-iso-fruix-freebsd-installer/installer.iso
installer_boot_efi_image=/frx/store/...-fruix-installer-iso-fruix-freebsd-installer/efiboot.img
installer_root_image=/frx/store/...-fruix-installer-iso-fruix-freebsd-installer/root.img
install_target_device=/dev/vtbd0
freebsd_source_kind=git
freebsd_source_ref=stable/15
freebsd_source_commit=332708a606f6bf0841c1d4a74c0d067f5640fe89
materialized_source_store=/frx/store/459499e0eb29f4c73ad455060dd2502d21fb56f205c0a676831cf723b3a0c378-freebsd-source-stable15-installer-iso-target-source
installer_state=done
installer_sshd_status=running
target_esp_fstype=msdosfs
target_root_fstype=ufs
target_shepherd_status=running
target_sshd_status=running
installer_iso_boot=ok
installer_iso_install=ok
installed_target_boot=ok

Notable QEMU-specific ISO validation detail:

  • unlike the disk-image-style installer environment from Phase 18.2, the ISO boots from cd0, so the target virtio disk appears as:
    • /dev/vtbd0
  • the earlier installer-environment default:
    • /dev/vtbd1 remains correct for the disk-image installer, but not for the ISO path

The harness verified all of the following:

  1. fruix system installer-iso produces a bootable ISO artifact in /frx/store
  2. the ISO boots successfully under QEMU/UEFI/TCG
  3. the booted installer ISO environment becomes reachable over SSH
  4. /run/current-system inside the installer ISO points at the installer closure
  5. the installer rc.d job reaches:
    • state=done
  6. the installer log records:
    • fruix-installer:done
  7. the installed target disk contains:
    • GPT partitioning
    • EFI filesystem: msdosfs
    • root filesystem: ufs
    • EFI/BOOT/BOOTX64.EFI
    • /var/lib/fruix/install.scm
  8. the installed target then boots successfully as its own Fruix system under QEMU/UEFI/TCG
  9. after target boot:
    • /run/current-system points at the target closure
    • shepherd is running
    • sshd is running
    • activation completed successfully

Real XCP-ng validation

Added:

  • tests/system/run-phase18-installer-iso-xcpng.sh

This harness validates the same installer-iso workflow on the approved real XCP-ng path:

  • VM: 90490f2e-e8fc-4b7a-388e-5c26f0157289
  • ISO SR: 537a6219-8452-7cb5-8d56-5eed6910c7a2
  • target VDIs:
    • 0f1f90d3-48ca-4fa2-91d8-fc6339b95743
    • 7061d761-3639-4bec-87f7-2ba1af924eaa

Because the current xo-cli disk.import @=/path/to.iso path returned an HTTP 500 error in this environment, the harness imports the ISO into the ISO SR via a temporary local HTTP URL, then inserts the resulting ISO VDI into the VM's CD drive.

Passing validation:

  • PASS phase18-installer-iso-xcpng

Validated result summary:

vm_id=90490f2e-e8fc-4b7a-388e-5c26f0157289
iso_id=<temporary-imported-iso-vdi>
guest_ip=192.168.213.62
installer_state=done
installer_target_device=/dev/ada0
kern_disks=cd0 ada1 ada0
installer_run_current_system=/frx/store/16969e825dbb65b5c27180030d4a7d98821893460fb3dccdc863ff6156ed61e0-fruix-system-fruix-freebsd-installer
installer_sshd_status=running
target_run_current_system=/frx/store/a98d3af6a1afbc4a927d47cea6458d5a70747b051ed994e5d9ff1ae79c4f2b42-fruix-system-fruix-freebsd
target_sshd_status=running
target_shepherd_status=running

Important XCP-ng-specific details:

  • the installer ISO still boots from:
    • cd0
  • on this Xen HVM path, the primary target disk is exposed through Xen block front as xbd0 and appears to FreeBSD as:
    • /dev/ada0
  • therefore the XCP-ng installer-iso path must target:
    • /dev/ada0 rather than QEMU's:
    • /dev/vtbd0
  • the visible EFI console can appear to stop at:
    • console vidconsole is unavailable but boot still continues and the installer becomes reachable over SSH; that message was not the actual failure mode on XCP-ng

The harness verified all of the following on the real VM path:

  1. fruix system installer-iso builds a bootable ISO with --install-target-device /dev/ada0
  2. the ISO can be imported into the operator-approved ISO SR and attached to the approved VM
  3. the VM boots the Fruix installer ISO successfully under UEFI
  4. the installer environment becomes reachable over SSH
  5. inside the installer guest:
    • kern.disks includes cd0 and ada0
    • /run/current-system points at the installer closure
    • the installer reaches state=done
  6. the installed target on ada0 is partitioned and formatted correctly
  7. after ejecting the ISO and rebooting, the installed target boots successfully on the same XCP-ng VM
  8. after target boot:
    • /run/current-system points at the target closure
    • shepherd is running
    • sshd is running
    • activation completed successfully
    • /var/lib/fruix/install.scm still records the materialized source store provenance

Result

Phase 18.3 is complete.

Fruix now has a validated bootable UEFI installer ISO on FreeBSD that can:

  • boot into a Fruix-managed installer environment from ISO media
  • perform the non-interactive installation flow onto a target disk
  • boot the installed target successfully
  • and do so on both:
    • local QEMU/UEFI/TCG
    • the approved real XCP-ng VM path