system: add UEFI installer ISO builder
This commit is contained in:
139
docs/reports/phase18-installer-iso-freebsd.md
Normal file
139
docs/reports/phase18-installer-iso-freebsd.md
Normal 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.
|
||||
@@ -46,10 +46,12 @@
|
||||
operating-system-install-spec
|
||||
operating-system-image-spec
|
||||
operating-system-installer-image-spec
|
||||
operating-system-installer-iso-spec
|
||||
installer-operating-system
|
||||
materialize-operating-system
|
||||
materialize-rootfs
|
||||
install-operating-system
|
||||
materialize-bhyve-image
|
||||
materialize-installer-image
|
||||
materialize-installer-iso
|
||||
default-minimal-operating-system))
|
||||
|
||||
@@ -155,7 +155,7 @@
|
||||
|
||||
(define (native-build-root common)
|
||||
(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)
|
||||
(append
|
||||
@@ -254,7 +254,7 @@
|
||||
(let* ((plan (freebsd-package-install-plan package))
|
||||
(common (native-build-common-manifest plan))
|
||||
(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"))
|
||||
(final-stage-root
|
||||
(case (freebsd-package-build-system package)
|
||||
@@ -312,7 +312,7 @@
|
||||
#:sha256 (build-plan-ref plan 'base-source-sha256 #f)))
|
||||
|
||||
(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)
|
||||
(let* ((key (source-cache-key source))
|
||||
@@ -360,11 +360,11 @@
|
||||
input-paths))
|
||||
(effective-input-paths (filter identity 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)))
|
||||
(if cached
|
||||
cached
|
||||
(let* ((hash (string-hash manifest))
|
||||
(let* ((hash (sha256-string manifest))
|
||||
(output-path (string-append store-dir "/" hash "-"
|
||||
(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"))))
|
||||
#t)
|
||||
|
||||
(define prefix-materializer-version "3")
|
||||
|
||||
(define (prefix-manifest-string source-path extra-files)
|
||||
(string-append
|
||||
"prefix-materializer-version=" prefix-materializer-version "\n"
|
||||
@@ -452,7 +454,7 @@
|
||||
|
||||
(define* (materialize-prefix source-path name version store-dir #:key (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)))
|
||||
(unless (file-exists? output-path)
|
||||
(mkdir-p output-path)
|
||||
|
||||
@@ -10,15 +10,18 @@
|
||||
#:use-module (ice-9 hash-table)
|
||||
#:use-module (srfi srfi-1)
|
||||
#:use-module (srfi srfi-13)
|
||||
#:use-module (rnrs io ports)
|
||||
#:export (operating-system-install-spec
|
||||
operating-system-image-spec
|
||||
operating-system-installer-image-spec
|
||||
operating-system-installer-iso-spec
|
||||
installer-operating-system
|
||||
materialize-operating-system
|
||||
materialize-rootfs
|
||||
install-operating-system
|
||||
materialize-bhyve-image
|
||||
materialize-installer-image))
|
||||
materialize-installer-image
|
||||
materialize-installer-iso))
|
||||
|
||||
(define (same-file-contents? a b)
|
||||
(zero? (system* "cmp" "-s" a b)))
|
||||
@@ -169,7 +172,7 @@
|
||||
"\n")
|
||||
"\nreferences=\n"
|
||||
(string-join references "\n")))
|
||||
(hash (string-hash manifest))
|
||||
(hash (sha256-string manifest))
|
||||
(closure-path (string-append store-dir "/" hash "-fruix-system-"
|
||||
(operating-system-host-name os))))
|
||||
(unless (file-exists? closure-path)
|
||||
@@ -459,9 +462,39 @@
|
||||
#:serial-console serial-console))
|
||||
(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 install-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)
|
||||
`((install-version . ,install-builder-version)
|
||||
@@ -754,7 +787,7 @@
|
||||
"\nstore-items=\n"
|
||||
(string-join store-items "\n")
|
||||
"\n"))
|
||||
(hash (string-hash manifest))
|
||||
(hash (sha256-string manifest))
|
||||
(image-store-path (string-append store-dir "/" hash "-fruix-bhyve-image-"
|
||||
(operating-system-host-name os)))
|
||||
(disk-image (string-append image-store-path "/disk.img"))
|
||||
@@ -908,7 +941,7 @@
|
||||
"\ninstall-metadata=\n"
|
||||
(object->string install-metadata)
|
||||
"\n"))
|
||||
(hash (string-hash manifest))
|
||||
(hash (sha256-string manifest))
|
||||
(image-store-path (string-append store-dir "/" hash "-fruix-installer-image-"
|
||||
(operating-system-host-name installer-os)))
|
||||
(disk-image (string-append image-store-path "/disk.img"))
|
||||
@@ -1029,3 +1062,277 @@
|
||||
(store-items . ,combined-store-items)
|
||||
(target-store-items . ,target-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))))
|
||||
|
||||
@@ -36,7 +36,7 @@
|
||||
(define (ensure-git-source-cache source cache-dir)
|
||||
(let* ((url (freebsd-source-url source))
|
||||
(repo-dir (string-append cache-dir "/git/"
|
||||
(string-hash (string-append "git:" url))
|
||||
(sha256-string (string-append "git:" url))
|
||||
".git")))
|
||||
(mkdir-p (dirname repo-dir))
|
||||
(unless (file-exists? repo-dir)
|
||||
@@ -84,7 +84,7 @@
|
||||
(expected-sha256 (or (normalize-expected-sha256 source)
|
||||
(error "src-txz freebsd source requires sha256 for materialization" source)))
|
||||
(archive-path (string-append cache-dir "/archives/"
|
||||
(string-hash (string-append "txz:" url))
|
||||
(sha256-string (string-append "txz:" url))
|
||||
"-src.txz")))
|
||||
(mkdir-p (dirname archive-path))
|
||||
(when (file-exists? archive-path)
|
||||
@@ -150,7 +150,7 @@
|
||||
(effective-source (assoc-ref resolution 'effective-source))
|
||||
(identity (assoc-ref resolution '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-"
|
||||
(safe-name-fragment (freebsd-source-name source))))
|
||||
(info-file (string-append output-path "/.freebsd-source-info.scm"))
|
||||
|
||||
@@ -13,7 +13,7 @@
|
||||
command-output
|
||||
safe-command-output
|
||||
write-file
|
||||
string-hash
|
||||
sha256-string
|
||||
file-hash
|
||||
directory-entries
|
||||
path-signature
|
||||
@@ -63,7 +63,7 @@
|
||||
(lambda (port)
|
||||
(display content port))))
|
||||
|
||||
(define (string-hash text)
|
||||
(define (sha256-string text)
|
||||
(let* ((tmp (string-append (getenv* "TMPDIR" "/tmp") "/fruix-system-hash.txt")))
|
||||
(write-file tmp text)
|
||||
(command-output "sha256" "-q" tmp)))
|
||||
@@ -110,7 +110,7 @@
|
||||
(stable-lines (filter (lambda (line)
|
||||
(not (string-prefix? "#" line)))
|
||||
(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)
|
||||
(let ((mode (stat:perms (stat source))))
|
||||
|
||||
@@ -20,6 +20,7 @@ System actions:\n\
|
||||
build Materialize the Fruix system closure 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-iso Materialize a bootable Fruix installer ISO in /frx/store.\n\
|
||||
install Install the Fruix system onto --target PATH.\n\
|
||||
rootfs Materialize a rootfs tree at --rootfs DIR or ROOTFS-DIR.\n\
|
||||
\n\
|
||||
@@ -27,7 +28,7 @@ System options:\n\
|
||||
--system NAME Scheme variable holding the operating-system object.\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\
|
||||
--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\
|
||||
--install-target-device DEVICE\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))
|
||||
(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)
|
||||
(let* ((parsed (parse-arguments argv))
|
||||
(command (assoc-ref parsed 'command))
|
||||
@@ -491,7 +557,7 @@ Common options:\n\
|
||||
(rootfs-opt (assoc-ref parsed 'rootfs))
|
||||
(system-name (assoc-ref parsed '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))
|
||||
(let* ((os-file (match positional
|
||||
((file . _) file)
|
||||
@@ -561,6 +627,16 @@ Common options:\n\
|
||||
#:install-target-device (or install-target-device "/dev/vtbd1")
|
||||
#:root-size (or root-size "10g")
|
||||
#: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")
|
||||
(unless target
|
||||
(error "install action requires TARGET or --target PATH"))
|
||||
|
||||
412
tests/system/run-phase18-installer-iso.sh
Executable file
412
tests/system/run-phase18-installer-iso.sh
Executable 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"
|
||||
Reference in New Issue
Block a user