system: add UEFI installer ISO builder

This commit is contained in:
2026-04-04 10:23:46 +02:00
parent ebe064a652
commit 1970c5c181
8 changed files with 956 additions and 18 deletions

View File

@@ -0,0 +1,139 @@
# Phase 18.3: bootable Fruix installer ISO on FreeBSD
Date: 2026-04-04
## Goal
Phase 18.3 extends the Phase 18.2 installer-environment work from a disk-image-style installer into a UEFI-bootable ISO artifact.
The intended first ISO is deliberately narrow:
- UEFI only
- serial-console-friendly
- non-interactive install flow reused from Phase 18.1/18.2
- target disk installation still performed by the same Fruix-managed in-guest installer logic
## Implementation
### New API
Added in `modules/fruix/system/freebsd.scm`:
- `operating-system-installer-iso-spec`
- `materialize-installer-iso`
The system module split done immediately before this phase was also exercised during this work.
### New CLI action
Added in `scripts/fruix.scm`:
- `fruix system installer-iso`
This action emits metadata for:
- ISO store path
- ISO image path
- EFI boot image path
- installer root image path
- installer and target closure paths
- installer state/log paths
- declared/materialized FreeBSD source metadata
- store closure counts
### ISO boot model
The ISO does not try to run the Fruix installer directly from a read-only cd9660 root.
Instead it uses a small UEFI El Torito boot image plus an in-memory installer root image:
1. a small FAT EFI boot image contains `EFI/BOOT/BOOTX64.EFI`
2. the ISO root contains real boot assets under `/boot`
3. the ISO root also contains `/boot/root.img`
4. `loader.conf` on the ISO is augmented with:
- `mdroot_load="YES"`
- `mdroot_type="md_image"`
- `mdroot_name="/boot/root.img"`
- `rootdev="ufs:/dev/md0"`
- `vfs.root.mountfrom="ufs:/dev/md0"`
- `vfs.root.mountfrom.options="rw"`
This preserves the existing Fruix installer environment semantics while avoiding the need to make the whole installer operate directly from a read-only ISO root.
### Installer root image contents
`materialize-installer-iso` stages the same installer payload model already validated in Phase 18.2:
- installer closure
- target closure
- target store closure
- staged target rootfs under `/var/lib/fruix/installer/target-rootfs`
- installer plan and state files under `/var/lib/fruix/installer`
- installer helper scripts:
- `/usr/local/libexec/fruix-installer-run`
- `/usr/local/etc/rc.d/fruix-installer`
The ISO root image is then built as a UFS image and embedded as `/boot/root.img`.
### Split-regression fixes found during this work
While exercising the refactored split modules, two issues surfaced and were fixed:
1. `string-hash` name-clash warnings
- the old helper name collided with Guile/SRFI bindings
- it was renamed to `sha256-string`
2. missing `prefix-materializer-version`
- this constant was accidentally omitted when `modules/fruix/system/freebsd.scm` was split
- the missing definition was restored in `modules/fruix/system/freebsd/build.scm`
## Current validation status
### Completed smoke validation
A host-side smoke build was completed successfully for the new ISO builder using a host-staged operating-system definition:
- command pattern:
- `fruix system installer-iso ...`
- result:
- successful ISO materialization in a temporary store
- artifact checks performed:
- `etdump` reports an EFI El Torito boot entry
- the ISO contains:
- `boot/kernel/kernel`
- `boot/kernel/linker.hints`
- `boot/loader.conf`
- `boot/loader.efi`
- `boot/root.img`
- `boot/loader.conf` inside the ISO contains the expected `mdroot_*` and `vfs.root.mountfrom` entries
Example smoke-build metadata:
```text
action=installer-iso
iso_volume_label=FRUIX_INSTALLER
iso_store_path=/tmp/...-fruix-installer-iso-fruix-freebsd-installer
iso_image=/tmp/...-fruix-installer-iso-fruix-freebsd-installer/installer.iso
boot_efi_image=/tmp/...-fruix-installer-iso-fruix-freebsd-installer/efiboot.img
root_image=/tmp/...-fruix-installer-iso-fruix-freebsd-installer/root.img
installer_closure_path=/tmp/...-fruix-system-fruix-freebsd-installer
target_closure_path=/tmp/...-fruix-system-fruix-freebsd
```
### Validation harness added
Added:
- `tests/system/run-phase18-installer-iso.sh`
This harness is intended to validate the full Phase 18.3 flow:
1. build installer ISO
2. boot it under QEMU/UEFI/TCG
3. install onto a second disk from inside the booted ISO environment
4. boot the installed target
## Status
Phase 18.3 implementation is now in place, with successful build-smoke validation and a dedicated end-to-end harness added.
The remaining step is full end-to-end boot/install validation of the ISO path under QEMU/UEFI/TCG and, if practical, the broader validated virtualization path.

View File

@@ -46,10 +46,12 @@
operating-system-install-spec operating-system-install-spec
operating-system-image-spec operating-system-image-spec
operating-system-installer-image-spec operating-system-installer-image-spec
operating-system-installer-iso-spec
installer-operating-system installer-operating-system
materialize-operating-system materialize-operating-system
materialize-rootfs materialize-rootfs
install-operating-system install-operating-system
materialize-bhyve-image materialize-bhyve-image
materialize-installer-image materialize-installer-image
materialize-installer-iso
default-minimal-operating-system)) default-minimal-operating-system))

View File

@@ -155,7 +155,7 @@
(define (native-build-root common) (define (native-build-root common)
(string-append "/var/tmp/fruix-freebsd-native-build-" (string-append "/var/tmp/fruix-freebsd-native-build-"
(string-hash (object->string common)))) (sha256-string (object->string common))))
(define (native-make-arguments common _build-root) (define (native-make-arguments common _build-root)
(append (append
@@ -254,7 +254,7 @@
(let* ((plan (freebsd-package-install-plan package)) (let* ((plan (freebsd-package-install-plan package))
(common (native-build-common-manifest plan)) (common (native-build-common-manifest plan))
(build-root (native-build-root common)) (build-root (native-build-root common))
(stage-root (string-append build-root "/stage-" (freebsd-package-name package) "-" (string-hash manifest))) (stage-root (string-append build-root "/stage-" (freebsd-package-name package) "-" (sha256-string manifest)))
(install-log (string-append build-root "/logs/install-" (freebsd-package-name package) ".log")) (install-log (string-append build-root "/logs/install-" (freebsd-package-name package) ".log"))
(final-stage-root (final-stage-root
(case (freebsd-package-build-system package) (case (freebsd-package-build-system package)
@@ -312,7 +312,7 @@
#:sha256 (build-plan-ref plan 'base-source-sha256 #f))) #:sha256 (build-plan-ref plan 'base-source-sha256 #f)))
(define (source-cache-key source) (define (source-cache-key source)
(string-hash (object->string (freebsd-source-spec source)))) (sha256-string (object->string (freebsd-source-spec source))))
(define (materialize-freebsd-source/cached source store-dir source-cache) (define (materialize-freebsd-source/cached source store-dir source-cache)
(let* ((key (source-cache-key source)) (let* ((key (source-cache-key source))
@@ -360,11 +360,11 @@
input-paths)) input-paths))
(effective-input-paths (filter identity effective-input-paths)) (effective-input-paths (filter identity effective-input-paths))
(manifest (package-manifest-string prepared-package effective-input-paths)) (manifest (package-manifest-string prepared-package effective-input-paths))
(cache-key (string-hash manifest)) (cache-key (sha256-string manifest))
(cached (hash-ref cache cache-key #f))) (cached (hash-ref cache cache-key #f)))
(if cached (if cached
cached cached
(let* ((hash (string-hash manifest)) (let* ((hash (sha256-string manifest))
(output-path (string-append store-dir "/" hash "-" (output-path (string-append store-dir "/" hash "-"
(freebsd-package-name prepared-package) (freebsd-package-name prepared-package)
"-" "-"
@@ -419,6 +419,8 @@
(delete-file-if-exists (string-append output-path "/lib/guile/3.0/site-ccache/shepherd/config.go")))) (delete-file-if-exists (string-append output-path "/lib/guile/3.0/site-ccache/shepherd/config.go"))))
#t) #t)
(define prefix-materializer-version "3")
(define (prefix-manifest-string source-path extra-files) (define (prefix-manifest-string source-path extra-files)
(string-append (string-append
"prefix-materializer-version=" prefix-materializer-version "\n" "prefix-materializer-version=" prefix-materializer-version "\n"
@@ -452,7 +454,7 @@
(define* (materialize-prefix source-path name version store-dir #:key (extra-files '())) (define* (materialize-prefix source-path name version store-dir #:key (extra-files '()))
(let* ((manifest (prefix-manifest-string source-path extra-files)) (let* ((manifest (prefix-manifest-string source-path extra-files))
(hash (string-hash manifest)) (hash (sha256-string manifest))
(output-path (string-append store-dir "/" hash "-" name "-" version))) (output-path (string-append store-dir "/" hash "-" name "-" version)))
(unless (file-exists? output-path) (unless (file-exists? output-path)
(mkdir-p output-path) (mkdir-p output-path)

View File

@@ -10,15 +10,18 @@
#:use-module (ice-9 hash-table) #:use-module (ice-9 hash-table)
#:use-module (srfi srfi-1) #:use-module (srfi srfi-1)
#:use-module (srfi srfi-13) #:use-module (srfi srfi-13)
#:use-module (rnrs io ports)
#:export (operating-system-install-spec #:export (operating-system-install-spec
operating-system-image-spec operating-system-image-spec
operating-system-installer-image-spec operating-system-installer-image-spec
operating-system-installer-iso-spec
installer-operating-system installer-operating-system
materialize-operating-system materialize-operating-system
materialize-rootfs materialize-rootfs
install-operating-system install-operating-system
materialize-bhyve-image materialize-bhyve-image
materialize-installer-image)) materialize-installer-image
materialize-installer-iso))
(define (same-file-contents? a b) (define (same-file-contents? a b)
(zero? (system* "cmp" "-s" a b))) (zero? (system* "cmp" "-s" a b)))
@@ -169,7 +172,7 @@
"\n") "\n")
"\nreferences=\n" "\nreferences=\n"
(string-join references "\n"))) (string-join references "\n")))
(hash (string-hash manifest)) (hash (sha256-string manifest))
(closure-path (string-append store-dir "/" hash "-fruix-system-" (closure-path (string-append store-dir "/" hash "-fruix-system-"
(operating-system-host-name os)))) (operating-system-host-name os))))
(unless (file-exists? closure-path) (unless (file-exists? closure-path)
@@ -459,9 +462,39 @@
#:serial-console serial-console)) #:serial-console serial-console))
(target-install . ,target-install-spec)))) (target-install . ,target-install-spec))))
(define* (operating-system-installer-iso-spec os
#:key
(install-target-device "/dev/vtbd1")
(installer-host-name (string-append (operating-system-host-name os)
"-installer"))
(root-size #f)
(iso-volume-label "FRUIX_INSTALLER")
(installer-root-partition-label "fruix-installer-root")
(target-efi-partition-label "efiboot")
(target-root-partition-label "fruix-root")
(serial-console "comconsole"))
(let ((target-install-spec (operating-system-install-spec os
#:target install-target-device
#:target-kind 'block-device
#:efi-size "64m"
#:root-size #f
#:disk-capacity #f
#:efi-partition-label target-efi-partition-label
#:root-partition-label target-root-partition-label
#:serial-console serial-console)))
`((installer-host-name . ,installer-host-name)
(install-target-device . ,install-target-device)
(boot-mode . uefi)
(image-format . iso9660)
(iso-volume-label . ,iso-volume-label)
(root-size . ,root-size)
(installer-root-partition-label . ,installer-root-partition-label)
(target-install . ,target-install-spec))))
(define image-builder-version "2") (define image-builder-version "2")
(define install-builder-version "1") (define install-builder-version "1")
(define installer-image-builder-version "1") (define installer-image-builder-version "1")
(define installer-iso-builder-version "1")
(define (operating-system-install-metadata-object install-spec closure-path store-items) (define (operating-system-install-metadata-object install-spec closure-path store-items)
`((install-version . ,install-builder-version) `((install-version . ,install-builder-version)
@@ -754,7 +787,7 @@
"\nstore-items=\n" "\nstore-items=\n"
(string-join store-items "\n") (string-join store-items "\n")
"\n")) "\n"))
(hash (string-hash manifest)) (hash (sha256-string manifest))
(image-store-path (string-append store-dir "/" hash "-fruix-bhyve-image-" (image-store-path (string-append store-dir "/" hash "-fruix-bhyve-image-"
(operating-system-host-name os))) (operating-system-host-name os)))
(disk-image (string-append image-store-path "/disk.img")) (disk-image (string-append image-store-path "/disk.img"))
@@ -908,7 +941,7 @@
"\ninstall-metadata=\n" "\ninstall-metadata=\n"
(object->string install-metadata) (object->string install-metadata)
"\n")) "\n"))
(hash (string-hash manifest)) (hash (sha256-string manifest))
(image-store-path (string-append store-dir "/" hash "-fruix-installer-image-" (image-store-path (string-append store-dir "/" hash "-fruix-installer-image-"
(operating-system-host-name installer-os))) (operating-system-host-name installer-os)))
(disk-image (string-append image-store-path "/disk.img")) (disk-image (string-append image-store-path "/disk.img"))
@@ -1029,3 +1062,277 @@
(store-items . ,combined-store-items) (store-items . ,combined-store-items)
(target-store-items . ,target-store-items) (target-store-items . ,target-store-items)
(installer-store-items . ,installer-store-items)))) (installer-store-items . ,installer-store-items))))
(define (resolved-path path)
(let ((target (false-if-exception (readlink path))))
(if target
(if (string-prefix? "/" target)
target
(string-append (dirname path) "/" target))
path)))
(define (copy-resolved-node source destination)
(copy-node (resolved-path source) destination))
(define (sanitize-iso-volume-label label)
(let* ((text (if (and (string? label) (not (string-null? label)))
label
"FRUIX_INSTALLER"))
(upper (string-upcase text))
(chars (map (lambda (ch)
(if (or (char-alphabetic? ch)
(char-numeric? ch)
(memv ch '(#\_ #\-)))
ch
#\_))
(string->list upper)))
(sanitized (list->string chars)))
(if (> (string-length sanitized) 32)
(substring sanitized 0 32)
sanitized)))
(define (write-installer-iso-loader-conf source-path destination)
(let* ((mode (stat:perms (stat source-path)))
(base (call-with-input-file source-path get-string-all))
(extra (string-append
"mdroot_load=\"YES\"\n"
"mdroot_type=\"md_image\"\n"
"mdroot_name=\"/boot/root.img\"\n"
"rootdev=\"ufs:/dev/md0\"\n"
"vfs.root.mountfrom=\"ufs:/dev/md0\"\n"
"vfs.root.mountfrom.options=\"rw\"\n")))
(write-file destination
(string-append base
(if (or (string-null? base)
(char=? (string-ref base (- (string-length base) 1)) #\newline))
""
"\n")
extra))
(chmod destination mode)))
(define* (make-ufs-image output-path source-root label #:key size)
(apply run-command
(append (list "makefs" "-t" "ffs" "-T" "0" "-B" "little")
(if size
(list "-s" size)
'())
(list "-o" (string-append "label=" label
",version=2,bsize=32768,fsize=4096,density=16384")
output-path
source-root))))
(define (make-efi-boot-image loader-efi output-path)
(let ((stage-root (mktemp-directory "/tmp/fruix-installer-iso-esp.XXXXXX")))
(dynamic-wind
(lambda () #t)
(lambda ()
(mkdir-p (string-append stage-root "/EFI/BOOT"))
(copy-regular-file loader-efi
(string-append stage-root "/EFI/BOOT/BOOTX64.EFI"))
(run-command "makefs" "-t" "msdos" "-T" "0"
"-o" "fat_type=12"
"-o" "sectors_per_cluster=1"
"-o" "volume_label=EFISYS"
"-s" "2048k"
output-path stage-root))
(lambda ()
(when (file-exists? stage-root)
(delete-file-recursively stage-root))))))
(define (populate-installer-iso-boot-tree installer-closure-path iso-root root-image-path)
(let ((boot-root (string-append iso-root "/boot")))
(mkdir-p (string-append boot-root "/kernel"))
(copy-resolved-node (string-append installer-closure-path "/boot/kernel/kernel")
(string-append boot-root "/kernel/kernel"))
(copy-resolved-node (string-append installer-closure-path "/boot/kernel/linker.hints")
(string-append boot-root "/kernel/linker.hints"))
(copy-resolved-node (string-append installer-closure-path "/boot/loader")
(string-append boot-root "/loader"))
(copy-resolved-node (string-append installer-closure-path "/boot/loader.efi")
(string-append boot-root "/loader.efi"))
(copy-resolved-node (string-append installer-closure-path "/boot/device.hints")
(string-append boot-root "/device.hints"))
(copy-resolved-node (string-append installer-closure-path "/boot/defaults")
(string-append boot-root "/defaults"))
(copy-resolved-node (string-append installer-closure-path "/boot/lua")
(string-append boot-root "/lua"))
(write-installer-iso-loader-conf (string-append installer-closure-path "/boot/loader.conf")
(string-append boot-root "/loader.conf"))
(copy-regular-file root-image-path
(string-append boot-root "/root.img"))))
(define* (materialize-installer-iso os
#:key
(store-dir "/frx/store")
(guile-prefix "/tmp/guile-freebsd-validate-install")
(guile-extra-prefix "/tmp/guile-gnutls-freebsd-validate-install")
(shepherd-prefix "/tmp/shepherd-freebsd-validate-install")
(install-target-device "/dev/vtbd1")
(root-size #f)
(installer-host-name (string-append (operating-system-host-name os)
"-installer"))
(installer-root-partition-label "fruix-installer-root")
(target-efi-partition-label "efiboot")
(target-root-partition-label "fruix-root")
(serial-console "comconsole")
(iso-volume-label "FRUIX_INSTALLER"))
(let* ((installer-os (installer-operating-system os
#:host-name installer-host-name
#:root-partition-label installer-root-partition-label))
(target-closure (materialize-operating-system os
#:store-dir store-dir
#:guile-prefix guile-prefix
#:guile-extra-prefix guile-extra-prefix
#:shepherd-prefix shepherd-prefix))
(installer-closure (materialize-operating-system installer-os
#:store-dir store-dir
#:guile-prefix guile-prefix
#:guile-extra-prefix guile-extra-prefix
#:shepherd-prefix shepherd-prefix))
(target-closure-path (assoc-ref target-closure 'closure-path))
(installer-closure-path (assoc-ref installer-closure 'closure-path))
(target-store-items (store-reference-closure (list target-closure-path)))
(installer-store-items (store-reference-closure (list installer-closure-path)))
(combined-store-items (delete-duplicates (append installer-store-items target-store-items)))
(sanitized-iso-volume-label (sanitize-iso-volume-label iso-volume-label))
(installer-iso-spec (operating-system-installer-iso-spec os
#:install-target-device install-target-device
#:installer-host-name installer-host-name
#:root-size root-size
#:iso-volume-label sanitized-iso-volume-label
#:installer-root-partition-label installer-root-partition-label
#:target-efi-partition-label target-efi-partition-label
#:target-root-partition-label target-root-partition-label
#:serial-console serial-console))
(target-install-spec (assoc-ref installer-iso-spec 'target-install))
(install-metadata (operating-system-install-metadata-object target-install-spec
target-closure-path
target-store-items))
(installer-plan-directory "/var/lib/fruix/installer")
(installer-state-path (string-append installer-plan-directory "/state"))
(installer-log-path "/var/log/fruix-installer.log")
(manifest (string-append
"installer-iso-builder-version=\n"
installer-iso-builder-version
"\ninstaller-iso-spec=\n"
(object->string installer-iso-spec)
"installer-closure-path=\n"
installer-closure-path
"\ntarget-closure-path=\n"
target-closure-path
"\ncombined-store-items=\n"
(string-join combined-store-items "\n")
"\ntarget-store-items=\n"
(string-join target-store-items "\n")
"\ninstall-metadata=\n"
(object->string install-metadata)
"\n"))
(hash (sha256-string manifest))
(iso-store-path (string-append store-dir "/" hash "-fruix-installer-iso-"
(operating-system-host-name installer-os)))
(iso-image (string-append iso-store-path "/installer.iso"))
(boot-efi-image (string-append iso-store-path "/efiboot.img"))
(root-image (string-append iso-store-path "/root.img")))
(unless (file-exists? iso-store-path)
(let* ((build-root (mktemp-directory "/tmp/fruix-installer-iso-build.XXXXXX"))
(installer-rootfs (string-append build-root "/installer-rootfs"))
(target-rootfs (string-append build-root "/target-rootfs"))
(image-rootfs (string-append build-root "/image-rootfs"))
(iso-root (string-append build-root "/iso-root"))
(temp-output (mktemp-directory (string-append store-dir "/.fruix-installer-iso.XXXXXX")))
(temp-iso (string-append build-root "/installer.iso"))
(temp-esp (string-append build-root "/efiboot.img"))
(temp-root (string-append build-root "/root.img"))
(plan-root (string-append image-rootfs installer-plan-directory)))
(dynamic-wind
(lambda () #t)
(lambda ()
(populate-rootfs-from-closure installer-os installer-rootfs installer-closure-path)
(populate-rootfs-from-closure os target-rootfs target-closure-path)
(copy-rootfs-for-image installer-rootfs image-rootfs)
(mkdir-p plan-root)
(mkdir-p (string-append image-rootfs "/usr/local/libexec"))
(mkdir-p (string-append image-rootfs "/usr/local/etc/rc.d"))
(mkdir-p (string-append plan-root "/target-rootfs"))
(copy-tree-contents target-rootfs (string-append plan-root "/target-rootfs"))
(copy-store-items-into-rootfs image-rootfs store-dir combined-store-items)
(write-file (string-append plan-root "/store-items")
(string-append (string-join (map path-basename target-store-items) "\n") "\n"))
(write-file (string-append plan-root "/install.scm")
(object->string install-metadata))
(copy-regular-file (string-append target-closure-path "/boot/loader.efi")
(string-append plan-root "/loader.efi"))
(write-file (string-append plan-root "/target-device")
(string-append install-target-device "\n"))
(write-file (string-append plan-root "/efi-size") "64m\n")
(write-file (string-append plan-root "/efi-partition-label")
(string-append target-efi-partition-label "\n"))
(write-file (string-append plan-root "/root-partition-label")
(string-append target-root-partition-label "\n"))
(write-file (string-append plan-root "/state") "pending\n")
(write-file (string-append image-rootfs "/usr/local/libexec/fruix-installer-run")
(render-installer-run-script store-dir installer-plan-directory))
(write-file (string-append image-rootfs "/usr/local/etc/rc.d/fruix-installer")
(render-installer-rc-script installer-plan-directory))
(chmod (string-append image-rootfs "/usr/local/libexec/fruix-installer-run") #o555)
(chmod (string-append image-rootfs "/usr/local/etc/rc.d/fruix-installer") #o555)
(make-ufs-image temp-root image-rootfs installer-root-partition-label #:size root-size)
(populate-installer-iso-boot-tree installer-closure-path iso-root temp-root)
(make-efi-boot-image (resolved-path (string-append installer-closure-path "/boot/loader.efi")) temp-esp)
(run-command "makefs" "-t" "cd9660" "-T" "0"
"-o" (string-append "bootimage=efi;" temp-esp)
"-o" "no-emul-boot"
"-o" "platformid=efi"
"-o" "rockridge"
"-o" (string-append "label=" sanitized-iso-volume-label)
temp-iso iso-root)
(mkdir-p temp-output)
(copy-regular-file temp-iso (string-append temp-output "/installer.iso"))
(copy-regular-file temp-esp (string-append temp-output "/efiboot.img"))
(copy-regular-file temp-root (string-append temp-output "/root.img"))
(write-file (string-append temp-output "/installer-iso-spec.scm")
(object->string installer-iso-spec))
(write-file (string-append temp-output "/installer-closure-path") installer-closure-path)
(write-file (string-append temp-output "/target-closure-path") target-closure-path)
(write-file (string-append temp-output "/.references")
(string-join combined-store-items "\n"))
(write-file (string-append temp-output "/.fruix-package") manifest)
(chmod temp-output #o755)
(for-each (lambda (path)
(chmod path #o644))
(list (string-append temp-output "/installer.iso")
(string-append temp-output "/efiboot.img")
(string-append temp-output "/root.img")
(string-append temp-output "/installer-iso-spec.scm")
(string-append temp-output "/installer-closure-path")
(string-append temp-output "/target-closure-path")
(string-append temp-output "/.references")
(string-append temp-output "/.fruix-package")))
(rename-file temp-output iso-store-path))
(lambda ()
(when (file-exists? build-root)
(delete-file-recursively build-root))))))
`((iso-store-path . ,iso-store-path)
(iso-image . ,iso-image)
(boot-efi-image . ,boot-efi-image)
(root-image . ,root-image)
(installer-closure-path . ,installer-closure-path)
(target-closure-path . ,target-closure-path)
(closure-path . ,installer-closure-path)
(installer-iso-spec . ,installer-iso-spec)
(install-spec . ,target-install-spec)
(installer-state-path . ,installer-state-path)
(installer-log-path . ,installer-log-path)
(install-target-device . ,install-target-device)
(host-base-stores . ,(assoc-ref target-closure 'host-base-stores))
(native-base-stores . ,(assoc-ref target-closure 'native-base-stores))
(fruix-runtime-stores . ,(assoc-ref target-closure 'fruix-runtime-stores))
(freebsd-base-file . ,(assoc-ref target-closure 'freebsd-base-file))
(freebsd-source-file . ,(assoc-ref target-closure 'freebsd-source-file))
(freebsd-source-materializations-file . ,(assoc-ref target-closure 'freebsd-source-materializations-file))
(materialized-source-stores . ,(assoc-ref target-closure 'materialized-source-stores))
(host-base-provenance-file . ,(assoc-ref target-closure 'host-base-provenance-file))
(store-layout-file . ,(assoc-ref target-closure 'store-layout-file))
(store-items . ,combined-store-items)
(target-store-items . ,target-store-items)
(installer-store-items . ,installer-store-items))))

View File

@@ -36,7 +36,7 @@
(define (ensure-git-source-cache source cache-dir) (define (ensure-git-source-cache source cache-dir)
(let* ((url (freebsd-source-url source)) (let* ((url (freebsd-source-url source))
(repo-dir (string-append cache-dir "/git/" (repo-dir (string-append cache-dir "/git/"
(string-hash (string-append "git:" url)) (sha256-string (string-append "git:" url))
".git"))) ".git")))
(mkdir-p (dirname repo-dir)) (mkdir-p (dirname repo-dir))
(unless (file-exists? repo-dir) (unless (file-exists? repo-dir)
@@ -84,7 +84,7 @@
(expected-sha256 (or (normalize-expected-sha256 source) (expected-sha256 (or (normalize-expected-sha256 source)
(error "src-txz freebsd source requires sha256 for materialization" source))) (error "src-txz freebsd source requires sha256 for materialization" source)))
(archive-path (string-append cache-dir "/archives/" (archive-path (string-append cache-dir "/archives/"
(string-hash (string-append "txz:" url)) (sha256-string (string-append "txz:" url))
"-src.txz"))) "-src.txz")))
(mkdir-p (dirname archive-path)) (mkdir-p (dirname archive-path))
(when (file-exists? archive-path) (when (file-exists? archive-path)
@@ -150,7 +150,7 @@
(effective-source (assoc-ref resolution 'effective-source)) (effective-source (assoc-ref resolution 'effective-source))
(identity (assoc-ref resolution 'identity)) (identity (assoc-ref resolution 'identity))
(manifest (freebsd-source-manifest source effective-source identity)) (manifest (freebsd-source-manifest source effective-source identity))
(hash (string-hash manifest)) (hash (sha256-string manifest))
(output-path (string-append store-dir "/" hash "-freebsd-source-" (output-path (string-append store-dir "/" hash "-freebsd-source-"
(safe-name-fragment (freebsd-source-name source)))) (safe-name-fragment (freebsd-source-name source))))
(info-file (string-append output-path "/.freebsd-source-info.scm")) (info-file (string-append output-path "/.freebsd-source-info.scm"))

View File

@@ -13,7 +13,7 @@
command-output command-output
safe-command-output safe-command-output
write-file write-file
string-hash sha256-string
file-hash file-hash
directory-entries directory-entries
path-signature path-signature
@@ -63,7 +63,7 @@
(lambda (port) (lambda (port)
(display content port)))) (display content port))))
(define (string-hash text) (define (sha256-string text)
(let* ((tmp (string-append (getenv* "TMPDIR" "/tmp") "/fruix-system-hash.txt"))) (let* ((tmp (string-append (getenv* "TMPDIR" "/tmp") "/fruix-system-hash.txt")))
(write-file tmp text) (write-file tmp text)
(command-output "sha256" "-q" tmp))) (command-output "sha256" "-q" tmp)))
@@ -110,7 +110,7 @@
(stable-lines (filter (lambda (line) (stable-lines (filter (lambda (line)
(not (string-prefix? "#" line))) (not (string-prefix? "#" line)))
(string-split mtree-output #\newline)))) (string-split mtree-output #\newline))))
(string-hash (string-join stable-lines "\n")))) (sha256-string (string-join stable-lines "\n"))))
(define (copy-regular-file source destination) (define (copy-regular-file source destination)
(let ((mode (stat:perms (stat source)))) (let ((mode (stat:perms (stat source))))

View File

@@ -20,6 +20,7 @@ System actions:\n\
build Materialize the Fruix system closure in /frx/store.\n\ build Materialize the Fruix system closure in /frx/store.\n\
image Materialize the Fruix disk image in /frx/store.\n\ image Materialize the Fruix disk image in /frx/store.\n\
installer Materialize a bootable Fruix installer image in /frx/store.\n\ installer Materialize a bootable Fruix installer image in /frx/store.\n\
installer-iso Materialize a bootable Fruix installer ISO in /frx/store.\n\
install Install the Fruix system onto --target PATH.\n\ install Install the Fruix system onto --target PATH.\n\
rootfs Materialize a rootfs tree at --rootfs DIR or ROOTFS-DIR.\n\ rootfs Materialize a rootfs tree at --rootfs DIR or ROOTFS-DIR.\n\
\n\ \n\
@@ -27,7 +28,7 @@ System options:\n\
--system NAME Scheme variable holding the operating-system object.\n\ --system NAME Scheme variable holding the operating-system object.\n\
--store DIR Store directory to use (default: /frx/store).\n\ --store DIR Store directory to use (default: /frx/store).\n\
--disk-capacity SIZE Disk capacity for 'image', 'installer', or raw-file 'install' targets.\n\ --disk-capacity SIZE Disk capacity for 'image', 'installer', or raw-file 'install' targets.\n\
--root-size SIZE Root filesystem size for 'image', 'installer', or 'install' (example: 6g).\n\ --root-size SIZE Root filesystem size for 'image', 'installer', 'installer-iso', or 'install' (example: 6g).\n\
--target PATH Install target for 'install' (raw image file or /dev/... device).\n\ --target PATH Install target for 'install' (raw image file or /dev/... device).\n\
--install-target-device DEVICE\n\ --install-target-device DEVICE\n\
Target block device used by the booted 'installer' environment.\n\ Target block device used by the booted 'installer' environment.\n\
@@ -476,6 +477,71 @@ Common options:\n\
(target_store_item_count . ,(length target-store-items)) (target_store_item_count . ,(length target-store-items))
(installer_store_item_count . ,(length installer-store-items)))))) (installer_store_item_count . ,(length installer-store-items))))))
(define (emit-system-installer-iso-metadata os-file resolved-symbol store-dir os result)
(let* ((installer-iso-spec (assoc-ref result 'installer-iso-spec))
(store-items (assoc-ref result 'store-items))
(target-store-items (assoc-ref result 'target-store-items))
(installer-store-items (assoc-ref result 'installer-store-items))
(host-base-stores (assoc-ref result 'host-base-stores))
(native-base-stores (assoc-ref result 'native-base-stores))
(fruix-runtime-stores (assoc-ref result 'fruix-runtime-stores))
(base (operating-system-freebsd-base os))
(source (freebsd-base-source base))
(host-provenance (call-with-input-file (assoc-ref result 'host-base-provenance-file) read)))
(emit-metadata
`((action . "installer-iso")
(os_file . ,os-file)
(system_variable . ,resolved-symbol)
(store_dir . ,store-dir)
(freebsd_base_name . ,(freebsd-base-name base))
(freebsd_base_version_label . ,(freebsd-base-version-label base))
(freebsd_base_release . ,(freebsd-base-release base))
(freebsd_base_branch . ,(freebsd-base-branch base))
(freebsd_base_source_root . ,(freebsd-base-source-root base))
(freebsd_base_target . ,(freebsd-base-target base))
(freebsd_base_target_arch . ,(freebsd-base-target-arch base))
(freebsd_base_kernconf . ,(freebsd-base-kernconf base))
(freebsd_base_file . ,(assoc-ref result 'freebsd-base-file))
(freebsd_source_name . ,(freebsd-source-name source))
(freebsd_source_kind . ,(freebsd-source-kind source))
(freebsd_source_url . ,(or (freebsd-source-url source) ""))
(freebsd_source_path . ,(or (freebsd-source-path source) ""))
(freebsd_source_ref . ,(or (freebsd-source-ref source) ""))
(freebsd_source_commit . ,(or (freebsd-source-commit source) ""))
(freebsd_source_sha256 . ,(or (freebsd-source-sha256 source) ""))
(freebsd_source_file . ,(assoc-ref result 'freebsd-source-file))
(freebsd_source_materializations_file . ,(assoc-ref result 'freebsd-source-materializations-file))
(materialized_source_store_count . ,(length (assoc-ref result 'materialized-source-stores)))
(materialized_source_stores . ,(string-join (assoc-ref result 'materialized-source-stores) ","))
(installer_host_name . ,(assoc-ref installer-iso-spec 'installer-host-name))
(install_target_device . ,(assoc-ref result 'install-target-device))
(iso_volume_label . ,(assoc-ref installer-iso-spec 'iso-volume-label))
(root_size . ,(assoc-ref installer-iso-spec 'root-size))
(installer_state_path . ,(assoc-ref result 'installer-state-path))
(installer_log_path . ,(assoc-ref result 'installer-log-path))
(iso_store_path . ,(assoc-ref result 'iso-store-path))
(iso_image . ,(assoc-ref result 'iso-image))
(boot_efi_image . ,(assoc-ref result 'boot-efi-image))
(root_image . ,(assoc-ref result 'root-image))
(installer_closure_path . ,(assoc-ref result 'installer-closure-path))
(target_closure_path . ,(assoc-ref result 'target-closure-path))
(host_base_store_count . ,(length host-base-stores))
(host_base_stores . ,(string-join host-base-stores ","))
(native_base_store_count . ,(length native-base-stores))
(native_base_stores . ,(string-join native-base-stores ","))
(fruix_runtime_store_count . ,(length fruix-runtime-stores))
(fruix_runtime_stores . ,(string-join fruix-runtime-stores ","))
(host_base_provenance_file . ,(assoc-ref result 'host-base-provenance-file))
(store_layout_file . ,(assoc-ref result 'store-layout-file))
(host_freebsd_version . ,(assoc-ref host-provenance 'freebsd-version-kru))
(host_uname . ,(assoc-ref host-provenance 'uname))
(usr_src_git_revision . ,(assoc-ref host-provenance 'usr-src-git-revision))
(usr_src_git_branch . ,(assoc-ref host-provenance 'usr-src-git-branch))
(usr_src_newvers_sha256 . ,(assoc-ref host-provenance 'usr-src-newvers-sha256))
(store_item_count . ,(length store-items))
(target_store_item_count . ,(length target-store-items))
(installer_store_item_count . ,(length installer-store-items))))))
(define (main argv) (define (main argv)
(let* ((parsed (parse-arguments argv)) (let* ((parsed (parse-arguments argv))
(command (assoc-ref parsed 'command)) (command (assoc-ref parsed 'command))
@@ -491,7 +557,7 @@ Common options:\n\
(rootfs-opt (assoc-ref parsed 'rootfs)) (rootfs-opt (assoc-ref parsed 'rootfs))
(system-name (assoc-ref parsed 'system-name)) (system-name (assoc-ref parsed 'system-name))
(requested-symbol (and system-name (string->symbol system-name)))) (requested-symbol (and system-name (string->symbol system-name))))
(unless (member action '("build" "image" "installer" "install" "rootfs")) (unless (member action '("build" "image" "installer" "installer-iso" "install" "rootfs"))
(error "unknown system action" action)) (error "unknown system action" action))
(let* ((os-file (match positional (let* ((os-file (match positional
((file . _) file) ((file . _) file)
@@ -561,6 +627,16 @@ Common options:\n\
#:install-target-device (or install-target-device "/dev/vtbd1") #:install-target-device (or install-target-device "/dev/vtbd1")
#:root-size (or root-size "10g") #:root-size (or root-size "10g")
#:disk-capacity disk-capacity))) #:disk-capacity disk-capacity)))
((string=? action "installer-iso")
(emit-system-installer-iso-metadata
os-file resolved-symbol store-dir os
(materialize-installer-iso os
#:store-dir store-dir
#:guile-prefix guile-prefix
#:guile-extra-prefix guile-extra-prefix
#:shepherd-prefix shepherd-prefix
#:install-target-device (or install-target-device "/dev/vtbd1")
#:root-size root-size)))
((string=? action "install") ((string=? action "install")
(unless target (unless target
(error "install action requires TARGET or --target PATH")) (error "install action requires TARGET or --target PATH"))

View File

@@ -0,0 +1,412 @@
#!/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-installer-target-operating-system.scm.in}
system_name=${SYSTEM_NAME:-phase18-target-operating-system}
store_dir=${STORE_DIR:-/frx/store}
installer_root_size=${INSTALLER_ROOT_SIZE:-}
target_disk_capacity=${TARGET_DISK_CAPACITY:-12g}
install_target_device=${INSTALL_TARGET_DEVICE:-/dev/vtbd1}
qemu_smp=${QEMU_SMP:-2}
installer_memory=${INSTALLER_MEMORY:-6144}
installer_ssh_port=${INSTALLER_SSH_PORT:-10027}
target_ssh_port=${TARGET_SSH_PORT:-10028}
base_name=${BASE_NAME:-phase18-installer-iso-target}
base_version_label=${BASE_VERSION_LABEL:-15.0-STABLE-installer-iso-target}
base_release=${BASE_RELEASE:-15.0-STABLE}
base_branch=${BASE_BRANCH:-stable/15}
source_name=${SOURCE_NAME:-stable15-installer-iso-target-source}
source_ref=${SOURCE_REF:-stable/15}
source_commit=${SOURCE_COMMIT:-332708a606f6bf0841c1d4a74c0d067f5640fe89}
declared_source_root=${DECLARED_SOURCE_ROOT:-/var/empty/fruix-unused-source-root-installer-iso-target}
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-installer-iso.XXXXXX)
cleanup=1
fi
if [ "${KEEP_WORKDIR:-0}" -eq 1 ]; then
cleanup=0
fi
target_os_file=$workdir/phase18-installer-iso-target-operating-system.scm
installer_out=$workdir/installer-iso.txt
metadata_file=$workdir/phase18-installer-iso-metadata.txt
installer_serial_log=$workdir/installer-iso-serial.log
target_serial_log=$workdir/target-serial.log
installer_qemu_pidfile=$workdir/installer-qemu.pid
target_qemu_pidfile=$workdir/target-qemu.pid
installer_uefi_vars=$workdir/installer-vars.fd
target_uefi_vars=$workdir/target-vars.fd
installer_boot_iso=$workdir/installer-boot.iso
target_image=$workdir/installed-target.img
gpart_log=$workdir/gpart-show.txt
mnt_esp=$workdir/mnt-esp
mnt_root=$workdir/mnt-root
md_unit=
cleanup_workdir() {
if [ -f "$installer_qemu_pidfile" ]; then
sudo kill "$(sudo cat "$installer_qemu_pidfile")" >/dev/null 2>&1 || true
fi
if [ -f "$target_qemu_pidfile" ]; then
sudo kill "$(sudo cat "$target_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" > "$target_os_file"
cp /usr/local/share/edk2-qemu/QEMU_UEFI_VARS-x86_64.fd "$installer_uefi_vars"
cp /usr/local/share/edk2-qemu/QEMU_UEFI_VARS-x86_64.fd "$target_uefi_vars"
truncate -s "$target_disk_capacity" "$target_image"
mkdir -p "$mnt_esp" "$mnt_root"
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}" \
"$@"
}
if [ -n "$installer_root_size" ]; then
action_env "$fruix_cmd" system installer-iso "$target_os_file" \
--system "$system_name" \
--store "$store_dir" \
--install-target-device "$install_target_device" \
--root-size "$installer_root_size" >"$installer_out"
else
action_env "$fruix_cmd" system installer-iso "$target_os_file" \
--system "$system_name" \
--store "$store_dir" \
--install-target-device "$install_target_device" >"$installer_out"
fi
field() {
sed -n "s/^$1=//p" "$installer_out" | tail -n 1
}
iso_store_path=$(field iso_store_path)
installer_iso_image=$(field iso_image)
installer_boot_efi_image=$(field boot_efi_image)
installer_root_image=$(field root_image)
installer_closure_path=$(field installer_closure_path)
target_closure_path=$(field target_closure_path)
installer_host_name=$(field installer_host_name)
install_target_device_out=$(field install_target_device)
installer_state_path=$(field installer_state_path)
installer_log_path=$(field installer_log_path)
iso_volume_label=$(field iso_volume_label)
root_size_out=$(field root_size)
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)
target_store_item_count=$(field target_store_item_count)
installer_store_item_count=$(field installer_store_item_count)
store_layout_file=$(field store_layout_file)
[ -d "$iso_store_path" ] || { echo "missing installer ISO store path: $iso_store_path" >&2; exit 1; }
[ -f "$installer_iso_image" ] || { echo "missing installer ISO image: $installer_iso_image" >&2; exit 1; }
[ -f "$installer_boot_efi_image" ] || { echo "missing installer EFI boot image: $installer_boot_efi_image" >&2; exit 1; }
[ -f "$installer_root_image" ] || { echo "missing installer root image: $installer_root_image" >&2; exit 1; }
[ -n "$installer_closure_path" ] || { echo "missing installer closure path" >&2; exit 1; }
[ -n "$target_closure_path" ] || { echo "missing target closure path" >&2; exit 1; }
[ "$install_target_device_out" = "$install_target_device" ] || { echo "unexpected install target device: $install_target_device_out" >&2; exit 1; }
[ "$installer_host_name" = fruix-freebsd-installer ] || { echo "unexpected installer host name: $installer_host_name" >&2; exit 1; }
[ -n "$iso_volume_label" ] || { echo "missing ISO volume label" >&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; }
case "$materialized_source_stores" in
/frx/store/*-freebsd-source-$source_name) : ;;
*) echo "unexpected materialized source store path: $materialized_source_stores" >&2; exit 1 ;;
esac
[ "$store_item_count" -ge "$target_store_item_count" ] || { echo "combined store item count smaller than target store item count" >&2; exit 1; }
[ "$installer_store_item_count" -ge 1 ] || { echo "expected installer store items" >&2; exit 1; }
cp "$installer_iso_image" "$installer_boot_iso"
target_closure_base=$(basename "$target_closure_path")
installer_closure_base=$(basename "$installer_closure_path")
sudo qemu-system-x86_64 \
-machine q35,accel=tcg \
-cpu max \
-m "$installer_memory" \
-smp "$qemu_smp" \
-display none \
-serial "file:$installer_serial_log" \
-monitor none \
-pidfile "$installer_qemu_pidfile" \
-daemonize \
-boot d \
-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="$installer_uefi_vars" \
-cdrom "$installer_boot_iso" \
-drive if=virtio,format=raw,file="$target_image" \
-netdev user,id=net0,hostfwd=tcp::${installer_ssh_port}-:22 \
-device virtio-net-pci,netdev=net0
installer_guest() {
ssh -p "$installer_ssh_port" -i "$root_ssh_private_key_file" \
-o BatchMode=yes \
-o StrictHostKeyChecking=no \
-o UserKnownHostsFile=/dev/null \
-o LogLevel=ERROR \
-o ConnectTimeout=5 \
root@127.0.0.1 "$@"
}
installer_ssh_reached=0
installer_state=missing
for attempt in $(jot 180 1 180); do
if installer_guest 'service sshd onestatus >/dev/null 2>&1' >/dev/null 2>&1; then
installer_ssh_reached=1
installer_state=$(installer_guest "cat '$installer_state_path' 2>/dev/null || echo missing")
[ "$installer_state" = done ] && break
fi
sleep 2
done
[ "$installer_ssh_reached" = 1 ] || { echo "installer ISO environment never became reachable over SSH" >&2; exit 1; }
[ "$installer_state" = done ] || { echo "installer ISO environment did not finish installation: $installer_state" >&2; exit 1; }
installer_run_current_system=$(installer_guest 'readlink /run/current-system')
installer_sshd_status=$(installer_guest 'service sshd onestatus >/dev/null 2>&1 && echo running || echo stopped')
installer_activate_log=$(installer_guest 'cat /var/log/fruix-activate.log 2>/dev/null || true' | tr '\n' ' ')
installer_log=$(installer_guest "cat '$installer_log_path' 2>/dev/null || true" | tr '\n' ' ')
[ "$installer_run_current_system" = "/frx/store/$installer_closure_base" ] || { echo "unexpected installer current-system target: $installer_run_current_system" >&2; exit 1; }
[ "$installer_sshd_status" = running ] || { echo "installer sshd is not running" >&2; exit 1; }
case "$installer_activate_log" in
*fruix-activate:done*) : ;;
*) echo "installer activation log does not show success" >&2; exit 1 ;;
esac
case "$installer_log" in
*fruix-installer:done*) : ;;
*) echo "installer log does not show completion" >&2; exit 1 ;;
esac
sudo kill "$(sudo cat "$installer_qemu_pidfile")" >/dev/null 2>&1 || true
rm -f "$installer_qemu_pidfile"
sleep 2
md=$(sudo mdconfig -a -t vnode -f "$target_image")
md_unit=${md#md}
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 target ESP filesystem: $esp_fstype" >&2; exit 1; }
[ "$root_fstype" = ufs ] || { echo "unexpected target 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; }
target_run_current_system=$(readlink "$mnt_root/run/current-system")
target_boot_loader=$(readlink "$mnt_root/boot/loader")
install_metadata_host=$(cat "$mnt_root/var/lib/fruix/install.scm")
[ "$target_run_current_system" = "/frx/store/$target_closure_base" ] || { echo "unexpected target /run/current-system target: $target_run_current_system" >&2; exit 1; }
[ "$target_boot_loader" = /run/current-system/boot/loader ] || { echo "unexpected target boot loader link: $target_boot_loader" >&2; exit 1; }
[ -d "$mnt_root/frx/store/$target_closure_base" ] || { echo "installed target closure missing from target root" >&2; exit 1; }
case "$install_metadata_host" in
*"$target_closure_path"*) : ;;
*) echo "installed target metadata does not record target closure path" >&2; exit 1 ;;
esac
case "$install_metadata_host" in
*"$materialized_source_stores"*) : ;;
*) echo "installed target metadata 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:$target_serial_log" \
-monitor none \
-pidfile "$target_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="$target_uefi_vars" \
-drive if=virtio,format=raw,file="$target_image" \
-netdev user,id=net0,hostfwd=tcp::${target_ssh_port}-:22 \
-device virtio-net-pci,netdev=net0
target_guest() {
ssh -p "$target_ssh_port" -i "$root_ssh_private_key_file" \
-o BatchMode=yes \
-o StrictHostKeyChecking=no \
-o UserKnownHostsFile=/dev/null \
-o LogLevel=ERROR \
-o ConnectTimeout=5 \
root@127.0.0.1 "$@"
}
for attempt in $(jot 120 1 120); do
if target_guest 'service sshd onestatus >/dev/null 2>&1' >/dev/null 2>&1; then
break
fi
sleep 2
done
target_run_current_system_guest=$(target_guest 'readlink /run/current-system')
target_shepherd_status=$(target_guest '/usr/local/etc/rc.d/fruix-shepherd onestatus >/dev/null 2>&1 && echo running || echo stopped')
target_sshd_status=$(target_guest 'service sshd onestatus >/dev/null 2>&1 && echo running || echo stopped')
target_install_metadata_guest=$(target_guest 'cat /var/lib/fruix/install.scm')
target_activate_log=$(target_guest 'cat /var/log/fruix-activate.log 2>/dev/null || true' | tr '\n' ' ')
[ "$target_run_current_system_guest" = "/frx/store/$target_closure_base" ] || { echo "unexpected booted target current-system: $target_run_current_system_guest" >&2; exit 1; }
[ "$target_shepherd_status" = running ] || { echo "fruix-shepherd is not running in booted target" >&2; exit 1; }
[ "$target_sshd_status" = running ] || { echo "sshd is not running in booted target" >&2; exit 1; }
case "$target_install_metadata_guest" in
*"$target_closure_path"*) : ;;
*) echo "booted target metadata does not record target closure path" >&2; exit 1 ;;
esac
case "$target_install_metadata_guest" in
*"$materialized_source_stores"*) : ;;
*) echo "booted target metadata does not record materialized source store" >&2; exit 1 ;;
esac
case "$target_activate_log" in
*fruix-activate:done*) : ;;
*) echo "booted target activation log does not show success" >&2; exit 1 ;;
esac
cat >"$metadata_file" <<EOF
workdir=$workdir
target_os_file=$target_os_file
installer_iso_store_path=$iso_store_path
installer_iso_image=$installer_iso_image
installer_boot_iso=$installer_boot_iso
installer_boot_efi_image=$installer_boot_efi_image
installer_root_image=$installer_root_image
installer_memory=$installer_memory
installer_root_size=$root_size_out
target_image=$target_image
target_disk_capacity=$target_disk_capacity
install_target_device=$install_target_device
qemu_smp=$qemu_smp
iso_volume_label=$iso_volume_label
freebsd_source_kind=$freebsd_source_kind_out
freebsd_source_ref=$freebsd_source_ref_out
freebsd_source_commit=$freebsd_source_commit_out
freebsd_source_file=$freebsd_source_file
freebsd_source_materializations_file=$freebsd_source_materializations_file
materialized_source_store_count=$materialized_source_store_count
materialized_source_store=$materialized_source_stores
installer_closure_path=$installer_closure_path
target_closure_path=$target_closure_path
native_base_store_count=$native_base_store_count
native_base_stores=$native_base_stores
store_item_count=$store_item_count
target_store_item_count=$target_store_item_count
installer_store_item_count=$installer_store_item_count
installer_state_path=$installer_state_path
installer_log_path=$installer_log_path
installer_state=$installer_state
installer_run_current_system=$installer_run_current_system
installer_sshd_status=$installer_sshd_status
installer_serial_log=$installer_serial_log
target_esp_fstype=$esp_fstype
target_root_fstype=$root_fstype
gpart_log=$gpart_log
target_run_current_system=$target_run_current_system_guest
target_shepherd_status=$target_shepherd_status
target_sshd_status=$target_sshd_status
target_serial_log=$target_serial_log
installer_iso_boot=ok
installer_iso_install=ok
installed_target_boot=ok
EOF
if [ -n "$metadata_target" ]; then
mkdir -p "$(dirname "$metadata_target")"
cp "$metadata_file" "$metadata_target"
fi
printf 'PASS phase18-installer-iso\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"