Add non-interactive Fruix installation flow
This commit is contained in:
@@ -1,5 +1,92 @@
|
||||
# Progress
|
||||
|
||||
## 2026-04-03 — Phase 18.1 completed: Fruix now has a minimal non-interactive installation flow
|
||||
|
||||
Completed work:
|
||||
|
||||
- added in `modules/fruix/system/freebsd.scm`:
|
||||
- `operating-system-install-spec`
|
||||
- `install-operating-system`
|
||||
- refactored rootfs staging with:
|
||||
- `populate-rootfs-from-closure`
|
||||
so image generation and installation can share the same declarative rootfs assembly logic
|
||||
- the new installer now supports:
|
||||
- raw image-file targets
|
||||
- `/dev/...` block-device targets
|
||||
- for raw image targets, Fruix now performs a repeatable install flow that:
|
||||
- creates/truncates the target image
|
||||
- attaches it with `mdconfig`
|
||||
- creates GPT partitions
|
||||
- formats an EFI partition and UFS root partition
|
||||
- stages the system rootfs
|
||||
- copies the selected closure and referenced `/frx/store` items into the installed root
|
||||
- installs `loader.efi` to `EFI/BOOT/BOOTX64.EFI`
|
||||
- writes install metadata to:
|
||||
- `/var/lib/fruix/install.scm`
|
||||
- added user-facing CLI support in `scripts/fruix.scm`:
|
||||
- `fruix system install`
|
||||
- new option:
|
||||
- `--target PATH`
|
||||
- install metadata emitted by the CLI now includes:
|
||||
- target/target-kind/device fields
|
||||
- install metadata path
|
||||
- disk/root sizing
|
||||
- declared/materialized FreeBSD source metadata
|
||||
- closure/native/runtime store metadata
|
||||
- added validation artifacts:
|
||||
- `tests/system/phase18-install-operating-system.scm.in`
|
||||
- `tests/system/run-phase18-system-install.sh`
|
||||
- wrote:
|
||||
- `docs/reports/phase18-minimal-installation-flow-freebsd.md`
|
||||
|
||||
Validation:
|
||||
|
||||
- `PASS phase18-system-install`
|
||||
- regression re-check:
|
||||
- `PASS phase17-source-revisions-qemu`
|
||||
- validated a full install to a raw target image:
|
||||
- target kind:
|
||||
- `raw-file`
|
||||
- disk capacity:
|
||||
- `12g`
|
||||
- root size:
|
||||
- `10g`
|
||||
- validated target layout and boot artifacts:
|
||||
- GPT image created
|
||||
- ESP filesystem:
|
||||
- `msdosfs`
|
||||
- root filesystem:
|
||||
- `ufs`
|
||||
- `EFI/BOOT/BOOTX64.EFI` present
|
||||
- validated installed-system boot through the already-validated mode:
|
||||
- `freebsd-init+rc.d-shepherd`
|
||||
- validated after boot:
|
||||
- `sshd` running
|
||||
- `/usr/local/etc/rc.d/fruix-shepherd onestatus` reports running
|
||||
- activation completed successfully
|
||||
- `/run/current-system` points at the installed closure under `/frx/store`
|
||||
- validated source-driven installation provenance from:
|
||||
- Git ref:
|
||||
- `stable/15`
|
||||
- pinned commit:
|
||||
- `332708a606f6bf0841c1d4a74c0d067f5640fe89`
|
||||
- installed materialized source store:
|
||||
- `/frx/store/a892afb425235de71c9da38884e2ebdba5dafd3a1993f432fe7c446f5af2151f-freebsd-source-stable15-install-source`
|
||||
|
||||
Operational note:
|
||||
|
||||
- I tested the idea of defaulting local QEMU/TCG validation to 8 vCPUs
|
||||
- result:
|
||||
- under TCG, higher SMP did not help this path and regressed boot responsiveness
|
||||
- current harnesses therefore keep `QEMU_SMP` configurable but retain the conservative local default of `2`
|
||||
|
||||
Current assessment:
|
||||
|
||||
- Phase 18.1 is complete
|
||||
- Fruix can now perform a repeatable, non-interactive installation of a declarative system onto a target image or disk without relying on ad hoc manual assembly
|
||||
- the next step is Phase 18.2:
|
||||
- build a minimal Fruix-managed installer environment that can boot into an install context and run this workflow from within that environment
|
||||
|
||||
## 2026-04-03 — Phase 17.3 completed: the repo now records Fruix FreeBSD source policy explicitly
|
||||
|
||||
Completed work:
|
||||
|
||||
@@ -31,6 +31,7 @@ Completed milestones include:
|
||||
- **Side-by-side source revisions**: Fruix can now keep distinct FreeBSD source identities side by side in `/frx/store` and produce distinct native base outputs from them, even when the visible base version label is held constant.
|
||||
- **Source-driven boot validation**: Fruix can now also boot systems built from distinct declared FreeBSD source revisions while preserving those source identities in image/build metadata.
|
||||
- **Explicit source policy**: the repo now records how FreeBSD source objects are fetched, cached, identified, invalidated, and consumed by native base builds in `docs/freebsd-source-policy.md`.
|
||||
- **Minimal installation workflow**: Fruix now has a non-interactive `fruix system install` path that can partition, format, populate, and boot a target image or disk from a declarative system closure.
|
||||
- **Base upgrade story**: Fruix can now keep distinct declared base versions side by side in `/frx/store` and roll forward / back between them through the normal system deployment flow.
|
||||
|
||||
## Major pain points now behind us
|
||||
@@ -45,7 +46,7 @@ Completed milestones include:
|
||||
## Major pain points still ahead
|
||||
|
||||
- **True store-native runtime artifacts**: some historical build/install prefixes are still embedded in binaries and metadata. They are no longer required at runtime, but the local Guile/guile-extra/Shepherd build/install flow should still be moved to a genuinely store-native prefix from the start.
|
||||
- **Installation workflow**: the major remaining near-term gap is no longer source identity itself, but turning the existing build/image machinery into a first-class installation story with clearer deployment semantics.
|
||||
- **Installer environment**: Fruix now has a host-driven non-interactive install path, but it still lacks a dedicated Fruix-managed installer environment that can boot into an install context and run that workflow from within the target environment.
|
||||
- **Boot-path simplification**: Fruix now supports both the legacy `freebsd-init+rc.d-shepherd` path and the more Guix-like `shepherd-pid1` path. We still need to decide whether Shepherd PID 1 becomes the preferred/default architecture.
|
||||
- **Reduce transitional FreeBSD glue**: more of the current bootstrap/activation/runtime setup should become cleaner and less prototype-specific over time.
|
||||
- **Tooling and platform constraints**: local bhyve remains blocked by missing nested virtualization under Xen, and XO permissions still prevent creating/importing new VDIs; current validation must keep reusing the approved VM/VDI path.
|
||||
@@ -53,4 +54,4 @@ Completed milestones include:
|
||||
|
||||
## Bottom line
|
||||
|
||||
Fruix has crossed the most important threshold: it is no longer just a collection of isolated FreeBSD experiments. It can now build declarative FreeBSD system artifacts, boot them on the real target VM, reach the network, serve SSH, run Shepherd as PID 1, operate from `/frx` without depending on temporary runtime-prefix shims, build native FreeBSD base artifacts into `/frx/store`, roll forward / back between declared base versions, materialize declared FreeBSD source inputs into `/frx/store`, drive native base builds from those materialized source snapshots, boot systems from distinct source revisions, and explain the source provenance/invalidation rules explicitly. The biggest remaining work is no longer “can this boot?” but “how do those source-driven system artifacts become a real installation and deployment workflow?”
|
||||
Fruix has crossed the most important threshold: it is no longer just a collection of isolated FreeBSD experiments. It can now build declarative FreeBSD system artifacts, boot them on the real target VM, reach the network, serve SSH, run Shepherd as PID 1, operate from `/frx` without depending on temporary runtime-prefix shims, build native FreeBSD base artifacts into `/frx/store`, roll forward / back between declared base versions, materialize declared FreeBSD source inputs into `/frx/store`, drive native base builds from those materialized source snapshots, boot systems from distinct source revisions, explain the source provenance/invalidation rules explicitly, and install a declarative system onto a target image through a repeatable Fruix workflow. The biggest remaining work is no longer “can this build/install at all?” but “how does this become a fuller installer/deployment/generation story?”
|
||||
|
||||
167
docs/reports/phase18-minimal-installation-flow-freebsd.md
Normal file
167
docs/reports/phase18-minimal-installation-flow-freebsd.md
Normal file
@@ -0,0 +1,167 @@
|
||||
# Phase 18.1: minimal non-interactive Fruix installation flow on FreeBSD
|
||||
|
||||
Date: 2026-04-03
|
||||
|
||||
## Goal
|
||||
|
||||
Phase 18.1 turns Fruix's existing closure/rootfs/image machinery into a real installation workflow.
|
||||
|
||||
The goal is not a polished installer yet. The goal is a repeatable, non-interactive install path that can:
|
||||
|
||||
- take a declarative Fruix system,
|
||||
- partition and format a target disk or image,
|
||||
- populate it with the selected system closure,
|
||||
- install boot assets,
|
||||
- and leave the target bootable.
|
||||
|
||||
## Implementation
|
||||
|
||||
### New install spec and installer entry point
|
||||
|
||||
Added in `modules/fruix/system/freebsd.scm`:
|
||||
|
||||
- `operating-system-install-spec`
|
||||
- `install-operating-system`
|
||||
|
||||
The installer currently supports:
|
||||
|
||||
- raw image-file targets
|
||||
- `/dev/...` block-device targets
|
||||
|
||||
For raw image-file targets, Fruix now:
|
||||
|
||||
- creates/truncates the target image
|
||||
- attaches it with `mdconfig`
|
||||
- creates a GPT layout
|
||||
- adds:
|
||||
- an EFI partition
|
||||
- a FreeBSD UFS root partition
|
||||
- formats them with:
|
||||
- `newfs_msdos`
|
||||
- `newfs`
|
||||
- mounts them
|
||||
- stages the declarative Fruix rootfs
|
||||
- copies the closure and referenced `/frx/store` items into the installed root
|
||||
- installs `loader.efi` to `EFI/BOOT/BOOTX64.EFI`
|
||||
- writes install metadata to:
|
||||
- `/var/lib/fruix/install.scm`
|
||||
|
||||
### Rootfs staging was factored for reuse
|
||||
|
||||
Added internal helper:
|
||||
|
||||
- `populate-rootfs-from-closure`
|
||||
|
||||
This lets image generation and installation reuse the same rootfs staging logic while differing in how the final target is created.
|
||||
|
||||
### New CLI action
|
||||
|
||||
Added user-facing command support in `scripts/fruix.scm`:
|
||||
|
||||
- `fruix system install`
|
||||
|
||||
New system option:
|
||||
|
||||
- `--target PATH`
|
||||
|
||||
Install metadata now emits machine-readable fields including:
|
||||
|
||||
- `target`
|
||||
- `target_kind`
|
||||
- `target_device`
|
||||
- `esp_device`
|
||||
- `root_device`
|
||||
- `install_metadata_path`
|
||||
- `disk_capacity`
|
||||
- `root_size`
|
||||
- declared/materialized FreeBSD source metadata
|
||||
- closure/native/runtime store metadata
|
||||
|
||||
### Validation harnesses
|
||||
|
||||
Added:
|
||||
|
||||
- `tests/system/phase18-install-operating-system.scm.in`
|
||||
- `tests/system/run-phase18-system-install.sh`
|
||||
|
||||
The Phase 18 install validation uses the already-validated boot mode:
|
||||
|
||||
- `freebsd-init+rc.d-shepherd`
|
||||
|
||||
This keeps the install-flow validation focused on installation mechanics rather than on the separate Shepherd-as-PID-1 boot path.
|
||||
|
||||
## Validation
|
||||
|
||||
Passing validation:
|
||||
|
||||
- `PASS phase18-system-install`
|
||||
- regression re-check:
|
||||
- `PASS phase17-source-revisions-qemu`
|
||||
|
||||
Validated install result:
|
||||
|
||||
```text
|
||||
target_image=/tmp/fruix-phase18-install.CyrgKc/installed.img
|
||||
target_kind=raw-file
|
||||
disk_capacity=12g
|
||||
root_size=10g
|
||||
closure_path=/frx/store/ee486985797103aa5d3eeeef7f2cf066bcbd6839cd81083dbe626a594e71a703-fruix-system-fruix-freebsd
|
||||
freebsd_source_kind=git
|
||||
freebsd_source_ref=stable/15
|
||||
freebsd_source_commit=332708a606f6bf0841c1d4a74c0d067f5640fe89
|
||||
materialized_source_store=/frx/store/a892afb425235de71c9da38884e2ebdba5dafd3a1993f432fe7c446f5af2151f-freebsd-source-stable15-install-source
|
||||
native_base_store_count=3
|
||||
install_metadata_path=/var/lib/fruix/install.scm
|
||||
esp_fstype=msdosfs
|
||||
root_fstype=ufs
|
||||
shepherd_status=running
|
||||
sshd_status=running
|
||||
install_flow=non_interactive
|
||||
init_mode=freebsd-init+rc.d-shepherd
|
||||
install_target_boot=ok
|
||||
```
|
||||
|
||||
The harness verified all of the following:
|
||||
|
||||
- GPT partitioning is created on the target image
|
||||
- the installed ESP is a valid `msdosfs` filesystem
|
||||
- the installed root partition is `ufs`
|
||||
- `EFI/BOOT/BOOTX64.EFI` exists on the target
|
||||
- `/run/current-system` points at the installed closure in `/frx/store`
|
||||
- the installed closure exists under the target's `/frx/store`
|
||||
- `/var/lib/fruix/install.scm` exists and records:
|
||||
- closure path
|
||||
- store items
|
||||
- install spec
|
||||
- materialized source provenance via the referenced closure/store items
|
||||
- the installed system boots under local QEMU/UEFI/TCG
|
||||
- after boot:
|
||||
- `sshd` is running
|
||||
- `/usr/local/etc/rc.d/fruix-shepherd onestatus` reports running
|
||||
- activation completed successfully
|
||||
|
||||
## QEMU SMP note
|
||||
|
||||
I tested the idea of defaulting local QEMU validation to 8 vCPUs.
|
||||
|
||||
Result:
|
||||
|
||||
- for local `qemu-system-x86_64` under **TCG**, higher SMP did not help this validation path and in practice regressed boot responsiveness
|
||||
- the harnesses therefore keep `QEMU_SMP` configurable but retain a conservative default of `2` for TCG-based local validation
|
||||
|
||||
This keeps the validated path reliable while still allowing manual override when useful.
|
||||
|
||||
## Result
|
||||
|
||||
Phase 18.1 is complete.
|
||||
|
||||
Fruix now has a real, repeatable, non-interactive installation workflow for FreeBSD systems:
|
||||
|
||||
- declarative system input
|
||||
- native/source-driven closure output
|
||||
- partition/format/install to a target image or disk
|
||||
- bootable installed result
|
||||
|
||||
The next step is Phase 18.2:
|
||||
|
||||
- a minimal Fruix-managed installer environment that can boot into an install context and run this workflow from within that environment.
|
||||
@@ -51,9 +51,11 @@
|
||||
validate-operating-system
|
||||
materialize-freebsd-source
|
||||
operating-system-closure-spec
|
||||
operating-system-install-spec
|
||||
operating-system-image-spec
|
||||
materialize-operating-system
|
||||
materialize-rootfs
|
||||
install-operating-system
|
||||
materialize-bhyve-image
|
||||
default-minimal-operating-system))
|
||||
|
||||
@@ -1833,18 +1835,7 @@
|
||||
(mkdir-p (dirname link-name))
|
||||
(symlink target link-name))
|
||||
|
||||
(define* (materialize-rootfs os rootfs
|
||||
#: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"))
|
||||
(let* ((closure (materialize-operating-system os
|
||||
#:store-dir store-dir
|
||||
#:guile-prefix guile-prefix
|
||||
#:guile-extra-prefix guile-extra-prefix
|
||||
#:shepherd-prefix shepherd-prefix))
|
||||
(closure-path (assoc-ref closure 'closure-path)))
|
||||
(define (populate-rootfs-from-closure os rootfs closure-path)
|
||||
(when (file-exists? rootfs)
|
||||
(delete-file-recursively rootfs))
|
||||
(mkdir-p rootfs)
|
||||
@@ -1894,7 +1885,48 @@
|
||||
`((rootfs . ,rootfs)
|
||||
(closure-path . ,closure-path)
|
||||
(ready-marker . ,(operating-system-ready-marker os))
|
||||
(rc-script . ,(string-append closure-path "/usr/local/etc/rc.d/fruix-shepherd")))))
|
||||
(rc-script . ,(string-append closure-path "/usr/local/etc/rc.d/fruix-shepherd"))))
|
||||
|
||||
(define* (materialize-rootfs os rootfs
|
||||
#: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"))
|
||||
(let* ((closure (materialize-operating-system os
|
||||
#:store-dir store-dir
|
||||
#:guile-prefix guile-prefix
|
||||
#:guile-extra-prefix guile-extra-prefix
|
||||
#:shepherd-prefix shepherd-prefix))
|
||||
(closure-path (assoc-ref closure 'closure-path)))
|
||||
(populate-rootfs-from-closure os rootfs closure-path)))
|
||||
|
||||
(define* (operating-system-install-spec os
|
||||
#:key
|
||||
target
|
||||
(target-kind 'raw-file)
|
||||
(boot-mode 'uefi)
|
||||
(partition-scheme 'gpt)
|
||||
(efi-size "64m")
|
||||
(root-size #f)
|
||||
(disk-capacity #f)
|
||||
(efi-partition-label "efiboot")
|
||||
(root-partition-label "fruix-root")
|
||||
(serial-console "comconsole"))
|
||||
`((host-name . ,(operating-system-host-name os))
|
||||
(freebsd-base . ,(freebsd-base-spec (operating-system-freebsd-base os)))
|
||||
(install-mode . non-interactive)
|
||||
(target . ,target)
|
||||
(target-kind . ,target-kind)
|
||||
(boot-mode . ,boot-mode)
|
||||
(partition-scheme . ,partition-scheme)
|
||||
(efi-size . ,efi-size)
|
||||
(root-size . ,root-size)
|
||||
(disk-capacity . ,disk-capacity)
|
||||
(efi-partition-label . ,efi-partition-label)
|
||||
(root-partition-label . ,root-partition-label)
|
||||
(serial-console . ,serial-console)
|
||||
(init-mode . ,(operating-system-init-mode os))))
|
||||
|
||||
(define* (operating-system-image-spec os
|
||||
#:key
|
||||
@@ -1966,6 +1998,7 @@
|
||||
(command-output "mktemp" "-d" pattern))
|
||||
|
||||
(define image-builder-version "2")
|
||||
(define install-builder-version "1")
|
||||
|
||||
(define (resize-gpt-image image disk-capacity)
|
||||
(when disk-capacity
|
||||
@@ -1978,6 +2011,133 @@
|
||||
(lambda ()
|
||||
(run-command "mdconfig" "-d" "-u" (string-drop md 2)))))))
|
||||
|
||||
(define* (install-operating-system os
|
||||
#:key
|
||||
target
|
||||
(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")
|
||||
(efi-size "64m")
|
||||
(root-size #f)
|
||||
(disk-capacity #f)
|
||||
(efi-partition-label "efiboot")
|
||||
(root-partition-label "fruix-root")
|
||||
(serial-console "comconsole"))
|
||||
(unless (and (string? target) (not (string-null? target)))
|
||||
(error "install target must be a non-empty path" target))
|
||||
(let* ((closure (materialize-operating-system os
|
||||
#:store-dir store-dir
|
||||
#:guile-prefix guile-prefix
|
||||
#:guile-extra-prefix guile-extra-prefix
|
||||
#:shepherd-prefix shepherd-prefix))
|
||||
(closure-path (assoc-ref closure 'closure-path))
|
||||
(store-items (store-reference-closure (list closure-path)))
|
||||
(target-kind (if (string-prefix? "/dev/" target)
|
||||
'block-device
|
||||
'raw-file))
|
||||
(install-spec (operating-system-install-spec os
|
||||
#:target target
|
||||
#:target-kind target-kind
|
||||
#:efi-size efi-size
|
||||
#:root-size root-size
|
||||
#:disk-capacity disk-capacity
|
||||
#:efi-partition-label efi-partition-label
|
||||
#:root-partition-label root-partition-label
|
||||
#:serial-console serial-console))
|
||||
(build-root (mktemp-directory "/tmp/fruix-system-install.XXXXXX"))
|
||||
(rootfs (string-append build-root "/rootfs"))
|
||||
(mnt-root (string-append build-root "/mnt-root"))
|
||||
(mnt-esp (string-append build-root "/mnt-esp"))
|
||||
(install-metadata-relative-path "/var/lib/fruix/install.scm")
|
||||
(target-device #f)
|
||||
(target-md #f)
|
||||
(esp-device #f)
|
||||
(root-device #f)
|
||||
(root-mounted? #f)
|
||||
(esp-mounted? #f))
|
||||
(dynamic-wind
|
||||
(lambda () #t)
|
||||
(lambda ()
|
||||
(populate-rootfs-from-closure os rootfs closure-path)
|
||||
(mkdir-p mnt-root)
|
||||
(mkdir-p mnt-esp)
|
||||
(case target-kind
|
||||
((raw-file)
|
||||
(unless disk-capacity
|
||||
(error "raw-file install target requires --disk-capacity" target))
|
||||
(mkdir-p (dirname target))
|
||||
(delete-path-if-exists target)
|
||||
(run-command "truncate" "-s" disk-capacity target)
|
||||
(let ((md (command-output "mdconfig" "-a" "-t" "vnode" "-f" target)))
|
||||
(set! target-md md)
|
||||
(set! target-device (string-append "/dev/" md))))
|
||||
((block-device)
|
||||
(set! target-device target)))
|
||||
(system* "sh" "-c"
|
||||
(string-append "gpart destroy -F " target-device " >/dev/null 2>&1"))
|
||||
(run-command "gpart" "create" "-s" "gpt" target-device)
|
||||
(run-command "gpart" "add" "-a" "1m" "-s" efi-size
|
||||
"-t" "efi" "-l" efi-partition-label target-device)
|
||||
(if root-size
|
||||
(run-command "gpart" "add" "-a" "1m" "-s" root-size
|
||||
"-t" "freebsd-ufs" "-l" root-partition-label target-device)
|
||||
(run-command "gpart" "add" "-a" "1m"
|
||||
"-t" "freebsd-ufs" "-l" root-partition-label target-device))
|
||||
(set! esp-device (string-append target-device "p1"))
|
||||
(set! root-device (string-append target-device "p2"))
|
||||
(run-command "newfs_msdos" "-L" "EFISYS" esp-device)
|
||||
(run-command "newfs" "-U" "-L" root-partition-label root-device)
|
||||
(run-command "mount" "-t" "ufs" root-device mnt-root)
|
||||
(set! root-mounted? #t)
|
||||
(run-command "mount" "-t" "msdosfs" esp-device mnt-esp)
|
||||
(set! esp-mounted? #t)
|
||||
(copy-tree-contents rootfs mnt-root)
|
||||
(copy-store-items-into-rootfs mnt-root store-dir store-items)
|
||||
(mkdir-p (string-append mnt-esp "/EFI/BOOT"))
|
||||
(copy-regular-file (string-append closure-path "/boot/loader.efi")
|
||||
(string-append mnt-esp "/EFI/BOOT/BOOTX64.EFI"))
|
||||
(let ((install-metadata-file (string-append mnt-root install-metadata-relative-path)))
|
||||
(write-file install-metadata-file
|
||||
(object->string
|
||||
`((install-version . ,install-builder-version)
|
||||
(install-spec . ,install-spec)
|
||||
(closure-path . ,closure-path)
|
||||
(store-item-count . ,(length store-items))
|
||||
(store-items . ,store-items))))
|
||||
(chmod install-metadata-file #o644))
|
||||
(run-command "sync")
|
||||
`((target . ,target)
|
||||
(target-kind . ,target-kind)
|
||||
(target-device . ,target-device)
|
||||
(esp-device . ,esp-device)
|
||||
(root-device . ,root-device)
|
||||
(install-spec . ,install-spec)
|
||||
(install-metadata-path . ,install-metadata-relative-path)
|
||||
(closure-path . ,closure-path)
|
||||
(host-base-stores . ,(assoc-ref closure 'host-base-stores))
|
||||
(native-base-stores . ,(assoc-ref closure 'native-base-stores))
|
||||
(fruix-runtime-stores . ,(assoc-ref closure 'fruix-runtime-stores))
|
||||
(freebsd-base-file . ,(assoc-ref closure 'freebsd-base-file))
|
||||
(freebsd-source-file . ,(assoc-ref closure 'freebsd-source-file))
|
||||
(freebsd-source-materializations-file . ,(assoc-ref closure 'freebsd-source-materializations-file))
|
||||
(materialized-source-stores . ,(assoc-ref closure 'materialized-source-stores))
|
||||
(host-base-provenance-file . ,(assoc-ref closure 'host-base-provenance-file))
|
||||
(store-layout-file . ,(assoc-ref closure 'store-layout-file))
|
||||
(store-items . ,store-items)))
|
||||
(lambda ()
|
||||
(when esp-mounted?
|
||||
(system* "umount" mnt-esp)
|
||||
(set! esp-mounted? #f))
|
||||
(when root-mounted?
|
||||
(system* "umount" mnt-root)
|
||||
(set! root-mounted? #f))
|
||||
(when target-md
|
||||
(system* "mdconfig" "-d" "-u" (string-drop target-md 2))
|
||||
(set! target-md #f))
|
||||
(when (file-exists? build-root)
|
||||
(delete-file-recursively build-root))))))
|
||||
|
||||
(define* (materialize-bhyve-image os
|
||||
#:key
|
||||
(store-dir "/frx/store")
|
||||
|
||||
@@ -19,13 +19,15 @@ Commands:\n\
|
||||
System actions:\n\
|
||||
build Materialize the Fruix system closure in /frx/store.\n\
|
||||
image Materialize the Fruix disk image 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\
|
||||
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' (example: 30g).\n\
|
||||
--root-size SIZE Root filesystem size for 'image' (example: 6g).\n\
|
||||
--disk-capacity SIZE Disk capacity for 'image' or raw-file 'install' targets.\n\
|
||||
--root-size SIZE Root filesystem size for 'image' or 'install' (example: 6g).\n\
|
||||
--target PATH Install target for 'install' (raw image file or /dev/... device).\n\
|
||||
--rootfs DIR Rootfs target for 'rootfs'.\n\
|
||||
\n\
|
||||
Source actions:\n\
|
||||
@@ -123,6 +125,7 @@ Common options:\n\
|
||||
(store-dir "/frx/store")
|
||||
(disk-capacity #f)
|
||||
(root-size #f)
|
||||
(target #f)
|
||||
(rootfs #f))
|
||||
(match args
|
||||
(()
|
||||
@@ -134,33 +137,38 @@ Common options:\n\
|
||||
(store-dir . ,store-dir)
|
||||
(disk-capacity . ,disk-capacity)
|
||||
(root-size . ,root-size)
|
||||
(target . ,target)
|
||||
(rootfs . ,rootfs))))
|
||||
(("--help")
|
||||
(usage 0))
|
||||
(((? (lambda (arg) (string-prefix? "--system=" arg)) arg) . tail)
|
||||
(loop tail positional (option-value arg "--system=") store-dir disk-capacity root-size rootfs))
|
||||
(loop tail positional (option-value arg "--system=") store-dir disk-capacity root-size target rootfs))
|
||||
(("--system" value . tail)
|
||||
(loop tail positional value store-dir disk-capacity root-size rootfs))
|
||||
(loop tail positional value store-dir disk-capacity root-size target rootfs))
|
||||
(((? (lambda (arg) (string-prefix? "--store=" arg)) arg) . tail)
|
||||
(loop tail positional system-name (option-value arg "--store=") disk-capacity root-size rootfs))
|
||||
(loop tail positional system-name (option-value arg "--store=") disk-capacity root-size target rootfs))
|
||||
(("--store" value . tail)
|
||||
(loop tail positional system-name value disk-capacity root-size rootfs))
|
||||
(loop tail positional system-name value disk-capacity root-size target rootfs))
|
||||
(((? (lambda (arg) (string-prefix? "--disk-capacity=" arg)) arg) . tail)
|
||||
(loop tail positional system-name store-dir (option-value arg "--disk-capacity=") root-size rootfs))
|
||||
(loop tail positional system-name store-dir (option-value arg "--disk-capacity=") root-size target rootfs))
|
||||
(("--disk-capacity" value . tail)
|
||||
(loop tail positional system-name store-dir value root-size rootfs))
|
||||
(loop tail positional system-name store-dir value root-size target rootfs))
|
||||
(((? (lambda (arg) (string-prefix? "--root-size=" arg)) arg) . tail)
|
||||
(loop tail positional system-name store-dir disk-capacity (option-value arg "--root-size=") rootfs))
|
||||
(loop tail positional system-name store-dir disk-capacity (option-value arg "--root-size=") target rootfs))
|
||||
(("--root-size" value . tail)
|
||||
(loop tail positional system-name store-dir disk-capacity value rootfs))
|
||||
(loop tail positional system-name store-dir disk-capacity value target rootfs))
|
||||
(((? (lambda (arg) (string-prefix? "--target=" arg)) arg) . tail)
|
||||
(loop tail positional system-name store-dir disk-capacity root-size (option-value arg "--target=") rootfs))
|
||||
(("--target" value . tail)
|
||||
(loop tail positional system-name store-dir disk-capacity root-size value rootfs))
|
||||
(((? (lambda (arg) (string-prefix? "--rootfs=" arg)) arg) . tail)
|
||||
(loop tail positional system-name store-dir disk-capacity root-size (option-value arg "--rootfs=")))
|
||||
(loop tail positional system-name store-dir disk-capacity root-size target (option-value arg "--rootfs=")))
|
||||
(("--rootfs" value . tail)
|
||||
(loop tail positional system-name store-dir disk-capacity root-size value))
|
||||
(loop tail positional system-name store-dir disk-capacity root-size target value))
|
||||
(((? (lambda (arg) (string-prefix? "--" arg)) arg) . _)
|
||||
(error "unknown option" arg))
|
||||
((arg . tail)
|
||||
(loop tail (cons arg positional) system-name store-dir disk-capacity root-size rootfs)))))
|
||||
(loop tail (cons arg positional) system-name store-dir disk-capacity root-size target rootfs)))))
|
||||
|
||||
(define (parse-source-arguments action rest)
|
||||
(let loop ((args rest)
|
||||
@@ -276,6 +284,65 @@ Common options:\n\
|
||||
(generated_file_count . ,(length generated-files))
|
||||
(reference_count . ,(length references))))))
|
||||
|
||||
(define (emit-system-install-metadata os-file resolved-symbol store-dir os result)
|
||||
(let* ((install-spec (assoc-ref result 'install-spec))
|
||||
(store-items (assoc-ref result '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 . "install")
|
||||
(os_file . ,os-file)
|
||||
(system_variable . ,resolved-symbol)
|
||||
(store_dir . ,store-dir)
|
||||
(target . ,(assoc-ref result 'target))
|
||||
(target_kind . ,(assoc-ref result 'target-kind))
|
||||
(target_device . ,(assoc-ref result 'target-device))
|
||||
(esp_device . ,(assoc-ref result 'esp-device))
|
||||
(root_device . ,(assoc-ref result 'root-device))
|
||||
(install_metadata_path . ,(assoc-ref result 'install-metadata-path))
|
||||
(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) ","))
|
||||
(disk_capacity . ,(assoc-ref install-spec 'disk-capacity))
|
||||
(root_size . ,(assoc-ref install-spec 'root-size))
|
||||
(efi_size . ,(assoc-ref install-spec 'efi-size))
|
||||
(closure_path . ,(assoc-ref result '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))))))
|
||||
|
||||
(define (emit-system-image-metadata os-file resolved-symbol store-dir os result)
|
||||
(let* ((image-spec (assoc-ref result 'image-spec))
|
||||
(store-items (assoc-ref result 'store-items))
|
||||
@@ -342,14 +409,20 @@ Common options:\n\
|
||||
(let* ((positional (assoc-ref parsed 'positional))
|
||||
(disk-capacity (assoc-ref parsed 'disk-capacity))
|
||||
(root-size (assoc-ref parsed 'root-size))
|
||||
(target-opt (assoc-ref parsed 'target))
|
||||
(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" "rootfs"))
|
||||
(unless (member action '("build" "image" "install" "rootfs"))
|
||||
(error "unknown system action" action))
|
||||
(let* ((os-file (match positional
|
||||
((file . _) file)
|
||||
(() (error "missing operating-system file argument"))))
|
||||
(target (or target-opt
|
||||
(and (string=? action "install")
|
||||
(match positional
|
||||
((_ target-path) target-path)
|
||||
(_ #f)))))
|
||||
(rootfs (or rootfs-opt
|
||||
(and (string=? action "rootfs")
|
||||
(match positional
|
||||
@@ -398,6 +471,19 @@ Common options:\n\
|
||||
#:guile-extra-prefix guile-extra-prefix
|
||||
#:shepherd-prefix shepherd-prefix
|
||||
#:root-size (or root-size "256m")
|
||||
#:disk-capacity disk-capacity)))
|
||||
((string=? action "install")
|
||||
(unless target
|
||||
(error "install action requires TARGET or --target PATH"))
|
||||
(emit-system-install-metadata
|
||||
os-file resolved-symbol store-dir os
|
||||
(install-operating-system os
|
||||
#:target target
|
||||
#:store-dir store-dir
|
||||
#:guile-prefix guile-prefix
|
||||
#:guile-extra-prefix guile-extra-prefix
|
||||
#:shepherd-prefix shepherd-prefix
|
||||
#:root-size root-size
|
||||
#:disk-capacity disk-capacity))))))))))
|
||||
((string=? command "source")
|
||||
(let* ((positional (assoc-ref parsed 'positional))
|
||||
|
||||
91
tests/system/phase18-install-operating-system.scm.in
Normal file
91
tests/system/phase18-install-operating-system.scm.in
Normal file
@@ -0,0 +1,91 @@
|
||||
(use-modules (fruix system freebsd)
|
||||
(fruix packages freebsd))
|
||||
|
||||
(define phase18-source
|
||||
(freebsd-source
|
||||
#:name "__SOURCE_NAME__"
|
||||
#:kind 'git
|
||||
#:ref "__SOURCE_REF__"
|
||||
#:commit "__SOURCE_COMMIT__"))
|
||||
|
||||
(define phase18-base
|
||||
(freebsd-base
|
||||
#:name "__BASE_NAME__"
|
||||
#:version-label "__BASE_VERSION_LABEL__"
|
||||
#:release "__BASE_RELEASE__"
|
||||
#:branch "__BASE_BRANCH__"
|
||||
#:source phase18-source
|
||||
#:source-root "__DECLARED_SOURCE_ROOT__"
|
||||
#:target "amd64"
|
||||
#:target-arch "amd64"
|
||||
#:kernconf "GENERIC"))
|
||||
|
||||
(define phase18-operating-system
|
||||
(operating-system
|
||||
#:host-name "fruix-freebsd"
|
||||
#:freebsd-base phase18-base
|
||||
#:kernel (freebsd-native-kernel-for phase18-base)
|
||||
#:bootloader (freebsd-native-bootloader-for phase18-base)
|
||||
#:base-packages (freebsd-native-system-packages-for phase18-base)
|
||||
#:groups (list (user-group #:name "wheel" #:gid 0 #:system? #t)
|
||||
(user-group #:name "sshd" #:gid 22 #:system? #t)
|
||||
(user-group #:name "_dhcp" #:gid 65 #:system? #t)
|
||||
(user-group #:name "operator" #:gid 1000 #:system? #f))
|
||||
#:users (list (user-account #:name "root"
|
||||
#:uid 0
|
||||
#:group "wheel"
|
||||
#:comment "Charlie &"
|
||||
#:home "/root"
|
||||
#:shell "/bin/sh"
|
||||
#:system? #t)
|
||||
(user-account #:name "sshd"
|
||||
#:uid 22
|
||||
#:group "sshd"
|
||||
#:comment "Secure Shell Daemon"
|
||||
#:home "/var/empty"
|
||||
#:shell "/usr/sbin/nologin"
|
||||
#:system? #t)
|
||||
(user-account #:name "_dhcp"
|
||||
#:uid 65
|
||||
#:group "_dhcp"
|
||||
#:comment "dhcp programs"
|
||||
#:home "/var/empty"
|
||||
#:shell "/usr/sbin/nologin"
|
||||
#:system? #t)
|
||||
(user-account #:name "operator"
|
||||
#:uid 1000
|
||||
#:group "operator"
|
||||
#:supplementary-groups '("wheel")
|
||||
#:comment "Fruix Operator"
|
||||
#:home "/home/operator"
|
||||
#:shell "/bin/sh"
|
||||
#:system? #f))
|
||||
#:file-systems (list (file-system #:device "/dev/gpt/fruix-root"
|
||||
#:mount-point "/"
|
||||
#:type "ufs"
|
||||
#:options "rw"
|
||||
#:needed-for-boot? #t)
|
||||
(file-system #:device "devfs"
|
||||
#:mount-point "/dev"
|
||||
#:type "devfs"
|
||||
#:options "rw"
|
||||
#:needed-for-boot? #t)
|
||||
(file-system #:device "tmpfs"
|
||||
#:mount-point "/tmp"
|
||||
#:type "tmpfs"
|
||||
#:options "rw,size=64m"))
|
||||
#:services '(shepherd ready-marker sshd)
|
||||
#:loader-entries '(("autoboot_delay" . "1")
|
||||
("boot_multicons" . "YES")
|
||||
("boot_serial" . "YES")
|
||||
("console" . "comconsole,vidconsole"))
|
||||
#:rc-conf-entries '(("clear_tmp_enable" . "NO")
|
||||
("hostid_enable" . "NO")
|
||||
("sendmail_enable" . "NONE")
|
||||
("sshd_enable" . "YES")
|
||||
("ifconfig_xn0" . "SYNCDHCP")
|
||||
("ifconfig_em0" . "SYNCDHCP")
|
||||
("ifconfig_vtnet0" . "SYNCDHCP"))
|
||||
#:init-mode 'freebsd-init+rc.d-shepherd
|
||||
#:ready-marker "/var/lib/fruix/ready"
|
||||
#:root-authorized-keys '("__ROOT_AUTHORIZED_KEY__")))
|
||||
@@ -8,6 +8,7 @@ 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}
|
||||
ssh_port=${QEMU_SSH_PORT:-10022}
|
||||
qemu_smp=${QEMU_SMP:-2}
|
||||
disk_capacity=${DISK_CAPACITY:-5g}
|
||||
cleanup=0
|
||||
|
||||
@@ -75,7 +76,7 @@ sudo qemu-system-x86_64 \
|
||||
-machine q35,accel=tcg \
|
||||
-cpu max \
|
||||
-m 2048 \
|
||||
-smp 2 \
|
||||
-smp "$qemu_smp" \
|
||||
-display none \
|
||||
-serial "file:$serial_log" \
|
||||
-monitor none \
|
||||
@@ -157,6 +158,7 @@ raw_sha256=$raw_sha256
|
||||
serial_log=$serial_log
|
||||
qemu_pidfile=$qemu_pidfile
|
||||
ssh_port=$ssh_port
|
||||
qemu_smp=$qemu_smp
|
||||
ready_marker=$ready_marker
|
||||
run_current_system_target=$run_current_system_target
|
||||
pid1_command=$pid1_command
|
||||
|
||||
318
tests/system/run-phase18-system-install.sh
Executable file
318
tests/system/run-phase18-system-install.sh
Executable file
@@ -0,0 +1,318 @@
|
||||
#!/bin/sh
|
||||
set -eu
|
||||
|
||||
project_root=${PROJECT_ROOT:-$(pwd)}
|
||||
script_dir=$(CDPATH= cd -- "$(dirname "$0")" && pwd)
|
||||
fruix_cmd=$project_root/bin/fruix
|
||||
os_template=${OS_TEMPLATE:-$script_dir/phase18-install-operating-system.scm.in}
|
||||
system_name=${SYSTEM_NAME:-phase18-operating-system}
|
||||
store_dir=${STORE_DIR:-/frx/store}
|
||||
disk_capacity=${DISK_CAPACITY:-12g}
|
||||
root_size=${ROOT_SIZE:-10g}
|
||||
qemu_smp=${QEMU_SMP:-2}
|
||||
ssh_port=${QEMU_SSH_PORT:-10024}
|
||||
base_name=${BASE_NAME:-phase18-install}
|
||||
base_version_label=${BASE_VERSION_LABEL:-15.0-STABLE-install}
|
||||
base_release=${BASE_RELEASE:-15.0-STABLE}
|
||||
base_branch=${BASE_BRANCH:-stable/15}
|
||||
source_name=${SOURCE_NAME:-stable15-install-source}
|
||||
source_ref=${SOURCE_REF:-stable/15}
|
||||
source_commit=${SOURCE_COMMIT:-332708a606f6bf0841c1d4a74c0d067f5640fe89}
|
||||
declared_source_root=${DECLARED_SOURCE_ROOT:-/var/empty/fruix-unused-source-root-install}
|
||||
metadata_target=${METADATA_OUT:-}
|
||||
root_authorized_key_file=${ROOT_AUTHORIZED_KEY_FILE:-$HOME/.ssh/id_ed25519.pub}
|
||||
root_ssh_private_key_file=${ROOT_SSH_PRIVATE_KEY_FILE:-$HOME/.ssh/id_ed25519}
|
||||
|
||||
[ -x "$fruix_cmd" ] || {
|
||||
echo "fruix command is not executable: $fruix_cmd" >&2
|
||||
exit 1
|
||||
}
|
||||
[ -f "$os_template" ] || {
|
||||
echo "missing operating-system template: $os_template" >&2
|
||||
exit 1
|
||||
}
|
||||
[ -f "$root_authorized_key_file" ] || {
|
||||
echo "missing root authorized key file: $root_authorized_key_file" >&2
|
||||
exit 1
|
||||
}
|
||||
[ -f "$root_ssh_private_key_file" ] || {
|
||||
echo "missing root SSH private key file: $root_ssh_private_key_file" >&2
|
||||
exit 1
|
||||
}
|
||||
command -v qemu-system-x86_64 >/dev/null 2>&1 || {
|
||||
echo "qemu-system-x86_64 is required" >&2
|
||||
exit 1
|
||||
}
|
||||
[ -f /usr/local/share/edk2-qemu/QEMU_UEFI_CODE-x86_64.fd ] || {
|
||||
echo "missing QEMU UEFI firmware" >&2
|
||||
exit 1
|
||||
}
|
||||
|
||||
cleanup=0
|
||||
if [ -n "${WORKDIR:-}" ]; then
|
||||
workdir=$WORKDIR
|
||||
mkdir -p "$workdir"
|
||||
else
|
||||
workdir=$(mktemp -d /tmp/fruix-phase18-install.XXXXXX)
|
||||
cleanup=1
|
||||
fi
|
||||
if [ "${KEEP_WORKDIR:-0}" -eq 1 ]; then
|
||||
cleanup=0
|
||||
fi
|
||||
|
||||
phase18_os_file=$workdir/phase18-install-operating-system.scm
|
||||
install_out=$workdir/install.txt
|
||||
target_image=$workdir/installed.img
|
||||
gpart_log=$workdir/gpart-show.txt
|
||||
serial_log=$workdir/serial.log
|
||||
qemu_pidfile=$workdir/qemu.pid
|
||||
uefi_vars=$workdir/QEMU_UEFI_VARS.fd
|
||||
metadata_file=$workdir/phase18-system-install-metadata.txt
|
||||
mnt_esp=$workdir/mnt-esp
|
||||
mnt_root=$workdir/mnt-root
|
||||
md_unit=
|
||||
|
||||
cleanup_workdir() {
|
||||
if [ -f "$qemu_pidfile" ]; then
|
||||
sudo kill "$(sudo cat "$qemu_pidfile")" >/dev/null 2>&1 || true
|
||||
fi
|
||||
if [ -n "$md_unit" ]; then
|
||||
sudo umount "$mnt_esp" >/dev/null 2>&1 || true
|
||||
sudo umount "$mnt_root" >/dev/null 2>&1 || true
|
||||
sudo mdconfig -d -u "$md_unit" >/dev/null 2>&1 || true
|
||||
fi
|
||||
if [ "$cleanup" -eq 1 ]; then
|
||||
rm -rf "$workdir" 2>/dev/null || sudo rm -rf "$workdir"
|
||||
fi
|
||||
}
|
||||
trap cleanup_workdir EXIT INT TERM
|
||||
|
||||
root_authorized_key=$(tr -d '\n' < "$root_authorized_key_file")
|
||||
sed \
|
||||
-e "s|__BASE_NAME__|$base_name|g" \
|
||||
-e "s|__BASE_VERSION_LABEL__|$base_version_label|g" \
|
||||
-e "s|__BASE_RELEASE__|$base_release|g" \
|
||||
-e "s|__BASE_BRANCH__|$base_branch|g" \
|
||||
-e "s|__SOURCE_NAME__|$source_name|g" \
|
||||
-e "s|__SOURCE_REF__|$source_ref|g" \
|
||||
-e "s|__SOURCE_COMMIT__|$source_commit|g" \
|
||||
-e "s|__DECLARED_SOURCE_ROOT__|$declared_source_root|g" \
|
||||
-e "s|__ROOT_AUTHORIZED_KEY__|$root_authorized_key|g" \
|
||||
"$os_template" > "$phase18_os_file"
|
||||
|
||||
cp /usr/local/share/edk2-qemu/QEMU_UEFI_VARS-x86_64.fd "$uefi_vars"
|
||||
|
||||
action_env() {
|
||||
sudo env \
|
||||
HOME="$HOME" \
|
||||
GUILE_AUTO_COMPILE=0 \
|
||||
FRUIX_FREEBSD_BUILD_JOBS="${FRUIX_FREEBSD_BUILD_JOBS:-8}" \
|
||||
GUIX_SOURCE_DIR="${GUIX_SOURCE_DIR:-$HOME/repos/guix}" \
|
||||
GUILE_BIN="${GUILE_BIN:-/tmp/guile-freebsd-validate-install/bin/guile}" \
|
||||
GUILE_EXTRA_PREFIX="${GUILE_EXTRA_PREFIX:-/tmp/guile-gnutls-freebsd-validate-install}" \
|
||||
SHEPHERD_PREFIX="${SHEPHERD_PREFIX:-/tmp/shepherd-freebsd-validate-install}" \
|
||||
"$@"
|
||||
}
|
||||
|
||||
action_env "$fruix_cmd" system install "$phase18_os_file" \
|
||||
--system "$system_name" \
|
||||
--store "$store_dir" \
|
||||
--target "$target_image" \
|
||||
--disk-capacity "$disk_capacity" \
|
||||
--root-size "$root_size" >"$install_out"
|
||||
|
||||
field() {
|
||||
sed -n "s/^$1=//p" "$install_out" | tail -n 1
|
||||
}
|
||||
|
||||
target_out=$(field target)
|
||||
target_kind=$(field target_kind)
|
||||
target_device=$(field target_device)
|
||||
esp_device=$(field esp_device)
|
||||
root_device=$(field root_device)
|
||||
install_metadata_path=$(field install_metadata_path)
|
||||
closure_path=$(field closure_path)
|
||||
freebsd_base_name_out=$(field freebsd_base_name)
|
||||
freebsd_base_version_label_out=$(field freebsd_base_version_label)
|
||||
freebsd_base_release_out=$(field freebsd_base_release)
|
||||
freebsd_base_branch_out=$(field freebsd_base_branch)
|
||||
freebsd_base_source_root_out=$(field freebsd_base_source_root)
|
||||
freebsd_source_name_out=$(field freebsd_source_name)
|
||||
freebsd_source_kind_out=$(field freebsd_source_kind)
|
||||
freebsd_source_ref_out=$(field freebsd_source_ref)
|
||||
freebsd_source_commit_out=$(field freebsd_source_commit)
|
||||
freebsd_source_file=$(field freebsd_source_file)
|
||||
freebsd_source_materializations_file=$(field freebsd_source_materializations_file)
|
||||
materialized_source_store_count=$(field materialized_source_store_count)
|
||||
materialized_source_stores=$(field materialized_source_stores)
|
||||
host_base_store_count=$(field host_base_store_count)
|
||||
native_base_store_count=$(field native_base_store_count)
|
||||
native_base_stores=$(field native_base_stores)
|
||||
store_item_count=$(field store_item_count)
|
||||
store_layout_file=$(field store_layout_file)
|
||||
|
||||
[ "$target_out" = "$target_image" ] || { echo "unexpected target path: $target_out" >&2; exit 1; }
|
||||
[ "$target_kind" = raw-file ] || { echo "unexpected target kind: $target_kind" >&2; exit 1; }
|
||||
[ -n "$target_device" ] || { echo "missing target device" >&2; exit 1; }
|
||||
[ -n "$esp_device" ] || { echo "missing esp device" >&2; exit 1; }
|
||||
[ -n "$root_device" ] || { echo "missing root device" >&2; exit 1; }
|
||||
[ "$install_metadata_path" = /var/lib/fruix/install.scm ] || { echo "unexpected install metadata path: $install_metadata_path" >&2; exit 1; }
|
||||
[ -n "$closure_path" ] || { echo "missing closure path" >&2; exit 1; }
|
||||
[ "$freebsd_base_name_out" = "$base_name" ] || { echo "unexpected base name: $freebsd_base_name_out" >&2; exit 1; }
|
||||
[ "$freebsd_base_version_label_out" = "$base_version_label" ] || { echo "unexpected base version label: $freebsd_base_version_label_out" >&2; exit 1; }
|
||||
[ "$freebsd_base_release_out" = "$base_release" ] || { echo "unexpected base release: $freebsd_base_release_out" >&2; exit 1; }
|
||||
[ "$freebsd_base_branch_out" = "$base_branch" ] || { echo "unexpected base branch: $freebsd_base_branch_out" >&2; exit 1; }
|
||||
[ "$freebsd_base_source_root_out" = "$declared_source_root" ] || { echo "unexpected declared source root: $freebsd_base_source_root_out" >&2; exit 1; }
|
||||
[ "$freebsd_source_name_out" = "$source_name" ] || { echo "unexpected source name: $freebsd_source_name_out" >&2; exit 1; }
|
||||
[ "$freebsd_source_kind_out" = git ] || { echo "unexpected source kind: $freebsd_source_kind_out" >&2; exit 1; }
|
||||
[ "$freebsd_source_ref_out" = "$source_ref" ] || { echo "unexpected source ref: $freebsd_source_ref_out" >&2; exit 1; }
|
||||
[ "$freebsd_source_commit_out" = "$source_commit" ] || { echo "unexpected source commit: $freebsd_source_commit_out" >&2; exit 1; }
|
||||
[ "$materialized_source_store_count" = 1 ] || { echo "unexpected materialized source store count: $materialized_source_store_count" >&2; exit 1; }
|
||||
[ "$host_base_store_count" = 0 ] || { echo "expected zero host base stores, got: $host_base_store_count" >&2; exit 1; }
|
||||
[ "$native_base_store_count" = 3 ] || { echo "expected three native base stores, got: $native_base_store_count" >&2; exit 1; }
|
||||
[ -f "$freebsd_source_file" ] || { echo "missing freebsd source file: $freebsd_source_file" >&2; exit 1; }
|
||||
[ -f "$freebsd_source_materializations_file" ] || { echo "missing source materializations file: $freebsd_source_materializations_file" >&2; exit 1; }
|
||||
[ -f "$store_layout_file" ] || { echo "missing store layout file: $store_layout_file" >&2; exit 1; }
|
||||
[ -f "$target_image" ] || { echo "missing target image: $target_image" >&2; exit 1; }
|
||||
|
||||
case "$materialized_source_stores" in
|
||||
/frx/store/*-freebsd-source-$source_name) : ;;
|
||||
*) echo "unexpected materialized source store path: $materialized_source_stores" >&2; exit 1 ;;
|
||||
esac
|
||||
|
||||
closure_base=$(basename "$closure_path")
|
||||
|
||||
md=$(sudo mdconfig -a -t vnode -f "$target_image")
|
||||
md_unit=${md#md}
|
||||
sudo mkdir -p "$mnt_esp" "$mnt_root"
|
||||
sudo gpart show -lp "/dev/$md" >"$gpart_log"
|
||||
esp_fstype=$(sudo fstyp "/dev/${md}p1")
|
||||
root_fstype=$(sudo fstyp "/dev/${md}p2")
|
||||
[ "$esp_fstype" = msdosfs ] || { echo "unexpected ESP filesystem: $esp_fstype" >&2; exit 1; }
|
||||
[ "$root_fstype" = ufs ] || { echo "unexpected root filesystem: $root_fstype" >&2; exit 1; }
|
||||
|
||||
sudo mount -t msdosfs "/dev/${md}p1" "$mnt_esp"
|
||||
sudo mount -t ufs -o ro "/dev/${md}p2" "$mnt_root"
|
||||
|
||||
[ -f "$mnt_esp/EFI/BOOT/BOOTX64.EFI" ] || { echo "missing EFI boot file on installed target" >&2; exit 1; }
|
||||
run_current_system_target=$(readlink "$mnt_root/run/current-system")
|
||||
boot_loader_target=$(readlink "$mnt_root/boot/loader")
|
||||
install_metadata_host=$(cat "$mnt_root$install_metadata_path")
|
||||
[ "$run_current_system_target" = "/frx/store/$closure_base" ] || { echo "unexpected /run/current-system target: $run_current_system_target" >&2; exit 1; }
|
||||
[ "$boot_loader_target" = /run/current-system/boot/loader ] || { echo "unexpected /boot/loader target: $boot_loader_target" >&2; exit 1; }
|
||||
[ -d "$mnt_root/frx/store/$closure_base" ] || { echo "installed closure missing from target root" >&2; exit 1; }
|
||||
case "$install_metadata_host" in
|
||||
*"$closure_path"*) : ;;
|
||||
*) echo "installed metadata file does not record closure path" >&2; exit 1 ;;
|
||||
esac
|
||||
case "$install_metadata_host" in
|
||||
*"$materialized_source_stores"*) : ;;
|
||||
*) echo "installed metadata file does not record materialized source store" >&2; exit 1 ;;
|
||||
esac
|
||||
|
||||
sudo umount "$mnt_esp"
|
||||
sudo umount "$mnt_root"
|
||||
sudo mdconfig -d -u "$md_unit"
|
||||
md_unit=
|
||||
|
||||
sudo qemu-system-x86_64 \
|
||||
-machine q35,accel=tcg \
|
||||
-cpu max \
|
||||
-m 2048 \
|
||||
-smp "$qemu_smp" \
|
||||
-display none \
|
||||
-serial "file:$serial_log" \
|
||||
-monitor none \
|
||||
-pidfile "$qemu_pidfile" \
|
||||
-daemonize \
|
||||
-drive if=pflash,format=raw,readonly=on,file=/usr/local/share/edk2-qemu/QEMU_UEFI_CODE-x86_64.fd \
|
||||
-drive if=pflash,format=raw,file="$uefi_vars" \
|
||||
-drive if=virtio,format=raw,file="$target_image" \
|
||||
-netdev user,id=net0,hostfwd=tcp::${ssh_port}-:22 \
|
||||
-device virtio-net-pci,netdev=net0
|
||||
|
||||
ssh_guest() {
|
||||
ssh -p "$ssh_port" -i "$root_ssh_private_key_file" \
|
||||
-o BatchMode=yes \
|
||||
-o StrictHostKeyChecking=no \
|
||||
-o UserKnownHostsFile=/dev/null \
|
||||
-o ConnectTimeout=5 \
|
||||
root@127.0.0.1 "$@"
|
||||
}
|
||||
|
||||
for attempt in $(jot 120 1 120); do
|
||||
if ssh_guest 'service sshd onestatus >/dev/null 2>&1' >/dev/null 2>&1; then
|
||||
break
|
||||
fi
|
||||
sleep 2
|
||||
done
|
||||
|
||||
run_current_system_guest=$(ssh_guest 'readlink /run/current-system')
|
||||
shepherd_status=$(ssh_guest '/usr/local/etc/rc.d/fruix-shepherd onestatus >/dev/null 2>&1 && echo running || echo stopped')
|
||||
sshd_status=$(ssh_guest 'service sshd onestatus >/dev/null 2>&1 && echo running || echo stopped')
|
||||
install_metadata_guest=$(ssh_guest 'cat /var/lib/fruix/install.scm')
|
||||
activate_log=$(ssh_guest 'cat /var/log/fruix-activate.log 2>/dev/null || true' | tr '\n' ' ')
|
||||
|
||||
[ "$run_current_system_guest" = "/frx/store/$closure_base" ] || { echo "unexpected guest current-system target: $run_current_system_guest" >&2; exit 1; }
|
||||
[ "$shepherd_status" = running ] || { echo "fruix-shepherd rc service is not running in installed system" >&2; exit 1; }
|
||||
[ "$sshd_status" = running ] || { echo "sshd is not running in installed system" >&2; exit 1; }
|
||||
case "$install_metadata_guest" in
|
||||
*"$closure_path"*) : ;;
|
||||
*) echo "guest install metadata does not record closure path" >&2; exit 1 ;;
|
||||
esac
|
||||
case "$install_metadata_guest" in
|
||||
*"$materialized_source_stores"*) : ;;
|
||||
*) echo "guest install metadata does not record materialized source store" >&2; exit 1 ;;
|
||||
esac
|
||||
case "$activate_log" in
|
||||
*fruix-activate:done*) : ;;
|
||||
*) echo "installed system activation log does not show success" >&2; exit 1 ;;
|
||||
esac
|
||||
|
||||
cat >"$metadata_file" <<EOF
|
||||
workdir=$workdir
|
||||
phase18_os_file=$phase18_os_file
|
||||
target_image=$target_image
|
||||
target_kind=$target_kind
|
||||
disk_capacity=$disk_capacity
|
||||
root_size=$root_size
|
||||
qemu_smp=$qemu_smp
|
||||
closure_path=$closure_path
|
||||
closure_base=$closure_base
|
||||
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
|
||||
native_base_store_count=$native_base_store_count
|
||||
native_base_stores=$native_base_stores
|
||||
store_item_count=$store_item_count
|
||||
install_metadata_path=$install_metadata_path
|
||||
gpart_log=$gpart_log
|
||||
esp_fstype=$esp_fstype
|
||||
root_fstype=$root_fstype
|
||||
serial_log=$serial_log
|
||||
ssh_port=$ssh_port
|
||||
run_current_system_target=$run_current_system_guest
|
||||
shepherd_status=$shepherd_status
|
||||
sshd_status=$sshd_status
|
||||
install_flow=non_interactive
|
||||
init_mode=freebsd-init+rc.d-shepherd
|
||||
install_target_boot=ok
|
||||
EOF
|
||||
|
||||
if [ -n "$metadata_target" ]; then
|
||||
mkdir -p "$(dirname "$metadata_target")"
|
||||
cp "$metadata_file" "$metadata_target"
|
||||
fi
|
||||
|
||||
printf 'PASS phase18-system-install\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