From f163a63b1f165dd914e2975b29420bc053c50253 Mon Sep 17 00:00:00 2001 From: Steffen Beyer Date: Fri, 3 Apr 2026 06:33:01 +0200 Subject: [PATCH] Add native FreeBSD runtime slice --- docs/PROGRESS.md | 104 +++++++++++++ .../reports/phase14-native-runtime-freebsd.md | 139 ++++++++++++++++++ modules/fruix/packages/freebsd.scm | 42 +++++- ...ative-runtime-pid1-operating-system.scm.in | 71 +++++++++ .../system/run-phase14-native-runtime-qemu.sh | 124 ++++++++++++++++ .../run-phase14-native-runtime-xcpng.sh | 130 ++++++++++++++++ 6 files changed, 606 insertions(+), 4 deletions(-) create mode 100644 docs/reports/phase14-native-runtime-freebsd.md create mode 100644 tests/system/phase14-native-runtime-pid1-operating-system.scm.in create mode 100755 tests/system/run-phase14-native-runtime-qemu.sh create mode 100755 tests/system/run-phase14-native-runtime-xcpng.sh diff --git a/docs/PROGRESS.md b/docs/PROGRESS.md index e8a0e4d..fe3fb8e 100644 --- a/docs/PROGRESS.md +++ b/docs/PROGRESS.md @@ -3224,3 +3224,107 @@ Next recommended step: 1. introduce a clearer native runtime slice so runtime is no longer modeled by reusing the broader native world output for both boot and runtime 2. validate that explicit native runtime slice on QEMU and XCP-ng 3. then revisit headers/toolchain/development package boundaries + +## 2026-04-03 — Phase 14.2: validated an explicit native FreeBSD runtime slice + +Completed work: + +- wrote the Phase 14.2 report: + - `docs/reports/phase14-native-runtime-freebsd.md` +- added a new native package in `modules/fruix/packages/freebsd.scm`: + - `freebsd-native-runtime` +- this runtime slice is built from `/usr/src` through the native world path and now prunes at least: + - `boot` + - `usr/include` + - `usr/share/doc` + - `usr/share/examples` + - `usr/share/info` + - `usr/share/man` + - `usr/share/mk` + - `usr/tests` +- added a dedicated Phase 14.2 operating-system template: + - `tests/system/phase14-native-runtime-pid1-operating-system.scm.in` +- added dedicated validation wrappers: + - `tests/system/run-phase14-native-runtime-qemu.sh` + - `tests/system/run-phase14-native-runtime-xcpng.sh` +- the validated Phase 14.2 model now uses: + - `#:kernel freebsd-native-kernel` + - `#:bootloader freebsd-native-world` + - `#:base-packages (list freebsd-native-runtime)` +- this makes the system composition more explicit: + - native world provides boot assets + - native runtime provides the guest runtime slice + - host base stores remain absent from the validated path + +Important finding: + +- this Phase 14.2 layout still duplicates some content because boot assets still come from the broader native world output while runtime comes from the separate native runtime slice +- that made the earlier Phase 13 image sizes too small +- the working values are now: + - local QEMU: + - `DISK_CAPACITY=12g` + - `ROOT_SIZE=10g` + - real XCP-ng: + - `ROOT_SIZE=10g` + - disk capacity still matched to the fixed 30 GiB VDI +- the new Phase 14.2 wrappers now use those larger defaults + +Validation: + +- local QEMU/UEFI/TCG runtime wrapper passes: + - `tests/system/run-phase14-native-runtime-qemu.sh` + - workdir: `/tmp/phase14-2-qemu2-1775189802` + - result: `PASS phase14-native-runtime-qemu` + - confirmed: + - `disk_capacity=12g` + - `root_size=10g` + - `runtime_store=/frx/store/684a82aeed2c9a353e3a09d2cbf5358274d758005e0bfa9b1025d101bc166f79-freebsd-native-runtime-15.0-STABLE` + - `native_base_store_count=3` + - `host_base_store_count=0` + - `shepherd_pid=1` + - `sshd_status=running` + - `native_runtime_ready=ok` +- real XCP-ng runtime wrapper passes: + - `tests/system/run-phase14-native-runtime-xcpng.sh` + - workdir: `/tmp/phase14-2-xcpng-1775190184` + - result: `PASS phase14-native-runtime-xcpng` + - confirmed: + - `vm_id=90490f2e-e8fc-4b7a-388e-5c26f0157289` + - `vdi_id=0f1f90d3-48ca-4fa2-91d8-fc6339b95743` + - `guest_ip=192.168.213.62` + - `root_size=10g` + - `runtime_store=/frx/store/684a82aeed2c9a353e3a09d2cbf5358274d758005e0bfa9b1025d101bc166f79-freebsd-native-runtime-15.0-STABLE` + - `native_base_store_count=3` + - `host_base_store_count=0` + - `shepherd_pid=1` + - `sshd_status=running` + - `compat_prefix_shims=absent` + - `guile_module_smoke=ok` + - `native_runtime_ready=ok` +- the wrappers also assert the runtime-split boundary directly: + - runtime store contains required boot-to-ready files such as: + - `/bin/sh` + - `/sbin/init` + - `/etc/rc` + - `/usr/sbin/sshd` + - `/sbin/dhclient` + - `/usr/bin/ssh-keygen` + - `/usr/share/locale/C.UTF-8/LC_CTYPE` + - runtime store no longer contains: + - `/boot` + - `/usr/include` + +Current assessment: + +- Phase 14.2 is complete +- the validated Fruix guest now reaches ready state using an explicit native runtime artifact rather than reusing the broad native world output for both boot and runtime roles +- the validated path remains host-base-free: + - native kernel + - native world as the temporary boot-source artifact + - native runtime as the guest runtime artifact + +Next recommended step: + +1. define cleaner runtime vs. development boundaries in code/package sets +2. introduce a narrower native boot asset package so the broader native world output is no longer needed as the temporary boot-source store +3. revalidate the full host-base-free path after that split diff --git a/docs/reports/phase14-native-runtime-freebsd.md b/docs/reports/phase14-native-runtime-freebsd.md new file mode 100644 index 0000000..d2bd9fe --- /dev/null +++ b/docs/reports/phase14-native-runtime-freebsd.md @@ -0,0 +1,139 @@ +# Phase 14.2: validated an explicit native FreeBSD runtime slice + +Date: 2026-04-03 + +## Goal + +Phase 14.1 proved that Fruix could boot without host-copied `/boot` material by sourcing boot assets from the existing native world output. + +Phase 14.2 removed the remaining model ambiguity on the runtime side: + +- the guest should now boot and reach ready state using an explicit native runtime output +- not by reusing the broader native world artifact for both boot and runtime roles + +## Changes + +Added a new native package: + +- `freebsd-native-runtime` + +This package is built from `/usr/src` via the existing native world build path and prunes at least: + +- `boot` +- `usr/include` +- `usr/share/doc` +- `usr/share/examples` +- `usr/share/info` +- `usr/share/man` +- `usr/share/mk` +- `usr/tests` + +The validated Phase 14.2 operating-system template now uses: + +- `#:kernel freebsd-native-kernel` +- `#:bootloader freebsd-native-world` +- `#:base-packages (list freebsd-native-runtime)` + +That means the model is now explicit: + +- native world provides boot assets +- native runtime provides the guest runtime slice +- host base stores are no longer part of the validated path + +## Practical sizing finding + +This Phase 14.2 layout still duplicates some native world/runtime content in the closure because the boot assets still come from the broader native world output. + +As a result, the Phase 13 image sizes were no longer large enough. + +Working values were: + +- local QEMU: + - `DISK_CAPACITY=12g` + - `ROOT_SIZE=10g` +- real XCP-ng: + - `ROOT_SIZE=10g` + - disk capacity still matched to the fixed 30 GiB VDI + +The wrappers were updated to use those larger defaults. + +This is acceptable for Phase 14.2 because the next subphase is specifically about cleaning up the runtime/development/boot boundary further. + +## New files + +Added: + +- `tests/system/phase14-native-runtime-pid1-operating-system.scm.in` +- `tests/system/run-phase14-native-runtime-qemu.sh` +- `tests/system/run-phase14-native-runtime-xcpng.sh` + +These wrappers assert: + +- `host_base_store_count=0` +- native kernel present +- native world present as the current boot-source artifact +- native runtime present +- runtime store still contains the files needed for boot-to-ready +- runtime store no longer contains `/boot` +- runtime store no longer contains `/usr/include` + +## Validation + +### Local QEMU / UEFI / TCG + +Passing run: + +- `PASS phase14-native-runtime-qemu` +- workdir: `/tmp/phase14-2-qemu2-1775189802` + +Confirmed: + +```text +disk_capacity=12g +root_size=10g +runtime_store=/frx/store/684a82aeed2c9a353e3a09d2cbf5358274d758005e0bfa9b1025d101bc166f79-freebsd-native-runtime-15.0-STABLE +native_base_store_count=3 +host_base_store_count=0 +shepherd_pid=1 +sshd_status=running +native_runtime_ready=ok +``` + +### Real XCP-ng VM + +Passing run: + +- `PASS phase14-native-runtime-xcpng` +- workdir: `/tmp/phase14-2-xcpng-1775190184` + +Confirmed: + +```text +vm_id=90490f2e-e8fc-4b7a-388e-5c26f0157289 +vdi_id=0f1f90d3-48ca-4fa2-91d8-fc6339b95743 +guest_ip=192.168.213.62 +root_size=10g +runtime_store=/frx/store/684a82aeed2c9a353e3a09d2cbf5358274d758005e0bfa9b1025d101bc166f79-freebsd-native-runtime-15.0-STABLE +native_base_store_count=3 +host_base_store_count=0 +shepherd_pid=1 +sshd_status=running +compat_prefix_shims=absent +guile_module_smoke=ok +native_runtime_ready=ok +``` + +## Result + +Phase 14.2 is complete. + +The validated Fruix guest now reaches ready state using an explicit native runtime artifact: + +- native kernel +- native world for boot assets +- native runtime for the guest runtime slice +- no host-staged FreeBSD base stores in the validated path + +## Next step + +Phase 14.3 should clean up the remaining redundancy by defining clearer runtime vs. development boundaries and, ideally, replacing the temporary use of the broad native world artifact as the boot-source package with a narrower native boot asset package. diff --git a/modules/fruix/packages/freebsd.scm b/modules/fruix/packages/freebsd.scm index 9a46900..8618ccc 100644 --- a/modules/fruix/packages/freebsd.scm +++ b/modules/fruix/packages/freebsd.scm @@ -30,6 +30,7 @@ freebsd-bash freebsd-native-kernel freebsd-native-world + freebsd-native-runtime freebsd-native-build-package? freebsd-host-staged-package? %freebsd-host-staged-all-packages @@ -495,10 +496,10 @@ the first native replacement for the earlier host-copy kernel package." #:home-page "https://www.freebsd.org/" #:synopsis "Native Fruix-managed FreeBSD world artifact" #:description - "FreeBSD-specific package definition that builds and installs a minimal -runtime-oriented world from /usr/src into a real Fruix store artifact. The -first split intentionally focuses on the world needed for the Fruix guest to -boot, network, activate, and run Shepherd." + "FreeBSD-specific package definition that builds and installs a broad +native world from /usr/src into a real Fruix store artifact. It still keeps a +large cross-section of boot and runtime files together and now serves as the +broad source artifact that later native runtime/boot splits derive from." #:license 'bsd-2 #:install-plan '((source-root . "/usr/src") @@ -516,6 +517,39 @@ boot, network, activate, and run Shepherd." "usr/share/man" "usr/tests"))))) +(define freebsd-native-runtime + (freebsd-package + #:name "freebsd-native-runtime" + #:version freebsd-release + #:build-system 'freebsd-world-build-system + #:home-page "https://www.freebsd.org/" + #:synopsis "Native Fruix-managed FreeBSD runtime slice" + #:description + "FreeBSD-specific package definition that stages a runtime-focused slice of +installworld/distribution from /usr/src. It removes the boot tree and obvious +development-oriented paths so the validated Fruix guest can use an explicit +native runtime output rather than reusing the broader native world artifact for +both boot and runtime roles." + #:license 'bsd-2 + #:install-plan + '((source-root . "/usr/src") + (target . "amd64") + (target-arch . "amd64") + (kernconf . "GENERIC") + (make-flags . ("__MAKE_CONF=/dev/null" + "SRCCONF=/dev/null" + "SRC_ENV_CONF=/dev/null" + "MK_DEBUG_FILES=no" + "MK_TESTS=no")) + (prune-paths . ("boot" + "usr/include" + "usr/share/doc" + "usr/share/examples" + "usr/share/info" + "usr/share/man" + "usr/share/mk" + "usr/tests"))))) + (define (freebsd-native-build-package? package) (not (not (memq (freebsd-package-build-system package) '(freebsd-kernel-build-system freebsd-world-build-system))))) diff --git a/tests/system/phase14-native-runtime-pid1-operating-system.scm.in b/tests/system/phase14-native-runtime-pid1-operating-system.scm.in new file mode 100644 index 0000000..cdd73a6 --- /dev/null +++ b/tests/system/phase14-native-runtime-pid1-operating-system.scm.in @@ -0,0 +1,71 @@ +(use-modules (fruix system freebsd) + (fruix packages freebsd)) + +(define phase14-operating-system + (operating-system + #:host-name "fruix-freebsd" + #:kernel freebsd-native-kernel + #:bootloader freebsd-native-world + #:base-packages (list freebsd-native-runtime) + #:groups (list (user-group #:name "wheel" #:gid 0 #:system? #t) + (user-group #:name "sshd" #:gid 22 #:system? #t) + (user-group #:name "_dhcp" #:gid 65 #:system? #t) + (user-group #:name "operator" #:gid 1000 #:system? #f)) + #:users (list (user-account #:name "root" + #:uid 0 + #:group "wheel" + #:comment "Charlie &" + #:home "/root" + #:shell "/bin/sh" + #:system? #t) + (user-account #:name "sshd" + #:uid 22 + #:group "sshd" + #:comment "Secure Shell Daemon" + #:home "/var/empty" + #:shell "/usr/sbin/nologin" + #:system? #t) + (user-account #:name "_dhcp" + #:uid 65 + #:group "_dhcp" + #:comment "dhcp programs" + #:home "/var/empty" + #:shell "/usr/sbin/nologin" + #:system? #t) + (user-account #:name "operator" + #:uid 1000 + #:group "operator" + #:supplementary-groups '("wheel") + #:comment "Fruix Operator" + #:home "/home/operator" + #:shell "/bin/sh" + #:system? #f)) + #:file-systems (list (file-system #:device "/dev/gpt/fruix-root" + #:mount-point "/" + #:type "ufs" + #:options "rw" + #:needed-for-boot? #t) + (file-system #:device "devfs" + #:mount-point "/dev" + #:type "devfs" + #:options "rw" + #:needed-for-boot? #t) + (file-system #:device "tmpfs" + #:mount-point "/tmp" + #:type "tmpfs" + #:options "rw,size=64m")) + #:services '(shepherd ready-marker sshd) + #:loader-entries '(("autoboot_delay" . "1") + ("boot_multicons" . "YES") + ("boot_serial" . "YES") + ("console" . "comconsole,vidconsole")) + #:rc-conf-entries '(("clear_tmp_enable" . "NO") + ("hostid_enable" . "NO") + ("sendmail_enable" . "NONE") + ("sshd_enable" . "YES") + ("ifconfig_xn0" . "SYNCDHCP") + ("ifconfig_em0" . "SYNCDHCP") + ("ifconfig_vtnet0" . "SYNCDHCP")) + #:init-mode 'shepherd-pid1 + #:ready-marker "/var/lib/fruix/ready" + #:root-authorized-keys '("__ROOT_AUTHORIZED_KEY__"))) diff --git a/tests/system/run-phase14-native-runtime-qemu.sh b/tests/system/run-phase14-native-runtime-qemu.sh new file mode 100755 index 0000000..3a0e862 --- /dev/null +++ b/tests/system/run-phase14-native-runtime-qemu.sh @@ -0,0 +1,124 @@ +#!/bin/sh +set -eu + +repo_root=${PROJECT_ROOT:-$(pwd)} +os_template=${OS_TEMPLATE:-$repo_root/tests/system/phase14-native-runtime-pid1-operating-system.scm.in} +system_name=${SYSTEM_NAME:-phase14-operating-system} +disk_capacity=${DISK_CAPACITY:-12g} +root_size=${ROOT_SIZE:-10g} +metadata_target=${METADATA_OUT:-} +cleanup=0 + +if [ -n "${WORKDIR:-}" ]; then + workdir=$WORKDIR + mkdir -p "$workdir" +else + workdir=$(mktemp -d /tmp/fruix-phase14-native-runtime-qemu.XXXXXX) + cleanup=1 +fi +if [ "${KEEP_WORKDIR:-0}" -eq 1 ]; then + cleanup=0 +fi + +inner_metadata=$workdir/phase14-native-runtime-qemu-inner-metadata.txt +metadata_file=$workdir/phase14-native-runtime-qemu-metadata.txt + +cleanup_workdir() { + if [ "$cleanup" -eq 1 ]; then + rm -rf "$workdir" 2>/dev/null || sudo rm -rf "$workdir" + fi +} +trap cleanup_workdir EXIT INT TERM + +KEEP_WORKDIR=1 WORKDIR="$workdir/inner" METADATA_OUT="$inner_metadata" \ + OS_TEMPLATE="$os_template" SYSTEM_NAME="$system_name" DISK_CAPACITY="$disk_capacity" ROOT_SIZE="$root_size" \ + "$repo_root/tests/system/run-phase11-shepherd-pid1-qemu.sh" + +phase8_metadata=$(sed -n 's/^phase8_metadata=//p' "$inner_metadata") +closure_path=$(sed -n 's/^closure_path=//p' "$inner_metadata") +closure_base=$(sed -n 's/^closure_base=//p' "$inner_metadata") +serial_log=$(sed -n 's/^serial_log=//p' "$inner_metadata") +ssh_port=$(sed -n 's/^ssh_port=//p' "$inner_metadata") +shepherd_pid=$(sed -n 's/^shepherd_pid=//p' "$inner_metadata") +sshd_status=$(sed -n 's/^sshd_status=//p' "$inner_metadata") +activate_log=$(sed -n 's/^activate_log=//p' "$inner_metadata") + +native_base_store_count=$(sed -n 's/^native_base_store_count=//p' "$phase8_metadata") +native_base_stores=$(sed -n 's/^native_base_stores=//p' "$phase8_metadata") +host_base_store_count=$(sed -n 's/^host_base_store_count=//p' "$phase8_metadata") +host_base_stores=$(sed -n 's/^host_base_stores=//p' "$phase8_metadata") + +[ "$native_base_store_count" = 3 ] || { echo "expected 3 native base stores, got: $native_base_store_count" >&2; exit 1; } +[ "$host_base_store_count" = 0 ] || { echo "expected 0 host base stores, got: $host_base_store_count" >&2; exit 1; } +[ -z "$host_base_stores" ] || { echo "host base stores are not empty: $host_base_stores" >&2; exit 1; } +printf '%s\n' "$native_base_stores" | tr ',' '\n' | grep 'freebsd-native-kernel-15.0-STABLE$' >/dev/null || { + echo "native base stores do not include the native kernel" >&2 + exit 1 +} +printf '%s\n' "$native_base_stores" | tr ',' '\n' | grep 'freebsd-native-world-15.0-STABLE$' >/dev/null || { + echo "native base stores do not include the native boot/world source artifact" >&2 + exit 1 +} +runtime_store=$(printf '%s\n' "$native_base_stores" | tr ',' '\n' | grep 'freebsd-native-runtime-15.0-STABLE$' | head -n 1) +[ -n "$runtime_store" ] || { + echo "native base stores do not include the explicit native runtime slice" >&2 + exit 1 +} +for path in \ + "$runtime_store/bin/sh" \ + "$runtime_store/sbin/init" \ + "$runtime_store/etc/rc" \ + "$runtime_store/usr/sbin/sshd" \ + "$runtime_store/sbin/dhclient" \ + "$runtime_store/usr/bin/ssh-keygen" \ + "$runtime_store/usr/share/locale/C.UTF-8/LC_CTYPE" +do + [ -e "$path" ] || { + echo "required native runtime path missing: $path" >&2 + exit 1 + } +done +[ ! -e "$runtime_store/boot" ] || { echo "native runtime still contains /boot" >&2; exit 1; } +[ ! -e "$runtime_store/usr/include" ] || { echo "native runtime still contains /usr/include" >&2; exit 1; } +[ "$shepherd_pid" = 1 ] || { echo "shepherd was not PID 1" >&2; exit 1; } +[ "$sshd_status" = running ] || { echo "sshd is not running" >&2; exit 1; } +case "$activate_log" in + *fruix-activate:done*) : ;; + *) echo "activation log does not show success" >&2; exit 1 ;; +esac + +cat >"$metadata_file" <&2; exit 1; } +[ "$host_base_store_count" = 0 ] || { echo "expected 0 host base stores, got: $host_base_store_count" >&2; exit 1; } +[ -z "$host_base_stores" ] || { echo "host base stores are not empty: $host_base_stores" >&2; exit 1; } +printf '%s\n' "$native_base_stores" | tr ',' '\n' | grep 'freebsd-native-kernel-15.0-STABLE$' >/dev/null || { + echo "native base stores do not include the native kernel" >&2 + exit 1 +} +printf '%s\n' "$native_base_stores" | tr ',' '\n' | grep 'freebsd-native-world-15.0-STABLE$' >/dev/null || { + echo "native base stores do not include the native boot/world source artifact" >&2 + exit 1 +} +runtime_store=$(printf '%s\n' "$native_base_stores" | tr ',' '\n' | grep 'freebsd-native-runtime-15.0-STABLE$' | head -n 1) +[ -n "$runtime_store" ] || { + echo "native base stores do not include the explicit native runtime slice" >&2 + exit 1 +} +for path in \ + "$runtime_store/bin/sh" \ + "$runtime_store/sbin/init" \ + "$runtime_store/etc/rc" \ + "$runtime_store/usr/sbin/sshd" \ + "$runtime_store/sbin/dhclient" \ + "$runtime_store/usr/bin/ssh-keygen" \ + "$runtime_store/usr/share/locale/C.UTF-8/LC_CTYPE" +do + [ -e "$path" ] || { + echo "required native runtime path missing: $path" >&2 + exit 1 + } +done +[ ! -e "$runtime_store/boot" ] || { echo "native runtime still contains /boot" >&2; exit 1; } +[ ! -e "$runtime_store/usr/include" ] || { echo "native runtime still contains /usr/include" >&2; exit 1; } +[ "$shepherd_pid" = 1 ] || { echo "shepherd was not PID 1" >&2; exit 1; } +[ "$sshd_status" = running ] || { echo "sshd is not running" >&2; exit 1; } +[ "$compat_prefix_shims" = absent ] || { echo "compatibility prefix shims reappeared" >&2; exit 1; } +[ "$guile_module_smoke" = ok ] || { echo "guest Guile module smoke failed" >&2; exit 1; } +case "$activate_log" in + *fruix-activate:done*) : ;; + *) echo "activation log does not show success" >&2; exit 1 ;; +esac + +cat >"$metadata_file" <