Validate FreeBSD Fruix rootfs trees
This commit is contained in:
@@ -1960,6 +1960,87 @@ Current assessment:
|
||||
- Phase 7.2 is now satisfied on the current FreeBSD prototype track
|
||||
- the next step is to materialize and statically validate an installable root filesystem tree from this system closure
|
||||
|
||||
## 2026-04-01 — Phase 7.3 completed: installable rootfs tree validated from the system closure
|
||||
|
||||
Completed work:
|
||||
|
||||
- added the Phase 7.3 rootfs materialization harnesses:
|
||||
- `tests/system/materialize-phase7-rootfs.scm`
|
||||
- `tests/system/run-phase7-rootfs.sh`
|
||||
- wrote the Phase 7.3 report:
|
||||
- `docs/reports/phase7-rootfs-freebsd.md`
|
||||
- ran the rootfs harness successfully and captured metadata under:
|
||||
- `/tmp/phase7-rootfs-metadata.txt`
|
||||
|
||||
Important findings:
|
||||
|
||||
- the declarative Fruix FreeBSD system can now be materialized as a root filesystem tree rather than only as a store closure directory
|
||||
- the rootfs uses a Guix-like anchor:
|
||||
- `/run/current-system`
|
||||
so that boot assets, generated configuration, and system-profile content remain tied to the declarative system closure
|
||||
- static validation confirmed:
|
||||
- boot asset linkage
|
||||
- generated `/etc` linkage
|
||||
- activation payload presence
|
||||
- Shepherd `rc.d` launch integration
|
||||
- declared filesystem entries
|
||||
- declared user/group provisioning in the activation path
|
||||
- deterministic ready-state wiring through `/var/lib/fruix/ready`
|
||||
- observed metadata confirmed:
|
||||
- `rootfs=/tmp/.../rootfs`
|
||||
- `closure_path=/frx/store/...-fruix-system-fruix-freebsd`
|
||||
- `run_current_system_target=/frx/store/...-fruix-system-fruix-freebsd`
|
||||
- `activate_target=/run/current-system/activate`
|
||||
- `bin_target=/run/current-system/profile/bin`
|
||||
- `sbin_target=/run/current-system/profile/sbin`
|
||||
- `boot_kernel_target=/run/current-system/boot/kernel`
|
||||
- `boot_loader_target=/run/current-system/boot/loader`
|
||||
- `boot_loader_efi_target=/run/current-system/boot/loader.efi`
|
||||
- `rc_conf_target=/run/current-system/etc/rc.conf`
|
||||
- `rc_script_target=/run/current-system/usr/local/etc/rc.d/fruix-shepherd`
|
||||
- `ready_marker=/var/lib/fruix/ready`
|
||||
- `validation_mode=static-rootfs-check`
|
||||
|
||||
Current assessment:
|
||||
|
||||
- Phase 7.3 is now satisfied on the current FreeBSD prototype track
|
||||
- Phase 7 as a whole is now complete on the active FreeBSD amd64 prototype path
|
||||
|
||||
## 2026-04-01 — Phase 7 completed on the current FreeBSD prototype track
|
||||
|
||||
Phase 7 is now considered complete for the active FreeBSD amd64 prototype path.
|
||||
|
||||
Why this milestone is satisfied:
|
||||
|
||||
- **Phase 7.1** success criteria were met on the prototype track:
|
||||
- a minimal Fruix operating-system object now exists for FreeBSD
|
||||
- it evaluates into a coherent system-closure specification
|
||||
- **Phase 7.2** success criteria were met on the prototype track:
|
||||
- that system model now materializes into a reproducible closure under `/frx/store`
|
||||
- the closure contains boot assets, generated `/etc` files, activation payloads, and Shepherd launch integration
|
||||
- **Phase 7.3** success criteria were met on the prototype track:
|
||||
- the closure now materializes into a concrete rootfs tree
|
||||
- the resulting rootfs passes static validation for later image-construction work
|
||||
|
||||
Important scope note:
|
||||
|
||||
- this completes the **declarative system-composition milestone** for the current prototype track, not a fully booted Fruix guest yet
|
||||
- the current output is a validated closure plus rootfs tree; Phase 8 still needs to turn that into a reproducible bhyve-friendly disk image
|
||||
- the chosen first system-init strategy remains:
|
||||
- FreeBSD init + `rc.d` launching Shepherd
|
||||
rather than Shepherd-as-PID-1
|
||||
- the current system model remains Fruix-owned and FreeBSD-oriented rather than attempting full upstream Guix System integration prematurely
|
||||
|
||||
Next recommended step:
|
||||
|
||||
1. begin Phase 8.1 by creating a reproducible disk-image build path from the generated Fruix rootfs tree
|
||||
2. keep the current init decision explicit for the first boot target:
|
||||
- FreeBSD init + `rc.d` + Shepherd
|
||||
3. continue preserving the selective Fruix naming policy:
|
||||
- Fruix at the product boundary
|
||||
- `/frx` as the canonical store root
|
||||
- stable upstream-derived internal names unless there is strong architectural value in renaming them
|
||||
|
||||
Current assessment:
|
||||
|
||||
- Phase 7.1 is now satisfied on the current FreeBSD prototype track
|
||||
|
||||
78
docs/reports/phase7-rootfs-freebsd.md
Normal file
78
docs/reports/phase7-rootfs-freebsd.md
Normal file
@@ -0,0 +1,78 @@
|
||||
# Phase 7.3: Installable FreeBSD rootfs tree materialized from the Fruix system closure
|
||||
|
||||
Date: 2026-04-01
|
||||
|
||||
## Summary
|
||||
|
||||
This step takes the Phase 7.2 system closure and materializes a root filesystem tree suitable for later image-construction work.
|
||||
|
||||
Added files:
|
||||
|
||||
- `tests/system/materialize-phase7-rootfs.scm`
|
||||
- `tests/system/run-phase7-rootfs.sh`
|
||||
|
||||
## Validation command
|
||||
|
||||
Run command:
|
||||
|
||||
```sh
|
||||
METADATA_OUT=/tmp/phase7-rootfs-metadata.txt \
|
||||
./tests/system/run-phase7-rootfs.sh
|
||||
```
|
||||
|
||||
## What the harness does
|
||||
|
||||
The harness:
|
||||
|
||||
1. ensures the local Guile/Fibers/Shepherd runtime is available
|
||||
2. reuses the declarative Phase 7 operating-system model
|
||||
3. materializes the referenced system closure under `/frx/store`
|
||||
4. creates a root filesystem tree that points at that closure through:
|
||||
- `/run/current-system`
|
||||
- boot symlinks
|
||||
- `/bin`, `/sbin`, `/lib`, and `/usr/*` links into the system profile
|
||||
- generated `/etc` links into the system closure
|
||||
- generated Shepherd `rc.d` launch integration
|
||||
5. performs static validation of the generated rootfs structure
|
||||
|
||||
## Observed results
|
||||
|
||||
Observed metadata included:
|
||||
|
||||
- `rootfs=/tmp/.../rootfs`
|
||||
- `closure_path=/frx/store/...-fruix-system-fruix-freebsd`
|
||||
- `run_current_system_target=/frx/store/...-fruix-system-fruix-freebsd`
|
||||
- `activate_target=/run/current-system/activate`
|
||||
- `bin_target=/run/current-system/profile/bin`
|
||||
- `sbin_target=/run/current-system/profile/sbin`
|
||||
- `boot_kernel_target=/run/current-system/boot/kernel`
|
||||
- `boot_loader_target=/run/current-system/boot/loader`
|
||||
- `boot_loader_efi_target=/run/current-system/boot/loader.efi`
|
||||
- `rc_conf_target=/run/current-system/etc/rc.conf`
|
||||
- `rc_script_target=/run/current-system/usr/local/etc/rc.d/fruix-shepherd`
|
||||
- `ready_marker=/var/lib/fruix/ready`
|
||||
- `validation_mode=static-rootfs-check`
|
||||
- `ready_state_mode=freebsd-init+rc.d-shepherd`
|
||||
|
||||
## Important findings
|
||||
|
||||
- the current FreeBSD Fruix track now has a concrete rootfs tree derived from the declarative system model and closure rather than only a closure directory in the store
|
||||
- the rootfs uses a Guix-like `/run/current-system` anchor so that generated configuration and system profile content remain tied back to the declarative closure
|
||||
- static validation confirmed:
|
||||
- boot asset linkage
|
||||
- generated `/etc` linkage
|
||||
- activation payload presence
|
||||
- Shepherd launch integration
|
||||
- declared filesystem content
|
||||
- declared user/group provisioning in the activation path
|
||||
- the deterministic ready-marker path for the first boot target
|
||||
- the chosen ready state for the first integrated FreeBSD system remains:
|
||||
- FreeBSD init + `rc.d` + Shepherd-managed ready marker
|
||||
|
||||
## Conclusion
|
||||
|
||||
Phase 7.3 is satisfied on the current FreeBSD prototype track:
|
||||
|
||||
- a root filesystem tree can now be materialized from the declarative Fruix system closure
|
||||
- the rootfs is internally coherent enough for the next image-construction phase
|
||||
- Phase 7 as a whole is now complete on the active FreeBSD amd64 prototype path
|
||||
139
tests/system/materialize-phase7-rootfs.scm
Normal file
139
tests/system/materialize-phase7-rootfs.scm
Normal file
@@ -0,0 +1,139 @@
|
||||
(use-modules (fruix system freebsd)
|
||||
(ice-9 format)
|
||||
(srfi srfi-13)
|
||||
(rnrs io ports))
|
||||
|
||||
(define workdir
|
||||
(or (getenv "WORKDIR")
|
||||
(error "WORKDIR environment variable is required")))
|
||||
(define os-file
|
||||
(or (getenv "OS_FILE")
|
||||
(error "OS_FILE environment variable is required")))
|
||||
(define store-dir
|
||||
(or (getenv "STORE_DIR")
|
||||
"/frx/store"))
|
||||
(define guile-prefix
|
||||
(or (getenv "GUILE_PREFIX")
|
||||
"/tmp/guile-freebsd-validate-install"))
|
||||
(define guile-extra-prefix
|
||||
(or (getenv "GUILE_EXTRA_PREFIX")
|
||||
"/tmp/guile-gnutls-freebsd-validate-install"))
|
||||
(define shepherd-prefix
|
||||
(or (getenv "SHEPHERD_PREFIX")
|
||||
"/tmp/shepherd-freebsd-validate-install"))
|
||||
(define metadata-file
|
||||
(string-append workdir "/phase7-rootfs-metadata.txt"))
|
||||
(define rootfs
|
||||
(string-append workdir "/rootfs"))
|
||||
|
||||
(primitive-load os-file)
|
||||
(validate-operating-system phase7-operating-system)
|
||||
|
||||
(define (assert-exists path)
|
||||
(unless (or (file-exists? path)
|
||||
(false-if-exception (readlink path)))
|
||||
(error "required path missing" path)))
|
||||
|
||||
(define (assert-symlink-target path expected)
|
||||
(let ((actual (readlink path)))
|
||||
(unless (string=? actual expected)
|
||||
(error "unexpected symlink target" path actual expected))
|
||||
actual))
|
||||
|
||||
(let* ((result (materialize-rootfs phase7-operating-system rootfs
|
||||
#:store-dir store-dir
|
||||
#:guile-prefix guile-prefix
|
||||
#:guile-extra-prefix guile-extra-prefix
|
||||
#:shepherd-prefix shepherd-prefix))
|
||||
(closure-path (assoc-ref result 'closure-path))
|
||||
(ready-marker (assoc-ref result 'ready-marker))
|
||||
(rc-script (assoc-ref result 'rc-script))
|
||||
(run-current-system-target (assert-symlink-target (string-append rootfs "/run/current-system")
|
||||
closure-path))
|
||||
(activate-target (assert-symlink-target (string-append rootfs "/activate")
|
||||
"/run/current-system/activate"))
|
||||
(bin-target (assert-symlink-target (string-append rootfs "/bin")
|
||||
"/run/current-system/profile/bin"))
|
||||
(sbin-target (assert-symlink-target (string-append rootfs "/sbin")
|
||||
"/run/current-system/profile/sbin"))
|
||||
(lib-target (assert-symlink-target (string-append rootfs "/lib")
|
||||
"/run/current-system/profile/lib"))
|
||||
(boot-kernel-target (assert-symlink-target (string-append rootfs "/boot/kernel")
|
||||
"/run/current-system/boot/kernel"))
|
||||
(boot-loader-target (assert-symlink-target (string-append rootfs "/boot/loader")
|
||||
"/run/current-system/boot/loader"))
|
||||
(boot-loader-efi-target (assert-symlink-target (string-append rootfs "/boot/loader.efi")
|
||||
"/run/current-system/boot/loader.efi"))
|
||||
(rc-conf-target (assert-symlink-target (string-append rootfs "/etc/rc.conf")
|
||||
"/run/current-system/etc/rc.conf"))
|
||||
(fstab-target (assert-symlink-target (string-append rootfs "/etc/fstab")
|
||||
"/run/current-system/etc/fstab"))
|
||||
(passwd-target (assert-symlink-target (string-append rootfs "/etc/passwd")
|
||||
"/run/current-system/etc/passwd"))
|
||||
(group-target (assert-symlink-target (string-append rootfs "/etc/group")
|
||||
"/run/current-system/etc/group"))
|
||||
(rc-script-target (assert-symlink-target (string-append rootfs "/usr/local/etc/rc.d/fruix-shepherd")
|
||||
"/run/current-system/usr/local/etc/rc.d/fruix-shepherd"))
|
||||
(rc-conf-content (call-with-input-file (string-append closure-path "/etc/rc.conf") get-string-all))
|
||||
(fstab-content (call-with-input-file (string-append closure-path "/etc/fstab") get-string-all))
|
||||
(activation-content (call-with-input-file (string-append closure-path "/activate") get-string-all))
|
||||
(shepherd-content (call-with-input-file (string-append closure-path "/shepherd/init.scm") get-string-all))
|
||||
(loader-conf-content (call-with-input-file (string-append closure-path "/boot/loader.conf") get-string-all)))
|
||||
(for-each assert-exists
|
||||
(list rootfs closure-path rc-script
|
||||
(string-append rootfs "/etc/rc")
|
||||
(string-append rootfs "/etc/rc.subr")
|
||||
(string-append rootfs "/etc/rc.d")
|
||||
(string-append rootfs "/etc/defaults")
|
||||
(string-append rootfs "/etc/motd")
|
||||
(string-append rootfs "/usr/sbin")
|
||||
(string-append rootfs "/usr/bin")
|
||||
(string-append rootfs "/var/lib/fruix")
|
||||
(string-append rootfs "/var/log")
|
||||
(string-append rootfs "/var/run")
|
||||
(string-append rootfs "/tmp")))
|
||||
(unless (string-contains rc-conf-content "hostname=\"fruix-freebsd\"")
|
||||
(error "rc.conf does not contain the expected hostname"))
|
||||
(unless (string-contains rc-conf-content "fruix_shepherd_enable=\"YES\"")
|
||||
(error "rc.conf does not enable fruix_shepherd"))
|
||||
(unless (and (string-contains fstab-content "/dev/ufs/fruix-root")
|
||||
(string-contains fstab-content "devfs")
|
||||
(string-contains fstab-content "tmpfs"))
|
||||
(error "fstab content was incomplete"))
|
||||
(unless (string-contains activation-content "pw useradd operator")
|
||||
(error "activation script does not provision the operator account"))
|
||||
(unless (string-contains shepherd-content ready-marker)
|
||||
(error "shepherd configuration does not mention the ready marker"))
|
||||
(unless (string-contains loader-conf-content "console=\"comconsole\"")
|
||||
(error "loader.conf does not contain the expected serial console setting"))
|
||||
(call-with-output-file metadata-file
|
||||
(lambda (port)
|
||||
(format port "rootfs=~a~%" rootfs)
|
||||
(format port "closure_path=~a~%" closure-path)
|
||||
(format port "run_current_system_target=~a~%" run-current-system-target)
|
||||
(format port "activate_target=~a~%" activate-target)
|
||||
(format port "bin_target=~a~%" bin-target)
|
||||
(format port "sbin_target=~a~%" sbin-target)
|
||||
(format port "lib_target=~a~%" lib-target)
|
||||
(format port "boot_kernel_target=~a~%" boot-kernel-target)
|
||||
(format port "boot_loader_target=~a~%" boot-loader-target)
|
||||
(format port "boot_loader_efi_target=~a~%" boot-loader-efi-target)
|
||||
(format port "rc_conf_target=~a~%" rc-conf-target)
|
||||
(format port "fstab_target=~a~%" fstab-target)
|
||||
(format port "passwd_target=~a~%" passwd-target)
|
||||
(format port "group_target=~a~%" group-target)
|
||||
(format port "rc_script=~a~%" rc-script)
|
||||
(format port "rc_script_target=~a~%" rc-script-target)
|
||||
(format port "ready_marker=~a~%" ready-marker)
|
||||
(format port "validation_mode=static-rootfs-check~%")
|
||||
(format port "ready_state_mode=freebsd-init+rc.d-shepherd~%")))
|
||||
|
||||
(when (getenv "METADATA_OUT")
|
||||
(copy-file metadata-file (getenv "METADATA_OUT")))
|
||||
|
||||
(format #t "PASS phase7-rootfs\n")
|
||||
(format #t "Metadata file: ~a\n" metadata-file)
|
||||
(when (getenv "METADATA_OUT")
|
||||
(format #t "Copied metadata to: ~a\n" (getenv "METADATA_OUT")))
|
||||
(display "--- metadata ---\n")
|
||||
(display (call-with-input-file metadata-file get-string-all)))
|
||||
79
tests/system/run-phase7-rootfs.sh
Executable file
79
tests/system/run-phase7-rootfs.sh
Executable file
@@ -0,0 +1,79 @@
|
||||
#!/bin/sh
|
||||
set -eu
|
||||
|
||||
project_root=${PROJECT_ROOT:-$(pwd)}
|
||||
guix_source_dir=${GUIX_SOURCE_DIR:-"$HOME/repos/guix"}
|
||||
script_dir=$(CDPATH= cd -- "$(dirname "$0")" && pwd)
|
||||
runner_scm=$script_dir/materialize-phase7-rootfs.scm
|
||||
os_file=$script_dir/phase7-minimal-operating-system.scm
|
||||
guile_bin=${GUILE_BIN:-/tmp/guile-freebsd-validate-install/bin/guile}
|
||||
guile_extra_prefix=${GUILE_EXTRA_PREFIX:-/tmp/guile-gnutls-freebsd-validate-install}
|
||||
shepherd_prefix=${SHEPHERD_PREFIX:-/tmp/shepherd-freebsd-validate-install}
|
||||
store_dir=${STORE_DIR:-/frx/store}
|
||||
metadata_target=${METADATA_OUT:-}
|
||||
|
||||
if [ ! -x "$guile_bin" ]; then
|
||||
echo "Guile binary is not executable: $guile_bin" >&2
|
||||
exit 1
|
||||
fi
|
||||
|
||||
ensure_built() {
|
||||
if [ ! -d "$guile_extra_prefix/share/guile/site" ] || \
|
||||
! GUILE_LOAD_PATH="$guile_extra_prefix/share/guile/site/3.0${GUILE_LOAD_PATH:+:$GUILE_LOAD_PATH}" \
|
||||
GUILE_LOAD_COMPILED_PATH="$guile_extra_prefix/lib/guile/3.0/site-ccache${GUILE_LOAD_COMPILED_PATH:+:$GUILE_LOAD_COMPILED_PATH}" \
|
||||
GUILE_EXTENSIONS_PATH="$guile_extra_prefix/lib/guile/3.0/extensions${GUILE_EXTENSIONS_PATH:+:$GUILE_EXTENSIONS_PATH}" \
|
||||
LD_LIBRARY_PATH="$guile_extra_prefix/lib:/tmp/guile-freebsd-validate-install/lib:/usr/local/lib${LD_LIBRARY_PATH:+:$LD_LIBRARY_PATH}" \
|
||||
"$guile_bin" -c '(catch #t (lambda () (use-modules (fibers)) (display "ok") (newline)) (lambda _ (display "missing") (newline)))' | grep -qx ok; then
|
||||
METADATA_OUT= ENV_OUT= "$project_root/tests/shepherd/build-local-guile-fibers.sh"
|
||||
fi
|
||||
|
||||
if [ ! -x "$shepherd_prefix/bin/shepherd" ] || [ ! -x "$shepherd_prefix/bin/herd" ]; then
|
||||
METADATA_OUT= ENV_OUT= GUILE_EXTRA_PREFIX="$guile_extra_prefix" "$project_root/tests/shepherd/build-local-shepherd.sh"
|
||||
fi
|
||||
}
|
||||
|
||||
ensure_built
|
||||
|
||||
guile_prefix=$(CDPATH= cd -- "$(dirname "$guile_bin")/.." && pwd)
|
||||
guile_lib_dir=$guile_prefix/lib
|
||||
cleanup=0
|
||||
if [ -n "${WORKDIR:-}" ]; then
|
||||
workdir=$WORKDIR
|
||||
mkdir -p "$workdir"
|
||||
else
|
||||
workdir=$(mktemp -d /tmp/fruix-phase7-rootfs.XXXXXX)
|
||||
cleanup=1
|
||||
fi
|
||||
if [ "${KEEP_WORKDIR:-0}" -eq 1 ]; then
|
||||
cleanup=0
|
||||
fi
|
||||
|
||||
cleanup_workdir() {
|
||||
if [ "$cleanup" -eq 1 ]; then
|
||||
rm -rf "$workdir" 2>/dev/null || sudo rm -rf "$workdir"
|
||||
fi
|
||||
}
|
||||
trap cleanup_workdir EXIT INT TERM
|
||||
|
||||
if [ -n "${GUILE_LOAD_PATH:-}" ]; then
|
||||
gui_load_path="$project_root/modules:$guix_source_dir:$GUILE_LOAD_PATH"
|
||||
else
|
||||
gui_load_path="$project_root/modules:$guix_source_dir"
|
||||
fi
|
||||
|
||||
printf 'Using Guile: %s\n' "$guile_bin"
|
||||
printf 'Working directory: %s\n' "$workdir"
|
||||
printf 'Store directory: %s\n' "$store_dir"
|
||||
|
||||
sudo env \
|
||||
GUILE_AUTO_COMPILE=0 \
|
||||
GUILE_LOAD_PATH="$gui_load_path" \
|
||||
LD_LIBRARY_PATH="$guile_lib_dir${LD_LIBRARY_PATH:+:$LD_LIBRARY_PATH}" \
|
||||
WORKDIR="$workdir" \
|
||||
OS_FILE="$os_file" \
|
||||
STORE_DIR="$store_dir" \
|
||||
GUILE_PREFIX="$guile_prefix" \
|
||||
GUILE_EXTRA_PREFIX="$guile_extra_prefix" \
|
||||
SHEPHERD_PREFIX="$shepherd_prefix" \
|
||||
METADATA_OUT="$metadata_target" \
|
||||
"$guile_bin" -s "$runner_scm"
|
||||
@@ -50,7 +50,7 @@ fi
|
||||
|
||||
cleanup_workdir() {
|
||||
if [ "$cleanup" -eq 1 ]; then
|
||||
rm -rf "$workdir"
|
||||
rm -rf "$workdir" 2>/dev/null || sudo rm -rf "$workdir"
|
||||
fi
|
||||
}
|
||||
trap cleanup_workdir EXIT INT TERM
|
||||
|
||||
Reference in New Issue
Block a user