Compare commits

...

6 Commits

25 changed files with 4386 additions and 290 deletions

View File

@@ -1,5 +1,381 @@
# Progress
## 2026-04-03 — Phase 18.1 completed: Fruix now has a minimal non-interactive installation flow
Completed work:
- added in `modules/fruix/system/freebsd.scm`:
- `operating-system-install-spec`
- `install-operating-system`
- refactored rootfs staging with:
- `populate-rootfs-from-closure`
so image generation and installation can share the same declarative rootfs assembly logic
- the new installer now supports:
- raw image-file targets
- `/dev/...` block-device targets
- for raw image targets, Fruix now performs a repeatable install flow that:
- creates/truncates the target image
- attaches it with `mdconfig`
- creates GPT partitions
- formats an EFI partition and UFS root partition
- stages the system rootfs
- copies the selected closure and referenced `/frx/store` items into the installed root
- installs `loader.efi` to `EFI/BOOT/BOOTX64.EFI`
- writes install metadata to:
- `/var/lib/fruix/install.scm`
- added user-facing CLI support in `scripts/fruix.scm`:
- `fruix system install`
- new option:
- `--target PATH`
- install metadata emitted by the CLI now includes:
- target/target-kind/device fields
- install metadata path
- disk/root sizing
- declared/materialized FreeBSD source metadata
- closure/native/runtime store metadata
- added validation artifacts:
- `tests/system/phase18-install-operating-system.scm.in`
- `tests/system/run-phase18-system-install.sh`
- wrote:
- `docs/reports/phase18-minimal-installation-flow-freebsd.md`
Validation:
- `PASS phase18-system-install`
- regression re-check:
- `PASS phase17-source-revisions-qemu`
- validated a full install to a raw target image:
- target kind:
- `raw-file`
- disk capacity:
- `12g`
- root size:
- `10g`
- validated target layout and boot artifacts:
- GPT image created
- ESP filesystem:
- `msdosfs`
- root filesystem:
- `ufs`
- `EFI/BOOT/BOOTX64.EFI` present
- validated installed-system boot through the already-validated mode:
- `freebsd-init+rc.d-shepherd`
- validated after boot:
- `sshd` running
- `/usr/local/etc/rc.d/fruix-shepherd onestatus` reports running
- activation completed successfully
- `/run/current-system` points at the installed closure under `/frx/store`
- validated source-driven installation provenance from:
- Git ref:
- `stable/15`
- pinned commit:
- `332708a606f6bf0841c1d4a74c0d067f5640fe89`
- installed materialized source store:
- `/frx/store/a892afb425235de71c9da38884e2ebdba5dafd3a1993f432fe7c446f5af2151f-freebsd-source-stable15-install-source`
Operational note:
- I tested the idea of defaulting local QEMU/TCG validation to 8 vCPUs
- result:
- under TCG, higher SMP did not help this path and regressed boot responsiveness
- current harnesses therefore keep `QEMU_SMP` configurable but retain the conservative local default of `2`
Current assessment:
- Phase 18.1 is complete
- Fruix can now perform a repeatable, non-interactive installation of a declarative system onto a target image or disk without relying on ad hoc manual assembly
- the next step is Phase 18.2:
- build a minimal Fruix-managed installer environment that can boot into an install context and run this workflow from within that environment
## 2026-04-03 — Phase 17.3 completed: the repo now records Fruix FreeBSD source policy explicitly
Completed work:
- added a repo-level source policy document:
- `docs/freebsd-source-policy.md`
- documented the current Fruix policy for:
- source kinds:
- `local-tree`
- `git`
- `src-txz`
- declared source vs effective source
- cache locations under `/frx/var/cache/fruix/freebsd-source`
- materialized source outputs under `/frx/store/*-freebsd-source-*`
- identity boundaries for:
- local-tree snapshots
- Git refs vs commits
- verified `src.txz` archives
- effective source root detection:
- `tree`
- `tree/usr/src`
- native build invalidation semantics
- closure provenance semantics
- update policy for moving refs vs pinned identities
- current patch/transformation policy
- wrote:
- `docs/reports/phase17-source-policy-freebsd.md`
Validation / evidence basis:
- policy matches the already-validated implementation from Phases 1617.2, including:
- `PASS phase16-source-materialization`
- `PASS phase16-source-driven-native-build`
- `PASS phase17-source-coexistence`
- `PASS phase17-source-revisions-qemu`
- the repo now states clearly that:
- Git refs are selectors, not stable reproducibility boundaries
- resolved Git commits are the effective Git identity boundary
- `src.txz` inputs require `sha256`
- native outputs must invalidate when materialized source identity changes, even when the visible base version label does not
Current assessment:
- Phase 17 is now fully complete
- Fruix can now:
- materialize FreeBSD sources declaratively
- keep distinct source revisions side by side in `/frx/store`
- boot systems built from those distinct source revisions
- explain the intended source caching/provenance/invalidation/update policy explicitly in the repo
- the next step is Phase 18:
- turn the current image/deployment primitives into a real installation workflow
## 2026-04-03 — Phase 17.2 completed: Fruix now boots systems from distinct declared FreeBSD source revisions
Completed work:
- added boot validation harness:
- `tests/system/run-phase17-source-revisions-qemu.sh`
- the new harness renders the Phase 17 Git and `src.txz` operating-system templates and boots both systems through the validated:
- QEMU
- UEFI
- TCG
- `shepherd-pid1`
path
- validated booted source identities:
- Git source:
- ref: `stable/15`
- commit: `332708a606f6bf0841c1d4a74c0d067f5640fe89`
- `src.txz` source:
- `https://download.freebsd.org/releases/amd64/15.0-RELEASE/src.txz`
- sha256:
- `83c3e8157b6d7afcae57167fda75693bf1e5f581ca149a6ecb2d398b71bdfab0`
- confirmed image/build metadata for both boots records:
- declared source kind
- ref/commit or archive URL/sha256
- materialized source store path
- distinct native base store sets
- wrote:
- `docs/reports/phase17-source-revision-boots-freebsd.md`
Validation:
- `PASS phase17-source-revisions-qemu`
- validated distinct booted closures:
- Git:
- `/frx/store/d6cbcc76f57fa9c392a80fe20e7499f7a837aab4fb96ea056e624cde95bc70c8-fruix-system-fruix-freebsd`
- `src.txz`:
- `/frx/store/02268e19930facb32e12b6ec191f2e5704d1e81033baf3637a889ad15924ff88-fruix-system-fruix-freebsd`
- validated both guests reached the validated runtime state:
- Shepherd as PID 1
- `sshd` running
- validated distinct materialized source stores and distinct native kernel/bootloader/runtime outputs for the two boots
Current assessment:
- Phase 17.2 is complete
- Fruix can now both build **and boot** systems from distinct declared FreeBSD source revisions
- the next step is Phase 17.3:
- document the intended policy for source provenance, caching, invalidation, and update semantics before installation work depends on it
## 2026-04-03 — Phase 17.1 completed: side-by-side FreeBSD source revisions now coexist in `/frx/store`
Completed work:
- added Phase 17 operating-system templates for distinct source identities:
- `tests/system/phase17-git-source-operating-system.scm.in`
- `tests/system/phase17-txz-source-operating-system.scm.in`
- modeled the Git side with both:
- ref: `stable/15`
- pinned commit:
- `332708a606f6bf0841c1d4a74c0d067f5640fe89`
- modeled the archive side with:
- `https://download.freebsd.org/releases/amd64/15.0-RELEASE/src.txz`
- sha256:
- `83c3e8157b6d7afcae57167fda75693bf1e5f581ca149a6ecb2d398b71bdfab0`
- added side-by-side source coexistence validation:
- `tests/system/run-phase17-source-coexistence.sh`
- the new harness builds:
- Git source build A
- `src.txz` source build
- Git source rebuild B
and verifies:
- Git rebuild stability when pinned by commit
- distinct closure paths for Git vs `src.txz`
- distinct materialized source stores
- distinct native kernel/bootloader/runtime outputs
- correct declared/materialized source metadata in closures and native build info
- continued use of the materialized source root instead of the unused declared transitional `source-root`
- wrote:
- `docs/reports/phase17-side-by-side-source-revisions-freebsd.md`
Validation:
- `PASS phase17-source-coexistence`
- validated side-by-side closures:
- Git closure:
- `/frx/store/d6cbcc76f57fa9c392a80fe20e7499f7a837aab4fb96ea056e624cde95bc70c8-fruix-system-fruix-freebsd`
- `src.txz` closure:
- `/frx/store/02268e19930facb32e12b6ec191f2e5704d1e81033baf3637a889ad15924ff88-fruix-system-fruix-freebsd`
- validated distinct materialized source stores:
- Git:
- `/frx/store/c9928605fa906b90a600dafeebe5005dd18ad3b8e62b7111d9d13ad60ee56490-freebsd-source-stable15-side-a`
- `src.txz`:
- `/frx/store/5eaeff5c6c55a95b6531d9cf2e1824cd4368d81c614608426bee1a5d2a664dc5-freebsd-source-release15-side-b`
- validated distinct native base outputs for the **same** version label:
- `15.0-source-side-by-side`
- validated effective source roots:
- Git:
- `.../tree`
- `src.txz`:
- `.../tree/usr/src`
Current assessment:
- Phase 17.1 is complete
- Fruix can now keep at least two distinct FreeBSD source revisions side by side in `/frx/store` as meaningful native base inputs/outputs
- the next step is Phase 17.2:
- boot systems built from at least two distinct declared source revisions and confirm that the booted metadata tracks those revisions
## 2026-04-03 — Phase 16.3 completed: native FreeBSD base builds now consume materialized source inputs
Completed work:
- refactored native FreeBSD package materialization in `modules/fruix/system/freebsd.scm` so native packages now:
- reconstruct the declared `freebsd-source` from the package plan
- materialize that source under Fruix control
- rewrite the native build plan to use the materialized source root
- add the materialized source store path to package references
- native build manifests now include:
- `declared-source`
- `materialized-source`
- native `.freebsd-native-build-info.scm` now records:
- declared source
- materialized source store path
- materialized source root
- materialized source tree sha256
- effective source details such as resolved Git commit / archive sha256
- native build common manifests now reuse the materialized source tree hash when available
- package materialization caching is now keyed by manifest identity instead of only package name/version, so distinct source-driven variants do not collide in-process
- system closure materialization now keeps a dedicated source-materialization cache and records:
- `metadata/freebsd-source-materializations.scm`
- `materialized-source-store-count`
- `materialized-source-stores`
- system closure references now include the materialized source stores explicitly
- `scripts/fruix.scm` now emits for `fruix system build` and `image`:
- `freebsd_source_materializations_file`
- `materialized_source_store_count`
- `materialized_source_stores`
- added validation artifacts:
- `tests/system/phase16-git-materialized-source-operating-system.scm.in`
- `tests/system/run-phase16-source-driven-native-build.sh`
- wrote:
- `docs/reports/phase16-source-driven-native-builds-freebsd.md`
Validation:
- `PASS phase16-source-driven-native-build`
- `PASS phase16-source-materialization`
- `PASS phase16-declarative-source-build`
- validated a full native system build from declared Git source:
- `https://git.FreeBSD.org/src.git`
- ref: `stable/15`
- resolved commit during validation:
- `332708a606f6bf0841c1d4a74c0d067f5640fe89`
- intentionally declared an unused transitional source-root:
- `/var/empty/fruix-unused-source-root`
and confirmed native build info instead used the materialized source root under `/frx/store/*-freebsd-source-*/tree`
Current assessment:
- Phase 16 is now fully complete
- Fruix can:
- declare FreeBSD source inputs
- materialize them under `/frx/store`
- build native FreeBSD base artifacts from those materialized source snapshots
- the next step is Phase 17:
- build and compare side-by-side source revisions, then boot from them
## 2026-04-03 — Phase 16.2 completed: Fruix now materializes FreeBSD source inputs under its control
Completed work:
- added a new exported source materializer in `modules/fruix/system/freebsd.scm`:
- `materialize-freebsd-source`
- added cache-backed materialization for source kinds:
- `local-tree`
- `git`
- `src-txz`
- added cache locations under:
- `/frx/var/cache/fruix/freebsd-source/git`
- `/frx/var/cache/fruix/freebsd-source/archives`
- materialized source outputs now live in `/frx/store` as:
- `*-freebsd-source-*`
- each materialized source now records:
- declared source
- effective/resolved source
- source store path
- effective source root
- source tree sha256
- cache path
- added automatic effective-root detection so archive-backed sources that unpack as `usr/src/...` are still usable later:
- Git exports use `.../tree`
- `src.txz` archives use `.../tree/usr/src`
- added a new user-facing CLI path in `scripts/fruix.scm`:
- `fruix source materialize SOURCE-FILE`
- new source command options:
- `--source NAME`
- `--store DIR`
- `--cache DIR`
- `--help`
- source CLI now emits machine-readable metadata for:
- declared source fields
- materialized store path/root
- source tree hash
- cache path
- resolved Git commit
- verified archive sha256
- tightened `src-txz` validation so materialization now requires:
- URL
- sha256
- added validation artifacts:
- `tests/system/phase16-git-freebsd-source.scm.in`
- `tests/system/phase16-txz-freebsd-source.scm.in`
- `tests/system/run-phase16-source-materialization.sh`
- wrote:
- `docs/reports/phase16-source-materialization-freebsd.md`
Validation:
- `PASS phase16-source-materialization`
- `PASS phase16-declarative-source-build`
- verified Git source fetch/materialization from:
- `https://git.FreeBSD.org/src.git`
- ref: `stable/15`
- resolved commit during validation:
- `332708a606f6bf0841c1d4a74c0d067f5640fe89`
- verified canonical release archive fetch/materialization from:
- `https://download.freebsd.org/releases/amd64/15.0-RELEASE/src.txz`
- sha256:
- `83c3e8157b6d7afcae57167fda75693bf1e5f581ca149a6ecb2d398b71bdfab0`
- verified repeated materialization returns stable store paths for both the Git and `src.txz` cases
Current assessment:
- Phase 16.2 is complete
- Fruix can now fetch or materialize declared FreeBSD source trees into `/frx/store` with cache-backed provenance under `/frx/var/cache/fruix/freebsd-source`
- the next step is Phase 16.3:
- teach native FreeBSD kernel/world/runtime builds to consume these materialized source artifacts instead of ambient `/usr/src`
## 2026-04-03 — Phase 16.1 completed: FreeBSD source inputs are now explicit Fruix objects
Completed work:

View File

@@ -22,6 +22,16 @@ Completed milestones include:
- `freebsd-native-bootloader`
- `freebsd-native-runtime`
- **Declarative FreeBSD base model**: the FreeBSD base is now an explicit system input via `freebsd-base`, not just an ambient property of the builder host.
- **Declarative FreeBSD source model and materialization**: Fruix can now describe FreeBSD sources explicitly via `freebsd-source` and materialize them from:
- local source trees
- `https://git.FreeBSD.org/src.git`
- official `src.txz` archives such as `https://download.freebsd.org/releases/amd64/15.0-RELEASE/src.txz`
into `/frx/store`, with cache-backed provenance under `/frx/var/cache/fruix/freebsd-source`.
- **Source-driven native base builds**: native FreeBSD kernel/bootloader/runtime artifacts now consume those materialized source snapshots rather than ambient `/usr/src`, and their build metadata records both the declared source and the effective materialized source identity.
- **Side-by-side source revisions**: Fruix can now keep distinct FreeBSD source identities side by side in `/frx/store` and produce distinct native base outputs from them, even when the visible base version label is held constant.
- **Source-driven boot validation**: Fruix can now also boot systems built from distinct declared FreeBSD source revisions while preserving those source identities in image/build metadata.
- **Explicit source policy**: the repo now records how FreeBSD source objects are fetched, cached, identified, invalidated, and consumed by native base builds in `docs/freebsd-source-policy.md`.
- **Minimal installation workflow**: Fruix now has a non-interactive `fruix system install` path that can partition, format, populate, and boot a target image or disk from a declarative system closure.
- **Base upgrade story**: Fruix can now keep distinct declared base versions side by side in `/frx/store` and roll forward / back between them through the normal system deployment flow.
## Major pain points now behind us
@@ -36,7 +46,7 @@ Completed milestones include:
## Major pain points still ahead
- **True store-native runtime artifacts**: some historical build/install prefixes are still embedded in binaries and metadata. They are no longer required at runtime, but the local Guile/guile-extra/Shepherd build/install flow should still be moved to a genuinely store-native prefix from the start.
- **Source reproducibility for the FreeBSD base**: the next major boundary is no longer host-copy boot/runtime assets; it is making source-tree selection/acquisition more reproducible and less tied to a single ambient `/usr/src`.
- **Installer environment**: Fruix now has a host-driven non-interactive install path, but it still lacks a dedicated Fruix-managed installer environment that can boot into an install context and run that workflow from within the target environment.
- **Boot-path simplification**: Fruix now supports both the legacy `freebsd-init+rc.d-shepherd` path and the more Guix-like `shepherd-pid1` path. We still need to decide whether Shepherd PID 1 becomes the preferred/default architecture.
- **Reduce transitional FreeBSD glue**: more of the current bootstrap/activation/runtime setup should become cleaner and less prototype-specific over time.
- **Tooling and platform constraints**: local bhyve remains blocked by missing nested virtualization under Xen, and XO permissions still prevent creating/importing new VDIs; current validation must keep reusing the approved VM/VDI path.
@@ -44,4 +54,4 @@ Completed milestones include:
## Bottom line
Fruix has crossed the most important threshold: it is no longer just a collection of isolated FreeBSD experiments. It can now build declarative FreeBSD system artifacts, boot them on the real target VM, reach the network, serve SSH, run Shepherd as PID 1, operate from `/frx` without depending on temporary runtime-prefix shims, build native FreeBSD base artifacts into `/frx/store`, and roll forward / back between declared base versions. The biggest remaining work is no longer “can this boot?” but “how reproducible and source-declarative can we make the native FreeBSD base path from here?”
Fruix has crossed the most important threshold: it is no longer just a collection of isolated FreeBSD experiments. It can now build declarative FreeBSD system artifacts, boot them on the real target VM, reach the network, serve SSH, run Shepherd as PID 1, operate from `/frx` without depending on temporary runtime-prefix shims, build native FreeBSD base artifacts into `/frx/store`, roll forward / back between declared base versions, materialize declared FreeBSD source inputs into `/frx/store`, drive native base builds from those materialized source snapshots, boot systems from distinct source revisions, explain the source provenance/invalidation rules explicitly, and install a declarative system onto a target image through a repeatable Fruix workflow. The biggest remaining work is no longer “can this build/install at all?” but “how does this become a fuller installer/deployment/generation story?”

View File

@@ -0,0 +1,330 @@
# Fruix FreeBSD Source Policy
This document records the current intended policy for how Fruix models, fetches, caches, identifies, invalidates, and consumes FreeBSD source inputs.
It reflects the behavior implemented through Plan 4 Phases 16 and 17.
## 1. Source object model
Fruix represents FreeBSD source inputs explicitly with `freebsd-source`.
Currently supported source kinds are:
- `local-tree`
- `git`
- `src-txz`
A source object records some combination of:
- `name`
- `kind`
- `url`
- `path`
- `ref`
- `commit`
- `sha256`
Fruix distinguishes between:
- the **declared source**
- what the operator asked for
- the **effective source**
- what Fruix actually resolved/materialized and built from
That distinction matters most for Git refs and local-tree snapshots.
## 2. Source identity boundaries
### `local-tree`
Declared selector:
- local filesystem path
Effective identity boundary:
- filtered source-tree hash computed from `mtree`
Current rule:
- Fruix computes source identity from:
- `mtree -c -k type,link,size,mode,sha256digest`
- comment lines are removed before hashing
This avoids unstable `mtree` comment headers such as:
- date
- user
- machine
So the effective local-tree identity is content-oriented rather than host-comment-oriented.
### `git`
Declared selectors:
- `commit`, or
- `ref`
Effective identity boundary:
- resolved commit
Current rule:
- if `commit` is present, Fruix uses it as the fetch selector
- otherwise, Fruix uses `ref`
- after fetch, Fruix records the resolved commit as the effective Git identity
Policy consequence:
- a Git **ref** is a convenience selector
- a Git **commit** is the reproducibility boundary
Therefore:
- use `ref` alone for exploratory/latest tracking when drift is acceptable
- use `commit` for reproducible builds, side-by-side comparisons, installation artifacts, and rollback-sensitive workflows
- it is valid to record both:
- `ref` for human provenance
- `commit` for stable identity
### `src-txz`
Declared selectors:
- URL
- expected `sha256`
Effective identity boundary:
- verified archive `sha256`
Policy consequence:
- `src-txz` materialization is only valid when `sha256` is declared
- archive URL alone is not enough
## 3. Cache policy
Default cache root:
- `/frx/var/cache/fruix/freebsd-source`
Current cache layout:
- Git:
- `/frx/var/cache/fruix/freebsd-source/git/<hash>.git`
- release/snapshot archives:
- `/frx/var/cache/fruix/freebsd-source/archives/<hash>-src.txz`
### Git cache behavior
Fruix keeps a bare repository cache keyed by source URL.
Current behavior:
- initialize bare repo if absent
- ensure `origin` matches the declared URL
- fetch the requested selector from `origin`
- archive the resolved commit into the materialized store object
Policy:
- the cache is a transport/proxy optimization, not the identity boundary
- the identity boundary is the resolved commit recorded in the effective source and materialization metadata
### `src-txz` cache behavior
Fruix keeps the downloaded archive under the cache root.
Current behavior:
- if a cached archive exists, hash it
- if its hash does not match the declared `sha256`, delete it
- fetch the archive if missing
- verify the downloaded archive hash
- fail if the verified hash does not match the declared `sha256`
Policy:
- the cache is reusable only when the declared hash still matches
- hash mismatch invalidates the cached archive immediately
### `local-tree`
Current behavior:
- no separate network cache
- Fruix snapshots the local tree into a materialized store object
Policy:
- the local path is only a selector to a mutable host tree
- the materialized snapshot and its filtered tree hash are the meaningful Fruix identity boundary
## 4. Materialized source store policy
Materialized FreeBSD sources are stored in:
- `/frx/store/*-freebsd-source-*`
Current manifest inputs for a materialized source object include:
- materializer version
- declared source
- effective source
- source identity tuple
- for example resolved commit, archive sha256, or local tree hash
Policy consequence:
- changing any of those identity inputs should produce a distinct source store path
- changing materialization semantics should bump the materializer version
Current materializer version:
- `freebsd-source-materializer-version = "2"`
## 5. Effective source root policy
Not every source unpacks to the same top-level directory shape.
Current behavior:
- if `tree/Makefile` exists, effective root is:
- `tree`
- else if `tree/usr/src/Makefile` exists, effective root is:
- `tree/usr/src`
This is why:
- Git exports materialize effectively at `.../tree`
- official `src.txz` archives materialize effectively at `.../tree/usr/src`
Policy:
- native builds must consume the detected effective source root, not assume `/usr/src`
- the declared transitional `source-root` may still be recorded for provenance, but it is not the effective build root once materialization is in use
## 6. Native build invalidation policy
Native FreeBSD kernel/world/runtime/bootloader outputs must be invalidated by source identity, not just by package name/version.
Current behavior:
- native package materialization rewrites the install plan to use the materialized source root
- native manifests record both:
- `declared-source`
- `materialized-source`
- package materialization caching keys on the full manifest identity rather than only package name/version
- native build common metadata includes:
- `source-root`
- `source-tree-sha256`
- kernconf hash
- target metadata
Policy consequence:
- two builds with the same visible base version label but different source identities must still produce different native output store paths
- that behavior is intentional and required
## 7. Closure provenance policy
System closures and images should preserve enough source metadata to explain exactly what source snapshot was used.
Current closure/image metadata includes:
- `metadata/freebsd-source.scm`
- `metadata/freebsd-source-materializations.scm`
- `materialized_source_store_count`
- `materialized_source_stores`
Native `.freebsd-native-build-info.scm` records:
- declared source
- materialized source store path
- materialized source root
- materialized source tree hash
- effective Git commit or archive hash when applicable
Policy:
- source provenance is part of the closure boundary, not just a transient fetch detail
## 8. Update policy
### Recommended operator policy
For reproducible and rollback-sensitive workflows:
- prefer Git sources pinned by `commit`
- prefer archive sources pinned by `sha256`
- treat `local-tree` as a development/debugging input unless the resulting materialized snapshot is itself the explicit artifact being compared
### Moving refs
A moving Git ref is expected to drift over time.
Policy:
- if only `ref` is declared, Fruix may legitimately produce a new materialized source store later
- that new materialized source identity should then invalidate native outputs
- this is expected behavior, not a cache bug
### Installed/deployed systems
For installation artifacts and generation management, the source identity should be stable enough to answer:
- what exact source revision produced this system?
- can it be rebuilt later?
- should this update be considered a new generation boundary?
That means later installation/deployment work should prefer:
- Git commit-pinned sources
- hash-pinned archives
rather than floating refs alone.
## 9. Patch/transformation policy
Fruix does not yet expose a first-class patch queue or transformation layer on top of `freebsd-source`.
Current policy until that exists:
- the declared source object should be treated as the full upstream-source identity boundary
- when a patch/transformation layer is introduced later, it must become part of the materialized source identity and manifest versioning
In other words:
- applying patches without changing source identity metadata would be wrong
## 10. Relation to Guix-inspired semantics
This policy follows the same important high-level idea as Guix:
- selectors are not enough
- resolved, content-stable identities matter
- cached transport objects are not the same as reproducible store identities
Fruix applies that idea in FreeBSD-specific form:
- `src.txz` handling
- FreeBSD source-tree effective-root detection
- mtree-based FreeBSD tree identity
- native FreeBSD base builds driven from materialized source snapshots under `/frx/store`
## 11. Practical summary
Use these rules:
- **Want reproducibility?**
- pin Git by `commit`
- pin archives by `sha256`
- **Want side-by-side comparison?**
- keep distinct source objects and let Fruix materialize them separately
- **Want native base outputs to differ only when they should?**
- rely on materialized source identity, not mutable `/usr/src`
- **Want stable deployment provenance?**
- preserve the closure metadata files and materialized source store references

View File

@@ -0,0 +1,182 @@
# Phase 16.3: build native FreeBSD base artifacts from materialized source inputs
Date: 2026-04-03
## Goal
Phase 16.3 closes the immediate gap between:
- declarative/materialized FreeBSD source inputs, and
- the native kernel/world/runtime/bootloader build path.
Before this step, Fruix could already:
- describe FreeBSD sources explicitly, and
- materialize them into `/frx/store`
but native base builds still consumed the ambient host source tree through the package plan's `source-root`.
After this step, native FreeBSD base artifacts are driven by the declared source input's **materialized store snapshot** instead.
## Implementation
### Native package materialization now injects materialized source inputs
`modules/fruix/system/freebsd.scm` now prepares native FreeBSD packages differently from copy-based packages.
For native packages, Fruix now:
1. reconstructs the declared `freebsd-source` from the package plan
2. materializes that source under Fruix control
3. rewrites the native build plan to use the materialized source root
4. records both the declared source and the materialized/effective source in the output metadata
5. adds the materialized source store path to the package references
This means native package output identity now depends on:
- the declared source object
- the resolved/materialized source identity
- the materialized source root and tree hash
rather than on an ambient host tree path.
### Native build manifests now record both declared and materialized source identity
Native package manifests now include:
- `declared-source`
- `materialized-source`
- the `native-build-common` block derived from the materialized source root
Native `.freebsd-native-build-info.scm` files now record:
- declared source
- materialized source store path
- materialized source root
- materialized source tree hash
- effective source details such as resolved Git commit or verified archive SHA256
### Source tree hashing now reuses materialized-source metadata when available
The native build common manifest now prefers the materialized source tree hash when one is already known, rather than recomputing it unnecessarily.
### Package caching is now manifest-driven rather than name/version-only
The in-process package materialization cache for native builds now keys on the computed manifest identity rather than only:
- package name
- package version
That is important once multiple source-driven variants of the same native package version can coexist.
### System closures now record source materializations explicitly
`materialize-operating-system` now keeps a dedicated source-materialization cache while building native packages.
Closures now record:
- `metadata/freebsd-source-materializations.scm`
- `materialized-source-store-count`
- `materialized-source-stores`
and system references now include the materialized source stores explicitly.
This makes the source artifacts themselves part of the closure provenance boundary.
### `fruix system` metadata now exposes materialized source stores
`scripts/fruix.scm` now emits for `fruix system build` and `image`:
- `freebsd_source_materializations_file`
- `materialized_source_store_count`
- `materialized_source_stores`
This makes it easy for harnesses and operators to see which source store snapshot was actually used for a build.
## New files
Added:
- `tests/system/phase16-git-materialized-source-operating-system.scm.in`
- `tests/system/run-phase16-source-driven-native-build.sh`
- `docs/reports/phase16-source-driven-native-builds-freebsd.md`
## Validation
Passing runs:
- `PASS phase16-source-driven-native-build`
- `PASS phase16-source-materialization`
- `PASS phase16-declarative-source-build`
### Source-driven native build validation
Validated a native system build from a declared Git source:
- source kind:
- `git`
- source URL:
- `https://git.FreeBSD.org/src.git`
- source ref:
- `stable/15`
- resolved commit during validation:
- `332708a606f6bf0841c1d4a74c0d067f5640fe89`
The test intentionally declared an **unused transitional source-root**:
- `/var/empty/fruix-unused-source-root`
and still built successfully, proving the native build path now uses the materialized source snapshot rather than the ambient declared root.
Confirmed build metadata:
```text
closure_path=/frx/store/efed0b8ee31b4f3f97b8ae04317ccd2f3f8a88e332172e2bf0bb4295271c6023-fruix-system-fruix-freebsd
kernel_store=/frx/store/c7e094a450f017a35c2d4d66451dcda893bc4ac27d810295fa491e41fdae8551-freebsd-native-kernel-15.0-STABLE-git-materialized
bootloader_store=/frx/store/36432acf23cdff7eb7601bd314ea82d75a5e7fbcd3480d753117f4c5f9a7c7db-freebsd-native-bootloader-15.0-STABLE-git-materialized
runtime_store=/frx/store/b6f9ebb5c844aedc3d4395ad08542eb67965a19fc28f68fb0f4b55f91aef1c8f-freebsd-native-runtime-15.0-STABLE-git-materialized
materialized_source_store=/frx/store/bcbf7554ce43d40b1bdc98823eb450ce050529b34bbe2dae2a9df48688004c4b-freebsd-source-stable15-network-source
materialized_source_root=/frx/store/bcbf7554ce43d40b1bdc98823eb450ce050529b34bbe2dae2a9df48688004c4b-freebsd-source-stable15-network-source/tree
resolved_commit=332708a606f6bf0841c1d4a74c0d067f5640fe89
source_driven_native_build=ok
```
The harness also verified that:
- closure metadata records exactly one materialized source store
- `metadata/freebsd-source-materializations.scm` exists
- `metadata/store-layout.scm` records the materialized source store count/path
- native build info files for kernel, bootloader, and runtime all record:
- a `declared-source` block with `ref . "stable/15"`
- a `materialized-source` block
- the materialized source store path
- the materialized source root
- the resolved Git commit
- native build info files do **not** use the unused declared source root as their actual `source-root`
### Regression checks
Re-ran:
- `PASS phase16-source-materialization`
- `PASS phase16-declarative-source-build`
The local-tree declarative source build now also drives native base outputs from a materialized local source snapshot in `/frx/store`, while preserving the same declared source metadata interface.
## Result
Phase 16.3 is complete.
Fruix native FreeBSD base artifacts are now built from the declared source input's materialized store snapshot rather than directly from ambient `/usr/src`.
That means Fruix has crossed the key source boundary planned for Phase 16:
- source inputs can be declared
- source inputs can be fetched/materialized
- native base artifacts can now consume those materialized source inputs
The next logical phase is no longer source modeling itself, but what to do with that stronger source boundary:
- side-by-side source revisions
- boot validation from distinct source revisions
- and then installation/deployment ergonomics on top of those source-driven native builds

View File

@@ -0,0 +1,256 @@
# Phase 16.2: materialize declarative FreeBSD source inputs under Fruix control
Date: 2026-04-03
## Goal
Phase 16.2 moves from merely *describing* FreeBSD source inputs to actually *materializing* them under Fruix control.
The objective in this subphase was not yet to switch the native base build path away from ambient `/usr/src`. Instead, it was to establish the missing fetch/materialization layer that later phases can consume.
That means Fruix now knows how to:
- fetch a Git-backed FreeBSD source declaration
- download and verify a `src.txz` declaration
- materialize the resulting source tree into `/frx/store`
- cache downloaded source state under `/frx/var/cache/fruix/freebsd-source`
- record stable source metadata for later native builds
## Implementation
### New source materializer in `modules/fruix/system/freebsd.scm`
Added:
- `materialize-freebsd-source`
and exported it for use by the Fruix CLI.
This materializer now supports all currently modeled source kinds:
- `local-tree`
- `git`
- `src-txz`
### Source artifacts are now first-class store objects
Materialized source outputs are now stored in paths of the form:
- `/frx/store/<hash>-freebsd-source-<name>`
Each source output contains:
- `tree/` or an auto-detected nested source root beneath it
- `.fruix-source`
- `.freebsd-source-info.scm`
- `.references`
The source info file records at least:
- declared source
- effective/resolved source
- materialized store path
- effective source root
- source tree SHA256
- cache path used to produce it
### Cache layout added under `/frx/var/cache/fruix/freebsd-source`
The new materializer caches downloaded source state under:
- `/frx/var/cache/fruix/freebsd-source/git/...`
- `/frx/var/cache/fruix/freebsd-source/archives/...`
Current behavior:
- Git sources use a cached bare repository
- `src.txz` sources use a cached archive file
- repeated materialization of the same resolved source identity reuses the same store output path
### Git source handling
Git materialization now:
- uses `https://git.FreeBSD.org/src.git`
- supports declarations by ref and/or commit
- fetches the selected ref/commit into a cached bare repository
- resolves refs to a concrete commit
- exports that commit into a store materialization
This means Fruix can now represent moving refs declaratively while still recording the exact resolved commit used for a given materialized source tree.
### `src.txz` source handling
`src.txz` materialization now:
- downloads the declared archive URL with `fetch`
- requires and verifies SHA256 for materialization
- extracts the archive into a store materialization
- records both the declared hash and the resulting source tree hash
For release archives, the canonical shorter URL form is now used in validation and documentation, for example:
- `https://download.freebsd.org/releases/amd64/15.0-RELEASE/src.txz`
rather than the longer doubled-architecture variant.
### Auto-detect the effective source root inside materialized trees
Git exports place the source tree directly at the materialized root.
Official `src.txz` archives instead unpack as:
- `usr/src/...`
inside the extracted directory tree.
To make this usable for later native builds, the materializer now auto-detects the effective source root and records it explicitly. For example:
- Git materialization root:
- `/frx/store/...-freebsd-source-stable15-git-ref/tree`
- `src.txz` materialization root:
- `/frx/store/...-freebsd-source-release15-src-txz/tree/usr/src`
### New user-facing CLI command
Added a new user-facing command path in `scripts/fruix.scm`:
- `fruix source materialize SOURCE-FILE`
with options:
- `--source NAME`
- `--store DIR`
- `--cache DIR`
- `--help`
The command emits machine-readable metadata including:
- declared source fields
- materialized store path
- effective source root
- source tree hash
- cache path
- resolved Git commit if applicable
- verified archive hash if applicable
This gives Phase 16.2 an operator-usable entry point rather than limiting it to internal Scheme helpers.
### Validation tightened for archive-backed sources
`src-txz` source validation now requires:
- URL
- SHA256
This is the right reproducibility boundary for archive downloads.
## Guix comparison
This step continues to mirror the most useful Guix source boundary without copying it mechanically:
- Guix models source objects with `origin`
- Git-backed origins use `git-reference`
Fruix's source materializer now plays a similar role for FreeBSD-specific source inputs:
- local tree snapshots
- FreeBSD Git refs/commits
- official `src.txz` archives
The key preserved idea is the same: source identity should become an explicit, recorded, materialized input rather than ambient host state.
## New files
Added:
- `tests/system/phase16-git-freebsd-source.scm.in`
- `tests/system/phase16-txz-freebsd-source.scm.in`
- `tests/system/run-phase16-source-materialization.sh`
## Validation
Passing run:
- `PASS phase16-source-materialization`
- workdir:
- `/tmp/fruix-phase16-source-materialization.QGuXi1`
### Git validation
Validated a Git declaration:
- name:
- `stable15-git-ref`
- URL:
- `https://git.FreeBSD.org/src.git`
- ref:
- `stable/15`
Resolved/materialized result:
```text
materialized_source_store=/frx/store/dd1cc6b5ffa95b4d0c0f269522d5739da05e0f4ae81b1b314221d28b49d1981f-freebsd-source-stable15-git-ref
materialized_source_root=/frx/store/dd1cc6b5ffa95b4d0c0f269522d5739da05e0f4ae81b1b314221d28b49d1981f-freebsd-source-stable15-git-ref/tree
materialized_source_tree_sha256=d0d8e085d913a511d7fa1ba410040eb697a4cef800f354a092c65249ab3c4eb4
materialized_source_cache_path=/frx/var/cache/fruix/freebsd-source/git/9d432c47301c356bd2cede3400de40870e0b541b276888e34c68b882b9b894c7.git
materialized_source_commit=332708a606f6bf0841c1d4a74c0d067f5640fe89
```
The harness also confirmed:
- repeated materialization returned the same store path
- the cached Git repository exists
- the materialized tree contains:
- `Makefile`
- `sys/conf/newvers.sh`
### `src.txz` validation
Validated an archive declaration:
- name:
- `release15-src-txz`
- URL:
- `https://download.freebsd.org/releases/amd64/15.0-RELEASE/src.txz`
- SHA256:
- `83c3e8157b6d7afcae57167fda75693bf1e5f581ca149a6ecb2d398b71bdfab0`
Resolved/materialized result:
```text
materialized_source_store=/frx/store/2e7857fb2c067b32acb482d048b8d1c2eeffdecd213108b3b0a4b2a87d56bc68-freebsd-source-release15-src-txz
materialized_source_root=/frx/store/2e7857fb2c067b32acb482d048b8d1c2eeffdecd213108b3b0a4b2a87d56bc68-freebsd-source-release15-src-txz/tree/usr/src
materialized_source_tree_sha256=afbe26f2213a19685fc2c3b875d26fab67e2cfcd605716cc66f669dabeaf7572
materialized_source_cache_path=/frx/var/cache/fruix/freebsd-source/archives/64ac7cc7d27435406995d63ef0b87ed0c485ce953ee8e9126127ca8f2a451d98-src.txz
materialized_source_sha256=83c3e8157b6d7afcae57167fda75693bf1e5f581ca149a6ecb2d398b71bdfab0
```
The harness also confirmed:
- repeated materialization returned the same store path
- the cached archive exists
- the effective materialized source root was detected as `tree/usr/src`
- that root contains:
- `Makefile`
- `sys/conf/newvers.sh`
### Regression check
Also re-ran:
- `PASS phase16-declarative-source-build`
This confirmed the new source materialization work did not break the earlier Phase 16.1 declarative source model path.
## Result
Phase 16.2 is complete.
Fruix can now fetch or materialize declared FreeBSD source inputs into `/frx/store` with cache-backed provenance under `/frx/var/cache/fruix/freebsd-source`.
The next step is now clear and narrower:
- teach native FreeBSD kernel/world/runtime builds to consume these materialized source artifacts instead of ambient `/usr/src`
That will be the real handoff from source acquisition to source-driven native base builds.

View File

@@ -0,0 +1,141 @@
# Phase 17.1: side-by-side FreeBSD source revisions in `/frx/store`
Date: 2026-04-03
## Goal
Phase 17.1 verifies that Fruix can treat FreeBSD source revisions the same way it already treats other declarative inputs:
- as explicit inputs,
- as side-by-side store objects,
- and as drivers of distinct native base outputs.
The key question was no longer whether Fruix could materialize a source tree at all. Phase 16 already established that.
The question here was stricter:
- can two distinct FreeBSD source inputs coexist,
- can they both drive native base builds,
- and can they do so even when the user-facing base version label is the same?
That last point matters because it proves the result is driven by source identity rather than by an arbitrary version-string rename.
## Implementation
Added two Phase 17 operating-system templates:
- `tests/system/phase17-git-source-operating-system.scm.in`
- `tests/system/phase17-txz-source-operating-system.scm.in`
These model two distinct source identities:
- a Git source with both:
- ref: `stable/15`
- pinned commit: `332708a606f6bf0841c1d4a74c0d067f5640fe89`
- an official release archive source:
- `https://download.freebsd.org/releases/amd64/15.0-RELEASE/src.txz`
- sha256:
- `83c3e8157b6d7afcae57167fda75693bf1e5f581ca149a6ecb2d398b71bdfab0`
Added validation harness:
- `tests/system/run-phase17-source-coexistence.sh`
## Validation design
The harness builds three times:
1. Git-backed system build
2. `src.txz`-backed system build
3. Git-backed rebuild
The Git and `src.txz` systems intentionally share the same:
- base name:
- `source-side-by-side`
- base version label:
- `15.0-source-side-by-side`
while differing only in their declared source/release metadata.
This means distinct outputs cannot be explained away by a version-label rename.
The harness verifies:
- Git rebuild stability when the Git source is pinned by commit
- distinct closure paths for Git vs `src.txz`
- distinct materialized source store paths
- distinct native kernel/bootloader/runtime store paths
- zero host-base stores in both builds
- one materialized source store in each closure
- correct closure metadata for:
- declared source
- materialized source
- materialized source store count/path
- correct native build info for:
- kernel
- runtime
- effective source roots:
- Git: `.../tree`
- `src.txz`: `.../tree/usr/src`
- the continued separation between:
- declared transitional `source-root`
- actual materialized source root used by the native build
## Results
Passing validation:
- `PASS phase17-source-coexistence`
Observed side-by-side closures:
```text
git_closure=/frx/store/d6cbcc76f57fa9c392a80fe20e7499f7a837aab4fb96ea056e624cde95bc70c8-fruix-system-fruix-freebsd
git_closure_rebuild=/frx/store/d6cbcc76f57fa9c392a80fe20e7499f7a837aab4fb96ea056e624cde95bc70c8-fruix-system-fruix-freebsd
txz_closure=/frx/store/02268e19930facb32e12b6ec191f2e5704d1e81033baf3637a889ad15924ff88-fruix-system-fruix-freebsd
base_version_label=15.0-source-side-by-side
same_base_version_label_distinct_sources=ok
```
Observed source identities:
```text
git_source_kind=git
git_source_ref=stable/15
git_source_commit=332708a606f6bf0841c1d4a74c0d067f5640fe89
git_materialized_source_store=/frx/store/c9928605fa906b90a600dafeebe5005dd18ad3b8e62b7111d9d13ad60ee56490-freebsd-source-stable15-side-a
git_materialized_source_root=/frx/store/c9928605fa906b90a600dafeebe5005dd18ad3b8e62b7111d9d13ad60ee56490-freebsd-source-stable15-side-a/tree
txz_source_kind=src-txz
txz_source_url=https://download.freebsd.org/releases/amd64/15.0-RELEASE/src.txz
txz_source_sha256=83c3e8157b6d7afcae57167fda75693bf1e5f581ca149a6ecb2d398b71bdfab0
txz_materialized_source_store=/frx/store/5eaeff5c6c55a95b6531d9cf2e1824cd4368d81c614608426bee1a5d2a664dc5-freebsd-source-release15-side-b
txz_materialized_source_root=/frx/store/5eaeff5c6c55a95b6531d9cf2e1824cd4368d81c614608426bee1a5d2a664dc5-freebsd-source-release15-side-b/tree/usr/src
```
Observed native base outputs for the same version label:
```text
git_native_base_stores=/frx/store/4b615431ec25c500a3bf0ed70ce39e2ebf4f584994a53756268e4383962bc86b-freebsd-native-kernel-15.0-source-side-by-side,/frx/store/3a5a0b2b88b4757cf9cb4e3040f992d8fdb5bd9a7f1b186da983854cd95392c5-freebsd-native-bootloader-15.0-source-side-by-side,/frx/store/177f78e7f2932986a380187eb09dc34cc2cd9a146c5ed1fe1f00aae15ddf78d9-freebsd-native-runtime-15.0-source-side-by-side
txz_native_base_stores=/frx/store/0c5141a86fa9c1974102f2bd8766eb3ab787b97dcccb71f17d80aefbe8ed4f3e-freebsd-native-kernel-15.0-source-side-by-side,/frx/store/3de6592f50a735d8461662cb393fc413325ce24ded45d4bb494525896f8cb5eb-freebsd-native-bootloader-15.0-source-side-by-side,/frx/store/46d256305198ee7d745b9032c71085aba97d55fdf7a0d3d2017dd4455173205d-freebsd-native-runtime-15.0-source-side-by-side
```
Those outputs differ only by content-address prefix, not by the human-readable version suffix. That is exactly the property Phase 17.1 needed.
## Key observation
A moving Git ref by itself is not a reproducibility boundary. For the reproducible half of this validation, the Git source was pinned by commit while still retaining its `stable/15` ref metadata.
This fits the intended Fruix model:
- refs are useful selectors,
- commits are the stable Git identity boundary,
- archive SHA256 values are the stable `src.txz` identity boundary.
## Result
Phase 17.1 is complete.
Fruix can now hold at least two distinct FreeBSD source revisions side by side in `/frx/store` and build distinct native FreeBSD base artifacts from them, even when the visible base version label is kept the same.

View File

@@ -0,0 +1,77 @@
# Phase 17.3: clarify FreeBSD source provenance, caching, and update policy
Date: 2026-04-03
## Goal
Phase 17.3 turns the source behavior implemented in Phases 16 through 17.2 into an explicit repo-level policy.
The main question was no longer whether Fruix *can* fetch and boot from declared FreeBSD sources. It can.
The question here is:
- how should operators think about source selectors versus stable source identity?
- what exactly lives in cache versus store?
- when should native outputs be invalidated?
- what is the intended policy for moving Git refs, pinned commits, and archive hashes?
## Added documentation
Added:
- `docs/freebsd-source-policy.md`
This document explains:
- supported source kinds:
- `local-tree`
- `git`
- `src-txz`
- declared source vs effective source
- cache locations under:
- `/frx/var/cache/fruix/freebsd-source`
- materialized source outputs under:
- `/frx/store/*-freebsd-source-*`
- effective identity rules for:
- local-tree snapshots
- Git refs/commits
- verified `src.txz` archives
- effective source root detection rules:
- `tree`
- `tree/usr/src`
- native build invalidation policy
- closure provenance policy
- update policy for moving refs vs pinned commits
- the current no-hidden-patch-layer rule
## Key policy conclusions
The repo now states clearly that:
- Git refs are selectors, not stable reproducibility boundaries
- resolved Git commits are the effective Git identity boundary
- `src.txz` URLs are not enough by themselves; `sha256` is required
- local trees are mutable selectors; the materialized snapshot and filtered tree hash are the meaningful Fruix identity
- native FreeBSD base outputs must invalidate when the materialized source identity changes, even if the visible base version label does not
- cache objects are transport optimizations, not the final identity boundary
## Relation to validated behavior
The new policy document matches the validated Phase 17 behavior:
- Phase 17.1 proved that distinct source identities can coexist side by side and produce different native outputs for the same visible base version label
- Phase 17.2 proved that systems built from those distinct source identities can boot successfully through the validated native path
## Result
Phase 17.3 is complete.
The repo now clearly explains how FreeBSD source objects are:
- fetched
- cached
- identified
- invalidated
- and consumed by native FreeBSD base builds
That completes Phase 17 and leaves Fruix in a better position to begin the installation/deployment work in Phase 18.

View File

@@ -0,0 +1,98 @@
# Phase 17.2: boot systems from distinct declared FreeBSD source revisions
Date: 2026-04-03
## Goal
Phase 17.2 extends Phase 17.1 from:
- side-by-side source-driven builds
to:
- side-by-side source-driven **boots**.
The important requirement was not visible runtime behavior differences between the guests. The requirement was that Fruix should be able to:
- build bootable systems from at least two distinct declared source revisions
- boot both systems with the validated native base path
- preserve source identity in the resulting system/image metadata
## Implementation
Added boot validation harness:
- `tests/system/run-phase17-source-revisions-qemu.sh`
This script renders the Phase 17 source templates from Phase 17.1 and then boots two systems under the already-validated QEMU/UEFI/TCG path:
- Git-backed source:
- ref: `stable/15`
- pinned commit: `332708a606f6bf0841c1d4a74c0d067f5640fe89`
- release archive source:
- `https://download.freebsd.org/releases/amd64/15.0-RELEASE/src.txz`
- sha256:
- `83c3e8157b6d7afcae57167fda75693bf1e5f581ca149a6ecb2d398b71bdfab0`
The harness reuses:
- `tests/system/run-phase11-shepherd-pid1-qemu.sh`
and checks both guest runtime behavior and image/build provenance metadata.
## Validation
Passing run:
- `PASS phase17-source-revisions-qemu`
Confirmed booted systems from two distinct source identities:
```text
git_closure=/frx/store/d6cbcc76f57fa9c392a80fe20e7499f7a837aab4fb96ea056e624cde95bc70c8-fruix-system-fruix-freebsd
txz_closure=/frx/store/02268e19930facb32e12b6ec191f2e5704d1e81033baf3637a889ad15924ff88-fruix-system-fruix-freebsd
```
Confirmed source metadata recorded in image/build artifacts:
```text
git_source_kind=git
git_source_ref=stable/15
git_source_commit=332708a606f6bf0841c1d4a74c0d067f5640fe89
git_materialized_source_store=/frx/store/c9928605fa906b90a600dafeebe5005dd18ad3b8e62b7111d9d13ad60ee56490-freebsd-source-stable15-side-a
txz_source_kind=src-txz
txz_source_url=https://download.freebsd.org/releases/amd64/15.0-RELEASE/src.txz
txz_source_sha256=83c3e8157b6d7afcae57167fda75693bf1e5f581ca149a6ecb2d398b71bdfab0
txz_materialized_source_store=/frx/store/5eaeff5c6c55a95b6531d9cf2e1824cd4368d81c614608426bee1a5d2a664dc5-freebsd-source-release15-side-b
```
Confirmed distinct native base outputs used by the two boots:
```text
git_native_base_stores=/frx/store/4b615431ec25c500a3bf0ed70ce39e2ebf4f584994a53756268e4383962bc86b-freebsd-native-kernel-15.0-source-side-by-side,/frx/store/3a5a0b2b88b4757cf9cb4e3040f992d8fdb5bd9a7f1b186da983854cd95392c5-freebsd-native-bootloader-15.0-source-side-by-side,/frx/store/177f78e7f2932986a380187eb09dc34cc2cd9a146c5ed1fe1f00aae15ddf78d9-freebsd-native-runtime-15.0-source-side-by-side
txz_native_base_stores=/frx/store/0c5141a86fa9c1974102f2bd8766eb3ab787b97dcccb71f17d80aefbe8ed4f3e-freebsd-native-kernel-15.0-source-side-by-side,/frx/store/3de6592f50a735d8461662cb393fc413325ce24ded45d4bb494525896f8cb5eb-freebsd-native-bootloader-15.0-source-side-by-side,/frx/store/46d256305198ee7d745b9032c71085aba97d55fdf7a0d3d2017dd4455173205d-freebsd-native-runtime-15.0-source-side-by-side
```
Confirmed both guests booted successfully through the validated PID 1 path:
- Shepherd ran as PID 1 in both boots
- `sshd` was running in both boots
- boot backend:
- `qemu-uefi-tcg`
Validation artifacts:
- Git serial log:
- `/tmp/fruix-phase17-source-qemu.7Za50q/git/serial.log`
- `src.txz` serial log:
- `/tmp/fruix-phase17-source-qemu.7Za50q/txz/serial.log`
## Result
Phase 17.2 is complete.
Fruix now boots systems built from at least two distinct declared FreeBSD source revisions while preserving those source identities in system/image metadata.
That means Phase 17 is no longer just about build-time coexistence. The validated native boot path now also works across distinct source identities.

View File

@@ -0,0 +1,167 @@
# Phase 18.1: minimal non-interactive Fruix installation flow on FreeBSD
Date: 2026-04-03
## Goal
Phase 18.1 turns Fruix's existing closure/rootfs/image machinery into a real installation workflow.
The goal is not a polished installer yet. The goal is a repeatable, non-interactive install path that can:
- take a declarative Fruix system,
- partition and format a target disk or image,
- populate it with the selected system closure,
- install boot assets,
- and leave the target bootable.
## Implementation
### New install spec and installer entry point
Added in `modules/fruix/system/freebsd.scm`:
- `operating-system-install-spec`
- `install-operating-system`
The installer currently supports:
- raw image-file targets
- `/dev/...` block-device targets
For raw image-file targets, Fruix now:
- creates/truncates the target image
- attaches it with `mdconfig`
- creates a GPT layout
- adds:
- an EFI partition
- a FreeBSD UFS root partition
- formats them with:
- `newfs_msdos`
- `newfs`
- mounts them
- stages the declarative Fruix rootfs
- copies the closure and referenced `/frx/store` items into the installed root
- installs `loader.efi` to `EFI/BOOT/BOOTX64.EFI`
- writes install metadata to:
- `/var/lib/fruix/install.scm`
### Rootfs staging was factored for reuse
Added internal helper:
- `populate-rootfs-from-closure`
This lets image generation and installation reuse the same rootfs staging logic while differing in how the final target is created.
### New CLI action
Added user-facing command support in `scripts/fruix.scm`:
- `fruix system install`
New system option:
- `--target PATH`
Install metadata now emits machine-readable fields including:
- `target`
- `target_kind`
- `target_device`
- `esp_device`
- `root_device`
- `install_metadata_path`
- `disk_capacity`
- `root_size`
- declared/materialized FreeBSD source metadata
- closure/native/runtime store metadata
### Validation harnesses
Added:
- `tests/system/phase18-install-operating-system.scm.in`
- `tests/system/run-phase18-system-install.sh`
The Phase 18 install validation uses the already-validated boot mode:
- `freebsd-init+rc.d-shepherd`
This keeps the install-flow validation focused on installation mechanics rather than on the separate Shepherd-as-PID-1 boot path.
## Validation
Passing validation:
- `PASS phase18-system-install`
- regression re-check:
- `PASS phase17-source-revisions-qemu`
Validated install result:
```text
target_image=/tmp/fruix-phase18-install.CyrgKc/installed.img
target_kind=raw-file
disk_capacity=12g
root_size=10g
closure_path=/frx/store/ee486985797103aa5d3eeeef7f2cf066bcbd6839cd81083dbe626a594e71a703-fruix-system-fruix-freebsd
freebsd_source_kind=git
freebsd_source_ref=stable/15
freebsd_source_commit=332708a606f6bf0841c1d4a74c0d067f5640fe89
materialized_source_store=/frx/store/a892afb425235de71c9da38884e2ebdba5dafd3a1993f432fe7c446f5af2151f-freebsd-source-stable15-install-source
native_base_store_count=3
install_metadata_path=/var/lib/fruix/install.scm
esp_fstype=msdosfs
root_fstype=ufs
shepherd_status=running
sshd_status=running
install_flow=non_interactive
init_mode=freebsd-init+rc.d-shepherd
install_target_boot=ok
```
The harness verified all of the following:
- GPT partitioning is created on the target image
- the installed ESP is a valid `msdosfs` filesystem
- the installed root partition is `ufs`
- `EFI/BOOT/BOOTX64.EFI` exists on the target
- `/run/current-system` points at the installed closure in `/frx/store`
- the installed closure exists under the target's `/frx/store`
- `/var/lib/fruix/install.scm` exists and records:
- closure path
- store items
- install spec
- materialized source provenance via the referenced closure/store items
- the installed system boots under local QEMU/UEFI/TCG
- after boot:
- `sshd` is running
- `/usr/local/etc/rc.d/fruix-shepherd onestatus` reports running
- activation completed successfully
## QEMU SMP note
I tested the idea of defaulting local QEMU validation to 8 vCPUs.
Result:
- for local `qemu-system-x86_64` under **TCG**, higher SMP did not help this validation path and in practice regressed boot responsiveness
- the harnesses therefore keep `QEMU_SMP` configurable but retain a conservative default of `2` for TCG-based local validation
This keeps the validated path reliable while still allowing manual override when useful.
## Result
Phase 18.1 is complete.
Fruix now has a real, repeatable, non-interactive installation workflow for FreeBSD systems:
- declarative system input
- native/source-driven closure output
- partition/format/install to a target image or disk
- bootable installed result
The next step is Phase 18.2:
- a minimal Fruix-managed installer environment that can boot into an install context and run this workflow from within that environment.

View File

@@ -26,6 +26,7 @@
freebsd-base-kernconf
freebsd-base-make-flags
%default-freebsd-base
freebsd-package
freebsd-package?
freebsd-package-name
freebsd-package-version

View File

@@ -49,10 +49,13 @@
operating-system-ready-marker
operating-system-root-authorized-keys
validate-operating-system
materialize-freebsd-source
operating-system-closure-spec
operating-system-install-spec
operating-system-image-spec
materialize-operating-system
materialize-rootfs
install-operating-system
materialize-bhyve-image
default-minimal-operating-system))
@@ -299,7 +302,8 @@
`((build-version . ,native-freebsd-build-version)
(source-root . ,source-root)
(source-tree-identity-mode . "mtree:type,link,size,mode,sha256digest")
(source-tree-sha256 . ,(native-build-source-tree-sha256 source-root))
(source-tree-sha256 . ,(or (build-plan-ref plan 'materialized-source-tree-sha256 #f)
(native-build-source-tree-sha256 source-root)))
(target . ,target)
(target-arch . ,target-arch)
(kernconf . ,kernconf)
@@ -322,11 +326,25 @@
(commit . ,(build-plan-ref plan 'base-source-commit #f))
(sha256 . ,(build-plan-ref plan 'base-source-sha256 #f))))
(define (native-build-materialized-source plan)
`((store-path . ,(build-plan-ref plan 'materialized-source-store #f))
(source-root . ,(build-plan-ref plan 'source-root "/usr/src"))
(info-file . ,(build-plan-ref plan 'materialized-source-info-file #f))
(tree-sha256 . ,(build-plan-ref plan 'materialized-source-tree-sha256 #f))
(cache-path . ,(build-plan-ref plan 'materialized-source-cache-path #f))
(effective-source . ((kind . ,(build-plan-ref plan 'effective-source-kind #f))
(url . ,(build-plan-ref plan 'effective-source-url #f))
(path . ,(build-plan-ref plan 'effective-source-path #f))
(ref . ,(build-plan-ref plan 'effective-source-ref #f))
(commit . ,(build-plan-ref plan 'effective-source-commit #f))
(sha256 . ,(build-plan-ref plan 'effective-source-sha256 #f))))))
(define (native-build-manifest-string package input-paths)
(let* ((plan (freebsd-package-install-plan package))
(common (native-build-common-manifest plan))
(declared-base (native-build-declared-base plan))
(declared-source (native-build-declared-source plan))
(materialized-source (native-build-materialized-source plan))
(keep-paths (build-plan-ref plan 'keep-paths '()))
(prune-paths (build-plan-ref plan 'prune-paths '())))
(string-append
@@ -338,6 +356,8 @@
(object->string declared-base)
"\ndeclared-source=\n"
(object->string declared-source)
"\nmaterialized-source=\n"
(object->string materialized-source)
"\nnative-build-common=\n"
(object->string common)
"\nkeep-paths=\n"
@@ -492,6 +512,7 @@
(version . ,(freebsd-package-version package))
(declared-base . ,(native-build-declared-base plan))
(declared-source . ,(native-build-declared-source plan))
(materialized-source . ,(native-build-materialized-source plan))
(build-system . ,(freebsd-package-build-system package))
(source-root . ,(assoc-ref common 'source-root))
(source-tree-sha256 . ,(assoc-ref common 'source-tree-sha256))
@@ -549,38 +570,101 @@
(write-file (string-append output-path "/.freebsd-native-build-info.scm")
(object->string (native-build-output-metadata package common build-root final-stage-root)))))
(define (package-cache-key package)
(string-append (freebsd-package-name package) "-" (freebsd-package-version package)))
(define (package-with-install-plan package install-plan)
(freebsd-package
#:name (freebsd-package-name package)
#:version (freebsd-package-version package)
#:build-system (freebsd-package-build-system package)
#:inputs (freebsd-package-inputs package)
#:home-page (freebsd-package-home-page package)
#:synopsis (freebsd-package-synopsis package)
#:description (freebsd-package-description package)
#:license (freebsd-package-license package)
#:install-plan install-plan))
(define (materialize-freebsd-package package store-dir cache)
(let ((cached (hash-ref cache (package-cache-key package) #f)))
(define (plan-freebsd-source plan)
(freebsd-source #:name (build-plan-ref plan 'base-source-name "default")
#:kind (build-plan-ref plan 'base-source-kind 'local-tree)
#:url (build-plan-ref plan 'base-source-url #f)
#:path (build-plan-ref plan 'base-source-path #f)
#:ref (build-plan-ref plan 'base-source-ref #f)
#:commit (build-plan-ref plan 'base-source-commit #f)
#:sha256 (build-plan-ref plan 'base-source-sha256 #f)))
(define (source-cache-key source)
(string-hash (object->string (freebsd-source-spec source))))
(define (materialize-freebsd-source/cached source store-dir source-cache)
(let* ((key (source-cache-key source))
(cached (hash-ref source-cache key #f)))
(or cached
(let ((result (materialize-freebsd-source source #:store-dir store-dir)))
(hash-set! source-cache key result)
result))))
(define (plan-with-materialized-source plan source-result)
(let* ((effective (assoc-ref source-result 'effective-source))
(overrides
`((source-root . ,(assoc-ref source-result 'source-root))
(materialized-source-store . ,(assoc-ref source-result 'source-store-path))
(materialized-source-info-file . ,(assoc-ref source-result 'source-info-file))
(materialized-source-tree-sha256 . ,(assoc-ref source-result 'source-tree-sha256))
(materialized-source-cache-path . ,(assoc-ref source-result 'cache-path))
(effective-source-kind . ,(assoc-ref effective 'kind))
(effective-source-url . ,(assoc-ref effective 'url))
(effective-source-path . ,(assoc-ref effective 'path))
(effective-source-ref . ,(assoc-ref effective 'ref))
(effective-source-commit . ,(assoc-ref effective 'commit))
(effective-source-sha256 . ,(assoc-ref effective 'sha256)))))
(append overrides plan)))
(define* (materialize-freebsd-package package store-dir cache #:optional source-cache)
(let* ((source-cache (or source-cache (make-hash-table)))
(input-paths (map (lambda (input)
(materialize-freebsd-package input store-dir cache source-cache))
(freebsd-package-inputs package)))
(prepared-package
(if (freebsd-native-build-package? package)
(let* ((source (plan-freebsd-source (freebsd-package-install-plan package)))
(source-result (materialize-freebsd-source/cached source store-dir source-cache))
(plan (plan-with-materialized-source (freebsd-package-install-plan package)
source-result)))
(package-with-install-plan package plan))
package))
(effective-input-paths
(if (freebsd-native-build-package? package)
(cons (build-plan-ref (freebsd-package-install-plan prepared-package)
'materialized-source-store
#f)
input-paths)
input-paths))
(effective-input-paths (filter identity effective-input-paths))
(manifest (package-manifest-string prepared-package effective-input-paths))
(cache-key (string-hash manifest))
(cached (hash-ref cache cache-key #f)))
(if cached
cached
(let* ((input-paths (map (lambda (input)
(materialize-freebsd-package input store-dir cache))
(freebsd-package-inputs package)))
(manifest (package-manifest-string package input-paths))
(hash (string-hash manifest))
(let* ((hash (string-hash manifest))
(output-path (string-append store-dir "/" hash "-"
(freebsd-package-name package)
(freebsd-package-name prepared-package)
"-"
(freebsd-package-version package))))
(freebsd-package-version prepared-package))))
(unless (file-exists? output-path)
(case (freebsd-package-build-system package)
(case (freebsd-package-build-system prepared-package)
((copy-build-system)
(mkdir-p output-path)
(for-each (lambda (entry)
(materialize-plan-entry output-path entry))
(freebsd-package-install-plan package))
(freebsd-package-install-plan prepared-package))
(write-file (string-append output-path "/.references")
(string-join input-paths "\n"))
(string-join effective-input-paths "\n"))
(write-file (string-append output-path "/.fruix-package") manifest))
((freebsd-world-build-system freebsd-kernel-build-system)
(materialize-native-freebsd-package package input-paths manifest output-path))
(materialize-native-freebsd-package prepared-package effective-input-paths manifest output-path))
(else
(error (format #f "unsupported package build system: ~a"
(freebsd-package-build-system package))))))
(hash-set! cache (package-cache-key package) output-path)
(freebsd-package-build-system prepared-package))))))
(hash-set! cache cache-key output-path)
output-path))))
(define prefix-materializer-version "3")
@@ -716,6 +800,198 @@
(kernconf . ,(freebsd-base-kernconf base))
(make-flags . ,(freebsd-base-make-flags base))))
(define freebsd-source-materializer-version "2")
(define (string-downcase* value)
(list->string (map char-downcase (string->list value))))
(define (safe-name-fragment value)
(let* ((text (if (and (string? value) (not (string-null? value))) value "source"))
(chars (map (lambda (ch)
(if (or (char-alphabetic? ch)
(char-numeric? ch)
(memv ch '(#\- #\_ #\.)))
ch
#\-))
(string->list text))))
(list->string chars)))
(define (freebsd-source-manifest source effective-source identity)
(string-append
"materializer-version=" freebsd-source-materializer-version "\n"
"declared-source=\n"
(object->string (freebsd-source-spec source))
"\neffective-source=\n"
(object->string (freebsd-source-spec effective-source))
"\nidentity=\n"
(object->string identity)))
(define (copy-tree-contents source-root target-root)
(mkdir-p target-root)
(for-each (lambda (entry)
(copy-node (string-append source-root "/" entry)
(string-append target-root "/" entry)))
(directory-entries source-root)))
(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))
".git")))
(mkdir-p (dirname repo-dir))
(unless (file-exists? repo-dir)
(unless (zero? (system* "git" "init" "--quiet" "--bare" repo-dir))
(error "failed to initialize git source cache" repo-dir))
(unless (zero? (system* "git" "-C" repo-dir "remote" "add" "origin" url))
(error "failed to add git source remote" url)))
(let ((current-url (safe-command-output "git" "-C" repo-dir "remote" "get-url" "origin")))
(unless (and current-url (string=? current-url url))
(unless (zero? (system* "git" "-C" repo-dir "remote" "set-url" "origin" url))
(error "failed to update git source remote" url))))
repo-dir))
(define (resolve-git-freebsd-source source cache-dir)
(let* ((selector (or (freebsd-source-commit source)
(freebsd-source-ref source)
(error "git freebsd source requires a ref or commit" source)))
(repo-dir (ensure-git-source-cache source cache-dir)))
(unless (zero? (system* "git" "-C" repo-dir "fetch" "--quiet" "--depth" "1" "origin" selector))
(error "failed to fetch git freebsd source" selector))
(let ((resolved-commit (command-output "git" "-C" repo-dir "rev-parse" "FETCH_HEAD")))
`((cache-path . ,repo-dir)
(effective-source . ,(freebsd-source #:name (freebsd-source-name source)
#:kind 'git
#:url (freebsd-source-url source)
#:ref (freebsd-source-ref source)
#:commit resolved-commit
#:sha256 #f))
(identity . ((resolved-commit . ,resolved-commit)))
(populate-tree . ,(lambda (tree-root)
(let ((archive-path (string-append (dirname tree-root) "/git-export.tar")))
(unless (zero? (system* "git" "-C" repo-dir "archive"
"--format=tar" "-o" archive-path resolved-commit))
(error "failed to archive git freebsd source" resolved-commit))
(unless (zero? (system* "tar" "-xpf" archive-path "-C" tree-root))
(error "failed to extract archived git freebsd source" archive-path))
(delete-path-if-exists archive-path))))))))
(define (normalize-expected-sha256 source)
(let ((sha256 (freebsd-source-sha256 source)))
(and sha256 (string-downcase* sha256))))
(define (resolve-txz-freebsd-source source cache-dir)
(let* ((url (freebsd-source-url source))
(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))
"-src.txz")))
(mkdir-p (dirname archive-path))
(when (file-exists? archive-path)
(let ((actual (string-downcase* (file-hash archive-path))))
(unless (string=? actual expected-sha256)
(delete-file archive-path))))
(unless (file-exists? archive-path)
(unless (zero? (system* "fetch" "-q" "-o" archive-path url))
(error "failed to download FreeBSD src.txz source" url)))
(let ((actual-sha256 (string-downcase* (file-hash archive-path))))
(unless (string=? actual-sha256 expected-sha256)
(error "downloaded src.txz hash mismatch" url expected-sha256 actual-sha256))
`((cache-path . ,archive-path)
(effective-source . ,(freebsd-source #:name (freebsd-source-name source)
#:kind 'src-txz
#:url url
#:path #f
#:ref #f
#:commit #f
#:sha256 actual-sha256))
(identity . ((archive-sha256 . ,actual-sha256)))
(populate-tree . ,(lambda (tree-root)
(unless (zero? (system* "tar" "-xpf" archive-path "-C" tree-root))
(error "failed to extract FreeBSD src.txz source" archive-path))))))))
(define (resolve-local-freebsd-source source)
(let* ((path (freebsd-source-path source))
(tree-sha256 (native-build-source-tree-sha256 path)))
`((cache-path . #f)
(effective-source . ,(freebsd-source #:name (freebsd-source-name source)
#:kind 'local-tree
#:url #f
#:path path
#:ref #f
#:commit #f
#:sha256 tree-sha256))
(identity . ((tree-sha256 . ,tree-sha256)))
(populate-tree . ,(lambda (tree-root)
(copy-tree-contents path tree-root))))))
(define (detect-materialized-source-relative-root tree-root)
(cond
((file-exists? (string-append tree-root "/Makefile"))
"tree")
((file-exists? (string-append tree-root "/usr/src/Makefile"))
"tree/usr/src")
(else
"tree")))
(define* (materialize-freebsd-source source #:key
(store-dir "/frx/store")
(cache-dir "/frx/var/cache/fruix/freebsd-source"))
(validate-freebsd-source source)
(let* ((resolution (case (freebsd-source-kind source)
((local-tree)
(resolve-local-freebsd-source source))
((git)
(resolve-git-freebsd-source source cache-dir))
((src-txz)
(resolve-txz-freebsd-source source cache-dir))
(else
(error "unsupported freebsd source kind" (freebsd-source-kind source)))))
(effective-source (assoc-ref resolution 'effective-source))
(identity (assoc-ref resolution 'identity))
(manifest (freebsd-source-manifest source effective-source identity))
(hash (string-hash 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"))
(cache-path (assoc-ref resolution 'cache-path))
(populate-tree (assoc-ref resolution 'populate-tree)))
(unless (file-exists? output-path)
(let* ((temp-output (string-append output-path ".tmp"))
(temp-tree-root (string-append temp-output "/tree")))
(delete-path-if-exists temp-output)
(mkdir-p temp-tree-root)
(populate-tree temp-tree-root)
(let* ((relative-root (detect-materialized-source-relative-root temp-tree-root))
(source-root (string-append output-path "/" relative-root))
(temp-source-root (string-append temp-output "/" relative-root))
(tree-sha256 (native-build-source-tree-sha256 temp-source-root)))
(write-file (string-append temp-output "/.references") "")
(write-file (string-append temp-output "/.fruix-source") manifest)
(write-file (string-append temp-output "/.freebsd-source-info.scm")
(object->string
`((materializer-version . ,freebsd-source-materializer-version)
(declared-source . ,(freebsd-source-spec source))
(effective-source . ,(freebsd-source-spec effective-source))
(identity . ,identity)
(source-store . ,output-path)
(source-root . ,source-root)
(source-tree-sha256 . ,tree-sha256)
(cache-path . ,cache-path)))))
(rename-file temp-output output-path)))
(call-with-input-file info-file
(lambda (port)
(let* ((info (read port))
(effective (assoc-ref info 'effective-source)))
`((source-store-path . ,output-path)
(source-root . ,(assoc-ref info 'source-root))
(source-info-file . ,info-file)
(source-tree-sha256 . ,(assoc-ref info 'source-tree-sha256))
(cache-path . ,(assoc-ref info 'cache-path))
(effective-source . ,effective)
(effective-commit . ,(assoc-ref effective 'commit))
(effective-sha256 . ,(assoc-ref effective 'sha256))))))))
(define (duplicate-elements values)
(let loop ((rest values) (seen '()) (duplicates '()))
(match rest
@@ -747,7 +1023,9 @@
(error "git freebsd source must declare a ref or commit" source)))
((src-txz)
(unless (non-empty-string? (freebsd-source-url source))
(error "src-txz freebsd source must declare a URL" source)))))
(error "src-txz freebsd source must declare a URL" source))
(unless (non-empty-string? (freebsd-source-sha256 source))
(error "src-txz freebsd source must declare a sha256" source)))))
#t)
(define (validate-operating-system os)
@@ -1377,6 +1655,20 @@
(mkdir-p tree-root)
(walk ""))
(define (hash-table-values table)
(hash-fold (lambda (_ value result)
(cons value result))
'()
table))
(define (freebsd-source-materialization-spec result)
`((source-store-path . ,(assoc-ref result 'source-store-path))
(source-root . ,(assoc-ref result 'source-root))
(source-info-file . ,(assoc-ref result 'source-info-file))
(source-tree-sha256 . ,(assoc-ref result 'source-tree-sha256))
(cache-path . ,(assoc-ref result 'cache-path))
(effective-source . ,(assoc-ref result 'effective-source))))
(define* (materialize-operating-system os
#:key
(store-dir "/frx/store")
@@ -1385,13 +1677,14 @@
(shepherd-prefix "/tmp/shepherd-freebsd-validate-install"))
(validate-operating-system os)
(let* ((cache (make-hash-table))
(source-cache (make-hash-table))
(kernel-package (operating-system-kernel os))
(bootloader-package (operating-system-bootloader os))
(base-packages (operating-system-base-packages os))
(kernel-store (materialize-freebsd-package kernel-package store-dir cache))
(bootloader-store (materialize-freebsd-package bootloader-package store-dir cache))
(kernel-store (materialize-freebsd-package kernel-package store-dir cache source-cache))
(bootloader-store (materialize-freebsd-package bootloader-package store-dir cache source-cache))
(base-package-stores (map (lambda (package)
(materialize-freebsd-package package store-dir cache))
(materialize-freebsd-package package store-dir cache source-cache))
base-packages))
(base-package-pairs (map cons base-packages base-package-stores))
(store-classification
@@ -1432,17 +1725,27 @@
(freebsd-native-build-package? (car entry)))
store-classification))))
(fruix-runtime-stores (list guile-store guile-extra-store shepherd-store))
(source-materializations
(delete-duplicates (hash-table-values source-cache)))
(materialized-source-stores
(delete-duplicates (map (lambda (result)
(assoc-ref result 'source-store-path))
source-materializations)))
(metadata-files
`(("metadata/freebsd-base.scm"
. ,(object->string (freebsd-base-spec (operating-system-freebsd-base os))))
("metadata/freebsd-source.scm"
. ,(object->string (freebsd-source-spec (freebsd-base-source (operating-system-freebsd-base os)))))
("metadata/freebsd-source-materializations.scm"
. ,(object->string (map freebsd-source-materialization-spec source-materializations)))
("metadata/host-base-provenance.scm"
. ,(object->string (host-freebsd-provenance)))
("metadata/store-layout.scm"
. ,(object->string
`((freebsd-base . ,(freebsd-base-spec (operating-system-freebsd-base os)))
(freebsd-source . ,(freebsd-source-spec (freebsd-base-source (operating-system-freebsd-base os))))
(materialized-source-store-count . ,(length materialized-source-stores))
(materialized-source-stores . ,materialized-source-stores)
(host-base-store-count . ,(length host-base-stores))
(host-base-stores . ,host-base-stores)
(native-base-store-count . ,(length native-base-stores))
@@ -1460,7 +1763,7 @@
. ,(render-activation-rc-script))
("usr/local/etc/rc.d/fruix-shepherd"
. ,(render-rc-script shepherd-store guile-store guile-extra-store)))))
(references (delete-duplicates (append host-base-stores native-base-stores fruix-runtime-stores)))
(references (delete-duplicates (append materialized-source-stores host-base-stores native-base-stores fruix-runtime-stores)))
(manifest (string-append
"closure-spec=\n"
(object->string (operating-system-closure-spec os))
@@ -1519,6 +1822,8 @@
(fruix-runtime-stores . ,fruix-runtime-stores)
(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"))
(materialized-source-stores . ,materialized-source-stores)
(host-base-provenance-file . ,(string-append closure-path "/metadata/host-base-provenance.scm"))
(store-layout-file . ,(string-append closure-path "/metadata/store-layout.scm"))
(generated-files . ,(map car generated-files))
@@ -1530,6 +1835,58 @@
(mkdir-p (dirname link-name))
(symlink target link-name))
(define (populate-rootfs-from-closure os rootfs closure-path)
(when (file-exists? rootfs)
(delete-file-recursively rootfs))
(mkdir-p rootfs)
(for-each (lambda (dir)
(mkdir-p (string-append rootfs dir)))
'("/run" "/boot" "/etc" "/etc/ssh" "/usr" "/usr/share" "/usr/local" "/usr/local/etc"
"/usr/local/etc/rc.d" "/var" "/var/cron" "/var/db" "/var/lib" "/var/lib/fruix"
"/var/log" "/var/run" "/tmp" "/dev" "/root" "/home"))
(chmod (string-append rootfs "/tmp") #o1777)
(symlink-force closure-path (string-append rootfs "/run/current-system"))
(symlink-force "/run/current-system/activate" (string-append rootfs "/activate"))
(for-each (lambda (dir)
(symlink-force (string-append "/run/current-system/profile/" dir)
(string-append rootfs "/" dir)))
'("bin" "sbin" "lib" "libexec"))
(for-each (lambda (dir)
(symlink-force (string-append "/run/current-system/profile/usr/" dir)
(string-append rootfs "/usr/" dir)))
'("bin" "lib" "sbin" "libexec"))
(when (file-exists? (string-append closure-path "/profile/usr/share/locale"))
(symlink-force "/run/current-system/profile/usr/share/locale"
(string-append rootfs "/usr/share/locale")))
(for-each (lambda (path)
(symlink-force (string-append "/run/current-system/profile/etc/" path)
(string-append rootfs "/etc/" path)))
'("rc" "rc.subr" "rc.shutdown" "rc.d" "defaults"
"devd.conf" "network.subr" "newsyslog.conf" "syslog.conf"))
(for-each (lambda (path)
(symlink-force (string-append "/run/current-system/etc/" path)
(string-append rootfs "/etc/" path)))
'("rc.conf" "fstab" "hosts" "shells" "motd" "ttys"))
(for-each (lambda (path)
(copy-regular-file (string-append closure-path "/etc/" path)
(string-append rootfs "/etc/" path)))
'("passwd" "master.passwd" "group" "login.conf"))
(when (file-exists? (string-append closure-path "/etc/ssh/sshd_config"))
(symlink-force "/run/current-system/etc/ssh/sshd_config"
(string-append rootfs "/etc/ssh/sshd_config")))
(for-each (lambda (path)
(symlink-force (string-append "/run/current-system/boot/" path)
(string-append rootfs "/boot/" path)))
'("kernel" "loader" "loader.efi" "device.hints" "defaults" "lua" "loader.conf"))
(symlink-force "/run/current-system/usr/local/etc/rc.d/fruix-activate"
(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"))
`((rootfs . ,rootfs)
(closure-path . ,closure-path)
(ready-marker . ,(operating-system-ready-marker os))
(rc-script . ,(string-append closure-path "/usr/local/etc/rc.d/fruix-shepherd"))))
(define* (materialize-rootfs os rootfs
#:key
(store-dir "/frx/store")
@@ -1542,56 +1899,34 @@
#:guile-extra-prefix guile-extra-prefix
#:shepherd-prefix shepherd-prefix))
(closure-path (assoc-ref closure 'closure-path)))
(when (file-exists? rootfs)
(delete-file-recursively rootfs))
(mkdir-p rootfs)
(for-each (lambda (dir)
(mkdir-p (string-append rootfs dir)))
'("/run" "/boot" "/etc" "/etc/ssh" "/usr" "/usr/share" "/usr/local" "/usr/local/etc"
"/usr/local/etc/rc.d" "/var" "/var/cron" "/var/db" "/var/lib" "/var/lib/fruix"
"/var/log" "/var/run" "/tmp" "/dev" "/root" "/home"))
(chmod (string-append rootfs "/tmp") #o1777)
(symlink-force closure-path (string-append rootfs "/run/current-system"))
(symlink-force "/run/current-system/activate" (string-append rootfs "/activate"))
(for-each (lambda (dir)
(symlink-force (string-append "/run/current-system/profile/" dir)
(string-append rootfs "/" dir)))
'("bin" "sbin" "lib" "libexec"))
(for-each (lambda (dir)
(symlink-force (string-append "/run/current-system/profile/usr/" dir)
(string-append rootfs "/usr/" dir)))
'("bin" "lib" "sbin" "libexec"))
(when (file-exists? (string-append closure-path "/profile/usr/share/locale"))
(symlink-force "/run/current-system/profile/usr/share/locale"
(string-append rootfs "/usr/share/locale")))
(for-each (lambda (path)
(symlink-force (string-append "/run/current-system/profile/etc/" path)
(string-append rootfs "/etc/" path)))
'("rc" "rc.subr" "rc.shutdown" "rc.d" "defaults"
"devd.conf" "network.subr" "newsyslog.conf" "syslog.conf"))
(for-each (lambda (path)
(symlink-force (string-append "/run/current-system/etc/" path)
(string-append rootfs "/etc/" path)))
'("rc.conf" "fstab" "hosts" "shells" "motd" "ttys"))
(for-each (lambda (path)
(copy-regular-file (string-append closure-path "/etc/" path)
(string-append rootfs "/etc/" path)))
'("passwd" "master.passwd" "group" "login.conf"))
(when (file-exists? (string-append closure-path "/etc/ssh/sshd_config"))
(symlink-force "/run/current-system/etc/ssh/sshd_config"
(string-append rootfs "/etc/ssh/sshd_config")))
(for-each (lambda (path)
(symlink-force (string-append "/run/current-system/boot/" path)
(string-append rootfs "/boot/" path)))
'("kernel" "loader" "loader.efi" "device.hints" "defaults" "lua" "loader.conf"))
(symlink-force "/run/current-system/usr/local/etc/rc.d/fruix-activate"
(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"))
`((rootfs . ,rootfs)
(closure-path . ,closure-path)
(ready-marker . ,(operating-system-ready-marker os))
(rc-script . ,(string-append closure-path "/usr/local/etc/rc.d/fruix-shepherd")))))
(populate-rootfs-from-closure os rootfs closure-path)))
(define* (operating-system-install-spec os
#:key
target
(target-kind 'raw-file)
(boot-mode 'uefi)
(partition-scheme 'gpt)
(efi-size "64m")
(root-size #f)
(disk-capacity #f)
(efi-partition-label "efiboot")
(root-partition-label "fruix-root")
(serial-console "comconsole"))
`((host-name . ,(operating-system-host-name os))
(freebsd-base . ,(freebsd-base-spec (operating-system-freebsd-base os)))
(install-mode . non-interactive)
(target . ,target)
(target-kind . ,target-kind)
(boot-mode . ,boot-mode)
(partition-scheme . ,partition-scheme)
(efi-size . ,efi-size)
(root-size . ,root-size)
(disk-capacity . ,disk-capacity)
(efi-partition-label . ,efi-partition-label)
(root-partition-label . ,root-partition-label)
(serial-console . ,serial-console)
(init-mode . ,(operating-system-init-mode os))))
(define* (operating-system-image-spec os
#:key
@@ -1663,6 +1998,7 @@
(command-output "mktemp" "-d" pattern))
(define image-builder-version "2")
(define install-builder-version "1")
(define (resize-gpt-image image disk-capacity)
(when disk-capacity
@@ -1675,6 +2011,133 @@
(lambda ()
(run-command "mdconfig" "-d" "-u" (string-drop md 2)))))))
(define* (install-operating-system os
#:key
target
(store-dir "/frx/store")
(guile-prefix "/tmp/guile-freebsd-validate-install")
(guile-extra-prefix "/tmp/guile-gnutls-freebsd-validate-install")
(shepherd-prefix "/tmp/shepherd-freebsd-validate-install")
(efi-size "64m")
(root-size #f)
(disk-capacity #f)
(efi-partition-label "efiboot")
(root-partition-label "fruix-root")
(serial-console "comconsole"))
(unless (and (string? target) (not (string-null? target)))
(error "install target must be a non-empty path" target))
(let* ((closure (materialize-operating-system os
#:store-dir store-dir
#:guile-prefix guile-prefix
#:guile-extra-prefix guile-extra-prefix
#:shepherd-prefix shepherd-prefix))
(closure-path (assoc-ref closure 'closure-path))
(store-items (store-reference-closure (list closure-path)))
(target-kind (if (string-prefix? "/dev/" target)
'block-device
'raw-file))
(install-spec (operating-system-install-spec os
#:target target
#:target-kind target-kind
#:efi-size efi-size
#:root-size root-size
#:disk-capacity disk-capacity
#:efi-partition-label efi-partition-label
#:root-partition-label root-partition-label
#:serial-console serial-console))
(build-root (mktemp-directory "/tmp/fruix-system-install.XXXXXX"))
(rootfs (string-append build-root "/rootfs"))
(mnt-root (string-append build-root "/mnt-root"))
(mnt-esp (string-append build-root "/mnt-esp"))
(install-metadata-relative-path "/var/lib/fruix/install.scm")
(target-device #f)
(target-md #f)
(esp-device #f)
(root-device #f)
(root-mounted? #f)
(esp-mounted? #f))
(dynamic-wind
(lambda () #t)
(lambda ()
(populate-rootfs-from-closure os rootfs closure-path)
(mkdir-p mnt-root)
(mkdir-p mnt-esp)
(case target-kind
((raw-file)
(unless disk-capacity
(error "raw-file install target requires --disk-capacity" target))
(mkdir-p (dirname target))
(delete-path-if-exists target)
(run-command "truncate" "-s" disk-capacity target)
(let ((md (command-output "mdconfig" "-a" "-t" "vnode" "-f" target)))
(set! target-md md)
(set! target-device (string-append "/dev/" md))))
((block-device)
(set! target-device target)))
(system* "sh" "-c"
(string-append "gpart destroy -F " target-device " >/dev/null 2>&1"))
(run-command "gpart" "create" "-s" "gpt" target-device)
(run-command "gpart" "add" "-a" "1m" "-s" efi-size
"-t" "efi" "-l" efi-partition-label target-device)
(if root-size
(run-command "gpart" "add" "-a" "1m" "-s" root-size
"-t" "freebsd-ufs" "-l" root-partition-label target-device)
(run-command "gpart" "add" "-a" "1m"
"-t" "freebsd-ufs" "-l" root-partition-label target-device))
(set! esp-device (string-append target-device "p1"))
(set! root-device (string-append target-device "p2"))
(run-command "newfs_msdos" "-L" "EFISYS" esp-device)
(run-command "newfs" "-U" "-L" root-partition-label root-device)
(run-command "mount" "-t" "ufs" root-device mnt-root)
(set! root-mounted? #t)
(run-command "mount" "-t" "msdosfs" esp-device mnt-esp)
(set! esp-mounted? #t)
(copy-tree-contents rootfs mnt-root)
(copy-store-items-into-rootfs mnt-root store-dir store-items)
(mkdir-p (string-append mnt-esp "/EFI/BOOT"))
(copy-regular-file (string-append closure-path "/boot/loader.efi")
(string-append mnt-esp "/EFI/BOOT/BOOTX64.EFI"))
(let ((install-metadata-file (string-append mnt-root install-metadata-relative-path)))
(write-file install-metadata-file
(object->string
`((install-version . ,install-builder-version)
(install-spec . ,install-spec)
(closure-path . ,closure-path)
(store-item-count . ,(length store-items))
(store-items . ,store-items))))
(chmod install-metadata-file #o644))
(run-command "sync")
`((target . ,target)
(target-kind . ,target-kind)
(target-device . ,target-device)
(esp-device . ,esp-device)
(root-device . ,root-device)
(install-spec . ,install-spec)
(install-metadata-path . ,install-metadata-relative-path)
(closure-path . ,closure-path)
(host-base-stores . ,(assoc-ref closure 'host-base-stores))
(native-base-stores . ,(assoc-ref closure 'native-base-stores))
(fruix-runtime-stores . ,(assoc-ref closure 'fruix-runtime-stores))
(freebsd-base-file . ,(assoc-ref closure 'freebsd-base-file))
(freebsd-source-file . ,(assoc-ref closure 'freebsd-source-file))
(freebsd-source-materializations-file . ,(assoc-ref closure 'freebsd-source-materializations-file))
(materialized-source-stores . ,(assoc-ref closure 'materialized-source-stores))
(host-base-provenance-file . ,(assoc-ref closure 'host-base-provenance-file))
(store-layout-file . ,(assoc-ref closure 'store-layout-file))
(store-items . ,store-items)))
(lambda ()
(when esp-mounted?
(system* "umount" mnt-esp)
(set! esp-mounted? #f))
(when root-mounted?
(system* "umount" mnt-root)
(set! root-mounted? #f))
(when target-md
(system* "mdconfig" "-d" "-u" (string-drop target-md 2))
(set! target-md #f))
(when (file-exists? build-root)
(delete-file-recursively build-root))))))
(define* (materialize-bhyve-image os
#:key
(store-dir "/frx/store")
@@ -1786,6 +2249,9 @@
(native-base-stores . ,(assoc-ref closure 'native-base-stores))
(fruix-runtime-stores . ,(assoc-ref closure 'fruix-runtime-stores))
(freebsd-base-file . ,(assoc-ref closure 'freebsd-base-file))
(freebsd-source-file . ,(assoc-ref closure 'freebsd-source-file))
(freebsd-source-materializations-file . ,(assoc-ref closure 'freebsd-source-materializations-file))
(materialized-source-stores . ,(assoc-ref closure 'materialized-source-stores))
(host-base-provenance-file . ,(assoc-ref closure 'host-base-provenance-file))
(store-layout-file . ,(assoc-ref closure 'store-layout-file))
(image-spec . ,image-spec)

View File

@@ -10,20 +10,36 @@
(define (usage code)
(format (if (= code 0) #t (current-error-port))
"Usage: fruix system ACTION OS-FILE [OPTIONS]\n\
"Usage: fruix COMMAND ...\n\
\n\
Actions:\n\
build Materialize the Fruix system closure in /frx/store.\n\
image Materialize the Fruix disk image in /frx/store.\n\
rootfs Materialize a rootfs tree at --rootfs DIR or ROOTFS-DIR.\n\
Commands:\n\
system ACTION ... Build or materialize Fruix system artifacts.\n\
source ACTION ... Fetch or snapshot declarative FreeBSD source inputs.\n\
\n\
Options:\n\
--system NAME Scheme variable holding the operating-system object.\n\
--store DIR Store directory to use (default: /frx/store).\n\
--disk-capacity SIZE Disk capacity for 'image' (example: 30g).\n\
--root-size SIZE Root filesystem size for 'image' (example: 6g).\n\
--rootfs DIR Rootfs target for 'rootfs'.\n\
--help Show this help.\n")
System actions:\n\
build Materialize the Fruix system closure in /frx/store.\n\
image Materialize the Fruix disk image in /frx/store.\n\
install Install the Fruix system onto --target PATH.\n\
rootfs Materialize a rootfs tree at --rootfs DIR or ROOTFS-DIR.\n\
\n\
System options:\n\
--system NAME Scheme variable holding the operating-system object.\n\
--store DIR Store directory to use (default: /frx/store).\n\
--disk-capacity SIZE Disk capacity for 'image' or raw-file 'install' targets.\n\
--root-size SIZE Root filesystem size for 'image' or 'install' (example: 6g).\n\
--target PATH Install target for 'install' (raw image file or /dev/... device).\n\
--rootfs DIR Rootfs target for 'rootfs'.\n\
\n\
Source actions:\n\
materialize Materialize a declared FreeBSD source tree in /frx/store.\n\
\n\
Source options:\n\
--source NAME Scheme variable holding the freebsd-source object.\n\
--store DIR Store directory to use (default: /frx/store).\n\
--cache DIR Cache directory to use (default: /frx/var/cache/fruix/freebsd-source).\n\
\n\
Common options:\n\
--help Show this help.\n")
(exit code))
(define (option-value arg prefix)
@@ -48,6 +64,8 @@ Options:\n\
(define candidate-operating-system-symbols
'(operating-system
phase16-operating-system
phase15-operating-system
phase10-operating-system
phase9-operating-system
phase8-operating-system
@@ -55,6 +73,12 @@ Options:\n\
default-operating-system
os))
(define candidate-freebsd-source-symbols
'(phase16-source
declared-source
source
src))
(define (resolve-operating-system-symbol module requested)
(or requested
(find (lambda (symbol)
@@ -63,6 +87,14 @@ Options:\n\
candidate-operating-system-symbols)
(error "could not infer operating-system variable; use --system NAME")))
(define (resolve-freebsd-source-symbol module requested)
(or requested
(find (lambda (symbol)
(let ((value (lookup-bound-value module symbol)))
(and value (freebsd-source? value))))
candidate-freebsd-source-symbols)
(error "could not infer freebsd-source variable; use --source NAME")))
(define (load-operating-system-from-file file requested-symbol)
(unless (file-exists? file)
(error "operating-system file does not exist" file))
@@ -75,6 +107,103 @@ Options:\n\
(validate-operating-system value)
(values value symbol)))
(define (load-freebsd-source-from-file file requested-symbol)
(unless (file-exists? file)
(error "freebsd-source file does not exist" file))
(primitive-load file)
(let* ((module (current-module))
(symbol (resolve-freebsd-source-symbol module requested-symbol))
(value (lookup-bound-value module symbol)))
(unless (and value (freebsd-source? value))
(error "resolved variable is not a freebsd-source" symbol))
(values value symbol)))
(define (parse-system-arguments action rest)
(let loop ((args rest)
(positional '())
(system-name #f)
(store-dir "/frx/store")
(disk-capacity #f)
(root-size #f)
(target #f)
(rootfs #f))
(match args
(()
(let ((positional (reverse positional)))
`((command . "system")
(action . ,action)
(positional . ,positional)
(system-name . ,system-name)
(store-dir . ,store-dir)
(disk-capacity . ,disk-capacity)
(root-size . ,root-size)
(target . ,target)
(rootfs . ,rootfs))))
(("--help")
(usage 0))
(((? (lambda (arg) (string-prefix? "--system=" arg)) arg) . tail)
(loop tail positional (option-value arg "--system=") store-dir disk-capacity root-size target rootfs))
(("--system" value . tail)
(loop tail positional value store-dir disk-capacity root-size target rootfs))
(((? (lambda (arg) (string-prefix? "--store=" arg)) arg) . tail)
(loop tail positional system-name (option-value arg "--store=") disk-capacity root-size target rootfs))
(("--store" value . tail)
(loop tail positional system-name value disk-capacity root-size target rootfs))
(((? (lambda (arg) (string-prefix? "--disk-capacity=" arg)) arg) . tail)
(loop tail positional system-name store-dir (option-value arg "--disk-capacity=") root-size target rootfs))
(("--disk-capacity" value . tail)
(loop tail positional system-name store-dir value root-size target rootfs))
(((? (lambda (arg) (string-prefix? "--root-size=" arg)) arg) . tail)
(loop tail positional system-name store-dir disk-capacity (option-value arg "--root-size=") target rootfs))
(("--root-size" value . tail)
(loop tail positional system-name store-dir disk-capacity value target rootfs))
(((? (lambda (arg) (string-prefix? "--target=" arg)) arg) . tail)
(loop tail positional system-name store-dir disk-capacity root-size (option-value arg "--target=") rootfs))
(("--target" value . tail)
(loop tail positional system-name store-dir disk-capacity root-size value rootfs))
(((? (lambda (arg) (string-prefix? "--rootfs=" arg)) arg) . tail)
(loop tail positional system-name store-dir disk-capacity root-size target (option-value arg "--rootfs=")))
(("--rootfs" value . tail)
(loop tail positional system-name store-dir disk-capacity root-size target value))
(((? (lambda (arg) (string-prefix? "--" arg)) arg) . _)
(error "unknown option" arg))
((arg . tail)
(loop tail (cons arg positional) system-name store-dir disk-capacity root-size target rootfs)))))
(define (parse-source-arguments action rest)
(let loop ((args rest)
(positional '())
(source-name #f)
(store-dir "/frx/store")
(cache-dir "/frx/var/cache/fruix/freebsd-source"))
(match args
(()
(let ((positional (reverse positional)))
`((command . "source")
(action . ,action)
(positional . ,positional)
(source-name . ,source-name)
(store-dir . ,store-dir)
(cache-dir . ,cache-dir))))
(("--help")
(usage 0))
(((? (lambda (arg) (string-prefix? "--source=" arg)) arg) . tail)
(loop tail positional (option-value arg "--source=") store-dir cache-dir))
(("--source" value . tail)
(loop tail positional value store-dir cache-dir))
(((? (lambda (arg) (string-prefix? "--store=" arg)) arg) . tail)
(loop tail positional source-name (option-value arg "--store=") cache-dir))
(("--store" value . tail)
(loop tail positional source-name value cache-dir))
(((? (lambda (arg) (string-prefix? "--cache=" arg)) arg) . tail)
(loop tail positional source-name store-dir (option-value arg "--cache=")))
(("--cache" value . tail)
(loop tail positional source-name store-dir value))
(((? (lambda (arg) (string-prefix? "--" arg)) arg) . _)
(error "unknown option" arg))
((arg . tail)
(loop tail (cons arg positional) source-name store-dir cache-dir)))))
(define (parse-arguments argv)
(match argv
((_)
@@ -85,114 +214,301 @@ Options:\n\
(usage 0))
((_ "system" "--help")
(usage 0))
((_ "source" "--help")
(usage 0))
((_ "system" action . rest)
(let loop ((args rest)
(positional '())
(system-name #f)
(store-dir "/frx/store")
(disk-capacity #f)
(root-size #f)
(rootfs #f))
(match args
(()
(let ((positional (reverse positional)))
`((action . ,action)
(positional . ,positional)
(system-name . ,system-name)
(store-dir . ,store-dir)
(disk-capacity . ,disk-capacity)
(root-size . ,root-size)
(rootfs . ,rootfs))))
(("--help")
(usage 0))
(((? (lambda (arg) (string-prefix? "--system=" arg)) arg) . tail)
(loop tail positional (option-value arg "--system=") store-dir disk-capacity root-size rootfs))
(("--system" value . tail)
(loop tail positional value store-dir disk-capacity root-size rootfs))
(((? (lambda (arg) (string-prefix? "--store=" arg)) arg) . tail)
(loop tail positional system-name (option-value arg "--store=") disk-capacity root-size rootfs))
(("--store" value . tail)
(loop tail positional system-name value disk-capacity root-size rootfs))
(((? (lambda (arg) (string-prefix? "--disk-capacity=" arg)) arg) . tail)
(loop tail positional system-name store-dir (option-value arg "--disk-capacity=") root-size rootfs))
(("--disk-capacity" value . tail)
(loop tail positional system-name store-dir value root-size rootfs))
(((? (lambda (arg) (string-prefix? "--root-size=" arg)) arg) . tail)
(loop tail positional system-name store-dir disk-capacity (option-value arg "--root-size=") rootfs))
(("--root-size" value . tail)
(loop tail positional system-name store-dir disk-capacity value rootfs))
(((? (lambda (arg) (string-prefix? "--rootfs=" arg)) arg) . tail)
(loop tail positional system-name store-dir disk-capacity root-size (option-value arg "--rootfs=")))
(("--rootfs" value . tail)
(loop tail positional system-name store-dir disk-capacity root-size value))
(((? (lambda (arg) (string-prefix? "--" arg)) arg) . _)
(error "unknown option" arg))
((arg . tail)
(loop tail (cons arg positional) system-name store-dir disk-capacity root-size rootfs)))))
(parse-system-arguments action rest))
((_ "source" action . rest)
(parse-source-arguments action rest))
((_ . _)
(usage 1))))
(define (emit-system-build-metadata os-file resolved-symbol store-dir os result)
(let* ((closure-path (assoc-ref result 'closure-path))
(generated-files (assoc-ref result 'generated-files))
(references (assoc-ref result 'references))
(base-package-stores (assoc-ref result 'base-package-stores))
(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 . "build")
(os_file . ,os-file)
(system_variable . ,resolved-symbol)
(store_dir . ,store-dir)
(closure_path . ,closure-path)
(freebsd_base_name . ,(freebsd-base-name base))
(freebsd_base_version_label . ,(freebsd-base-version-label base))
(freebsd_base_release . ,(freebsd-base-release base))
(freebsd_base_branch . ,(freebsd-base-branch base))
(freebsd_base_source_root . ,(freebsd-base-source-root base))
(freebsd_base_target . ,(freebsd-base-target base))
(freebsd_base_target_arch . ,(freebsd-base-target-arch base))
(freebsd_base_kernconf . ,(freebsd-base-kernconf base))
(freebsd_base_file . ,(assoc-ref result 'freebsd-base-file))
(freebsd_source_name . ,(freebsd-source-name source))
(freebsd_source_kind . ,(freebsd-source-kind source))
(freebsd_source_url . ,(or (freebsd-source-url source) ""))
(freebsd_source_path . ,(or (freebsd-source-path source) ""))
(freebsd_source_ref . ,(or (freebsd-source-ref source) ""))
(freebsd_source_commit . ,(or (freebsd-source-commit source) ""))
(freebsd_source_sha256 . ,(or (freebsd-source-sha256 source) ""))
(freebsd_source_file . ,(assoc-ref result 'freebsd-source-file))
(freebsd_source_materializations_file . ,(assoc-ref result 'freebsd-source-materializations-file))
(materialized_source_store_count . ,(length (assoc-ref result 'materialized-source-stores)))
(materialized_source_stores . ,(string-join (assoc-ref result 'materialized-source-stores) ","))
(ready_marker . ,(operating-system-ready-marker os))
(kernel_store . ,(assoc-ref result 'kernel-store))
(bootloader_store . ,(assoc-ref result 'bootloader-store))
(guile_store . ,(assoc-ref result 'guile-store))
(guile_extra_store . ,(assoc-ref result 'guile-extra-store))
(shepherd_store . ,(assoc-ref result 'shepherd-store))
(base_package_store_count . ,(length base-package-stores))
(base_package_stores . ,(string-join base-package-stores ","))
(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))
(generated_file_count . ,(length generated-files))
(reference_count . ,(length references))))))
(define (emit-system-install-metadata os-file resolved-symbol store-dir os result)
(let* ((install-spec (assoc-ref result 'install-spec))
(store-items (assoc-ref result 'store-items))
(host-base-stores (assoc-ref result 'host-base-stores))
(native-base-stores (assoc-ref result 'native-base-stores))
(fruix-runtime-stores (assoc-ref result 'fruix-runtime-stores))
(base (operating-system-freebsd-base os))
(source (freebsd-base-source base))
(host-provenance (call-with-input-file (assoc-ref result 'host-base-provenance-file) read)))
(emit-metadata
`((action . "install")
(os_file . ,os-file)
(system_variable . ,resolved-symbol)
(store_dir . ,store-dir)
(target . ,(assoc-ref result 'target))
(target_kind . ,(assoc-ref result 'target-kind))
(target_device . ,(assoc-ref result 'target-device))
(esp_device . ,(assoc-ref result 'esp-device))
(root_device . ,(assoc-ref result 'root-device))
(install_metadata_path . ,(assoc-ref result 'install-metadata-path))
(freebsd_base_name . ,(freebsd-base-name base))
(freebsd_base_version_label . ,(freebsd-base-version-label base))
(freebsd_base_release . ,(freebsd-base-release base))
(freebsd_base_branch . ,(freebsd-base-branch base))
(freebsd_base_source_root . ,(freebsd-base-source-root base))
(freebsd_base_target . ,(freebsd-base-target base))
(freebsd_base_target_arch . ,(freebsd-base-target-arch base))
(freebsd_base_kernconf . ,(freebsd-base-kernconf base))
(freebsd_base_file . ,(assoc-ref result 'freebsd-base-file))
(freebsd_source_name . ,(freebsd-source-name source))
(freebsd_source_kind . ,(freebsd-source-kind source))
(freebsd_source_url . ,(or (freebsd-source-url source) ""))
(freebsd_source_path . ,(or (freebsd-source-path source) ""))
(freebsd_source_ref . ,(or (freebsd-source-ref source) ""))
(freebsd_source_commit . ,(or (freebsd-source-commit source) ""))
(freebsd_source_sha256 . ,(or (freebsd-source-sha256 source) ""))
(freebsd_source_file . ,(assoc-ref result 'freebsd-source-file))
(freebsd_source_materializations_file . ,(assoc-ref result 'freebsd-source-materializations-file))
(materialized_source_store_count . ,(length (assoc-ref result 'materialized-source-stores)))
(materialized_source_stores . ,(string-join (assoc-ref result 'materialized-source-stores) ","))
(disk_capacity . ,(assoc-ref install-spec 'disk-capacity))
(root_size . ,(assoc-ref install-spec 'root-size))
(efi_size . ,(assoc-ref install-spec 'efi-size))
(closure_path . ,(assoc-ref result 'closure-path))
(host_base_store_count . ,(length host-base-stores))
(host_base_stores . ,(string-join host-base-stores ","))
(native_base_store_count . ,(length native-base-stores))
(native_base_stores . ,(string-join native-base-stores ","))
(fruix_runtime_store_count . ,(length fruix-runtime-stores))
(fruix_runtime_stores . ,(string-join fruix-runtime-stores ","))
(host_base_provenance_file . ,(assoc-ref result 'host-base-provenance-file))
(store_layout_file . ,(assoc-ref result 'store-layout-file))
(host_freebsd_version . ,(assoc-ref host-provenance 'freebsd-version-kru))
(host_uname . ,(assoc-ref host-provenance 'uname))
(usr_src_git_revision . ,(assoc-ref host-provenance 'usr-src-git-revision))
(usr_src_git_branch . ,(assoc-ref host-provenance 'usr-src-git-branch))
(usr_src_newvers_sha256 . ,(assoc-ref host-provenance 'usr-src-newvers-sha256))
(store_item_count . ,(length store-items))))))
(define (emit-system-image-metadata os-file resolved-symbol store-dir os result)
(let* ((image-spec (assoc-ref result 'image-spec))
(store-items (assoc-ref result 'store-items))
(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 . "image")
(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) ","))
(disk_capacity . ,(assoc-ref image-spec 'disk-capacity))
(root_size . ,(assoc-ref image-spec 'root-size))
(image_store_path . ,(assoc-ref result 'image-store-path))
(disk_image . ,(assoc-ref result 'disk-image))
(esp_image . ,(assoc-ref result 'esp-image))
(root_image . ,(assoc-ref result 'root-image))
(closure_path . ,(assoc-ref result 'closure-path))
(host_base_store_count . ,(length host-base-stores))
(host_base_stores . ,(string-join host-base-stores ","))
(native_base_store_count . ,(length native-base-stores))
(native_base_stores . ,(string-join native-base-stores ","))
(fruix_runtime_store_count . ,(length fruix-runtime-stores))
(fruix_runtime_stores . ,(string-join fruix-runtime-stores ","))
(host_base_provenance_file . ,(assoc-ref result 'host-base-provenance-file))
(store_layout_file . ,(assoc-ref result 'store-layout-file))
(host_freebsd_version . ,(assoc-ref host-provenance 'freebsd-version-kru))
(host_uname . ,(assoc-ref host-provenance 'uname))
(usr_src_git_revision . ,(assoc-ref host-provenance 'usr-src-git-revision))
(usr_src_git_branch . ,(assoc-ref host-provenance 'usr-src-git-branch))
(usr_src_newvers_sha256 . ,(assoc-ref host-provenance 'usr-src-newvers-sha256))
(store_item_count . ,(length store-items))))))
(define (main argv)
(let* ((parsed (parse-arguments argv))
(command (assoc-ref parsed 'command))
(action (assoc-ref parsed 'action))
(positional (assoc-ref parsed 'positional))
(store-dir (assoc-ref parsed 'store-dir))
(disk-capacity (assoc-ref parsed 'disk-capacity))
(root-size (assoc-ref parsed 'root-size))
(rootfs-opt (assoc-ref parsed 'rootfs))
(system-name (assoc-ref parsed 'system-name))
(requested-symbol (and system-name (string->symbol system-name))))
(store-dir (assoc-ref parsed 'store-dir)))
(cond
((member action '("build" "image" "rootfs")) #t)
(else (error "unknown system action" action)))
(let* ((os-file (match positional
((file . _) file)
(() (error "missing operating-system file argument"))))
(rootfs (or rootfs-opt
(and (string=? action "rootfs")
(match positional
((_ dir) dir)
((_ _ dir . _) dir)
(_ #f))))))
(call-with-values
(lambda ()
(load-operating-system-from-file os-file requested-symbol))
(lambda (os resolved-symbol)
(let* ((guile-prefix (or (getenv "GUILE_PREFIX") "/tmp/guile-freebsd-validate-install"))
(guile-extra-prefix (or (getenv "GUILE_EXTRA_PREFIX") "/tmp/guile-gnutls-freebsd-validate-install"))
(shepherd-prefix (or (getenv "SHEPHERD_PREFIX") "/tmp/shepherd-freebsd-validate-install")))
(cond
((string=? action "build")
(let* ((result (materialize-operating-system os
#:store-dir store-dir
#:guile-prefix guile-prefix
#:guile-extra-prefix guile-extra-prefix
#:shepherd-prefix shepherd-prefix))
(closure-path (assoc-ref result 'closure-path))
(generated-files (assoc-ref result 'generated-files))
(references (assoc-ref result 'references))
(base-package-stores (assoc-ref result 'base-package-stores))
(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)))
((string=? command "system")
(let* ((positional (assoc-ref parsed 'positional))
(disk-capacity (assoc-ref parsed 'disk-capacity))
(root-size (assoc-ref parsed 'root-size))
(target-opt (assoc-ref parsed 'target))
(rootfs-opt (assoc-ref parsed 'rootfs))
(system-name (assoc-ref parsed 'system-name))
(requested-symbol (and system-name (string->symbol system-name))))
(unless (member action '("build" "image" "install" "rootfs"))
(error "unknown system action" action))
(let* ((os-file (match positional
((file . _) file)
(() (error "missing operating-system file argument"))))
(target (or target-opt
(and (string=? action "install")
(match positional
((_ target-path) target-path)
(_ #f)))))
(rootfs (or rootfs-opt
(and (string=? action "rootfs")
(match positional
((_ dir) dir)
((_ _ dir . _) dir)
(_ #f))))))
(call-with-values
(lambda ()
(load-operating-system-from-file os-file requested-symbol))
(lambda (os resolved-symbol)
(let* ((guile-prefix (or (getenv "GUILE_PREFIX") "/tmp/guile-freebsd-validate-install"))
(guile-extra-prefix (or (getenv "GUILE_EXTRA_PREFIX") "/tmp/guile-gnutls-freebsd-validate-install"))
(shepherd-prefix (or (getenv "SHEPHERD_PREFIX") "/tmp/shepherd-freebsd-validate-install")))
(cond
((string=? action "build")
(emit-system-build-metadata
os-file resolved-symbol store-dir os
(materialize-operating-system os
#:store-dir store-dir
#:guile-prefix guile-prefix
#:guile-extra-prefix guile-extra-prefix
#:shepherd-prefix shepherd-prefix)))
((string=? action "rootfs")
(unless rootfs
(error "rootfs action requires ROOTFS-DIR or --rootfs DIR"))
(let ((result (materialize-rootfs os rootfs
#:store-dir store-dir
#:guile-prefix guile-prefix
#:guile-extra-prefix guile-extra-prefix
#:shepherd-prefix shepherd-prefix)))
(emit-metadata
`((action . "rootfs")
(os_file . ,os-file)
(system_variable . ,resolved-symbol)
(store_dir . ,store-dir)
(rootfs . ,(assoc-ref result 'rootfs))
(closure_path . ,(assoc-ref result 'closure-path))
(ready_marker . ,(assoc-ref result 'ready-marker))
(rc_script . ,(assoc-ref result 'rc-script))))))
((string=? action "image")
(emit-system-image-metadata
os-file resolved-symbol store-dir os
(materialize-bhyve-image os
#:store-dir store-dir
#:guile-prefix guile-prefix
#:guile-extra-prefix guile-extra-prefix
#:shepherd-prefix shepherd-prefix
#:root-size (or root-size "256m")
#:disk-capacity disk-capacity)))
((string=? action "install")
(unless target
(error "install action requires TARGET or --target PATH"))
(emit-system-install-metadata
os-file resolved-symbol store-dir os
(install-operating-system os
#:target target
#:store-dir store-dir
#:guile-prefix guile-prefix
#:guile-extra-prefix guile-extra-prefix
#:shepherd-prefix shepherd-prefix
#:root-size root-size
#:disk-capacity disk-capacity))))))))))
((string=? command "source")
(let* ((positional (assoc-ref parsed 'positional))
(cache-dir (assoc-ref parsed 'cache-dir))
(source-name (assoc-ref parsed 'source-name))
(requested-symbol (and source-name (string->symbol source-name))))
(unless (string=? action "materialize")
(error "unknown source action" action))
(let ((source-file (match positional
((file . _) file)
(() (error "missing freebsd-source file argument")))))
(call-with-values
(lambda ()
(load-freebsd-source-from-file source-file requested-symbol))
(lambda (source resolved-symbol)
(let* ((result (materialize-freebsd-source source
#:store-dir store-dir
#:cache-dir cache-dir))
(effective (assoc-ref result 'effective-source)))
(emit-metadata
`((action . "build")
(os_file . ,os-file)
(system_variable . ,resolved-symbol)
`((action . "materialize")
(source_file . ,source-file)
(source_variable . ,resolved-symbol)
(store_dir . ,store-dir)
(closure_path . ,closure-path)
(freebsd_base_name . ,(freebsd-base-name base))
(freebsd_base_version_label . ,(freebsd-base-version-label base))
(freebsd_base_release . ,(freebsd-base-release base))
(freebsd_base_branch . ,(freebsd-base-branch base))
(freebsd_base_source_root . ,(freebsd-base-source-root base))
(freebsd_base_target . ,(freebsd-base-target base))
(freebsd_base_target_arch . ,(freebsd-base-target-arch base))
(freebsd_base_kernconf . ,(freebsd-base-kernconf base))
(freebsd_base_file . ,(assoc-ref result 'freebsd-base-file))
(cache_dir . ,cache-dir)
(freebsd_source_name . ,(freebsd-source-name source))
(freebsd_source_kind . ,(freebsd-source-kind source))
(freebsd_source_url . ,(or (freebsd-source-url source) ""))
@@ -200,105 +516,18 @@ Options:\n\
(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))
(ready_marker . ,(operating-system-ready-marker os))
(kernel_store . ,(assoc-ref result 'kernel-store))
(bootloader_store . ,(assoc-ref result 'bootloader-store))
(guile_store . ,(assoc-ref result 'guile-store))
(guile_extra_store . ,(assoc-ref result 'guile-extra-store))
(shepherd_store . ,(assoc-ref result 'shepherd-store))
(base_package_store_count . ,(length base-package-stores))
(base_package_stores . ,(string-join base-package-stores ","))
(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))
(generated_file_count . ,(length generated-files))
(reference_count . ,(length references))))))
((string=? action "rootfs")
(unless rootfs
(error "rootfs action requires ROOTFS-DIR or --rootfs DIR"))
(let ((result (materialize-rootfs os rootfs
#:store-dir store-dir
#:guile-prefix guile-prefix
#:guile-extra-prefix guile-extra-prefix
#:shepherd-prefix shepherd-prefix)))
(emit-metadata
`((action . "rootfs")
(os_file . ,os-file)
(system_variable . ,resolved-symbol)
(store_dir . ,store-dir)
(rootfs . ,(assoc-ref result 'rootfs))
(closure_path . ,(assoc-ref result 'closure-path))
(ready_marker . ,(assoc-ref result 'ready-marker))
(rc_script . ,(assoc-ref result 'rc-script))))))
((string=? action "image")
(let* ((result (materialize-bhyve-image os
#:store-dir store-dir
#:guile-prefix guile-prefix
#:guile-extra-prefix guile-extra-prefix
#:shepherd-prefix shepherd-prefix
#:root-size (or root-size "256m")
#:disk-capacity disk-capacity))
(image-spec (assoc-ref result 'image-spec))
(store-items (assoc-ref result 'store-items))
(host-base-stores (assoc-ref result 'host-base-stores))
(native-base-stores (assoc-ref result 'native-base-stores))
(fruix-runtime-stores (assoc-ref result 'fruix-runtime-stores))
(base (operating-system-freebsd-base os))
(source (freebsd-base-source base))
(host-provenance (call-with-input-file (assoc-ref result 'host-base-provenance-file) read)))
(emit-metadata
`((action . "image")
(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))
(disk_capacity . ,(assoc-ref image-spec 'disk-capacity))
(root_size . ,(assoc-ref image-spec 'root-size))
(image_store_path . ,(assoc-ref result 'image-store-path))
(disk_image . ,(assoc-ref result 'disk-image))
(esp_image . ,(assoc-ref result 'esp-image))
(root_image . ,(assoc-ref result 'root-image))
(closure_path . ,(assoc-ref result 'closure-path))
(host_base_store_count . ,(length host-base-stores))
(host_base_stores . ,(string-join host-base-stores ","))
(native_base_store_count . ,(length native-base-stores))
(native_base_stores . ,(string-join native-base-stores ","))
(fruix_runtime_store_count . ,(length fruix-runtime-stores))
(fruix_runtime_stores . ,(string-join fruix-runtime-stores ","))
(host_base_provenance_file . ,(assoc-ref result 'host-base-provenance-file))
(store_layout_file . ,(assoc-ref result 'store-layout-file))
(host_freebsd_version . ,(assoc-ref host-provenance 'freebsd-version-kru))
(host_uname . ,(assoc-ref host-provenance 'uname))
(usr_src_git_revision . ,(assoc-ref host-provenance 'usr-src-git-revision))
(usr_src_git_branch . ,(assoc-ref host-provenance 'usr-src-git-branch))
(usr_src_newvers_sha256 . ,(assoc-ref host-provenance 'usr-src-newvers-sha256))
(store_item_count . ,(length store-items)))))))))))))
(materialized_source_store . ,(assoc-ref result 'source-store-path))
(materialized_source_root . ,(assoc-ref result 'source-root))
(materialized_source_info_file . ,(assoc-ref result 'source-info-file))
(materialized_source_tree_sha256 . ,(assoc-ref result 'source-tree-sha256))
(materialized_source_cache_path . ,(or (assoc-ref result 'cache-path) ""))
(materialized_source_kind . ,(assoc-ref effective 'kind))
(materialized_source_url . ,(or (assoc-ref effective 'url) ""))
(materialized_source_path . ,(or (assoc-ref effective 'path) ""))
(materialized_source_ref . ,(or (assoc-ref effective 'ref) ""))
(materialized_source_commit . ,(or (assoc-ref result 'effective-commit) ""))
(materialized_source_sha256 . ,(or (assoc-ref result 'effective-sha256) ""))))))))))
(else
(usage 1)))))
(main (command-line))

View File

@@ -0,0 +1,7 @@
(use-modules (fruix packages freebsd))
(define phase16-source
(freebsd-source
#:name "__SOURCE_NAME__"
#:kind 'git
#:ref "__SOURCE_REF__"))

View File

@@ -0,0 +1,90 @@
(use-modules (fruix system freebsd)
(fruix packages freebsd))
(define phase16-source
(freebsd-source
#:name "__SOURCE_NAME__"
#:kind 'git
#:ref "__SOURCE_REF__"))
(define phase16-base
(freebsd-base
#:name "__BASE_NAME__"
#:version-label "__BASE_VERSION_LABEL__"
#:release "__BASE_RELEASE__"
#:branch "__BASE_BRANCH__"
#:source phase16-source
#:source-root "__DECLARED_SOURCE_ROOT__"
#:target "amd64"
#:target-arch "amd64"
#:kernconf "GENERIC"))
(define phase16-operating-system
(operating-system
#:host-name "fruix-freebsd"
#:freebsd-base phase16-base
#:kernel (freebsd-native-kernel-for phase16-base)
#:bootloader (freebsd-native-bootloader-for phase16-base)
#:base-packages (freebsd-native-system-packages-for phase16-base)
#:groups (list (user-group #:name "wheel" #:gid 0 #:system? #t)
(user-group #:name "sshd" #:gid 22 #:system? #t)
(user-group #:name "_dhcp" #:gid 65 #:system? #t)
(user-group #:name "operator" #:gid 1000 #:system? #f))
#:users (list (user-account #:name "root"
#:uid 0
#:group "wheel"
#:comment "Charlie &"
#:home "/root"
#:shell "/bin/sh"
#:system? #t)
(user-account #:name "sshd"
#:uid 22
#:group "sshd"
#:comment "Secure Shell Daemon"
#:home "/var/empty"
#:shell "/usr/sbin/nologin"
#:system? #t)
(user-account #:name "_dhcp"
#:uid 65
#:group "_dhcp"
#:comment "dhcp programs"
#:home "/var/empty"
#:shell "/usr/sbin/nologin"
#:system? #t)
(user-account #:name "operator"
#:uid 1000
#:group "operator"
#:supplementary-groups '("wheel")
#:comment "Fruix Operator"
#:home "/home/operator"
#:shell "/bin/sh"
#:system? #f))
#:file-systems (list (file-system #:device "/dev/gpt/fruix-root"
#:mount-point "/"
#:type "ufs"
#:options "rw"
#:needed-for-boot? #t)
(file-system #:device "devfs"
#:mount-point "/dev"
#:type "devfs"
#:options "rw"
#:needed-for-boot? #t)
(file-system #:device "tmpfs"
#:mount-point "/tmp"
#:type "tmpfs"
#:options "rw,size=64m"))
#:services '(shepherd ready-marker sshd)
#:loader-entries '(("autoboot_delay" . "1")
("boot_multicons" . "YES")
("boot_serial" . "YES")
("console" . "comconsole,vidconsole"))
#:rc-conf-entries '(("clear_tmp_enable" . "NO")
("hostid_enable" . "NO")
("sendmail_enable" . "NONE")
("sshd_enable" . "YES")
("ifconfig_xn0" . "SYNCDHCP")
("ifconfig_em0" . "SYNCDHCP")
("ifconfig_vtnet0" . "SYNCDHCP"))
#:init-mode 'shepherd-pid1
#:ready-marker "/var/lib/fruix/ready"
#:root-authorized-keys '("__ROOT_AUTHORIZED_KEY__")))

View File

@@ -0,0 +1,8 @@
(use-modules (fruix packages freebsd))
(define phase16-source
(freebsd-source
#:name "__SOURCE_NAME__"
#:kind 'src-txz
#:url "__SOURCE_URL__"
#:sha256 "__SOURCE_SHA256__"))

View File

@@ -0,0 +1,91 @@
(use-modules (fruix system freebsd)
(fruix packages freebsd))
(define phase17-source
(freebsd-source
#:name "__SOURCE_NAME__"
#:kind 'git
#:ref "__SOURCE_REF__"
#:commit "__SOURCE_COMMIT__"))
(define phase17-base
(freebsd-base
#:name "__BASE_NAME__"
#:version-label "__BASE_VERSION_LABEL__"
#:release "__BASE_RELEASE__"
#:branch "__BASE_BRANCH__"
#:source phase17-source
#:source-root "__DECLARED_SOURCE_ROOT__"
#:target "amd64"
#:target-arch "amd64"
#:kernconf "GENERIC"))
(define phase17-operating-system
(operating-system
#:host-name "fruix-freebsd"
#:freebsd-base phase17-base
#:kernel (freebsd-native-kernel-for phase17-base)
#:bootloader (freebsd-native-bootloader-for phase17-base)
#:base-packages (freebsd-native-system-packages-for phase17-base)
#:groups (list (user-group #:name "wheel" #:gid 0 #:system? #t)
(user-group #:name "sshd" #:gid 22 #:system? #t)
(user-group #:name "_dhcp" #:gid 65 #:system? #t)
(user-group #:name "operator" #:gid 1000 #:system? #f))
#:users (list (user-account #:name "root"
#:uid 0
#:group "wheel"
#:comment "Charlie &"
#:home "/root"
#:shell "/bin/sh"
#:system? #t)
(user-account #:name "sshd"
#:uid 22
#:group "sshd"
#:comment "Secure Shell Daemon"
#:home "/var/empty"
#:shell "/usr/sbin/nologin"
#:system? #t)
(user-account #:name "_dhcp"
#:uid 65
#:group "_dhcp"
#:comment "dhcp programs"
#:home "/var/empty"
#:shell "/usr/sbin/nologin"
#:system? #t)
(user-account #:name "operator"
#:uid 1000
#:group "operator"
#:supplementary-groups '("wheel")
#:comment "Fruix Operator"
#:home "/home/operator"
#:shell "/bin/sh"
#:system? #f))
#:file-systems (list (file-system #:device "/dev/gpt/fruix-root"
#:mount-point "/"
#:type "ufs"
#:options "rw"
#:needed-for-boot? #t)
(file-system #:device "devfs"
#:mount-point "/dev"
#:type "devfs"
#:options "rw"
#:needed-for-boot? #t)
(file-system #:device "tmpfs"
#:mount-point "/tmp"
#:type "tmpfs"
#:options "rw,size=64m"))
#:services '(shepherd ready-marker sshd)
#:loader-entries '(("autoboot_delay" . "1")
("boot_multicons" . "YES")
("boot_serial" . "YES")
("console" . "comconsole,vidconsole"))
#:rc-conf-entries '(("clear_tmp_enable" . "NO")
("hostid_enable" . "NO")
("sendmail_enable" . "NONE")
("sshd_enable" . "YES")
("ifconfig_xn0" . "SYNCDHCP")
("ifconfig_em0" . "SYNCDHCP")
("ifconfig_vtnet0" . "SYNCDHCP"))
#:init-mode 'shepherd-pid1
#:ready-marker "/var/lib/fruix/ready"
#:root-authorized-keys '("__ROOT_AUTHORIZED_KEY__")))

View File

@@ -0,0 +1,91 @@
(use-modules (fruix system freebsd)
(fruix packages freebsd))
(define phase17-source
(freebsd-source
#:name "__SOURCE_NAME__"
#:kind 'src-txz
#:url "__SOURCE_URL__"
#:sha256 "__SOURCE_SHA256__"))
(define phase17-base
(freebsd-base
#:name "__BASE_NAME__"
#:version-label "__BASE_VERSION_LABEL__"
#:release "__BASE_RELEASE__"
#:branch "__BASE_BRANCH__"
#:source phase17-source
#:source-root "__DECLARED_SOURCE_ROOT__"
#:target "amd64"
#:target-arch "amd64"
#:kernconf "GENERIC"))
(define phase17-operating-system
(operating-system
#:host-name "fruix-freebsd"
#:freebsd-base phase17-base
#:kernel (freebsd-native-kernel-for phase17-base)
#:bootloader (freebsd-native-bootloader-for phase17-base)
#:base-packages (freebsd-native-system-packages-for phase17-base)
#:groups (list (user-group #:name "wheel" #:gid 0 #:system? #t)
(user-group #:name "sshd" #:gid 22 #:system? #t)
(user-group #:name "_dhcp" #:gid 65 #:system? #t)
(user-group #:name "operator" #:gid 1000 #:system? #f))
#:users (list (user-account #:name "root"
#:uid 0
#:group "wheel"
#:comment "Charlie &"
#:home "/root"
#:shell "/bin/sh"
#:system? #t)
(user-account #:name "sshd"
#:uid 22
#:group "sshd"
#:comment "Secure Shell Daemon"
#:home "/var/empty"
#:shell "/usr/sbin/nologin"
#:system? #t)
(user-account #:name "_dhcp"
#:uid 65
#:group "_dhcp"
#:comment "dhcp programs"
#:home "/var/empty"
#:shell "/usr/sbin/nologin"
#:system? #t)
(user-account #:name "operator"
#:uid 1000
#:group "operator"
#:supplementary-groups '("wheel")
#:comment "Fruix Operator"
#:home "/home/operator"
#:shell "/bin/sh"
#:system? #f))
#:file-systems (list (file-system #:device "/dev/gpt/fruix-root"
#:mount-point "/"
#:type "ufs"
#:options "rw"
#:needed-for-boot? #t)
(file-system #:device "devfs"
#:mount-point "/dev"
#:type "devfs"
#:options "rw"
#:needed-for-boot? #t)
(file-system #:device "tmpfs"
#:mount-point "/tmp"
#:type "tmpfs"
#:options "rw,size=64m"))
#:services '(shepherd ready-marker sshd)
#:loader-entries '(("autoboot_delay" . "1")
("boot_multicons" . "YES")
("boot_serial" . "YES")
("console" . "comconsole,vidconsole"))
#:rc-conf-entries '(("clear_tmp_enable" . "NO")
("hostid_enable" . "NO")
("sendmail_enable" . "NONE")
("sshd_enable" . "YES")
("ifconfig_xn0" . "SYNCDHCP")
("ifconfig_em0" . "SYNCDHCP")
("ifconfig_vtnet0" . "SYNCDHCP"))
#:init-mode 'shepherd-pid1
#:ready-marker "/var/lib/fruix/ready"
#:root-authorized-keys '("__ROOT_AUTHORIZED_KEY__")))

View File

@@ -0,0 +1,91 @@
(use-modules (fruix system freebsd)
(fruix packages freebsd))
(define phase18-source
(freebsd-source
#:name "__SOURCE_NAME__"
#:kind 'git
#:ref "__SOURCE_REF__"
#:commit "__SOURCE_COMMIT__"))
(define phase18-base
(freebsd-base
#:name "__BASE_NAME__"
#:version-label "__BASE_VERSION_LABEL__"
#:release "__BASE_RELEASE__"
#:branch "__BASE_BRANCH__"
#:source phase18-source
#:source-root "__DECLARED_SOURCE_ROOT__"
#:target "amd64"
#:target-arch "amd64"
#:kernconf "GENERIC"))
(define phase18-operating-system
(operating-system
#:host-name "fruix-freebsd"
#:freebsd-base phase18-base
#:kernel (freebsd-native-kernel-for phase18-base)
#:bootloader (freebsd-native-bootloader-for phase18-base)
#:base-packages (freebsd-native-system-packages-for phase18-base)
#:groups (list (user-group #:name "wheel" #:gid 0 #:system? #t)
(user-group #:name "sshd" #:gid 22 #:system? #t)
(user-group #:name "_dhcp" #:gid 65 #:system? #t)
(user-group #:name "operator" #:gid 1000 #:system? #f))
#:users (list (user-account #:name "root"
#:uid 0
#:group "wheel"
#:comment "Charlie &"
#:home "/root"
#:shell "/bin/sh"
#:system? #t)
(user-account #:name "sshd"
#:uid 22
#:group "sshd"
#:comment "Secure Shell Daemon"
#:home "/var/empty"
#:shell "/usr/sbin/nologin"
#:system? #t)
(user-account #:name "_dhcp"
#:uid 65
#:group "_dhcp"
#:comment "dhcp programs"
#:home "/var/empty"
#:shell "/usr/sbin/nologin"
#:system? #t)
(user-account #:name "operator"
#:uid 1000
#:group "operator"
#:supplementary-groups '("wheel")
#:comment "Fruix Operator"
#:home "/home/operator"
#:shell "/bin/sh"
#:system? #f))
#:file-systems (list (file-system #:device "/dev/gpt/fruix-root"
#:mount-point "/"
#:type "ufs"
#:options "rw"
#:needed-for-boot? #t)
(file-system #:device "devfs"
#:mount-point "/dev"
#:type "devfs"
#:options "rw"
#:needed-for-boot? #t)
(file-system #:device "tmpfs"
#:mount-point "/tmp"
#:type "tmpfs"
#:options "rw,size=64m"))
#:services '(shepherd ready-marker sshd)
#:loader-entries '(("autoboot_delay" . "1")
("boot_multicons" . "YES")
("boot_serial" . "YES")
("console" . "comconsole,vidconsole"))
#:rc-conf-entries '(("clear_tmp_enable" . "NO")
("hostid_enable" . "NO")
("sendmail_enable" . "NONE")
("sshd_enable" . "YES")
("ifconfig_xn0" . "SYNCDHCP")
("ifconfig_em0" . "SYNCDHCP")
("ifconfig_vtnet0" . "SYNCDHCP"))
#:init-mode 'freebsd-init+rc.d-shepherd
#:ready-marker "/var/lib/fruix/ready"
#:root-authorized-keys '("__ROOT_AUTHORIZED_KEY__")))

View File

@@ -8,6 +8,7 @@ metadata_target=${METADATA_OUT:-}
root_authorized_key_file=${ROOT_AUTHORIZED_KEY_FILE:-$HOME/.ssh/id_ed25519.pub}
root_ssh_private_key_file=${ROOT_SSH_PRIVATE_KEY_FILE:-$HOME/.ssh/id_ed25519}
ssh_port=${QEMU_SSH_PORT:-10022}
qemu_smp=${QEMU_SMP:-2}
disk_capacity=${DISK_CAPACITY:-5g}
cleanup=0
@@ -75,7 +76,7 @@ sudo qemu-system-x86_64 \
-machine q35,accel=tcg \
-cpu max \
-m 2048 \
-smp 2 \
-smp "$qemu_smp" \
-display none \
-serial "file:$serial_log" \
-monitor none \
@@ -157,6 +158,7 @@ raw_sha256=$raw_sha256
serial_log=$serial_log
qemu_pidfile=$qemu_pidfile
ssh_port=$ssh_port
qemu_smp=$qemu_smp
ready_marker=$ready_marker
run_current_system_target=$run_current_system_target
pid1_command=$pid1_command

View File

@@ -139,13 +139,29 @@ grep -F 'native-base-stores' "$store_layout_file" >/dev/null || {
exit 1
}
grep -F 'source-root . "/usr/src"' "$world_build_info" >/dev/null || {
echo "world build info is missing /usr/src provenance" >&2
grep -F '(materialized-source' "$world_build_info" >/dev/null || {
echo "world build info is missing materialized source metadata" >&2
exit 1
}
grep -F 'path . "/usr/src"' "$world_build_info" >/dev/null || {
echo "world build info is missing declared local-tree source provenance" >&2
exit 1
}
grep -F 'source-root . "/frx/store/' "$world_build_info" >/dev/null || {
echo "world build info is not using a materialized source root" >&2
exit 1
}
grep -F 'source-root . "/usr/src"' "$kernel_build_info" >/dev/null || {
echo "kernel build info is missing /usr/src provenance" >&2
grep -F '(materialized-source' "$kernel_build_info" >/dev/null || {
echo "kernel build info is missing materialized source metadata" >&2
exit 1
}
grep -F 'path . "/usr/src"' "$kernel_build_info" >/dev/null || {
echo "kernel build info is missing declared local-tree source provenance" >&2
exit 1
}
grep -F 'source-root . "/frx/store/' "$kernel_build_info" >/dev/null || {
echo "kernel build info is not using a materialized source root" >&2
exit 1
}

View File

@@ -0,0 +1,252 @@
#!/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/phase16-git-materialized-source-operating-system.scm.in}
system_name=${SYSTEM_NAME:-phase16-operating-system}
store_dir=${STORE_DIR:-/frx/store}
base_name=${BASE_NAME:-git-materialized-source}
base_version_label=${BASE_VERSION_LABEL:-15.0-STABLE-git-materialized}
base_release=${BASE_RELEASE:-15.0-STABLE}
base_branch=${BASE_BRANCH:-stable/15}
source_name=${SOURCE_NAME:-stable15-network-source}
source_ref=${SOURCE_REF:-stable/15}
declared_source_root=${DECLARED_SOURCE_ROOT:-/var/empty/fruix-unused-source-root}
metadata_target=${METADATA_OUT:-}
root_authorized_key_file=${ROOT_AUTHORIZED_KEY_FILE:-$HOME/.ssh/id_ed25519.pub}
[ -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
}
cleanup=0
if [ -n "${WORKDIR:-}" ]; then
workdir=$WORKDIR
mkdir -p "$workdir"
else
workdir=$(mktemp -d /tmp/fruix-phase16-source-native-build.XXXXXX)
cleanup=1
fi
if [ "${KEEP_WORKDIR:-0}" -eq 1 ]; then
cleanup=0
fi
cleanup_workdir() {
if [ "$cleanup" -eq 1 ]; then
rm -rf "$workdir" 2>/dev/null || sudo rm -rf "$workdir"
fi
}
trap cleanup_workdir EXIT INT TERM
phase16_os_file=$workdir/phase16-git-materialized-source-operating-system.scm
build_out=$workdir/build.txt
metadata_file=$workdir/phase16-source-driven-native-build-metadata.txt
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|__DECLARED_SOURCE_ROOT__|$declared_source_root|g" \
-e "s|__ROOT_AUTHORIZED_KEY__|$root_authorized_key|g" \
"$os_template" > "$phase16_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}" \
"$@"
}
action_env "$fruix_cmd" system build "$phase16_os_file" --system "$system_name" --store "$store_dir" >"$build_out"
field() {
sed -n "s/^$1=//p" "$build_out" | tail -n 1
}
closure_path=$(field closure_path)
kernel_store=$(field kernel_store)
bootloader_store=$(field bootloader_store)
native_base_store_count=$(field native_base_store_count)
native_base_stores=$(field native_base_stores)
host_base_store_count=$(field host_base_store_count)
freebsd_base_name_out=$(field freebsd_base_name)
freebsd_base_version_label_out=$(field freebsd_base_version_label)
freebsd_base_release_out=$(field freebsd_base_release)
freebsd_base_branch_out=$(field freebsd_base_branch)
freebsd_base_source_root_out=$(field freebsd_base_source_root)
freebsd_base_file=$(field freebsd_base_file)
freebsd_source_name_out=$(field freebsd_source_name)
freebsd_source_kind_out=$(field freebsd_source_kind)
freebsd_source_url_out=$(field freebsd_source_url)
freebsd_source_ref_out=$(field freebsd_source_ref)
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)
store_layout_file=$(field store_layout_file)
[ -n "$closure_path" ] || { echo "missing closure path" >&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; }
[ "$materialized_source_store_count" = 1 ] || { echo "expected one materialized source store, got: $materialized_source_store_count" >&2; exit 1; }
[ "$freebsd_base_name_out" = "$base_name" ] || { echo "unexpected freebsd base name: $freebsd_base_name_out" >&2; exit 1; }
[ "$freebsd_base_version_label_out" = "$base_version_label" ] || { echo "unexpected freebsd base version label: $freebsd_base_version_label_out" >&2; exit 1; }
[ "$freebsd_base_release_out" = "$base_release" ] || { echo "unexpected freebsd base release: $freebsd_base_release_out" >&2; exit 1; }
[ "$freebsd_base_branch_out" = "$base_branch" ] || { echo "unexpected freebsd base branch: $freebsd_base_branch_out" >&2; exit 1; }
[ "$freebsd_base_source_root_out" = "$declared_source_root" ] || { echo "unexpected declared source root: $freebsd_base_source_root_out" >&2; exit 1; }
[ "$freebsd_source_name_out" = "$source_name" ] || { echo "unexpected freebsd source name: $freebsd_source_name_out" >&2; exit 1; }
[ "$freebsd_source_kind_out" = git ] || { echo "unexpected freebsd source kind: $freebsd_source_kind_out" >&2; exit 1; }
[ "$freebsd_source_url_out" = https://git.FreeBSD.org/src.git ] || { echo "unexpected freebsd source URL: $freebsd_source_url_out" >&2; exit 1; }
[ "$freebsd_source_ref_out" = "$source_ref" ] || { echo "unexpected freebsd source ref: $freebsd_source_ref_out" >&2; exit 1; }
[ -f "$freebsd_base_file" ] || { echo "missing freebsd base file: $freebsd_base_file" >&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 "$kernel_store" in
/frx/store/*-freebsd-native-kernel-$base_version_label) : ;;
*) echo "unexpected kernel store path: $kernel_store" >&2; exit 1 ;;
esac
case "$bootloader_store" in
/frx/store/*-freebsd-native-bootloader-$base_version_label) : ;;
*) echo "unexpected bootloader store path: $bootloader_store" >&2; exit 1 ;;
esac
case "$materialized_source_stores" in
/frx/store/*-freebsd-source-$source_name) : ;;
*) echo "unexpected materialized source store path: $materialized_source_stores" >&2; exit 1 ;;
esac
materialized_source_root=$materialized_source_stores/tree
[ -f "$materialized_source_root/Makefile" ] || { echo "materialized git source root missing Makefile" >&2; exit 1; }
[ -f "$materialized_source_root/sys/conf/newvers.sh" ] || { echo "materialized git source root missing newvers.sh" >&2; exit 1; }
runtime_store=$(printf '%s\n' "$native_base_stores" | tr ',' '\n' | grep "freebsd-native-runtime-$base_version_label$" | head -n 1)
[ -n "$runtime_store" ] || { echo "failed to recover runtime store" >&2; exit 1; }
resolved_commit=$(grep -Eo '\(commit \. "[0-9a-f]{40}"\)' "$freebsd_source_materializations_file" | head -n 1 | sed 's/.*"\([0-9a-f]\{40\}\)".*/\1/')
printf '%s\n' "$resolved_commit" | grep -E '^[0-9a-f]{40}$' >/dev/null || {
echo "failed to recover resolved git commit from source materializations file" >&2
exit 1
}
grep -F "$materialized_source_stores" "$freebsd_source_materializations_file" >/dev/null || {
echo "source materializations file missing store path" >&2
exit 1
}
grep -F "$materialized_source_root" "$freebsd_source_materializations_file" >/dev/null || {
echo "source materializations file missing source root" >&2
exit 1
}
grep -F "(materialized-source-store-count . 1)" "$store_layout_file" >/dev/null || {
echo "store layout file missing materialized source count" >&2
exit 1
}
grep -F "$materialized_source_stores" "$store_layout_file" >/dev/null || {
echo "store layout file missing materialized source store path" >&2
exit 1
}
for path in "$kernel_store/.freebsd-native-build-info.scm" "$bootloader_store/.freebsd-native-build-info.scm" "$runtime_store/.freebsd-native-build-info.scm"; do
[ -f "$path" ] || {
echo "missing native build info file: $path" >&2
exit 1
}
grep -F "(declared-source" "$path" >/dev/null || {
echo "native build info missing declared-source block in $path" >&2
exit 1
}
grep -F "(materialized-source" "$path" >/dev/null || {
echo "native build info missing materialized-source block in $path" >&2
exit 1
}
grep -F "(ref . \"$source_ref\")" "$path" >/dev/null || {
echo "native build info missing declared source ref in $path" >&2
exit 1
}
grep -F "(source-root . \"$materialized_source_root\")" "$path" >/dev/null || {
echo "native build info missing materialized source root in $path" >&2
exit 1
}
grep -F "(store-path . \"$materialized_source_stores\")" "$path" >/dev/null || {
echo "native build info missing materialized source store path in $path" >&2
exit 1
}
grep -F "(commit . \"$resolved_commit\")" "$path" >/dev/null || {
echo "native build info missing resolved git commit in $path" >&2
exit 1
}
if grep -F "(source-root . \"$declared_source_root\")" "$path" >/dev/null; then
echo "native build info still records the unused declared source root in $path" >&2
exit 1
fi
done
grep -F "(source-root . \"$declared_source_root\")" "$closure_path/parameters.scm" >/dev/null || {
echo "closure parameters no longer record the declared transitional source root" >&2
exit 1
}
closure_base=$(basename "$closure_path")
cat >"$metadata_file" <<EOF
workdir=$workdir
phase16_os_file=$phase16_os_file
closure_path=$closure_path
closure_base=$closure_base
kernel_store=$kernel_store
bootloader_store=$bootloader_store
runtime_store=$runtime_store
native_base_store_count=$native_base_store_count
native_base_stores=$native_base_stores
host_base_store_count=$host_base_store_count
freebsd_base_name=$freebsd_base_name_out
freebsd_base_version_label=$freebsd_base_version_label_out
freebsd_base_release=$freebsd_base_release_out
freebsd_base_branch=$freebsd_base_branch_out
freebsd_base_source_root=$freebsd_base_source_root_out
freebsd_base_file=$freebsd_base_file
freebsd_source_name=$freebsd_source_name_out
freebsd_source_kind=$freebsd_source_kind_out
freebsd_source_url=$freebsd_source_url_out
freebsd_source_ref=$freebsd_source_ref_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
materialized_source_root=$materialized_source_root
resolved_commit=$resolved_commit
store_layout_file=$store_layout_file
source_driven_native_build=ok
EOF
if [ -n "$metadata_target" ]; then
mkdir -p "$(dirname "$metadata_target")"
cp "$metadata_file" "$metadata_target"
fi
printf 'PASS phase16-source-driven-native-build\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"

View File

@@ -0,0 +1,224 @@
#!/bin/sh
set -eu
project_root=${PROJECT_ROOT:-$(pwd)}
script_dir=$(CDPATH= cd -- "$(dirname "$0")" && pwd)
fruix_cmd=$project_root/bin/fruix
git_template=${GIT_SOURCE_TEMPLATE:-$script_dir/phase16-git-freebsd-source.scm.in}
txz_template=${TXZ_SOURCE_TEMPLATE:-$script_dir/phase16-txz-freebsd-source.scm.in}
store_dir=${STORE_DIR:-/frx/store}
cache_dir=${CACHE_DIR:-/frx/var/cache/fruix/freebsd-source}
git_source_name=${GIT_SOURCE_NAME:-stable15-git-ref}
git_source_ref=${GIT_SOURCE_REF:-stable/15}
txz_source_name=${TXZ_SOURCE_NAME:-release15-src-txz}
txz_source_url=${TXZ_SOURCE_URL:-https://download.freebsd.org/releases/amd64/15.0-RELEASE/src.txz}
txz_source_sha256=${TXZ_SOURCE_SHA256:-83c3e8157b6d7afcae57167fda75693bf1e5f581ca149a6ecb2d398b71bdfab0}
metadata_target=${METADATA_OUT:-}
[ -x "$fruix_cmd" ] || {
echo "fruix command is not executable: $fruix_cmd" >&2
exit 1
}
[ -f "$git_template" ] || {
echo "missing git source template: $git_template" >&2
exit 1
}
[ -f "$txz_template" ] || {
echo "missing txz source template: $txz_template" >&2
exit 1
}
cleanup=0
if [ -n "${WORKDIR:-}" ]; then
workdir=$WORKDIR
mkdir -p "$workdir"
else
workdir=$(mktemp -d /tmp/fruix-phase16-source-materialization.XXXXXX)
cleanup=1
fi
if [ "${KEEP_WORKDIR:-0}" -eq 1 ]; then
cleanup=0
fi
cleanup_workdir() {
if [ "$cleanup" -eq 1 ]; then
rm -rf "$workdir" 2>/dev/null || sudo rm -rf "$workdir"
fi
}
trap cleanup_workdir EXIT INT TERM
git_source_file=$workdir/git-source.scm
txz_source_file=$workdir/txz-source.scm
git_out_first=$workdir/git-first.txt
git_out_second=$workdir/git-second.txt
txz_out_first=$workdir/txz-first.txt
txz_out_second=$workdir/txz-second.txt
metadata_file=$workdir/phase16-source-materialization-metadata.txt
sed \
-e "s|__SOURCE_NAME__|$git_source_name|g" \
-e "s|__SOURCE_REF__|$git_source_ref|g" \
"$git_template" > "$git_source_file"
sed \
-e "s|__SOURCE_NAME__|$txz_source_name|g" \
-e "s|__SOURCE_URL__|$txz_source_url|g" \
-e "s|__SOURCE_SHA256__|$txz_source_sha256|g" \
"$txz_template" > "$txz_source_file"
action_env() {
sudo env \
HOME="$HOME" \
GUILE_AUTO_COMPILE=0 \
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}" \
"$@"
}
extract_field() {
key=$1
file=$2
sed -n "s/^${key}=//p" "$file" | tail -n 1
}
assert_hex40() {
value=$1
label=$2
printf '%s\n' "$value" | grep -E '^[0-9a-f]{40}$' >/dev/null || {
echo "expected 40-hex $label, got: $value" >&2
exit 1
}
}
assert_hex64() {
value=$1
label=$2
printf '%s\n' "$value" | grep -E '^[0-9a-f]{64}$' >/dev/null || {
echo "expected 64-hex $label, got: $value" >&2
exit 1
}
}
assert_file() {
path=$1
[ -e "$path" ] || {
echo "missing expected path: $path" >&2
exit 1
}
}
action_env "$fruix_cmd" source materialize "$git_source_file" --source phase16-source --store "$store_dir" --cache "$cache_dir" > "$git_out_first"
action_env "$fruix_cmd" source materialize "$git_source_file" --source phase16-source --store "$store_dir" --cache "$cache_dir" > "$git_out_second"
action_env "$fruix_cmd" source materialize "$txz_source_file" --source phase16-source --store "$store_dir" --cache "$cache_dir" > "$txz_out_first"
action_env "$fruix_cmd" source materialize "$txz_source_file" --source phase16-source --store "$store_dir" --cache "$cache_dir" > "$txz_out_second"
git_store_first=$(extract_field materialized_source_store "$git_out_first")
git_store_second=$(extract_field materialized_source_store "$git_out_second")
git_root_first=$(extract_field materialized_source_root "$git_out_first")
git_info_first=$(extract_field materialized_source_info_file "$git_out_first")
git_tree_sha_first=$(extract_field materialized_source_tree_sha256 "$git_out_first")
git_cache_first=$(extract_field materialized_source_cache_path "$git_out_first")
git_kind_first=$(extract_field materialized_source_kind "$git_out_first")
git_url_first=$(extract_field materialized_source_url "$git_out_first")
git_ref_first=$(extract_field materialized_source_ref "$git_out_first")
git_commit_first=$(extract_field materialized_source_commit "$git_out_first")
txz_store_first=$(extract_field materialized_source_store "$txz_out_first")
txz_store_second=$(extract_field materialized_source_store "$txz_out_second")
txz_root_first=$(extract_field materialized_source_root "$txz_out_first")
txz_info_first=$(extract_field materialized_source_info_file "$txz_out_first")
txz_tree_sha_first=$(extract_field materialized_source_tree_sha256 "$txz_out_first")
txz_cache_first=$(extract_field materialized_source_cache_path "$txz_out_first")
txz_kind_first=$(extract_field materialized_source_kind "$txz_out_first")
txz_url_first=$(extract_field materialized_source_url "$txz_out_first")
txz_sha_first=$(extract_field materialized_source_sha256 "$txz_out_first")
[ "$git_store_first" = "$git_store_second" ] || {
echo "git materialization was not stable across two runs" >&2
exit 1
}
[ "$txz_store_first" = "$txz_store_second" ] || {
echo "txz materialization was not stable across two runs" >&2
exit 1
}
[ "$git_kind_first" = git ] || {
echo "unexpected git materialized kind: $git_kind_first" >&2
exit 1
}
[ "$git_url_first" = https://git.FreeBSD.org/src.git ] || {
echo "unexpected git materialized URL: $git_url_first" >&2
exit 1
}
[ "$git_ref_first" = "$git_source_ref" ] || {
echo "unexpected git materialized ref: $git_ref_first" >&2
exit 1
}
assert_hex40 "$git_commit_first" "git materialized commit"
assert_hex64 "$git_tree_sha_first" "git source tree sha256"
assert_file "$git_store_first"
assert_file "$git_root_first"
assert_file "$git_info_first"
assert_file "$git_cache_first"
assert_file "$git_root_first/Makefile"
assert_file "$git_root_first/sys/conf/newvers.sh"
[ "$txz_kind_first" = src-txz ] || {
echo "unexpected txz materialized kind: $txz_kind_first" >&2
exit 1
}
[ "$txz_url_first" = "$txz_source_url" ] || {
echo "unexpected txz materialized URL: $txz_url_first" >&2
exit 1
}
[ "$txz_sha_first" = "$txz_source_sha256" ] || {
echo "unexpected txz materialized sha256: $txz_sha_first" >&2
exit 1
}
assert_hex64 "$txz_tree_sha_first" "txz source tree sha256"
assert_file "$txz_store_first"
assert_file "$txz_root_first"
assert_file "$txz_info_first"
assert_file "$txz_cache_first"
assert_file "$txz_root_first/Makefile"
assert_file "$txz_root_first/sys/conf/newvers.sh"
cat > "$metadata_file" <<EOF
workdir=$workdir
git_source_file=$git_source_file
git_store_path=$git_store_first
git_source_root=$git_root_first
git_source_info_file=$git_info_first
git_source_tree_sha256=$git_tree_sha_first
git_cache_path=$git_cache_first
git_url=$git_url_first
git_ref=$git_ref_first
git_resolved_commit=$git_commit_first
txz_source_file=$txz_source_file
txz_store_path=$txz_store_first
txz_source_root=$txz_root_first
txz_source_info_file=$txz_info_first
txz_source_tree_sha256=$txz_tree_sha_first
txz_cache_path=$txz_cache_first
txz_url=$txz_url_first
txz_sha256=$txz_sha_first
source_materialization=ok
EOF
if [ -n "$metadata_target" ]; then
mkdir -p "$(dirname "$metadata_target")"
cp "$metadata_file" "$metadata_target"
fi
printf 'PASS phase16-source-materialization\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' '--- git materialization ---'
cat "$git_out_first"
printf '%s\n' '--- txz materialization ---'
cat "$txz_out_first"
printf '%s\n' '--- metadata ---'
cat "$metadata_file"

View File

@@ -0,0 +1,379 @@
#!/bin/sh
set -eu
project_root=${PROJECT_ROOT:-$(pwd)}
script_dir=$(CDPATH= cd -- "$(dirname "$0")" && pwd)
fruix_cmd=$project_root/bin/fruix
git_template=${GIT_TEMPLATE:-$script_dir/phase17-git-source-operating-system.scm.in}
txz_template=${TXZ_TEMPLATE:-$script_dir/phase17-txz-source-operating-system.scm.in}
system_name=${SYSTEM_NAME:-phase17-operating-system}
store_dir=${STORE_DIR:-/frx/store}
base_name=${BASE_NAME:-source-side-by-side}
base_version_label=${BASE_VERSION_LABEL:-15.0-source-side-by-side}
git_base_release=${GIT_BASE_RELEASE:-15.0-STABLE}
git_base_branch=${GIT_BASE_BRANCH:-stable/15}
git_source_name=${GIT_SOURCE_NAME:-stable15-side-a}
git_source_ref=${GIT_SOURCE_REF:-stable/15}
git_source_commit=${GIT_SOURCE_COMMIT:-332708a606f6bf0841c1d4a74c0d067f5640fe89}
git_declared_source_root=${GIT_DECLARED_SOURCE_ROOT:-/var/empty/fruix-unused-source-root-git}
txz_base_release=${TXZ_BASE_RELEASE:-15.0-RELEASE}
txz_base_branch=${TXZ_BASE_BRANCH:-releng/15.0}
txz_source_name=${TXZ_SOURCE_NAME:-release15-side-b}
txz_source_url=${TXZ_SOURCE_URL:-https://download.freebsd.org/releases/amd64/15.0-RELEASE/src.txz}
txz_source_sha256=${TXZ_SOURCE_SHA256:-83c3e8157b6d7afcae57167fda75693bf1e5f581ca149a6ecb2d398b71bdfab0}
txz_declared_source_root=${TXZ_DECLARED_SOURCE_ROOT:-/var/empty/fruix-unused-source-root-txz}
metadata_target=${METADATA_OUT:-}
root_authorized_key_file=${ROOT_AUTHORIZED_KEY_FILE:-$HOME/.ssh/id_ed25519.pub}
[ -x "$fruix_cmd" ] || {
echo "fruix command is not executable: $fruix_cmd" >&2
exit 1
}
[ -f "$git_template" ] || {
echo "missing git operating-system template: $git_template" >&2
exit 1
}
[ -f "$txz_template" ] || {
echo "missing txz operating-system template: $txz_template" >&2
exit 1
}
[ -f "$root_authorized_key_file" ] || {
echo "missing root authorized key file: $root_authorized_key_file" >&2
exit 1
}
cleanup=0
if [ -n "${WORKDIR:-}" ]; then
workdir=$WORKDIR
mkdir -p "$workdir"
else
workdir=$(mktemp -d /tmp/fruix-phase17-source-coexistence.XXXXXX)
cleanup=1
fi
if [ "${KEEP_WORKDIR:-0}" -eq 1 ]; then
cleanup=0
fi
cleanup_workdir() {
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")
git_os=$workdir/git-operating-system.scm
txz_os=$workdir/txz-operating-system.scm
git_out_a=$workdir/git-build-a.txt
git_out_b=$workdir/git-build-b.txt
txz_out=$workdir/txz-build.txt
metadata_file=$workdir/phase17-source-coexistence-metadata.txt
sed \
-e "s|__BASE_NAME__|$base_name|g" \
-e "s|__BASE_VERSION_LABEL__|$base_version_label|g" \
-e "s|__BASE_RELEASE__|$git_base_release|g" \
-e "s|__BASE_BRANCH__|$git_base_branch|g" \
-e "s|__SOURCE_NAME__|$git_source_name|g" \
-e "s|__SOURCE_REF__|$git_source_ref|g" \
-e "s|__SOURCE_COMMIT__|$git_source_commit|g" \
-e "s|__DECLARED_SOURCE_ROOT__|$git_declared_source_root|g" \
-e "s|__ROOT_AUTHORIZED_KEY__|$root_authorized_key|g" \
"$git_template" > "$git_os"
sed \
-e "s|__BASE_NAME__|$base_name|g" \
-e "s|__BASE_VERSION_LABEL__|$base_version_label|g" \
-e "s|__BASE_RELEASE__|$txz_base_release|g" \
-e "s|__BASE_BRANCH__|$txz_base_branch|g" \
-e "s|__SOURCE_NAME__|$txz_source_name|g" \
-e "s|__SOURCE_URL__|$txz_source_url|g" \
-e "s|__SOURCE_SHA256__|$txz_source_sha256|g" \
-e "s|__DECLARED_SOURCE_ROOT__|$txz_declared_source_root|g" \
-e "s|__ROOT_AUTHORIZED_KEY__|$root_authorized_key|g" \
"$txz_template" > "$txz_os"
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}" \
"$@"
}
build_os() {
os_file=$1
out_file=$2
action_env "$fruix_cmd" system build "$os_file" --system "$system_name" --store "$store_dir" >"$out_file"
}
field() {
key=$1
file=$2
sed -n "s/^$key=//p" "$file" | tail -n 1
}
resolved_git_commit() {
file=$1
grep -Eo '\(commit \. "[0-9a-f]{40}"\)' "$file" | head -n 1 | sed 's/.*"\([0-9a-f]\{40\}\)".*/\1/'
}
build_os "$git_os" "$git_out_a"
build_os "$txz_os" "$txz_out"
build_os "$git_os" "$git_out_b"
git_closure=$(field closure_path "$git_out_a")
git_closure_rebuild=$(field closure_path "$git_out_b")
txz_closure=$(field closure_path "$txz_out")
git_kernel_store=$(field kernel_store "$git_out_a")
txz_kernel_store=$(field kernel_store "$txz_out")
git_native_stores=$(field native_base_stores "$git_out_a")
txz_native_stores=$(field native_base_stores "$txz_out")
git_host_count=$(field host_base_store_count "$git_out_a")
txz_host_count=$(field host_base_store_count "$txz_out")
git_materialized_count=$(field materialized_source_store_count "$git_out_a")
txz_materialized_count=$(field materialized_source_store_count "$txz_out")
git_materialized_store=$(field materialized_source_stores "$git_out_a")
txz_materialized_store=$(field materialized_source_stores "$txz_out")
git_source_kind=$(field freebsd_source_kind "$git_out_a")
txz_source_kind=$(field freebsd_source_kind "$txz_out")
git_source_ref_out=$(field freebsd_source_ref "$git_out_a")
git_source_commit_out=$(field freebsd_source_commit "$git_out_a")
txz_source_url_out=$(field freebsd_source_url "$txz_out")
txz_source_sha256_out=$(field freebsd_source_sha256 "$txz_out")
git_source_file=$(field freebsd_source_file "$git_out_a")
txz_source_file=$(field freebsd_source_file "$txz_out")
git_source_materializations_file=$(field freebsd_source_materializations_file "$git_out_a")
txz_source_materializations_file=$(field freebsd_source_materializations_file "$txz_out")
git_store_layout_file=$(field store_layout_file "$git_out_a")
txz_store_layout_file=$(field store_layout_file "$txz_out")
git_base_name_out=$(field freebsd_base_name "$git_out_a")
txz_base_name_out=$(field freebsd_base_name "$txz_out")
git_base_version_out=$(field freebsd_base_version_label "$git_out_a")
txz_base_version_out=$(field freebsd_base_version_label "$txz_out")
git_base_release_out=$(field freebsd_base_release "$git_out_a")
txz_base_release_out=$(field freebsd_base_release "$txz_out")
git_base_source_root_out=$(field freebsd_base_source_root "$git_out_a")
txz_base_source_root_out=$(field freebsd_base_source_root "$txz_out")
[ -n "$git_closure" ] || { echo "missing git closure" >&2; exit 1; }
[ -n "$txz_closure" ] || { echo "missing txz closure" >&2; exit 1; }
[ "$git_closure" = "$git_closure_rebuild" ] || {
echo "git closure path was not reproducible across rebuilds: $git_closure != $git_closure_rebuild" >&2
exit 1
}
[ "$git_closure" != "$txz_closure" ] || {
echo "git and txz closures unexpectedly match" >&2
exit 1
}
[ "$git_host_count" = 0 ] || { echo "git build has host base stores" >&2; exit 1; }
[ "$txz_host_count" = 0 ] || { echo "txz build has host base stores" >&2; exit 1; }
[ "$git_materialized_count" = 1 ] || { echo "expected one git materialized source store" >&2; exit 1; }
[ "$txz_materialized_count" = 1 ] || { echo "expected one txz materialized source store" >&2; exit 1; }
[ "$git_source_kind" = git ] || { echo "unexpected git source kind: $git_source_kind" >&2; exit 1; }
[ "$txz_source_kind" = src-txz ] || { echo "unexpected txz source kind: $txz_source_kind" >&2; exit 1; }
[ "$git_source_ref_out" = "$git_source_ref" ] || { echo "unexpected git source ref: $git_source_ref_out" >&2; exit 1; }
[ "$git_source_commit_out" = "$git_source_commit" ] || { echo "unexpected git source commit: $git_source_commit_out" >&2; exit 1; }
[ "$txz_source_url_out" = "$txz_source_url" ] || { echo "unexpected txz source URL: $txz_source_url_out" >&2; exit 1; }
[ "$txz_source_sha256_out" = "$txz_source_sha256" ] || { echo "unexpected txz source sha256: $txz_source_sha256_out" >&2; exit 1; }
[ "$git_base_name_out" = "$base_name" ] || { echo "unexpected git base name: $git_base_name_out" >&2; exit 1; }
[ "$txz_base_name_out" = "$base_name" ] || { echo "unexpected txz base name: $txz_base_name_out" >&2; exit 1; }
[ "$git_base_version_out" = "$base_version_label" ] || { echo "unexpected git base version label: $git_base_version_out" >&2; exit 1; }
[ "$txz_base_version_out" = "$base_version_label" ] || { echo "unexpected txz base version label: $txz_base_version_out" >&2; exit 1; }
[ "$git_base_release_out" = "$git_base_release" ] || { echo "unexpected git base release: $git_base_release_out" >&2; exit 1; }
[ "$txz_base_release_out" = "$txz_base_release" ] || { echo "unexpected txz base release: $txz_base_release_out" >&2; exit 1; }
[ "$git_base_source_root_out" = "$git_declared_source_root" ] || { echo "unexpected git declared source root: $git_base_source_root_out" >&2; exit 1; }
[ "$txz_base_source_root_out" = "$txz_declared_source_root" ] || { echo "unexpected txz declared source root: $txz_base_source_root_out" >&2; exit 1; }
[ "$git_materialized_store" != "$txz_materialized_store" ] || {
echo "git and txz materialized source stores unexpectedly match" >&2
exit 1
}
[ "$git_native_stores" != "$txz_native_stores" ] || {
echo "git and txz native store sets unexpectedly match" >&2
exit 1
}
[ "$git_kernel_store" != "$txz_kernel_store" ] || {
echo "git and txz kernel stores unexpectedly match" >&2
exit 1
}
case "$git_kernel_store" in
/frx/store/*-freebsd-native-kernel-$base_version_label) : ;;
*) echo "unexpected git kernel store path: $git_kernel_store" >&2; exit 1 ;;
esac
case "$txz_kernel_store" in
/frx/store/*-freebsd-native-kernel-$base_version_label) : ;;
*) echo "unexpected txz kernel store path: $txz_kernel_store" >&2; exit 1 ;;
esac
case "$git_materialized_store" in
/frx/store/*-freebsd-source-$git_source_name) : ;;
*) echo "unexpected git materialized source store path: $git_materialized_store" >&2; exit 1 ;;
esac
case "$txz_materialized_store" in
/frx/store/*-freebsd-source-$txz_source_name) : ;;
*) echo "unexpected txz materialized source store path: $txz_materialized_store" >&2; exit 1 ;;
esac
[ -f "$git_source_file" ] || { echo "missing git source file: $git_source_file" >&2; exit 1; }
[ -f "$txz_source_file" ] || { echo "missing txz source file: $txz_source_file" >&2; exit 1; }
[ -f "$git_source_materializations_file" ] || { echo "missing git source materializations file: $git_source_materializations_file" >&2; exit 1; }
[ -f "$txz_source_materializations_file" ] || { echo "missing txz source materializations file: $txz_source_materializations_file" >&2; exit 1; }
[ -f "$git_store_layout_file" ] || { echo "missing git store layout file: $git_store_layout_file" >&2; exit 1; }
[ -f "$txz_store_layout_file" ] || { echo "missing txz store layout file: $txz_store_layout_file" >&2; exit 1; }
git_materialized_root=$git_materialized_store/tree
txz_materialized_root=$txz_materialized_store/tree/usr/src
[ -f "$git_materialized_root/Makefile" ] || { echo "git materialized source root missing Makefile" >&2; exit 1; }
[ -f "$txz_materialized_root/Makefile" ] || { echo "txz materialized source root missing Makefile" >&2; exit 1; }
git_commit=$(resolved_git_commit "$git_source_materializations_file")
printf '%s\n' "$git_commit" | grep -E '^[0-9a-f]{40}$' >/dev/null || {
echo "failed to recover resolved git commit" >&2
exit 1
}
grep -F "(source-root . \"$git_materialized_root\")" "$git_source_materializations_file" >/dev/null || {
echo "git source materializations file missing effective root" >&2
exit 1
}
[ "$git_commit" = "$git_source_commit" ] || {
echo "resolved git commit does not match the declared pinned commit: $git_commit != $git_source_commit" >&2
exit 1
}
grep -F "(commit . \"$git_commit\")" "$git_source_materializations_file" >/dev/null || {
echo "git source materializations file missing resolved commit" >&2
exit 1
}
grep -F "(sha256 . \"$txz_source_sha256\")" "$txz_source_materializations_file" >/dev/null || {
echo "txz source materializations file missing verified archive hash" >&2
exit 1
}
grep -F "(source-root . \"$txz_materialized_root\")" "$txz_source_materializations_file" >/dev/null || {
echo "txz source materializations file missing effective root" >&2
exit 1
}
grep -F "(materialized-source-store-count . 1)" "$git_store_layout_file" >/dev/null || {
echo "git store layout missing materialized source count" >&2
exit 1
}
grep -F "(materialized-source-store-count . 1)" "$txz_store_layout_file" >/dev/null || {
echo "txz store layout missing materialized source count" >&2
exit 1
}
grep -F "$git_materialized_store" "$git_store_layout_file" >/dev/null || {
echo "git store layout missing materialized source store path" >&2
exit 1
}
grep -F "$txz_materialized_store" "$txz_store_layout_file" >/dev/null || {
echo "txz store layout missing materialized source store path" >&2
exit 1
}
git_runtime_store=$(printf '%s\n' "$git_native_stores" | tr ',' '\n' | grep "freebsd-native-runtime-$base_version_label$" | head -n 1)
txz_runtime_store=$(printf '%s\n' "$txz_native_stores" | tr ',' '\n' | grep "freebsd-native-runtime-$base_version_label$" | head -n 1)
[ -n "$git_runtime_store" ] || { echo "failed to recover git runtime store" >&2; exit 1; }
[ -n "$txz_runtime_store" ] || { echo "failed to recover txz runtime store" >&2; exit 1; }
for path in "$git_kernel_store/.freebsd-native-build-info.scm" "$git_runtime_store/.freebsd-native-build-info.scm"; do
[ -f "$path" ] || { echo "missing git native build info file: $path" >&2; exit 1; }
grep -F "(materialized-source" "$path" >/dev/null || {
echo "git native build info missing materialized-source block in $path" >&2
exit 1
}
grep -F "(ref . \"$git_source_ref\")" "$path" >/dev/null || {
echo "git native build info missing source ref in $path" >&2
exit 1
}
grep -F "(commit . \"$git_commit\")" "$path" >/dev/null || {
echo "git native build info missing resolved commit in $path" >&2
exit 1
}
grep -F "(source-root . \"$git_materialized_root\")" "$path" >/dev/null || {
echo "git native build info missing materialized source root in $path" >&2
exit 1
}
grep -F "(store-path . \"$git_materialized_store\")" "$path" >/dev/null || {
echo "git native build info missing materialized source store path in $path" >&2
exit 1
}
if grep -F "(source-root . \"$git_declared_source_root\")" "$path" >/dev/null; then
echo "git native build info still records the unused declared source root in $path" >&2
exit 1
fi
done
for path in "$txz_kernel_store/.freebsd-native-build-info.scm" "$txz_runtime_store/.freebsd-native-build-info.scm"; do
[ -f "$path" ] || { echo "missing txz native build info file: $path" >&2; exit 1; }
grep -F "(materialized-source" "$path" >/dev/null || {
echo "txz native build info missing materialized-source block in $path" >&2
exit 1
}
grep -F "(sha256 . \"$txz_source_sha256\")" "$path" >/dev/null || {
echo "txz native build info missing archive sha256 in $path" >&2
exit 1
}
grep -F "(source-root . \"$txz_materialized_root\")" "$path" >/dev/null || {
echo "txz native build info missing materialized source root in $path" >&2
exit 1
}
grep -F "(store-path . \"$txz_materialized_store\")" "$path" >/dev/null || {
echo "txz native build info missing materialized source store path in $path" >&2
exit 1
}
if grep -F "(source-root . \"$txz_declared_source_root\")" "$path" >/dev/null; then
echo "txz native build info still records the unused declared source root in $path" >&2
exit 1
fi
done
git_closure_base=$(basename "$git_closure")
txz_closure_base=$(basename "$txz_closure")
cat >"$metadata_file" <<EOF
workdir=$workdir
git_os=$git_os
txz_os=$txz_os
git_closure=$git_closure
git_closure_rebuild=$git_closure_rebuild
git_closure_base=$git_closure_base
txz_closure=$txz_closure
txz_closure_base=$txz_closure_base
base_name=$base_name
base_version_label=$base_version_label
git_base_release=$git_base_release_out
txz_base_release=$txz_base_release_out
git_source_kind=$git_source_kind
git_source_ref=$git_source_ref_out
git_source_commit=$git_source_commit_out
git_source_materializations_file=$git_source_materializations_file
git_materialized_source_store=$git_materialized_store
git_materialized_source_root=$git_materialized_root
git_resolved_commit=$git_commit
git_native_base_stores=$git_native_stores
txz_source_kind=$txz_source_kind
txz_source_url=$txz_source_url_out
txz_source_sha256=$txz_source_sha256_out
txz_source_materializations_file=$txz_source_materializations_file
txz_materialized_source_store=$txz_materialized_store
txz_materialized_source_root=$txz_materialized_root
txz_native_base_stores=$txz_native_stores
same_base_version_label_distinct_sources=ok
side_by_side_source_revisions=ok
EOF
if [ -n "$metadata_target" ]; then
mkdir -p "$(dirname "$metadata_target")"
cp "$metadata_file" "$metadata_target"
fi
printf 'PASS phase17-source-coexistence\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"

View File

@@ -0,0 +1,194 @@
#!/bin/sh
set -eu
repo_root=${PROJECT_ROOT:-$(pwd)}
script_dir=$(CDPATH= cd -- "$(dirname "$0")" && pwd)
git_template=${GIT_TEMPLATE:-$script_dir/phase17-git-source-operating-system.scm.in}
txz_template=${TXZ_TEMPLATE:-$script_dir/phase17-txz-source-operating-system.scm.in}
system_name=${SYSTEM_NAME:-phase17-operating-system}
disk_capacity=${DISK_CAPACITY:-12g}
root_size=${ROOT_SIZE:-10g}
base_name=${BASE_NAME:-source-side-by-side}
base_version_label=${BASE_VERSION_LABEL:-15.0-source-side-by-side}
git_base_release=${GIT_BASE_RELEASE:-15.0-STABLE}
git_base_branch=${GIT_BASE_BRANCH:-stable/15}
git_source_name=${GIT_SOURCE_NAME:-stable15-side-a}
git_source_ref=${GIT_SOURCE_REF:-stable/15}
git_source_commit=${GIT_SOURCE_COMMIT:-332708a606f6bf0841c1d4a74c0d067f5640fe89}
git_declared_source_root=${GIT_DECLARED_SOURCE_ROOT:-/var/empty/fruix-unused-source-root-git}
txz_base_release=${TXZ_BASE_RELEASE:-15.0-RELEASE}
txz_base_branch=${TXZ_BASE_BRANCH:-releng/15.0}
txz_source_name=${TXZ_SOURCE_NAME:-release15-side-b}
txz_source_url=${TXZ_SOURCE_URL:-https://download.freebsd.org/releases/amd64/15.0-RELEASE/src.txz}
txz_source_sha256=${TXZ_SOURCE_SHA256:-83c3e8157b6d7afcae57167fda75693bf1e5f581ca149a6ecb2d398b71bdfab0}
txz_declared_source_root=${TXZ_DECLARED_SOURCE_ROOT:-/var/empty/fruix-unused-source-root-txz}
metadata_target=${METADATA_OUT:-}
cleanup=0
if [ -n "${WORKDIR:-}" ]; then
workdir=$WORKDIR
mkdir -p "$workdir"
else
workdir=$(mktemp -d /tmp/fruix-phase17-source-qemu.XXXXXX)
cleanup=1
fi
if [ "${KEEP_WORKDIR:-0}" -eq 1 ]; then
cleanup=0
fi
metadata_file=$workdir/phase17-source-revisions-qemu-metadata.txt
git_rendered_template=$workdir/git-template.scm.in
txz_rendered_template=$workdir/txz-template.scm.in
tidy() {
if [ "$cleanup" -eq 1 ]; then
rm -rf "$workdir" 2>/dev/null || sudo rm -rf "$workdir"
fi
}
trap tidy EXIT INT TERM
[ -f "$git_template" ] || { echo "missing git template: $git_template" >&2; exit 1; }
[ -f "$txz_template" ] || { echo "missing txz template: $txz_template" >&2; exit 1; }
sed \
-e "s|__BASE_NAME__|$base_name|g" \
-e "s|__BASE_VERSION_LABEL__|$base_version_label|g" \
-e "s|__BASE_RELEASE__|$git_base_release|g" \
-e "s|__BASE_BRANCH__|$git_base_branch|g" \
-e "s|__SOURCE_NAME__|$git_source_name|g" \
-e "s|__SOURCE_REF__|$git_source_ref|g" \
-e "s|__SOURCE_COMMIT__|$git_source_commit|g" \
-e "s|__DECLARED_SOURCE_ROOT__|$git_declared_source_root|g" \
"$git_template" > "$git_rendered_template"
sed \
-e "s|__BASE_NAME__|$base_name|g" \
-e "s|__BASE_VERSION_LABEL__|$base_version_label|g" \
-e "s|__BASE_RELEASE__|$txz_base_release|g" \
-e "s|__BASE_BRANCH__|$txz_base_branch|g" \
-e "s|__SOURCE_NAME__|$txz_source_name|g" \
-e "s|__SOURCE_URL__|$txz_source_url|g" \
-e "s|__SOURCE_SHA256__|$txz_source_sha256|g" \
-e "s|__DECLARED_SOURCE_ROOT__|$txz_declared_source_root|g" \
"$txz_template" > "$txz_rendered_template"
run_boot() {
name=$1
template=$2
metadata_out=$3
qemu_port=$4
KEEP_WORKDIR=1 WORKDIR="$workdir/$name" METADATA_OUT="$metadata_out" \
OS_TEMPLATE="$template" SYSTEM_NAME="$system_name" DISK_CAPACITY="$disk_capacity" ROOT_SIZE="$root_size" \
QEMU_SSH_PORT="$qemu_port" "$repo_root/tests/system/run-phase11-shepherd-pid1-qemu.sh" >/dev/null
}
git_metadata=$workdir/git-metadata.txt
txz_metadata=$workdir/txz-metadata.txt
run_boot git "$git_rendered_template" "$git_metadata" 10022
run_boot txz "$txz_rendered_template" "$txz_metadata" 10023
field() {
key=$1
file=$2
sed -n "s/^$key=//p" "$file" | tail -n 1
}
git_phase8=$(field phase8_metadata "$git_metadata")
txz_phase8=$(field phase8_metadata "$txz_metadata")
[ -f "$git_phase8" ] || { echo "missing git phase8 metadata: $git_phase8" >&2; exit 1; }
[ -f "$txz_phase8" ] || { echo "missing txz phase8 metadata: $txz_phase8" >&2; exit 1; }
git_phase8_build=$(field build_metadata "$git_phase8")
txz_phase8_build=$(field build_metadata "$txz_phase8")
[ -f "$git_phase8_build" ] || { echo "missing git phase8 build metadata: $git_phase8_build" >&2; exit 1; }
[ -f "$txz_phase8_build" ] || { echo "missing txz phase8 build metadata: $txz_phase8_build" >&2; exit 1; }
git_closure=$(field closure_path "$git_metadata")
txz_closure=$(field closure_path "$txz_metadata")
git_shepherd_pid=$(field shepherd_pid "$git_metadata")
txz_shepherd_pid=$(field shepherd_pid "$txz_metadata")
git_sshd=$(field sshd_status "$git_metadata")
txz_sshd=$(field sshd_status "$txz_metadata")
git_serial_log=$(field serial_log "$git_metadata")
txz_serial_log=$(field serial_log "$txz_metadata")
git_source_kind=$(field freebsd_source_kind "$git_phase8_build")
txz_source_kind=$(field freebsd_source_kind "$txz_phase8_build")
git_source_ref_out=$(field freebsd_source_ref "$git_phase8_build")
git_source_commit_out=$(field freebsd_source_commit "$git_phase8_build")
txz_source_url_out=$(field freebsd_source_url "$txz_phase8_build")
txz_source_sha256_out=$(field freebsd_source_sha256 "$txz_phase8_build")
git_materialized_count=$(field materialized_source_store_count "$git_phase8_build")
txz_materialized_count=$(field materialized_source_store_count "$txz_phase8_build")
git_materialized_store=$(field materialized_source_stores "$git_phase8_build")
txz_materialized_store=$(field materialized_source_stores "$txz_phase8_build")
git_native_stores=$(field native_base_stores "$git_phase8_build")
txz_native_stores=$(field native_base_stores "$txz_phase8_build")
[ "$git_shepherd_pid" = 1 ] || { echo "git boot did not run Shepherd as PID 1" >&2; exit 1; }
[ "$txz_shepherd_pid" = 1 ] || { echo "txz boot did not run Shepherd as PID 1" >&2; exit 1; }
[ "$git_sshd" = running ] || { echo "git boot does not have sshd running" >&2; exit 1; }
[ "$txz_sshd" = running ] || { echo "txz boot does not have sshd running" >&2; exit 1; }
[ "$git_closure" != "$txz_closure" ] || { echo "git and txz boot closures unexpectedly match" >&2; exit 1; }
[ "$git_source_kind" = git ] || { echo "unexpected git boot source kind: $git_source_kind" >&2; exit 1; }
[ "$txz_source_kind" = src-txz ] || { echo "unexpected txz boot source kind: $txz_source_kind" >&2; exit 1; }
[ "$git_source_ref_out" = "$git_source_ref" ] || { echo "unexpected git boot source ref: $git_source_ref_out" >&2; exit 1; }
[ "$git_source_commit_out" = "$git_source_commit" ] || { echo "unexpected git boot source commit: $git_source_commit_out" >&2; exit 1; }
[ "$txz_source_url_out" = "$txz_source_url" ] || { echo "unexpected txz boot source URL: $txz_source_url_out" >&2; exit 1; }
[ "$txz_source_sha256_out" = "$txz_source_sha256" ] || { echo "unexpected txz boot source sha256: $txz_source_sha256_out" >&2; exit 1; }
[ "$git_materialized_count" = 1 ] || { echo "unexpected git materialized source count: $git_materialized_count" >&2; exit 1; }
[ "$txz_materialized_count" = 1 ] || { echo "unexpected txz materialized source count: $txz_materialized_count" >&2; exit 1; }
[ "$git_materialized_store" != "$txz_materialized_store" ] || { echo "git and txz materialized source stores unexpectedly match" >&2; exit 1; }
[ "$git_native_stores" != "$txz_native_stores" ] || { echo "git and txz native base stores unexpectedly match" >&2; exit 1; }
case "$git_materialized_store" in
/frx/store/*-freebsd-source-$git_source_name) : ;;
*) echo "unexpected git materialized source store: $git_materialized_store" >&2; exit 1 ;;
esac
case "$txz_materialized_store" in
/frx/store/*-freebsd-source-$txz_source_name) : ;;
*) echo "unexpected txz materialized source store: $txz_materialized_store" >&2; exit 1 ;;
esac
cat >"$metadata_file" <<EOF
workdir=$workdir
git_metadata=$git_metadata
txz_metadata=$txz_metadata
git_phase8_metadata=$git_phase8
txz_phase8_metadata=$txz_phase8
git_phase8_build_metadata=$git_phase8_build
txz_phase8_build_metadata=$txz_phase8_build
git_closure=$git_closure
txz_closure=$txz_closure
git_source_kind=$git_source_kind
git_source_ref=$git_source_ref_out
git_source_commit=$git_source_commit_out
git_materialized_source_store=$git_materialized_store
git_native_base_stores=$git_native_stores
git_serial_log=$git_serial_log
txz_source_kind=$txz_source_kind
txz_source_url=$txz_source_url_out
txz_source_sha256=$txz_source_sha256_out
txz_materialized_source_store=$txz_materialized_store
txz_native_base_stores=$txz_native_stores
txz_serial_log=$txz_serial_log
disk_capacity=$disk_capacity
root_size=$root_size
boot_backend=qemu-uefi-tcg
source_revision_boots=ok
EOF
if [ -n "$metadata_target" ]; then
mkdir -p "$(dirname "$metadata_target")"
cp "$metadata_file" "$metadata_target"
fi
printf 'PASS phase17-source-revisions-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"

View File

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