# 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 ### Canonical development-path compatibility links 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: ```text 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