Files
fruix/docs/reports/phase20-host-initiated-native-builds-freebsd.md

6.0 KiB

Phase 20.2: host-initiated native base builds inside a Fruix-managed environment

Date: 2026-04-05

Goal

Validate the next step after Phase 20.1:

  • the host still orchestrates the outer loop
  • the actual FreeBSD native base build work runs inside a booted Fruix-managed system

This is the intermediate path between:

  • purely host-side native base builds
  • any future claim of guest self-hosting

The target here was not a new self-hosted package manager story.

It was narrower:

  • boot a Fruix-managed FreeBSD system on the approved real XCP-ng path
  • expose the development environment required for native base work
  • run real buildworld / buildkernel / staged install steps inside that Fruix guest
  • confirm that the resulting staged artifacts are the expected FreeBSD base slices

Implementation

Phase 20.1 proved that Fruix could keep development content separate in:

  • /run/current-system/development-profile
  • /run/current-development

Phase 20.2 exposed an additional practical requirement:

  • FreeBSD native base builds still expect canonical system paths such as:
    • /usr/include
    • /usr/share/mk

For development-enabled systems, populate-rootfs-from-closure now also exposes:

  • /usr/include -> /run/current-system/development-profile/usr/include
  • /usr/share/mk -> /run/current-system/development-profile/usr/share/mk

This keeps the development profile separate while still satisfying the buildworld/buildkernel assumptions of the native FreeBSD build system.

Media builder invalidation

Because this changed the visible rootfs layout of booted systems, the media builder versions were bumped in modules/fruix/system/freebsd/media.scm:

  • image-builder-version
  • install-builder-version
  • installer-image-builder-version
  • installer-iso-builder-version

That ensured booted images and future installed targets actually pick up the new compatibility links.

New validation harness

Added:

  • tests/system/run-phase20-host-initiated-native-build-xcpng.sh

This harness reuses the validated Phase 20.1 XCP-ng path first, then performs the 20.2-native-build step over SSH from the host.

The guest build flow is:

  1. boot the development-enabled Fruix guest on XCP-ng
  2. recover the materialized source store from /run/current-system/metadata/store-layout.scm
  3. run real FreeBSD native build commands inside the guest:
    • make -j8 buildworld
    • make -j8 buildkernel
    • make DESTDIR=... installworld
    • make DESTDIR=... distribution
    • make DESTDIR=... installkernel
  4. stage narrower artifact slices from the staged output:
    • headers slice
    • bootloader slice
    • kernel stage

Why DB_FROM_SRC=yes is used for staged install steps

The development-enabled Fruix guest is intentionally lean and does not carry the full ambient host account database.

installworld on modern FreeBSD checks for required users/groups unless DB_FROM_SRC is defined. For staged installs into DESTDIR, the appropriate controlled input is the source tree's own account database under etc/, not the minimal running guest's /etc/master.passwd.

So the validated Phase 20.2 staged install path uses:

  • DB_FROM_SRC=yes

for:

  • installworld
  • distribution
  • installkernel

That keeps the staged install driven by the declared source input rather than by accidental guest-local account state.

Validation

Passing run:

  • PASS phase20-host-initiated-native-build-xcpng
  • workdir: /tmp/fruix-phase20-host-initiated-native-build-xcpng

Validated on the approved real XCP-ng path:

  • VM 90490f2e-e8fc-4b7a-388e-5c26f0157289
  • VDI 0f1f90d3-48ca-4fa2-91d8-fc6339b95743

Representative result:

build_jobs=8
source_store=/frx/store/12d7704362e95afc2697db63f168b878e082b372-freebsd-source-default
source_root=/frx/store/12d7704362e95afc2697db63f168b878e082b372-freebsd-source-default/tree
build_root=/var/tmp/fruix-phase20-native-build
world_stage=/var/tmp/fruix-phase20-native-build/stage-world
kernel_stage=/var/tmp/fruix-phase20-native-build/stage-kernel
headers_stage=/var/tmp/fruix-phase20-native-build/artifact-headers
bootloader_stage=/var/tmp/fruix-phase20-native-build/artifact-bootloader
build_root_size=7.6G
world_stage_size=672M
kernel_stage_size=739M
headers_stage_size=32M
bootloader_stage_size=1.3M
sha_kernel=16950f116a52134b98e2f8e0dacc556e18fe254e4a0ac2c1741422dde281a341
sha_loader=ea417846167ece270ada611624dca622ca38bd30125b9a125cd8ebb8b3600313
sha_param=9eb140ca7d9666f3d484a4174c9acd94b45427db6292b4e17de19af2c6aa5219
host_initiated_native_build=ok

The harness verified all of the following:

  • the guest still boots and passes the Phase 20.1 development-environment checks first
  • development-enabled systems expose canonical native-build compatibility links at:
    • /usr/include
    • /usr/share/mk
  • the guest can recover the declared materialized FreeBSD source store from system metadata
  • real FreeBSD buildworld succeeds inside the booted Fruix guest
  • real FreeBSD buildkernel succeeds inside the booted Fruix guest
  • staged installworld, distribution, and installkernel also succeed inside the guest
  • the staged outputs contain the expected artifact shapes:
    • boot/kernel/kernel
    • usr/include/sys/param.h
    • usr/share/mk/bsd.prog.mk
    • boot/loader.efi
    • boot/defaults/loader.conf
    • boot/lua/loader.lua

Result

Phase 20.2 is complete.

Fruix now validates a real host-orchestrated path where:

  • the host boots and reaches a Fruix-managed development-enabled guest
  • the guest uses its own Fruix-exposed development paths and declared source store
  • the native FreeBSD base build work runs inside that Fruix-managed environment
  • the host remains the outer orchestrator and result collector

This materially narrows the gap to any future self-hosting experiment while still avoiding the complexity jump to a full guest-driven package/deployment loop.

Next step

Per docs/PLAN_4.md, the next planned step is:

  • Phase 20.3 — reassess and potentially prototype guest self-hosted base builds