Compare commits
5 Commits
ebe064a652
...
b3b1ba2489
| Author | SHA1 | Date | |
|---|---|---|---|
| b3b1ba2489 | |||
| e86f74af97 | |||
| 43c155bb9f | |||
| 604ad82f4f | |||
| 1970c5c181 |
1
.gitignore
vendored
Normal file
1
.gitignore
vendored
Normal file
@@ -0,0 +1 @@
|
||||
/TODO.md
|
||||
225
docs/GUIX_DIFFERENCES.md
Normal file
225
docs/GUIX_DIFFERENCES.md
Normal file
@@ -0,0 +1,225 @@
|
||||
# Fruix differences for Guix sysadmins
|
||||
|
||||
Date: 2026-04-04
|
||||
|
||||
This document is aimed at operators who already know Guix well and want a quick map of where Fruix behaves similarly and where it intentionally differs.
|
||||
|
||||
The short version is:
|
||||
|
||||
- Fruix is strongly **Guix-inspired**
|
||||
- it tries to preserve the important semantic properties
|
||||
- but it does **not** copy Guix mechanically where FreeBSD or Fruix-specific concerns make a different representation clearer
|
||||
|
||||
## Big picture
|
||||
|
||||
Fruix keeps the core Guix ideas you would expect:
|
||||
|
||||
- declarative inputs
|
||||
- content-addressed store paths
|
||||
- immutable build outputs
|
||||
- rollback-friendly deployment identity
|
||||
- provenance tied to the deployed closure rather than mutable in-place state
|
||||
|
||||
But Fruix differs in at least four major ways today:
|
||||
|
||||
1. it targets **FreeBSD**, not GNU/Linux
|
||||
2. its system frontend is currently smaller:
|
||||
- `fruix system build|rootfs|image|install|installer|installer-iso`
|
||||
3. it treats **FreeBSD source provenance** as an explicit deployment concern
|
||||
4. its installed-system generation model is still earlier-stage than Guix's mature system-generation workflow
|
||||
|
||||
## Similarities to Guix
|
||||
|
||||
If you know Guix System, these Fruix properties should feel familiar.
|
||||
|
||||
### Immutable deployment identity
|
||||
|
||||
A deployed Fruix system is identified primarily by its closure path in `/frx/store`, not by mutable files under `/etc` or `/usr/local`.
|
||||
|
||||
### `/run/current-system`
|
||||
|
||||
Fruix keeps the Guix-like runtime convention:
|
||||
|
||||
- `/run/current-system`
|
||||
|
||||
That path remains the active runtime boundary used by activation and service wiring.
|
||||
|
||||
### Rollback-friendly semantics
|
||||
|
||||
Fruix avoids in-place mutation of an older deployed closure.
|
||||
|
||||
The validated rollback story today is:
|
||||
|
||||
- keep the earlier declaration
|
||||
- rebuild or rematerialize it
|
||||
- boot or redeploy that earlier closure again
|
||||
|
||||
That is Guix-like in spirit even though Fruix does not yet expose the same installed-system rollback command surface.
|
||||
|
||||
### Generation-style metadata and roots
|
||||
|
||||
Fruix now records explicit installed-system generation state under:
|
||||
|
||||
- `/var/lib/fruix/system`
|
||||
|
||||
and explicit retention roots under:
|
||||
|
||||
- `/frx/var/fruix/gcroots`
|
||||
|
||||
This preserves the basic Guix idea that deployment state and reachability should be represented explicitly rather than inferred from whatever happens to be on disk.
|
||||
|
||||
## Important differences from Guix
|
||||
|
||||
## 1. Fruix does not mirror Guix's on-disk generation layout 1:1
|
||||
|
||||
This is intentional.
|
||||
|
||||
Guix heavily reuses its profile-generation model and represents a lot of meaning through symlink structure such as profile links and system generation links.
|
||||
|
||||
Fruix keeps the **semantics** but uses a more explicit metadata-oriented layout for installed systems.
|
||||
|
||||
Current Fruix layout:
|
||||
|
||||
```text
|
||||
/var/lib/fruix/system/
|
||||
current -> generations/1
|
||||
current-generation
|
||||
generations/
|
||||
1/
|
||||
closure -> /frx/store/...-fruix-system-...
|
||||
metadata.scm
|
||||
provenance.scm
|
||||
install.scm
|
||||
```
|
||||
|
||||
Why Fruix does this:
|
||||
|
||||
- it makes deployment state easier to inspect directly
|
||||
- it gives FreeBSD-specific install and provenance details a clearer home
|
||||
- it keeps room for richer operator tooling later without losing the Guix properties
|
||||
|
||||
So the rule of thumb is:
|
||||
|
||||
- **same semantics as Guix where practical**
|
||||
- **not necessarily the same directory names or symlink vocabulary**
|
||||
|
||||
## 2. Fruix currently keeps `/run/current-system` simple
|
||||
|
||||
Even though Fruix now records explicit generation metadata under `/var/lib/fruix/system`, it still keeps:
|
||||
|
||||
- `/run/current-system -> /frx/store/...`
|
||||
|
||||
rather than making `/run/current-system` point through a generation directory first.
|
||||
|
||||
This was chosen to preserve compatibility with the already-validated activation and runtime model while adding explicit generation metadata separately.
|
||||
|
||||
## 3. Fruix treats source provenance more explicitly
|
||||
|
||||
Guix sysadmins are used to derivation/store provenance. Fruix adds an extra emphasis because its current FreeBSD story depends on explicit source selection and materialization.
|
||||
|
||||
Fruix routinely records:
|
||||
|
||||
- declared FreeBSD source object metadata
|
||||
- materialized source store paths
|
||||
- source materialization metadata files
|
||||
- closure-level store layout metadata
|
||||
- install metadata on the target system
|
||||
|
||||
This is more prominent in Fruix than most Guix system docs because FreeBSD base/source identity has been an active design concern for this project.
|
||||
|
||||
## 4. Fruix has installer-media workflows as first-class system actions
|
||||
|
||||
Guix has installation media and image workflows, but Fruix's current system frontend makes these especially explicit because they are still part of the active architectural bring-up story.
|
||||
|
||||
Current Fruix actions include:
|
||||
|
||||
- `fruix system install`
|
||||
- `fruix system installer`
|
||||
- `fruix system installer-iso`
|
||||
|
||||
That is a little more deployment-medium-oriented than the mental model many Guix users start with.
|
||||
|
||||
## 5. Device naming is more environment-sensitive than Guix users may expect
|
||||
|
||||
Because Fruix is on FreeBSD, the install target device naming is not the same as on Linux.
|
||||
|
||||
Validated examples:
|
||||
|
||||
- installer disk-image path under QEMU:
|
||||
- `/dev/vtbd1`
|
||||
- installer ISO path under QEMU:
|
||||
- `/dev/vtbd0`
|
||||
- installer ISO path under XCP-ng:
|
||||
- `/dev/ada0`
|
||||
|
||||
So compared with Guix-on-Linux intuition, Fruix operators should be more explicit about target-device selection during installation and installer-media validation.
|
||||
|
||||
## 6. Fruix does not yet have Guix-equivalent installed-system generation commands
|
||||
|
||||
This is the biggest current operational gap.
|
||||
|
||||
Fruix does **not** yet provide a mature equivalent of the familiar Guix System operator flow around in-place generation switching and rollback commands.
|
||||
|
||||
Today, Fruix rollback is mostly:
|
||||
|
||||
- declaration-driven
|
||||
- rebuild/redeploy based
|
||||
|
||||
rather than:
|
||||
|
||||
- switch current system generation in place through a dedicated command
|
||||
|
||||
So if you come from Guix, assume that Fruix currently has:
|
||||
|
||||
- strong closure/store semantics
|
||||
- explicit install artifacts
|
||||
- explicit generation metadata roots
|
||||
- but a less mature installed-system generation UX
|
||||
|
||||
## Where Fruix is intentionally trying to improve on Guix's representation
|
||||
|
||||
Fruix is not trying to improve on Guix's core semantics. Guix already got those right.
|
||||
|
||||
Where Fruix is intentionally experimenting is mostly the **representation layer**:
|
||||
|
||||
- make generation state more legible to operators
|
||||
- make provenance more visible without needing to reconstruct it mentally from symlink layout alone
|
||||
- separate:
|
||||
- runtime entry point (`/run/current-system`)
|
||||
- installed deployment state (`/var/lib/fruix/system`)
|
||||
- retention roots (`/frx/var/fruix/gcroots`)
|
||||
|
||||
That is why Fruix currently prefers a small per-generation metadata directory instead of only a bare generation link.
|
||||
|
||||
## Practical operator advice for Guix users
|
||||
|
||||
If you are already comfortable with Guix, the safest Fruix mental model today is:
|
||||
|
||||
1. think in terms of immutable closures and declarations first
|
||||
2. use `fruix system build` as the canonical starting point
|
||||
3. treat image/install/installer/installer-iso as deployment materializers built from the same declaration
|
||||
4. identify a deployment by:
|
||||
- closure path
|
||||
- source provenance metadata
|
||||
- install metadata
|
||||
5. think of rollback today as:
|
||||
- “redeploy the earlier declaration again”
|
||||
rather than:
|
||||
- “switch to an already-managed previous generation in place”
|
||||
|
||||
## Status summary
|
||||
|
||||
Today Fruix is closest to Guix in:
|
||||
|
||||
- store and closure semantics
|
||||
- declarative deployment identity
|
||||
- rollback-friendly immutability
|
||||
- `/run/current-system` runtime model
|
||||
|
||||
It differs most from Guix in:
|
||||
|
||||
- FreeBSD platform details
|
||||
- source-provenance emphasis
|
||||
- installer-medium-oriented workflows
|
||||
- generation-layout representation
|
||||
- and the still-maturing installed-system generation command surface
|
||||
@@ -24,6 +24,12 @@ Fruix currently has:
|
||||
- `fruix system install`
|
||||
- a bootable Fruix-managed installer environment:
|
||||
- `fruix system installer`
|
||||
- a bootable Fruix-managed installer ISO:
|
||||
- `fruix system installer-iso`
|
||||
- an explicit installed-system generation layout under:
|
||||
- `/var/lib/fruix/system`
|
||||
- explicit installed-system retention roots under:
|
||||
- `/frx/var/fruix/gcroots`
|
||||
|
||||
Validated boot modes still are:
|
||||
|
||||
@@ -36,45 +42,38 @@ The validated Phase 18 installation work currently uses:
|
||||
|
||||
## Latest completed achievement
|
||||
|
||||
### 2026-04-04 — Phase 18.2 completed
|
||||
### 2026-04-04 — Phase 19.2 completed
|
||||
|
||||
Fruix now boots a minimal installer environment and installs a target system from inside it.
|
||||
Fruix now records an explicit installed-system generation layout and retention-root model instead of relying mainly on harness knowledge.
|
||||
|
||||
Highlights:
|
||||
|
||||
- added in `modules/fruix/system/freebsd.scm`:
|
||||
- `installer-operating-system`
|
||||
- `operating-system-installer-image-spec`
|
||||
- `materialize-installer-image`
|
||||
- added CLI support in `scripts/fruix.scm`:
|
||||
- `fruix system installer`
|
||||
- `--install-target-device DEVICE`
|
||||
- the installer image now carries:
|
||||
- its own installer closure
|
||||
- the selected target closure
|
||||
- the target store closure
|
||||
- a staged target rootfs payload
|
||||
- in-guest installer state/log/scripts
|
||||
- validated workflow:
|
||||
- boot installer image in QEMU/UEFI/TCG
|
||||
- reach installer over SSH
|
||||
- install target system onto second disk from inside the guest
|
||||
- boot the installed target successfully
|
||||
- added explicit installed-system generation layout under:
|
||||
- `/var/lib/fruix/system`
|
||||
- added explicit installed-system retention roots under:
|
||||
- `/frx/var/fruix/gcroots`
|
||||
- installed targets now record a first-generation deployment directory containing:
|
||||
- `closure`
|
||||
- `metadata.scm`
|
||||
- `provenance.scm`
|
||||
- `install.scm`
|
||||
- `/run/current-system` remains the runtime boundary and still points directly at the active closure path
|
||||
- added Guix-oriented operator notes in:
|
||||
- `docs/GUIX_DIFFERENCES.md`
|
||||
- updated deployment workflow documentation to reflect the new explicit generation model
|
||||
|
||||
Validation:
|
||||
|
||||
- `PASS phase18-installer-environment`
|
||||
- regression re-checks:
|
||||
- `PASS phase18-system-install`
|
||||
- `PASS phase17-source-revisions-qemu`
|
||||
- `PASS phase19-generation-layout-qemu`
|
||||
- regression re-check:
|
||||
- `PASS phase18-installer-iso`
|
||||
|
||||
Report:
|
||||
Reports:
|
||||
|
||||
- `docs/reports/phase18-installer-environment-freebsd.md`
|
||||
|
||||
Commit:
|
||||
|
||||
- `1d00907` — `Add Fruix bootable installer environment`
|
||||
- `docs/system-deployment-workflow.md`
|
||||
- `docs/GUIX_DIFFERENCES.md`
|
||||
- `docs/reports/phase19-deployment-workflow-freebsd.md`
|
||||
- `docs/reports/phase19-generation-layout-freebsd.md`
|
||||
|
||||
## Recent major milestones
|
||||
|
||||
@@ -100,6 +99,6 @@ Commit:
|
||||
|
||||
Per `docs/PLAN_4.md`, the next planned step is:
|
||||
|
||||
- **Phase 18.3** — produce a bootable UEFI installer ISO
|
||||
- **Phase 19.3** — validate installed-system rollback through the intended operator-facing workflow
|
||||
|
||||
That should build on the now-validated installer environment rather than replacing it.
|
||||
Phase 19.2 is now complete: Fruix has an explicit installed-system generation layout and retention-root model on FreeBSD.
|
||||
|
||||
277
docs/reports/phase18-installer-iso-freebsd.md
Normal file
277
docs/reports/phase18-installer-iso-freebsd.md
Normal file
@@ -0,0 +1,277 @@
|
||||
# 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="mfs_root"`
|
||||
- `mdroot_name="/boot/root.img"`
|
||||
- `vfs.root.mountfrom="ufs:/dev/md0"`
|
||||
- `vfs.root.mountfrom.options="rw"`
|
||||
|
||||
A practical loader detail surfaced during validation:
|
||||
|
||||
- setting `rootdev` or `currdev` to `md0:` in the ISO loader path is wrong for this loader configuration and caused an early EFI-loader crash before kernel handoff
|
||||
- the reliable ISO path is to let loader keep its current device on the CD media, preload `/boot/root.img`, and pass only `vfs.root.mountfrom=ufs:/dev/md0`
|
||||
|
||||
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 runtime store closure needed for installation/boot
|
||||
- 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`
|
||||
|
||||
## Validation
|
||||
|
||||
### 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
|
||||
```
|
||||
|
||||
### End-to-end harness validation
|
||||
|
||||
Added:
|
||||
|
||||
- `tests/system/run-phase18-installer-iso.sh`
|
||||
|
||||
This harness validates the full Phase 18.3 flow:
|
||||
|
||||
1. build installer ISO
|
||||
2. boot it under QEMU/UEFI/TCG
|
||||
3. install onto a target disk from inside the booted ISO environment
|
||||
4. boot the installed target
|
||||
|
||||
Passing validation:
|
||||
|
||||
- `PASS phase18-installer-iso`
|
||||
|
||||
Validated result summary:
|
||||
|
||||
```text
|
||||
installer_iso_store_path=/frx/store/...-fruix-installer-iso-fruix-freebsd-installer
|
||||
installer_iso_image=/frx/store/...-fruix-installer-iso-fruix-freebsd-installer/installer.iso
|
||||
installer_boot_efi_image=/frx/store/...-fruix-installer-iso-fruix-freebsd-installer/efiboot.img
|
||||
installer_root_image=/frx/store/...-fruix-installer-iso-fruix-freebsd-installer/root.img
|
||||
install_target_device=/dev/vtbd0
|
||||
freebsd_source_kind=git
|
||||
freebsd_source_ref=stable/15
|
||||
freebsd_source_commit=332708a606f6bf0841c1d4a74c0d067f5640fe89
|
||||
materialized_source_store=/frx/store/459499e0eb29f4c73ad455060dd2502d21fb56f205c0a676831cf723b3a0c378-freebsd-source-stable15-installer-iso-target-source
|
||||
installer_state=done
|
||||
installer_sshd_status=running
|
||||
target_esp_fstype=msdosfs
|
||||
target_root_fstype=ufs
|
||||
target_shepherd_status=running
|
||||
target_sshd_status=running
|
||||
installer_iso_boot=ok
|
||||
installer_iso_install=ok
|
||||
installed_target_boot=ok
|
||||
```
|
||||
|
||||
Notable QEMU-specific ISO validation detail:
|
||||
|
||||
- unlike the disk-image-style installer environment from Phase 18.2, the ISO boots from `cd0`, so the target virtio disk appears as:
|
||||
- `/dev/vtbd0`
|
||||
- the earlier installer-environment default:
|
||||
- `/dev/vtbd1`
|
||||
remains correct for the disk-image installer, but not for the ISO path
|
||||
|
||||
The harness verified all of the following:
|
||||
|
||||
1. `fruix system installer-iso` produces a bootable ISO artifact in `/frx/store`
|
||||
2. the ISO boots successfully under QEMU/UEFI/TCG
|
||||
3. the booted installer ISO environment becomes reachable over SSH
|
||||
4. `/run/current-system` inside the installer ISO points at the installer closure
|
||||
5. the installer rc.d job reaches:
|
||||
- `state=done`
|
||||
6. the installer log records:
|
||||
- `fruix-installer:done`
|
||||
7. the installed target disk contains:
|
||||
- GPT partitioning
|
||||
- EFI filesystem: `msdosfs`
|
||||
- root filesystem: `ufs`
|
||||
- `EFI/BOOT/BOOTX64.EFI`
|
||||
- `/var/lib/fruix/install.scm`
|
||||
8. the installed target then boots successfully as its own Fruix system under QEMU/UEFI/TCG
|
||||
9. after target boot:
|
||||
- `/run/current-system` points at the target closure
|
||||
- shepherd is running
|
||||
- `sshd` is running
|
||||
- activation completed successfully
|
||||
|
||||
### Real XCP-ng validation
|
||||
|
||||
Added:
|
||||
|
||||
- `tests/system/run-phase18-installer-iso-xcpng.sh`
|
||||
|
||||
This harness validates the same installer-iso workflow on the approved real XCP-ng path:
|
||||
|
||||
- VM: `90490f2e-e8fc-4b7a-388e-5c26f0157289`
|
||||
- ISO SR: `537a6219-8452-7cb5-8d56-5eed6910c7a2`
|
||||
- target VDIs:
|
||||
- `0f1f90d3-48ca-4fa2-91d8-fc6339b95743`
|
||||
- `7061d761-3639-4bec-87f7-2ba1af924eaa`
|
||||
|
||||
Because the current `xo-cli disk.import @=/path/to.iso` path returned an HTTP 500 error in this environment, the harness imports the ISO into the ISO SR via a temporary local HTTP URL, then inserts the resulting ISO VDI into the VM's CD drive.
|
||||
|
||||
Passing validation:
|
||||
|
||||
- `PASS phase18-installer-iso-xcpng`
|
||||
|
||||
Validated result summary:
|
||||
|
||||
```text
|
||||
vm_id=90490f2e-e8fc-4b7a-388e-5c26f0157289
|
||||
iso_id=<temporary-imported-iso-vdi>
|
||||
guest_ip=192.168.213.62
|
||||
installer_state=done
|
||||
installer_target_device=/dev/ada0
|
||||
kern_disks=cd0 ada1 ada0
|
||||
installer_run_current_system=/frx/store/16969e825dbb65b5c27180030d4a7d98821893460fb3dccdc863ff6156ed61e0-fruix-system-fruix-freebsd-installer
|
||||
installer_sshd_status=running
|
||||
target_run_current_system=/frx/store/a98d3af6a1afbc4a927d47cea6458d5a70747b051ed994e5d9ff1ae79c4f2b42-fruix-system-fruix-freebsd
|
||||
target_sshd_status=running
|
||||
target_shepherd_status=running
|
||||
```
|
||||
|
||||
Important XCP-ng-specific details:
|
||||
|
||||
- the installer ISO still boots from:
|
||||
- `cd0`
|
||||
- on this Xen HVM path, the primary target disk is exposed through Xen block front as `xbd0` and appears to FreeBSD as:
|
||||
- `/dev/ada0`
|
||||
- therefore the XCP-ng installer-iso path must target:
|
||||
- `/dev/ada0`
|
||||
rather than QEMU's:
|
||||
- `/dev/vtbd0`
|
||||
- the visible EFI console can appear to stop at:
|
||||
- `console vidconsole is unavailable`
|
||||
but boot still continues and the installer becomes reachable over SSH; that message was not the actual failure mode on XCP-ng
|
||||
|
||||
The harness verified all of the following on the real VM path:
|
||||
|
||||
1. `fruix system installer-iso` builds a bootable ISO with `--install-target-device /dev/ada0`
|
||||
2. the ISO can be imported into the operator-approved ISO SR and attached to the approved VM
|
||||
3. the VM boots the Fruix installer ISO successfully under UEFI
|
||||
4. the installer environment becomes reachable over SSH
|
||||
5. inside the installer guest:
|
||||
- `kern.disks` includes `cd0` and `ada0`
|
||||
- `/run/current-system` points at the installer closure
|
||||
- the installer reaches `state=done`
|
||||
6. the installed target on `ada0` is partitioned and formatted correctly
|
||||
7. after ejecting the ISO and rebooting, the installed target boots successfully on the same XCP-ng VM
|
||||
8. after target boot:
|
||||
- `/run/current-system` points at the target closure
|
||||
- shepherd is running
|
||||
- `sshd` is running
|
||||
- activation completed successfully
|
||||
- `/var/lib/fruix/install.scm` still records the materialized source store provenance
|
||||
|
||||
## Result
|
||||
|
||||
Phase 18.3 is complete.
|
||||
|
||||
Fruix now has a validated bootable UEFI installer ISO on FreeBSD that can:
|
||||
|
||||
- boot into a Fruix-managed installer environment from ISO media
|
||||
- perform the non-interactive installation flow onto a target disk
|
||||
- boot the installed target successfully
|
||||
- and do so on both:
|
||||
- local `QEMU/UEFI/TCG`
|
||||
- the approved real `XCP-ng` VM path
|
||||
143
docs/reports/phase19-deployment-workflow-freebsd.md
Normal file
143
docs/reports/phase19-deployment-workflow-freebsd.md
Normal file
@@ -0,0 +1,143 @@
|
||||
# Phase 19.1: canonical Fruix deployment workflow on FreeBSD
|
||||
|
||||
Date: 2026-04-04
|
||||
|
||||
## Goal
|
||||
|
||||
Phase 19.1 is about turning Fruix's already-validated closure/image/install behavior into a clear operator-facing deployment story.
|
||||
|
||||
The verification target here is documentation clarity rather than a new low-level boot primitive.
|
||||
|
||||
The repo needed a single coherent explanation of how Fruix expects operators to:
|
||||
|
||||
- build a system closure
|
||||
- materialize a rootfs or image
|
||||
- install directly to an image or block device
|
||||
- use the installer image and installer ISO paths
|
||||
- roll forward to a candidate declaration
|
||||
- roll back to an earlier declaration
|
||||
|
||||
## Result
|
||||
|
||||
Phase 19.1 is complete.
|
||||
|
||||
The repository now documents a first-class Fruix deployment workflow in:
|
||||
|
||||
- `docs/system-deployment-workflow.md`
|
||||
|
||||
That document defines the current canonical command surface and explains how the already-existing validated paths fit together operationally.
|
||||
|
||||
## What was documented
|
||||
|
||||
### Canonical frontend
|
||||
|
||||
The documented user-facing frontend is now explicitly:
|
||||
|
||||
- `./bin/fruix system ...`
|
||||
|
||||
This includes the currently supported deployment-oriented actions:
|
||||
|
||||
- `build`
|
||||
- `rootfs`
|
||||
- `image`
|
||||
- `install`
|
||||
- `installer`
|
||||
- `installer-iso`
|
||||
|
||||
### Canonical deployment model
|
||||
|
||||
The workflow document now defines Fruix's current deployment model as:
|
||||
|
||||
1. declare a system in Scheme
|
||||
2. build the system closure in `/frx/store`
|
||||
3. materialize the artifact appropriate to the target environment
|
||||
4. boot or install that artifact
|
||||
5. treat the resulting closure path and emitted provenance metadata as the deployment identity
|
||||
|
||||
### Roll-forward and rollback semantics
|
||||
|
||||
The document makes explicit an important current design point:
|
||||
|
||||
- Fruix rollback is already real at the declaration/closure/deployment layer
|
||||
- but it is not yet a first-class installed-system generation switch operation
|
||||
|
||||
So the documented rollback workflow today is:
|
||||
|
||||
- retain the earlier declaration
|
||||
- rebuild or rematerialize it
|
||||
- redeploy or reboot that earlier closure again
|
||||
|
||||
That matches what Fruix has already validated in earlier phases.
|
||||
|
||||
### Platform-specific installer target-device detail
|
||||
|
||||
The workflow document also records the now-important target-device distinctions between validated environments:
|
||||
|
||||
- installer disk-image path under QEMU:
|
||||
- `/dev/vtbd1`
|
||||
- installer ISO path under QEMU:
|
||||
- `/dev/vtbd0`
|
||||
- installer ISO path under XCP-ng:
|
||||
- `/dev/ada0`
|
||||
|
||||
That makes the deployment story less harness-specific and more operator-explicit.
|
||||
|
||||
## Why this satisfies Phase 19.1
|
||||
|
||||
Before this phase, Fruix already had the machinery for:
|
||||
|
||||
- building declarative system closures
|
||||
- generating bootable images
|
||||
- performing direct non-interactive installation
|
||||
- booting a Fruix installer environment
|
||||
- booting and installing from a Fruix installer ISO
|
||||
- rollback-friendly redeploy of earlier declarations
|
||||
|
||||
What was missing was a repo-level explanation that unified those into a single operator workflow.
|
||||
|
||||
The new document closes that gap by connecting:
|
||||
|
||||
- Phase 10 command-surface work
|
||||
- Phase 15 redeploy/rollback validation
|
||||
- Phase 18 install and installer-media validation
|
||||
- and the recent QEMU + XCP-ng installer ISO validation
|
||||
|
||||
## Current boundaries now made explicit
|
||||
|
||||
The documentation intentionally records what Fruix has **not** solved yet:
|
||||
|
||||
- installed-system generation links
|
||||
- explicit rollback targets and generation metadata
|
||||
- a first-class `switch` or `reconfigure` command
|
||||
- installed-system rollback as an in-place operator workflow
|
||||
- GC-root management for installed systems
|
||||
|
||||
Those are left for later Phase 19 steps rather than being blurred into the current deployment story.
|
||||
|
||||
## References to existing validation
|
||||
|
||||
The documented workflow rests on already-passing validation paths, including:
|
||||
|
||||
- `PASS phase18-system-install`
|
||||
- `PASS phase18-installer-environment`
|
||||
- `PASS phase18-installer-iso`
|
||||
- `PASS phase18-installer-iso-xcpng`
|
||||
- `PASS phase15-base-rollback-qemu`
|
||||
- `PASS phase15-base-rollback-xcpng`
|
||||
|
||||
## Conclusion
|
||||
|
||||
Phase 19.1 is now complete.
|
||||
|
||||
Fruix has a documented canonical deployment workflow for FreeBSD covering:
|
||||
|
||||
- build
|
||||
- image generation
|
||||
- direct install
|
||||
- installer-media install
|
||||
- roll-forward
|
||||
- rollback by redeploying an earlier declaration
|
||||
|
||||
The next step is Phase 19.2:
|
||||
|
||||
- model installed-system generations, rollback targets, and deployment roots more explicitly.
|
||||
192
docs/reports/phase19-generation-layout-freebsd.md
Normal file
192
docs/reports/phase19-generation-layout-freebsd.md
Normal file
@@ -0,0 +1,192 @@
|
||||
# Phase 19.2: explicit installed-system generation layout on FreeBSD
|
||||
|
||||
Date: 2026-04-04
|
||||
|
||||
## Goal
|
||||
|
||||
Phase 19.2 is about making Fruix's installed-system generation model more explicit.
|
||||
|
||||
The target here is not yet a full Guix-equivalent in-place `switch-generation` workflow.
|
||||
|
||||
The immediate goal is to stop relying mainly on harness knowledge and implicit symlink expectations by recording installed deployment state more explicitly on disk.
|
||||
|
||||
## Decision
|
||||
|
||||
Fruix now follows this design direction:
|
||||
|
||||
- keep Guix-like **semantics**
|
||||
- do not mirror Guix's installed-system/profile layout **mechanically 1:1**
|
||||
|
||||
What Fruix preserves from Guix:
|
||||
|
||||
- immutable closure identity
|
||||
- rollback-friendly deployment semantics
|
||||
- explicit current deployment pointer
|
||||
- GC-root-style retention links
|
||||
- `/run/current-system` as the active runtime boundary
|
||||
|
||||
What Fruix intentionally changes:
|
||||
|
||||
- installed-system generation state is represented as a small metadata-bearing directory
|
||||
- the generation model is recorded under a Fruix-native path
|
||||
- deployment metadata and provenance are easier to inspect directly without reconstructing intent from symlink layout alone
|
||||
|
||||
## Implemented layout
|
||||
|
||||
Installed systems now record an explicit generation root under:
|
||||
|
||||
- `/var/lib/fruix/system`
|
||||
|
||||
Current validated initial layout:
|
||||
|
||||
```text
|
||||
/var/lib/fruix/system/
|
||||
current -> generations/1
|
||||
current-generation
|
||||
generations/
|
||||
1/
|
||||
closure -> /frx/store/...-fruix-system-...
|
||||
metadata.scm
|
||||
provenance.scm
|
||||
install.scm
|
||||
```
|
||||
|
||||
Installed systems now also create explicit retention roots under:
|
||||
|
||||
- `/frx/var/fruix/gcroots`
|
||||
|
||||
Current validated initial layout:
|
||||
|
||||
```text
|
||||
/frx/var/fruix/gcroots/
|
||||
current-system -> /frx/store/...-fruix-system-...
|
||||
system-1 -> /frx/store/...-fruix-system-...
|
||||
```
|
||||
|
||||
Important compatibility point:
|
||||
|
||||
- `/run/current-system` still points directly at the active closure in `/frx/store`
|
||||
|
||||
That means the new explicit generation model strengthens deployment metadata without changing the already-validated runtime contract used by activation, rc.d integration, and service startup.
|
||||
|
||||
## Code changes
|
||||
|
||||
### `modules/fruix/system/freebsd/media.scm`
|
||||
|
||||
Added explicit generation-layout helpers:
|
||||
|
||||
- generation metadata object writer
|
||||
- generation provenance object writer
|
||||
- generation layout population for staged rootfs trees
|
||||
|
||||
The system rootfs staging path now creates explicit generation state during rootfs population.
|
||||
|
||||
That affects:
|
||||
|
||||
- direct rootfs materialization
|
||||
- direct image materialization
|
||||
- direct installation targets
|
||||
- target rootfs payloads staged inside installer images
|
||||
- target rootfs payloads staged inside installer ISOs
|
||||
|
||||
The direct install path now also refreshes the generation layout after writing:
|
||||
|
||||
- `/var/lib/fruix/install.scm`
|
||||
|
||||
so the generation directory carries the same install metadata.
|
||||
|
||||
### Installer runtime path
|
||||
|
||||
The generated installer runtime script now also copies install metadata into:
|
||||
|
||||
- `/var/lib/fruix/system/generations/1/install.scm`
|
||||
|
||||
on the installed target.
|
||||
|
||||
This keeps direct-install and installer-mediated installs aligned.
|
||||
|
||||
## New validation harness
|
||||
|
||||
Added:
|
||||
|
||||
- `tests/system/run-phase19-generation-layout-qemu.sh`
|
||||
|
||||
This harness builds on the already-passing direct install validation from Phase 18.1 and then verifies the new explicit generation layout on the installed target image.
|
||||
|
||||
Passing validation:
|
||||
|
||||
- `PASS phase19-generation-layout-qemu`
|
||||
|
||||
Validated result summary:
|
||||
|
||||
```text
|
||||
closure_path=/frx/store/882fb4a9fbb05f08e77de29f70ca50f3c01dd29141e72688d32770a3172747e7-fruix-system-fruix-freebsd
|
||||
current_generation=1
|
||||
current_link=generations/1
|
||||
generation_closure=/frx/store/882fb4a9fbb05f08e77de29f70ca50f3c01dd29141e72688d32770a3172747e7-fruix-system-fruix-freebsd
|
||||
gcroot_current=/frx/store/882fb4a9fbb05f08e77de29f70ca50f3c01dd29141e72688d32770a3172747e7-fruix-system-fruix-freebsd
|
||||
gcroot_generation=/frx/store/882fb4a9fbb05f08e77de29f70ca50f3c01dd29141e72688d32770a3172747e7-fruix-system-fruix-freebsd
|
||||
run_current_system_target=/frx/store/882fb4a9fbb05f08e77de29f70ca50f3c01dd29141e72688d32770a3172747e7-fruix-system-fruix-freebsd
|
||||
generation_layout=explicit
|
||||
generation_layout_validation=ok
|
||||
```
|
||||
|
||||
The harness verified all of the following:
|
||||
|
||||
1. the installed target contains:
|
||||
- `/var/lib/fruix/system`
|
||||
2. the current generation pointer exists and resolves to:
|
||||
- `generations/1`
|
||||
3. the generation directory contains:
|
||||
- `closure`
|
||||
- `metadata.scm`
|
||||
- `provenance.scm`
|
||||
- `install.scm`
|
||||
4. the generation closure link points at the installed closure in `/frx/store`
|
||||
5. the generation metadata records:
|
||||
- closure path
|
||||
- install metadata path
|
||||
6. the generation install metadata records:
|
||||
- closure path
|
||||
- materialized source provenance
|
||||
7. explicit retention roots exist under:
|
||||
- `/frx/var/fruix/gcroots/current-system`
|
||||
- `/frx/var/fruix/gcroots/system-1`
|
||||
8. those GC-root-style links point at the same active closure
|
||||
9. `/run/current-system` still points directly at the active closure path
|
||||
10. existing install boot validation remains intact
|
||||
|
||||
## Relationship to Guix
|
||||
|
||||
Fruix now has an explicit installed-system generation model, but it is still intentionally not a byte-for-byte clone of Guix's on-disk conventions.
|
||||
|
||||
The design choice is:
|
||||
|
||||
- preserve Guix's deployment semantics
|
||||
- use a Fruix-native metadata-oriented representation where that improves clarity for operators and debugging
|
||||
|
||||
That decision is documented separately in:
|
||||
|
||||
- `docs/GUIX_DIFFERENCES.md`
|
||||
|
||||
## Current limitations
|
||||
|
||||
This phase does **not** yet add:
|
||||
|
||||
- multi-generation switching in place
|
||||
- a `switch`/`reconfigure` command
|
||||
- an operator-facing rollback command that flips from current to a previous installed generation without redeploy
|
||||
- explicit `rollback` link management beyond the initial current-generation layout
|
||||
|
||||
Those belong to later Phase 19 work.
|
||||
|
||||
## Conclusion
|
||||
|
||||
Phase 19.2 is complete.
|
||||
|
||||
Fruix now has a clearer, explicit installed-system generation and retention-root model on FreeBSD:
|
||||
|
||||
- generation metadata is recorded under `/var/lib/fruix/system`
|
||||
- retention links are recorded under `/frx/var/fruix/gcroots`
|
||||
- `/run/current-system` remains stable as the runtime boundary
|
||||
- and the model is documented in Fruix-native terms for Guix-familiar operators
|
||||
400
docs/system-deployment-workflow.md
Normal file
400
docs/system-deployment-workflow.md
Normal file
@@ -0,0 +1,400 @@
|
||||
# Fruix system deployment workflow
|
||||
|
||||
Date: 2026-04-04
|
||||
|
||||
## Purpose
|
||||
|
||||
This document defines the current canonical Fruix workflow for:
|
||||
|
||||
- building a declarative system closure
|
||||
- materializing deployable artifacts
|
||||
- installing a declarative system onto an image or disk
|
||||
- booting through installer media
|
||||
- rolling forward to a candidate system
|
||||
- rolling back to an earlier declared system
|
||||
|
||||
This is the Phase 19.1 operator-facing view of the system model already implemented in earlier phases.
|
||||
|
||||
## Core model
|
||||
|
||||
A Fruix system workflow starts from a Scheme file that binds an `operating-system` object.
|
||||
|
||||
Today, the canonical frontend is:
|
||||
|
||||
- `./bin/fruix system ...`
|
||||
|
||||
The important output objects are:
|
||||
|
||||
- **system closure**
|
||||
- a content-addressed store item under `/frx/store/*-fruix-system-<host-name>`
|
||||
- includes boot assets, activation logic, profile tree, metadata, and references
|
||||
- **rootfs tree**
|
||||
- a materialized runtime tree for inspection or image staging
|
||||
- **disk image**
|
||||
- a bootable GPT/UEFI raw disk image
|
||||
- **installer image**
|
||||
- a bootable Fruix installer disk image that installs a selected target system from inside the guest
|
||||
- **installer ISO**
|
||||
- a bootable UEFI ISO with an embedded installer mdroot payload
|
||||
- **install metadata**
|
||||
- `/var/lib/fruix/install.scm` on installed targets
|
||||
- records the selected closure path, install spec, and referenced store items including source provenance
|
||||
|
||||
The current deployment story is therefore already declaration-driven and content-addressed, even before first-class installed-system generations are modeled more explicitly.
|
||||
|
||||
## Canonical command surface
|
||||
|
||||
### Build a system closure
|
||||
|
||||
```sh
|
||||
sudo env HOME="$HOME" \
|
||||
GUILE_AUTO_COMPILE=0 \
|
||||
GUIX_SOURCE_DIR="$HOME/repos/guix" \
|
||||
GUILE_BIN="/tmp/guile-freebsd-validate-install/bin/guile" \
|
||||
GUILE_EXTRA_PREFIX="/tmp/guile-gnutls-freebsd-validate-install" \
|
||||
SHEPHERD_PREFIX="/tmp/shepherd-freebsd-validate-install" \
|
||||
./bin/fruix system build path/to/system.scm --system my-operating-system
|
||||
```
|
||||
|
||||
Primary result:
|
||||
|
||||
- `closure_path=/frx/store/...-fruix-system-...`
|
||||
|
||||
Use this when you want to:
|
||||
|
||||
- validate the declarative system composition itself
|
||||
- inspect provenance/layout metadata
|
||||
- compare candidate and current closure paths
|
||||
- drive later rootfs/image/install steps from the same declaration
|
||||
|
||||
### Materialize a rootfs tree
|
||||
|
||||
```sh
|
||||
sudo env HOME="$HOME" ... \
|
||||
./bin/fruix system rootfs path/to/system.scm ./rootfs --system my-operating-system
|
||||
```
|
||||
|
||||
Primary result:
|
||||
|
||||
- `rootfs=...`
|
||||
- `closure_path=/frx/store/...`
|
||||
|
||||
Use this when you want to:
|
||||
|
||||
- inspect the runtime filesystem layout directly
|
||||
- stage a tree for debugging
|
||||
- validate `/run/current-system`-style symlink layout without booting a full image
|
||||
|
||||
### Materialize a bootable disk image
|
||||
|
||||
```sh
|
||||
sudo env HOME="$HOME" ... \
|
||||
./bin/fruix system image path/to/system.scm \
|
||||
--system my-operating-system \
|
||||
--root-size 6g
|
||||
```
|
||||
|
||||
Primary result:
|
||||
|
||||
- `disk_image=/frx/store/.../disk.img`
|
||||
|
||||
Use this when you want to:
|
||||
|
||||
- boot the system directly as a VM image
|
||||
- test a candidate deployment under QEMU or XCP-ng
|
||||
- validate a roll-forward or rollback candidate by image boot
|
||||
|
||||
### Install directly to an image file or block device
|
||||
|
||||
```sh
|
||||
sudo env HOME="$HOME" ... \
|
||||
./bin/fruix system install path/to/system.scm \
|
||||
--system my-operating-system \
|
||||
--target ./installed.img \
|
||||
--disk-capacity 12g \
|
||||
--root-size 10g
|
||||
```
|
||||
|
||||
Primary result:
|
||||
|
||||
- `target=...`
|
||||
- `target_kind=raw-file` or `block-device`
|
||||
- `install_metadata_path=/var/lib/fruix/install.scm`
|
||||
|
||||
Use this when you want to:
|
||||
|
||||
- produce an installed target image without booting an installer guest
|
||||
- validate installation mechanics directly
|
||||
- populate a raw image or a real `/dev/...` target
|
||||
|
||||
### Materialize a bootable installer disk image
|
||||
|
||||
```sh
|
||||
sudo env HOME="$HOME" ... \
|
||||
./bin/fruix system installer path/to/system.scm \
|
||||
--system my-operating-system \
|
||||
--install-target-device /dev/vtbd1 \
|
||||
--root-size 10g
|
||||
```
|
||||
|
||||
Primary result:
|
||||
|
||||
- `installer_disk_image=/frx/store/.../disk.img`
|
||||
|
||||
Use this when you want to:
|
||||
|
||||
- boot a Fruix installer environment as a disk image
|
||||
- let the in-guest installer partition and install onto a second disk
|
||||
- validate non-interactive installation from inside a booted Fruix guest
|
||||
|
||||
### Materialize a bootable installer ISO
|
||||
|
||||
```sh
|
||||
sudo env HOME="$HOME" ... \
|
||||
./bin/fruix system installer-iso path/to/system.scm \
|
||||
--system my-operating-system \
|
||||
--install-target-device /dev/vtbd0
|
||||
```
|
||||
|
||||
Primary result:
|
||||
|
||||
- `iso_image=/frx/store/.../installer.iso`
|
||||
- `boot_efi_image=/frx/store/.../efiboot.img`
|
||||
- `root_image=/frx/store/.../root.img`
|
||||
|
||||
Use this when you want to:
|
||||
|
||||
- boot through UEFI ISO media instead of a writable installer disk image
|
||||
- install from an ISO-attached Fruix environment
|
||||
- test the same install model on more realistic VM paths
|
||||
|
||||
## Deployment patterns
|
||||
|
||||
### 1. Build-first workflow
|
||||
|
||||
The default Fruix operator workflow starts by building the closure first:
|
||||
|
||||
1. edit the system declaration
|
||||
2. run `fruix system build`
|
||||
3. inspect emitted metadata
|
||||
4. if needed, produce one of:
|
||||
- `rootfs`
|
||||
- `image`
|
||||
- `install`
|
||||
- `installer`
|
||||
- `installer-iso`
|
||||
|
||||
This keeps the declaration-to-closure boundary explicit.
|
||||
|
||||
### 2. VM image deployment workflow
|
||||
|
||||
Use this when you want to boot a system directly rather than through an installer.
|
||||
|
||||
1. run `fruix system image`
|
||||
2. boot the image in QEMU or convert/import it for XCP-ng
|
||||
3. validate:
|
||||
- `/run/current-system`
|
||||
- shepherd/sshd state
|
||||
- activation log
|
||||
4. keep the closure path from the build metadata as the deployment identity
|
||||
|
||||
This is the current canonical direct deployment path for already-built images.
|
||||
|
||||
### 3. Direct installation workflow
|
||||
|
||||
Use this when you want an installed target image or disk without a booted installer guest.
|
||||
|
||||
1. run `fruix system install --target ...`
|
||||
2. let Fruix partition, format, populate, and install the target
|
||||
3. boot the installed result
|
||||
4. validate `/var/lib/fruix/install.scm` and target services
|
||||
|
||||
This is the most direct install path.
|
||||
|
||||
### 4. Installer-environment workflow
|
||||
|
||||
Use this when the install itself should happen from inside a booted Fruix environment.
|
||||
|
||||
1. run `fruix system installer`
|
||||
2. boot the installer disk image
|
||||
3. let the in-guest installer run onto the selected target device
|
||||
4. boot the installed target
|
||||
|
||||
This is useful when the installer environment itself is part of what needs validation.
|
||||
|
||||
### 5. Installer-ISO workflow
|
||||
|
||||
Use this when the desired operator artifact is a bootable UEFI ISO.
|
||||
|
||||
1. run `fruix system installer-iso`
|
||||
2. boot the ISO under the target virtualization path
|
||||
3. let the in-guest installer run onto the selected target device
|
||||
4. eject the ISO and reboot the installed target
|
||||
|
||||
This is now validated on both:
|
||||
|
||||
- local `QEMU/UEFI/TCG`
|
||||
- the approved real `XCP-ng` VM path
|
||||
|
||||
## Install-target device conventions
|
||||
|
||||
The install target device is not identical across all boot styles.
|
||||
|
||||
Current validated defaults are:
|
||||
|
||||
- direct installer disk-image path under QEMU:
|
||||
- `/dev/vtbd1`
|
||||
- installer ISO path under QEMU:
|
||||
- `/dev/vtbd0`
|
||||
- installer ISO path under XCP-ng:
|
||||
- `/dev/ada0`
|
||||
|
||||
Therefore the canonical workflow is:
|
||||
|
||||
- always treat `--install-target-device` as an explicit deployment parameter when moving between virtualization environments
|
||||
|
||||
Do not assume that a device name validated in one harness is portable to another.
|
||||
|
||||
## Installed-system generation layout
|
||||
|
||||
Installed Fruix systems now record an explicit first-generation deployment layout under:
|
||||
|
||||
- `/var/lib/fruix/system`
|
||||
|
||||
Current validated shape:
|
||||
|
||||
```text
|
||||
/var/lib/fruix/system/
|
||||
current -> generations/1
|
||||
current-generation
|
||||
generations/
|
||||
1/
|
||||
closure -> /frx/store/...-fruix-system-...
|
||||
metadata.scm
|
||||
provenance.scm
|
||||
install.scm # present on installed targets
|
||||
```
|
||||
|
||||
Installed systems also now create explicit GC-root-style deployment links under:
|
||||
|
||||
- `/frx/var/fruix/gcroots`
|
||||
|
||||
Current validated shape:
|
||||
|
||||
```text
|
||||
/frx/var/fruix/gcroots/
|
||||
current-system -> /frx/store/...-fruix-system-...
|
||||
system-1 -> /frx/store/...-fruix-system-...
|
||||
```
|
||||
|
||||
Important detail:
|
||||
|
||||
- `/run/current-system` still points directly at the active closure path in `/frx/store`
|
||||
- the explicit generation layout therefore adds deployment metadata and retention roots without changing the already-validated runtime contract used by activation, rc.d wiring, and tests
|
||||
|
||||
## Roll-forward workflow
|
||||
|
||||
The current Fruix roll-forward model is declaration-driven.
|
||||
|
||||
Canonical process:
|
||||
|
||||
1. keep the current known-good system declaration
|
||||
2. prepare a candidate declaration
|
||||
- this may differ by FreeBSD base identity
|
||||
- source revision
|
||||
- services
|
||||
- users/groups
|
||||
- or other operating-system fields
|
||||
3. run `fruix system build` for the candidate
|
||||
4. materialize either:
|
||||
- `fruix system image`
|
||||
- `fruix system install`
|
||||
- `fruix system installer`
|
||||
- `fruix system installer-iso`
|
||||
5. boot or install the candidate
|
||||
6. validate the candidate closure in the booted system
|
||||
|
||||
The important property is that the candidate closure appears beside the earlier one in `/frx/store` rather than mutating it in place.
|
||||
|
||||
## Rollback workflow
|
||||
|
||||
The current canonical rollback workflow is also declaration-driven.
|
||||
|
||||
Today, rollback means:
|
||||
|
||||
1. retain the earlier declaration that produced the known-good closure
|
||||
2. rebuild or rematerialize that earlier declaration
|
||||
3. redeploy or reboot that earlier artifact again
|
||||
|
||||
Concretely, the usual rollback choices are:
|
||||
|
||||
- rebuild the earlier declaration with `fruix system build` and confirm the old closure path reappears
|
||||
- boot the earlier declaration again through `fruix system image`
|
||||
- reinstall the earlier declaration through `fruix system install`, `installer`, or `installer-iso` if the deployment medium itself must change
|
||||
|
||||
This rollback story has already been validated at the closure/image/deployment level:
|
||||
|
||||
- side-by-side base-version coexistence in `/frx/store`
|
||||
- roll-forward to a candidate closure
|
||||
- rollback by rebuilding and booting the earlier declaration again
|
||||
- validation on both local QEMU and the approved XCP-ng VM path
|
||||
|
||||
### Important scope note
|
||||
|
||||
This is not yet the same thing as a first-class installed-system generation switch command.
|
||||
|
||||
Current rollback is:
|
||||
|
||||
- **redeploy the earlier declaration again**
|
||||
|
||||
What still remains for later Phase 19 work is making rollback itself operator-driven at the installed-system layer, rather than only declaration/redeploy driven.
|
||||
|
||||
Still pending:
|
||||
|
||||
- previous-generation tracking beyond the initial explicit generation-1 layout
|
||||
- an explicit rollback target link distinct from `current`
|
||||
- an operator-facing installed-system rollback workflow
|
||||
- generation switching without full redeploy
|
||||
|
||||
## Provenance and deployment identity
|
||||
|
||||
For any serious deployment or rollback decision, the canonical identity is not merely the host name. It is the emitted metadata:
|
||||
|
||||
- `closure_path`
|
||||
- declared FreeBSD base/source metadata
|
||||
- materialized source store paths
|
||||
- install metadata at `/var/lib/fruix/install.scm`
|
||||
- store item counts and reference lists
|
||||
|
||||
Operators should retain metadata from successful candidate and current deployments because Fruix already emits enough data to answer:
|
||||
|
||||
- which declaration was built
|
||||
- which closure booted
|
||||
- which source snapshot was materialized
|
||||
- which target device or image was installed
|
||||
|
||||
## Current limitations
|
||||
|
||||
The deployment workflow is now coherent, but it is not yet the final generation-management story.
|
||||
|
||||
Not yet first-class:
|
||||
|
||||
- a dedicated `switch` or `reconfigure` command
|
||||
- an installed-system rollback command that moves between generations in place
|
||||
- multi-generation retention and previous-generation tracking beyond generation 1
|
||||
- generation switching policy independent of full redeploy
|
||||
|
||||
Those are the next logical steps after the current explicit-generation layout.
|
||||
|
||||
## Summary
|
||||
|
||||
The current canonical Fruix deployment model is:
|
||||
|
||||
- **declare** a system in Scheme
|
||||
- **build** the closure with `fruix system build`
|
||||
- **materialize** the artifact appropriate to the deployment target
|
||||
- **boot or install** that artifact
|
||||
- **identify deployments by closure path and provenance metadata**
|
||||
- **roll back by rebuilding/redeploying the earlier declaration**, not by mutating the current closure in place
|
||||
|
||||
That is the operator-facing workflow Fruix should document and use while installed-system generation switching remains more limited than Guix's mature in-place system-generation workflow.
|
||||
@@ -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)
|
||||
@@ -230,7 +233,76 @@
|
||||
(mkdir-p (dirname link-name))
|
||||
(symlink target link-name))
|
||||
|
||||
(define (populate-rootfs-from-closure os rootfs closure-path)
|
||||
(define system-generation-layout-version "1")
|
||||
|
||||
(define* (system-generation-metadata-object os closure-path
|
||||
#:key
|
||||
(generation-number 1)
|
||||
install-spec
|
||||
install-metadata-path)
|
||||
`((system-generation-version . ,system-generation-layout-version)
|
||||
(generation-number . ,generation-number)
|
||||
(host-name . ,(operating-system-host-name os))
|
||||
(ready-marker . ,(operating-system-ready-marker os))
|
||||
(init-mode . ,(operating-system-init-mode os))
|
||||
(closure-path . ,closure-path)
|
||||
(parameters-file . ,(string-append closure-path "/parameters.scm"))
|
||||
(freebsd-base-file . ,(string-append closure-path "/metadata/freebsd-base.scm"))
|
||||
(freebsd-source-file . ,(string-append closure-path "/metadata/freebsd-source.scm"))
|
||||
(freebsd-source-materializations-file
|
||||
. ,(string-append closure-path "/metadata/freebsd-source-materializations.scm"))
|
||||
(host-base-provenance-file . ,(string-append closure-path "/metadata/host-base-provenance.scm"))
|
||||
(store-layout-file . ,(string-append closure-path "/metadata/store-layout.scm"))
|
||||
(install-metadata-path . ,install-metadata-path)
|
||||
(install-spec . ,install-spec)))
|
||||
|
||||
(define (system-generation-provenance-object closure-path)
|
||||
`((closure-path . ,closure-path)
|
||||
(parameters-file . ,(string-append closure-path "/parameters.scm"))
|
||||
(freebsd-base-file . ,(string-append closure-path "/metadata/freebsd-base.scm"))
|
||||
(freebsd-source-file . ,(string-append closure-path "/metadata/freebsd-source.scm"))
|
||||
(freebsd-source-materializations-file
|
||||
. ,(string-append closure-path "/metadata/freebsd-source-materializations.scm"))
|
||||
(host-base-provenance-file . ,(string-append closure-path "/metadata/host-base-provenance.scm"))
|
||||
(store-layout-file . ,(string-append closure-path "/metadata/store-layout.scm"))))
|
||||
|
||||
(define* (populate-system-generation-layout os rootfs closure-path
|
||||
#:key
|
||||
(generation-number 1)
|
||||
install-spec
|
||||
install-metadata-path)
|
||||
(let* ((system-root (string-append rootfs "/var/lib/fruix/system"))
|
||||
(generation-name (number->string generation-number))
|
||||
(generation-link-target (string-append "generations/" generation-name))
|
||||
(generation-dir (string-append system-root "/generations/" generation-name))
|
||||
(gcroots-dir (string-append rootfs "/frx/var/fruix/gcroots"))
|
||||
(generation-install-file (string-append generation-dir "/install.scm"))
|
||||
(root-install-file (and install-metadata-path
|
||||
(string-append rootfs install-metadata-path))))
|
||||
(mkdir-p generation-dir)
|
||||
(symlink-force closure-path (string-append generation-dir "/closure"))
|
||||
(write-file (string-append generation-dir "/metadata.scm")
|
||||
(object->string
|
||||
(system-generation-metadata-object os closure-path
|
||||
#:generation-number generation-number
|
||||
#:install-spec install-spec
|
||||
#:install-metadata-path install-metadata-path)))
|
||||
(write-file (string-append generation-dir "/provenance.scm")
|
||||
(object->string (system-generation-provenance-object closure-path)))
|
||||
(when (and root-install-file (file-exists? root-install-file))
|
||||
(copy-regular-file root-install-file generation-install-file)
|
||||
(chmod generation-install-file #o644))
|
||||
(symlink-force generation-link-target (string-append system-root "/current"))
|
||||
(write-file (string-append system-root "/current-generation")
|
||||
(string-append generation-name "\n"))
|
||||
(mkdir-p gcroots-dir)
|
||||
(symlink-force closure-path (string-append gcroots-dir "/system-" generation-name))
|
||||
(symlink-force closure-path (string-append gcroots-dir "/current-system"))))
|
||||
|
||||
(define* (populate-rootfs-from-closure os rootfs closure-path
|
||||
#:key
|
||||
install-spec
|
||||
install-metadata-path)
|
||||
(when (file-exists? rootfs)
|
||||
(delete-file-recursively rootfs))
|
||||
(mkdir-p rootfs)
|
||||
@@ -277,6 +349,9 @@
|
||||
(string-append rootfs "/usr/local/etc/rc.d/fruix-activate"))
|
||||
(symlink-force "/run/current-system/usr/local/etc/rc.d/fruix-shepherd"
|
||||
(string-append rootfs "/usr/local/etc/rc.d/fruix-shepherd"))
|
||||
(populate-system-generation-layout os rootfs closure-path
|
||||
#:install-spec install-spec
|
||||
#:install-metadata-path install-metadata-path)
|
||||
`((rootfs . ,rootfs)
|
||||
(closure-path . ,closure-path)
|
||||
(ready-marker . ,(operating-system-ready-marker os))
|
||||
@@ -459,9 +534,39 @@
|
||||
#:serial-console serial-console))
|
||||
(target-install . ,target-install-spec))))
|
||||
|
||||
(define* (operating-system-installer-iso-spec os
|
||||
#:key
|
||||
(install-target-device "/dev/vtbd0")
|
||||
(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 "2")
|
||||
|
||||
(define (operating-system-install-metadata-object install-spec closure-path store-items)
|
||||
`((install-version . ,install-builder-version)
|
||||
@@ -543,9 +648,10 @@
|
||||
" [ -n \"$item_base\" ] || continue\n"
|
||||
" (cd '" store-dir "' && pax -rw -pe \"$item_base\" \"$mnt_root" store-dir "\")\n"
|
||||
"done <\"$store_items_file\"\n"
|
||||
"mkdir -p \"$mnt_root/var/lib/fruix\" \"$mnt_esp/EFI/BOOT\"\n"
|
||||
"mkdir -p \"$mnt_root/var/lib/fruix\" \"$mnt_root/var/lib/fruix/system/generations/1\" \"$mnt_esp/EFI/BOOT\"\n"
|
||||
"cp \"$target_loader_efi\" \"$mnt_esp/EFI/BOOT/BOOTX64.EFI\"\n"
|
||||
"cp \"$install_metadata_source\" \"$mnt_root/var/lib/fruix/install.scm\"\n"
|
||||
"cp \"$install_metadata_source\" \"$mnt_root/var/lib/fruix/system/generations/1/install.scm\"\n"
|
||||
"sync\n"
|
||||
"echo 'fruix-installer:done'\n"
|
||||
"write_state done\n")))
|
||||
@@ -643,7 +749,9 @@
|
||||
(dynamic-wind
|
||||
(lambda () #t)
|
||||
(lambda ()
|
||||
(populate-rootfs-from-closure os rootfs closure-path)
|
||||
(populate-rootfs-from-closure os rootfs closure-path
|
||||
#:install-spec install-spec
|
||||
#:install-metadata-path install-metadata-relative-path)
|
||||
(mkdir-p mnt-root)
|
||||
(mkdir-p mnt-esp)
|
||||
(case target-kind
|
||||
@@ -685,7 +793,10 @@
|
||||
(write-file install-metadata-file
|
||||
(object->string
|
||||
(operating-system-install-metadata-object install-spec closure-path store-items)))
|
||||
(chmod install-metadata-file #o644))
|
||||
(chmod install-metadata-file #o644)
|
||||
(populate-system-generation-layout os mnt-root closure-path
|
||||
#:install-spec install-spec
|
||||
#:install-metadata-path install-metadata-relative-path))
|
||||
(run-command "sync")
|
||||
`((target . ,target)
|
||||
(target-kind . ,target-kind)
|
||||
@@ -754,7 +865,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 +1019,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"))
|
||||
@@ -929,7 +1040,9 @@
|
||||
(lambda () #t)
|
||||
(lambda ()
|
||||
(populate-rootfs-from-closure installer-os installer-rootfs installer-closure-path)
|
||||
(populate-rootfs-from-closure os target-rootfs target-closure-path)
|
||||
(populate-rootfs-from-closure os target-rootfs target-closure-path
|
||||
#:install-spec target-install-spec
|
||||
#:install-metadata-path "/var/lib/fruix/install.scm")
|
||||
(copy-rootfs-for-image installer-rootfs image-rootfs)
|
||||
(mkdir-p plan-root)
|
||||
(mkdir-p (string-append image-rootfs "/usr/local/libexec"))
|
||||
@@ -1029,3 +1142,297 @@
|
||||
(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 (source-store-item? item)
|
||||
(string-contains (path-basename item) "-freebsd-source-"))
|
||||
|
||||
(define (runtime-store-items items)
|
||||
(filter (lambda (item)
|
||||
(not (source-store-item? item)))
|
||||
items))
|
||||
|
||||
(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=\"mfs_root\"\n"
|
||||
"mdroot_name=\"/boot/root.img\"\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 (rewrite-installer-iso-fstab image-rootfs installer-closure-path)
|
||||
(let ((fstab-path (string-append image-rootfs "/frx/store/"
|
||||
(path-basename installer-closure-path)
|
||||
"/etc/fstab")))
|
||||
(rewrite-text-file fstab-path
|
||||
'(("/dev/gpt/fruix-installer-root\t/\tufs"
|
||||
. "/dev/md0\t/\tufs")))))
|
||||
|
||||
(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/vtbd0")
|
||||
(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-closure-store-items (store-reference-closure (list target-closure-path)))
|
||||
(target-runtime-store-items (runtime-store-items target-closure-store-items))
|
||||
(installer-store-items (runtime-store-items
|
||||
(store-reference-closure (list installer-closure-path))))
|
||||
(combined-store-items (delete-duplicates (append installer-store-items target-runtime-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-closure-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-closure-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
|
||||
#:install-spec target-install-spec
|
||||
#:install-metadata-path "/var/lib/fruix/install.scm")
|
||||
(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-runtime-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)
|
||||
(rewrite-installer-iso-fstab image-rootfs installer-closure-path)
|
||||
(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-closure-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/vtbd0")
|
||||
#:root-size root-size)))
|
||||
((string=? action "install")
|
||||
(unless target
|
||||
(error "install action requires TARGET or --target PATH"))
|
||||
|
||||
455
tests/system/run-phase18-installer-iso-xcpng.sh
Executable file
455
tests/system/run-phase18-installer-iso-xcpng.sh
Executable file
@@ -0,0 +1,455 @@
|
||||
#!/bin/sh
|
||||
set -eu
|
||||
|
||||
repo_root=${PROJECT_ROOT:-$(pwd)}
|
||||
script_dir=$(CDPATH= cd -- "$(dirname "$0")" && pwd)
|
||||
fruix_cmd=$repo_root/bin/fruix
|
||||
vm_id=${VM_ID:-90490f2e-e8fc-4b7a-388e-5c26f0157289}
|
||||
iso_sr_id=${ISO_SR_ID:-537a6219-8452-7cb5-8d56-5eed6910c7a2}
|
||||
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}
|
||||
install_target_device=${INSTALL_TARGET_DEVICE:-/dev/ada0}
|
||||
installer_root_size=${INSTALLER_ROOT_SIZE:-}
|
||||
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}
|
||||
iso_http_port=${ISO_HTTP_PORT:-$(jot -r 1 18080 18999)}
|
||||
keep_imported_iso=${KEEP_IMPORTED_ISO:-0}
|
||||
|
||||
[ -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 node >/dev/null 2>&1 || {
|
||||
echo "node is required to serve the ISO for XO import" >&2
|
||||
exit 1
|
||||
}
|
||||
command -v xo-cli >/dev/null 2>&1 || {
|
||||
echo "xo-cli is required" >&2
|
||||
exit 1
|
||||
}
|
||||
command -v jq >/dev/null 2>&1 || {
|
||||
echo "jq is required" >&2
|
||||
exit 1
|
||||
}
|
||||
|
||||
cleanup=0
|
||||
if [ -n "${WORKDIR:-}" ]; then
|
||||
workdir=$WORKDIR
|
||||
mkdir -p "$workdir"
|
||||
else
|
||||
workdir=$(mktemp -d /tmp/fruix-phase18-installer-iso-xcpng.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-xcpng-metadata.txt
|
||||
vm_info_json=$workdir/vm-info.json
|
||||
vdi_info_json=$workdir/vdi-info.json
|
||||
installer_log_file=$workdir/installer.log
|
||||
installer_gpart_file=$workdir/installer-gpart.txt
|
||||
installer_kern_disks_file=$workdir/installer-kern-disks.txt
|
||||
target_install_metadata_file=$workdir/target-install.scm
|
||||
server_script=$workdir/serve-iso.mjs
|
||||
server_log=$workdir/serve-iso.log
|
||||
arp_scan_log=$workdir/arp-scan.log
|
||||
|
||||
server_pid=
|
||||
imported_iso_id=
|
||||
imported_iso_name=
|
||||
guest_ip=
|
||||
vm_mac=
|
||||
|
||||
cleanup_workdir() {
|
||||
if [ -n "$server_pid" ]; then
|
||||
kill "$server_pid" >/dev/null 2>&1 || true
|
||||
fi
|
||||
xo-cli vm.ejectCd id=$vm_id >/dev/null 2>&1 || true
|
||||
if [ -n "$imported_iso_id" ] && [ "$keep_imported_iso" -ne 1 ]; then
|
||||
xo-cli vdi.delete id=$imported_iso_id >/dev/null 2>&1 || true
|
||||
fi
|
||||
if [ "$cleanup" -eq 1 ]; then
|
||||
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"
|
||||
|
||||
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; }
|
||||
|
||||
xo-cli list-objects id=$vm_id >"$vm_info_json"
|
||||
vdi_id=$(xo-cli list-objects type=VBD | jq -r '.[] | select(.VM=="'$vm_id'" and .is_cd_drive==false and .position=="0") | .VDI' | head -n 1)
|
||||
secondary_vdi_id=$(xo-cli list-objects type=VBD | jq -r '.[] | select(.VM=="'$vm_id'" and .is_cd_drive==false and .position=="1") | .VDI' | head -n 1)
|
||||
[ -n "$vdi_id" ] || { echo "failed to discover primary target VDI for VM $vm_id" >&2; exit 1; }
|
||||
xo-cli list-objects type=VDI | jq '[.[] | select(.id=="'$vdi_id'")]' >"$vdi_info_json"
|
||||
vdi_size=$(jq -r '.[0].size' "$vdi_info_json")
|
||||
[ -n "$vdi_size" ] || { echo "failed to discover VDI size for $vdi_id" >&2; exit 1; }
|
||||
|
||||
vif_id=$(jq -r '.[0].VIFs[0]' "$vm_info_json")
|
||||
if [ -n "$vif_id" ] && [ "$vif_id" != null ]; then
|
||||
vm_mac=$(xo-cli list-objects type=VIF | jq -r '.[] | select(.id=="'$vif_id'") | .MAC' | tr 'A-Z' 'a-z')
|
||||
else
|
||||
vm_mac=
|
||||
fi
|
||||
[ -n "$vm_mac" ] || { echo "failed to discover VM MAC address" >&2; exit 1; }
|
||||
|
||||
host_interface=$(route -n get default | awk '/interface:/{print $2; exit}')
|
||||
host_ip=$(ifconfig "$host_interface" | awk '/inet /{print $2; exit}')
|
||||
subnet_prefix=${host_ip%.*}
|
||||
|
||||
cat >"$server_script" <<'EOF'
|
||||
import { createServer } from 'node:http'
|
||||
import { createReadStream, statSync } from 'node:fs'
|
||||
const file = process.argv[2]
|
||||
const port = Number(process.argv[3])
|
||||
const size = statSync(file).size
|
||||
createServer((req, res) => {
|
||||
if (req.url !== '/installer.iso') {
|
||||
res.statusCode = 404
|
||||
res.end('not found\n')
|
||||
return
|
||||
}
|
||||
res.writeHead(200, {
|
||||
'Content-Type': 'application/octet-stream',
|
||||
'Content-Length': size,
|
||||
'Content-Disposition': 'attachment; filename="installer.iso"'
|
||||
})
|
||||
createReadStream(file).pipe(res)
|
||||
}).listen(port, '0.0.0.0', () => {
|
||||
console.log(`listening ${port}`)
|
||||
})
|
||||
EOF
|
||||
|
||||
node "$server_script" "$installer_iso_image" "$iso_http_port" >"$server_log" 2>&1 &
|
||||
server_pid=$!
|
||||
for _ in $(jot 50 1 50); do
|
||||
if grep -q 'listening' "$server_log"; then
|
||||
break
|
||||
fi
|
||||
sleep 1
|
||||
if ! kill -0 "$server_pid" >/dev/null 2>&1; then
|
||||
echo "temporary ISO HTTP server failed to start" >&2
|
||||
cat "$server_log" >&2 || true
|
||||
exit 1
|
||||
fi
|
||||
done
|
||||
iso_import_url="http://$host_ip:$iso_http_port/installer.iso"
|
||||
imported_iso_name="fruix-installer-iso-ada0-$(date +%s).iso"
|
||||
imported_iso_id=$(xo-cli disk.import name="$imported_iso_name" sr="$iso_sr_id" type=iso url="$iso_import_url")
|
||||
kill "$server_pid" >/dev/null 2>&1 || true
|
||||
server_pid=
|
||||
|
||||
refresh_guest_ip() {
|
||||
guest_ip=$(arp -an | awk -v mac="$vm_mac" 'tolower($4)==mac {gsub(/[()]/,"",$2); print $2; exit}')
|
||||
}
|
||||
|
||||
ping_sweep() {
|
||||
: >"$arp_scan_log"
|
||||
for host in $(jot 254 1 254); do
|
||||
ip=$subnet_prefix.$host
|
||||
(
|
||||
ping -c 1 -W 1000 "$ip" >/dev/null 2>&1 && echo "$ip" >>"$arp_scan_log"
|
||||
) &
|
||||
done
|
||||
wait
|
||||
}
|
||||
|
||||
ssh_guest() {
|
||||
ssh -i "$root_ssh_private_key_file" \
|
||||
-o BatchMode=yes \
|
||||
-o StrictHostKeyChecking=no \
|
||||
-o UserKnownHostsFile=/dev/null \
|
||||
-o LogLevel=ERROR \
|
||||
-o ConnectTimeout=5 \
|
||||
root@"$guest_ip" "$@"
|
||||
}
|
||||
|
||||
wait_for_guest_command() {
|
||||
probe=$1
|
||||
attempts=$2
|
||||
delay=$3
|
||||
guest_ip=
|
||||
for attempt in $(jot "$attempts" 1 "$attempts"); do
|
||||
refresh_guest_ip
|
||||
if [ -z "$guest_ip" ]; then
|
||||
ping_sweep
|
||||
refresh_guest_ip
|
||||
fi
|
||||
if [ -n "$guest_ip" ]; then
|
||||
if ssh_guest "$probe" >/dev/null 2>&1; then
|
||||
return 0
|
||||
fi
|
||||
fi
|
||||
sleep "$delay"
|
||||
done
|
||||
return 1
|
||||
}
|
||||
|
||||
xo-cli vm.stop id=$vm_id force=true >/dev/null 2>&1 || true
|
||||
xo-cli vm.insertCd id=$vm_id cd_id=$imported_iso_id force=true >"$workdir/insert-cd.out"
|
||||
xo-cli vm.setBootOrder vm=$vm_id order=dcn >"$workdir/set-boot-order.out"
|
||||
xo-cli vm.start id=$vm_id >"$workdir/vm-start-installer.out"
|
||||
|
||||
wait_for_guest_command 'test -e /var/lib/fruix/installer/state' 90 5 || {
|
||||
echo "installer ISO guest never became reachable over SSH" >&2
|
||||
exit 1
|
||||
}
|
||||
|
||||
installer_state=missing
|
||||
for attempt in $(jot 180 1 180); do
|
||||
if ssh_guest 'test -e /var/lib/fruix/installer/state' >/dev/null 2>&1; then
|
||||
installer_state=$(ssh_guest "cat '$installer_state_path' 2>/dev/null || echo missing")
|
||||
[ "$installer_state" = done ] && break
|
||||
fi
|
||||
sleep 5
|
||||
done
|
||||
|
||||
installer_run_current_system=$(ssh_guest 'readlink /run/current-system')
|
||||
installer_sshd_status=$(ssh_guest 'service sshd onestatus >/dev/null 2>&1 && echo running || echo stopped')
|
||||
installer_log=$(ssh_guest "cat '$installer_log_path' 2>/dev/null || true")
|
||||
installer_target_device=$(ssh_guest 'cat /var/lib/fruix/installer/target-device')
|
||||
installer_kern_disks=$(ssh_guest 'sysctl -n kern.disks')
|
||||
installer_gpart=$(ssh_guest 'gpart show ada0')
|
||||
installer_esp_fstype=$(ssh_guest 'fstyp /dev/ada0p1')
|
||||
installer_root_fstype=$(ssh_guest 'fstyp /dev/ada0p2')
|
||||
printf '%s\n' "$installer_log" >"$installer_log_file"
|
||||
printf '%s\n' "$installer_kern_disks" >"$installer_kern_disks_file"
|
||||
printf '%s\n' "$installer_gpart" >"$installer_gpart_file"
|
||||
|
||||
[ "$installer_state" = done ] || { echo "installer ISO environment did not finish installation: $installer_state" >&2; exit 1; }
|
||||
[ "$installer_run_current_system" = "/frx/store/$(basename "$installer_closure_path")" ] || { 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; }
|
||||
[ "$installer_target_device" = "$install_target_device" ] || { echo "unexpected installer target device in guest: $installer_target_device" >&2; exit 1; }
|
||||
[ "$installer_esp_fstype" = msdosfs ] || { echo "unexpected target ESP filesystem: $installer_esp_fstype" >&2; exit 1; }
|
||||
[ "$installer_root_fstype" = ufs ] || { echo "unexpected target root filesystem: $installer_root_fstype" >&2; exit 1; }
|
||||
case "$installer_log" in
|
||||
*fruix-installer:done*) : ;;
|
||||
*) echo "installer log does not show completion" >&2; exit 1 ;;
|
||||
esac
|
||||
case "$installer_kern_disks" in
|
||||
*cd0*ada0*) : ;;
|
||||
*) echo "unexpected installer kern.disks output: $installer_kern_disks" >&2; exit 1 ;;
|
||||
esac
|
||||
case "$installer_gpart" in
|
||||
*ada0*GPT*) : ;;
|
||||
*) echo "unexpected gpart output for ada0" >&2; exit 1 ;;
|
||||
esac
|
||||
|
||||
xo-cli vm.ejectCd id=$vm_id >"$workdir/eject-cd.out"
|
||||
xo-cli vm.restart id=$vm_id force=true >"$workdir/vm-restart-target.out"
|
||||
|
||||
wait_for_guest_command 'test -f /var/lib/fruix/ready' 120 5 || {
|
||||
echo "installed target never became reachable over SSH" >&2
|
||||
exit 1
|
||||
}
|
||||
|
||||
target_run_current_system=$(ssh_guest 'readlink /run/current-system')
|
||||
target_shepherd_status=$(ssh_guest '/usr/local/etc/rc.d/fruix-shepherd onestatus >/dev/null 2>&1 && echo running || echo stopped')
|
||||
target_sshd_status=$(ssh_guest 'service sshd onestatus >/dev/null 2>&1 && echo running || echo stopped')
|
||||
target_activate_log=$(ssh_guest 'cat /var/log/fruix-activate.log 2>/dev/null || true' | tr '\n' ' ')
|
||||
target_install_metadata=$(ssh_guest 'cat /var/lib/fruix/install.scm')
|
||||
printf '%s\n' "$target_install_metadata" >"$target_install_metadata_file"
|
||||
|
||||
[ "$target_run_current_system" = "/frx/store/$(basename "$target_closure_path")" ] || { echo "unexpected booted target current-system: $target_run_current_system" >&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" in
|
||||
*"$target_closure_path"*) : ;;
|
||||
*) echo "booted target metadata does not record target closure path" >&2; exit 1 ;;
|
||||
esac
|
||||
case "$target_install_metadata" in
|
||||
*"$materialized_source_stores"*) : ;;
|
||||
*) echo "booted target metadata does not record materialized source store" >&2; exit 1 ;;
|
||||
esac
|
||||
case "$target_install_metadata" in
|
||||
*"$install_target_device"*) : ;;
|
||||
*) echo "booted target metadata does not record install target device" >&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
|
||||
vm_id=$vm_id
|
||||
primary_vdi_id=$vdi_id
|
||||
secondary_vdi_id=$secondary_vdi_id
|
||||
vdi_size=$vdi_size
|
||||
iso_sr_id=$iso_sr_id
|
||||
imported_iso_id=$imported_iso_id
|
||||
imported_iso_name=$imported_iso_name
|
||||
guest_ip=$guest_ip
|
||||
target_os_file=$target_os_file
|
||||
installer_iso_store_path=$iso_store_path
|
||||
installer_iso_image=$installer_iso_image
|
||||
installer_boot_efi_image=$installer_boot_efi_image
|
||||
installer_root_image=$installer_root_image
|
||||
install_target_device=$install_target_device
|
||||
installer_root_size=$root_size_out
|
||||
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_target_device=$installer_target_device
|
||||
installer_kern_disks=$installer_kern_disks
|
||||
installer_log_file=$installer_log_file
|
||||
installer_gpart_file=$installer_gpart_file
|
||||
installer_kern_disks_file=$installer_kern_disks_file
|
||||
target_esp_fstype=$installer_esp_fstype
|
||||
target_root_fstype=$installer_root_fstype
|
||||
target_run_current_system=$target_run_current_system
|
||||
target_shepherd_status=$target_shepherd_status
|
||||
target_sshd_status=$target_sshd_status
|
||||
target_install_metadata_file=$target_install_metadata_file
|
||||
boot_backend=xcp-ng-xo-cli
|
||||
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-xcpng\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"
|
||||
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/vtbd0}
|
||||
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"
|
||||
160
tests/system/run-phase19-generation-layout-qemu.sh
Executable file
160
tests/system/run-phase19-generation-layout-qemu.sh
Executable file
@@ -0,0 +1,160 @@
|
||||
#!/bin/sh
|
||||
set -eu
|
||||
|
||||
repo_root=${PROJECT_ROOT:-$(pwd)}
|
||||
metadata_target=${METADATA_OUT:-}
|
||||
cleanup=0
|
||||
|
||||
if [ -n "${WORKDIR:-}" ]; then
|
||||
workdir=$WORKDIR
|
||||
mkdir -p "$workdir"
|
||||
else
|
||||
workdir=$(mktemp -d /tmp/fruix-phase19-generation-layout-qemu.XXXXXX)
|
||||
cleanup=1
|
||||
fi
|
||||
if [ "${KEEP_WORKDIR:-0}" -eq 1 ]; then
|
||||
cleanup=0
|
||||
fi
|
||||
|
||||
inner_workdir=$workdir/phase18-install
|
||||
inner_metadata=$workdir/phase18-system-install-metadata.txt
|
||||
metadata_file=$workdir/phase19-generation-layout-qemu-metadata.txt
|
||||
mnt_esp=$workdir/mnt-esp
|
||||
mnt_root=$workdir/mnt-root
|
||||
md_unit=
|
||||
|
||||
cleanup_workdir() {
|
||||
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
|
||||
|
||||
mkdir -p "$mnt_esp" "$mnt_root"
|
||||
|
||||
KEEP_WORKDIR=1 WORKDIR="$inner_workdir" METADATA_OUT="$inner_metadata" \
|
||||
"$repo_root/tests/system/run-phase18-system-install.sh" >/dev/null
|
||||
|
||||
field() {
|
||||
sed -n "s/^$1=//p" "$inner_metadata" | tail -n 1
|
||||
}
|
||||
|
||||
target_image=$(field target_image)
|
||||
closure_path=$(field closure_path)
|
||||
materialized_source_store=$(field materialized_source_store)
|
||||
install_metadata_path=$(field install_metadata_path)
|
||||
run_current_system_target_reported=$(field run_current_system_target)
|
||||
serial_log=$(field serial_log)
|
||||
closure_base=$(basename "$closure_path")
|
||||
|
||||
guest_md=$(sudo mdconfig -a -t vnode -f "$target_image")
|
||||
md_unit=${guest_md#md}
|
||||
sudo mount -t msdosfs "/dev/${guest_md}p1" "$mnt_esp"
|
||||
sudo mount -t ufs -o ro "/dev/${guest_md}p2" "$mnt_root"
|
||||
|
||||
system_root=$mnt_root/var/lib/fruix/system
|
||||
generation_root=$system_root/generations/1
|
||||
gcroots_root=$mnt_root/frx/var/fruix/gcroots
|
||||
|
||||
[ -d "$system_root" ] || { echo "missing explicit system generation root" >&2; exit 1; }
|
||||
[ -d "$generation_root" ] || { echo "missing generation 1 directory" >&2; exit 1; }
|
||||
[ -f "$system_root/current-generation" ] || { echo "missing current-generation file" >&2; exit 1; }
|
||||
[ -L "$system_root/current" ] || { echo "missing current generation link" >&2; exit 1; }
|
||||
[ -L "$generation_root/closure" ] || { echo "missing generation closure link" >&2; exit 1; }
|
||||
[ -f "$generation_root/metadata.scm" ] || { echo "missing generation metadata file" >&2; exit 1; }
|
||||
[ -f "$generation_root/provenance.scm" ] || { echo "missing generation provenance file" >&2; exit 1; }
|
||||
[ -f "$generation_root/install.scm" ] || { echo "missing generation install metadata file" >&2; exit 1; }
|
||||
[ -L "$gcroots_root/current-system" ] || { echo "missing current-system gc root" >&2; exit 1; }
|
||||
[ -L "$gcroots_root/system-1" ] || { echo "missing system-1 gc root" >&2; exit 1; }
|
||||
|
||||
current_generation=$(tr -d '\n' < "$system_root/current-generation")
|
||||
current_link=$(readlink "$system_root/current")
|
||||
generation_closure=$(readlink "$generation_root/closure")
|
||||
gcroot_current=$(readlink "$gcroots_root/current-system")
|
||||
gcroot_generation=$(readlink "$gcroots_root/system-1")
|
||||
run_current_system_target=$(readlink "$mnt_root/run/current-system")
|
||||
generation_metadata=$(cat "$generation_root/metadata.scm")
|
||||
generation_provenance=$(cat "$generation_root/provenance.scm")
|
||||
generation_install=$(cat "$generation_root/install.scm")
|
||||
install_metadata_host=$(cat "$mnt_root$install_metadata_path")
|
||||
|
||||
[ "$current_generation" = 1 ] || { echo "unexpected current generation: $current_generation" >&2; exit 1; }
|
||||
[ "$current_link" = generations/1 ] || { echo "unexpected current link target: $current_link" >&2; exit 1; }
|
||||
[ "$generation_closure" = "/frx/store/$closure_base" ] || { echo "unexpected generation closure target: $generation_closure" >&2; exit 1; }
|
||||
[ "$gcroot_current" = "/frx/store/$closure_base" ] || { echo "unexpected current-system gc root: $gcroot_current" >&2; exit 1; }
|
||||
[ "$gcroot_generation" = "/frx/store/$closure_base" ] || { echo "unexpected system-1 gc root: $gcroot_generation" >&2; exit 1; }
|
||||
[ "$run_current_system_target" = "/frx/store/$closure_base" ] || { echo "unexpected /run/current-system target: $run_current_system_target" >&2; exit 1; }
|
||||
[ "$run_current_system_target_reported" = "/frx/store/$closure_base" ] || { echo "unexpected reported guest current-system target: $run_current_system_target_reported" >&2; exit 1; }
|
||||
|
||||
case "$generation_metadata" in
|
||||
*"$closure_path"*) : ;;
|
||||
*) echo "generation metadata does not record closure path" >&2; exit 1 ;;
|
||||
esac
|
||||
case "$generation_metadata" in
|
||||
*"$install_metadata_path"*) : ;;
|
||||
*) echo "generation metadata does not record install metadata path" >&2; exit 1 ;;
|
||||
esac
|
||||
case "$generation_provenance" in
|
||||
*"$closure_path/metadata/store-layout.scm"*) : ;;
|
||||
*) echo "generation provenance does not reference store layout metadata" >&2; exit 1 ;;
|
||||
esac
|
||||
case "$generation_install" in
|
||||
*"$closure_path"*) : ;;
|
||||
*) echo "generation install metadata does not record closure path" >&2; exit 1 ;;
|
||||
esac
|
||||
case "$generation_install" in
|
||||
*"$materialized_source_store"*) : ;;
|
||||
*) echo "generation install metadata does not record materialized source store" >&2; exit 1 ;;
|
||||
esac
|
||||
case "$install_metadata_host" in
|
||||
*"$closure_path"*) : ;;
|
||||
*) echo "installed target metadata does not record closure path" >&2; exit 1 ;;
|
||||
esac
|
||||
|
||||
sudo umount "$mnt_esp"
|
||||
sudo umount "$mnt_root"
|
||||
sudo mdconfig -d -u "$md_unit"
|
||||
md_unit=
|
||||
|
||||
cat >"$metadata_file" <<EOF
|
||||
workdir=$workdir
|
||||
inner_workdir=$inner_workdir
|
||||
inner_metadata=$inner_metadata
|
||||
target_image=$target_image
|
||||
closure_path=$closure_path
|
||||
closure_base=$closure_base
|
||||
materialized_source_store=$materialized_source_store
|
||||
install_metadata_path=$install_metadata_path
|
||||
system_root=$system_root
|
||||
generation_root=$generation_root
|
||||
gcroots_root=$gcroots_root
|
||||
current_generation=$current_generation
|
||||
current_link=$current_link
|
||||
generation_closure=$generation_closure
|
||||
gcroot_current=$gcroot_current
|
||||
gcroot_generation=$gcroot_generation
|
||||
run_current_system_target=$run_current_system_target
|
||||
serial_log=$serial_log
|
||||
generation_layout=explicit
|
||||
boot_backend=qemu-uefi-tcg
|
||||
generation_layout_validation=ok
|
||||
EOF
|
||||
|
||||
if [ -n "$metadata_target" ]; then
|
||||
mkdir -p "$(dirname "$metadata_target")"
|
||||
cp "$metadata_file" "$metadata_target"
|
||||
fi
|
||||
|
||||
printf 'PASS phase19-generation-layout-qemu\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