#!/bin/sh set -eu project_root=${PROJECT_ROOT:-$(pwd)} script_dir=$(CDPATH= cd -- "$(dirname "$0")" && pwd) fruix_cmd=$project_root/bin/fruix os_template=${OS_TEMPLATE:-$script_dir/phase18-install-operating-system.scm.in} system_name=${SYSTEM_NAME:-phase18-operating-system} store_dir=${STORE_DIR:-/frx/store} disk_capacity=${DISK_CAPACITY:-12g} root_size=${ROOT_SIZE:-10g} qemu_smp=${QEMU_SMP:-2} ssh_port=${QEMU_SSH_PORT:-10024} base_name=${BASE_NAME:-phase18-install} base_version_label=${BASE_VERSION_LABEL:-15.0-STABLE-install} base_release=${BASE_RELEASE:-15.0-STABLE} base_branch=${BASE_BRANCH:-stable/15} source_name=${SOURCE_NAME:-stable15-install-source} source_ref=${SOURCE_REF:-stable/15} source_commit=${SOURCE_COMMIT:-332708a606f6bf0841c1d4a74c0d067f5640fe89} declared_source_root=${DECLARED_SOURCE_ROOT:-/var/empty/fruix-unused-source-root-install} metadata_target=${METADATA_OUT:-} root_authorized_key_file=${ROOT_AUTHORIZED_KEY_FILE:-$HOME/.ssh/id_ed25519.pub} root_ssh_private_key_file=${ROOT_SSH_PRIVATE_KEY_FILE:-$HOME/.ssh/id_ed25519} [ -x "$fruix_cmd" ] || { echo "fruix command is not executable: $fruix_cmd" >&2 exit 1 } [ -f "$os_template" ] || { echo "missing operating-system template: $os_template" >&2 exit 1 } [ -f "$root_authorized_key_file" ] || { echo "missing root authorized key file: $root_authorized_key_file" >&2 exit 1 } [ -f "$root_ssh_private_key_file" ] || { echo "missing root SSH private key file: $root_ssh_private_key_file" >&2 exit 1 } command -v qemu-system-x86_64 >/dev/null 2>&1 || { echo "qemu-system-x86_64 is required" >&2 exit 1 } [ -f /usr/local/share/edk2-qemu/QEMU_UEFI_CODE-x86_64.fd ] || { echo "missing QEMU UEFI firmware" >&2 exit 1 } cleanup=0 if [ -n "${WORKDIR:-}" ]; then workdir=$WORKDIR mkdir -p "$workdir" else workdir=$(mktemp -d /tmp/fruix-phase18-install.XXXXXX) cleanup=1 fi if [ "${KEEP_WORKDIR:-0}" -eq 1 ]; then cleanup=0 fi phase18_os_file=$workdir/phase18-install-operating-system.scm install_out=$workdir/install.txt target_image=$workdir/installed.img gpart_log=$workdir/gpart-show.txt serial_log=$workdir/serial.log qemu_pidfile=$workdir/qemu.pid uefi_vars=$workdir/QEMU_UEFI_VARS.fd metadata_file=$workdir/phase18-system-install-metadata.txt mnt_esp=$workdir/mnt-esp mnt_root=$workdir/mnt-root md_unit= cleanup_workdir() { if [ -f "$qemu_pidfile" ]; then sudo kill "$(sudo cat "$qemu_pidfile")" >/dev/null 2>&1 || true fi if [ -n "$md_unit" ]; then sudo umount "$mnt_esp" >/dev/null 2>&1 || true sudo umount "$mnt_root" >/dev/null 2>&1 || true sudo mdconfig -d -u "$md_unit" >/dev/null 2>&1 || true fi if [ "$cleanup" -eq 1 ]; then rm -rf "$workdir" 2>/dev/null || sudo rm -rf "$workdir" fi } trap cleanup_workdir EXIT INT TERM root_authorized_key=$(tr -d '\n' < "$root_authorized_key_file") sed \ -e "s|__BASE_NAME__|$base_name|g" \ -e "s|__BASE_VERSION_LABEL__|$base_version_label|g" \ -e "s|__BASE_RELEASE__|$base_release|g" \ -e "s|__BASE_BRANCH__|$base_branch|g" \ -e "s|__SOURCE_NAME__|$source_name|g" \ -e "s|__SOURCE_REF__|$source_ref|g" \ -e "s|__SOURCE_COMMIT__|$source_commit|g" \ -e "s|__DECLARED_SOURCE_ROOT__|$declared_source_root|g" \ -e "s|__ROOT_AUTHORIZED_KEY__|$root_authorized_key|g" \ "$os_template" > "$phase18_os_file" cp /usr/local/share/edk2-qemu/QEMU_UEFI_VARS-x86_64.fd "$uefi_vars" action_env() { sudo env \ HOME="$HOME" \ GUILE_AUTO_COMPILE=0 \ FRUIX_FREEBSD_BUILD_JOBS="${FRUIX_FREEBSD_BUILD_JOBS:-8}" \ 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}" \ "$@" } action_env "$fruix_cmd" system install "$phase18_os_file" \ --system "$system_name" \ --store "$store_dir" \ --target "$target_image" \ --disk-capacity "$disk_capacity" \ --root-size "$root_size" >"$install_out" field() { sed -n "s/^$1=//p" "$install_out" | tail -n 1 } target_out=$(field target) target_kind=$(field target_kind) target_device=$(field target_device) esp_device=$(field esp_device) root_device=$(field root_device) install_metadata_path=$(field install_metadata_path) closure_path=$(field closure_path) freebsd_base_name_out=$(field freebsd_base_name) freebsd_base_version_label_out=$(field freebsd_base_version_label) freebsd_base_release_out=$(field freebsd_base_release) freebsd_base_branch_out=$(field freebsd_base_branch) freebsd_base_source_root_out=$(field freebsd_base_source_root) freebsd_source_name_out=$(field freebsd_source_name) freebsd_source_kind_out=$(field freebsd_source_kind) freebsd_source_ref_out=$(field freebsd_source_ref) freebsd_source_commit_out=$(field freebsd_source_commit) freebsd_source_file=$(field freebsd_source_file) freebsd_source_materializations_file=$(field freebsd_source_materializations_file) materialized_source_store_count=$(field materialized_source_store_count) materialized_source_stores=$(field materialized_source_stores) host_base_store_count=$(field host_base_store_count) native_base_store_count=$(field native_base_store_count) native_base_stores=$(field native_base_stores) store_item_count=$(field store_item_count) store_layout_file=$(field store_layout_file) [ "$target_out" = "$target_image" ] || { echo "unexpected target path: $target_out" >&2; exit 1; } [ "$target_kind" = raw-file ] || { echo "unexpected target kind: $target_kind" >&2; exit 1; } [ -n "$target_device" ] || { echo "missing target device" >&2; exit 1; } [ -n "$esp_device" ] || { echo "missing esp device" >&2; exit 1; } [ -n "$root_device" ] || { echo "missing root device" >&2; exit 1; } [ "$install_metadata_path" = /var/lib/fruix/install.scm ] || { echo "unexpected install metadata path: $install_metadata_path" >&2; exit 1; } [ -n "$closure_path" ] || { echo "missing closure path" >&2; exit 1; } [ "$freebsd_base_name_out" = "$base_name" ] || { echo "unexpected base name: $freebsd_base_name_out" >&2; exit 1; } [ "$freebsd_base_version_label_out" = "$base_version_label" ] || { echo "unexpected base version label: $freebsd_base_version_label_out" >&2; exit 1; } [ "$freebsd_base_release_out" = "$base_release" ] || { echo "unexpected base release: $freebsd_base_release_out" >&2; exit 1; } [ "$freebsd_base_branch_out" = "$base_branch" ] || { echo "unexpected base branch: $freebsd_base_branch_out" >&2; exit 1; } [ "$freebsd_base_source_root_out" = "$declared_source_root" ] || { echo "unexpected declared source root: $freebsd_base_source_root_out" >&2; exit 1; } [ "$freebsd_source_name_out" = "$source_name" ] || { echo "unexpected source name: $freebsd_source_name_out" >&2; exit 1; } [ "$freebsd_source_kind_out" = git ] || { echo "unexpected source kind: $freebsd_source_kind_out" >&2; exit 1; } [ "$freebsd_source_ref_out" = "$source_ref" ] || { echo "unexpected source ref: $freebsd_source_ref_out" >&2; exit 1; } [ "$freebsd_source_commit_out" = "$source_commit" ] || { echo "unexpected source commit: $freebsd_source_commit_out" >&2; exit 1; } [ "$materialized_source_store_count" = 1 ] || { echo "unexpected materialized source store count: $materialized_source_store_count" >&2; exit 1; } [ "$host_base_store_count" = 0 ] || { echo "expected zero host base stores, got: $host_base_store_count" >&2; exit 1; } [ "$native_base_store_count" = 3 ] || { echo "expected three native base stores, got: $native_base_store_count" >&2; exit 1; } [ -f "$freebsd_source_file" ] || { echo "missing freebsd source file: $freebsd_source_file" >&2; exit 1; } [ -f "$freebsd_source_materializations_file" ] || { echo "missing source materializations file: $freebsd_source_materializations_file" >&2; exit 1; } [ -f "$store_layout_file" ] || { echo "missing store layout file: $store_layout_file" >&2; exit 1; } [ -f "$target_image" ] || { echo "missing target image: $target_image" >&2; exit 1; } case "$materialized_source_stores" in /frx/store/*-freebsd-source-$source_name) : ;; *) echo "unexpected materialized source store path: $materialized_source_stores" >&2; exit 1 ;; esac closure_base=$(basename "$closure_path") md=$(sudo mdconfig -a -t vnode -f "$target_image") md_unit=${md#md} sudo mkdir -p "$mnt_esp" "$mnt_root" sudo gpart show -lp "/dev/$md" >"$gpart_log" 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" [ -f "$mnt_esp/EFI/BOOT/BOOTX64.EFI" ] || { echo "missing EFI boot file on installed target" >&2; exit 1; } run_current_system_target=$(readlink "$mnt_root/run/current-system") boot_loader_target=$(readlink "$mnt_root/boot/loader") install_metadata_host=$(cat "$mnt_root$install_metadata_path") [ "$run_current_system_target" = "/frx/store/$closure_base" ] || { echo "unexpected /run/current-system target: $run_current_system_target" >&2; exit 1; } [ "$boot_loader_target" = /run/current-system/boot/loader ] || { echo "unexpected /boot/loader target: $boot_loader_target" >&2; exit 1; } [ -d "$mnt_root/frx/store/$closure_base" ] || { echo "installed closure missing from target root" >&2; exit 1; } case "$install_metadata_host" in *"$closure_path"*) : ;; *) echo "installed metadata file does not record closure path" >&2; exit 1 ;; esac case "$install_metadata_host" in *"$materialized_source_stores"*) : ;; *) echo "installed metadata file does not record materialized source store" >&2; exit 1 ;; esac sudo umount "$mnt_esp" sudo umount "$mnt_root" sudo mdconfig -d -u "$md_unit" md_unit= sudo qemu-system-x86_64 \ -machine q35,accel=tcg \ -cpu max \ -m 2048 \ -smp "$qemu_smp" \ -display none \ -serial "file:$serial_log" \ -monitor none \ -pidfile "$qemu_pidfile" \ -daemonize \ -drive if=pflash,format=raw,readonly=on,file=/usr/local/share/edk2-qemu/QEMU_UEFI_CODE-x86_64.fd \ -drive if=pflash,format=raw,file="$uefi_vars" \ -drive if=virtio,format=raw,file="$target_image" \ -netdev user,id=net0,hostfwd=tcp::${ssh_port}-:22 \ -device virtio-net-pci,netdev=net0 ssh_guest() { ssh -p "$ssh_port" -i "$root_ssh_private_key_file" \ -o BatchMode=yes \ -o StrictHostKeyChecking=no \ -o UserKnownHostsFile=/dev/null \ -o ConnectTimeout=5 \ root@127.0.0.1 "$@" } for attempt in $(jot 120 1 120); do if ssh_guest 'service sshd onestatus >/dev/null 2>&1' >/dev/null 2>&1; then break fi sleep 2 done run_current_system_guest=$(ssh_guest 'readlink /run/current-system') shepherd_status=$(ssh_guest '/usr/local/etc/rc.d/fruix-shepherd onestatus >/dev/null 2>&1 && echo running || echo stopped') sshd_status=$(ssh_guest 'service sshd onestatus >/dev/null 2>&1 && echo running || echo stopped') install_metadata_guest=$(ssh_guest 'cat /var/lib/fruix/install.scm') activate_log=$(ssh_guest 'cat /var/log/fruix-activate.log 2>/dev/null || true' | tr '\n' ' ') [ "$run_current_system_guest" = "/frx/store/$closure_base" ] || { echo "unexpected guest current-system target: $run_current_system_guest" >&2; exit 1; } [ "$shepherd_status" = running ] || { echo "fruix-shepherd rc service is not running in installed system" >&2; exit 1; } [ "$sshd_status" = running ] || { echo "sshd is not running in installed system" >&2; exit 1; } case "$install_metadata_guest" in *"$closure_path"*) : ;; *) echo "guest install metadata does not record closure path" >&2; exit 1 ;; esac case "$install_metadata_guest" in *"$materialized_source_stores"*) : ;; *) echo "guest install metadata does not record materialized source store" >&2; exit 1 ;; esac case "$activate_log" in *fruix-activate:done*) : ;; *) echo "installed system activation log does not show success" >&2; exit 1 ;; esac cat >"$metadata_file" <