Complete Phase 10 FreeBSD system tooling

This commit is contained in:
2026-04-02 11:19:30 +02:00
parent 7db5c76541
commit c62c89b078
6 changed files with 555 additions and 146 deletions

View File

@@ -2353,3 +2353,71 @@ Next recommended step:
1. continue Phase 10 by making more of the existing system workflows call `bin/fruix` directly instead of bespoke phase scripts
2. reduce the current runtime compatibility shims for locally built Guile / Shepherd prefixes and move toward a more native store-path-aware Fruix runtime arrangement
3. consider adding the next operator-facing subcommand on top of the now-working image path, such as a `vm`/deploy-oriented flow for the active XCP-ng workflow
## 2026-04-02 — Phase 10 completed: canonical system workflows now use `fruix system`
Completed work:
- completed the current Optional Phase 10 track by making the existing FreeBSD system workflows use the real Fruix CLI as their canonical frontend
- wrote the completion report:
- `docs/reports/phase10-canonical-system-workflows-freebsd.md`
- extended the `fruix system build` metadata in:
- `scripts/fruix.scm`
- added:
- `ready_marker`
- `base_package_store_count`
- `base_package_stores`
- refactored the main static system harnesses to call `bin/fruix` instead of invoking internal Scheme materializer runners directly:
- `tests/system/run-phase7-system-closure.sh`
- `tests/system/run-phase7-rootfs.sh`
- `tests/system/run-phase8-system-image.sh`
What changed in practice:
- the closure path is now validated through:
- `fruix system build`
- the rootfs path is now validated through:
- `fruix system rootfs`
- the image path is now validated through:
- `fruix system image`
- the real XCP-ng boot path now benefits from this automatically because:
- `tests/system/run-phase9-xcpng-boot.sh`
still calls the Phase 8 harness, and that harness now builds its image through `bin/fruix`
Validation:
- `tests/system/run-phase7-system-closure.sh` passes
- workdir: `/tmp/phase10-canon-closure2-1775119728`
- `tests/system/run-phase7-rootfs.sh` passes
- workdir: `/tmp/phase10-canon-rootfs3-1775120391`
- `tests/system/run-phase8-system-image.sh` passes
- workdir: `/tmp/phase10-canon-image-1775120548`
- full real XCP-ng regression still passes after the frontend refactor:
- `tests/system/run-phase9-xcpng-boot.sh`
- workdir: `/tmp/phase10-canon-xcpng-1775120869`
Important findings:
- the Phase 9 booted system path was already real, but the remaining transitional layer was the tooling/frontend boundary rather than the system internals
- adding `bin/fruix` in Phase 10.1 was necessary but not sufficient on its own; the existing validation and deployment workflows also had to adopt it, or the project would still effectively be driven by bespoke phase scripts
- after this refactor, the command path is now exercised by:
- static closure validation
- static rootfs validation
- static image validation
- and the real XCP-ng boot/import/SSH/ready-marker path
Current assessment:
- Optional Phase 10 is now complete for the current FreeBSD prototype track
- Fruix now has:
- a user-facing command surface in `bin/fruix`
- real `system build`, `system rootfs`, and `system image` actions
- canonical validation/deployment workflows that use that command instead of directly entering the materializers
- and a command-driven image path that remains validated on the real XCP-ng VM
- this means the project now has not just declarative OS internals, but also a real Fruix operator/tooling layer around those internals
Next recommended step:
1. begin the next post-Phase-10 cleanup/polish pass outside the plan milestones
2. prioritize replacing the current Guile / Shepherd compatibility-prefix shims with a more native store-path-aware runtime arrangement
3. consider adding richer deploy/vm-oriented `fruix` commands beyond the now-canonical `system build/rootfs/image` path

View File

@@ -0,0 +1,163 @@
# Phase 10 completion: canonical FreeBSD system workflows now use `fruix system`
Date: 2026-04-02
## Goal
Complete the current Optional Phase 10 track by moving the existing FreeBSD system workflows away from phase-specific direct materializer entry points and toward the real Fruix command surface introduced in Phase 10.1.
Concretely, this subphase targeted three things:
- make the existing system validation harnesses call `bin/fruix` as their canonical frontend,
- keep the deeper static and boot validations intact after that refactor,
- and prove that the real XCP-ng deployment path still works when the image is produced through the `fruix system` path.
## Result
This refactor succeeded.
The main static system harnesses no longer invoke the Scheme materializer runners directly as their primary frontend. They now call the real Fruix command and then perform their existing validation logic on the resulting artifacts.
Updated harnesses:
- `tests/system/run-phase7-system-closure.sh`
- `tests/system/run-phase7-rootfs.sh`
- `tests/system/run-phase8-system-image.sh`
These harnesses now use:
- `bin/fruix system build`
- `bin/fruix system rootfs`
- `bin/fruix system image`
The Phase 9 XCP-ng boot path benefits from this automatically because:
- `tests/system/run-phase9-xcpng-boot.sh`
still calls `tests/system/run-phase8-system-image.sh`,
- and that Phase 8 harness now builds the image through `bin/fruix`.
## Code-level changes
### `scripts/fruix.scm`
Extended the `fruix system build` metadata so callers can recover more of the closure structure without having to re-enter the Scheme materializer directly.
Added emitted fields for:
- `ready_marker`
- `base_package_store_count`
- `base_package_stores`
That made it practical for the refactored shell harnesses to keep their old validation/reporting fidelity while using the new CLI frontend.
### `tests/system/run-phase7-system-closure.sh`
Reworked to:
- call `bin/fruix system build` twice,
- validate closure reproducibility through the command frontend,
- validate closure contents and key symlink targets,
- and record metadata showing `frontend_invocation=/.../bin/fruix system build`.
### `tests/system/run-phase7-rootfs.sh`
Reworked to:
- call `bin/fruix system rootfs`,
- validate the generated rootfs symlink structure and content expectations,
- and record metadata showing `frontend_invocation=/.../bin/fruix system rootfs`.
### `tests/system/run-phase8-system-image.sh`
Reworked to:
- call `bin/fruix system image`,
- preserve the existing GPT/filesystem/mount/static-layout validation logic,
- and record metadata showing `frontend_invocation=/.../bin/fruix system image`.
## Validation
### Static workflow validation
Successful closure validation:
- `PASS phase7-system-closure`
- workdir: `/tmp/phase10-canon-closure2-1775119728`
Successful rootfs validation:
- `PASS phase7-rootfs`
- workdir: `/tmp/phase10-canon-rootfs3-1775120391`
Successful image validation:
- `PASS phase8-system-image`
- workdir: `/tmp/phase10-canon-image-1775120548`
The resulting metadata confirms that all of those paths are now driven by `bin/fruix`.
### Real XCP-ng regression validation
To ensure this was not merely a static refactor, the full real-VM path was rerun after the harness changes.
Successful real boot validation:
- `PASS phase9-xcpng-boot`
- workdir: `/tmp/phase10-canon-xcpng-1775120869`
That successful rerun confirmed that the `fruix system image`-driven path still produces an image that can:
- be converted to dynamic VHD,
- be imported into the existing XCP-ng VDI,
- boot on VM `90490f2e-e8fc-4b7a-388e-5c26f0157289`,
- reach DHCP and SSH,
- keep Shepherd running,
- and reach the ready marker.
Representative final guest state from that pass:
```text
ready_marker=ready
shepherd_status=running
sshd_status=running
run_current_system_target=/frx/store/0fe459ea22156510e64cea794b7a001151b59625bd5f12a488d6851e1c6d2198-fruix-system-fruix-freebsd
boot_backend=xcp-ng-xo-cli
```
## Why this completes Phase 10 on the current track
By the end of Phase 9, Fruix already had:
- a declarative system model,
- real closure and image outputs in `/frx/store`,
- and a booted FreeBSD VM with Shepherd and SSH.
What was still transitional was the operator/tooling layer: too much of the system workflow was still centered on phase-specific scripts invoking internal materializers directly.
After Phase 10.1 and this completion step, the current track now has:
- a user-facing Fruix command: `bin/fruix`
- real system actions under that command:
- `system build`
- `system rootfs`
- `system image`
- existing validation/deployment workflows using that command as their canonical frontend
- and successful regression validation on the real XCP-ng guest path
That is enough to treat Optional Phase 10 as complete for the current FreeBSD prototype track.
## Remaining follow-up work beyond Phase 10
There is still good follow-up work available, but it is no longer required to say that Fruix has crossed from “prototype scripts only” into a real OS-tooling shape.
Useful future cleanup includes:
- replacing the current Guile/Shepherd compatibility-prefix shims with a more native runtime arrangement,
- polishing residual boot noise and base-service rough edges,
- and extending `fruix` with richer deploy/vm-oriented commands.
## Conclusion
Optional Phase 10 is now complete on the current track.
Fruix no longer just has a declarative FreeBSD system implementation internally; it now has a real Fruix command surface that is used by the canonical closure/rootfs/image workflows, and that command path has been validated all the way through a successful real XCP-ng boot.

View File

@@ -161,18 +161,22 @@ Options:\n\
#:shepherd-prefix shepherd-prefix))
(closure-path (assoc-ref result 'closure-path))
(generated-files (assoc-ref result 'generated-files))
(references (assoc-ref result 'references)))
(references (assoc-ref result 'references))
(base-package-stores (assoc-ref result 'base-package-stores)))
(emit-metadata
`((action . "build")
(os_file . ,os-file)
(system_variable . ,resolved-symbol)
(store_dir . ,store-dir)
(closure_path . ,closure-path)
(ready_marker . ,(operating-system-ready-marker os))
(kernel_store . ,(assoc-ref result 'kernel-store))
(bootloader_store . ,(assoc-ref result 'bootloader-store))
(guile_store . ,(assoc-ref result 'guile-store))
(guile_extra_store . ,(assoc-ref result 'guile-extra-store))
(shepherd_store . ,(assoc-ref result 'shepherd-store))
(base_package_store_count . ,(length base-package-stores))
(base_package_stores . ,(string-join base-package-stores ","))
(generated_file_count . ,(length generated-files))
(reference_count . ,(length references))))))
((string=? action "rootfs")

View File

@@ -2,40 +2,18 @@
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}
fruix_cmd=$project_root/bin/fruix
os_file=${OS_FILE:-$script_dir/phase7-minimal-operating-system.scm}
system_name=${SYSTEM_NAME:-phase7-operating-system}
store_dir=${STORE_DIR:-/frx/store}
metadata_target=${METADATA_OUT:-}
if [ ! -x "$guile_bin" ]; then
echo "Guile binary is not executable: $guile_bin" >&2
[ -x "$fruix_cmd" ] || {
echo "fruix command is not executable: $fruix_cmd" >&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
@@ -47,6 +25,7 @@ fi
if [ "${KEEP_WORKDIR:-0}" -eq 1 ]; then
cleanup=0
fi
rootfs=${ROOTFS_DIR:-$workdir/rootfs}
cleanup_workdir() {
if [ "$cleanup" -eq 1 ]; then
@@ -55,25 +34,158 @@ cleanup_workdir() {
}
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
rootfs_out=$workdir/rootfs.txt
metadata_file=$workdir/phase7-rootfs-metadata.txt
printf 'Using Guile: %s\n' "$guile_bin"
action_env() {
sudo env \
HOME="$HOME" \
GUIX_SOURCE_DIR="${GUIX_SOURCE_DIR:-$HOME/repos/guix}" \
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}" \
"$@"
}
assert_present() {
path=$1
[ -e "$path" ] || [ -L "$path" ] || {
echo "required path missing: $path" >&2
exit 1
}
}
assert_target() {
path=$1
expected=$2
actual=$(readlink "$path")
[ "$actual" = "$expected" ] || {
echo "unexpected symlink target for $path: $actual != $expected" >&2
exit 1
}
printf '%s\n' "$actual"
}
printf 'Using fruix command: %s\n' "$fruix_cmd"
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"
action_env "$fruix_cmd" system rootfs "$os_file" "$rootfs" --system "$system_name" --store "$store_dir" >"$rootfs_out"
rootfs_reported=$(sed -n 's/^rootfs=//p' "$rootfs_out")
closure_path=$(sed -n 's/^closure_path=//p' "$rootfs_out")
ready_marker=$(sed -n 's/^ready_marker=//p' "$rootfs_out")
rc_script=$(sed -n 's/^rc_script=//p' "$rootfs_out")
[ "$rootfs_reported" = "$rootfs" ] || {
echo "unexpected rootfs path reported: $rootfs_reported" >&2
exit 1
}
case "$closure_path" in
/frx/store/*-fruix-system-fruix-freebsd) : ;;
*) echo "unexpected closure path: $closure_path" >&2; exit 1 ;;
esac
for path in \
"$rootfs" \
"$closure_path" \
"$rc_script" \
"$rootfs/etc/rc" \
"$rootfs/etc/rc.subr" \
"$rootfs/etc/rc.d" \
"$rootfs/etc/defaults" \
"$rootfs/etc/motd" \
"$rootfs/usr/sbin" \
"$rootfs/usr/bin" \
"$rootfs/var/lib/fruix" \
"$rootfs/var/log" \
"$rootfs/var/run" \
"$rootfs/tmp"
do
assert_present "$path"
done
run_current_system_target=$(assert_target "$rootfs/run/current-system" "$closure_path")
activate_target=$(assert_target "$rootfs/activate" /run/current-system/activate)
bin_target=$(assert_target "$rootfs/bin" /run/current-system/profile/bin)
sbin_target=$(assert_target "$rootfs/sbin" /run/current-system/profile/sbin)
lib_target=$(assert_target "$rootfs/lib" /run/current-system/profile/lib)
boot_kernel_target=$(assert_target "$rootfs/boot/kernel" /run/current-system/boot/kernel)
boot_loader_target=$(assert_target "$rootfs/boot/loader" /run/current-system/boot/loader)
boot_loader_efi_target=$(assert_target "$rootfs/boot/loader.efi" /run/current-system/boot/loader.efi)
rc_conf_target=$(assert_target "$rootfs/etc/rc.conf" /run/current-system/etc/rc.conf)
fstab_target=$(assert_target "$rootfs/etc/fstab" /run/current-system/etc/fstab)
passwd_target=$(assert_target "$rootfs/etc/passwd" /run/current-system/etc/passwd)
group_target=$(assert_target "$rootfs/etc/group" /run/current-system/etc/group)
rc_script_target=$(assert_target "$rootfs/usr/local/etc/rc.d/fruix-shepherd" /run/current-system/usr/local/etc/rc.d/fruix-shepherd)
grep -F 'hostname="fruix-freebsd"' "$closure_path/etc/rc.conf" >/dev/null || {
echo "rc.conf does not contain the expected hostname" >&2
exit 1
}
grep -F 'fruix_shepherd_enable="YES"' "$closure_path/etc/rc.conf" >/dev/null || {
echo "rc.conf does not enable fruix_shepherd" >&2
exit 1
}
grep -F '/dev/ufs/fruix-root' "$closure_path/etc/fstab" >/dev/null || {
echo "fstab is missing the root filesystem" >&2
exit 1
}
grep -F 'devfs' "$closure_path/etc/fstab" >/dev/null || {
echo "fstab is missing devfs" >&2
exit 1
}
grep -F 'tmpfs' "$closure_path/etc/fstab" >/dev/null || {
echo "fstab is missing tmpfs" >&2
exit 1
}
grep -F 'mkdir -p /home/operator' "$closure_path/activate" >/dev/null || {
echo "activation script does not provision the operator home" >&2
exit 1
}
grep -F "$ready_marker" "$closure_path/shepherd/init.scm" >/dev/null || {
echo "shepherd configuration does not mention the ready marker" >&2
exit 1
}
grep -F 'console="comconsole"' "$closure_path/boot/loader.conf" >/dev/null || {
echo "loader.conf does not contain the expected serial console setting" >&2
exit 1
}
cat >"$metadata_file" <<EOF
workdir=$workdir
rootfs=$rootfs
closure_path=$closure_path
run_current_system_target=$run_current_system_target
activate_target=$activate_target
bin_target=$bin_target
sbin_target=$sbin_target
lib_target=$lib_target
boot_kernel_target=$boot_kernel_target
boot_loader_target=$boot_loader_target
boot_loader_efi_target=$boot_loader_efi_target
rc_conf_target=$rc_conf_target
fstab_target=$fstab_target
passwd_target=$passwd_target
group_target=$group_target
rc_script=$rc_script
rc_script_target=$rc_script_target
ready_marker=$ready_marker
validation_mode=static-rootfs-check
ready_state_mode=freebsd-init+rc.d-shepherd
frontend_invocation=$fruix_cmd system rootfs
EOF
if [ -n "$metadata_target" ]; then
mkdir -p "$(dirname "$metadata_target")"
cp "$metadata_file" "$metadata_target"
fi
printf 'PASS phase7-rootfs\n'
printf 'Work directory: %s\n' "$workdir"
printf 'Metadata file: %s\n' "$metadata_file"
if [ -n "$metadata_target" ]; then
printf 'Copied metadata to: %s\n' "$metadata_target"
fi
printf '%s\n' '--- metadata ---'
cat "$metadata_file"

View File

@@ -2,40 +2,18 @@
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-system-closure.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}
fruix_cmd=$project_root/bin/fruix
os_file=${OS_FILE:-$script_dir/phase7-minimal-operating-system.scm}
system_name=${SYSTEM_NAME:-phase7-operating-system}
store_dir=${STORE_DIR:-/frx/store}
metadata_target=${METADATA_OUT:-}
if [ ! -x "$guile_bin" ]; then
echo "Guile binary is not executable: $guile_bin" >&2
[ -x "$fruix_cmd" ] || {
echo "fruix command is not executable: $fruix_cmd" >&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
@@ -55,33 +33,142 @@ cleanup_workdir() {
}
trap cleanup_workdir EXIT INT TERM
export GUILE_AUTO_COMPILE=0
export WORKDIR="$workdir"
export OS_FILE="$os_file"
export STORE_DIR="$store_dir"
export GUILE_PREFIX="$guile_prefix"
export GUILE_EXTRA_PREFIX="$guile_extra_prefix"
export SHEPHERD_PREFIX="$shepherd_prefix"
build_out_a=$workdir/build-a.txt
build_out_b=$workdir/build-b.txt
metadata_file=$workdir/phase7-system-closure-metadata.txt
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
action_env() {
sudo env \
HOME="$HOME" \
GUIX_SOURCE_DIR="${GUIX_SOURCE_DIR:-$HOME/repos/guix}" \
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}" \
"$@"
}
printf 'Using Guile: %s\n' "$guile_bin"
printf 'Using fruix command: %s\n' "$fruix_cmd"
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"
action_env "$fruix_cmd" system build "$os_file" --system "$system_name" --store "$store_dir" >"$build_out_a"
action_env "$fruix_cmd" system build "$os_file" --system "$system_name" --store "$store_dir" >"$build_out_b"
closure_path=$(sed -n 's/^closure_path=//p' "$build_out_a")
closure_rebuild_path=$(sed -n 's/^closure_path=//p' "$build_out_b")
ready_marker=$(sed -n 's/^ready_marker=//p' "$build_out_a")
kernel_store=$(sed -n 's/^kernel_store=//p' "$build_out_a")
bootloader_store=$(sed -n 's/^bootloader_store=//p' "$build_out_a")
guile_store=$(sed -n 's/^guile_store=//p' "$build_out_a")
guile_extra_store=$(sed -n 's/^guile_extra_store=//p' "$build_out_a")
shepherd_store=$(sed -n 's/^shepherd_store=//p' "$build_out_a")
base_package_store_count=$(sed -n 's/^base_package_store_count=//p' "$build_out_a")
base_package_stores=$(sed -n 's/^base_package_stores=//p' "$build_out_a")
reference_count=$(sed -n 's/^reference_count=//p' "$build_out_a")
generated_file_count=$(sed -n 's/^generated_file_count=//p' "$build_out_a")
case "$closure_path" in
/frx/store/*-fruix-system-fruix-freebsd) : ;;
*) echo "unexpected closure path: $closure_path" >&2; exit 1 ;;
esac
[ "$closure_path" = "$closure_rebuild_path" ] || {
echo "closure path was not reproducible: $closure_path != $closure_rebuild_path" >&2
exit 1
}
rc_script=$closure_path/usr/local/etc/rc.d/fruix-shepherd
shepherd_config=$closure_path/shepherd/init.scm
activation_script=$closure_path/activate
loader_conf=$closure_path/boot/loader.conf
profile_bin_sh=$closure_path/profile/bin/sh
profile_sbin_init=$closure_path/profile/sbin/init
profile_rc=$closure_path/profile/etc/rc
boot_loader_target=$(readlink "$closure_path/boot/loader")
kernel_link_target=$(readlink "$closure_path/boot/kernel/kernel")
for path in \
"$closure_path" \
"$kernel_store" \
"$bootloader_store" \
"$guile_store" \
"$guile_extra_store" \
"$shepherd_store" \
"$rc_script" \
"$shepherd_config" \
"$activation_script" \
"$loader_conf" \
"$profile_bin_sh" \
"$profile_sbin_init" \
"$profile_rc" \
"$closure_path/parameters.scm"
do
[ -e "$path" ] || {
echo "required path missing: $path" >&2
exit 1
}
done
[ -x "$activation_script" ] || { echo "activation script is not executable" >&2; exit 1; }
[ -x "$rc_script" ] || { echo "fruix shepherd rc script is not executable" >&2; exit 1; }
[ -n "$base_package_store_count" ] || { echo "missing base package store count" >&2; exit 1; }
[ -n "$generated_file_count" ] || { echo "missing generated file count" >&2; exit 1; }
[ -n "$reference_count" ] || { echo "missing reference count" >&2; exit 1; }
case "$boot_loader_target" in
/frx/store/*/boot/loader) : ;;
*) echo "unexpected /boot/loader target in closure: $boot_loader_target" >&2; exit 1 ;;
esac
case "$kernel_link_target" in
/frx/store/*/boot/kernel/kernel) : ;;
*) echo "unexpected kernel link target: $kernel_link_target" >&2; exit 1 ;;
esac
grep -F 'fruix-ready' "$shepherd_config" >/dev/null || {
echo "shepherd config missing fruix-ready service" >&2
exit 1
}
grep -F "$ready_marker" "$shepherd_config" >/dev/null || {
echo "shepherd config missing ready marker path" >&2
exit 1
}
cat >"$metadata_file" <<EOF
workdir=$workdir
store_dir=$store_dir
closure_path=$closure_path
closure_rebuild_path=$closure_rebuild_path
ready_marker=$ready_marker
kernel_store=$kernel_store
bootloader_store=$bootloader_store
guile_store=$guile_store
guile_extra_store=$guile_extra_store
shepherd_store=$shepherd_store
base_package_store_count=$base_package_store_count
base_package_stores=$base_package_stores
reference_count=$reference_count
generated_file_count=$generated_file_count
rc_script=$rc_script
shepherd_config=$shepherd_config
activation_script=$activation_script
loader_conf=$loader_conf
boot_loader_target=$boot_loader_target
kernel_link_target=$kernel_link_target
profile_bin_sh=$profile_bin_sh
profile_sbin_init=$profile_sbin_init
profile_rc=$profile_rc
init_integration=freebsd-init+rc.d-shepherd
frontend_invocation=$fruix_cmd system build
EOF
if [ -n "$metadata_target" ]; then
mkdir -p "$(dirname "$metadata_target")"
cp "$metadata_file" "$metadata_target"
fi
printf 'PASS phase7-system-closure\n'
printf 'Work directory: %s\n' "$workdir"
printf 'Metadata file: %s\n' "$metadata_file"
if [ -n "$metadata_target" ]; then
printf 'Copied metadata to: %s\n' "$metadata_target"
fi
printf '%s\n' '--- metadata ---'
cat "$metadata_file"

View File

@@ -2,41 +2,19 @@
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-phase8-system-image.scm
fruix_cmd=$project_root/bin/fruix
os_file=${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}
system_name=${SYSTEM_NAME:-phase7-operating-system}
store_dir=${STORE_DIR:-/frx/store}
disk_capacity=${DISK_CAPACITY:-}
metadata_target=${METADATA_OUT:-}
if [ ! -x "$guile_bin" ]; then
echo "Guile binary is not executable: $guile_bin" >&2
[ -x "$fruix_cmd" ] || {
echo "fruix command is not executable: $fruix_cmd" >&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
@@ -68,37 +46,33 @@ cleanup_workdir() {
}
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
action_env() {
sudo env \
HOME="$HOME" \
GUIX_SOURCE_DIR="${GUIX_SOURCE_DIR:-$HOME/repos/guix}" \
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}" \
"$@"
}
printf 'Using Guile: %s\n' "$guile_bin"
printf 'Using fruix command: %s\n' "$fruix_cmd"
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" \
DISK_CAPACITY="$disk_capacity" \
GUILE_PREFIX="$guile_prefix" \
GUILE_EXTRA_PREFIX="$guile_extra_prefix" \
SHEPHERD_PREFIX="$shepherd_prefix" \
METADATA_OUT="$build_metadata" \
"$guile_bin" -s "$runner_scm"
if [ -n "$disk_capacity" ]; then
action_env "$fruix_cmd" system image "$os_file" --system "$system_name" --store "$store_dir" --disk-capacity "$disk_capacity" >"$build_metadata"
else
action_env "$fruix_cmd" system image "$os_file" --system "$system_name" --store "$store_dir" >"$build_metadata"
fi
image_store_path=$(sed -n 's/^image_store_path=//p' "$build_metadata")
disk_image=$(sed -n 's/^disk_image=//p' "$build_metadata")
closure_path=$(sed -n 's/^closure_path=//p' "$build_metadata")
raw_sha256=$(sed -n 's/^raw_sha256=//p' "$build_metadata")
image_size_bytes=$(sed -n 's/^image_size_bytes=//p' "$build_metadata")
disk_capacity_reported=$(sed -n 's/^disk_capacity=//p' "$build_metadata")
store_item_count=$(sed -n 's/^store_item_count=//p' "$build_metadata")
raw_sha256=$(sha256 -q "$disk_image")
image_size_bytes=$(stat -f '%z' "$disk_image")
closure_base=$(basename "$closure_path")
case "$image_store_path" in
@@ -118,6 +92,7 @@ esp_fstype=$(sudo fstyp "/dev/${md}p1")
root_fstype=$(sudo fstyp "/dev/${md}p2")
[ "$esp_fstype" = msdosfs ] || { echo "unexpected ESP filesystem: $esp_fstype" >&2; exit 1; }
[ "$root_fstype" = ufs ] || { echo "unexpected root filesystem: $root_fstype" >&2; exit 1; }
sudo mount -t msdosfs "/dev/${md}p1" "$mnt_esp"
sudo mount -t ufs -o ro "/dev/${md}p2" "$mnt_root"
@@ -158,7 +133,7 @@ boot_loader_conf_target=$boot_loader_conf_target
rc_conf_target=$rc_conf_target
rc_script_target=$rc_script_target
image_generation_mode=declarative-system-layer
frontend_invocation=$runner_scm
frontend_invocation=$fruix_cmd system image
EOF
if [ -n "$metadata_target" ]; then