Compare commits
16 Commits
ebe064a652
...
master
| Author | SHA1 | Date | |
|---|---|---|---|
| 2a1a6c3b81 | |||
| cb9e7332f4 | |||
| db4d5bdf4c | |||
| 0e8b30434f | |||
| f41a916f45 | |||
| 006ffee615 | |||
| 4614592a25 | |||
| a3dd5556ae | |||
| 9e9a0b59fc | |||
| 4975084baa | |||
| 9dae4e5c84 | |||
| b3b1ba2489 | |||
| e86f74af97 | |||
| 43c155bb9f | |||
| 604ad82f4f | |||
| 1970c5c181 |
1
.gitignore
vendored
Normal file
1
.gitignore
vendored
Normal file
@@ -0,0 +1 @@
|
|||||||
|
/TODO.md
|
||||||
384
docs/GUIX_DIFFERENCES.md
Normal file
384
docs/GUIX_DIFFERENCES.md
Normal file
@@ -0,0 +1,384 @@
|
|||||||
|
# Fruix differences for Guix sysadmins
|
||||||
|
|
||||||
|
Date: 2026-04-06
|
||||||
|
|
||||||
|
This document is aimed at operators who already know Guix well and want a quick map of where Fruix behaves similarly and where it intentionally differs.
|
||||||
|
|
||||||
|
The short version is:
|
||||||
|
|
||||||
|
- Fruix is strongly **Guix-inspired**
|
||||||
|
- it tries to preserve the important semantic properties
|
||||||
|
- but it does **not** copy Guix mechanically where FreeBSD or Fruix-specific concerns make a different representation clearer
|
||||||
|
|
||||||
|
## Big picture
|
||||||
|
|
||||||
|
Fruix keeps the core Guix ideas you would expect:
|
||||||
|
|
||||||
|
- declarative inputs
|
||||||
|
- content-addressed store paths
|
||||||
|
- immutable build outputs
|
||||||
|
- rollback-friendly deployment identity
|
||||||
|
- provenance tied to the deployed closure rather than mutable in-place state
|
||||||
|
|
||||||
|
But Fruix differs in at least four major ways today:
|
||||||
|
|
||||||
|
1. it targets **FreeBSD**, not GNU/Linux
|
||||||
|
2. its system frontend is currently smaller:
|
||||||
|
- `fruix system build|rootfs|image|install|installer|installer-iso`
|
||||||
|
3. it treats **FreeBSD source provenance** as an explicit deployment concern
|
||||||
|
4. its installed-system generation model is still earlier-stage than Guix's mature system-generation workflow
|
||||||
|
|
||||||
|
## Similarities to Guix
|
||||||
|
|
||||||
|
If you know Guix System, these Fruix properties should feel familiar.
|
||||||
|
|
||||||
|
### Immutable deployment identity
|
||||||
|
|
||||||
|
A deployed Fruix system is identified primarily by its closure path in `/frx/store`, not by mutable files under `/etc` or `/usr/local`.
|
||||||
|
|
||||||
|
### `/run/current-system`
|
||||||
|
|
||||||
|
Fruix keeps the Guix-like runtime convention:
|
||||||
|
|
||||||
|
- `/run/current-system`
|
||||||
|
|
||||||
|
That path remains the active runtime boundary used by activation and service wiring.
|
||||||
|
|
||||||
|
### Rollback-friendly semantics
|
||||||
|
|
||||||
|
Fruix avoids in-place mutation of an older deployed closure.
|
||||||
|
|
||||||
|
The validated rollback story now has two layers:
|
||||||
|
|
||||||
|
- declaration-level rollback by rebuilding/redeploying an earlier declaration
|
||||||
|
- installed-system rollback between already-recorded generations on the target itself
|
||||||
|
|
||||||
|
That is Guix-like in spirit, although Fruix still exposes a smaller installed-system workflow than Guix's more mature `reconfigure` model.
|
||||||
|
|
||||||
|
### Generation-style metadata and roots
|
||||||
|
|
||||||
|
Fruix now records explicit installed-system generation state under:
|
||||||
|
|
||||||
|
- `/var/lib/fruix/system`
|
||||||
|
|
||||||
|
and explicit retention roots under:
|
||||||
|
|
||||||
|
- `/frx/var/fruix/gcroots`
|
||||||
|
|
||||||
|
This preserves the basic Guix idea that deployment state and reachability should be represented explicitly rather than inferred from whatever happens to be on disk.
|
||||||
|
|
||||||
|
## Important differences from Guix
|
||||||
|
|
||||||
|
## 1. Fruix does not mirror Guix's on-disk generation layout 1:1
|
||||||
|
|
||||||
|
This is intentional.
|
||||||
|
|
||||||
|
Guix heavily reuses its profile-generation model and represents a lot of meaning through symlink structure such as profile links and system generation links.
|
||||||
|
|
||||||
|
Fruix keeps the **semantics** but uses a more explicit metadata-oriented layout for installed systems.
|
||||||
|
|
||||||
|
Current Fruix layout starts as:
|
||||||
|
|
||||||
|
```text
|
||||||
|
/var/lib/fruix/system/
|
||||||
|
current -> generations/1
|
||||||
|
current-generation
|
||||||
|
generations/
|
||||||
|
1/
|
||||||
|
closure -> /frx/store/...-fruix-system-...
|
||||||
|
metadata.scm
|
||||||
|
provenance.scm
|
||||||
|
install.scm
|
||||||
|
```
|
||||||
|
|
||||||
|
After a validated installed-system switch, Fruix also records:
|
||||||
|
|
||||||
|
```text
|
||||||
|
/var/lib/fruix/system/
|
||||||
|
rollback -> generations/1
|
||||||
|
rollback-generation
|
||||||
|
generations/
|
||||||
|
2/
|
||||||
|
closure -> /frx/store/...-fruix-system-...
|
||||||
|
metadata.scm
|
||||||
|
provenance.scm
|
||||||
|
install.scm
|
||||||
|
```
|
||||||
|
|
||||||
|
Why Fruix does this:
|
||||||
|
|
||||||
|
- it makes deployment state easier to inspect directly
|
||||||
|
- it gives FreeBSD-specific install and provenance details a clearer home
|
||||||
|
- it keeps room for richer operator tooling later without losing the Guix properties
|
||||||
|
|
||||||
|
So the rule of thumb is:
|
||||||
|
|
||||||
|
- **same semantics as Guix where practical**
|
||||||
|
- **not necessarily the same directory names or symlink vocabulary**
|
||||||
|
|
||||||
|
## 2. Fruix currently keeps `/run/current-system` simple
|
||||||
|
|
||||||
|
Even though Fruix now records explicit generation metadata under `/var/lib/fruix/system`, it still keeps:
|
||||||
|
|
||||||
|
- `/run/current-system -> /frx/store/...`
|
||||||
|
|
||||||
|
rather than making `/run/current-system` point through a generation directory first.
|
||||||
|
|
||||||
|
This was chosen to preserve compatibility with the already-validated activation and runtime model while adding explicit generation metadata separately.
|
||||||
|
|
||||||
|
## 3. Fruix treats source provenance more explicitly
|
||||||
|
|
||||||
|
Guix sysadmins are used to derivation/store provenance. Fruix adds an extra emphasis because its current FreeBSD story depends on explicit source selection and materialization.
|
||||||
|
|
||||||
|
Fruix routinely records:
|
||||||
|
|
||||||
|
- declared FreeBSD source object metadata
|
||||||
|
- materialized source store paths
|
||||||
|
- source materialization metadata files
|
||||||
|
- closure-level store layout metadata
|
||||||
|
- install metadata on the target system
|
||||||
|
|
||||||
|
This is more prominent in Fruix than most Guix system docs because FreeBSD base/source identity has been an active design concern for this project.
|
||||||
|
|
||||||
|
## 4. Fruix has installer-media workflows as first-class system actions
|
||||||
|
|
||||||
|
Guix has installation media and image workflows, but Fruix's current system frontend makes these especially explicit because they are still part of the active architectural bring-up story.
|
||||||
|
|
||||||
|
Current Fruix actions include:
|
||||||
|
|
||||||
|
- `fruix system install`
|
||||||
|
- `fruix system installer`
|
||||||
|
- `fruix system installer-iso`
|
||||||
|
|
||||||
|
That is a little more deployment-medium-oriented than the mental model many Guix users start with.
|
||||||
|
|
||||||
|
## 5. Device naming is more environment-sensitive than Guix users may expect
|
||||||
|
|
||||||
|
Because Fruix is on FreeBSD, the install target device naming is not the same as on Linux.
|
||||||
|
|
||||||
|
Validated examples:
|
||||||
|
|
||||||
|
- installer disk-image path under QEMU:
|
||||||
|
- `/dev/vtbd1`
|
||||||
|
- installer ISO path under QEMU:
|
||||||
|
- `/dev/vtbd0`
|
||||||
|
- installer ISO path under XCP-ng:
|
||||||
|
- `/dev/ada0`
|
||||||
|
|
||||||
|
So compared with Guix-on-Linux intuition, Fruix operators should be more explicit about target-device selection during installation and installer-media validation.
|
||||||
|
|
||||||
|
## 6. Fruix now has a minimal installed-system generation command surface, but it is still smaller than Guix's
|
||||||
|
|
||||||
|
This remains the biggest operational gap, but it is no longer a complete gap.
|
||||||
|
|
||||||
|
Installed Fruix systems now provide a larger in-guest helper surface:
|
||||||
|
|
||||||
|
- `fruix system build`
|
||||||
|
- `fruix system reconfigure`
|
||||||
|
- `fruix system status`
|
||||||
|
- `fruix system switch /frx/store/...-fruix-system-...`
|
||||||
|
- `fruix system rollback`
|
||||||
|
|
||||||
|
What this gives you today:
|
||||||
|
|
||||||
|
- explicit current-generation tracking
|
||||||
|
- explicit rollback-generation tracking
|
||||||
|
- in-place switching between already-staged closures on the installed target
|
||||||
|
- rollback without reinstalling the whole system image again
|
||||||
|
- a validated in-node build path that can read the embedded current declaration inputs
|
||||||
|
- a validated in-node `reconfigure` path that can build a candidate closure locally and stage it as the next generation
|
||||||
|
|
||||||
|
What it still does **not** give you yet compared with Guix:
|
||||||
|
|
||||||
|
- a mature end-to-end `upgrade` story for advancing declared inputs automatically
|
||||||
|
- automatic closure transfer/fetch as part of `switch`
|
||||||
|
- a higher-level `deploy` workflow across multiple machines or targets
|
||||||
|
- the broader generation-management UX Guix operators expect
|
||||||
|
|
||||||
|
So if you come from Guix, assume that Fruix now has:
|
||||||
|
|
||||||
|
- strong closure/store semantics
|
||||||
|
- explicit install artifacts
|
||||||
|
- explicit generation metadata roots
|
||||||
|
- a real installed-system build/reconfigure/switch/rollback surface
|
||||||
|
- but not yet the fuller long-term node/deployment UX that Guix users may expect
|
||||||
|
|
||||||
|
## 7. Fruix keeps Guix-like store semantics, but not Guix/Nix hash-prefix machinery exactly
|
||||||
|
|
||||||
|
Fruix still uses immutable store paths under:
|
||||||
|
|
||||||
|
- `/frx/store`
|
||||||
|
|
||||||
|
and it still treats a store path as a deployment identity boundary.
|
||||||
|
|
||||||
|
But Fruix now intentionally differs from Guix/Nix in how the visible store-path prefix is constructed.
|
||||||
|
|
||||||
|
Current Fruix policy is:
|
||||||
|
|
||||||
|
- centralize store-path naming behind shared helpers
|
||||||
|
- hash a small semantic identity record rather than copying Nix's historical path-hash formula exactly
|
||||||
|
- include at least:
|
||||||
|
- object kind
|
||||||
|
- logical/display name
|
||||||
|
- output name
|
||||||
|
- payload or manifest identity
|
||||||
|
- hash-scheme version marker
|
||||||
|
- truncate the visible SHA-256 prefix to **160 bits**
|
||||||
|
- render that visible prefix as **40 hex characters**
|
||||||
|
|
||||||
|
Why Fruix does this instead of copying Guix/Nix exactly:
|
||||||
|
|
||||||
|
- the main goal was shorter store prefixes, not Nix compatibility for its own sake
|
||||||
|
- distinct outputs should have distinct identities because `out`, `lib`, `debug`, and `doc` are semantically different artifacts
|
||||||
|
- Fruix wanted one central policy point that can be swapped later without touching every materializer again
|
||||||
|
- Fruix did **not** want to inherit Nix's legacy details unless they provide clear value here
|
||||||
|
- custom base32 alphabet and bit ordering
|
||||||
|
- compressed/XOR-folded path hashes
|
||||||
|
- exact historical `output:out` / `source` path-hash conventions
|
||||||
|
|
||||||
|
So compared with Guix:
|
||||||
|
|
||||||
|
- the important semantic property is the same:
|
||||||
|
- different store objects should get different immutable identities
|
||||||
|
- the exact printable prefix algorithm is intentionally simpler in Fruix today
|
||||||
|
|
||||||
|
For Guix-familiar operators, the practical takeaway is:
|
||||||
|
|
||||||
|
- still think of `/frx/store/...` paths as immutable deployment identities
|
||||||
|
- do **not** assume Fruix store prefixes are byte-for-byte comparable to Guix/Nix ones
|
||||||
|
- expect Fruix to prefer a simpler, centralized naming policy unless exact Guix/Nix behavior becomes necessary later
|
||||||
|
|
||||||
|
## 8. Fruix can expose separate in-system development and build overlays
|
||||||
|
|
||||||
|
For the validated Phase 20 path, Fruix now distinguishes three layers instead of two:
|
||||||
|
|
||||||
|
- runtime profile
|
||||||
|
- development profile
|
||||||
|
- build profile
|
||||||
|
|
||||||
|
On those systems, Fruix exposes:
|
||||||
|
|
||||||
|
- development:
|
||||||
|
- `/run/current-system/development-profile`
|
||||||
|
- `/run/current-development`
|
||||||
|
- `/usr/local/bin/fruix-development-environment`
|
||||||
|
- build:
|
||||||
|
- `/run/current-system/build-profile`
|
||||||
|
- `/run/current-build`
|
||||||
|
- `/usr/local/bin/fruix-build-environment`
|
||||||
|
- canonical base-build compatibility links:
|
||||||
|
- `/usr/include -> /run/current-system/build-profile/usr/include`
|
||||||
|
- `/usr/share/mk -> /run/current-system/build-profile/usr/share/mk`
|
||||||
|
|
||||||
|
The intent is:
|
||||||
|
|
||||||
|
- keep the main runtime profile lean
|
||||||
|
- keep the development helper interactive and convenient
|
||||||
|
- keep the build helper narrower, more sanitized, and closer to the actual `buildworld` / `buildkernel` contract
|
||||||
|
- avoid treating a development-heavy or build-heavy image as the default runtime shape
|
||||||
|
|
||||||
|
Compared with Guix, this is conceptually similar to keeping development-oriented and build-oriented state separate from the main runtime identity, but Fruix currently expresses it as system-attached overlays rather than through Guix's broader profile/tooling model.
|
||||||
|
|
||||||
|
Fruix still has the guest self-hosted native-build prototype helper at:
|
||||||
|
|
||||||
|
- `/usr/local/bin/fruix-self-hosted-native-build`
|
||||||
|
|
||||||
|
But that helper now explicitly uses the build helper contract instead of trying to reuse the full interactive development shell. The practical Fruix takeaway is:
|
||||||
|
|
||||||
|
- the development overlay makes native base work possible inside the system
|
||||||
|
- the build overlay gives Fruix a stricter, more reproducible contract for real base builds
|
||||||
|
|
||||||
|
Fruix now also makes an explicit distinction between:
|
||||||
|
|
||||||
|
- mutable native-build staging/results under:
|
||||||
|
- `/var/lib/fruix/native-builds`
|
||||||
|
- immutable promoted native-build identities under:
|
||||||
|
- `/frx/store`
|
||||||
|
|
||||||
|
So a guest self-hosted run can stage a result tree locally, while the host can later promote that result into first-class Fruix store objects such as:
|
||||||
|
|
||||||
|
- `/frx/store/...-fruix-native-world-...`
|
||||||
|
- `/frx/store/...-fruix-native-kernel-...`
|
||||||
|
- `/frx/store/...-fruix-native-headers-...`
|
||||||
|
- `/frx/store/...-fruix-native-bootloader-...`
|
||||||
|
- `/frx/store/...-fruix-native-build-result-...`
|
||||||
|
|
||||||
|
Compared with Guix, this is a more explicit split between:
|
||||||
|
|
||||||
|
- a mutable result/staging area for native build execution
|
||||||
|
- and the immutable store identities that Fruix treats as the real promoted result
|
||||||
|
|
||||||
|
Fruix also now models native-build placement more explicitly as an executor choice.
|
||||||
|
|
||||||
|
Current executor kinds are:
|
||||||
|
|
||||||
|
- `host`
|
||||||
|
- `ssh-guest`
|
||||||
|
- `self-hosted`
|
||||||
|
|
||||||
|
So instead of treating:
|
||||||
|
|
||||||
|
- host-driven builds
|
||||||
|
- host-initiated guest builds
|
||||||
|
- guest self-hosted builds
|
||||||
|
|
||||||
|
as three unrelated architectural forks, Fruix is moving toward one native-build result model with different executor policies attached to it.
|
||||||
|
|
||||||
|
Fruix also now lets system declarations consume a promoted native-build result bundle directly.
|
||||||
|
|
||||||
|
That means Fruix can now distinguish more explicitly between:
|
||||||
|
|
||||||
|
- a mutable build/result root used during execution
|
||||||
|
- an immutable promoted native-build identity in `/frx/store`
|
||||||
|
- and a later system declaration that points at that promoted identity as an input
|
||||||
|
|
||||||
|
Compared with Guix, this is still Guix-like in the important sense that the deployed system is identified by immutable store objects, but Fruix is making the FreeBSD native-base result set itself a more explicit first-class declaration input than it was before.
|
||||||
|
|
||||||
|
## Where Fruix is intentionally trying to improve on Guix's representation
|
||||||
|
|
||||||
|
Fruix is not trying to improve on Guix's core semantics. Guix already got those right.
|
||||||
|
|
||||||
|
Where Fruix is intentionally experimenting is mostly the **representation layer**:
|
||||||
|
|
||||||
|
- make generation state more legible to operators
|
||||||
|
- make provenance more visible without needing to reconstruct it mentally from symlink layout alone
|
||||||
|
- separate:
|
||||||
|
- runtime entry point (`/run/current-system`)
|
||||||
|
- installed deployment state (`/var/lib/fruix/system`)
|
||||||
|
- retention roots (`/frx/var/fruix/gcroots`)
|
||||||
|
|
||||||
|
That is why Fruix currently prefers a small per-generation metadata directory instead of only a bare generation link.
|
||||||
|
|
||||||
|
## Practical operator advice for Guix users
|
||||||
|
|
||||||
|
If you are already comfortable with Guix, the safest Fruix mental model today is:
|
||||||
|
|
||||||
|
1. think in terms of immutable closures and declarations first
|
||||||
|
2. use `fruix system build` as the canonical starting point
|
||||||
|
3. treat image/install/installer/installer-iso as deployment materializers built from the same declaration
|
||||||
|
4. identify a deployment by:
|
||||||
|
- closure path
|
||||||
|
- source provenance metadata
|
||||||
|
- install metadata
|
||||||
|
5. think of rollback in two layers:
|
||||||
|
- if the target already has the desired closure staged locally:
|
||||||
|
- use `fruix system rollback`
|
||||||
|
- otherwise:
|
||||||
|
- redeploy the earlier declaration again
|
||||||
|
|
||||||
|
## Status summary
|
||||||
|
|
||||||
|
Today Fruix is closest to Guix in:
|
||||||
|
|
||||||
|
- store and closure semantics
|
||||||
|
- declarative deployment identity
|
||||||
|
- rollback-friendly immutability
|
||||||
|
- `/run/current-system` runtime model
|
||||||
|
|
||||||
|
It differs most from Guix in:
|
||||||
|
|
||||||
|
- FreeBSD platform details
|
||||||
|
- source-provenance emphasis
|
||||||
|
- installer-medium-oriented workflows
|
||||||
|
- generation-layout representation
|
||||||
|
- and an installed-system generation command surface that now exists, but is still much smaller than Guix's
|
||||||
175
docs/PROGRESS.md
175
docs/PROGRESS.md
@@ -24,6 +24,57 @@ Fruix currently has:
|
|||||||
- `fruix system install`
|
- `fruix system install`
|
||||||
- a bootable Fruix-managed installer environment:
|
- a bootable Fruix-managed installer environment:
|
||||||
- `fruix system installer`
|
- `fruix system installer`
|
||||||
|
- a bootable Fruix-managed installer ISO:
|
||||||
|
- `fruix system installer-iso`
|
||||||
|
- an explicit installed-system generation layout under:
|
||||||
|
- `/var/lib/fruix/system`
|
||||||
|
- explicit installed-system retention roots under:
|
||||||
|
- `/frx/var/fruix/gcroots`
|
||||||
|
- a validated installed-system generation switch/rollback workflow via:
|
||||||
|
- `fruix system status`
|
||||||
|
- `fruix system switch`
|
||||||
|
- `fruix system rollback`
|
||||||
|
- a validated separate in-system development environment overlay via:
|
||||||
|
- `/run/current-system/development-profile`
|
||||||
|
- `/run/current-development`
|
||||||
|
- `/usr/local/bin/fruix-development-environment`
|
||||||
|
- a validated separate in-system build environment overlay via:
|
||||||
|
- `/run/current-system/build-profile`
|
||||||
|
- `/run/current-build`
|
||||||
|
- `/usr/local/bin/fruix-build-environment`
|
||||||
|
- `/usr/include -> /run/current-system/build-profile/usr/include`
|
||||||
|
- `/usr/share/mk -> /run/current-system/build-profile/usr/share/mk`
|
||||||
|
- a validated host-initiated native base-build path inside a Fruix-managed guest via:
|
||||||
|
- real XCP-ng boot of a development-enabled Fruix system
|
||||||
|
- in-guest `buildworld` / `buildkernel`
|
||||||
|
- staged `installworld` / `distribution` / `installkernel`
|
||||||
|
- a validated controlled guest self-hosted native base-build prototype via:
|
||||||
|
- `/usr/local/bin/fruix-self-hosted-native-build`
|
||||||
|
- result roots under `/var/lib/fruix/native-builds`
|
||||||
|
- in-guest source recovery from current-system metadata
|
||||||
|
- a validated promotion path that turns native-build result roots into first-class Fruix store objects via:
|
||||||
|
- `fruix native-build promote`
|
||||||
|
- `/frx/store/...-fruix-native-world-...`
|
||||||
|
- `/frx/store/...-fruix-native-kernel-...`
|
||||||
|
- `/frx/store/...-fruix-native-headers-...`
|
||||||
|
- `/frx/store/...-fruix-native-bootloader-...`
|
||||||
|
- `/frx/store/...-fruix-native-build-result-...`
|
||||||
|
- an explicit executor model for native base builds with current executor kinds:
|
||||||
|
- `host`
|
||||||
|
- `ssh-guest`
|
||||||
|
- `self-hosted`
|
||||||
|
- end-to-end validated staged-result-plus-promotion paths for executor policies:
|
||||||
|
- `ssh-guest`
|
||||||
|
- `self-hosted`
|
||||||
|
- system declarations that can now consume a promoted native-build result bundle directly via:
|
||||||
|
- `promoted-native-build-result`
|
||||||
|
- `operating-system-from-promoted-native-build-result`
|
||||||
|
- a real XCP-ng boot validation of a system materialized from a promoted native-build result identity
|
||||||
|
- installed systems that now carry their own canonical declaration inputs and bundled Fruix node CLI sources
|
||||||
|
- a real XCP-ng validation of in-node:
|
||||||
|
- `fruix system build`
|
||||||
|
- `fruix system reconfigure`
|
||||||
|
- `fruix system rollback`
|
||||||
|
|
||||||
Validated boot modes still are:
|
Validated boot modes still are:
|
||||||
|
|
||||||
@@ -36,45 +87,98 @@ The validated Phase 18 installation work currently uses:
|
|||||||
|
|
||||||
## Latest completed achievement
|
## Latest completed achievement
|
||||||
|
|
||||||
### 2026-04-04 — Phase 18.2 completed
|
### 2026-04-06 — Fruix now separates interactive development from strict native base-build environment
|
||||||
|
|
||||||
Fruix now boots a minimal installer environment and installs a target system from inside it.
|
Fruix now has a more explicit three-layer model for build-capable FreeBSD systems:
|
||||||
|
|
||||||
|
- runtime profile
|
||||||
|
- development profile
|
||||||
|
- build profile
|
||||||
|
|
||||||
Highlights:
|
Highlights:
|
||||||
|
|
||||||
- added in `modules/fruix/system/freebsd.scm`:
|
- `<operating-system>` now supports separate `build-packages`
|
||||||
- `installer-operating-system`
|
- system closures can now materialize both:
|
||||||
- `operating-system-installer-image-spec`
|
- `development-profile`
|
||||||
- `materialize-installer-image`
|
- `build-profile`
|
||||||
- added CLI support in `scripts/fruix.scm`:
|
- build-capable systems now expose:
|
||||||
- `fruix system installer`
|
- `/run/current-system/build-profile`
|
||||||
- `--install-target-device DEVICE`
|
- `/run/current-build`
|
||||||
- the installer image now carries:
|
- `/usr/local/bin/fruix-build-environment`
|
||||||
- its own installer closure
|
- canonical compatibility links for native base builds now come from the build profile:
|
||||||
- the selected target closure
|
- `/usr/include -> /run/current-system/build-profile/usr/include`
|
||||||
- the target store closure
|
- `/usr/share/mk -> /run/current-system/build-profile/usr/share/mk`
|
||||||
- a staged target rootfs payload
|
- the new build helper intentionally clears development-shell variables such as:
|
||||||
- in-guest installer state/log/scripts
|
- `MAKEFLAGS`
|
||||||
- validated workflow:
|
- `CPPFLAGS`
|
||||||
- boot installer image in QEMU/UEFI/TCG
|
- `CFLAGS`
|
||||||
- reach installer over SSH
|
- `CXXFLAGS`
|
||||||
- install target system onto second disk from inside the guest
|
- `LDFLAGS`
|
||||||
- boot the installed target successfully
|
- the self-hosted native-build helper now uses this stricter build-helper contract instead of manually reconstructing that sanitization ad hoc
|
||||||
|
- promotion metadata for native-build results now records `build-profile` explicitly
|
||||||
|
|
||||||
Validation:
|
Validation:
|
||||||
|
|
||||||
- `PASS phase18-installer-environment`
|
- `PASS phase20-development-environment-xcpng`
|
||||||
- regression re-checks:
|
- `PASS phase20-self-hosted-native-build-xcpng`
|
||||||
- `PASS phase18-system-install`
|
- `PASS phase20-native-build-store-promotion-xcpng`
|
||||||
- `PASS phase17-source-revisions-qemu`
|
- `PASS phase20-host-initiated-native-build-xcpng`
|
||||||
|
- `PASS phase20-host-initiated-native-build-store-promotion-xcpng`
|
||||||
|
|
||||||
|
Representative validated metadata included:
|
||||||
|
|
||||||
|
- `build_profile_guest=/run/current-system/build-profile`
|
||||||
|
- `build_profile=/run/current-system/build-profile`
|
||||||
|
- `helper_version=5`
|
||||||
|
- `executor_version=5`
|
||||||
|
|
||||||
Report:
|
Report:
|
||||||
|
|
||||||
- `docs/reports/phase18-installer-environment-freebsd.md`
|
- `docs/reports/postphase20-build-profile-separation-freebsd.md`
|
||||||
|
- `docs/system-deployment-workflow.md`
|
||||||
|
- `docs/GUIX_DIFFERENCES.md`
|
||||||
|
|
||||||
Commit:
|
### 2026-04-06 — Installed systems can now build and reconfigure themselves from local declaration state
|
||||||
|
|
||||||
- `1d00907` — `Add Fruix bootable installer environment`
|
Fruix-installed systems are now meaningfully closer to real Fruix nodes.
|
||||||
|
|
||||||
|
Highlights:
|
||||||
|
|
||||||
|
- system closures now carry canonical declaration metadata in:
|
||||||
|
- `metadata/system-declaration.scm`
|
||||||
|
- `metadata/system-declaration-info.scm`
|
||||||
|
- `metadata/system-declaration-system`
|
||||||
|
- system closures now also carry bundled Fruix node CLI sources in:
|
||||||
|
- `share/fruix/node/scripts/fruix.scm`
|
||||||
|
- `share/fruix/node/modules/...`
|
||||||
|
- `share/fruix/node/guix/guix/build/utils.scm`
|
||||||
|
- the installed helper at `/usr/local/bin/fruix` now supports:
|
||||||
|
- `fruix system build`
|
||||||
|
- `fruix system reconfigure`
|
||||||
|
- `fruix system status`
|
||||||
|
- `fruix system switch`
|
||||||
|
- `fruix system rollback`
|
||||||
|
- no-argument in-node `build` and `reconfigure` now use the node's own embedded declaration inputs
|
||||||
|
- in-node Fruix builds now reuse the installed Guile/Shepherd runtime stores already referenced by the system instead of assuming host-only `/tmp/...` build prefixes
|
||||||
|
- the real XCP-ng validation proved the full installed-node flow:
|
||||||
|
- boot current system
|
||||||
|
- build from local declaration state
|
||||||
|
- build a candidate declaration on-node
|
||||||
|
- `reconfigure` into that candidate generation
|
||||||
|
- reboot into the candidate generation
|
||||||
|
- `rollback`
|
||||||
|
- reboot back into the original generation
|
||||||
|
|
||||||
|
Validation:
|
||||||
|
|
||||||
|
- `PASS postphase20-installed-node-build-reconfigure-xcpng`
|
||||||
|
|
||||||
|
Reports:
|
||||||
|
|
||||||
|
- `docs/reports/postphase20-promoted-native-base-declarations-freebsd.md`
|
||||||
|
- `docs/reports/postphase20-installed-node-management-freebsd.md`
|
||||||
|
- `docs/system-deployment-workflow.md`
|
||||||
|
- `docs/GUIX_DIFFERENCES.md`
|
||||||
|
|
||||||
## Recent major milestones
|
## Recent major milestones
|
||||||
|
|
||||||
@@ -98,8 +202,19 @@ Commit:
|
|||||||
|
|
||||||
## Next step
|
## Next step
|
||||||
|
|
||||||
Per `docs/PLAN_4.md`, the next planned step is:
|
`docs/PLAN_4.md` currently ends at Phase 20.3, and that milestone sequence is now complete.
|
||||||
|
|
||||||
- **Phase 18.3** — produce a bootable UEFI installer ISO
|
The next practical follow-up is now clearer:
|
||||||
|
|
||||||
That should build on the now-validated installer environment rather than replacing it.
|
- grow the installed-node command surface from validated `build`/`reconfigure`/`rollback` toward:
|
||||||
|
- `upgrade`
|
||||||
|
- `build-base`
|
||||||
|
- `deploy`
|
||||||
|
- decide how executor policy, native-base promotion, and installed-node reconfiguration should compose in one operator-facing workflow
|
||||||
|
- determine how much of native-build request/promotion should remain explicit versus being absorbed into higher-level Fruix node actions
|
||||||
|
|
||||||
|
The immediate architectural direction is no longer just “can guest self-hosting work?”
|
||||||
|
|
||||||
|
It is now:
|
||||||
|
|
||||||
|
- how should Fruix expose real managed-node behavior across declaration inputs, native-base results, generation switching, and deployment actions?
|
||||||
|
|||||||
277
docs/reports/phase18-installer-iso-freebsd.md
Normal file
277
docs/reports/phase18-installer-iso-freebsd.md
Normal file
@@ -0,0 +1,277 @@
|
|||||||
|
# Phase 18.3: bootable Fruix installer ISO on FreeBSD
|
||||||
|
|
||||||
|
Date: 2026-04-04
|
||||||
|
|
||||||
|
## Goal
|
||||||
|
|
||||||
|
Phase 18.3 extends the Phase 18.2 installer-environment work from a disk-image-style installer into a UEFI-bootable ISO artifact.
|
||||||
|
|
||||||
|
The intended first ISO is deliberately narrow:
|
||||||
|
|
||||||
|
- UEFI only
|
||||||
|
- serial-console-friendly
|
||||||
|
- non-interactive install flow reused from Phase 18.1/18.2
|
||||||
|
- target disk installation still performed by the same Fruix-managed in-guest installer logic
|
||||||
|
|
||||||
|
## Implementation
|
||||||
|
|
||||||
|
### New API
|
||||||
|
|
||||||
|
Added in `modules/fruix/system/freebsd.scm`:
|
||||||
|
|
||||||
|
- `operating-system-installer-iso-spec`
|
||||||
|
- `materialize-installer-iso`
|
||||||
|
|
||||||
|
The system module split done immediately before this phase was also exercised during this work.
|
||||||
|
|
||||||
|
### New CLI action
|
||||||
|
|
||||||
|
Added in `scripts/fruix.scm`:
|
||||||
|
|
||||||
|
- `fruix system installer-iso`
|
||||||
|
|
||||||
|
This action emits metadata for:
|
||||||
|
|
||||||
|
- ISO store path
|
||||||
|
- ISO image path
|
||||||
|
- EFI boot image path
|
||||||
|
- installer root image path
|
||||||
|
- installer and target closure paths
|
||||||
|
- installer state/log paths
|
||||||
|
- declared/materialized FreeBSD source metadata
|
||||||
|
- store closure counts
|
||||||
|
|
||||||
|
### ISO boot model
|
||||||
|
|
||||||
|
The ISO does not try to run the Fruix installer directly from a read-only cd9660 root.
|
||||||
|
|
||||||
|
Instead it uses a small UEFI El Torito boot image plus an in-memory installer root image:
|
||||||
|
|
||||||
|
1. a small FAT EFI boot image contains `EFI/BOOT/BOOTX64.EFI`
|
||||||
|
2. the ISO root contains real boot assets under `/boot`
|
||||||
|
3. the ISO root also contains `/boot/root.img`
|
||||||
|
4. `loader.conf` on the ISO is augmented with:
|
||||||
|
- `mdroot_load="YES"`
|
||||||
|
- `mdroot_type="mfs_root"`
|
||||||
|
- `mdroot_name="/boot/root.img"`
|
||||||
|
- `vfs.root.mountfrom="ufs:/dev/md0"`
|
||||||
|
- `vfs.root.mountfrom.options="rw"`
|
||||||
|
|
||||||
|
A practical loader detail surfaced during validation:
|
||||||
|
|
||||||
|
- setting `rootdev` or `currdev` to `md0:` in the ISO loader path is wrong for this loader configuration and caused an early EFI-loader crash before kernel handoff
|
||||||
|
- the reliable ISO path is to let loader keep its current device on the CD media, preload `/boot/root.img`, and pass only `vfs.root.mountfrom=ufs:/dev/md0`
|
||||||
|
|
||||||
|
This preserves the existing Fruix installer environment semantics while avoiding the need to make the whole installer operate directly from a read-only ISO root.
|
||||||
|
|
||||||
|
### Installer root image contents
|
||||||
|
|
||||||
|
`materialize-installer-iso` stages the same installer payload model already validated in Phase 18.2:
|
||||||
|
|
||||||
|
- installer closure
|
||||||
|
- target closure
|
||||||
|
- target runtime store closure needed for installation/boot
|
||||||
|
- staged target rootfs under `/var/lib/fruix/installer/target-rootfs`
|
||||||
|
- installer plan and state files under `/var/lib/fruix/installer`
|
||||||
|
- installer helper scripts:
|
||||||
|
- `/usr/local/libexec/fruix-installer-run`
|
||||||
|
- `/usr/local/etc/rc.d/fruix-installer`
|
||||||
|
|
||||||
|
The ISO root image is then built as a UFS image and embedded as `/boot/root.img`.
|
||||||
|
|
||||||
|
### Split-regression fixes found during this work
|
||||||
|
|
||||||
|
While exercising the refactored split modules, two issues surfaced and were fixed:
|
||||||
|
|
||||||
|
1. `string-hash` name-clash warnings
|
||||||
|
- the old helper name collided with Guile/SRFI bindings
|
||||||
|
- it was renamed to `sha256-string`
|
||||||
|
2. missing `prefix-materializer-version`
|
||||||
|
- this constant was accidentally omitted when `modules/fruix/system/freebsd.scm` was split
|
||||||
|
- the missing definition was restored in `modules/fruix/system/freebsd/build.scm`
|
||||||
|
|
||||||
|
## Validation
|
||||||
|
|
||||||
|
### Completed smoke validation
|
||||||
|
|
||||||
|
A host-side smoke build was completed successfully for the new ISO builder using a host-staged operating-system definition:
|
||||||
|
|
||||||
|
- command pattern:
|
||||||
|
- `fruix system installer-iso ...`
|
||||||
|
- result:
|
||||||
|
- successful ISO materialization in a temporary store
|
||||||
|
- artifact checks performed:
|
||||||
|
- `etdump` reports an EFI El Torito boot entry
|
||||||
|
- the ISO contains:
|
||||||
|
- `boot/kernel/kernel`
|
||||||
|
- `boot/kernel/linker.hints`
|
||||||
|
- `boot/loader.conf`
|
||||||
|
- `boot/loader.efi`
|
||||||
|
- `boot/root.img`
|
||||||
|
- `boot/loader.conf` inside the ISO contains the expected `mdroot_*` and `vfs.root.mountfrom` entries
|
||||||
|
|
||||||
|
Example smoke-build metadata:
|
||||||
|
|
||||||
|
```text
|
||||||
|
action=installer-iso
|
||||||
|
iso_volume_label=FRUIX_INSTALLER
|
||||||
|
iso_store_path=/tmp/...-fruix-installer-iso-fruix-freebsd-installer
|
||||||
|
iso_image=/tmp/...-fruix-installer-iso-fruix-freebsd-installer/installer.iso
|
||||||
|
boot_efi_image=/tmp/...-fruix-installer-iso-fruix-freebsd-installer/efiboot.img
|
||||||
|
root_image=/tmp/...-fruix-installer-iso-fruix-freebsd-installer/root.img
|
||||||
|
installer_closure_path=/tmp/...-fruix-system-fruix-freebsd-installer
|
||||||
|
target_closure_path=/tmp/...-fruix-system-fruix-freebsd
|
||||||
|
```
|
||||||
|
|
||||||
|
### End-to-end harness validation
|
||||||
|
|
||||||
|
Added:
|
||||||
|
|
||||||
|
- `tests/system/run-phase18-installer-iso.sh`
|
||||||
|
|
||||||
|
This harness validates the full Phase 18.3 flow:
|
||||||
|
|
||||||
|
1. build installer ISO
|
||||||
|
2. boot it under QEMU/UEFI/TCG
|
||||||
|
3. install onto a target disk from inside the booted ISO environment
|
||||||
|
4. boot the installed target
|
||||||
|
|
||||||
|
Passing validation:
|
||||||
|
|
||||||
|
- `PASS phase18-installer-iso`
|
||||||
|
|
||||||
|
Validated result summary:
|
||||||
|
|
||||||
|
```text
|
||||||
|
installer_iso_store_path=/frx/store/...-fruix-installer-iso-fruix-freebsd-installer
|
||||||
|
installer_iso_image=/frx/store/...-fruix-installer-iso-fruix-freebsd-installer/installer.iso
|
||||||
|
installer_boot_efi_image=/frx/store/...-fruix-installer-iso-fruix-freebsd-installer/efiboot.img
|
||||||
|
installer_root_image=/frx/store/...-fruix-installer-iso-fruix-freebsd-installer/root.img
|
||||||
|
install_target_device=/dev/vtbd0
|
||||||
|
freebsd_source_kind=git
|
||||||
|
freebsd_source_ref=stable/15
|
||||||
|
freebsd_source_commit=332708a606f6bf0841c1d4a74c0d067f5640fe89
|
||||||
|
materialized_source_store=/frx/store/459499e0eb29f4c73ad455060dd2502d21fb56f205c0a676831cf723b3a0c378-freebsd-source-stable15-installer-iso-target-source
|
||||||
|
installer_state=done
|
||||||
|
installer_sshd_status=running
|
||||||
|
target_esp_fstype=msdosfs
|
||||||
|
target_root_fstype=ufs
|
||||||
|
target_shepherd_status=running
|
||||||
|
target_sshd_status=running
|
||||||
|
installer_iso_boot=ok
|
||||||
|
installer_iso_install=ok
|
||||||
|
installed_target_boot=ok
|
||||||
|
```
|
||||||
|
|
||||||
|
Notable QEMU-specific ISO validation detail:
|
||||||
|
|
||||||
|
- unlike the disk-image-style installer environment from Phase 18.2, the ISO boots from `cd0`, so the target virtio disk appears as:
|
||||||
|
- `/dev/vtbd0`
|
||||||
|
- the earlier installer-environment default:
|
||||||
|
- `/dev/vtbd1`
|
||||||
|
remains correct for the disk-image installer, but not for the ISO path
|
||||||
|
|
||||||
|
The harness verified all of the following:
|
||||||
|
|
||||||
|
1. `fruix system installer-iso` produces a bootable ISO artifact in `/frx/store`
|
||||||
|
2. the ISO boots successfully under QEMU/UEFI/TCG
|
||||||
|
3. the booted installer ISO environment becomes reachable over SSH
|
||||||
|
4. `/run/current-system` inside the installer ISO points at the installer closure
|
||||||
|
5. the installer rc.d job reaches:
|
||||||
|
- `state=done`
|
||||||
|
6. the installer log records:
|
||||||
|
- `fruix-installer:done`
|
||||||
|
7. the installed target disk contains:
|
||||||
|
- GPT partitioning
|
||||||
|
- EFI filesystem: `msdosfs`
|
||||||
|
- root filesystem: `ufs`
|
||||||
|
- `EFI/BOOT/BOOTX64.EFI`
|
||||||
|
- `/var/lib/fruix/install.scm`
|
||||||
|
8. the installed target then boots successfully as its own Fruix system under QEMU/UEFI/TCG
|
||||||
|
9. after target boot:
|
||||||
|
- `/run/current-system` points at the target closure
|
||||||
|
- shepherd is running
|
||||||
|
- `sshd` is running
|
||||||
|
- activation completed successfully
|
||||||
|
|
||||||
|
### Real XCP-ng validation
|
||||||
|
|
||||||
|
Added:
|
||||||
|
|
||||||
|
- `tests/system/run-phase18-installer-iso-xcpng.sh`
|
||||||
|
|
||||||
|
This harness validates the same installer-iso workflow on the approved real XCP-ng path:
|
||||||
|
|
||||||
|
- VM: `90490f2e-e8fc-4b7a-388e-5c26f0157289`
|
||||||
|
- ISO SR: `537a6219-8452-7cb5-8d56-5eed6910c7a2`
|
||||||
|
- target VDIs:
|
||||||
|
- `0f1f90d3-48ca-4fa2-91d8-fc6339b95743`
|
||||||
|
- `7061d761-3639-4bec-87f7-2ba1af924eaa`
|
||||||
|
|
||||||
|
Because the current `xo-cli disk.import @=/path/to.iso` path returned an HTTP 500 error in this environment, the harness imports the ISO into the ISO SR via a temporary local HTTP URL, then inserts the resulting ISO VDI into the VM's CD drive.
|
||||||
|
|
||||||
|
Passing validation:
|
||||||
|
|
||||||
|
- `PASS phase18-installer-iso-xcpng`
|
||||||
|
|
||||||
|
Validated result summary:
|
||||||
|
|
||||||
|
```text
|
||||||
|
vm_id=90490f2e-e8fc-4b7a-388e-5c26f0157289
|
||||||
|
iso_id=<temporary-imported-iso-vdi>
|
||||||
|
guest_ip=192.168.213.62
|
||||||
|
installer_state=done
|
||||||
|
installer_target_device=/dev/ada0
|
||||||
|
kern_disks=cd0 ada1 ada0
|
||||||
|
installer_run_current_system=/frx/store/16969e825dbb65b5c27180030d4a7d98821893460fb3dccdc863ff6156ed61e0-fruix-system-fruix-freebsd-installer
|
||||||
|
installer_sshd_status=running
|
||||||
|
target_run_current_system=/frx/store/a98d3af6a1afbc4a927d47cea6458d5a70747b051ed994e5d9ff1ae79c4f2b42-fruix-system-fruix-freebsd
|
||||||
|
target_sshd_status=running
|
||||||
|
target_shepherd_status=running
|
||||||
|
```
|
||||||
|
|
||||||
|
Important XCP-ng-specific details:
|
||||||
|
|
||||||
|
- the installer ISO still boots from:
|
||||||
|
- `cd0`
|
||||||
|
- on this Xen HVM path, the primary target disk is exposed through Xen block front as `xbd0` and appears to FreeBSD as:
|
||||||
|
- `/dev/ada0`
|
||||||
|
- therefore the XCP-ng installer-iso path must target:
|
||||||
|
- `/dev/ada0`
|
||||||
|
rather than QEMU's:
|
||||||
|
- `/dev/vtbd0`
|
||||||
|
- the visible EFI console can appear to stop at:
|
||||||
|
- `console vidconsole is unavailable`
|
||||||
|
but boot still continues and the installer becomes reachable over SSH; that message was not the actual failure mode on XCP-ng
|
||||||
|
|
||||||
|
The harness verified all of the following on the real VM path:
|
||||||
|
|
||||||
|
1. `fruix system installer-iso` builds a bootable ISO with `--install-target-device /dev/ada0`
|
||||||
|
2. the ISO can be imported into the operator-approved ISO SR and attached to the approved VM
|
||||||
|
3. the VM boots the Fruix installer ISO successfully under UEFI
|
||||||
|
4. the installer environment becomes reachable over SSH
|
||||||
|
5. inside the installer guest:
|
||||||
|
- `kern.disks` includes `cd0` and `ada0`
|
||||||
|
- `/run/current-system` points at the installer closure
|
||||||
|
- the installer reaches `state=done`
|
||||||
|
6. the installed target on `ada0` is partitioned and formatted correctly
|
||||||
|
7. after ejecting the ISO and rebooting, the installed target boots successfully on the same XCP-ng VM
|
||||||
|
8. after target boot:
|
||||||
|
- `/run/current-system` points at the target closure
|
||||||
|
- shepherd is running
|
||||||
|
- `sshd` is running
|
||||||
|
- activation completed successfully
|
||||||
|
- `/var/lib/fruix/install.scm` still records the materialized source store provenance
|
||||||
|
|
||||||
|
## Result
|
||||||
|
|
||||||
|
Phase 18.3 is complete.
|
||||||
|
|
||||||
|
Fruix now has a validated bootable UEFI installer ISO on FreeBSD that can:
|
||||||
|
|
||||||
|
- boot into a Fruix-managed installer environment from ISO media
|
||||||
|
- perform the non-interactive installation flow onto a target disk
|
||||||
|
- boot the installed target successfully
|
||||||
|
- and do so on both:
|
||||||
|
- local `QEMU/UEFI/TCG`
|
||||||
|
- the approved real `XCP-ng` VM path
|
||||||
143
docs/reports/phase19-deployment-workflow-freebsd.md
Normal file
143
docs/reports/phase19-deployment-workflow-freebsd.md
Normal file
@@ -0,0 +1,143 @@
|
|||||||
|
# Phase 19.1: canonical Fruix deployment workflow on FreeBSD
|
||||||
|
|
||||||
|
Date: 2026-04-04
|
||||||
|
|
||||||
|
## Goal
|
||||||
|
|
||||||
|
Phase 19.1 is about turning Fruix's already-validated closure/image/install behavior into a clear operator-facing deployment story.
|
||||||
|
|
||||||
|
The verification target here is documentation clarity rather than a new low-level boot primitive.
|
||||||
|
|
||||||
|
The repo needed a single coherent explanation of how Fruix expects operators to:
|
||||||
|
|
||||||
|
- build a system closure
|
||||||
|
- materialize a rootfs or image
|
||||||
|
- install directly to an image or block device
|
||||||
|
- use the installer image and installer ISO paths
|
||||||
|
- roll forward to a candidate declaration
|
||||||
|
- roll back to an earlier declaration
|
||||||
|
|
||||||
|
## Result
|
||||||
|
|
||||||
|
Phase 19.1 is complete.
|
||||||
|
|
||||||
|
The repository now documents a first-class Fruix deployment workflow in:
|
||||||
|
|
||||||
|
- `docs/system-deployment-workflow.md`
|
||||||
|
|
||||||
|
That document defines the current canonical command surface and explains how the already-existing validated paths fit together operationally.
|
||||||
|
|
||||||
|
## What was documented
|
||||||
|
|
||||||
|
### Canonical frontend
|
||||||
|
|
||||||
|
The documented user-facing frontend is now explicitly:
|
||||||
|
|
||||||
|
- `./bin/fruix system ...`
|
||||||
|
|
||||||
|
This includes the currently supported deployment-oriented actions:
|
||||||
|
|
||||||
|
- `build`
|
||||||
|
- `rootfs`
|
||||||
|
- `image`
|
||||||
|
- `install`
|
||||||
|
- `installer`
|
||||||
|
- `installer-iso`
|
||||||
|
|
||||||
|
### Canonical deployment model
|
||||||
|
|
||||||
|
The workflow document now defines Fruix's current deployment model as:
|
||||||
|
|
||||||
|
1. declare a system in Scheme
|
||||||
|
2. build the system closure in `/frx/store`
|
||||||
|
3. materialize the artifact appropriate to the target environment
|
||||||
|
4. boot or install that artifact
|
||||||
|
5. treat the resulting closure path and emitted provenance metadata as the deployment identity
|
||||||
|
|
||||||
|
### Roll-forward and rollback semantics
|
||||||
|
|
||||||
|
The document makes explicit an important current design point:
|
||||||
|
|
||||||
|
- Fruix rollback is already real at the declaration/closure/deployment layer
|
||||||
|
- but it is not yet a first-class installed-system generation switch operation
|
||||||
|
|
||||||
|
So the documented rollback workflow today is:
|
||||||
|
|
||||||
|
- retain the earlier declaration
|
||||||
|
- rebuild or rematerialize it
|
||||||
|
- redeploy or reboot that earlier closure again
|
||||||
|
|
||||||
|
That matches what Fruix has already validated in earlier phases.
|
||||||
|
|
||||||
|
### Platform-specific installer target-device detail
|
||||||
|
|
||||||
|
The workflow document also records the now-important target-device distinctions between validated environments:
|
||||||
|
|
||||||
|
- installer disk-image path under QEMU:
|
||||||
|
- `/dev/vtbd1`
|
||||||
|
- installer ISO path under QEMU:
|
||||||
|
- `/dev/vtbd0`
|
||||||
|
- installer ISO path under XCP-ng:
|
||||||
|
- `/dev/ada0`
|
||||||
|
|
||||||
|
That makes the deployment story less harness-specific and more operator-explicit.
|
||||||
|
|
||||||
|
## Why this satisfies Phase 19.1
|
||||||
|
|
||||||
|
Before this phase, Fruix already had the machinery for:
|
||||||
|
|
||||||
|
- building declarative system closures
|
||||||
|
- generating bootable images
|
||||||
|
- performing direct non-interactive installation
|
||||||
|
- booting a Fruix installer environment
|
||||||
|
- booting and installing from a Fruix installer ISO
|
||||||
|
- rollback-friendly redeploy of earlier declarations
|
||||||
|
|
||||||
|
What was missing was a repo-level explanation that unified those into a single operator workflow.
|
||||||
|
|
||||||
|
The new document closes that gap by connecting:
|
||||||
|
|
||||||
|
- Phase 10 command-surface work
|
||||||
|
- Phase 15 redeploy/rollback validation
|
||||||
|
- Phase 18 install and installer-media validation
|
||||||
|
- and the recent QEMU + XCP-ng installer ISO validation
|
||||||
|
|
||||||
|
## Current boundaries now made explicit
|
||||||
|
|
||||||
|
The documentation intentionally records what Fruix has **not** solved yet:
|
||||||
|
|
||||||
|
- installed-system generation links
|
||||||
|
- explicit rollback targets and generation metadata
|
||||||
|
- a first-class `switch` or `reconfigure` command
|
||||||
|
- installed-system rollback as an in-place operator workflow
|
||||||
|
- GC-root management for installed systems
|
||||||
|
|
||||||
|
Those are left for later Phase 19 steps rather than being blurred into the current deployment story.
|
||||||
|
|
||||||
|
## References to existing validation
|
||||||
|
|
||||||
|
The documented workflow rests on already-passing validation paths, including:
|
||||||
|
|
||||||
|
- `PASS phase18-system-install`
|
||||||
|
- `PASS phase18-installer-environment`
|
||||||
|
- `PASS phase18-installer-iso`
|
||||||
|
- `PASS phase18-installer-iso-xcpng`
|
||||||
|
- `PASS phase15-base-rollback-qemu`
|
||||||
|
- `PASS phase15-base-rollback-xcpng`
|
||||||
|
|
||||||
|
## Conclusion
|
||||||
|
|
||||||
|
Phase 19.1 is now complete.
|
||||||
|
|
||||||
|
Fruix has a documented canonical deployment workflow for FreeBSD covering:
|
||||||
|
|
||||||
|
- build
|
||||||
|
- image generation
|
||||||
|
- direct install
|
||||||
|
- installer-media install
|
||||||
|
- roll-forward
|
||||||
|
- rollback by redeploying an earlier declaration
|
||||||
|
|
||||||
|
The next step is Phase 19.2:
|
||||||
|
|
||||||
|
- model installed-system generations, rollback targets, and deployment roots more explicitly.
|
||||||
192
docs/reports/phase19-generation-layout-freebsd.md
Normal file
192
docs/reports/phase19-generation-layout-freebsd.md
Normal file
@@ -0,0 +1,192 @@
|
|||||||
|
# Phase 19.2: explicit installed-system generation layout on FreeBSD
|
||||||
|
|
||||||
|
Date: 2026-04-04
|
||||||
|
|
||||||
|
## Goal
|
||||||
|
|
||||||
|
Phase 19.2 is about making Fruix's installed-system generation model more explicit.
|
||||||
|
|
||||||
|
The target here is not yet a full Guix-equivalent in-place `switch-generation` workflow.
|
||||||
|
|
||||||
|
The immediate goal is to stop relying mainly on harness knowledge and implicit symlink expectations by recording installed deployment state more explicitly on disk.
|
||||||
|
|
||||||
|
## Decision
|
||||||
|
|
||||||
|
Fruix now follows this design direction:
|
||||||
|
|
||||||
|
- keep Guix-like **semantics**
|
||||||
|
- do not mirror Guix's installed-system/profile layout **mechanically 1:1**
|
||||||
|
|
||||||
|
What Fruix preserves from Guix:
|
||||||
|
|
||||||
|
- immutable closure identity
|
||||||
|
- rollback-friendly deployment semantics
|
||||||
|
- explicit current deployment pointer
|
||||||
|
- GC-root-style retention links
|
||||||
|
- `/run/current-system` as the active runtime boundary
|
||||||
|
|
||||||
|
What Fruix intentionally changes:
|
||||||
|
|
||||||
|
- installed-system generation state is represented as a small metadata-bearing directory
|
||||||
|
- the generation model is recorded under a Fruix-native path
|
||||||
|
- deployment metadata and provenance are easier to inspect directly without reconstructing intent from symlink layout alone
|
||||||
|
|
||||||
|
## Implemented layout
|
||||||
|
|
||||||
|
Installed systems now record an explicit generation root under:
|
||||||
|
|
||||||
|
- `/var/lib/fruix/system`
|
||||||
|
|
||||||
|
Current validated initial layout:
|
||||||
|
|
||||||
|
```text
|
||||||
|
/var/lib/fruix/system/
|
||||||
|
current -> generations/1
|
||||||
|
current-generation
|
||||||
|
generations/
|
||||||
|
1/
|
||||||
|
closure -> /frx/store/...-fruix-system-...
|
||||||
|
metadata.scm
|
||||||
|
provenance.scm
|
||||||
|
install.scm
|
||||||
|
```
|
||||||
|
|
||||||
|
Installed systems now also create explicit retention roots under:
|
||||||
|
|
||||||
|
- `/frx/var/fruix/gcroots`
|
||||||
|
|
||||||
|
Current validated initial layout:
|
||||||
|
|
||||||
|
```text
|
||||||
|
/frx/var/fruix/gcroots/
|
||||||
|
current-system -> /frx/store/...-fruix-system-...
|
||||||
|
system-1 -> /frx/store/...-fruix-system-...
|
||||||
|
```
|
||||||
|
|
||||||
|
Important compatibility point:
|
||||||
|
|
||||||
|
- `/run/current-system` still points directly at the active closure in `/frx/store`
|
||||||
|
|
||||||
|
That means the new explicit generation model strengthens deployment metadata without changing the already-validated runtime contract used by activation, rc.d integration, and service startup.
|
||||||
|
|
||||||
|
## Code changes
|
||||||
|
|
||||||
|
### `modules/fruix/system/freebsd/media.scm`
|
||||||
|
|
||||||
|
Added explicit generation-layout helpers:
|
||||||
|
|
||||||
|
- generation metadata object writer
|
||||||
|
- generation provenance object writer
|
||||||
|
- generation layout population for staged rootfs trees
|
||||||
|
|
||||||
|
The system rootfs staging path now creates explicit generation state during rootfs population.
|
||||||
|
|
||||||
|
That affects:
|
||||||
|
|
||||||
|
- direct rootfs materialization
|
||||||
|
- direct image materialization
|
||||||
|
- direct installation targets
|
||||||
|
- target rootfs payloads staged inside installer images
|
||||||
|
- target rootfs payloads staged inside installer ISOs
|
||||||
|
|
||||||
|
The direct install path now also refreshes the generation layout after writing:
|
||||||
|
|
||||||
|
- `/var/lib/fruix/install.scm`
|
||||||
|
|
||||||
|
so the generation directory carries the same install metadata.
|
||||||
|
|
||||||
|
### Installer runtime path
|
||||||
|
|
||||||
|
The generated installer runtime script now also copies install metadata into:
|
||||||
|
|
||||||
|
- `/var/lib/fruix/system/generations/1/install.scm`
|
||||||
|
|
||||||
|
on the installed target.
|
||||||
|
|
||||||
|
This keeps direct-install and installer-mediated installs aligned.
|
||||||
|
|
||||||
|
## New validation harness
|
||||||
|
|
||||||
|
Added:
|
||||||
|
|
||||||
|
- `tests/system/run-phase19-generation-layout-qemu.sh`
|
||||||
|
|
||||||
|
This harness builds on the already-passing direct install validation from Phase 18.1 and then verifies the new explicit generation layout on the installed target image.
|
||||||
|
|
||||||
|
Passing validation:
|
||||||
|
|
||||||
|
- `PASS phase19-generation-layout-qemu`
|
||||||
|
|
||||||
|
Validated result summary:
|
||||||
|
|
||||||
|
```text
|
||||||
|
closure_path=/frx/store/882fb4a9fbb05f08e77de29f70ca50f3c01dd29141e72688d32770a3172747e7-fruix-system-fruix-freebsd
|
||||||
|
current_generation=1
|
||||||
|
current_link=generations/1
|
||||||
|
generation_closure=/frx/store/882fb4a9fbb05f08e77de29f70ca50f3c01dd29141e72688d32770a3172747e7-fruix-system-fruix-freebsd
|
||||||
|
gcroot_current=/frx/store/882fb4a9fbb05f08e77de29f70ca50f3c01dd29141e72688d32770a3172747e7-fruix-system-fruix-freebsd
|
||||||
|
gcroot_generation=/frx/store/882fb4a9fbb05f08e77de29f70ca50f3c01dd29141e72688d32770a3172747e7-fruix-system-fruix-freebsd
|
||||||
|
run_current_system_target=/frx/store/882fb4a9fbb05f08e77de29f70ca50f3c01dd29141e72688d32770a3172747e7-fruix-system-fruix-freebsd
|
||||||
|
generation_layout=explicit
|
||||||
|
generation_layout_validation=ok
|
||||||
|
```
|
||||||
|
|
||||||
|
The harness verified all of the following:
|
||||||
|
|
||||||
|
1. the installed target contains:
|
||||||
|
- `/var/lib/fruix/system`
|
||||||
|
2. the current generation pointer exists and resolves to:
|
||||||
|
- `generations/1`
|
||||||
|
3. the generation directory contains:
|
||||||
|
- `closure`
|
||||||
|
- `metadata.scm`
|
||||||
|
- `provenance.scm`
|
||||||
|
- `install.scm`
|
||||||
|
4. the generation closure link points at the installed closure in `/frx/store`
|
||||||
|
5. the generation metadata records:
|
||||||
|
- closure path
|
||||||
|
- install metadata path
|
||||||
|
6. the generation install metadata records:
|
||||||
|
- closure path
|
||||||
|
- materialized source provenance
|
||||||
|
7. explicit retention roots exist under:
|
||||||
|
- `/frx/var/fruix/gcroots/current-system`
|
||||||
|
- `/frx/var/fruix/gcroots/system-1`
|
||||||
|
8. those GC-root-style links point at the same active closure
|
||||||
|
9. `/run/current-system` still points directly at the active closure path
|
||||||
|
10. existing install boot validation remains intact
|
||||||
|
|
||||||
|
## Relationship to Guix
|
||||||
|
|
||||||
|
Fruix now has an explicit installed-system generation model, but it is still intentionally not a byte-for-byte clone of Guix's on-disk conventions.
|
||||||
|
|
||||||
|
The design choice is:
|
||||||
|
|
||||||
|
- preserve Guix's deployment semantics
|
||||||
|
- use a Fruix-native metadata-oriented representation where that improves clarity for operators and debugging
|
||||||
|
|
||||||
|
That decision is documented separately in:
|
||||||
|
|
||||||
|
- `docs/GUIX_DIFFERENCES.md`
|
||||||
|
|
||||||
|
## Current limitations
|
||||||
|
|
||||||
|
This phase does **not** yet add:
|
||||||
|
|
||||||
|
- multi-generation switching in place
|
||||||
|
- a `switch`/`reconfigure` command
|
||||||
|
- an operator-facing rollback command that flips from current to a previous installed generation without redeploy
|
||||||
|
- explicit `rollback` link management beyond the initial current-generation layout
|
||||||
|
|
||||||
|
Those belong to later Phase 19 work.
|
||||||
|
|
||||||
|
## Conclusion
|
||||||
|
|
||||||
|
Phase 19.2 is complete.
|
||||||
|
|
||||||
|
Fruix now has a clearer, explicit installed-system generation and retention-root model on FreeBSD:
|
||||||
|
|
||||||
|
- generation metadata is recorded under `/var/lib/fruix/system`
|
||||||
|
- retention links are recorded under `/frx/var/fruix/gcroots`
|
||||||
|
- `/run/current-system` remains stable as the runtime boundary
|
||||||
|
- and the model is documented in Fruix-native terms for Guix-familiar operators
|
||||||
226
docs/reports/phase19-installed-system-rollback-freebsd.md
Normal file
226
docs/reports/phase19-installed-system-rollback-freebsd.md
Normal file
@@ -0,0 +1,226 @@
|
|||||||
|
# Phase 19.3: installed-system rollback workflow on FreeBSD
|
||||||
|
|
||||||
|
Date: 2026-04-04
|
||||||
|
|
||||||
|
## Goal
|
||||||
|
|
||||||
|
Phase 19.3 is about validating installed-system rollback through the intended operator-facing workflow, not only through host-side build/image redeploy harnesses.
|
||||||
|
|
||||||
|
The key question was:
|
||||||
|
|
||||||
|
- can an already-installed Fruix system move between recorded generations coherently, using an operator-facing command surface on the target itself?
|
||||||
|
|
||||||
|
## Decision
|
||||||
|
|
||||||
|
The current Fruix solution is intentionally modest.
|
||||||
|
|
||||||
|
Fruix now provides a small installed-system helper on the target itself:
|
||||||
|
|
||||||
|
- `/usr/local/bin/fruix`
|
||||||
|
|
||||||
|
Validated in-guest commands:
|
||||||
|
|
||||||
|
- `fruix system status`
|
||||||
|
- `fruix system switch /frx/store/...-fruix-system-...`
|
||||||
|
- `fruix system rollback`
|
||||||
|
|
||||||
|
Important scope choice:
|
||||||
|
|
||||||
|
- `switch` assumes the candidate closure is already present on the target's `/frx/store`
|
||||||
|
- Fruix does **not** yet fetch or transfer that closure onto the target automatically
|
||||||
|
|
||||||
|
That keeps Phase 19.3 focused on generation-state correctness rather than introducing a larger store-transfer story prematurely.
|
||||||
|
|
||||||
|
## Implemented model
|
||||||
|
|
||||||
|
Installed systems now support the following validated operator pattern:
|
||||||
|
|
||||||
|
1. build a candidate closure with the host-side Fruix frontend
|
||||||
|
2. stage that closure into the installed system's `/frx/store`
|
||||||
|
3. run:
|
||||||
|
- `fruix system switch /frx/store/...candidate...`
|
||||||
|
4. reboot into the candidate generation
|
||||||
|
5. if needed, run:
|
||||||
|
- `fruix system rollback`
|
||||||
|
6. reboot into the recorded rollback generation
|
||||||
|
|
||||||
|
The installed system now records explicit rollback state under:
|
||||||
|
|
||||||
|
```text
|
||||||
|
/var/lib/fruix/system/
|
||||||
|
current -> generations/N
|
||||||
|
current-generation
|
||||||
|
rollback -> generations/M
|
||||||
|
rollback-generation
|
||||||
|
generations/
|
||||||
|
...
|
||||||
|
```
|
||||||
|
|
||||||
|
and explicit rollback reachability under:
|
||||||
|
|
||||||
|
```text
|
||||||
|
/frx/var/fruix/gcroots/
|
||||||
|
current-system -> /frx/store/...current...
|
||||||
|
rollback-system -> /frx/store/...rollback...
|
||||||
|
system-1 -> ...
|
||||||
|
system-2 -> ...
|
||||||
|
```
|
||||||
|
|
||||||
|
## Code changes
|
||||||
|
|
||||||
|
### `modules/fruix/system/freebsd/render.scm`
|
||||||
|
|
||||||
|
Added a generated in-guest Fruix deployment helper script under:
|
||||||
|
|
||||||
|
- `usr/local/bin/fruix`
|
||||||
|
|
||||||
|
That helper now:
|
||||||
|
|
||||||
|
- reports installed-system state with `fruix system status`
|
||||||
|
- stages a new current generation with `fruix system switch`
|
||||||
|
- stages the recorded rollback generation with `fruix system rollback`
|
||||||
|
- updates:
|
||||||
|
- `/var/lib/fruix/system/current`
|
||||||
|
- `/var/lib/fruix/system/current-generation`
|
||||||
|
- `/var/lib/fruix/system/rollback`
|
||||||
|
- `/var/lib/fruix/system/rollback-generation`
|
||||||
|
- `/frx/var/fruix/gcroots/current-system`
|
||||||
|
- `/frx/var/fruix/gcroots/rollback-system`
|
||||||
|
- `/frx/var/fruix/gcroots/system-N`
|
||||||
|
- refreshes the ESP bootloader file from the selected closure's `boot/loader.efi`
|
||||||
|
|
||||||
|
A practical implementation detail mattered here:
|
||||||
|
|
||||||
|
- replacing `/run/current-system` with a remove-then-recreate strategy caused the live shell environment to break while the link was absent
|
||||||
|
- switching that update to an atomic symlink replacement path for `/run/current-system` avoided that gap and made the in-guest operator command reliable
|
||||||
|
|
||||||
|
### `modules/fruix/system/freebsd/media.scm`
|
||||||
|
|
||||||
|
Updated installed rootfs staging so that installed targets expose:
|
||||||
|
|
||||||
|
- `/usr/local/bin/fruix -> /run/current-system/usr/local/bin/fruix`
|
||||||
|
|
||||||
|
Also bumped the explicit generation-layout version from:
|
||||||
|
|
||||||
|
- `1` to `2`
|
||||||
|
|
||||||
|
because the installed-system model now includes operator-driven switch/rollback state as part of the validated layout story.
|
||||||
|
|
||||||
|
### `modules/fruix/system/freebsd/model.scm`
|
||||||
|
|
||||||
|
Updated generated-file metadata so the system closure records:
|
||||||
|
|
||||||
|
- `usr/local/bin/fruix`
|
||||||
|
|
||||||
|
as part of the generated operating-system file set.
|
||||||
|
|
||||||
|
## New validation harness
|
||||||
|
|
||||||
|
Added:
|
||||||
|
|
||||||
|
- `tests/system/run-phase19-installed-system-rollback-qemu.sh`
|
||||||
|
|
||||||
|
This harness validates the actual installed-system operator flow on local `QEMU/UEFI/TCG`.
|
||||||
|
|
||||||
|
## Validation flow
|
||||||
|
|
||||||
|
The harness now performs all of the following:
|
||||||
|
|
||||||
|
1. installs a current system image directly to a target disk image
|
||||||
|
2. builds a distinct candidate closure
|
||||||
|
- in the validated harness this differs by host name so the closure identity changes cleanly without needing a heavier base-version rebuild
|
||||||
|
3. stages the candidate closure and its referenced store items into the installed target's `/frx/store`
|
||||||
|
4. boots the installed current system
|
||||||
|
5. validates initial state:
|
||||||
|
- current generation = `1`
|
||||||
|
- current closure = current installed closure
|
||||||
|
- no rollback generation yet recorded
|
||||||
|
6. runs:
|
||||||
|
- `fruix system switch /frx/store/...candidate...`
|
||||||
|
7. validates staged switch state:
|
||||||
|
- current generation = `2`
|
||||||
|
- rollback generation = `1`
|
||||||
|
- current closure = candidate closure
|
||||||
|
- rollback closure = original current closure
|
||||||
|
- generation 2 metadata/install files were written
|
||||||
|
8. reboots and validates boot into the candidate closure
|
||||||
|
9. runs:
|
||||||
|
- `fruix system rollback`
|
||||||
|
10. validates staged rollback state:
|
||||||
|
- current generation = `1`
|
||||||
|
- rollback generation = `2`
|
||||||
|
- current closure = original closure
|
||||||
|
- rollback closure = candidate closure
|
||||||
|
11. reboots and validates boot back into the original current system
|
||||||
|
12. confirms post-rollback service state:
|
||||||
|
- `fruix-shepherd` running
|
||||||
|
- `sshd` running
|
||||||
|
- activation log still shows success
|
||||||
|
|
||||||
|
## Passing validation
|
||||||
|
|
||||||
|
Passing result:
|
||||||
|
|
||||||
|
- `PASS phase19-installed-system-rollback-qemu`
|
||||||
|
|
||||||
|
Validated metadata summary:
|
||||||
|
|
||||||
|
```text
|
||||||
|
current_closure_path=/frx/store/4debd106d62f14594ba1612e1e7105f1658bf5f4075d6e5db5436efeaf929d90-fruix-system-fruix-freebsd-current
|
||||||
|
candidate_closure_path=/frx/store/54fb14e6071b8e5704a5dc75e2881c2f0533767771c26c4181f57afea88d1e8b-fruix-system-fruix-freebsd-canary
|
||||||
|
current_host_name=fruix-freebsd-current
|
||||||
|
candidate_host_name=fruix-freebsd-canary
|
||||||
|
final_current_generation=1
|
||||||
|
final_current_closure=/frx/store/4debd106d62f14594ba1612e1e7105f1658bf5f4075d6e5db5436efeaf929d90-fruix-system-fruix-freebsd-current
|
||||||
|
final_rollback_generation=2
|
||||||
|
final_rollback_closure=/frx/store/54fb14e6071b8e5704a5dc75e2881c2f0533767771c26c4181f57afea88d1e8b-fruix-system-fruix-freebsd-canary
|
||||||
|
installed_system_switch=ok
|
||||||
|
installed_system_rollback=ok
|
||||||
|
```
|
||||||
|
|
||||||
|
## Regression checks
|
||||||
|
|
||||||
|
After landing the installed-system switch/rollback workflow, the following regression checks still pass:
|
||||||
|
|
||||||
|
- `PASS phase19-generation-layout-qemu`
|
||||||
|
- `PASS phase18-installer-iso`
|
||||||
|
|
||||||
|
That means the new in-guest generation-management path did not regress:
|
||||||
|
|
||||||
|
- the previously validated explicit generation layout
|
||||||
|
- or the UEFI installer ISO boot/install path
|
||||||
|
|
||||||
|
## Relationship to Guix
|
||||||
|
|
||||||
|
This phase does **not** claim that Fruix now matches Guix's full installed-system UX.
|
||||||
|
|
||||||
|
What Fruix now has is:
|
||||||
|
|
||||||
|
- explicit generation state on disk
|
||||||
|
- explicit current/rollback pointers
|
||||||
|
- a minimal installed-system operator command surface
|
||||||
|
- validated switching and rollback between already-staged closures
|
||||||
|
|
||||||
|
What still remains compared with Guix:
|
||||||
|
|
||||||
|
- building/staging the candidate closure from inside the target system itself
|
||||||
|
- automatic closure transfer/fetch as part of `switch`
|
||||||
|
- a richer long-term generation lifecycle policy
|
||||||
|
|
||||||
|
## Conclusion
|
||||||
|
|
||||||
|
Phase 19.3 is complete.
|
||||||
|
|
||||||
|
Fruix now validates an actual installed-system rollback workflow on FreeBSD:
|
||||||
|
|
||||||
|
- the target system itself can report current/rollback state
|
||||||
|
- it can switch to a staged candidate generation
|
||||||
|
- it can reboot into that candidate generation
|
||||||
|
- it can roll back to the recorded prior generation
|
||||||
|
- and it can reboot into the restored current system
|
||||||
|
|
||||||
|
That closes the Phase 19 deployment story from:
|
||||||
|
|
||||||
|
- documented deployment workflow
|
||||||
|
- to explicit generation layout
|
||||||
|
- to validated installed-system operator rollback behavior
|
||||||
160
docs/reports/phase20-development-environment-freebsd.md
Normal file
160
docs/reports/phase20-development-environment-freebsd.md
Normal file
@@ -0,0 +1,160 @@
|
|||||||
|
# Phase 20.1: Fruix-managed development environment for native FreeBSD base work
|
||||||
|
|
||||||
|
Date: 2026-04-05
|
||||||
|
|
||||||
|
## Goal
|
||||||
|
|
||||||
|
Validate that a booted Fruix-managed FreeBSD system can expose a usable development environment for deeper native base work without collapsing the runtime/development boundary back into one broad profile.
|
||||||
|
|
||||||
|
This step explicitly builds on the Phase 14 split between:
|
||||||
|
|
||||||
|
- native runtime/boot artifacts for the running system
|
||||||
|
- separate development-facing artifacts such as headers and toolchain pieces
|
||||||
|
|
||||||
|
The goal is **not** full self-hosting yet.
|
||||||
|
|
||||||
|
It is to prove that a running Fruix system can expose the tools and paths needed for native FreeBSD build work in a controlled way.
|
||||||
|
|
||||||
|
## Implementation
|
||||||
|
|
||||||
|
### New operating-system field
|
||||||
|
|
||||||
|
`modules/fruix/system/freebsd/model.scm` now supports:
|
||||||
|
|
||||||
|
- `#:development-packages`
|
||||||
|
- `operating-system-development-packages`
|
||||||
|
|
||||||
|
The default remains empty, so existing systems do not change unless they opt in.
|
||||||
|
|
||||||
|
### Separate development profile inside the system closure
|
||||||
|
|
||||||
|
`modules/fruix/system/freebsd/media.scm` now materializes an additional profile tree when `development-packages` is non-empty:
|
||||||
|
|
||||||
|
- `/frx/store/...-fruix-system-.../development-profile`
|
||||||
|
|
||||||
|
The main runtime tree remains:
|
||||||
|
|
||||||
|
- `/frx/store/...-fruix-system-.../profile`
|
||||||
|
|
||||||
|
This preserves the runtime/development split:
|
||||||
|
|
||||||
|
- runtime stays under `profile`
|
||||||
|
- development tooling stays under `development-profile`
|
||||||
|
|
||||||
|
The development stores are also now part of the closure references and recorded in `metadata/store-layout.scm`.
|
||||||
|
|
||||||
|
### In-guest development environment helper
|
||||||
|
|
||||||
|
Opt-in systems with development packages now ship:
|
||||||
|
|
||||||
|
- `/usr/local/bin/fruix-development-environment`
|
||||||
|
|
||||||
|
and expose a stable runtime link:
|
||||||
|
|
||||||
|
- `/run/current-development -> /run/current-system/development-profile`
|
||||||
|
|
||||||
|
The helper emits shell exports for the active development profile, including at least:
|
||||||
|
|
||||||
|
- `FRUIX_DEVELOPMENT_PROFILE`
|
||||||
|
- `FRUIX_DEVELOPMENT_INCLUDE`
|
||||||
|
- `FRUIX_DEVELOPMENT_SHARE_MK`
|
||||||
|
- `FRUIX_CC`
|
||||||
|
- `FRUIX_CXX`
|
||||||
|
- `FRUIX_AR`
|
||||||
|
- `FRUIX_RANLIB`
|
||||||
|
- `FRUIX_NM`
|
||||||
|
- `FRUIX_BMAKE`
|
||||||
|
- `CPPFLAGS`
|
||||||
|
- `MAKEFLAGS`
|
||||||
|
- `PATH`
|
||||||
|
|
||||||
|
Intended use:
|
||||||
|
|
||||||
|
```sh
|
||||||
|
eval "$(/usr/local/bin/fruix-development-environment)"
|
||||||
|
```
|
||||||
|
|
||||||
|
### Chosen development overlay for this phase
|
||||||
|
|
||||||
|
For the validated Phase 20.1 guest path, the development overlay is intentionally narrow:
|
||||||
|
|
||||||
|
- `freebsd-native-headers`
|
||||||
|
- `freebsd-clang-toolchain`
|
||||||
|
|
||||||
|
This was chosen deliberately.
|
||||||
|
|
||||||
|
The earlier standalone Phase 14 development-profile package set was designed for broad profile composition, but for a running Fruix system it unnecessarily duplicated runtime pieces already present in the system profile.
|
||||||
|
|
||||||
|
For native base work, the important additions here are:
|
||||||
|
|
||||||
|
- `usr/include`
|
||||||
|
- `usr/share/mk`
|
||||||
|
- Clang/binutils-style frontend tools
|
||||||
|
|
||||||
|
while the running system already supplies:
|
||||||
|
|
||||||
|
- base runtime
|
||||||
|
- `/usr/bin/make`
|
||||||
|
- system libraries and boot/runtime state
|
||||||
|
|
||||||
|
That keeps the Phase 20.1 environment focused on native base development rather than reintroducing a broad mixed profile.
|
||||||
|
|
||||||
|
## New files
|
||||||
|
|
||||||
|
Added:
|
||||||
|
|
||||||
|
- `tests/system/phase20-development-operating-system.scm.in`
|
||||||
|
- `tests/system/run-phase20-development-environment-xcpng.sh`
|
||||||
|
|
||||||
|
## Validation
|
||||||
|
|
||||||
|
Passing run:
|
||||||
|
|
||||||
|
- `PASS phase20-development-environment-xcpng`
|
||||||
|
- workdir: `/tmp/fruix-phase20-development-xcpng`
|
||||||
|
|
||||||
|
Validated on the approved real XCP-ng path:
|
||||||
|
|
||||||
|
- VM `90490f2e-e8fc-4b7a-388e-5c26f0157289`
|
||||||
|
- VDI `0f1f90d3-48ca-4fa2-91d8-fc6339b95743`
|
||||||
|
|
||||||
|
Representative result:
|
||||||
|
|
||||||
|
```text
|
||||||
|
closure_path=/frx/store/c0ad43d7ef72323d4270a4f1e96ca1f5cc99566c-fruix-system-fruix-freebsd
|
||||||
|
development_profile_path=/frx/store/c0ad43d7ef72323d4270a4f1e96ca1f5cc99566c-fruix-system-fruix-freebsd/development-profile
|
||||||
|
development_profile_guest=/run/current-system/development-profile
|
||||||
|
cc_version=FreeBSD clang version 19.1.7 (https://github.com/llvm/llvm-project.git llvmorg-19.1.7-0-gcd708029e0b2)
|
||||||
|
hello_direct=hello-from-direct-dev-env
|
||||||
|
hello_make=hello-from-make-dev-env
|
||||||
|
development_environment=ok
|
||||||
|
```
|
||||||
|
|
||||||
|
The harness verified all of the following on the real booted Fruix guest:
|
||||||
|
|
||||||
|
- runtime boot/regression still succeeds on XCP-ng
|
||||||
|
- the closure contains a separate `development-profile`
|
||||||
|
- runtime `profile` does **not** regain headers or `usr/share/mk`
|
||||||
|
- `/usr/local/bin/fruix-development-environment` exists and emits the expected exports
|
||||||
|
- `/run/current-development` points at `/run/current-system/development-profile`
|
||||||
|
- direct compilation works with the exported Clang toolchain and headers
|
||||||
|
- a simple native FreeBSD `make` build using `.include <bsd.prog.mk>` also succeeds
|
||||||
|
|
||||||
|
## Result
|
||||||
|
|
||||||
|
Phase 20.1 is complete.
|
||||||
|
|
||||||
|
Fruix now validates a real booted FreeBSD system path where:
|
||||||
|
|
||||||
|
- the running system remains lean and runtime-focused
|
||||||
|
- native development artifacts are exposed separately and explicitly
|
||||||
|
- the guest can compile code directly with the Fruix-provided toolchain
|
||||||
|
- the guest can also drive a simple `bsd.prog.mk` build using the exported development environment
|
||||||
|
|
||||||
|
This is enough to say that Fruix can host a controlled native FreeBSD base-development environment, without yet claiming full self-hosting.
|
||||||
|
|
||||||
|
## Next step
|
||||||
|
|
||||||
|
Per `docs/PLAN_4.md`, the next planned step is:
|
||||||
|
|
||||||
|
- **Phase 20.2** — run host-initiated native base builds inside a Fruix-managed environment
|
||||||
169
docs/reports/phase20-host-initiated-native-builds-freebsd.md
Normal file
169
docs/reports/phase20-host-initiated-native-builds-freebsd.md
Normal file
@@ -0,0 +1,169 @@
|
|||||||
|
# Phase 20.2: host-initiated native base builds inside a Fruix-managed environment
|
||||||
|
|
||||||
|
Date: 2026-04-05
|
||||||
|
|
||||||
|
## Goal
|
||||||
|
|
||||||
|
Validate the next step after Phase 20.1:
|
||||||
|
|
||||||
|
- the host still orchestrates the outer loop
|
||||||
|
- the actual FreeBSD native base build work runs inside a booted Fruix-managed system
|
||||||
|
|
||||||
|
This is the intermediate path between:
|
||||||
|
|
||||||
|
- purely host-side native base builds
|
||||||
|
- any future claim of guest self-hosting
|
||||||
|
|
||||||
|
The target here was not a new self-hosted package manager story.
|
||||||
|
|
||||||
|
It was narrower:
|
||||||
|
|
||||||
|
- boot a Fruix-managed FreeBSD system on the approved real XCP-ng path
|
||||||
|
- expose the development environment required for native base work
|
||||||
|
- run real `buildworld` / `buildkernel` / staged install steps inside that Fruix guest
|
||||||
|
- confirm that the resulting staged artifacts are the expected FreeBSD base slices
|
||||||
|
|
||||||
|
## Implementation
|
||||||
|
|
||||||
|
### Canonical development-path compatibility links
|
||||||
|
|
||||||
|
Phase 20.1 proved that Fruix could keep development content separate in:
|
||||||
|
|
||||||
|
- `/run/current-system/development-profile`
|
||||||
|
- `/run/current-development`
|
||||||
|
|
||||||
|
Phase 20.2 exposed an additional practical requirement:
|
||||||
|
|
||||||
|
- FreeBSD native base builds still expect canonical system paths such as:
|
||||||
|
- `/usr/include`
|
||||||
|
- `/usr/share/mk`
|
||||||
|
|
||||||
|
For development-enabled systems, `populate-rootfs-from-closure` now also exposes:
|
||||||
|
|
||||||
|
- `/usr/include -> /run/current-system/development-profile/usr/include`
|
||||||
|
- `/usr/share/mk -> /run/current-system/development-profile/usr/share/mk`
|
||||||
|
|
||||||
|
This keeps the development profile separate while still satisfying the buildworld/buildkernel assumptions of the native FreeBSD build system.
|
||||||
|
|
||||||
|
### Media builder invalidation
|
||||||
|
|
||||||
|
Because this changed the visible rootfs layout of booted systems, the media builder versions were bumped in `modules/fruix/system/freebsd/media.scm`:
|
||||||
|
|
||||||
|
- `image-builder-version`
|
||||||
|
- `install-builder-version`
|
||||||
|
- `installer-image-builder-version`
|
||||||
|
- `installer-iso-builder-version`
|
||||||
|
|
||||||
|
That ensured booted images and future installed targets actually pick up the new compatibility links.
|
||||||
|
|
||||||
|
### New validation harness
|
||||||
|
|
||||||
|
Added:
|
||||||
|
|
||||||
|
- `tests/system/run-phase20-host-initiated-native-build-xcpng.sh`
|
||||||
|
|
||||||
|
This harness reuses the validated Phase 20.1 XCP-ng path first, then performs the 20.2-native-build step over SSH from the host.
|
||||||
|
|
||||||
|
The guest build flow is:
|
||||||
|
|
||||||
|
1. boot the development-enabled Fruix guest on XCP-ng
|
||||||
|
2. recover the materialized source store from `/run/current-system/metadata/store-layout.scm`
|
||||||
|
3. run real FreeBSD native build commands inside the guest:
|
||||||
|
- `make -j8 buildworld`
|
||||||
|
- `make -j8 buildkernel`
|
||||||
|
- `make DESTDIR=... installworld`
|
||||||
|
- `make DESTDIR=... distribution`
|
||||||
|
- `make DESTDIR=... installkernel`
|
||||||
|
4. stage narrower artifact slices from the staged output:
|
||||||
|
- headers slice
|
||||||
|
- bootloader slice
|
||||||
|
- kernel stage
|
||||||
|
|
||||||
|
### Why `DB_FROM_SRC=yes` is used for staged install steps
|
||||||
|
|
||||||
|
The development-enabled Fruix guest is intentionally lean and does not carry the full ambient host account database.
|
||||||
|
|
||||||
|
`installworld` on modern FreeBSD checks for required users/groups unless `DB_FROM_SRC` is defined. For staged installs into `DESTDIR`, the appropriate controlled input is the source tree's own account database under `etc/`, not the minimal running guest's `/etc/master.passwd`.
|
||||||
|
|
||||||
|
So the validated Phase 20.2 staged install path uses:
|
||||||
|
|
||||||
|
- `DB_FROM_SRC=yes`
|
||||||
|
|
||||||
|
for:
|
||||||
|
|
||||||
|
- `installworld`
|
||||||
|
- `distribution`
|
||||||
|
- `installkernel`
|
||||||
|
|
||||||
|
That keeps the staged install driven by the declared source input rather than by accidental guest-local account state.
|
||||||
|
|
||||||
|
## Validation
|
||||||
|
|
||||||
|
Passing run:
|
||||||
|
|
||||||
|
- `PASS phase20-host-initiated-native-build-xcpng`
|
||||||
|
- workdir: `/tmp/fruix-phase20-host-initiated-native-build-xcpng`
|
||||||
|
|
||||||
|
Validated on the approved real XCP-ng path:
|
||||||
|
|
||||||
|
- VM `90490f2e-e8fc-4b7a-388e-5c26f0157289`
|
||||||
|
- VDI `0f1f90d3-48ca-4fa2-91d8-fc6339b95743`
|
||||||
|
|
||||||
|
Representative result:
|
||||||
|
|
||||||
|
```text
|
||||||
|
build_jobs=8
|
||||||
|
source_store=/frx/store/12d7704362e95afc2697db63f168b878e082b372-freebsd-source-default
|
||||||
|
source_root=/frx/store/12d7704362e95afc2697db63f168b878e082b372-freebsd-source-default/tree
|
||||||
|
build_root=/var/tmp/fruix-phase20-native-build
|
||||||
|
world_stage=/var/tmp/fruix-phase20-native-build/stage-world
|
||||||
|
kernel_stage=/var/tmp/fruix-phase20-native-build/stage-kernel
|
||||||
|
headers_stage=/var/tmp/fruix-phase20-native-build/artifact-headers
|
||||||
|
bootloader_stage=/var/tmp/fruix-phase20-native-build/artifact-bootloader
|
||||||
|
build_root_size=7.6G
|
||||||
|
world_stage_size=672M
|
||||||
|
kernel_stage_size=739M
|
||||||
|
headers_stage_size=32M
|
||||||
|
bootloader_stage_size=1.3M
|
||||||
|
sha_kernel=16950f116a52134b98e2f8e0dacc556e18fe254e4a0ac2c1741422dde281a341
|
||||||
|
sha_loader=ea417846167ece270ada611624dca622ca38bd30125b9a125cd8ebb8b3600313
|
||||||
|
sha_param=9eb140ca7d9666f3d484a4174c9acd94b45427db6292b4e17de19af2c6aa5219
|
||||||
|
host_initiated_native_build=ok
|
||||||
|
```
|
||||||
|
|
||||||
|
The harness verified all of the following:
|
||||||
|
|
||||||
|
- the guest still boots and passes the Phase 20.1 development-environment checks first
|
||||||
|
- development-enabled systems expose canonical native-build compatibility links at:
|
||||||
|
- `/usr/include`
|
||||||
|
- `/usr/share/mk`
|
||||||
|
- the guest can recover the declared materialized FreeBSD source store from system metadata
|
||||||
|
- real FreeBSD `buildworld` succeeds inside the booted Fruix guest
|
||||||
|
- real FreeBSD `buildkernel` succeeds inside the booted Fruix guest
|
||||||
|
- staged `installworld`, `distribution`, and `installkernel` also succeed inside the guest
|
||||||
|
- the staged outputs contain the expected artifact shapes:
|
||||||
|
- `boot/kernel/kernel`
|
||||||
|
- `usr/include/sys/param.h`
|
||||||
|
- `usr/share/mk/bsd.prog.mk`
|
||||||
|
- `boot/loader.efi`
|
||||||
|
- `boot/defaults/loader.conf`
|
||||||
|
- `boot/lua/loader.lua`
|
||||||
|
|
||||||
|
## Result
|
||||||
|
|
||||||
|
Phase 20.2 is complete.
|
||||||
|
|
||||||
|
Fruix now validates a real host-orchestrated path where:
|
||||||
|
|
||||||
|
- the host boots and reaches a Fruix-managed development-enabled guest
|
||||||
|
- the guest uses its own Fruix-exposed development paths and declared source store
|
||||||
|
- the native FreeBSD base build work runs inside that Fruix-managed environment
|
||||||
|
- the host remains the outer orchestrator and result collector
|
||||||
|
|
||||||
|
This materially narrows the gap to any future self-hosting experiment while still avoiding the complexity jump to a full guest-driven package/deployment loop.
|
||||||
|
|
||||||
|
## Next step
|
||||||
|
|
||||||
|
Per `docs/PLAN_4.md`, the next planned step is:
|
||||||
|
|
||||||
|
- **Phase 20.3** — reassess and potentially prototype guest self-hosted base builds
|
||||||
202
docs/reports/phase20-native-build-executor-model-freebsd.md
Normal file
202
docs/reports/phase20-native-build-executor-model-freebsd.md
Normal file
@@ -0,0 +1,202 @@
|
|||||||
|
# Post-Phase 20: native build executor model
|
||||||
|
|
||||||
|
Date: 2026-04-06
|
||||||
|
|
||||||
|
## Goal
|
||||||
|
|
||||||
|
Turn the now-proven native base-build placement options into one Fruix abstraction instead of treating them as unrelated paths.
|
||||||
|
|
||||||
|
The desired model is:
|
||||||
|
|
||||||
|
- same declared source identity
|
||||||
|
- same expected artifact kinds
|
||||||
|
- same staged-result shape
|
||||||
|
- same promotion/provenance shape
|
||||||
|
- different executor policy
|
||||||
|
|
||||||
|
So the question becomes:
|
||||||
|
|
||||||
|
- where should the build run?
|
||||||
|
|
||||||
|
and the answer is expressed as executor policy rather than as a separate architecture each time.
|
||||||
|
|
||||||
|
## Executor model
|
||||||
|
|
||||||
|
Fruix now has an explicit native-build executor model with current executor kinds:
|
||||||
|
|
||||||
|
- `host`
|
||||||
|
- `ssh-guest`
|
||||||
|
- `self-hosted`
|
||||||
|
|
||||||
|
and intended future extension points:
|
||||||
|
|
||||||
|
- `jail`
|
||||||
|
- `remote-builder`
|
||||||
|
|
||||||
|
The new executor model is implemented in:
|
||||||
|
|
||||||
|
- `modules/fruix/system/freebsd/executor.scm`
|
||||||
|
|
||||||
|
It defines a structured executor object that records at least:
|
||||||
|
|
||||||
|
- `kind`
|
||||||
|
- `name`
|
||||||
|
- `version`
|
||||||
|
- `properties`
|
||||||
|
|
||||||
|
## What changed
|
||||||
|
|
||||||
|
### 1. Structured executor metadata
|
||||||
|
|
||||||
|
Native-build result metadata is no longer limited to a flat string such as:
|
||||||
|
|
||||||
|
- `guest-self-hosted`
|
||||||
|
|
||||||
|
Instead, result/promotion objects now carry a structured executor description.
|
||||||
|
|
||||||
|
Representative executor objects now look like:
|
||||||
|
|
||||||
|
```scheme
|
||||||
|
((kind . self-hosted)
|
||||||
|
(name . "guest-self-hosted")
|
||||||
|
(version . "4")
|
||||||
|
(properties . (...)))
|
||||||
|
```
|
||||||
|
|
||||||
|
or:
|
||||||
|
|
||||||
|
```scheme
|
||||||
|
((kind . ssh-guest)
|
||||||
|
(name . "ssh-guest")
|
||||||
|
(version . "1")
|
||||||
|
(properties . (...)))
|
||||||
|
```
|
||||||
|
|
||||||
|
Promoted store metadata also now records compatibility fields derived from that executor object:
|
||||||
|
|
||||||
|
- `executor-kind`
|
||||||
|
- `executor-name`
|
||||||
|
- `executor-version`
|
||||||
|
|
||||||
|
### 2. Shared staged-result shape across executors
|
||||||
|
|
||||||
|
Both validated executor paths now converge on the same mutable staging layout under:
|
||||||
|
|
||||||
|
- `/var/lib/fruix/native-builds/<run-id>`
|
||||||
|
|
||||||
|
with promoted artifacts staged under:
|
||||||
|
|
||||||
|
- `artifacts/world`
|
||||||
|
- `artifacts/kernel`
|
||||||
|
- `artifacts/headers`
|
||||||
|
- `artifacts/bootloader`
|
||||||
|
|
||||||
|
and promotion metadata recorded in:
|
||||||
|
|
||||||
|
- `promotion.scm`
|
||||||
|
|
||||||
|
The heavy build work remains executor-specific, but the result shape is now shared.
|
||||||
|
|
||||||
|
### 3. Shared immutable promotion path
|
||||||
|
|
||||||
|
Both validated executor paths now promote through the same command:
|
||||||
|
|
||||||
|
```sh
|
||||||
|
fruix native-build promote RESULT_ROOT
|
||||||
|
```
|
||||||
|
|
||||||
|
That promotion creates immutable store identities for:
|
||||||
|
|
||||||
|
- `world`
|
||||||
|
- `kernel`
|
||||||
|
- `headers`
|
||||||
|
- `bootloader`
|
||||||
|
- result bundle
|
||||||
|
|
||||||
|
under `/frx/store/...`.
|
||||||
|
|
||||||
|
## Validated executor policies
|
||||||
|
|
||||||
|
### `self-hosted`
|
||||||
|
|
||||||
|
The self-hosted guest helper now emits the structured executor metadata directly from:
|
||||||
|
|
||||||
|
- `/usr/local/bin/fruix-self-hosted-native-build`
|
||||||
|
|
||||||
|
Validated self-hosted promotion flow:
|
||||||
|
|
||||||
|
- `PASS phase20-self-hosted-native-build-xcpng`
|
||||||
|
- `PASS phase20-native-build-store-promotion-xcpng`
|
||||||
|
|
||||||
|
Representative promoted result:
|
||||||
|
|
||||||
|
```text
|
||||||
|
result_store=/frx/store/0423193b9bd5e652bdb9d94d077e40dfcc3e9e78-fruix-native-build-result-15.0-STABLE-guest-self-hosted
|
||||||
|
world_store=/frx/store/5f67e95058186147206ff6f5da2243a09212e358-fruix-native-world-15.0-STABLE-guest-self-hosted
|
||||||
|
kernel_store=/frx/store/3a32797cc187a90e8273f205eababae6246568d9-fruix-native-kernel-15.0-STABLE-guest-self-hosted
|
||||||
|
headers_store=/frx/store/1d54d9814461f003e91add8cd37e94ac5f3d04ce-fruix-native-headers-15.0-STABLE-guest-self-hosted
|
||||||
|
bootloader_store=/frx/store/49f4885f0f05a324ac826f2618d4c7a923ca30d2-fruix-native-bootloader-15.0-STABLE-guest-self-hosted
|
||||||
|
```
|
||||||
|
|
||||||
|
### `ssh-guest`
|
||||||
|
|
||||||
|
The host-initiated guest path now also stages a shared native-build result root under:
|
||||||
|
|
||||||
|
- `/var/lib/fruix/native-builds/<run-id>`
|
||||||
|
|
||||||
|
instead of stopping at temporary build directories alone.
|
||||||
|
|
||||||
|
Its promotion metadata records executor policy as:
|
||||||
|
|
||||||
|
- `kind = ssh-guest`
|
||||||
|
- `name = ssh-guest`
|
||||||
|
- `version = 1`
|
||||||
|
|
||||||
|
with executor properties including:
|
||||||
|
|
||||||
|
- `transport = ssh`
|
||||||
|
- `orchestrator = host`
|
||||||
|
- guest addressing / VM identity metadata
|
||||||
|
|
||||||
|
Validated host-initiated promotion flow:
|
||||||
|
|
||||||
|
- `PASS phase20-host-initiated-native-build-xcpng`
|
||||||
|
- `PASS phase20-host-initiated-native-build-store-promotion-xcpng`
|
||||||
|
|
||||||
|
Representative promoted result:
|
||||||
|
|
||||||
|
```text
|
||||||
|
result_store=/frx/store/ffe44f5d1ba576e1f811ad3fe3a526a242b5c4a5-fruix-native-build-result-15.0-STABLE-ssh-guest
|
||||||
|
world_store=/frx/store/89c7a71c3df148a1f99b13d57fd6be88243eb2cb-fruix-native-world-15.0-STABLE-ssh-guest
|
||||||
|
kernel_store=/frx/store/93bac81122022b40438d356146a6854b4ee48513-fruix-native-kernel-15.0-STABLE-ssh-guest
|
||||||
|
headers_store=/frx/store/dd7f39f526bca4849caf1eaf96ae25d29b43493c-fruix-native-headers-15.0-STABLE-ssh-guest
|
||||||
|
bootloader_store=/frx/store/78b1c6b0b5c0c2c1549f5f42f3d64b6d9293669b-fruix-native-bootloader-15.0-STABLE-ssh-guest
|
||||||
|
```
|
||||||
|
|
||||||
|
## Important result
|
||||||
|
|
||||||
|
The same declared source and artifact contract now works across two different executor policies:
|
||||||
|
|
||||||
|
- `ssh-guest`
|
||||||
|
- `self-hosted`
|
||||||
|
|
||||||
|
while preserving the same Fruix-native staging/promotion split:
|
||||||
|
|
||||||
|
- mutable result roots under `/var/lib/fruix/native-builds/...`
|
||||||
|
- immutable promoted identities under `/frx/store/...`
|
||||||
|
|
||||||
|
That is the core architectural win of the executor model.
|
||||||
|
|
||||||
|
## What is not yet fully unified
|
||||||
|
|
||||||
|
The `host` executor kind now exists in the model, but the fully shared staged-result-plus-promotion workflow is not yet wired through the existing host-local native build path.
|
||||||
|
|
||||||
|
So the executor model is introduced and real-VM validated across two policies, but not yet uniformly productized across every native-build entry point.
|
||||||
|
|
||||||
|
## Result
|
||||||
|
|
||||||
|
Fruix now treats native-build placement as executor policy rather than as an architectural fork.
|
||||||
|
|
||||||
|
That means the next step is no longer to invent another build path from scratch.
|
||||||
|
|
||||||
|
It is to keep extending the same executor/result/promotion model so additional execution policies can plug into the same Fruix-native object story.
|
||||||
190
docs/reports/phase20-native-build-store-promotion-freebsd.md
Normal file
190
docs/reports/phase20-native-build-store-promotion-freebsd.md
Normal file
@@ -0,0 +1,190 @@
|
|||||||
|
# Post-Phase 20: native build result promotion into first-class Fruix store objects
|
||||||
|
|
||||||
|
Date: 2026-04-05
|
||||||
|
|
||||||
|
## Goal
|
||||||
|
|
||||||
|
Make native FreeBSD base-build results feel like real Fruix objects instead of stopping at mutable staged files under:
|
||||||
|
|
||||||
|
- `/var/lib/fruix/native-builds/...`
|
||||||
|
|
||||||
|
The desired model is:
|
||||||
|
|
||||||
|
- `/var/lib/fruix/native-builds/...` remains a staging/result area
|
||||||
|
- `/frx/store/...` remains the real immutable identity
|
||||||
|
|
||||||
|
Validated artifact identities:
|
||||||
|
|
||||||
|
- `world`
|
||||||
|
- `kernel`
|
||||||
|
- `headers`
|
||||||
|
- `bootloader`
|
||||||
|
|
||||||
|
## What changed
|
||||||
|
|
||||||
|
### Promotion metadata in guest result roots
|
||||||
|
|
||||||
|
The guest self-hosted helper now emits a promotion description file at:
|
||||||
|
|
||||||
|
- `/var/lib/fruix/native-builds/<run-id>/promotion.scm`
|
||||||
|
|
||||||
|
That metadata records at least:
|
||||||
|
|
||||||
|
- executor / executor-version
|
||||||
|
- run-id / guest-host-name
|
||||||
|
- closure path
|
||||||
|
- development profile path
|
||||||
|
- declared FreeBSD base metadata
|
||||||
|
- source store provenance
|
||||||
|
- build policy
|
||||||
|
- artifact entries for:
|
||||||
|
- `world`
|
||||||
|
- `kernel`
|
||||||
|
- `headers`
|
||||||
|
- `bootloader`
|
||||||
|
|
||||||
|
The helper also stages a promotable `world` artifact tree in addition to the already validated narrower artifacts.
|
||||||
|
|
||||||
|
### Host-side promotion API
|
||||||
|
|
||||||
|
Fruix now exports:
|
||||||
|
|
||||||
|
- `promote-native-build-result`
|
||||||
|
|
||||||
|
and the CLI now exposes:
|
||||||
|
|
||||||
|
- `fruix native-build promote RESULT_ROOT [--store DIR]`
|
||||||
|
|
||||||
|
### Promoted store object layout
|
||||||
|
|
||||||
|
Promotion now creates immutable store objects for:
|
||||||
|
|
||||||
|
- `/frx/store/...-fruix-native-world-...`
|
||||||
|
- `/frx/store/...-fruix-native-kernel-...`
|
||||||
|
- `/frx/store/...-fruix-native-headers-...`
|
||||||
|
- `/frx/store/...-fruix-native-bootloader-...`
|
||||||
|
|
||||||
|
Each promoted artifact store records:
|
||||||
|
|
||||||
|
- `.fruix-native-build-object.scm`
|
||||||
|
- `.references`
|
||||||
|
|
||||||
|
Promotion also creates a result-bundle store object:
|
||||||
|
|
||||||
|
- `/frx/store/...-fruix-native-build-result-...`
|
||||||
|
|
||||||
|
That bundle records:
|
||||||
|
|
||||||
|
- `.fruix-native-build-result.scm`
|
||||||
|
- `artifacts/world`
|
||||||
|
- `artifacts/kernel`
|
||||||
|
- `artifacts/headers`
|
||||||
|
- `artifacts/bootloader`
|
||||||
|
|
||||||
|
where the `artifacts/*` entries are symlinks to the promoted artifact stores.
|
||||||
|
|
||||||
|
### Identity policy
|
||||||
|
|
||||||
|
Artifact identity is now based on Fruix metadata plus a tree-content signature of the staged artifact tree.
|
||||||
|
|
||||||
|
That means promotion identity depends on both:
|
||||||
|
|
||||||
|
- the explicit Fruix-native build metadata
|
||||||
|
- the actual content of the promoted artifact tree
|
||||||
|
|
||||||
|
## Validation harness
|
||||||
|
|
||||||
|
Added:
|
||||||
|
|
||||||
|
- `tests/system/run-phase20-native-build-store-promotion-xcpng.sh`
|
||||||
|
|
||||||
|
This harness:
|
||||||
|
|
||||||
|
1. boots the approved real XCP-ng guest path
|
||||||
|
2. runs the validated in-guest self-hosted native build helper
|
||||||
|
3. imports the guest result root back to the host
|
||||||
|
4. runs `fruix native-build promote`
|
||||||
|
5. verifies promoted store paths, metadata, symlink structure, and representative hashes
|
||||||
|
|
||||||
|
## Validation
|
||||||
|
|
||||||
|
Passing run:
|
||||||
|
|
||||||
|
- `PASS phase20-native-build-store-promotion-xcpng`
|
||||||
|
- workdir: `/tmp/current-phase20-native-build-store-promotion-xcpng`
|
||||||
|
|
||||||
|
Approved real XCP-ng path:
|
||||||
|
|
||||||
|
- VM `90490f2e-e8fc-4b7a-388e-5c26f0157289`
|
||||||
|
- VDI `0f1f90d3-48ca-4fa2-91d8-fc6339b95743`
|
||||||
|
|
||||||
|
Representative metadata:
|
||||||
|
|
||||||
|
```text
|
||||||
|
run_id=20260405T213444Z
|
||||||
|
source_store=/frx/store/12d7704362e95afc2697db63f168b878e082b372-freebsd-source-default
|
||||||
|
guest_result_root=/var/lib/fruix/native-builds/20260405T213444Z
|
||||||
|
result_store=/frx/store/c6329a0053720b05aff3274b8b1d522c909f475d-fruix-native-build-result-15.0-STABLE-guest-self-hosted
|
||||||
|
world_store=/frx/store/dfe37b36f6537a95ceea16ea62001b2ca5617eb7-fruix-native-world-15.0-STABLE-guest-self-hosted
|
||||||
|
kernel_store=/frx/store/0ab7cbceca240ab2c3b91e83e059844ea792e49e-fruix-native-kernel-15.0-STABLE-guest-self-hosted
|
||||||
|
headers_store=/frx/store/5bbeae9266687a229f1c6d176a08886c35243ff0-fruix-native-headers-15.0-STABLE-guest-self-hosted
|
||||||
|
bootloader_store=/frx/store/bd49a508bd7a3b94a2535d6774f31c993c406552-fruix-native-bootloader-15.0-STABLE-guest-self-hosted
|
||||||
|
artifact_store_count=4
|
||||||
|
sha_kernel=16950f116a52134b98e2f8e0dacc556e18fe254e4a0ac2c1741422dde281a341
|
||||||
|
sha_loader=ea417846167ece270ada611624dca622ca38bd30125b9a125cd8ebb8b3600313
|
||||||
|
sha_param=9eb140ca7d9666f3d484a4174c9acd94b45427db6292b4e17de19af2c6aa5219
|
||||||
|
promoted_kernel_sha=16950f116a52134b98e2f8e0dacc556e18fe254e4a0ac2c1741422dde281a341
|
||||||
|
promoted_loader_sha=ea417846167ece270ada611624dca622ca38bd30125b9a125cd8ebb8b3600313
|
||||||
|
promoted_param_sha=9eb140ca7d9666f3d484a4174c9acd94b45427db6292b4e17de19af2c6aa5219
|
||||||
|
native_build_store_promotion=ok
|
||||||
|
```
|
||||||
|
|
||||||
|
Validated facts:
|
||||||
|
|
||||||
|
- guest self-hosted native-build results remain available under `/var/lib/fruix/native-builds/...` as mutable staging/results
|
||||||
|
- those staged results are now sufficient to promote first-class Fruix identities on the host
|
||||||
|
- promotion creates four immutable artifact store objects plus one immutable result-bundle store object
|
||||||
|
- promoted metadata retains executor/source/build-policy/provenance information explicitly
|
||||||
|
- promoted kernel, bootloader, and headers hashes match the already validated staged artifacts
|
||||||
|
- the promoted `world` artifact is now also preserved as a first-class Fruix store object instead of remaining only as an unpromoted staged tree
|
||||||
|
|
||||||
|
## Important implementation note
|
||||||
|
|
||||||
|
The first full promotion attempt exposed an incorrect assumption in the validation surface:
|
||||||
|
|
||||||
|
- the promoted/staged `world` artifact should be checked at:
|
||||||
|
- `bin/sh`
|
||||||
|
- not at:
|
||||||
|
- `usr/bin/sh`
|
||||||
|
|
||||||
|
That path expectation was corrected in:
|
||||||
|
|
||||||
|
- helper validation
|
||||||
|
- emitted promotion metadata
|
||||||
|
- the end-to-end promotion harness
|
||||||
|
|
||||||
|
## Result
|
||||||
|
|
||||||
|
Fruix now has a validated native-build object model with a clear split:
|
||||||
|
|
||||||
|
- mutable native-build result roots under `/var/lib/fruix/native-builds/...`
|
||||||
|
- immutable promoted identities under `/frx/store/...`
|
||||||
|
|
||||||
|
That makes native FreeBSD base builds feel substantially more Fruix-native:
|
||||||
|
|
||||||
|
- build outputs have explicit immutable identities
|
||||||
|
- metadata is Fruix-native rather than implied only by ad hoc directory layout
|
||||||
|
- executor/source/provenance/build-policy remain attached to the promoted result
|
||||||
|
- the staged result area and the real store identity are now intentionally distinct
|
||||||
|
|
||||||
|
## Next direction
|
||||||
|
|
||||||
|
This suggests the next product step is not merely “more self-hosting”.
|
||||||
|
|
||||||
|
It is to generalize this result model so different execution modes can converge on the same promoted object story, for example:
|
||||||
|
|
||||||
|
- host-initiated in-guest builds
|
||||||
|
- guest self-hosted builds
|
||||||
|
- future executor variants
|
||||||
|
|
||||||
|
That would move Fruix closer to a shared executor model rather than treating each validation path as a one-off harness.
|
||||||
201
docs/reports/phase20-self-hosted-native-builds-freebsd.md
Normal file
201
docs/reports/phase20-self-hosted-native-builds-freebsd.md
Normal file
@@ -0,0 +1,201 @@
|
|||||||
|
# Phase 20.3: controlled guest self-hosted native base-build prototype
|
||||||
|
|
||||||
|
Date: 2026-04-05
|
||||||
|
|
||||||
|
## Goal
|
||||||
|
|
||||||
|
Reassess guest self-hosting now that Fruix has already completed the earlier source, installation, generation-layout, rollback, development-overlay, and host-initiated in-guest native-build steps.
|
||||||
|
|
||||||
|
Phase 20.3 asked for real evidence about:
|
||||||
|
|
||||||
|
- what self-hosting would improve
|
||||||
|
- what it would cost in complexity
|
||||||
|
- how it fits with the Fruix source/deployment model already in place
|
||||||
|
|
||||||
|
## What changed
|
||||||
|
|
||||||
|
### New in-guest helper
|
||||||
|
|
||||||
|
Development-enabled systems now also ship:
|
||||||
|
|
||||||
|
- `/usr/local/bin/fruix-self-hosted-native-build`
|
||||||
|
|
||||||
|
This helper performs a controlled in-guest native FreeBSD base build using the system's own declared materialized source store recorded in:
|
||||||
|
|
||||||
|
- `/run/current-system/metadata/store-layout.scm`
|
||||||
|
|
||||||
|
The helper:
|
||||||
|
|
||||||
|
1. verifies the development overlay is present
|
||||||
|
2. verifies the canonical compatibility links exist:
|
||||||
|
- `/usr/include`
|
||||||
|
- `/usr/share/mk`
|
||||||
|
3. recovers the materialized FreeBSD source store from current-system metadata
|
||||||
|
4. runs:
|
||||||
|
- `buildworld`
|
||||||
|
- `buildkernel`
|
||||||
|
- `installworld`
|
||||||
|
- `distribution`
|
||||||
|
- `installkernel`
|
||||||
|
5. stages narrower artifact outputs under:
|
||||||
|
- `/var/lib/fruix/native-builds/<run-id>/artifacts/`
|
||||||
|
6. records metadata and status under:
|
||||||
|
- `/var/lib/fruix/native-builds/<run-id>/`
|
||||||
|
- `/var/lib/fruix/native-builds/latest`
|
||||||
|
|
||||||
|
The heavy object/stage work stays under:
|
||||||
|
|
||||||
|
- `/var/tmp/fruix-self-hosted-native-builds/<run-id>`
|
||||||
|
|
||||||
|
so the installed-system result area remains smaller and more legible.
|
||||||
|
|
||||||
|
### Important environment fix discovered during prototyping
|
||||||
|
|
||||||
|
The first prototype attempt failed even though Phase 20.2 had already succeeded.
|
||||||
|
|
||||||
|
Cause:
|
||||||
|
|
||||||
|
- directly evaluating `fruix-development-environment` before `buildworld` exported development-oriented variables like:
|
||||||
|
- `MAKEFLAGS`
|
||||||
|
- `CPPFLAGS`
|
||||||
|
- `CFLAGS`
|
||||||
|
- `CXXFLAGS`
|
||||||
|
- `LDFLAGS`
|
||||||
|
- those are appropriate for smaller development builds, but they polluted FreeBSD's world/kernel bootstrap environment and broke the LLVM bootstrap phase
|
||||||
|
|
||||||
|
Representative failure:
|
||||||
|
|
||||||
|
- missing generated LLVM config headers during bootstrap (`llvm/Config/abi-breaking.h`)
|
||||||
|
|
||||||
|
The validated fix was to make the self-hosted helper explicitly sanitize that environment first:
|
||||||
|
|
||||||
|
- reset `PATH` to the normal base paths
|
||||||
|
- unset development-shell variables such as:
|
||||||
|
- `MAKEFLAGS`
|
||||||
|
- `CC`, `CXX`, `AR`, `RANLIB`, `NM`
|
||||||
|
- `CPPFLAGS`, `CFLAGS`, `CXXFLAGS`, `LDFLAGS`
|
||||||
|
- `FRUIX_DEVELOPMENT_*`
|
||||||
|
- `FRUIX_*` tool variables
|
||||||
|
|
||||||
|
So the final 20.3 result is not “just reuse the development shell wholesale”.
|
||||||
|
|
||||||
|
It is more precise:
|
||||||
|
|
||||||
|
- use the development overlay for canonical paths and available content
|
||||||
|
- but run the real base-build steps in a cleaner, purpose-built helper environment
|
||||||
|
|
||||||
|
### Closure invalidation
|
||||||
|
|
||||||
|
To ensure the updated helper actually affects generated system closures, the operating-system closure spec now also records helper-version markers for development-enabled systems.
|
||||||
|
|
||||||
|
That ensures guest images pick up helper changes instead of silently reusing an older cached closure path.
|
||||||
|
|
||||||
|
### Validation harness
|
||||||
|
|
||||||
|
Added:
|
||||||
|
|
||||||
|
- `tests/system/run-phase20-self-hosted-native-build-xcpng.sh`
|
||||||
|
|
||||||
|
This harness:
|
||||||
|
|
||||||
|
1. boots the validated development-enabled Fruix guest on the approved XCP-ng path
|
||||||
|
2. verifies the new helper exists in the guest
|
||||||
|
3. invokes the helper from inside the guest
|
||||||
|
4. verifies the recorded result/status/`latest` pointer
|
||||||
|
5. validates the resulting staged artifact metadata and hashes
|
||||||
|
|
||||||
|
## Validation
|
||||||
|
|
||||||
|
Passing run:
|
||||||
|
|
||||||
|
- `PASS phase20-self-hosted-native-build-xcpng`
|
||||||
|
- workdir: `/tmp/fruix-phase20-self-hosted-native-build-xcpng`
|
||||||
|
|
||||||
|
Validated on the approved real XCP-ng path:
|
||||||
|
|
||||||
|
- VM `90490f2e-e8fc-4b7a-388e-5c26f0157289`
|
||||||
|
- VDI `0f1f90d3-48ca-4fa2-91d8-fc6339b95743`
|
||||||
|
|
||||||
|
Representative metadata:
|
||||||
|
|
||||||
|
```text
|
||||||
|
run_id=20260405T150359Z
|
||||||
|
helper_version=2
|
||||||
|
build_jobs=8
|
||||||
|
source_store=/frx/store/12d7704362e95afc2697db63f168b878e082b372-freebsd-source-default
|
||||||
|
build_root=/var/tmp/fruix-self-hosted-native-builds/20260405T150359Z
|
||||||
|
result_root=/var/lib/fruix/native-builds/20260405T150359Z
|
||||||
|
latest_link=/var/lib/fruix/native-builds/latest
|
||||||
|
latest_target=/var/lib/fruix/native-builds/20260405T150359Z
|
||||||
|
status_value=ok
|
||||||
|
build_root_size=7.5G
|
||||||
|
result_root_size=343M
|
||||||
|
kernel_artifact_size=158M
|
||||||
|
headers_artifact_size=32M
|
||||||
|
bootloader_artifact_size=1.3M
|
||||||
|
sha_kernel=16950f116a52134b98e2f8e0dacc556e18fe254e4a0ac2c1741422dde281a341
|
||||||
|
sha_loader=ea417846167ece270ada611624dca622ca38bd30125b9a125cd8ebb8b3600313
|
||||||
|
sha_param=9eb140ca7d9666f3d484a4174c9acd94b45427db6292b4e17de19af2c6aa5219
|
||||||
|
self_hosted_native_build=ok
|
||||||
|
```
|
||||||
|
|
||||||
|
Validated facts:
|
||||||
|
|
||||||
|
- the development-enabled Fruix guest can now run a controlled self-hosted native base-build helper from inside the installed system itself
|
||||||
|
- the helper can recover the declared source store from current-system metadata without host-side parsing
|
||||||
|
- `buildworld` and `buildkernel` succeed in the guest
|
||||||
|
- staged `installworld`, `distribution`, and `installkernel` succeed in the guest
|
||||||
|
- the helper records a stable result directory and `latest` pointer under:
|
||||||
|
- `/var/lib/fruix/native-builds`
|
||||||
|
- the resulting artifact hashes match the earlier validated Phase 20.2 host-initiated in-guest path
|
||||||
|
|
||||||
|
## What self-hosting improved
|
||||||
|
|
||||||
|
The prototype demonstrates a few real improvements:
|
||||||
|
|
||||||
|
- the build recipe itself now lives inside the Fruix-managed system, not only in a host-side SSH harness
|
||||||
|
- the guest can derive its own declared source input from current-system metadata
|
||||||
|
- result/state recording now has a Fruix-native installed-system location:
|
||||||
|
- `/var/lib/fruix/native-builds`
|
||||||
|
- the host no longer needs to spell out every `make` phase just to validate the in-guest path
|
||||||
|
|
||||||
|
## What it cost in complexity
|
||||||
|
|
||||||
|
The prototype also made the extra complexity visible:
|
||||||
|
|
||||||
|
- the guest helper needs its own controlled environment contract
|
||||||
|
- a naive reuse of the development-shell exports was wrong for real `buildworld`
|
||||||
|
- helper-version invalidation had to be made explicit so closure caching would not hide helper changes
|
||||||
|
- the in-guest result/staging model now needs its own operator-facing conventions
|
||||||
|
|
||||||
|
So the experiment did not eliminate complexity.
|
||||||
|
|
||||||
|
It mostly moved some of it from the host harness into an explicit in-guest helper contract.
|
||||||
|
|
||||||
|
## Decision after the prototype
|
||||||
|
|
||||||
|
Phase 20.3 is complete because Fruix now has a **first controlled guest self-hosted native base-build prototype**.
|
||||||
|
|
||||||
|
However, the evidence does **not** suggest replacing the Phase 20.2 path as the default operator workflow yet.
|
||||||
|
|
||||||
|
The current recommendation is:
|
||||||
|
|
||||||
|
- keep the **host-initiated in-guest native-build path** as the simpler default validation and orchestration flow
|
||||||
|
- keep the new **self-hosted helper** as a controlled prototype and stepping stone toward deeper guest-driven workflows
|
||||||
|
|
||||||
|
That fits the existing Fruix model well:
|
||||||
|
|
||||||
|
- source identity still comes from declared store-backed metadata
|
||||||
|
- deployment identity still comes from immutable closures under `/frx/store`
|
||||||
|
- the guest-side prototype adds a narrower in-system build/result workflow without replacing the existing deployment story
|
||||||
|
|
||||||
|
## Result
|
||||||
|
|
||||||
|
Phase 20.3 is complete.
|
||||||
|
|
||||||
|
Fruix now has:
|
||||||
|
|
||||||
|
- a validated host-orchestrated in-guest native base-build workflow
|
||||||
|
- and a validated first controlled guest self-hosted native base-build prototype
|
||||||
|
|
||||||
|
That answers the Phase 20.3 question with real evidence instead of only prior caution.
|
||||||
223
docs/reports/postphase20-build-profile-separation-freebsd.md
Normal file
223
docs/reports/postphase20-build-profile-separation-freebsd.md
Normal file
@@ -0,0 +1,223 @@
|
|||||||
|
# Post-Phase 20: separate development from native base-build environment
|
||||||
|
|
||||||
|
Date: 2026-04-06
|
||||||
|
|
||||||
|
## Goal
|
||||||
|
|
||||||
|
Tighten the distinction that Phase 20.3 exposed in practice:
|
||||||
|
|
||||||
|
- an interactive development shell is not the same thing as a reliable native base-build environment
|
||||||
|
|
||||||
|
Fruix now models three layers instead of two:
|
||||||
|
|
||||||
|
- runtime profile
|
||||||
|
- development profile
|
||||||
|
- build profile
|
||||||
|
|
||||||
|
## Why this change was needed
|
||||||
|
|
||||||
|
The earlier self-hosted native-build prototype proved that simply reusing the exported development environment for `buildworld` / `buildkernel` was too loose.
|
||||||
|
|
||||||
|
Development-oriented exports like:
|
||||||
|
|
||||||
|
- `MAKEFLAGS`
|
||||||
|
- `CPPFLAGS`
|
||||||
|
- `CFLAGS`
|
||||||
|
- `CXXFLAGS`
|
||||||
|
- `LDFLAGS`
|
||||||
|
|
||||||
|
are useful for interactive compilation work, but they are the wrong contract for a real FreeBSD world/kernel bootstrap.
|
||||||
|
|
||||||
|
Phase 20.3 previously worked around that by manually sanitizing the shell before running the build.
|
||||||
|
|
||||||
|
This change makes that separation explicit in the product model instead of keeping it as an ad hoc helper detail.
|
||||||
|
|
||||||
|
## Implementation
|
||||||
|
|
||||||
|
### New operating-system layer
|
||||||
|
|
||||||
|
`modules/fruix/system/freebsd/model.scm` now supports:
|
||||||
|
|
||||||
|
- `#:build-packages`
|
||||||
|
- `operating-system-build-packages`
|
||||||
|
|
||||||
|
So a build-capable system can now carry both:
|
||||||
|
|
||||||
|
- `development-packages`
|
||||||
|
- `build-packages`
|
||||||
|
|
||||||
|
separately.
|
||||||
|
|
||||||
|
### New build profile inside the system closure
|
||||||
|
|
||||||
|
When `build-packages` is non-empty, Fruix now materializes:
|
||||||
|
|
||||||
|
- `/frx/store/...-fruix-system-.../build-profile`
|
||||||
|
|
||||||
|
alongside the existing:
|
||||||
|
|
||||||
|
- `/frx/store/...-fruix-system-.../profile`
|
||||||
|
- `/frx/store/...-fruix-system-.../development-profile`
|
||||||
|
|
||||||
|
Store-layout metadata now records both development-package stores and build-package stores explicitly.
|
||||||
|
|
||||||
|
### New in-guest build helper
|
||||||
|
|
||||||
|
Build-capable systems now ship:
|
||||||
|
|
||||||
|
- `/usr/local/bin/fruix-build-environment`
|
||||||
|
|
||||||
|
and expose a stable runtime link:
|
||||||
|
|
||||||
|
- `/run/current-build -> /run/current-system/build-profile`
|
||||||
|
|
||||||
|
That helper intentionally emits a stricter environment contract than the interactive development helper.
|
||||||
|
|
||||||
|
It clears development-shell variables such as:
|
||||||
|
|
||||||
|
- `MAKEOBJDIRPREFIX`
|
||||||
|
- `MAKEFLAGS`
|
||||||
|
- `CC`
|
||||||
|
- `CXX`
|
||||||
|
- `AR`
|
||||||
|
- `RANLIB`
|
||||||
|
- `NM`
|
||||||
|
- `CPPFLAGS`
|
||||||
|
- `CFLAGS`
|
||||||
|
- `CXXFLAGS`
|
||||||
|
- `LDFLAGS`
|
||||||
|
|
||||||
|
and also clears development-helper exports such as:
|
||||||
|
|
||||||
|
- `FRUIX_DEVELOPMENT_PROFILE`
|
||||||
|
- `FRUIX_CC`
|
||||||
|
- `FRUIX_CXX`
|
||||||
|
- `FRUIX_AR`
|
||||||
|
- `FRUIX_RANLIB`
|
||||||
|
- `FRUIX_NM`
|
||||||
|
- `FRUIX_BMAKE`
|
||||||
|
|
||||||
|
Then it exports build-specific values including at least:
|
||||||
|
|
||||||
|
- `FRUIX_BUILD_PROFILE`
|
||||||
|
- `FRUIX_BUILD_INCLUDE`
|
||||||
|
- `FRUIX_BUILD_SHARE_MK`
|
||||||
|
- `FRUIX_BUILD_BIN`
|
||||||
|
- `FRUIX_BUILD_USR_BIN`
|
||||||
|
- `FRUIX_BUILD_CC`
|
||||||
|
- `FRUIX_BUILD_CXX`
|
||||||
|
- `FRUIX_BUILD_AR`
|
||||||
|
- `FRUIX_BUILD_RANLIB`
|
||||||
|
- `FRUIX_BUILD_NM`
|
||||||
|
- `FRUIX_BMAKE`
|
||||||
|
- `PATH`
|
||||||
|
|
||||||
|
Intended use:
|
||||||
|
|
||||||
|
```sh
|
||||||
|
eval "$(/usr/local/bin/fruix-build-environment)"
|
||||||
|
```
|
||||||
|
|
||||||
|
### Canonical build compatibility links now come from the build profile
|
||||||
|
|
||||||
|
For native base-build compatibility, build-capable systems now expose:
|
||||||
|
|
||||||
|
- `/usr/include -> /run/current-system/build-profile/usr/include`
|
||||||
|
- `/usr/share/mk -> /run/current-system/build-profile/usr/share/mk`
|
||||||
|
|
||||||
|
This means the running system can still keep development and build content separate while offering the canonical paths that FreeBSD native build machinery expects.
|
||||||
|
|
||||||
|
### Self-hosted native-build helper now uses the build helper contract
|
||||||
|
|
||||||
|
`/usr/local/bin/fruix-self-hosted-native-build` now:
|
||||||
|
|
||||||
|
- requires `build-profile`
|
||||||
|
- requires `/usr/local/bin/fruix-build-environment`
|
||||||
|
- evaluates the build helper contract before `buildworld`
|
||||||
|
- verifies the canonical compatibility links point at `build-profile`
|
||||||
|
|
||||||
|
This replaces the earlier approach where the helper had to reconstruct sanitization manually around a development-oriented environment.
|
||||||
|
|
||||||
|
### Promotion metadata now records build profile provenance
|
||||||
|
|
||||||
|
Promotion metadata emitted for self-hosted native-build results now records:
|
||||||
|
|
||||||
|
- `build-profile`
|
||||||
|
|
||||||
|
Promoted artifact/result metadata also now preserves that field.
|
||||||
|
|
||||||
|
## Validation
|
||||||
|
|
||||||
|
Validated on the approved real XCP-ng path:
|
||||||
|
|
||||||
|
- VM `90490f2e-e8fc-4b7a-388e-5c26f0157289`
|
||||||
|
- VDI `0f1f90d3-48ca-4fa2-91d8-fc6339b95743`
|
||||||
|
|
||||||
|
Passing runs:
|
||||||
|
|
||||||
|
- `PASS phase20-development-environment-xcpng`
|
||||||
|
- `PASS phase20-self-hosted-native-build-xcpng`
|
||||||
|
- `PASS phase20-native-build-store-promotion-xcpng`
|
||||||
|
- `PASS phase20-host-initiated-native-build-xcpng`
|
||||||
|
- `PASS phase20-host-initiated-native-build-store-promotion-xcpng`
|
||||||
|
|
||||||
|
## Representative validated results
|
||||||
|
|
||||||
|
Development/build environment validation:
|
||||||
|
|
||||||
|
```text
|
||||||
|
development_profile_guest=/run/current-system/development-profile
|
||||||
|
build_profile_guest=/run/current-system/build-profile
|
||||||
|
development_env_script=/frx/store/...-fruix-system-.../usr/local/bin/fruix-development-environment
|
||||||
|
build_env_script=/frx/store/...-fruix-system-.../usr/local/bin/fruix-build-environment
|
||||||
|
development_environment=ok
|
||||||
|
```
|
||||||
|
|
||||||
|
Self-hosted native-build validation:
|
||||||
|
|
||||||
|
```text
|
||||||
|
helper_version=5
|
||||||
|
executor_kind=self-hosted
|
||||||
|
executor_name=guest-self-hosted
|
||||||
|
executor_version=5
|
||||||
|
build_profile=/run/current-system/build-profile
|
||||||
|
source_store=/frx/store/12d7704362e95afc2697db63f168b878e082b372-freebsd-source-default
|
||||||
|
self_hosted_native_build=ok
|
||||||
|
```
|
||||||
|
|
||||||
|
Promotion validation:
|
||||||
|
|
||||||
|
```text
|
||||||
|
executor_kind=self-hosted
|
||||||
|
executor_name=guest-self-hosted
|
||||||
|
executor_version=5
|
||||||
|
result_store=/frx/store/3ce6aefd564bc51f2465dcbb5c261355be4c7076-fruix-native-build-result-15.0-STABLE-guest-self-hosted
|
||||||
|
native_build_store_promotion=ok
|
||||||
|
```
|
||||||
|
|
||||||
|
Host-initiated path revalidation with the new build-profile split also succeeded:
|
||||||
|
|
||||||
|
```text
|
||||||
|
executor_kind=ssh-guest
|
||||||
|
executor_name=ssh-guest
|
||||||
|
executor_version=1
|
||||||
|
result_store=/frx/store/93a4837d2588acfde2262010b296df9c7b7b367a-fruix-native-build-result-15.0-STABLE-ssh-guest
|
||||||
|
host_initiated_native_build_store_promotion=ok
|
||||||
|
```
|
||||||
|
|
||||||
|
## Result
|
||||||
|
|
||||||
|
Fruix now expresses an important product distinction more honestly:
|
||||||
|
|
||||||
|
- development is for interactive work
|
||||||
|
- build is for a stricter native base-build contract
|
||||||
|
|
||||||
|
That reduces special-case cleanup inside the self-hosted helper and gives Fruix a clearer path for future operator-facing commands such as:
|
||||||
|
|
||||||
|
- `fruix system build-base`
|
||||||
|
- `fruix system upgrade`
|
||||||
|
|
||||||
|
because the system model now has an explicit place to say:
|
||||||
|
|
||||||
|
- what is available for interactive development
|
||||||
|
- what is available for real native base builds
|
||||||
178
docs/reports/postphase20-installed-node-management-freebsd.md
Normal file
178
docs/reports/postphase20-installed-node-management-freebsd.md
Normal file
@@ -0,0 +1,178 @@
|
|||||||
|
# Post-Phase 20: installed systems as real Fruix nodes
|
||||||
|
|
||||||
|
Date: 2026-04-06
|
||||||
|
|
||||||
|
## Goal
|
||||||
|
|
||||||
|
Make a Fruix-installed machine feel like a managed Fruix node instead of only a deployed image with switch/rollback helpers.
|
||||||
|
|
||||||
|
The immediate target was not yet the full long-term command surface of:
|
||||||
|
|
||||||
|
- `fruix system upgrade`
|
||||||
|
- `fruix system build-base`
|
||||||
|
- `fruix system deploy`
|
||||||
|
|
||||||
|
but it was enough to cross an important product threshold:
|
||||||
|
|
||||||
|
- installed nodes can now remember their own declaration inputs
|
||||||
|
- installed nodes can build from those inputs locally
|
||||||
|
- installed nodes can reconfigure themselves into a newly built generation
|
||||||
|
- installed nodes can still roll back cleanly
|
||||||
|
|
||||||
|
## What changed
|
||||||
|
|
||||||
|
### Canonical declaration state now ships inside the system closure
|
||||||
|
|
||||||
|
Fruix system closures now carry explicit declaration metadata:
|
||||||
|
|
||||||
|
- `metadata/system-declaration.scm`
|
||||||
|
- `metadata/system-declaration-info.scm`
|
||||||
|
- `metadata/system-declaration-system`
|
||||||
|
|
||||||
|
That gives an installed system a canonical local answer to:
|
||||||
|
|
||||||
|
- what declaration source produced me?
|
||||||
|
- what top-level system variable should be used?
|
||||||
|
|
||||||
|
This declaration metadata is also recorded through the installed generation layout metadata.
|
||||||
|
|
||||||
|
### Bundled Fruix node CLI sources now ship inside the closure
|
||||||
|
|
||||||
|
Installed system closures now also carry a self-contained Fruix node CLI source bundle under:
|
||||||
|
|
||||||
|
- `share/fruix/node/scripts/fruix.scm`
|
||||||
|
- `share/fruix/node/modules/...`
|
||||||
|
- `share/fruix/node/guix/guix/build/utils.scm`
|
||||||
|
|
||||||
|
This gives the installed node enough local Fruix/Guix Scheme source to run Fruix system actions from the node itself.
|
||||||
|
|
||||||
|
### Installed `fruix` helper gained local build/reconfigure support
|
||||||
|
|
||||||
|
The installed helper at:
|
||||||
|
|
||||||
|
- `/usr/local/bin/fruix`
|
||||||
|
|
||||||
|
now supports:
|
||||||
|
|
||||||
|
- `fruix system build`
|
||||||
|
- `fruix system reconfigure`
|
||||||
|
- `fruix system status`
|
||||||
|
- `fruix system switch`
|
||||||
|
- `fruix system rollback`
|
||||||
|
|
||||||
|
Current behavior:
|
||||||
|
|
||||||
|
- `fruix system build` with no extra arguments uses:
|
||||||
|
- `/run/current-system/metadata/system-declaration.scm`
|
||||||
|
- `/run/current-system/metadata/system-declaration-system`
|
||||||
|
- `fruix system reconfigure` with no extra arguments builds from that same embedded declaration and then stages a switch to the resulting closure
|
||||||
|
- both commands can also take an explicit declaration file plus `--system NAME`, using the same general CLI shape as the host-side Fruix frontend
|
||||||
|
|
||||||
|
### In-node builds now reuse the installed Fruix runtime stores
|
||||||
|
|
||||||
|
A crucial implementation fix was needed here.
|
||||||
|
|
||||||
|
An installed node should not try to reconstruct the Guile/Shepherd runtime prefixes from host-side `/tmp/...` build roots or host `/usr/local/lib/...` assumptions.
|
||||||
|
|
||||||
|
Instead, in-node Fruix builds now explicitly reuse the installed runtime stores already referenced by the current system closure.
|
||||||
|
|
||||||
|
That allows the in-node build path to work on the real installed system rather than depending on build-host-only paths.
|
||||||
|
|
||||||
|
## Validation harness
|
||||||
|
|
||||||
|
Added:
|
||||||
|
|
||||||
|
- `tests/system/postphase20-installed-node-operating-system.scm.in`
|
||||||
|
- `tests/system/run-postphase20-installed-node-build-reconfigure-xcpng.sh`
|
||||||
|
|
||||||
|
This harness validates the new installed-node workflow on the approved real XCP-ng path.
|
||||||
|
|
||||||
|
## Validation performed
|
||||||
|
|
||||||
|
Approved real XCP-ng path:
|
||||||
|
|
||||||
|
- VM `90490f2e-e8fc-4b7a-388e-5c26f0157289`
|
||||||
|
- VDI `0f1f90d3-48ca-4fa2-91d8-fc6339b95743`
|
||||||
|
|
||||||
|
Promoted native-base result consumed by the installed node declaration:
|
||||||
|
|
||||||
|
- `/frx/store/ffe44f5d1ba576e1f811ad3fe3a526a242b5c4a5-fruix-native-build-result-15.0-STABLE-ssh-guest`
|
||||||
|
|
||||||
|
Validated flow:
|
||||||
|
|
||||||
|
1. boot a Fruix-installed node built from the promoted native-base result
|
||||||
|
2. confirm the installed node exposes:
|
||||||
|
- embedded declaration metadata
|
||||||
|
- bundled node CLI sources
|
||||||
|
3. run in-guest:
|
||||||
|
- `fruix system build`
|
||||||
|
using the node's own embedded declaration inputs
|
||||||
|
4. copy a candidate declaration to the guest
|
||||||
|
5. run in-guest:
|
||||||
|
- `fruix system build /root/candidate.scm --system postphase20-installed-node-operating-system`
|
||||||
|
6. run in-guest:
|
||||||
|
- `fruix system reconfigure /root/candidate.scm --system postphase20-installed-node-operating-system`
|
||||||
|
7. reboot and verify the candidate generation boots
|
||||||
|
8. run in-guest:
|
||||||
|
- `fruix system rollback`
|
||||||
|
9. reboot and verify the original generation boots again
|
||||||
|
|
||||||
|
Passing run:
|
||||||
|
|
||||||
|
- `PASS postphase20-installed-node-build-reconfigure-xcpng`
|
||||||
|
|
||||||
|
Representative metadata:
|
||||||
|
|
||||||
|
```text
|
||||||
|
closure_path=/frx/store/cd4e52d8bff348953939401c8623d4189d7c9432-fruix-system-fruix-node-current
|
||||||
|
current_built_closure=/frx/store/18ee10925a15b48c676463a3359c45ff766e16a0-fruix-system-fruix-node-current
|
||||||
|
candidate_closure=/frx/store/46fc9631faf556c30a1a5f39f718d5d38a3f6ba8-fruix-system-fruix-node-canary
|
||||||
|
reconfigure_closure=/frx/store/46fc9631faf556c30a1a5f39f718d5d38a3f6ba8-fruix-system-fruix-node-canary
|
||||||
|
reconfigure_current_generation=2
|
||||||
|
reconfigure_current_closure=/frx/store/46fc9631faf556c30a1a5f39f718d5d38a3f6ba8-fruix-system-fruix-node-canary
|
||||||
|
reconfigure_rollback_generation=1
|
||||||
|
reconfigure_rollback_closure=/frx/store/cd4e52d8bff348953939401c8623d4189d7c9432-fruix-system-fruix-node-current
|
||||||
|
candidate_hostname=fruix-node-canary
|
||||||
|
rollback_current_generation=1
|
||||||
|
rollback_current_closure=/frx/store/cd4e52d8bff348953939401c8623d4189d7c9432-fruix-system-fruix-node-current
|
||||||
|
rollback_rollback_generation=2
|
||||||
|
rollback_rollback_closure=/frx/store/46fc9631faf556c30a1a5f39f718d5d38a3f6ba8-fruix-system-fruix-node-canary
|
||||||
|
rollback_hostname=fruix-node-current
|
||||||
|
installed_node_build_reconfigure=ok
|
||||||
|
```
|
||||||
|
|
||||||
|
## Important observation
|
||||||
|
|
||||||
|
The no-argument in-node build of the current declaration did not necessarily reproduce the exact original current closure path.
|
||||||
|
|
||||||
|
That is expected with the current model because closure metadata still records builder-local provenance such as the current build host context.
|
||||||
|
|
||||||
|
So the significant validated fact is not strict bit-for-bit closure reuse.
|
||||||
|
|
||||||
|
It is that the installed node can now:
|
||||||
|
|
||||||
|
- read its own declaration inputs locally
|
||||||
|
- build a valid local candidate closure from them
|
||||||
|
- build a different candidate declaration locally
|
||||||
|
- reconfigure itself into that candidate generation
|
||||||
|
- boot the candidate generation
|
||||||
|
- roll back and boot the prior generation again
|
||||||
|
|
||||||
|
## Result
|
||||||
|
|
||||||
|
Fruix-installed machines are now meaningfully closer to real Fruix nodes.
|
||||||
|
|
||||||
|
They no longer only support:
|
||||||
|
|
||||||
|
- `status`
|
||||||
|
- `switch`
|
||||||
|
- `rollback`
|
||||||
|
|
||||||
|
They now also support a validated local node workflow for:
|
||||||
|
|
||||||
|
- reading embedded declaration inputs
|
||||||
|
- building a local candidate closure
|
||||||
|
- reconfiguring into that locally built candidate
|
||||||
|
- rolling back through the same installed generation model
|
||||||
|
|
||||||
|
This is the first concrete step toward a fuller operator-facing Fruix node command surface.
|
||||||
@@ -0,0 +1,165 @@
|
|||||||
|
# Post-Phase 20: promoted native base result sets as declaration inputs
|
||||||
|
|
||||||
|
Date: 2026-04-06
|
||||||
|
|
||||||
|
## Goal
|
||||||
|
|
||||||
|
Move Fruix from:
|
||||||
|
|
||||||
|
- “the guest built a kernel”
|
||||||
|
|
||||||
|
to:
|
||||||
|
|
||||||
|
- “Fruix materialized a native-base result set with identity X”
|
||||||
|
|
||||||
|
and then prove that a normal Fruix system declaration can consume that promoted result set directly.
|
||||||
|
|
||||||
|
Concretely, the desired product behavior was:
|
||||||
|
|
||||||
|
1. build runs somewhere
|
||||||
|
2. result is recorded in a staging/result root
|
||||||
|
3. Fruix validates the result shape and metadata
|
||||||
|
4. Fruix promotes it into store objects
|
||||||
|
5. system declarations can refer to those promoted artifacts
|
||||||
|
|
||||||
|
Before this step, Fruix already had validated steps 1 through 4.
|
||||||
|
|
||||||
|
This step implemented and validated step 5.
|
||||||
|
|
||||||
|
## What changed
|
||||||
|
|
||||||
|
### First-class promoted result reference in system declarations
|
||||||
|
|
||||||
|
Fruix now has a first-class promoted native-build result reference object.
|
||||||
|
|
||||||
|
Current declaration-level entry points are:
|
||||||
|
|
||||||
|
- `promoted-native-build-result`
|
||||||
|
- `operating-system-from-promoted-native-build-result`
|
||||||
|
|
||||||
|
A declaration can now point at a promoted native-build result bundle in `/frx/store`, for example:
|
||||||
|
|
||||||
|
```scheme
|
||||||
|
(define promoted
|
||||||
|
(promoted-native-build-result
|
||||||
|
#:store-path "/frx/store/...-fruix-native-build-result-..."))
|
||||||
|
|
||||||
|
(define os
|
||||||
|
(operating-system-from-promoted-native-build-result
|
||||||
|
promoted
|
||||||
|
...))
|
||||||
|
```
|
||||||
|
|
||||||
|
### Promoted result bundles now drive system composition
|
||||||
|
|
||||||
|
From a promoted result bundle, Fruix now derives:
|
||||||
|
|
||||||
|
- the effective FreeBSD base metadata used by the system declaration
|
||||||
|
- kernel package identity from the promoted kernel artifact store
|
||||||
|
- bootloader package identity from the promoted bootloader artifact store
|
||||||
|
- base-package identity from the promoted world artifact store
|
||||||
|
- optional development-package identity from the promoted headers artifact store
|
||||||
|
|
||||||
|
This means a system declaration can now be based on a promoted native-build result set rather than only on source-driven base-package reconstruction.
|
||||||
|
|
||||||
|
### Closure metadata now records promoted-result provenance explicitly
|
||||||
|
|
||||||
|
When a system declaration uses a promoted native-build result set, Fruix now records that explicitly in the resulting closure metadata.
|
||||||
|
|
||||||
|
New closure metadata includes:
|
||||||
|
|
||||||
|
- `metadata/promoted-native-build-result.scm`
|
||||||
|
- `metadata/store-layout.scm` entries for:
|
||||||
|
- promoted result summary
|
||||||
|
- promoted artifact stores
|
||||||
|
|
||||||
|
The system closure also retains the promoted result bundle itself as an explicit reference in:
|
||||||
|
|
||||||
|
- `/frx/store/...-fruix-system-.../.references`
|
||||||
|
|
||||||
|
So the resulting deployed closure does not merely happen to contain files copied from a promoted artifact; it explicitly records which promoted result set it came from.
|
||||||
|
|
||||||
|
## Implementation notes
|
||||||
|
|
||||||
|
The key plumbing additions were:
|
||||||
|
|
||||||
|
- a promoted native-build result record in:
|
||||||
|
- `modules/fruix/system/freebsd/model.scm`
|
||||||
|
- promoted-result readers/helpers in:
|
||||||
|
- `modules/fruix/system/freebsd/build.scm`
|
||||||
|
- direct system-construction helper:
|
||||||
|
- `operating-system-from-promoted-native-build-result`
|
||||||
|
- support for consuming promoted artifact stores directly when materializing packages
|
||||||
|
- closure/store-layout metadata wiring in:
|
||||||
|
- `modules/fruix/system/freebsd/media.scm`
|
||||||
|
|
||||||
|
The important product-level effect is that promoted native-build results are no longer only post-build artifacts. They are now declaration inputs.
|
||||||
|
|
||||||
|
## Validation harness
|
||||||
|
|
||||||
|
Added:
|
||||||
|
|
||||||
|
- `tests/system/phase20-promoted-native-base-operating-system.scm.in`
|
||||||
|
- `tests/system/run-phase20-promoted-native-base-declaration-xcpng.sh`
|
||||||
|
|
||||||
|
The harness supports two modes:
|
||||||
|
|
||||||
|
- use an explicit `RESULT_STORE=/frx/store/...-fruix-native-build-result-...`
|
||||||
|
- or, if no result store is supplied, first run the validated host-initiated promotion path and consume that result
|
||||||
|
|
||||||
|
## Validation performed
|
||||||
|
|
||||||
|
Validated on the approved real XCP-ng path using the promoted `ssh-guest` result bundle:
|
||||||
|
|
||||||
|
- result bundle:
|
||||||
|
- `/frx/store/ffe44f5d1ba576e1f811ad3fe3a526a242b5c4a5-fruix-native-build-result-15.0-STABLE-ssh-guest`
|
||||||
|
- VM:
|
||||||
|
- `90490f2e-e8fc-4b7a-388e-5c26f0157289`
|
||||||
|
- VDI:
|
||||||
|
- `0f1f90d3-48ca-4fa2-91d8-fc6339b95743`
|
||||||
|
|
||||||
|
Passing run:
|
||||||
|
|
||||||
|
- `PASS phase20-promoted-native-base-declaration-xcpng`
|
||||||
|
|
||||||
|
Representative metadata:
|
||||||
|
|
||||||
|
```text
|
||||||
|
closure_path=/frx/store/ac1e6dcfe67d3cde49d4fd5da97740f6244276b4-fruix-system-fruix-freebsd
|
||||||
|
result_store=/frx/store/ffe44f5d1ba576e1f811ad3fe3a526a242b5c4a5-fruix-native-build-result-15.0-STABLE-ssh-guest
|
||||||
|
executor_kind=ssh-guest
|
||||||
|
executor_name=ssh-guest
|
||||||
|
executor_version=1
|
||||||
|
world_store=/frx/store/89c7a71c3df148a1f99b13d57fd6be88243eb2cb-fruix-native-world-15.0-STABLE-ssh-guest
|
||||||
|
kernel_store=/frx/store/93bac81122022b40438d356146a6854b4ee48513-fruix-native-kernel-15.0-STABLE-ssh-guest
|
||||||
|
headers_store=/frx/store/dd7f39f526bca4849caf1eaf96ae25d29b43493c-fruix-native-headers-15.0-STABLE-ssh-guest
|
||||||
|
bootloader_store=/frx/store/78b1c6b0b5c0c2c1549f5f42f3d64b6d9293669b-fruix-native-bootloader-15.0-STABLE-ssh-guest
|
||||||
|
kernel_link=/frx/store/93bac81122022b40438d356146a6854b4ee48513-fruix-native-kernel-15.0-STABLE-ssh-guest/boot/kernel/kernel
|
||||||
|
bootloader_link=/frx/store/78b1c6b0b5c0c2c1549f5f42f3d64b6d9293669b-fruix-native-bootloader-15.0-STABLE-ssh-guest/boot/loader.efi
|
||||||
|
guest_kernel_link=/frx/store/93bac81122022b40438d356146a6854b4ee48513-fruix-native-kernel-15.0-STABLE-ssh-guest/boot/kernel/kernel
|
||||||
|
guest_bootloader_link=/frx/store/78b1c6b0b5c0c2c1549f5f42f3d64b6d9293669b-fruix-native-bootloader-15.0-STABLE-ssh-guest/boot/loader.efi
|
||||||
|
guest_uname=FreeBSD 15.0-STABLE
|
||||||
|
promoted_native_base_declaration=ok
|
||||||
|
```
|
||||||
|
|
||||||
|
## Validated facts
|
||||||
|
|
||||||
|
The real-VM validation proved that Fruix can now:
|
||||||
|
|
||||||
|
- consume a promoted native-build result bundle directly from a declaration
|
||||||
|
- materialize a normal system closure from that promoted identity
|
||||||
|
- retain explicit promoted-result provenance in closure metadata
|
||||||
|
- boot the resulting system successfully on the approved XCP-ng path
|
||||||
|
- keep the closure's kernel and bootloader linked directly to the promoted artifact stores
|
||||||
|
- preserve the promoted result-bundle store itself as an explicit closure reference
|
||||||
|
|
||||||
|
So the operator-facing story is now materially better:
|
||||||
|
|
||||||
|
- not only “a build happened somewhere”
|
||||||
|
- but “this system declaration is based on promoted native-base result X”
|
||||||
|
|
||||||
|
## Result
|
||||||
|
|
||||||
|
Promoted native-build result sets are now first-class Fruix declaration inputs.
|
||||||
|
|
||||||
|
That is the point where the native FreeBSD base path stops being only a build/promotion harness and starts acting more like real product behavior.
|
||||||
794
docs/system-deployment-workflow.md
Normal file
794
docs/system-deployment-workflow.md
Normal file
@@ -0,0 +1,794 @@
|
|||||||
|
# Fruix system deployment workflow
|
||||||
|
|
||||||
|
Date: 2026-04-06
|
||||||
|
|
||||||
|
## Purpose
|
||||||
|
|
||||||
|
This document defines the current canonical Fruix workflow for:
|
||||||
|
|
||||||
|
- building a declarative system closure
|
||||||
|
- materializing deployable artifacts
|
||||||
|
- installing a declarative system onto an image or disk
|
||||||
|
- booting through installer media
|
||||||
|
- rolling forward to a candidate system
|
||||||
|
- switching an installed system to a staged candidate generation
|
||||||
|
- rolling an installed system back to an earlier recorded generation
|
||||||
|
|
||||||
|
This is now the Phase 19 operator-facing view of the system model as validated through explicit installed-system generation switching and rollback.
|
||||||
|
|
||||||
|
## Core model
|
||||||
|
|
||||||
|
A Fruix system workflow starts from a Scheme file that binds an `operating-system` object.
|
||||||
|
|
||||||
|
Today, the canonical frontend is:
|
||||||
|
|
||||||
|
- `./bin/fruix system ...`
|
||||||
|
|
||||||
|
The important output objects are:
|
||||||
|
|
||||||
|
- **system closure**
|
||||||
|
- a content-addressed store item under `/frx/store/*-fruix-system-<host-name>`
|
||||||
|
- includes boot assets, activation logic, profile tree, metadata, and references
|
||||||
|
- **rootfs tree**
|
||||||
|
- a materialized runtime tree for inspection or image staging
|
||||||
|
- **disk image**
|
||||||
|
- a bootable GPT/UEFI raw disk image
|
||||||
|
- **installer image**
|
||||||
|
- a bootable Fruix installer disk image that installs a selected target system from inside the guest
|
||||||
|
- **installer ISO**
|
||||||
|
- a bootable UEFI ISO with an embedded installer mdroot payload
|
||||||
|
- **install metadata**
|
||||||
|
- `/var/lib/fruix/install.scm` on installed targets
|
||||||
|
- records the selected closure path, install spec, and referenced store items including source provenance
|
||||||
|
|
||||||
|
The current deployment story is therefore already declaration-driven and content-addressed, even before first-class installed-system generations are modeled more explicitly.
|
||||||
|
|
||||||
|
## Canonical command surface
|
||||||
|
|
||||||
|
### Build a system closure
|
||||||
|
|
||||||
|
```sh
|
||||||
|
sudo env HOME="$HOME" \
|
||||||
|
GUILE_AUTO_COMPILE=0 \
|
||||||
|
GUIX_SOURCE_DIR="$HOME/repos/guix" \
|
||||||
|
GUILE_BIN="/tmp/guile-freebsd-validate-install/bin/guile" \
|
||||||
|
GUILE_EXTRA_PREFIX="/tmp/guile-gnutls-freebsd-validate-install" \
|
||||||
|
SHEPHERD_PREFIX="/tmp/shepherd-freebsd-validate-install" \
|
||||||
|
./bin/fruix system build path/to/system.scm --system my-operating-system
|
||||||
|
```
|
||||||
|
|
||||||
|
Primary result:
|
||||||
|
|
||||||
|
- `closure_path=/frx/store/...-fruix-system-...`
|
||||||
|
|
||||||
|
Use this when you want to:
|
||||||
|
|
||||||
|
- validate the declarative system composition itself
|
||||||
|
- inspect provenance/layout metadata
|
||||||
|
- compare candidate and current closure paths
|
||||||
|
- drive later rootfs/image/install steps from the same declaration
|
||||||
|
|
||||||
|
### Materialize a rootfs tree
|
||||||
|
|
||||||
|
```sh
|
||||||
|
sudo env HOME="$HOME" ... \
|
||||||
|
./bin/fruix system rootfs path/to/system.scm ./rootfs --system my-operating-system
|
||||||
|
```
|
||||||
|
|
||||||
|
Primary result:
|
||||||
|
|
||||||
|
- `rootfs=...`
|
||||||
|
- `closure_path=/frx/store/...`
|
||||||
|
|
||||||
|
Use this when you want to:
|
||||||
|
|
||||||
|
- inspect the runtime filesystem layout directly
|
||||||
|
- stage a tree for debugging
|
||||||
|
- validate `/run/current-system`-style symlink layout without booting a full image
|
||||||
|
|
||||||
|
### Materialize a bootable disk image
|
||||||
|
|
||||||
|
```sh
|
||||||
|
sudo env HOME="$HOME" ... \
|
||||||
|
./bin/fruix system image path/to/system.scm \
|
||||||
|
--system my-operating-system \
|
||||||
|
--root-size 6g
|
||||||
|
```
|
||||||
|
|
||||||
|
Primary result:
|
||||||
|
|
||||||
|
- `disk_image=/frx/store/.../disk.img`
|
||||||
|
|
||||||
|
Use this when you want to:
|
||||||
|
|
||||||
|
- boot the system directly as a VM image
|
||||||
|
- test a candidate deployment under QEMU or XCP-ng
|
||||||
|
- validate a roll-forward or rollback candidate by image boot
|
||||||
|
|
||||||
|
### Install directly to an image file or block device
|
||||||
|
|
||||||
|
```sh
|
||||||
|
sudo env HOME="$HOME" ... \
|
||||||
|
./bin/fruix system install path/to/system.scm \
|
||||||
|
--system my-operating-system \
|
||||||
|
--target ./installed.img \
|
||||||
|
--disk-capacity 12g \
|
||||||
|
--root-size 10g
|
||||||
|
```
|
||||||
|
|
||||||
|
Primary result:
|
||||||
|
|
||||||
|
- `target=...`
|
||||||
|
- `target_kind=raw-file` or `block-device`
|
||||||
|
- `install_metadata_path=/var/lib/fruix/install.scm`
|
||||||
|
|
||||||
|
Use this when you want to:
|
||||||
|
|
||||||
|
- produce an installed target image without booting an installer guest
|
||||||
|
- validate installation mechanics directly
|
||||||
|
- populate a raw image or a real `/dev/...` target
|
||||||
|
|
||||||
|
### Materialize a bootable installer disk image
|
||||||
|
|
||||||
|
```sh
|
||||||
|
sudo env HOME="$HOME" ... \
|
||||||
|
./bin/fruix system installer path/to/system.scm \
|
||||||
|
--system my-operating-system \
|
||||||
|
--install-target-device /dev/vtbd1 \
|
||||||
|
--root-size 10g
|
||||||
|
```
|
||||||
|
|
||||||
|
Primary result:
|
||||||
|
|
||||||
|
- `installer_disk_image=/frx/store/.../disk.img`
|
||||||
|
|
||||||
|
Use this when you want to:
|
||||||
|
|
||||||
|
- boot a Fruix installer environment as a disk image
|
||||||
|
- let the in-guest installer partition and install onto a second disk
|
||||||
|
- validate non-interactive installation from inside a booted Fruix guest
|
||||||
|
|
||||||
|
### Materialize a bootable installer ISO
|
||||||
|
|
||||||
|
```sh
|
||||||
|
sudo env HOME="$HOME" ... \
|
||||||
|
./bin/fruix system installer-iso path/to/system.scm \
|
||||||
|
--system my-operating-system \
|
||||||
|
--install-target-device /dev/vtbd0
|
||||||
|
```
|
||||||
|
|
||||||
|
Primary result:
|
||||||
|
|
||||||
|
- `iso_image=/frx/store/.../installer.iso`
|
||||||
|
- `boot_efi_image=/frx/store/.../efiboot.img`
|
||||||
|
- `root_image=/frx/store/.../root.img`
|
||||||
|
|
||||||
|
Use this when you want to:
|
||||||
|
|
||||||
|
- boot through UEFI ISO media instead of a writable installer disk image
|
||||||
|
- install from an ISO-attached Fruix environment
|
||||||
|
- test the same install model on more realistic VM paths
|
||||||
|
|
||||||
|
### Installed-system generation commands
|
||||||
|
|
||||||
|
Installed Fruix systems now also ship a small in-guest deployment helper at:
|
||||||
|
|
||||||
|
- `/usr/local/bin/fruix`
|
||||||
|
|
||||||
|
Current validated in-guest commands are:
|
||||||
|
|
||||||
|
```sh
|
||||||
|
fruix system build
|
||||||
|
fruix system reconfigure
|
||||||
|
fruix system status
|
||||||
|
fruix system switch /frx/store/...-fruix-system-...
|
||||||
|
fruix system rollback
|
||||||
|
```
|
||||||
|
|
||||||
|
Installed systems now carry canonical declaration state in:
|
||||||
|
|
||||||
|
- `/run/current-system/metadata/system-declaration.scm`
|
||||||
|
- `/run/current-system/metadata/system-declaration-info.scm`
|
||||||
|
- `/run/current-system/metadata/system-declaration-system`
|
||||||
|
|
||||||
|
So the in-guest helper can now build from the node's own embedded declaration inputs.
|
||||||
|
|
||||||
|
Current validated build/reconfigure behavior is:
|
||||||
|
|
||||||
|
- `fruix system build`
|
||||||
|
- with no extra arguments, builds from the embedded current declaration
|
||||||
|
- `fruix system reconfigure`
|
||||||
|
- with no extra arguments, builds from the embedded current declaration and stages a switch to the resulting closure
|
||||||
|
- both commands can also take an explicit declaration file plus `--system NAME`
|
||||||
|
|
||||||
|
Current intended usage now has two validated patterns.
|
||||||
|
|
||||||
|
### Pattern A: build elsewhere, then switch/rollback locally
|
||||||
|
|
||||||
|
1. build a candidate closure on the operator side with `./bin/fruix system build`
|
||||||
|
2. ensure that candidate closure is present on the installed target's `/frx/store`
|
||||||
|
3. run `fruix system switch /frx/store/...` on the installed system
|
||||||
|
4. reboot into the staged candidate generation
|
||||||
|
5. if needed, run `fruix system rollback`
|
||||||
|
6. reboot back into the recorded rollback generation
|
||||||
|
|
||||||
|
Important current limitation of this lower-level pattern:
|
||||||
|
|
||||||
|
- `fruix system switch` does **not** yet fetch or copy the candidate closure onto the target for you
|
||||||
|
- it assumes the selected closure is already present in the installed system's `/frx/store`
|
||||||
|
|
||||||
|
### Pattern B: build and reconfigure from the node itself
|
||||||
|
|
||||||
|
1. inspect or edit the node declaration inputs
|
||||||
|
- embedded current declaration, or
|
||||||
|
- an explicit replacement declaration file
|
||||||
|
2. run:
|
||||||
|
|
||||||
|
```sh
|
||||||
|
fruix system build
|
||||||
|
```
|
||||||
|
|
||||||
|
or:
|
||||||
|
|
||||||
|
```sh
|
||||||
|
fruix system build /path/to/candidate.scm --system my-operating-system
|
||||||
|
```
|
||||||
|
|
||||||
|
3. stage a local generation update with:
|
||||||
|
|
||||||
|
```sh
|
||||||
|
fruix system reconfigure
|
||||||
|
```
|
||||||
|
|
||||||
|
or:
|
||||||
|
|
||||||
|
```sh
|
||||||
|
fruix system reconfigure /path/to/candidate.scm --system my-operating-system
|
||||||
|
```
|
||||||
|
|
||||||
|
4. reboot into the staged generation
|
||||||
|
5. if needed, run `fruix system rollback`
|
||||||
|
6. reboot back into the recorded prior generation
|
||||||
|
|
||||||
|
### In-guest development and build environments
|
||||||
|
|
||||||
|
Opt-in systems can now expose two separate overlays above the main runtime profile:
|
||||||
|
|
||||||
|
- development:
|
||||||
|
- `/run/current-system/development-profile`
|
||||||
|
- `/run/current-development`
|
||||||
|
- `/usr/local/bin/fruix-development-environment`
|
||||||
|
- build:
|
||||||
|
- `/run/current-system/build-profile`
|
||||||
|
- `/run/current-build`
|
||||||
|
- `/usr/local/bin/fruix-build-environment`
|
||||||
|
|
||||||
|
Intended use:
|
||||||
|
|
||||||
|
```sh
|
||||||
|
eval "$(/usr/local/bin/fruix-development-environment)"
|
||||||
|
```
|
||||||
|
|
||||||
|
for interactive development work, and:
|
||||||
|
|
||||||
|
```sh
|
||||||
|
eval "$(/usr/local/bin/fruix-build-environment)"
|
||||||
|
```
|
||||||
|
|
||||||
|
for a narrower native base-build contract.
|
||||||
|
|
||||||
|
The current split is:
|
||||||
|
|
||||||
|
- runtime profile
|
||||||
|
- development profile
|
||||||
|
- build profile
|
||||||
|
|
||||||
|
The development helper remains intentionally interactive and currently exposes at least:
|
||||||
|
|
||||||
|
- native headers under `usr/include`
|
||||||
|
- FreeBSD `share/mk` files for `bsd.*.mk`
|
||||||
|
- Clang toolchain commands such as `cc`, `c++`, `ar`, `ranlib`, and `nm`
|
||||||
|
- `MAKEFLAGS` pointing at the development profile's `usr/share/mk`
|
||||||
|
|
||||||
|
The build helper is intentionally more sanitized and less interactive. It clears development-shell variables such as:
|
||||||
|
|
||||||
|
- `MAKEFLAGS`
|
||||||
|
- `CPPFLAGS`
|
||||||
|
- `CFLAGS`
|
||||||
|
- `CXXFLAGS`
|
||||||
|
- `LDFLAGS`
|
||||||
|
|
||||||
|
and then exposes build-oriented paths such as:
|
||||||
|
|
||||||
|
- `FRUIX_BUILD_PROFILE`
|
||||||
|
- `FRUIX_BUILD_INCLUDE`
|
||||||
|
- `FRUIX_BUILD_SHARE_MK`
|
||||||
|
- `FRUIX_BUILD_CC`
|
||||||
|
- `FRUIX_BUILD_CXX`
|
||||||
|
- `FRUIX_BUILD_AR`
|
||||||
|
- `FRUIX_BUILD_RANLIB`
|
||||||
|
- `FRUIX_BUILD_NM`
|
||||||
|
- `FRUIX_BMAKE`
|
||||||
|
|
||||||
|
For native base-build compatibility, build-enabled systems now expose canonical links at:
|
||||||
|
|
||||||
|
- `/usr/include -> /run/current-system/build-profile/usr/include`
|
||||||
|
- `/usr/share/mk -> /run/current-system/build-profile/usr/share/mk`
|
||||||
|
|
||||||
|
So Fruix now separates interactive development support from the stricter environment used for `buildworld` / `buildkernel` style work, instead of treating them as one overlay.
|
||||||
|
|
||||||
|
### Host-initiated native base builds inside a Fruix-managed guest
|
||||||
|
|
||||||
|
The currently validated intermediate path toward self-hosting is still host-orchestrated.
|
||||||
|
|
||||||
|
The host:
|
||||||
|
|
||||||
|
1. boots a development-enabled Fruix guest
|
||||||
|
2. connects over SSH
|
||||||
|
3. recovers the materialized FreeBSD source store from system metadata
|
||||||
|
4. runs native FreeBSD build commands inside the guest
|
||||||
|
5. collects and records the staged outputs
|
||||||
|
|
||||||
|
The validated build sequence inside the guest is:
|
||||||
|
|
||||||
|
- `make -jN buildworld`
|
||||||
|
- `make -jN buildkernel`
|
||||||
|
- `make DESTDIR=... installworld`
|
||||||
|
- `make DESTDIR=... distribution`
|
||||||
|
- `make DESTDIR=... installkernel`
|
||||||
|
|
||||||
|
For staged install steps, the validated path uses:
|
||||||
|
|
||||||
|
- `DB_FROM_SRC=yes`
|
||||||
|
|
||||||
|
so the staged install is driven by the declared source tree's account database rather than by accidental guest-local `/etc/master.passwd` contents.
|
||||||
|
|
||||||
|
This is the current Phase 20.2 answer to “where should native base builds run?”
|
||||||
|
|
||||||
|
- **inside** a Fruix-managed FreeBSD environment
|
||||||
|
- but still with the **host** driving the outer orchestration loop
|
||||||
|
|
||||||
|
### Controlled guest self-hosted native-build prototype
|
||||||
|
|
||||||
|
Fruix now also has a narrower in-guest prototype helper at:
|
||||||
|
|
||||||
|
- `/usr/local/bin/fruix-self-hosted-native-build`
|
||||||
|
|
||||||
|
Intended use:
|
||||||
|
|
||||||
|
```sh
|
||||||
|
FRUIX_SELF_HOSTED_NATIVE_BUILD_JOBS=8 \
|
||||||
|
/usr/local/bin/fruix-self-hosted-native-build
|
||||||
|
```
|
||||||
|
|
||||||
|
That helper:
|
||||||
|
|
||||||
|
1. evaluates the build helper and verifies the build overlay plus canonical compatibility links
|
||||||
|
2. recovers the materialized FreeBSD source store from:
|
||||||
|
- `/run/current-system/metadata/store-layout.scm`
|
||||||
|
3. runs the native FreeBSD build/install phases inside the guest
|
||||||
|
4. records staged results under:
|
||||||
|
- `/var/lib/fruix/native-builds/<run-id>`
|
||||||
|
- `/var/lib/fruix/native-builds/latest`
|
||||||
|
5. emits promotion metadata for first-class artifact identities covering:
|
||||||
|
- `world`
|
||||||
|
- `kernel`
|
||||||
|
- `headers`
|
||||||
|
- `bootloader`
|
||||||
|
6. keeps the heavier object/stage work under:
|
||||||
|
- `/var/tmp/fruix-self-hosted-native-builds/<run-id>`
|
||||||
|
|
||||||
|
Important current detail:
|
||||||
|
|
||||||
|
- the self-hosted helper now uses the separate `fruix-build-environment` contract instead of reusing the interactive development helper wholesale
|
||||||
|
- that build helper intentionally clears development-shell exports such as `MAKEFLAGS`, `CPPFLAGS`, `CFLAGS`, `CXXFLAGS`, and `LDFLAGS` before `buildworld`
|
||||||
|
- this keeps the base-build path closer to the exact contract needed for real world/kernel bootstrap work
|
||||||
|
|
||||||
|
So the validated Phase 20.3 answer is:
|
||||||
|
|
||||||
|
- a controlled guest self-hosted base-build prototype now works
|
||||||
|
- but the simpler default operator flow should still be the Phase 20.2 host-initiated in-guest path unless there is a specific reason to push the build loop farther into the guest
|
||||||
|
|
||||||
|
### Promoting native-build results into first-class Fruix store objects
|
||||||
|
|
||||||
|
The guest-side result root is now explicitly a **staging/result area**, not the final immutable identity.
|
||||||
|
|
||||||
|
Current validated flow:
|
||||||
|
|
||||||
|
1. run the in-guest helper so the guest records a result under:
|
||||||
|
- `/var/lib/fruix/native-builds/<run-id>`
|
||||||
|
2. copy that result root back to the host
|
||||||
|
3. run:
|
||||||
|
|
||||||
|
```sh
|
||||||
|
fruix native-build promote RESULT_ROOT
|
||||||
|
```
|
||||||
|
|
||||||
|
The promotion step creates immutable `/frx/store` identities for:
|
||||||
|
|
||||||
|
- `world`
|
||||||
|
- `kernel`
|
||||||
|
- `headers`
|
||||||
|
- `bootloader`
|
||||||
|
|
||||||
|
and also creates a result-bundle store object that references those promoted artifact stores.
|
||||||
|
|
||||||
|
Current metadata split:
|
||||||
|
|
||||||
|
- mutable staging/result root:
|
||||||
|
- `/var/lib/fruix/native-builds/<run-id>`
|
||||||
|
- immutable artifact stores:
|
||||||
|
- `/frx/store/...-fruix-native-world-...`
|
||||||
|
- `/frx/store/...-fruix-native-kernel-...`
|
||||||
|
- `/frx/store/...-fruix-native-headers-...`
|
||||||
|
- `/frx/store/...-fruix-native-bootloader-...`
|
||||||
|
- immutable result bundle:
|
||||||
|
- `/frx/store/...-fruix-native-build-result-...`
|
||||||
|
|
||||||
|
The promoted store objects record explicit Fruix-native metadata including at least:
|
||||||
|
|
||||||
|
- executor kind / name / version
|
||||||
|
- run-id / guest-host-name
|
||||||
|
- closure path
|
||||||
|
- source store provenance
|
||||||
|
- build policy
|
||||||
|
- artifact kind
|
||||||
|
- required-file expectations
|
||||||
|
- recorded content signatures and hashes
|
||||||
|
|
||||||
|
This is the current Fruix-native answer to the question:
|
||||||
|
|
||||||
|
- where should mutable native-build state live?
|
||||||
|
- `/var/lib/fruix/native-builds/...`
|
||||||
|
- where should immutable native-build identity live?
|
||||||
|
- `/frx/store/...`
|
||||||
|
|
||||||
|
### Using promoted native-build results in system declarations
|
||||||
|
|
||||||
|
Fruix system declarations can now refer directly to a promoted native-build result bundle.
|
||||||
|
|
||||||
|
Current declaration-level helpers are:
|
||||||
|
|
||||||
|
- `promoted-native-build-result`
|
||||||
|
- `operating-system-from-promoted-native-build-result`
|
||||||
|
|
||||||
|
Representative pattern:
|
||||||
|
|
||||||
|
```scheme
|
||||||
|
(define promoted
|
||||||
|
(promoted-native-build-result
|
||||||
|
#:store-path "/frx/store/...-fruix-native-build-result-..."))
|
||||||
|
|
||||||
|
(define os
|
||||||
|
(operating-system-from-promoted-native-build-result
|
||||||
|
promoted
|
||||||
|
#:host-name "fruix-freebsd"
|
||||||
|
...))
|
||||||
|
```
|
||||||
|
|
||||||
|
That now gives Fruix a more product-like story:
|
||||||
|
|
||||||
|
1. a build runs under some executor policy
|
||||||
|
2. Fruix records the staged mutable result
|
||||||
|
3. Fruix promotes it into immutable store identities
|
||||||
|
4. a later system declaration can point at that promoted result identity
|
||||||
|
5. Fruix materializes and boots a normal system from that promoted identity
|
||||||
|
|
||||||
|
The resulting closure now records that provenance explicitly through:
|
||||||
|
|
||||||
|
- `metadata/promoted-native-build-result.scm`
|
||||||
|
- `metadata/store-layout.scm`
|
||||||
|
- closure references that retain the selected result-bundle store path
|
||||||
|
|
||||||
|
So the operator-facing statement is now:
|
||||||
|
|
||||||
|
- “this Fruix system is based on promoted native-base result X”
|
||||||
|
|
||||||
|
not only:
|
||||||
|
|
||||||
|
- “some earlier build happened and its files were copied somewhere.”
|
||||||
|
|
||||||
|
### Native-build executor model
|
||||||
|
|
||||||
|
Fruix now has an explicit executor model for native base builds.
|
||||||
|
|
||||||
|
Current executor kinds are:
|
||||||
|
|
||||||
|
- `host`
|
||||||
|
- `ssh-guest`
|
||||||
|
- `self-hosted`
|
||||||
|
|
||||||
|
and the intended future extension points are:
|
||||||
|
|
||||||
|
- `jail`
|
||||||
|
- `remote-builder`
|
||||||
|
|
||||||
|
The important change is architectural:
|
||||||
|
|
||||||
|
- declared source identity stays the same
|
||||||
|
- expected artifact kinds stay the same
|
||||||
|
- result/promotion metadata shape stays the same
|
||||||
|
- only the executor policy changes
|
||||||
|
|
||||||
|
So “where the build runs” is now treated as executor policy rather than as a separate native-build architecture each time.
|
||||||
|
|
||||||
|
Current end-to-end validated executors for the staged-result-plus-promotion model are:
|
||||||
|
|
||||||
|
- `ssh-guest`
|
||||||
|
- `self-hosted`
|
||||||
|
|
||||||
|
Both now converge on the same Fruix-native flow:
|
||||||
|
|
||||||
|
1. run the build under a selected executor
|
||||||
|
2. stage a result root under `/var/lib/fruix/native-builds/...`
|
||||||
|
3. emit the same promotion/provenance shape
|
||||||
|
4. promote the result into immutable `/frx/store/...` objects
|
||||||
|
|
||||||
|
## Deployment patterns
|
||||||
|
|
||||||
|
### 1. Build-first workflow
|
||||||
|
|
||||||
|
The default Fruix operator workflow starts by building the closure first:
|
||||||
|
|
||||||
|
1. edit the system declaration
|
||||||
|
2. run `fruix system build`
|
||||||
|
3. inspect emitted metadata
|
||||||
|
4. if needed, produce one of:
|
||||||
|
- `rootfs`
|
||||||
|
- `image`
|
||||||
|
- `install`
|
||||||
|
- `installer`
|
||||||
|
- `installer-iso`
|
||||||
|
|
||||||
|
This keeps the declaration-to-closure boundary explicit.
|
||||||
|
|
||||||
|
### 2. VM image deployment workflow
|
||||||
|
|
||||||
|
Use this when you want to boot a system directly rather than through an installer.
|
||||||
|
|
||||||
|
1. run `fruix system image`
|
||||||
|
2. boot the image in QEMU or convert/import it for XCP-ng
|
||||||
|
3. validate:
|
||||||
|
- `/run/current-system`
|
||||||
|
- shepherd/sshd state
|
||||||
|
- activation log
|
||||||
|
4. keep the closure path from the build metadata as the deployment identity
|
||||||
|
|
||||||
|
This is the current canonical direct deployment path for already-built images.
|
||||||
|
|
||||||
|
### 3. Direct installation workflow
|
||||||
|
|
||||||
|
Use this when you want an installed target image or disk without a booted installer guest.
|
||||||
|
|
||||||
|
1. run `fruix system install --target ...`
|
||||||
|
2. let Fruix partition, format, populate, and install the target
|
||||||
|
3. boot the installed result
|
||||||
|
4. validate `/var/lib/fruix/install.scm` and target services
|
||||||
|
|
||||||
|
This is the most direct install path.
|
||||||
|
|
||||||
|
### 4. Installer-environment workflow
|
||||||
|
|
||||||
|
Use this when the install itself should happen from inside a booted Fruix environment.
|
||||||
|
|
||||||
|
1. run `fruix system installer`
|
||||||
|
2. boot the installer disk image
|
||||||
|
3. let the in-guest installer run onto the selected target device
|
||||||
|
4. boot the installed target
|
||||||
|
|
||||||
|
This is useful when the installer environment itself is part of what needs validation.
|
||||||
|
|
||||||
|
### 5. Installer-ISO workflow
|
||||||
|
|
||||||
|
Use this when the desired operator artifact is a bootable UEFI ISO.
|
||||||
|
|
||||||
|
1. run `fruix system installer-iso`
|
||||||
|
2. boot the ISO under the target virtualization path
|
||||||
|
3. let the in-guest installer run onto the selected target device
|
||||||
|
4. eject the ISO and reboot the installed target
|
||||||
|
|
||||||
|
This is now validated on both:
|
||||||
|
|
||||||
|
- local `QEMU/UEFI/TCG`
|
||||||
|
- the approved real `XCP-ng` VM path
|
||||||
|
|
||||||
|
## Install-target device conventions
|
||||||
|
|
||||||
|
The install target device is not identical across all boot styles.
|
||||||
|
|
||||||
|
Current validated defaults are:
|
||||||
|
|
||||||
|
- direct installer disk-image path under QEMU:
|
||||||
|
- `/dev/vtbd1`
|
||||||
|
- installer ISO path under QEMU:
|
||||||
|
- `/dev/vtbd0`
|
||||||
|
- installer ISO path under XCP-ng:
|
||||||
|
- `/dev/ada0`
|
||||||
|
|
||||||
|
Therefore the canonical workflow is:
|
||||||
|
|
||||||
|
- always treat `--install-target-device` as an explicit deployment parameter when moving between virtualization environments
|
||||||
|
|
||||||
|
Do not assume that a device name validated in one harness is portable to another.
|
||||||
|
|
||||||
|
## Installed-system generation layout
|
||||||
|
|
||||||
|
Installed Fruix systems now record an explicit first-generation deployment layout under:
|
||||||
|
|
||||||
|
- `/var/lib/fruix/system`
|
||||||
|
|
||||||
|
Initial installed shape:
|
||||||
|
|
||||||
|
```text
|
||||||
|
/var/lib/fruix/system/
|
||||||
|
current -> generations/1
|
||||||
|
current-generation
|
||||||
|
generations/
|
||||||
|
1/
|
||||||
|
closure -> /frx/store/...-fruix-system-...
|
||||||
|
metadata.scm
|
||||||
|
provenance.scm
|
||||||
|
install.scm # present on installed targets
|
||||||
|
```
|
||||||
|
|
||||||
|
After a validated in-place switch, the layout extends to:
|
||||||
|
|
||||||
|
```text
|
||||||
|
/var/lib/fruix/system/
|
||||||
|
current -> generations/2
|
||||||
|
current-generation
|
||||||
|
rollback -> generations/1
|
||||||
|
rollback-generation
|
||||||
|
generations/
|
||||||
|
1/
|
||||||
|
...
|
||||||
|
2/
|
||||||
|
closure -> /frx/store/...-fruix-system-...
|
||||||
|
metadata.scm
|
||||||
|
provenance.scm
|
||||||
|
install.scm # deployment metadata for the switch operation
|
||||||
|
```
|
||||||
|
|
||||||
|
Installed systems also now create explicit GC-root-style deployment links under:
|
||||||
|
|
||||||
|
- `/frx/var/fruix/gcroots`
|
||||||
|
|
||||||
|
Current validated shape:
|
||||||
|
|
||||||
|
```text
|
||||||
|
/frx/var/fruix/gcroots/
|
||||||
|
current-system -> /frx/store/...-fruix-system-...
|
||||||
|
rollback-system -> /frx/store/...-fruix-system-...
|
||||||
|
system-1 -> /frx/store/...-fruix-system-...
|
||||||
|
system-2 -> /frx/store/...-fruix-system-...
|
||||||
|
```
|
||||||
|
|
||||||
|
Important detail:
|
||||||
|
|
||||||
|
- `/run/current-system` still points directly at the active closure path in `/frx/store`
|
||||||
|
- the explicit generation layout therefore adds deployment metadata and retention roots without changing the already-validated runtime contract used by activation, rc.d wiring, and tests
|
||||||
|
|
||||||
|
## Roll-forward workflow
|
||||||
|
|
||||||
|
The current Fruix roll-forward model now has two validated layers.
|
||||||
|
|
||||||
|
### Declaration/deployment roll-forward
|
||||||
|
|
||||||
|
Canonical process:
|
||||||
|
|
||||||
|
1. keep the current known-good system declaration
|
||||||
|
2. prepare a candidate declaration
|
||||||
|
- this may differ by FreeBSD base identity
|
||||||
|
- source revision
|
||||||
|
- services
|
||||||
|
- users/groups
|
||||||
|
- or other operating-system fields
|
||||||
|
3. run `fruix system build` for the candidate
|
||||||
|
4. materialize either:
|
||||||
|
- `fruix system image`
|
||||||
|
- `fruix system install`
|
||||||
|
- `fruix system installer`
|
||||||
|
- `fruix system installer-iso`
|
||||||
|
5. boot or install the candidate
|
||||||
|
6. validate the candidate closure in the booted system
|
||||||
|
|
||||||
|
### Installed-system generation roll-forward
|
||||||
|
|
||||||
|
When the candidate closure is already present on an installed target:
|
||||||
|
|
||||||
|
1. run `fruix system switch /frx/store/...candidate...`
|
||||||
|
2. confirm the staged state with `fruix system status`
|
||||||
|
3. reboot into the candidate generation
|
||||||
|
4. validate the new active closure after reboot
|
||||||
|
|
||||||
|
The important property is still that the candidate closure appears beside the earlier one in `/frx/store` rather than mutating it in place.
|
||||||
|
|
||||||
|
## Rollback workflow
|
||||||
|
|
||||||
|
The current canonical rollback workflow also now has two validated layers.
|
||||||
|
|
||||||
|
### Declaration/deployment rollback
|
||||||
|
|
||||||
|
You can still roll back by redeploying the earlier declaration:
|
||||||
|
|
||||||
|
1. retain the earlier declaration that produced the known-good closure
|
||||||
|
2. rebuild or rematerialize that earlier declaration
|
||||||
|
3. redeploy or reboot that earlier artifact again
|
||||||
|
|
||||||
|
Concretely, the usual declaration-level rollback choices are:
|
||||||
|
|
||||||
|
- rebuild the earlier declaration with `fruix system build` and confirm the old closure path reappears
|
||||||
|
- boot the earlier declaration again through `fruix system image`
|
||||||
|
- reinstall the earlier declaration through `fruix system install`, `installer`, or `installer-iso` if the deployment medium itself must change
|
||||||
|
|
||||||
|
### Installed-system generation rollback
|
||||||
|
|
||||||
|
When an installed target already has both the current and rollback generations recorded:
|
||||||
|
|
||||||
|
1. run `fruix system rollback`
|
||||||
|
2. confirm the staged state with `fruix system status`
|
||||||
|
3. reboot into the rollback generation
|
||||||
|
4. validate the restored active closure after reboot
|
||||||
|
|
||||||
|
This installed-system rollback path is now validated on local `QEMU/UEFI/TCG`.
|
||||||
|
|
||||||
|
### Important scope note
|
||||||
|
|
||||||
|
This is still not yet the same thing as Guix's full `reconfigure`/generation UX.
|
||||||
|
|
||||||
|
Current installed-system rollback is intentionally modest:
|
||||||
|
|
||||||
|
- it switches between already-recorded generations on the target
|
||||||
|
- it does not yet fetch candidate closures onto the machine for you
|
||||||
|
- it does not yet expose a richer history-management or generation-pruning policy
|
||||||
|
|
||||||
|
Still pending:
|
||||||
|
|
||||||
|
- operator-facing closure transfer or fetch onto installed systems
|
||||||
|
- multi-generation lifecycle policy beyond the validated `current` and `rollback` pointers
|
||||||
|
- a fuller `reconfigure`-style installed-system UX
|
||||||
|
|
||||||
|
## Provenance and deployment identity
|
||||||
|
|
||||||
|
For any serious deployment or rollback decision, the canonical identity is not merely the host name. It is the emitted metadata:
|
||||||
|
|
||||||
|
- `closure_path`
|
||||||
|
- declared FreeBSD base/source metadata
|
||||||
|
- materialized source store paths
|
||||||
|
- install metadata at `/var/lib/fruix/install.scm`
|
||||||
|
- store item counts and reference lists
|
||||||
|
|
||||||
|
Operators should retain metadata from successful candidate and current deployments because Fruix already emits enough data to answer:
|
||||||
|
|
||||||
|
- which declaration was built
|
||||||
|
- which closure booted
|
||||||
|
- which source snapshot was materialized
|
||||||
|
- which target device or image was installed
|
||||||
|
|
||||||
|
## Current limitations
|
||||||
|
|
||||||
|
The deployment workflow is now coherent, and Fruix now has a validated installed-system switch/rollback path, but it is still not the final generation-management story.
|
||||||
|
|
||||||
|
Not yet first-class:
|
||||||
|
|
||||||
|
- host-side closure transfer/fetch onto installed systems as part of `fruix system switch`
|
||||||
|
- a fuller `reconfigure` workflow that builds and stages the new closure from inside the target environment
|
||||||
|
- multi-generation lifecycle policy beyond the validated `current` and `rollback` pointers
|
||||||
|
- generation pruning and retention policy independent of full redeploy
|
||||||
|
|
||||||
|
Those are the next logical steps after the current explicit-generation switch/rollback model.
|
||||||
|
|
||||||
|
## Summary
|
||||||
|
|
||||||
|
The current canonical Fruix deployment model is:
|
||||||
|
|
||||||
|
- **declare** a system in Scheme
|
||||||
|
- **build** the closure with `fruix system build`
|
||||||
|
- **materialize** the artifact appropriate to the deployment target
|
||||||
|
- **boot or install** that artifact
|
||||||
|
- **identify deployments by closure path and provenance metadata**
|
||||||
|
- on installed systems, **switch** to a staged candidate with `fruix system switch`
|
||||||
|
- on installed systems, **roll back** to the recorded rollback generation with `fruix system rollback`
|
||||||
|
- still use declaration/redeploy rollback when the target does not already have the desired closure staged locally
|
||||||
|
|
||||||
|
That is the operator-facing workflow Fruix should document and use while its installed-system generation UX remains simpler than Guix's mature in-place system-generation workflow.
|
||||||
@@ -1,6 +1,8 @@
|
|||||||
(define-module (fruix system freebsd)
|
(define-module (fruix system freebsd)
|
||||||
#:use-module (fruix system freebsd model)
|
#:use-module (fruix system freebsd model)
|
||||||
#:use-module (fruix system freebsd source)
|
#:use-module (fruix system freebsd source)
|
||||||
|
#:use-module (fruix system freebsd executor)
|
||||||
|
#:use-module (fruix system freebsd build)
|
||||||
#:use-module (fruix system freebsd media)
|
#:use-module (fruix system freebsd media)
|
||||||
#:re-export (user-group
|
#:re-export (user-group
|
||||||
user-group?
|
user-group?
|
||||||
@@ -24,13 +26,21 @@
|
|||||||
file-system-type
|
file-system-type
|
||||||
file-system-options
|
file-system-options
|
||||||
file-system-needed-for-boot?
|
file-system-needed-for-boot?
|
||||||
|
promoted-native-build-result?
|
||||||
|
promoted-native-build-result-store-path
|
||||||
|
promoted-native-build-result-metadata-file
|
||||||
|
promoted-native-build-result-metadata
|
||||||
|
promoted-native-build-result-spec
|
||||||
operating-system
|
operating-system
|
||||||
operating-system?
|
operating-system?
|
||||||
operating-system-host-name
|
operating-system-host-name
|
||||||
operating-system-freebsd-base
|
operating-system-freebsd-base
|
||||||
|
operating-system-native-build-result
|
||||||
operating-system-kernel
|
operating-system-kernel
|
||||||
operating-system-bootloader
|
operating-system-bootloader
|
||||||
operating-system-base-packages
|
operating-system-base-packages
|
||||||
|
operating-system-development-packages
|
||||||
|
operating-system-build-packages
|
||||||
operating-system-users
|
operating-system-users
|
||||||
operating-system-groups
|
operating-system-groups
|
||||||
operating-system-file-systems
|
operating-system-file-systems
|
||||||
@@ -42,14 +52,36 @@
|
|||||||
operating-system-root-authorized-keys
|
operating-system-root-authorized-keys
|
||||||
validate-operating-system
|
validate-operating-system
|
||||||
materialize-freebsd-source
|
materialize-freebsd-source
|
||||||
|
native-build-executor
|
||||||
|
native-build-executor?
|
||||||
|
native-build-executor-ref
|
||||||
|
native-build-executor-kind
|
||||||
|
native-build-executor-name
|
||||||
|
native-build-executor-version
|
||||||
|
native-build-executor-properties
|
||||||
|
normalize-native-build-executor
|
||||||
|
host-native-build-executor
|
||||||
|
ssh-guest-native-build-executor
|
||||||
|
self-hosted-native-build-executor
|
||||||
|
promoted-native-build-result
|
||||||
|
promoted-native-build-result->freebsd-base
|
||||||
|
promoted-native-build-result-artifact-store
|
||||||
|
promoted-native-build-result-kernel-package
|
||||||
|
promoted-native-build-result-bootloader-package
|
||||||
|
promoted-native-build-result-base-packages
|
||||||
|
promoted-native-build-result-development-packages
|
||||||
|
operating-system-from-promoted-native-build-result
|
||||||
|
promote-native-build-result
|
||||||
operating-system-closure-spec
|
operating-system-closure-spec
|
||||||
operating-system-install-spec
|
operating-system-install-spec
|
||||||
operating-system-image-spec
|
operating-system-image-spec
|
||||||
operating-system-installer-image-spec
|
operating-system-installer-image-spec
|
||||||
|
operating-system-installer-iso-spec
|
||||||
installer-operating-system
|
installer-operating-system
|
||||||
materialize-operating-system
|
materialize-operating-system
|
||||||
materialize-rootfs
|
materialize-rootfs
|
||||||
install-operating-system
|
install-operating-system
|
||||||
materialize-bhyve-image
|
materialize-bhyve-image
|
||||||
materialize-installer-image
|
materialize-installer-image
|
||||||
|
materialize-installer-iso
|
||||||
default-minimal-operating-system))
|
default-minimal-operating-system))
|
||||||
|
|||||||
@@ -2,6 +2,7 @@
|
|||||||
#:use-module (fruix packages freebsd)
|
#:use-module (fruix packages freebsd)
|
||||||
#:use-module (fruix system freebsd model)
|
#:use-module (fruix system freebsd model)
|
||||||
#:use-module (fruix system freebsd source)
|
#:use-module (fruix system freebsd source)
|
||||||
|
#:use-module (fruix system freebsd executor)
|
||||||
#:use-module (fruix system freebsd utils)
|
#:use-module (fruix system freebsd utils)
|
||||||
#:use-module (guix build utils)
|
#:use-module (guix build utils)
|
||||||
#:use-module (ice-9 format)
|
#:use-module (ice-9 format)
|
||||||
@@ -10,7 +11,16 @@
|
|||||||
#:use-module (srfi srfi-1)
|
#:use-module (srfi srfi-1)
|
||||||
#:use-module (srfi srfi-13)
|
#:use-module (srfi srfi-13)
|
||||||
#:export (host-freebsd-provenance
|
#:export (host-freebsd-provenance
|
||||||
|
promoted-native-build-result
|
||||||
|
promoted-native-build-result->freebsd-base
|
||||||
|
promoted-native-build-result-artifact-store
|
||||||
|
promoted-native-build-result-kernel-package
|
||||||
|
promoted-native-build-result-bootloader-package
|
||||||
|
promoted-native-build-result-base-packages
|
||||||
|
promoted-native-build-result-development-packages
|
||||||
|
operating-system-from-promoted-native-build-result
|
||||||
materialize-freebsd-package
|
materialize-freebsd-package
|
||||||
|
promote-native-build-result
|
||||||
materialize-prefix))
|
materialize-prefix))
|
||||||
|
|
||||||
(define (host-freebsd-provenance)
|
(define (host-freebsd-provenance)
|
||||||
@@ -155,7 +165,7 @@
|
|||||||
|
|
||||||
(define (native-build-root common)
|
(define (native-build-root common)
|
||||||
(string-append "/var/tmp/fruix-freebsd-native-build-"
|
(string-append "/var/tmp/fruix-freebsd-native-build-"
|
||||||
(string-hash (object->string common))))
|
(sha256-string (object->string common))))
|
||||||
|
|
||||||
(define (native-make-arguments common _build-root)
|
(define (native-make-arguments common _build-root)
|
||||||
(append
|
(append
|
||||||
@@ -254,7 +264,7 @@
|
|||||||
(let* ((plan (freebsd-package-install-plan package))
|
(let* ((plan (freebsd-package-install-plan package))
|
||||||
(common (native-build-common-manifest plan))
|
(common (native-build-common-manifest plan))
|
||||||
(build-root (native-build-root common))
|
(build-root (native-build-root common))
|
||||||
(stage-root (string-append build-root "/stage-" (freebsd-package-name package) "-" (string-hash manifest)))
|
(stage-root (string-append build-root "/stage-" (freebsd-package-name package) "-" (sha256-string manifest)))
|
||||||
(install-log (string-append build-root "/logs/install-" (freebsd-package-name package) ".log"))
|
(install-log (string-append build-root "/logs/install-" (freebsd-package-name package) ".log"))
|
||||||
(final-stage-root
|
(final-stage-root
|
||||||
(case (freebsd-package-build-system package)
|
(case (freebsd-package-build-system package)
|
||||||
@@ -312,7 +322,7 @@
|
|||||||
#:sha256 (build-plan-ref plan 'base-source-sha256 #f)))
|
#:sha256 (build-plan-ref plan 'base-source-sha256 #f)))
|
||||||
|
|
||||||
(define (source-cache-key source)
|
(define (source-cache-key source)
|
||||||
(string-hash (object->string (freebsd-source-spec source))))
|
(sha256-string (object->string (freebsd-source-spec source))))
|
||||||
|
|
||||||
(define (materialize-freebsd-source/cached source store-dir source-cache)
|
(define (materialize-freebsd-source/cached source store-dir source-cache)
|
||||||
(let* ((key (source-cache-key source))
|
(let* ((key (source-cache-key source))
|
||||||
@@ -339,55 +349,505 @@
|
|||||||
(append overrides plan)))
|
(append overrides plan)))
|
||||||
|
|
||||||
(define* (materialize-freebsd-package package store-dir cache #:optional source-cache)
|
(define* (materialize-freebsd-package package store-dir cache #:optional source-cache)
|
||||||
(let* ((source-cache (or source-cache (make-hash-table)))
|
(if (existing-store-package? package)
|
||||||
(input-paths (map (lambda (input)
|
(validate-existing-store-package package)
|
||||||
(materialize-freebsd-package input store-dir cache source-cache))
|
(let* ((source-cache (or source-cache (make-hash-table)))
|
||||||
(freebsd-package-inputs package)))
|
(input-paths (map (lambda (input)
|
||||||
(prepared-package
|
(materialize-freebsd-package input store-dir cache source-cache))
|
||||||
(if (freebsd-native-build-package? package)
|
(freebsd-package-inputs package)))
|
||||||
(let* ((source (plan-freebsd-source (freebsd-package-install-plan package)))
|
(prepared-package
|
||||||
(source-result (materialize-freebsd-source/cached source store-dir source-cache))
|
(if (freebsd-native-build-package? package)
|
||||||
(plan (plan-with-materialized-source (freebsd-package-install-plan package)
|
(let* ((source (plan-freebsd-source (freebsd-package-install-plan package)))
|
||||||
source-result)))
|
(source-result (materialize-freebsd-source/cached source store-dir source-cache))
|
||||||
(package-with-install-plan package plan))
|
(plan (plan-with-materialized-source (freebsd-package-install-plan package)
|
||||||
package))
|
source-result)))
|
||||||
(effective-input-paths
|
(package-with-install-plan package plan))
|
||||||
(if (freebsd-native-build-package? package)
|
package))
|
||||||
(cons (build-plan-ref (freebsd-package-install-plan prepared-package)
|
(effective-input-paths
|
||||||
'materialized-source-store
|
(if (freebsd-native-build-package? package)
|
||||||
#f)
|
(cons (build-plan-ref (freebsd-package-install-plan prepared-package)
|
||||||
input-paths)
|
'materialized-source-store
|
||||||
input-paths))
|
#f)
|
||||||
(effective-input-paths (filter identity effective-input-paths))
|
input-paths)
|
||||||
(manifest (package-manifest-string prepared-package effective-input-paths))
|
input-paths))
|
||||||
(cache-key (string-hash manifest))
|
(effective-input-paths (filter identity effective-input-paths))
|
||||||
(cached (hash-ref cache cache-key #f)))
|
(manifest (package-manifest-string prepared-package effective-input-paths))
|
||||||
(if cached
|
(cache-key (sha256-string manifest))
|
||||||
cached
|
(cached (hash-ref cache cache-key #f)))
|
||||||
(let* ((hash (string-hash manifest))
|
(if cached
|
||||||
(output-path (string-append store-dir "/" hash "-"
|
cached
|
||||||
(freebsd-package-name prepared-package)
|
(let* ((display-name (string-append (freebsd-package-name prepared-package)
|
||||||
"-"
|
"-"
|
||||||
(freebsd-package-version prepared-package))))
|
(freebsd-package-version prepared-package)))
|
||||||
(unless (file-exists? output-path)
|
(output-path (make-store-path store-dir display-name manifest
|
||||||
(case (freebsd-package-build-system prepared-package)
|
#:kind 'freebsd-package)))
|
||||||
((copy-build-system)
|
(unless (file-exists? output-path)
|
||||||
(mkdir-p output-path)
|
(case (freebsd-package-build-system prepared-package)
|
||||||
(for-each (lambda (entry)
|
((copy-build-system)
|
||||||
(materialize-plan-entry output-path entry))
|
(mkdir-p output-path)
|
||||||
(freebsd-package-install-plan prepared-package))
|
(for-each (lambda (entry)
|
||||||
(write-file (string-append output-path "/.references")
|
(materialize-plan-entry output-path entry))
|
||||||
(string-join effective-input-paths "\n"))
|
(freebsd-package-install-plan prepared-package))
|
||||||
(write-file (string-append output-path "/.fruix-package") manifest))
|
(write-file (string-append output-path "/.references")
|
||||||
((freebsd-world-build-system freebsd-kernel-build-system)
|
(string-join effective-input-paths "\n"))
|
||||||
(materialize-native-freebsd-package prepared-package effective-input-paths manifest output-path))
|
(write-file (string-append output-path "/.fruix-package") manifest))
|
||||||
(else
|
((freebsd-world-build-system freebsd-kernel-build-system)
|
||||||
(error (format #f "unsupported package build system: ~a"
|
(materialize-native-freebsd-package prepared-package effective-input-paths manifest output-path))
|
||||||
(freebsd-package-build-system prepared-package))))))
|
(else
|
||||||
(hash-set! cache cache-key output-path)
|
(error (format #f "unsupported package build system: ~a"
|
||||||
output-path))))
|
(freebsd-package-build-system prepared-package))))))
|
||||||
|
(hash-set! cache cache-key output-path)
|
||||||
|
output-path)))))
|
||||||
|
|
||||||
|
|
||||||
|
(define native-build-result-promotion-version "1")
|
||||||
|
|
||||||
|
(define (native-build-result-ref data key default)
|
||||||
|
(match (assoc key data)
|
||||||
|
((_ . value) value)
|
||||||
|
(#f default)))
|
||||||
|
|
||||||
|
(define (native-build-result-executor result)
|
||||||
|
(let* ((executor (native-build-result-ref result 'executor #f))
|
||||||
|
(legacy-version (native-build-result-ref result 'executor-version "legacy")))
|
||||||
|
(cond
|
||||||
|
((native-build-executor? executor)
|
||||||
|
executor)
|
||||||
|
((string? executor)
|
||||||
|
(let ((normalized (normalize-native-build-executor executor)))
|
||||||
|
`((kind . ,(native-build-executor-kind normalized))
|
||||||
|
(name . ,(native-build-executor-name normalized))
|
||||||
|
(version . ,legacy-version)
|
||||||
|
(properties . ,(native-build-executor-properties normalized)))))
|
||||||
|
(else
|
||||||
|
(native-build-executor #:kind 'unknown
|
||||||
|
#:name "unknown"
|
||||||
|
#:version legacy-version)))))
|
||||||
|
|
||||||
|
(define (native-build-result-executor-kind result)
|
||||||
|
(native-build-executor-kind (native-build-result-executor result)))
|
||||||
|
|
||||||
|
(define (native-build-result-executor-name result)
|
||||||
|
(native-build-executor-name (native-build-result-executor result)))
|
||||||
|
|
||||||
|
(define (native-build-result-executor-version result)
|
||||||
|
(native-build-executor-version (native-build-result-executor result)))
|
||||||
|
|
||||||
|
(define (read-native-build-result result-root)
|
||||||
|
(let ((promotion-file (string-append result-root "/promotion.scm")))
|
||||||
|
(unless (file-exists? promotion-file)
|
||||||
|
(error "native build result is missing promotion.scm" result-root))
|
||||||
|
(let ((result (call-with-input-file promotion-file read)))
|
||||||
|
(unless (equal? (native-build-result-ref result 'native-build-result-version #f)
|
||||||
|
native-build-result-promotion-version)
|
||||||
|
(error "unsupported native build result promotion version" promotion-file))
|
||||||
|
result)))
|
||||||
|
|
||||||
|
(define existing-store-package-build-system 'existing-store-item-build-system)
|
||||||
|
|
||||||
|
(define (existing-store-package? package)
|
||||||
|
(eq? (freebsd-package-build-system package)
|
||||||
|
existing-store-package-build-system))
|
||||||
|
|
||||||
|
(define (existing-store-package-ref package key default)
|
||||||
|
(build-plan-ref (freebsd-package-install-plan package) key default))
|
||||||
|
|
||||||
|
(define (validate-existing-store-package package)
|
||||||
|
(let* ((store-path (existing-store-package-ref package 'store-path #f))
|
||||||
|
(required-file (existing-store-package-ref package 'required-file #f))
|
||||||
|
(metadata-file (existing-store-package-ref package 'metadata-file #f)))
|
||||||
|
(unless (and (string? store-path) (file-exists? store-path))
|
||||||
|
(error "existing-store package is missing a valid store path" package store-path))
|
||||||
|
(when metadata-file
|
||||||
|
(unless (file-exists? metadata-file)
|
||||||
|
(error "existing-store package metadata file is missing" package metadata-file)))
|
||||||
|
(when required-file
|
||||||
|
(unless (file-exists? (string-append store-path "/" required-file))
|
||||||
|
(error "existing-store package is missing required file"
|
||||||
|
package
|
||||||
|
(string-append store-path "/" required-file))))
|
||||||
|
store-path))
|
||||||
|
|
||||||
|
(define (normalize-promoted-native-build-result value)
|
||||||
|
(cond
|
||||||
|
((promoted-native-build-result? value)
|
||||||
|
value)
|
||||||
|
((string? value)
|
||||||
|
(promoted-native-build-result #:store-path value))
|
||||||
|
(else
|
||||||
|
(error "expected a promoted native build result or store path" value))))
|
||||||
|
|
||||||
|
(define (read-promoted-native-build-artifact-metadata metadata-file)
|
||||||
|
(unless (file-exists? metadata-file)
|
||||||
|
(error "promoted native build artifact metadata file is missing" metadata-file))
|
||||||
|
(let ((metadata (call-with-input-file metadata-file read)))
|
||||||
|
(unless (equal? (native-build-result-ref metadata 'native-build-object-version #f)
|
||||||
|
native-build-result-promotion-version)
|
||||||
|
(error "unsupported promoted native build object version" metadata-file))
|
||||||
|
(unless (eq? (native-build-result-ref metadata 'object-kind #f) 'artifact)
|
||||||
|
(error "promoted native build object is not an artifact" metadata-file))
|
||||||
|
metadata))
|
||||||
|
|
||||||
|
(define (promoted-native-build-result-artifact-entry result artifact-kind)
|
||||||
|
(let* ((metadata (promoted-native-build-result-metadata result))
|
||||||
|
(artifacts (native-build-result-ref metadata 'artifacts '()))
|
||||||
|
(entry (find (lambda (item)
|
||||||
|
(eq? (native-build-result-ref item 'artifact-kind #f)
|
||||||
|
artifact-kind))
|
||||||
|
artifacts)))
|
||||||
|
(unless entry
|
||||||
|
(error "promoted native build result is missing artifact entry" artifact-kind))
|
||||||
|
entry))
|
||||||
|
|
||||||
|
(define (promoted-native-build-result-artifact-store result artifact-kind)
|
||||||
|
(let* ((result (normalize-promoted-native-build-result result))
|
||||||
|
(entry (promoted-native-build-result-artifact-entry result artifact-kind))
|
||||||
|
(store-path (native-build-result-ref entry 'store-path #f))
|
||||||
|
(metadata-file (native-build-result-ref entry 'metadata-file #f))
|
||||||
|
(artifact-metadata (and metadata-file
|
||||||
|
(read-promoted-native-build-artifact-metadata metadata-file)))
|
||||||
|
(required-file (and artifact-metadata
|
||||||
|
(native-build-result-ref artifact-metadata 'required-file #f))))
|
||||||
|
(unless (and (string? store-path) (file-exists? store-path))
|
||||||
|
(error "promoted native build result is missing artifact store" artifact-kind store-path))
|
||||||
|
(when artifact-metadata
|
||||||
|
(unless (eq? (native-build-result-ref artifact-metadata 'artifact-kind #f)
|
||||||
|
artifact-kind)
|
||||||
|
(error "promoted native build artifact metadata kind mismatch"
|
||||||
|
artifact-kind
|
||||||
|
metadata-file)))
|
||||||
|
(when required-file
|
||||||
|
(unless (file-exists? (string-append store-path "/" required-file))
|
||||||
|
(error "promoted native build artifact store is missing required file"
|
||||||
|
artifact-kind
|
||||||
|
(string-append store-path "/" required-file))))
|
||||||
|
store-path))
|
||||||
|
|
||||||
|
(define* (promoted-native-build-result #:key store-path)
|
||||||
|
(unless (and (string? store-path) (file-exists? store-path))
|
||||||
|
(error "promoted native build result store path does not exist" store-path))
|
||||||
|
(let* ((metadata-file (string-append store-path "/.fruix-native-build-result.scm")))
|
||||||
|
(unless (file-exists? metadata-file)
|
||||||
|
(error "promoted native build result store is missing metadata" metadata-file))
|
||||||
|
(let* ((metadata (call-with-input-file metadata-file read))
|
||||||
|
(result (make-promoted-native-build-result store-path metadata-file metadata)))
|
||||||
|
(unless (equal? (native-build-result-ref metadata 'native-build-result-version #f)
|
||||||
|
native-build-result-promotion-version)
|
||||||
|
(error "unsupported promoted native build result version" metadata-file))
|
||||||
|
(unless (eq? (native-build-result-ref metadata 'object-kind #f) 'result-bundle)
|
||||||
|
(error "promoted native build result store does not contain a result bundle" metadata-file))
|
||||||
|
(for-each (lambda (artifact-kind)
|
||||||
|
(promoted-native-build-result-artifact-store result artifact-kind))
|
||||||
|
'(world kernel headers bootloader))
|
||||||
|
result)))
|
||||||
|
|
||||||
|
(define (promoted-native-build-result->freebsd-base result)
|
||||||
|
(let* ((result (normalize-promoted-native-build-result result))
|
||||||
|
(metadata (promoted-native-build-result-metadata result))
|
||||||
|
(base (native-build-result-ref metadata 'freebsd-base '()))
|
||||||
|
(source (native-build-result-ref metadata 'source '()))
|
||||||
|
(source-root (or (native-build-result-ref source 'source-root #f)
|
||||||
|
(native-build-result-ref base 'source-root #f)
|
||||||
|
"/usr/src"))
|
||||||
|
(source-name (string-append "promoted-native-build-result-source-"
|
||||||
|
(path-basename
|
||||||
|
(promoted-native-build-result-store-path result))))
|
||||||
|
(synthetic-source (freebsd-source #:name source-name
|
||||||
|
#:kind 'local-tree
|
||||||
|
#:path source-root)))
|
||||||
|
(freebsd-base #:name (native-build-result-ref base 'name "promoted-native-build-result")
|
||||||
|
#:version-label (native-build-result-ref base 'version-label "unknown")
|
||||||
|
#:release (native-build-result-ref base 'release "unknown")
|
||||||
|
#:branch (native-build-result-ref base 'branch "unknown")
|
||||||
|
#:source synthetic-source
|
||||||
|
#:source-root (native-build-result-ref base 'source-root source-root)
|
||||||
|
#:target (native-build-result-ref base 'target "amd64")
|
||||||
|
#:target-arch (native-build-result-ref base 'target-arch "amd64")
|
||||||
|
#:kernconf (native-build-result-ref base 'kernconf "GENERIC"))))
|
||||||
|
|
||||||
|
(define (promoted-native-build-result-artifact-package result artifact-kind
|
||||||
|
package-name synopsis description)
|
||||||
|
(let* ((result (normalize-promoted-native-build-result result))
|
||||||
|
(metadata (promoted-native-build-result-metadata result))
|
||||||
|
(base (native-build-result-ref metadata 'freebsd-base '()))
|
||||||
|
(entry (promoted-native-build-result-artifact-entry result artifact-kind))
|
||||||
|
(store-path (promoted-native-build-result-artifact-store result artifact-kind))
|
||||||
|
(metadata-file (native-build-result-ref entry 'metadata-file #f))
|
||||||
|
(artifact-metadata (and metadata-file
|
||||||
|
(read-promoted-native-build-artifact-metadata metadata-file)))
|
||||||
|
(required-file (and artifact-metadata
|
||||||
|
(native-build-result-ref artifact-metadata 'required-file #f))))
|
||||||
|
(freebsd-package
|
||||||
|
#:name package-name
|
||||||
|
#:version (native-build-result-ref base 'version-label "unknown")
|
||||||
|
#:build-system existing-store-package-build-system
|
||||||
|
#:home-page "https://www.freebsd.org/"
|
||||||
|
#:synopsis synopsis
|
||||||
|
#:description description
|
||||||
|
#:license 'bsd-2
|
||||||
|
#:install-plan `((store-path . ,store-path)
|
||||||
|
(metadata-file . ,metadata-file)
|
||||||
|
(required-file . ,required-file)
|
||||||
|
(artifact-kind . ,artifact-kind)
|
||||||
|
(result-store . ,(promoted-native-build-result-store-path result))))))
|
||||||
|
|
||||||
|
(define (promoted-native-build-result-world-package result)
|
||||||
|
(promoted-native-build-result-artifact-package
|
||||||
|
result
|
||||||
|
'world
|
||||||
|
"freebsd-promoted-world"
|
||||||
|
"Promoted Fruix-native FreeBSD world artifact"
|
||||||
|
"FreeBSD world artifact imported from a promoted Fruix native-build result bundle."))
|
||||||
|
|
||||||
|
(define (promoted-native-build-result-kernel-package result)
|
||||||
|
(promoted-native-build-result-artifact-package
|
||||||
|
result
|
||||||
|
'kernel
|
||||||
|
"freebsd-promoted-kernel"
|
||||||
|
"Promoted Fruix-native FreeBSD kernel artifact"
|
||||||
|
"FreeBSD kernel artifact imported from a promoted Fruix native-build result bundle."))
|
||||||
|
|
||||||
|
(define (promoted-native-build-result-bootloader-package result)
|
||||||
|
(promoted-native-build-result-artifact-package
|
||||||
|
result
|
||||||
|
'bootloader
|
||||||
|
"freebsd-promoted-bootloader"
|
||||||
|
"Promoted Fruix-native FreeBSD bootloader artifact"
|
||||||
|
"FreeBSD bootloader artifact imported from a promoted Fruix native-build result bundle."))
|
||||||
|
|
||||||
|
(define (promoted-native-build-result-headers-package result)
|
||||||
|
(promoted-native-build-result-artifact-package
|
||||||
|
result
|
||||||
|
'headers
|
||||||
|
"freebsd-promoted-headers"
|
||||||
|
"Promoted Fruix-native FreeBSD headers artifact"
|
||||||
|
"FreeBSD headers artifact imported from a promoted Fruix native-build result bundle."))
|
||||||
|
|
||||||
|
(define (promoted-native-build-result-base-packages result)
|
||||||
|
(list (promoted-native-build-result-world-package result)))
|
||||||
|
|
||||||
|
(define (promoted-native-build-result-development-packages result)
|
||||||
|
(list (promoted-native-build-result-headers-package result)))
|
||||||
|
|
||||||
|
(define* (operating-system-from-promoted-native-build-result result
|
||||||
|
#:key
|
||||||
|
(host-name #f)
|
||||||
|
(freebsd-base #f)
|
||||||
|
(kernel #f)
|
||||||
|
(bootloader #f)
|
||||||
|
(base-packages #f)
|
||||||
|
(development-packages #f)
|
||||||
|
(users #f)
|
||||||
|
(groups #f)
|
||||||
|
(file-systems #f)
|
||||||
|
(services #f)
|
||||||
|
(loader-entries #f)
|
||||||
|
(rc-conf-entries #f)
|
||||||
|
(init-mode #f)
|
||||||
|
(ready-marker #f)
|
||||||
|
(root-authorized-keys #f))
|
||||||
|
(let* ((result (normalize-promoted-native-build-result result))
|
||||||
|
(defaults default-minimal-operating-system)
|
||||||
|
(fallback (lambda (value thunk)
|
||||||
|
(if (eq? value #f) (thunk) value))))
|
||||||
|
(operating-system
|
||||||
|
#:host-name (fallback host-name (lambda () (operating-system-host-name defaults)))
|
||||||
|
#:freebsd-base (fallback freebsd-base (lambda ()
|
||||||
|
(promoted-native-build-result->freebsd-base result)))
|
||||||
|
#:native-build-result result
|
||||||
|
#:kernel (fallback kernel (lambda ()
|
||||||
|
(promoted-native-build-result-kernel-package result)))
|
||||||
|
#:bootloader (fallback bootloader (lambda ()
|
||||||
|
(promoted-native-build-result-bootloader-package result)))
|
||||||
|
#:base-packages (fallback base-packages (lambda ()
|
||||||
|
(promoted-native-build-result-base-packages result)))
|
||||||
|
#:development-packages (fallback development-packages (lambda ()
|
||||||
|
(operating-system-development-packages defaults)))
|
||||||
|
#:users (fallback users (lambda () (operating-system-users defaults)))
|
||||||
|
#:groups (fallback groups (lambda () (operating-system-groups defaults)))
|
||||||
|
#:file-systems (fallback file-systems (lambda () (operating-system-file-systems defaults)))
|
||||||
|
#:services (fallback services (lambda () (operating-system-services defaults)))
|
||||||
|
#:loader-entries (fallback loader-entries (lambda () (operating-system-loader-entries defaults)))
|
||||||
|
#:rc-conf-entries (fallback rc-conf-entries (lambda () (operating-system-rc-conf-entries defaults)))
|
||||||
|
#:init-mode (fallback init-mode (lambda () (operating-system-init-mode defaults)))
|
||||||
|
#:ready-marker (fallback ready-marker (lambda () (operating-system-ready-marker defaults)))
|
||||||
|
#:root-authorized-keys (fallback root-authorized-keys (lambda ()
|
||||||
|
(operating-system-root-authorized-keys defaults))))))
|
||||||
|
|
||||||
|
(define (native-build-artifact-entry result artifact-kind)
|
||||||
|
(let* ((artifacts (native-build-result-ref result 'artifacts '()))
|
||||||
|
(entry (assoc artifact-kind artifacts)))
|
||||||
|
(unless entry
|
||||||
|
(error "native build result is missing artifact entry" artifact-kind))
|
||||||
|
(cdr entry)))
|
||||||
|
|
||||||
|
(define (native-build-artifact-root result-root result artifact-kind)
|
||||||
|
(let* ((entry (native-build-artifact-entry result artifact-kind))
|
||||||
|
(relative-path (native-build-result-ref entry 'path #f))
|
||||||
|
(required-file (native-build-result-ref entry 'required-file #f))
|
||||||
|
(artifact-root (and relative-path
|
||||||
|
(string-append result-root "/" relative-path))))
|
||||||
|
(unless (and artifact-root (file-exists? artifact-root))
|
||||||
|
(error "native build result is missing artifact tree" artifact-kind artifact-root))
|
||||||
|
(when required-file
|
||||||
|
(unless (file-exists? (string-append artifact-root "/" required-file))
|
||||||
|
(error "native build artifact is missing required file"
|
||||||
|
artifact-kind
|
||||||
|
(string-append artifact-root "/" required-file))))
|
||||||
|
artifact-root))
|
||||||
|
|
||||||
|
(define (native-build-existing-store-references result store-dir)
|
||||||
|
(filter identity
|
||||||
|
(map (lambda (path)
|
||||||
|
(and (string? path)
|
||||||
|
(string-prefix? (string-append store-dir "/") path)
|
||||||
|
(file-exists? path)
|
||||||
|
path))
|
||||||
|
(list (native-build-result-ref result 'closure-path #f)
|
||||||
|
(let ((source (native-build-result-ref result 'source '())))
|
||||||
|
(native-build-result-ref source 'store-path #f))))))
|
||||||
|
|
||||||
|
(define (native-build-artifact-display-name result artifact-kind)
|
||||||
|
(let* ((base (native-build-result-ref result 'freebsd-base '()))
|
||||||
|
(version-label (native-build-result-ref base 'version-label "unknown"))
|
||||||
|
(executor-name (native-build-result-executor-name result)))
|
||||||
|
(string-append "fruix-native-"
|
||||||
|
(symbol->string artifact-kind)
|
||||||
|
"-"
|
||||||
|
version-label
|
||||||
|
"-"
|
||||||
|
executor-name)))
|
||||||
|
|
||||||
|
(define (native-build-promoted-artifact-metadata result artifact-kind content-signature)
|
||||||
|
(let* ((entry (native-build-artifact-entry result artifact-kind))
|
||||||
|
(executor (native-build-result-executor result))
|
||||||
|
(build-profile (native-build-result-ref result 'build-profile
|
||||||
|
(native-build-result-ref result 'development-profile ""))))
|
||||||
|
`((native-build-object-version . ,native-build-result-promotion-version)
|
||||||
|
(object-kind . artifact)
|
||||||
|
(artifact-kind . ,artifact-kind)
|
||||||
|
(executor . ,executor)
|
||||||
|
(executor-kind . ,(native-build-result-executor-kind result))
|
||||||
|
(executor-name . ,(native-build-result-executor-name result))
|
||||||
|
(executor-version . ,(native-build-result-executor-version result))
|
||||||
|
(run-id . ,(native-build-result-ref result 'run-id "unknown"))
|
||||||
|
(guest-host-name . ,(native-build-result-ref result 'guest-host-name "unknown"))
|
||||||
|
(closure-path . ,(native-build-result-ref result 'closure-path ""))
|
||||||
|
(development-profile . ,(native-build-result-ref result 'development-profile ""))
|
||||||
|
(build-profile . ,build-profile)
|
||||||
|
(freebsd-base . ,(native-build-result-ref result 'freebsd-base '()))
|
||||||
|
(source . ,(native-build-result-ref result 'source '()))
|
||||||
|
(build-policy . ,(native-build-result-ref result 'build-policy '()))
|
||||||
|
(required-file . ,(native-build-result-ref entry 'required-file ""))
|
||||||
|
(recorded-sha256 . ,(native-build-result-ref entry 'recorded-sha256 ""))
|
||||||
|
(content-signature . ,content-signature))))
|
||||||
|
|
||||||
|
(define (promote-native-build-artifact result-root result store-dir artifact-kind)
|
||||||
|
(let* ((artifact-root (native-build-artifact-root result-root result artifact-kind))
|
||||||
|
(content-signature (tree-content-signature artifact-root))
|
||||||
|
(metadata (native-build-promoted-artifact-metadata result artifact-kind content-signature))
|
||||||
|
(payload (object->string metadata))
|
||||||
|
(display-name (native-build-artifact-display-name result artifact-kind))
|
||||||
|
(output-path (make-store-path store-dir display-name payload
|
||||||
|
#:kind 'native-build-artifact
|
||||||
|
#:output artifact-kind))
|
||||||
|
(references (native-build-existing-store-references result store-dir)))
|
||||||
|
(unless (file-exists? output-path)
|
||||||
|
(mkdir-p output-path)
|
||||||
|
(stage-tree-into-output artifact-root output-path)
|
||||||
|
(write-file (string-append output-path "/.references")
|
||||||
|
(string-join references "\n"))
|
||||||
|
(write-file (string-append output-path "/.fruix-native-build-object.scm")
|
||||||
|
payload))
|
||||||
|
`((artifact-kind . ,artifact-kind)
|
||||||
|
(artifact-root . ,artifact-root)
|
||||||
|
(store-path . ,output-path)
|
||||||
|
(content-signature . ,content-signature)
|
||||||
|
(metadata-file . ,(string-append output-path "/.fruix-native-build-object.scm")))) )
|
||||||
|
|
||||||
|
(define (native-build-result-display-name result)
|
||||||
|
(let* ((base (native-build-result-ref result 'freebsd-base '()))
|
||||||
|
(version-label (native-build-result-ref base 'version-label "unknown"))
|
||||||
|
(executor-name (native-build-result-executor-name result)))
|
||||||
|
(string-append "fruix-native-build-result-" version-label "-" executor-name)))
|
||||||
|
|
||||||
|
(define (native-build-promoted-result-object result promoted-artifacts)
|
||||||
|
(let ((executor (native-build-result-executor result))
|
||||||
|
(build-profile (native-build-result-ref result 'build-profile
|
||||||
|
(native-build-result-ref result 'development-profile ""))))
|
||||||
|
`((native-build-result-version . ,native-build-result-promotion-version)
|
||||||
|
(object-kind . result-bundle)
|
||||||
|
(executor . ,executor)
|
||||||
|
(executor-kind . ,(native-build-result-executor-kind result))
|
||||||
|
(executor-name . ,(native-build-result-executor-name result))
|
||||||
|
(executor-version . ,(native-build-result-executor-version result))
|
||||||
|
(run-id . ,(native-build-result-ref result 'run-id "unknown"))
|
||||||
|
(guest-host-name . ,(native-build-result-ref result 'guest-host-name "unknown"))
|
||||||
|
(closure-path . ,(native-build-result-ref result 'closure-path ""))
|
||||||
|
(development-profile . ,(native-build-result-ref result 'development-profile ""))
|
||||||
|
(build-profile . ,build-profile)
|
||||||
|
(freebsd-base . ,(native-build-result-ref result 'freebsd-base '()))
|
||||||
|
(source . ,(native-build-result-ref result 'source '()))
|
||||||
|
(build-policy . ,(native-build-result-ref result 'build-policy '()))
|
||||||
|
(artifact-count . ,(length promoted-artifacts))
|
||||||
|
(artifacts . ,(map (lambda (entry)
|
||||||
|
`((artifact-kind . ,(assoc-ref entry 'artifact-kind))
|
||||||
|
(store-path . ,(assoc-ref entry 'store-path))
|
||||||
|
(content-signature . ,(assoc-ref entry 'content-signature))
|
||||||
|
(metadata-file . ,(assoc-ref entry 'metadata-file))))
|
||||||
|
promoted-artifacts)))))
|
||||||
|
|
||||||
|
(define* (promote-native-build-result result-root #:key (store-dir "/frx/store"))
|
||||||
|
(let* ((result (read-native-build-result result-root))
|
||||||
|
(promoted-artifacts (map (lambda (artifact-kind)
|
||||||
|
(promote-native-build-artifact result-root result store-dir artifact-kind))
|
||||||
|
'(world kernel headers bootloader)))
|
||||||
|
(result-object (native-build-promoted-result-object result promoted-artifacts))
|
||||||
|
(payload (object->string result-object))
|
||||||
|
(display-name (native-build-result-display-name result))
|
||||||
|
(result-store (make-store-path store-dir display-name payload
|
||||||
|
#:kind 'native-build-result))
|
||||||
|
(result-references (append (map (lambda (entry)
|
||||||
|
(assoc-ref entry 'store-path))
|
||||||
|
promoted-artifacts)
|
||||||
|
(native-build-existing-store-references result store-dir))))
|
||||||
|
(unless (file-exists? result-store)
|
||||||
|
(mkdir-p (string-append result-store "/artifacts"))
|
||||||
|
(for-each (lambda (entry)
|
||||||
|
(symlink (assoc-ref entry 'store-path)
|
||||||
|
(string-append result-store
|
||||||
|
"/artifacts/"
|
||||||
|
(symbol->string (assoc-ref entry 'artifact-kind)))))
|
||||||
|
promoted-artifacts)
|
||||||
|
(write-file (string-append result-store "/.references")
|
||||||
|
(string-join result-references "\n"))
|
||||||
|
(write-file (string-append result-store "/.fruix-native-build-result.scm")
|
||||||
|
payload))
|
||||||
|
`((result-root . ,result-root)
|
||||||
|
(executor-kind . ,(native-build-result-executor-kind result))
|
||||||
|
(executor-name . ,(native-build-result-executor-name result))
|
||||||
|
(executor-version . ,(native-build-result-executor-version result))
|
||||||
|
(result-store . ,result-store)
|
||||||
|
(result-metadata-file . ,(string-append result-store "/.fruix-native-build-result.scm"))
|
||||||
|
(artifact-store-count . ,(length promoted-artifacts))
|
||||||
|
(artifact-stores . ,(map (lambda (entry) (assoc-ref entry 'store-path)) promoted-artifacts))
|
||||||
|
(world-store . ,(assoc-ref (find (lambda (entry)
|
||||||
|
(eq? (assoc-ref entry 'artifact-kind) 'world))
|
||||||
|
promoted-artifacts)
|
||||||
|
'store-path))
|
||||||
|
(kernel-store . ,(assoc-ref (find (lambda (entry)
|
||||||
|
(eq? (assoc-ref entry 'artifact-kind) 'kernel))
|
||||||
|
promoted-artifacts)
|
||||||
|
'store-path))
|
||||||
|
(headers-store . ,(assoc-ref (find (lambda (entry)
|
||||||
|
(eq? (assoc-ref entry 'artifact-kind) 'headers))
|
||||||
|
promoted-artifacts)
|
||||||
|
'store-path))
|
||||||
|
(bootloader-store . ,(assoc-ref (find (lambda (entry)
|
||||||
|
(eq? (assoc-ref entry 'artifact-kind) 'bootloader))
|
||||||
|
promoted-artifacts)
|
||||||
|
'store-path)))))
|
||||||
|
|
||||||
(define (sanitize-materialized-prefix name output-path)
|
(define (sanitize-materialized-prefix name output-path)
|
||||||
(cond
|
(cond
|
||||||
((string=? name "fruix-guile-extra")
|
((string=? name "fruix-guile-extra")
|
||||||
@@ -419,6 +879,8 @@
|
|||||||
(delete-file-if-exists (string-append output-path "/lib/guile/3.0/site-ccache/shepherd/config.go"))))
|
(delete-file-if-exists (string-append output-path "/lib/guile/3.0/site-ccache/shepherd/config.go"))))
|
||||||
#t)
|
#t)
|
||||||
|
|
||||||
|
(define prefix-materializer-version "3")
|
||||||
|
|
||||||
(define (prefix-manifest-string source-path extra-files)
|
(define (prefix-manifest-string source-path extra-files)
|
||||||
(string-append
|
(string-append
|
||||||
"prefix-materializer-version=" prefix-materializer-version "\n"
|
"prefix-materializer-version=" prefix-materializer-version "\n"
|
||||||
@@ -452,8 +914,9 @@
|
|||||||
|
|
||||||
(define* (materialize-prefix source-path name version store-dir #:key (extra-files '()))
|
(define* (materialize-prefix source-path name version store-dir #:key (extra-files '()))
|
||||||
(let* ((manifest (prefix-manifest-string source-path extra-files))
|
(let* ((manifest (prefix-manifest-string source-path extra-files))
|
||||||
(hash (string-hash manifest))
|
(display-name (string-append name "-" version))
|
||||||
(output-path (string-append store-dir "/" hash "-" name "-" version)))
|
(output-path (make-store-path store-dir display-name manifest
|
||||||
|
#:kind 'prefix)))
|
||||||
(unless (file-exists? output-path)
|
(unless (file-exists? output-path)
|
||||||
(mkdir-p output-path)
|
(mkdir-p output-path)
|
||||||
(for-each (lambda (entry)
|
(for-each (lambda (entry)
|
||||||
|
|||||||
121
modules/fruix/system/freebsd/executor.scm
Normal file
121
modules/fruix/system/freebsd/executor.scm
Normal file
@@ -0,0 +1,121 @@
|
|||||||
|
(define-module (fruix system freebsd executor)
|
||||||
|
#:use-module (ice-9 match)
|
||||||
|
#:use-module (srfi srfi-1)
|
||||||
|
#:export (native-build-executor-model-version
|
||||||
|
native-build-executor
|
||||||
|
native-build-executor?
|
||||||
|
native-build-executor-ref
|
||||||
|
native-build-executor-kind
|
||||||
|
native-build-executor-name
|
||||||
|
native-build-executor-version
|
||||||
|
native-build-executor-properties
|
||||||
|
normalize-native-build-executor
|
||||||
|
host-native-build-executor
|
||||||
|
ssh-guest-native-build-executor
|
||||||
|
self-hosted-native-build-executor))
|
||||||
|
|
||||||
|
(define native-build-executor-model-version "1")
|
||||||
|
|
||||||
|
(define (association-list? value)
|
||||||
|
(and (list? value)
|
||||||
|
(every pair? value)))
|
||||||
|
|
||||||
|
(define (executor-name kind provided-name)
|
||||||
|
(or provided-name
|
||||||
|
(symbol->string kind)))
|
||||||
|
|
||||||
|
(define* (native-build-executor #:key kind name
|
||||||
|
(version native-build-executor-model-version)
|
||||||
|
(properties '()))
|
||||||
|
(unless (symbol? kind)
|
||||||
|
(error "native build executor kind must be a symbol" kind))
|
||||||
|
(unless (string? (executor-name kind name))
|
||||||
|
(error "native build executor name must be a string" name))
|
||||||
|
(unless (string? version)
|
||||||
|
(error "native build executor version must be a string" version))
|
||||||
|
(unless (association-list? properties)
|
||||||
|
(error "native build executor properties must be an association list" properties))
|
||||||
|
`((kind . ,kind)
|
||||||
|
(name . ,(executor-name kind name))
|
||||||
|
(version . ,version)
|
||||||
|
(properties . ,properties)))
|
||||||
|
|
||||||
|
(define (native-build-executor-ref executor key default)
|
||||||
|
(match (assoc key executor)
|
||||||
|
((_ . value) value)
|
||||||
|
(#f default)))
|
||||||
|
|
||||||
|
(define (native-build-executor? value)
|
||||||
|
(and (association-list? value)
|
||||||
|
(symbol? (native-build-executor-ref value 'kind #f))
|
||||||
|
(string? (native-build-executor-ref value 'name #f))
|
||||||
|
(string? (native-build-executor-ref value 'version #f))
|
||||||
|
(association-list? (native-build-executor-ref value 'properties '()))))
|
||||||
|
|
||||||
|
(define (native-build-executor-kind executor)
|
||||||
|
(native-build-executor-ref executor 'kind 'unknown))
|
||||||
|
|
||||||
|
(define (native-build-executor-name executor)
|
||||||
|
(native-build-executor-ref executor 'name "unknown"))
|
||||||
|
|
||||||
|
(define (native-build-executor-version executor)
|
||||||
|
(native-build-executor-ref executor 'version "unknown"))
|
||||||
|
|
||||||
|
(define (native-build-executor-properties executor)
|
||||||
|
(native-build-executor-ref executor 'properties '()))
|
||||||
|
|
||||||
|
(define (legacy-executor-kind name)
|
||||||
|
(cond
|
||||||
|
((member name '("host")) 'host)
|
||||||
|
((member name '("ssh-guest" "guest-ssh" "guest-host-initiated")) 'ssh-guest)
|
||||||
|
((member name '("self-hosted" "guest-self-hosted")) 'self-hosted)
|
||||||
|
((member name '("jail")) 'jail)
|
||||||
|
((member name '("remote-builder")) 'remote-builder)
|
||||||
|
(else 'legacy)))
|
||||||
|
|
||||||
|
(define (normalize-native-build-executor value)
|
||||||
|
(cond
|
||||||
|
((native-build-executor? value)
|
||||||
|
value)
|
||||||
|
((string? value)
|
||||||
|
(native-build-executor #:kind (legacy-executor-kind value)
|
||||||
|
#:name value
|
||||||
|
#:version "legacy"))
|
||||||
|
(else
|
||||||
|
(error "unsupported native build executor representation" value))))
|
||||||
|
|
||||||
|
(define* (host-native-build-executor #:key (name "host")
|
||||||
|
host-name working-directory)
|
||||||
|
(native-build-executor
|
||||||
|
#:kind 'host
|
||||||
|
#:name name
|
||||||
|
#:properties (filter-map identity
|
||||||
|
`((host-name . ,host-name)
|
||||||
|
(working-directory . ,working-directory)))))
|
||||||
|
|
||||||
|
(define* (ssh-guest-native-build-executor #:key (name "ssh-guest")
|
||||||
|
transport orchestrator
|
||||||
|
guest-host-name guest-ip vm-id vdi-id)
|
||||||
|
(native-build-executor
|
||||||
|
#:kind 'ssh-guest
|
||||||
|
#:name name
|
||||||
|
#:properties (filter-map identity
|
||||||
|
`((transport . ,(or transport "ssh"))
|
||||||
|
(orchestrator . ,(or orchestrator "host"))
|
||||||
|
(guest-host-name . ,guest-host-name)
|
||||||
|
(guest-ip . ,guest-ip)
|
||||||
|
(vm-id . ,vm-id)
|
||||||
|
(vdi-id . ,vdi-id)))))
|
||||||
|
|
||||||
|
(define* (self-hosted-native-build-executor #:key (name "self-hosted")
|
||||||
|
helper-path helper-version
|
||||||
|
guest-host-name build-root-base result-root-base)
|
||||||
|
(native-build-executor
|
||||||
|
#:kind 'self-hosted
|
||||||
|
#:name name
|
||||||
|
#:version (or helper-version native-build-executor-model-version)
|
||||||
|
#:properties (filter-map identity
|
||||||
|
`((helper-path . ,helper-path)
|
||||||
|
(guest-host-name . ,guest-host-name)
|
||||||
|
(build-root-base . ,build-root-base)
|
||||||
|
(result-root-base . ,result-root-base)))))
|
||||||
@@ -10,15 +10,18 @@
|
|||||||
#:use-module (ice-9 hash-table)
|
#:use-module (ice-9 hash-table)
|
||||||
#:use-module (srfi srfi-1)
|
#:use-module (srfi srfi-1)
|
||||||
#:use-module (srfi srfi-13)
|
#:use-module (srfi srfi-13)
|
||||||
|
#:use-module (rnrs io ports)
|
||||||
#:export (operating-system-install-spec
|
#:export (operating-system-install-spec
|
||||||
operating-system-image-spec
|
operating-system-image-spec
|
||||||
operating-system-installer-image-spec
|
operating-system-installer-image-spec
|
||||||
|
operating-system-installer-iso-spec
|
||||||
installer-operating-system
|
installer-operating-system
|
||||||
materialize-operating-system
|
materialize-operating-system
|
||||||
materialize-rootfs
|
materialize-rootfs
|
||||||
install-operating-system
|
install-operating-system
|
||||||
materialize-bhyve-image
|
materialize-bhyve-image
|
||||||
materialize-installer-image))
|
materialize-installer-image
|
||||||
|
materialize-installer-iso))
|
||||||
|
|
||||||
(define (same-file-contents? a b)
|
(define (same-file-contents? a b)
|
||||||
(zero? (system* "cmp" "-s" a b)))
|
(zero? (system* "cmp" "-s" a b)))
|
||||||
@@ -69,18 +72,35 @@
|
|||||||
(store-dir "/frx/store")
|
(store-dir "/frx/store")
|
||||||
(guile-prefix "/tmp/guile-freebsd-validate-install")
|
(guile-prefix "/tmp/guile-freebsd-validate-install")
|
||||||
(guile-extra-prefix "/tmp/guile-gnutls-freebsd-validate-install")
|
(guile-extra-prefix "/tmp/guile-gnutls-freebsd-validate-install")
|
||||||
(shepherd-prefix "/tmp/shepherd-freebsd-validate-install"))
|
(shepherd-prefix "/tmp/shepherd-freebsd-validate-install")
|
||||||
|
(guile-store-path #f)
|
||||||
|
(guile-extra-store-path #f)
|
||||||
|
(shepherd-store-path #f)
|
||||||
|
(declaration-source #f)
|
||||||
|
(declaration-origin #f)
|
||||||
|
(declaration-system-symbol #f))
|
||||||
(validate-operating-system os)
|
(validate-operating-system os)
|
||||||
(let* ((cache (make-hash-table))
|
(let* ((cache (make-hash-table))
|
||||||
(source-cache (make-hash-table))
|
(source-cache (make-hash-table))
|
||||||
|
(native-build-result (operating-system-native-build-result os))
|
||||||
(kernel-package (operating-system-kernel os))
|
(kernel-package (operating-system-kernel os))
|
||||||
(bootloader-package (operating-system-bootloader os))
|
(bootloader-package (operating-system-bootloader os))
|
||||||
(base-packages (operating-system-base-packages os))
|
(base-packages (operating-system-base-packages os))
|
||||||
|
(development-packages (operating-system-development-packages os))
|
||||||
|
(build-packages (operating-system-build-packages os))
|
||||||
(kernel-store (materialize-freebsd-package kernel-package store-dir cache source-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))
|
(bootloader-store (materialize-freebsd-package bootloader-package store-dir cache source-cache))
|
||||||
(base-package-stores (map (lambda (package)
|
(base-package-stores (map (lambda (package)
|
||||||
(materialize-freebsd-package package store-dir cache source-cache))
|
(materialize-freebsd-package package store-dir cache source-cache))
|
||||||
base-packages))
|
base-packages))
|
||||||
|
(development-package-stores
|
||||||
|
(map (lambda (package)
|
||||||
|
(materialize-freebsd-package package store-dir cache source-cache))
|
||||||
|
development-packages))
|
||||||
|
(build-package-stores
|
||||||
|
(map (lambda (package)
|
||||||
|
(materialize-freebsd-package package store-dir cache source-cache))
|
||||||
|
build-packages))
|
||||||
(base-package-pairs (map cons base-packages base-package-stores))
|
(base-package-pairs (map cons base-packages base-package-stores))
|
||||||
(store-classification
|
(store-classification
|
||||||
(append (list (cons kernel-package kernel-store)
|
(append (list (cons kernel-package kernel-store)
|
||||||
@@ -101,12 +121,15 @@
|
|||||||
("/usr/local/lib/libtasn1.so.6" . "lib/libtasn1.so.6")
|
("/usr/local/lib/libtasn1.so.6" . "lib/libtasn1.so.6")
|
||||||
("/usr/local/lib/libhogweed.so.6" . "lib/libhogweed.so.6")
|
("/usr/local/lib/libhogweed.so.6" . "lib/libhogweed.so.6")
|
||||||
("/usr/local/lib/libnettle.so.8" . "lib/libnettle.so.8")))
|
("/usr/local/lib/libnettle.so.8" . "lib/libnettle.so.8")))
|
||||||
(guile-store (materialize-prefix guile-prefix "fruix-guile-runtime" "3.0" store-dir
|
(guile-store (or guile-store-path
|
||||||
#:extra-files guile-runtime-extra-files))
|
(materialize-prefix guile-prefix "fruix-guile-runtime" "3.0" store-dir
|
||||||
(guile-extra-store (materialize-prefix guile-extra-prefix "fruix-guile-extra" "3.0" store-dir
|
#:extra-files guile-runtime-extra-files)))
|
||||||
#:extra-files (append guile-runtime-extra-files
|
(guile-extra-store (or guile-extra-store-path
|
||||||
guile-extra-runtime-files)))
|
(materialize-prefix guile-extra-prefix "fruix-guile-extra" "3.0" store-dir
|
||||||
(shepherd-store (materialize-prefix shepherd-prefix "fruix-shepherd-runtime" "1.0.9" store-dir))
|
#:extra-files (append guile-runtime-extra-files
|
||||||
|
guile-extra-runtime-files))))
|
||||||
|
(shepherd-store (or shepherd-store-path
|
||||||
|
(materialize-prefix shepherd-prefix "fruix-shepherd-runtime" "1.0.9" store-dir)))
|
||||||
(host-base-stores
|
(host-base-stores
|
||||||
(delete-duplicates
|
(delete-duplicates
|
||||||
(map cdr
|
(map cdr
|
||||||
@@ -126,29 +149,76 @@
|
|||||||
(delete-duplicates (map (lambda (result)
|
(delete-duplicates (map (lambda (result)
|
||||||
(assoc-ref result 'source-store-path))
|
(assoc-ref result 'source-store-path))
|
||||||
source-materializations)))
|
source-materializations)))
|
||||||
|
(promoted-native-build-result-summary
|
||||||
|
(and native-build-result
|
||||||
|
(promoted-native-build-result-spec native-build-result)))
|
||||||
|
(promoted-native-build-result-store
|
||||||
|
(and native-build-result
|
||||||
|
(promoted-native-build-result-store-path native-build-result)))
|
||||||
|
(promoted-native-build-artifact-stores
|
||||||
|
(delete-duplicates
|
||||||
|
(filter identity
|
||||||
|
(if native-build-result
|
||||||
|
(map (lambda (artifact-kind)
|
||||||
|
(promoted-native-build-result-artifact-store native-build-result artifact-kind))
|
||||||
|
'(world kernel headers bootloader))
|
||||||
|
'()))))
|
||||||
|
(declaration-source-text
|
||||||
|
(or declaration-source
|
||||||
|
";; Fruix declaration source is unavailable for this closure.\n"))
|
||||||
|
(declaration-origin-text (or declaration-origin ""))
|
||||||
|
(declaration-system-text
|
||||||
|
(cond ((symbol? declaration-system-symbol)
|
||||||
|
(symbol->string declaration-system-symbol))
|
||||||
|
((string? declaration-system-symbol)
|
||||||
|
declaration-system-symbol)
|
||||||
|
(else "")))
|
||||||
|
(declaration-info-object
|
||||||
|
`((available? . ,(not (not declaration-source)))
|
||||||
|
(system-variable . ,declaration-system-text)))
|
||||||
(metadata-files
|
(metadata-files
|
||||||
`(("metadata/freebsd-base.scm"
|
(append
|
||||||
. ,(object->string (freebsd-base-spec (operating-system-freebsd-base os))))
|
(list (cons "metadata/freebsd-base.scm"
|
||||||
("metadata/freebsd-source.scm"
|
(object->string (freebsd-base-spec (operating-system-freebsd-base os))))
|
||||||
. ,(object->string (freebsd-source-spec (freebsd-base-source (operating-system-freebsd-base os)))))
|
(cons "metadata/freebsd-source.scm"
|
||||||
("metadata/freebsd-source-materializations.scm"
|
(object->string (freebsd-source-spec (freebsd-base-source (operating-system-freebsd-base os)))))
|
||||||
. ,(object->string (map freebsd-source-materialization-spec source-materializations)))
|
(cons "metadata/freebsd-source-materializations.scm"
|
||||||
("metadata/host-base-provenance.scm"
|
(object->string (map freebsd-source-materialization-spec source-materializations)))
|
||||||
. ,(object->string (host-freebsd-provenance)))
|
(cons "metadata/host-base-provenance.scm"
|
||||||
("metadata/store-layout.scm"
|
(object->string (host-freebsd-provenance)))
|
||||||
. ,(object->string
|
(cons "metadata/system-declaration.scm"
|
||||||
`((freebsd-base . ,(freebsd-base-spec (operating-system-freebsd-base os)))
|
declaration-source-text)
|
||||||
(freebsd-source . ,(freebsd-source-spec (freebsd-base-source (operating-system-freebsd-base os))))
|
(cons "metadata/system-declaration-info.scm"
|
||||||
(materialized-source-store-count . ,(length materialized-source-stores))
|
(object->string declaration-info-object))
|
||||||
(materialized-source-stores . ,materialized-source-stores)
|
(cons "metadata/system-declaration-system"
|
||||||
(host-base-store-count . ,(length host-base-stores))
|
(string-append declaration-system-text "\n"))
|
||||||
(host-base-stores . ,host-base-stores)
|
(cons "metadata/store-layout.scm"
|
||||||
(native-base-store-count . ,(length native-base-stores))
|
(object->string
|
||||||
(native-base-stores . ,native-base-stores)
|
`((freebsd-base . ,(freebsd-base-spec (operating-system-freebsd-base os)))
|
||||||
(fruix-runtime-store-count . ,(length fruix-runtime-stores))
|
(freebsd-source . ,(freebsd-source-spec (freebsd-base-source (operating-system-freebsd-base os))))
|
||||||
(fruix-runtime-stores . ,fruix-runtime-stores)
|
(system-declaration-available? . ,(not (not declaration-source)))
|
||||||
(host-base-replacement-order . ,%freebsd-host-staged-replacement-order)
|
(system-declaration-system-variable . ,declaration-system-text)
|
||||||
(init-mode . ,(operating-system-init-mode os)))))))
|
(promoted-native-build-result . ,promoted-native-build-result-summary)
|
||||||
|
(promoted-native-build-artifact-store-count . ,(length promoted-native-build-artifact-stores))
|
||||||
|
(promoted-native-build-artifact-stores . ,promoted-native-build-artifact-stores)
|
||||||
|
(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))
|
||||||
|
(native-base-stores . ,native-base-stores)
|
||||||
|
(development-package-store-count . ,(length development-package-stores))
|
||||||
|
(development-package-stores . ,development-package-stores)
|
||||||
|
(build-package-store-count . ,(length build-package-stores))
|
||||||
|
(build-package-stores . ,build-package-stores)
|
||||||
|
(fruix-runtime-store-count . ,(length fruix-runtime-stores))
|
||||||
|
(fruix-runtime-stores . ,fruix-runtime-stores)
|
||||||
|
(host-base-replacement-order . ,%freebsd-host-staged-replacement-order)
|
||||||
|
(init-mode . ,(operating-system-init-mode os))))))
|
||||||
|
(if promoted-native-build-result-summary
|
||||||
|
(list (cons "metadata/promoted-native-build-result.scm"
|
||||||
|
(object->string promoted-native-build-result-summary)))
|
||||||
|
'())))
|
||||||
(generated-files (append (operating-system-generated-files os
|
(generated-files (append (operating-system-generated-files os
|
||||||
#:guile-store guile-store
|
#:guile-store guile-store
|
||||||
#:guile-extra-store guile-extra-store
|
#:guile-extra-store guile-extra-store
|
||||||
@@ -158,7 +228,16 @@
|
|||||||
. ,(render-activation-rc-script))
|
. ,(render-activation-rc-script))
|
||||||
("usr/local/etc/rc.d/fruix-shepherd"
|
("usr/local/etc/rc.d/fruix-shepherd"
|
||||||
. ,(render-rc-script shepherd-store guile-store guile-extra-store)))))
|
. ,(render-rc-script shepherd-store guile-store guile-extra-store)))))
|
||||||
(references (delete-duplicates (append materialized-source-stores host-base-stores native-base-stores fruix-runtime-stores)))
|
(references (delete-duplicates
|
||||||
|
(append (if promoted-native-build-result-store
|
||||||
|
(list promoted-native-build-result-store)
|
||||||
|
'())
|
||||||
|
materialized-source-stores
|
||||||
|
host-base-stores
|
||||||
|
native-base-stores
|
||||||
|
development-package-stores
|
||||||
|
build-package-stores
|
||||||
|
fruix-runtime-stores)))
|
||||||
(manifest (string-append
|
(manifest (string-append
|
||||||
"closure-spec=\n"
|
"closure-spec=\n"
|
||||||
(object->string (operating-system-closure-spec os))
|
(object->string (operating-system-closure-spec os))
|
||||||
@@ -169,9 +248,14 @@
|
|||||||
"\n")
|
"\n")
|
||||||
"\nreferences=\n"
|
"\nreferences=\n"
|
||||||
(string-join references "\n")))
|
(string-join references "\n")))
|
||||||
(hash (string-hash manifest))
|
(display-name (string-append "fruix-system-"
|
||||||
(closure-path (string-append store-dir "/" hash "-fruix-system-"
|
(operating-system-host-name os)))
|
||||||
(operating-system-host-name os))))
|
(closure-path (make-store-path store-dir display-name manifest
|
||||||
|
#:kind 'operating-system))
|
||||||
|
(development-profile-path (and (not (null? development-package-stores))
|
||||||
|
(string-append closure-path "/development-profile")))
|
||||||
|
(build-profile-path (and (not (null? build-package-stores))
|
||||||
|
(string-append closure-path "/build-profile"))))
|
||||||
(unless (file-exists? closure-path)
|
(unless (file-exists? closure-path)
|
||||||
(mkdir-p closure-path)
|
(mkdir-p closure-path)
|
||||||
(mkdir-p (string-append closure-path "/boot/kernel"))
|
(mkdir-p (string-append closure-path "/boot/kernel"))
|
||||||
@@ -189,6 +273,16 @@
|
|||||||
(for-each (lambda (output)
|
(for-each (lambda (output)
|
||||||
(merge-output-into-tree output (string-append closure-path "/profile")))
|
(merge-output-into-tree output (string-append closure-path "/profile")))
|
||||||
base-package-stores)
|
base-package-stores)
|
||||||
|
(when development-profile-path
|
||||||
|
(mkdir-p development-profile-path)
|
||||||
|
(for-each (lambda (output)
|
||||||
|
(merge-output-into-tree output development-profile-path))
|
||||||
|
development-package-stores))
|
||||||
|
(when build-profile-path
|
||||||
|
(mkdir-p build-profile-path)
|
||||||
|
(for-each (lambda (output)
|
||||||
|
(merge-output-into-tree output build-profile-path))
|
||||||
|
build-package-stores))
|
||||||
(for-each
|
(for-each
|
||||||
(lambda (entry)
|
(lambda (entry)
|
||||||
(write-file (string-append closure-path "/" (car entry)) (cdr entry)))
|
(write-file (string-append closure-path "/" (car entry)) (cdr entry)))
|
||||||
@@ -198,6 +292,14 @@
|
|||||||
(chmod (string-append closure-path "/etc/master.passwd") #o600))
|
(chmod (string-append closure-path "/etc/master.passwd") #o600))
|
||||||
(chmod (string-append closure-path "/usr/local/etc/rc.d/fruix-activate") #o555)
|
(chmod (string-append closure-path "/usr/local/etc/rc.d/fruix-activate") #o555)
|
||||||
(chmod (string-append closure-path "/usr/local/etc/rc.d/fruix-shepherd") #o555)
|
(chmod (string-append closure-path "/usr/local/etc/rc.d/fruix-shepherd") #o555)
|
||||||
|
(when (file-exists? (string-append closure-path "/usr/local/bin/fruix"))
|
||||||
|
(chmod (string-append closure-path "/usr/local/bin/fruix") #o555))
|
||||||
|
(when (file-exists? (string-append closure-path "/usr/local/bin/fruix-development-environment"))
|
||||||
|
(chmod (string-append closure-path "/usr/local/bin/fruix-development-environment") #o555))
|
||||||
|
(when (file-exists? (string-append closure-path "/usr/local/bin/fruix-build-environment"))
|
||||||
|
(chmod (string-append closure-path "/usr/local/bin/fruix-build-environment") #o555))
|
||||||
|
(when (file-exists? (string-append closure-path "/usr/local/bin/fruix-self-hosted-native-build"))
|
||||||
|
(chmod (string-append closure-path "/usr/local/bin/fruix-self-hosted-native-build") #o555))
|
||||||
(when (file-exists? (string-append closure-path "/boot/fruix-pid1"))
|
(when (file-exists? (string-append closure-path "/boot/fruix-pid1"))
|
||||||
(chmod (string-append closure-path "/boot/fruix-pid1") #o555))
|
(chmod (string-append closure-path "/boot/fruix-pid1") #o555))
|
||||||
(write-file (string-append closure-path "/parameters.scm")
|
(write-file (string-append closure-path "/parameters.scm")
|
||||||
@@ -212,6 +314,10 @@
|
|||||||
(guile-extra-store . ,guile-extra-store)
|
(guile-extra-store . ,guile-extra-store)
|
||||||
(shepherd-store . ,shepherd-store)
|
(shepherd-store . ,shepherd-store)
|
||||||
(base-package-stores . ,base-package-stores)
|
(base-package-stores . ,base-package-stores)
|
||||||
|
(development-package-stores . ,development-package-stores)
|
||||||
|
(build-package-stores . ,build-package-stores)
|
||||||
|
(development-profile-path . ,development-profile-path)
|
||||||
|
(build-profile-path . ,build-profile-path)
|
||||||
(host-base-stores . ,host-base-stores)
|
(host-base-stores . ,host-base-stores)
|
||||||
(native-base-stores . ,native-base-stores)
|
(native-base-stores . ,native-base-stores)
|
||||||
(fruix-runtime-stores . ,fruix-runtime-stores)
|
(fruix-runtime-stores . ,fruix-runtime-stores)
|
||||||
@@ -220,7 +326,13 @@
|
|||||||
(freebsd-source-materializations-file . ,(string-append closure-path "/metadata/freebsd-source-materializations.scm"))
|
(freebsd-source-materializations-file . ,(string-append closure-path "/metadata/freebsd-source-materializations.scm"))
|
||||||
(materialized-source-stores . ,materialized-source-stores)
|
(materialized-source-stores . ,materialized-source-stores)
|
||||||
(host-base-provenance-file . ,(string-append closure-path "/metadata/host-base-provenance.scm"))
|
(host-base-provenance-file . ,(string-append closure-path "/metadata/host-base-provenance.scm"))
|
||||||
|
(system-declaration-file . ,(string-append closure-path "/metadata/system-declaration.scm"))
|
||||||
|
(system-declaration-info-file . ,(string-append closure-path "/metadata/system-declaration-info.scm"))
|
||||||
|
(system-declaration-system-file . ,(string-append closure-path "/metadata/system-declaration-system"))
|
||||||
(store-layout-file . ,(string-append closure-path "/metadata/store-layout.scm"))
|
(store-layout-file . ,(string-append closure-path "/metadata/store-layout.scm"))
|
||||||
|
(promoted-native-build-result-file
|
||||||
|
. ,(and promoted-native-build-result-summary
|
||||||
|
(string-append closure-path "/metadata/promoted-native-build-result.scm")))
|
||||||
(generated-files . ,(map car generated-files))
|
(generated-files . ,(map car generated-files))
|
||||||
(references . ,references))))
|
(references . ,references))))
|
||||||
|
|
||||||
@@ -230,14 +342,90 @@
|
|||||||
(mkdir-p (dirname link-name))
|
(mkdir-p (dirname link-name))
|
||||||
(symlink target link-name))
|
(symlink target link-name))
|
||||||
|
|
||||||
(define (populate-rootfs-from-closure os rootfs closure-path)
|
(define system-generation-layout-version "2")
|
||||||
|
|
||||||
|
(define* (system-generation-metadata-object os closure-path
|
||||||
|
#:key
|
||||||
|
(generation-number 1)
|
||||||
|
install-spec
|
||||||
|
install-metadata-path)
|
||||||
|
`((system-generation-version . ,system-generation-layout-version)
|
||||||
|
(generation-number . ,generation-number)
|
||||||
|
(host-name . ,(operating-system-host-name os))
|
||||||
|
(ready-marker . ,(operating-system-ready-marker os))
|
||||||
|
(init-mode . ,(operating-system-init-mode os))
|
||||||
|
(closure-path . ,closure-path)
|
||||||
|
(parameters-file . ,(string-append closure-path "/parameters.scm"))
|
||||||
|
(freebsd-base-file . ,(string-append closure-path "/metadata/freebsd-base.scm"))
|
||||||
|
(freebsd-source-file . ,(string-append closure-path "/metadata/freebsd-source.scm"))
|
||||||
|
(freebsd-source-materializations-file
|
||||||
|
. ,(string-append closure-path "/metadata/freebsd-source-materializations.scm"))
|
||||||
|
(host-base-provenance-file . ,(string-append closure-path "/metadata/host-base-provenance.scm"))
|
||||||
|
(system-declaration-file . ,(string-append closure-path "/metadata/system-declaration.scm"))
|
||||||
|
(system-declaration-info-file . ,(string-append closure-path "/metadata/system-declaration-info.scm"))
|
||||||
|
(system-declaration-system-file . ,(string-append closure-path "/metadata/system-declaration-system"))
|
||||||
|
(store-layout-file . ,(string-append closure-path "/metadata/store-layout.scm"))
|
||||||
|
(install-metadata-path . ,install-metadata-path)
|
||||||
|
(install-spec . ,install-spec)))
|
||||||
|
|
||||||
|
(define (system-generation-provenance-object closure-path)
|
||||||
|
`((closure-path . ,closure-path)
|
||||||
|
(parameters-file . ,(string-append closure-path "/parameters.scm"))
|
||||||
|
(freebsd-base-file . ,(string-append closure-path "/metadata/freebsd-base.scm"))
|
||||||
|
(freebsd-source-file . ,(string-append closure-path "/metadata/freebsd-source.scm"))
|
||||||
|
(freebsd-source-materializations-file
|
||||||
|
. ,(string-append closure-path "/metadata/freebsd-source-materializations.scm"))
|
||||||
|
(host-base-provenance-file . ,(string-append closure-path "/metadata/host-base-provenance.scm"))
|
||||||
|
(system-declaration-file . ,(string-append closure-path "/metadata/system-declaration.scm"))
|
||||||
|
(system-declaration-info-file . ,(string-append closure-path "/metadata/system-declaration-info.scm"))
|
||||||
|
(system-declaration-system-file . ,(string-append closure-path "/metadata/system-declaration-system"))
|
||||||
|
(store-layout-file . ,(string-append closure-path "/metadata/store-layout.scm"))))
|
||||||
|
|
||||||
|
(define* (populate-system-generation-layout os rootfs closure-path
|
||||||
|
#:key
|
||||||
|
(generation-number 1)
|
||||||
|
install-spec
|
||||||
|
install-metadata-path)
|
||||||
|
(let* ((system-root (string-append rootfs "/var/lib/fruix/system"))
|
||||||
|
(generation-name (number->string generation-number))
|
||||||
|
(generation-link-target (string-append "generations/" generation-name))
|
||||||
|
(generation-dir (string-append system-root "/generations/" generation-name))
|
||||||
|
(gcroots-dir (string-append rootfs "/frx/var/fruix/gcroots"))
|
||||||
|
(generation-install-file (string-append generation-dir "/install.scm"))
|
||||||
|
(root-install-file (and install-metadata-path
|
||||||
|
(string-append rootfs install-metadata-path))))
|
||||||
|
(mkdir-p generation-dir)
|
||||||
|
(symlink-force closure-path (string-append generation-dir "/closure"))
|
||||||
|
(write-file (string-append generation-dir "/metadata.scm")
|
||||||
|
(object->string
|
||||||
|
(system-generation-metadata-object os closure-path
|
||||||
|
#:generation-number generation-number
|
||||||
|
#:install-spec install-spec
|
||||||
|
#:install-metadata-path install-metadata-path)))
|
||||||
|
(write-file (string-append generation-dir "/provenance.scm")
|
||||||
|
(object->string (system-generation-provenance-object closure-path)))
|
||||||
|
(when (and root-install-file (file-exists? root-install-file))
|
||||||
|
(copy-regular-file root-install-file generation-install-file)
|
||||||
|
(chmod generation-install-file #o644))
|
||||||
|
(symlink-force generation-link-target (string-append system-root "/current"))
|
||||||
|
(write-file (string-append system-root "/current-generation")
|
||||||
|
(string-append generation-name "\n"))
|
||||||
|
(mkdir-p gcroots-dir)
|
||||||
|
(symlink-force closure-path (string-append gcroots-dir "/system-" generation-name))
|
||||||
|
(symlink-force closure-path (string-append gcroots-dir "/current-system"))))
|
||||||
|
|
||||||
|
(define* (populate-rootfs-from-closure os rootfs closure-path
|
||||||
|
#:key
|
||||||
|
install-spec
|
||||||
|
install-metadata-path)
|
||||||
(when (file-exists? rootfs)
|
(when (file-exists? rootfs)
|
||||||
(delete-file-recursively rootfs))
|
(delete-file-recursively rootfs))
|
||||||
(mkdir-p rootfs)
|
(mkdir-p rootfs)
|
||||||
(for-each (lambda (dir)
|
(for-each (lambda (dir)
|
||||||
(mkdir-p (string-append rootfs dir)))
|
(mkdir-p (string-append rootfs dir)))
|
||||||
'("/run" "/boot" "/etc" "/etc/ssh" "/usr" "/usr/share" "/usr/local" "/usr/local/etc"
|
'("/run" "/boot" "/etc" "/etc/ssh" "/usr" "/usr/share" "/usr/local"
|
||||||
"/usr/local/etc/rc.d" "/var" "/var/cron" "/var/db" "/var/lib" "/var/lib/fruix"
|
"/usr/local/bin" "/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"))
|
"/var/log" "/var/run" "/tmp" "/dev" "/root" "/home"))
|
||||||
(chmod (string-append rootfs "/tmp") #o1777)
|
(chmod (string-append rootfs "/tmp") #o1777)
|
||||||
(symlink-force closure-path (string-append rootfs "/run/current-system"))
|
(symlink-force closure-path (string-append rootfs "/run/current-system"))
|
||||||
@@ -273,10 +461,44 @@
|
|||||||
(symlink-force (string-append "/run/current-system/boot/" path)
|
(symlink-force (string-append "/run/current-system/boot/" path)
|
||||||
(string-append rootfs "/boot/" path)))
|
(string-append rootfs "/boot/" path)))
|
||||||
'("kernel" "loader" "loader.efi" "device.hints" "defaults" "lua" "loader.conf"))
|
'("kernel" "loader" "loader.efi" "device.hints" "defaults" "lua" "loader.conf"))
|
||||||
|
(symlink-force "/run/current-system/usr/local/bin/fruix"
|
||||||
|
(string-append rootfs "/usr/local/bin/fruix"))
|
||||||
|
(when (file-exists? (string-append closure-path "/development-profile"))
|
||||||
|
(symlink-force "/run/current-system/development-profile"
|
||||||
|
(string-append rootfs "/run/current-development")))
|
||||||
|
(when (file-exists? (string-append closure-path "/build-profile"))
|
||||||
|
(symlink-force "/run/current-system/build-profile"
|
||||||
|
(string-append rootfs "/run/current-build"))
|
||||||
|
(when (file-exists? (string-append closure-path "/build-profile/usr/include"))
|
||||||
|
(symlink-force "/run/current-system/build-profile/usr/include"
|
||||||
|
(string-append rootfs "/usr/include")))
|
||||||
|
(when (file-exists? (string-append closure-path "/build-profile/usr/share/mk"))
|
||||||
|
(symlink-force "/run/current-system/build-profile/usr/share/mk"
|
||||||
|
(string-append rootfs "/usr/share/mk"))))
|
||||||
|
(when (and (not (file-exists? (string-append closure-path "/build-profile")))
|
||||||
|
(file-exists? (string-append closure-path "/development-profile")))
|
||||||
|
(when (file-exists? (string-append closure-path "/development-profile/usr/include"))
|
||||||
|
(symlink-force "/run/current-system/development-profile/usr/include"
|
||||||
|
(string-append rootfs "/usr/include")))
|
||||||
|
(when (file-exists? (string-append closure-path "/development-profile/usr/share/mk"))
|
||||||
|
(symlink-force "/run/current-system/development-profile/usr/share/mk"
|
||||||
|
(string-append rootfs "/usr/share/mk"))))
|
||||||
|
(when (file-exists? (string-append closure-path "/usr/local/bin/fruix-development-environment"))
|
||||||
|
(symlink-force "/run/current-system/usr/local/bin/fruix-development-environment"
|
||||||
|
(string-append rootfs "/usr/local/bin/fruix-development-environment")))
|
||||||
|
(when (file-exists? (string-append closure-path "/usr/local/bin/fruix-build-environment"))
|
||||||
|
(symlink-force "/run/current-system/usr/local/bin/fruix-build-environment"
|
||||||
|
(string-append rootfs "/usr/local/bin/fruix-build-environment")))
|
||||||
|
(when (file-exists? (string-append closure-path "/usr/local/bin/fruix-self-hosted-native-build"))
|
||||||
|
(symlink-force "/run/current-system/usr/local/bin/fruix-self-hosted-native-build"
|
||||||
|
(string-append rootfs "/usr/local/bin/fruix-self-hosted-native-build")))
|
||||||
(symlink-force "/run/current-system/usr/local/etc/rc.d/fruix-activate"
|
(symlink-force "/run/current-system/usr/local/etc/rc.d/fruix-activate"
|
||||||
(string-append rootfs "/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"
|
(symlink-force "/run/current-system/usr/local/etc/rc.d/fruix-shepherd"
|
||||||
(string-append rootfs "/usr/local/etc/rc.d/fruix-shepherd"))
|
(string-append rootfs "/usr/local/etc/rc.d/fruix-shepherd"))
|
||||||
|
(populate-system-generation-layout os rootfs closure-path
|
||||||
|
#:install-spec install-spec
|
||||||
|
#:install-metadata-path install-metadata-path)
|
||||||
`((rootfs . ,rootfs)
|
`((rootfs . ,rootfs)
|
||||||
(closure-path . ,closure-path)
|
(closure-path . ,closure-path)
|
||||||
(ready-marker . ,(operating-system-ready-marker os))
|
(ready-marker . ,(operating-system-ready-marker os))
|
||||||
@@ -287,12 +509,18 @@
|
|||||||
(store-dir "/frx/store")
|
(store-dir "/frx/store")
|
||||||
(guile-prefix "/tmp/guile-freebsd-validate-install")
|
(guile-prefix "/tmp/guile-freebsd-validate-install")
|
||||||
(guile-extra-prefix "/tmp/guile-gnutls-freebsd-validate-install")
|
(guile-extra-prefix "/tmp/guile-gnutls-freebsd-validate-install")
|
||||||
(shepherd-prefix "/tmp/shepherd-freebsd-validate-install"))
|
(shepherd-prefix "/tmp/shepherd-freebsd-validate-install")
|
||||||
|
(declaration-source #f)
|
||||||
|
(declaration-origin #f)
|
||||||
|
(declaration-system-symbol #f))
|
||||||
(let* ((closure (materialize-operating-system os
|
(let* ((closure (materialize-operating-system os
|
||||||
#:store-dir store-dir
|
#:store-dir store-dir
|
||||||
#:guile-prefix guile-prefix
|
#:guile-prefix guile-prefix
|
||||||
#:guile-extra-prefix guile-extra-prefix
|
#:guile-extra-prefix guile-extra-prefix
|
||||||
#:shepherd-prefix shepherd-prefix))
|
#:shepherd-prefix shepherd-prefix
|
||||||
|
#:declaration-source declaration-source
|
||||||
|
#:declaration-origin declaration-origin
|
||||||
|
#:declaration-system-symbol declaration-system-symbol))
|
||||||
(closure-path (assoc-ref closure 'closure-path)))
|
(closure-path (assoc-ref closure 'closure-path)))
|
||||||
(populate-rootfs-from-closure os rootfs closure-path)))
|
(populate-rootfs-from-closure os rootfs closure-path)))
|
||||||
|
|
||||||
@@ -459,9 +687,39 @@
|
|||||||
#:serial-console serial-console))
|
#:serial-console serial-console))
|
||||||
(target-install . ,target-install-spec))))
|
(target-install . ,target-install-spec))))
|
||||||
|
|
||||||
(define image-builder-version "2")
|
(define* (operating-system-installer-iso-spec os
|
||||||
(define install-builder-version "1")
|
#:key
|
||||||
(define installer-image-builder-version "1")
|
(install-target-device "/dev/vtbd0")
|
||||||
|
(installer-host-name (string-append (operating-system-host-name os)
|
||||||
|
"-installer"))
|
||||||
|
(root-size #f)
|
||||||
|
(iso-volume-label "FRUIX_INSTALLER")
|
||||||
|
(installer-root-partition-label "fruix-installer-root")
|
||||||
|
(target-efi-partition-label "efiboot")
|
||||||
|
(target-root-partition-label "fruix-root")
|
||||||
|
(serial-console "comconsole"))
|
||||||
|
(let ((target-install-spec (operating-system-install-spec os
|
||||||
|
#:target install-target-device
|
||||||
|
#:target-kind 'block-device
|
||||||
|
#:efi-size "64m"
|
||||||
|
#:root-size #f
|
||||||
|
#:disk-capacity #f
|
||||||
|
#:efi-partition-label target-efi-partition-label
|
||||||
|
#:root-partition-label target-root-partition-label
|
||||||
|
#:serial-console serial-console)))
|
||||||
|
`((installer-host-name . ,installer-host-name)
|
||||||
|
(install-target-device . ,install-target-device)
|
||||||
|
(boot-mode . uefi)
|
||||||
|
(image-format . iso9660)
|
||||||
|
(iso-volume-label . ,iso-volume-label)
|
||||||
|
(root-size . ,root-size)
|
||||||
|
(installer-root-partition-label . ,installer-root-partition-label)
|
||||||
|
(target-install . ,target-install-spec))))
|
||||||
|
|
||||||
|
(define image-builder-version "3")
|
||||||
|
(define install-builder-version "2")
|
||||||
|
(define installer-image-builder-version "2")
|
||||||
|
(define installer-iso-builder-version "3")
|
||||||
|
|
||||||
(define (operating-system-install-metadata-object install-spec closure-path store-items)
|
(define (operating-system-install-metadata-object install-spec closure-path store-items)
|
||||||
`((install-version . ,install-builder-version)
|
`((install-version . ,install-builder-version)
|
||||||
@@ -543,9 +801,10 @@
|
|||||||
" [ -n \"$item_base\" ] || continue\n"
|
" [ -n \"$item_base\" ] || continue\n"
|
||||||
" (cd '" store-dir "' && pax -rw -pe \"$item_base\" \"$mnt_root" store-dir "\")\n"
|
" (cd '" store-dir "' && pax -rw -pe \"$item_base\" \"$mnt_root" store-dir "\")\n"
|
||||||
"done <\"$store_items_file\"\n"
|
"done <\"$store_items_file\"\n"
|
||||||
"mkdir -p \"$mnt_root/var/lib/fruix\" \"$mnt_esp/EFI/BOOT\"\n"
|
"mkdir -p \"$mnt_root/var/lib/fruix\" \"$mnt_root/var/lib/fruix/system/generations/1\" \"$mnt_esp/EFI/BOOT\"\n"
|
||||||
"cp \"$target_loader_efi\" \"$mnt_esp/EFI/BOOT/BOOTX64.EFI\"\n"
|
"cp \"$target_loader_efi\" \"$mnt_esp/EFI/BOOT/BOOTX64.EFI\"\n"
|
||||||
"cp \"$install_metadata_source\" \"$mnt_root/var/lib/fruix/install.scm\"\n"
|
"cp \"$install_metadata_source\" \"$mnt_root/var/lib/fruix/install.scm\"\n"
|
||||||
|
"cp \"$install_metadata_source\" \"$mnt_root/var/lib/fruix/system/generations/1/install.scm\"\n"
|
||||||
"sync\n"
|
"sync\n"
|
||||||
"echo 'fruix-installer:done'\n"
|
"echo 'fruix-installer:done'\n"
|
||||||
"write_state done\n")))
|
"write_state done\n")))
|
||||||
@@ -602,6 +861,9 @@
|
|||||||
(guile-prefix "/tmp/guile-freebsd-validate-install")
|
(guile-prefix "/tmp/guile-freebsd-validate-install")
|
||||||
(guile-extra-prefix "/tmp/guile-gnutls-freebsd-validate-install")
|
(guile-extra-prefix "/tmp/guile-gnutls-freebsd-validate-install")
|
||||||
(shepherd-prefix "/tmp/shepherd-freebsd-validate-install")
|
(shepherd-prefix "/tmp/shepherd-freebsd-validate-install")
|
||||||
|
(declaration-source #f)
|
||||||
|
(declaration-origin #f)
|
||||||
|
(declaration-system-symbol #f)
|
||||||
(efi-size "64m")
|
(efi-size "64m")
|
||||||
(root-size #f)
|
(root-size #f)
|
||||||
(disk-capacity #f)
|
(disk-capacity #f)
|
||||||
@@ -614,7 +876,10 @@
|
|||||||
#:store-dir store-dir
|
#:store-dir store-dir
|
||||||
#:guile-prefix guile-prefix
|
#:guile-prefix guile-prefix
|
||||||
#:guile-extra-prefix guile-extra-prefix
|
#:guile-extra-prefix guile-extra-prefix
|
||||||
#:shepherd-prefix shepherd-prefix))
|
#:shepherd-prefix shepherd-prefix
|
||||||
|
#:declaration-source declaration-source
|
||||||
|
#:declaration-origin declaration-origin
|
||||||
|
#:declaration-system-symbol declaration-system-symbol))
|
||||||
(closure-path (assoc-ref closure 'closure-path))
|
(closure-path (assoc-ref closure 'closure-path))
|
||||||
(store-items (store-reference-closure (list closure-path)))
|
(store-items (store-reference-closure (list closure-path)))
|
||||||
(target-kind (if (string-prefix? "/dev/" target)
|
(target-kind (if (string-prefix? "/dev/" target)
|
||||||
@@ -643,7 +908,9 @@
|
|||||||
(dynamic-wind
|
(dynamic-wind
|
||||||
(lambda () #t)
|
(lambda () #t)
|
||||||
(lambda ()
|
(lambda ()
|
||||||
(populate-rootfs-from-closure os rootfs closure-path)
|
(populate-rootfs-from-closure os rootfs closure-path
|
||||||
|
#:install-spec install-spec
|
||||||
|
#:install-metadata-path install-metadata-relative-path)
|
||||||
(mkdir-p mnt-root)
|
(mkdir-p mnt-root)
|
||||||
(mkdir-p mnt-esp)
|
(mkdir-p mnt-esp)
|
||||||
(case target-kind
|
(case target-kind
|
||||||
@@ -685,7 +952,10 @@
|
|||||||
(write-file install-metadata-file
|
(write-file install-metadata-file
|
||||||
(object->string
|
(object->string
|
||||||
(operating-system-install-metadata-object install-spec closure-path store-items)))
|
(operating-system-install-metadata-object install-spec closure-path store-items)))
|
||||||
(chmod install-metadata-file #o644))
|
(chmod install-metadata-file #o644)
|
||||||
|
(populate-system-generation-layout os mnt-root closure-path
|
||||||
|
#:install-spec install-spec
|
||||||
|
#:install-metadata-path install-metadata-relative-path))
|
||||||
(run-command "sync")
|
(run-command "sync")
|
||||||
`((target . ,target)
|
`((target . ,target)
|
||||||
(target-kind . ,target-kind)
|
(target-kind . ,target-kind)
|
||||||
@@ -724,6 +994,9 @@
|
|||||||
(guile-prefix "/tmp/guile-freebsd-validate-install")
|
(guile-prefix "/tmp/guile-freebsd-validate-install")
|
||||||
(guile-extra-prefix "/tmp/guile-gnutls-freebsd-validate-install")
|
(guile-extra-prefix "/tmp/guile-gnutls-freebsd-validate-install")
|
||||||
(shepherd-prefix "/tmp/shepherd-freebsd-validate-install")
|
(shepherd-prefix "/tmp/shepherd-freebsd-validate-install")
|
||||||
|
(declaration-source #f)
|
||||||
|
(declaration-origin #f)
|
||||||
|
(declaration-system-symbol #f)
|
||||||
(efi-size "64m")
|
(efi-size "64m")
|
||||||
(root-size "256m")
|
(root-size "256m")
|
||||||
(disk-capacity #f)
|
(disk-capacity #f)
|
||||||
@@ -734,7 +1007,10 @@
|
|||||||
#:store-dir store-dir
|
#:store-dir store-dir
|
||||||
#:guile-prefix guile-prefix
|
#:guile-prefix guile-prefix
|
||||||
#:guile-extra-prefix guile-extra-prefix
|
#:guile-extra-prefix guile-extra-prefix
|
||||||
#:shepherd-prefix shepherd-prefix))
|
#:shepherd-prefix shepherd-prefix
|
||||||
|
#:declaration-source declaration-source
|
||||||
|
#:declaration-origin declaration-origin
|
||||||
|
#:declaration-system-symbol declaration-system-symbol))
|
||||||
(closure-path (assoc-ref closure 'closure-path))
|
(closure-path (assoc-ref closure 'closure-path))
|
||||||
(image-spec (operating-system-image-spec os
|
(image-spec (operating-system-image-spec os
|
||||||
#:efi-size efi-size
|
#:efi-size efi-size
|
||||||
@@ -754,9 +1030,10 @@
|
|||||||
"\nstore-items=\n"
|
"\nstore-items=\n"
|
||||||
(string-join store-items "\n")
|
(string-join store-items "\n")
|
||||||
"\n"))
|
"\n"))
|
||||||
(hash (string-hash manifest))
|
(display-name (string-append "fruix-bhyve-image-"
|
||||||
(image-store-path (string-append store-dir "/" hash "-fruix-bhyve-image-"
|
(operating-system-host-name os)))
|
||||||
(operating-system-host-name os)))
|
(image-store-path (make-store-path store-dir display-name manifest
|
||||||
|
#:kind 'bhyve-image))
|
||||||
(disk-image (string-append image-store-path "/disk.img"))
|
(disk-image (string-append image-store-path "/disk.img"))
|
||||||
(esp-image (string-append image-store-path "/esp.img"))
|
(esp-image (string-append image-store-path "/esp.img"))
|
||||||
(root-image (string-append image-store-path "/root.ufs")))
|
(root-image (string-append image-store-path "/root.ufs")))
|
||||||
@@ -776,7 +1053,10 @@
|
|||||||
#:store-dir store-dir
|
#:store-dir store-dir
|
||||||
#:guile-prefix guile-prefix
|
#:guile-prefix guile-prefix
|
||||||
#:guile-extra-prefix guile-extra-prefix
|
#:guile-extra-prefix guile-extra-prefix
|
||||||
#:shepherd-prefix shepherd-prefix)
|
#:shepherd-prefix shepherd-prefix
|
||||||
|
#:declaration-source declaration-source
|
||||||
|
#:declaration-origin declaration-origin
|
||||||
|
#:declaration-system-symbol declaration-system-symbol)
|
||||||
(copy-rootfs-for-image rootfs image-rootfs)
|
(copy-rootfs-for-image rootfs image-rootfs)
|
||||||
(copy-store-items-into-rootfs image-rootfs store-dir store-items)
|
(copy-store-items-into-rootfs image-rootfs store-dir store-items)
|
||||||
(mkdir-p (string-append esp-stage "/EFI/BOOT"))
|
(mkdir-p (string-append esp-stage "/EFI/BOOT"))
|
||||||
@@ -844,6 +1124,9 @@
|
|||||||
(guile-prefix "/tmp/guile-freebsd-validate-install")
|
(guile-prefix "/tmp/guile-freebsd-validate-install")
|
||||||
(guile-extra-prefix "/tmp/guile-gnutls-freebsd-validate-install")
|
(guile-extra-prefix "/tmp/guile-gnutls-freebsd-validate-install")
|
||||||
(shepherd-prefix "/tmp/shepherd-freebsd-validate-install")
|
(shepherd-prefix "/tmp/shepherd-freebsd-validate-install")
|
||||||
|
(declaration-source #f)
|
||||||
|
(declaration-origin #f)
|
||||||
|
(declaration-system-symbol #f)
|
||||||
(install-target-device "/dev/vtbd1")
|
(install-target-device "/dev/vtbd1")
|
||||||
(efi-size "64m")
|
(efi-size "64m")
|
||||||
(root-size "10g")
|
(root-size "10g")
|
||||||
@@ -862,12 +1145,18 @@
|
|||||||
#:store-dir store-dir
|
#:store-dir store-dir
|
||||||
#:guile-prefix guile-prefix
|
#:guile-prefix guile-prefix
|
||||||
#:guile-extra-prefix guile-extra-prefix
|
#:guile-extra-prefix guile-extra-prefix
|
||||||
#:shepherd-prefix shepherd-prefix))
|
#:shepherd-prefix shepherd-prefix
|
||||||
|
#:declaration-source declaration-source
|
||||||
|
#:declaration-origin declaration-origin
|
||||||
|
#:declaration-system-symbol declaration-system-symbol))
|
||||||
(installer-closure (materialize-operating-system installer-os
|
(installer-closure (materialize-operating-system installer-os
|
||||||
#:store-dir store-dir
|
#:store-dir store-dir
|
||||||
#:guile-prefix guile-prefix
|
#:guile-prefix guile-prefix
|
||||||
#:guile-extra-prefix guile-extra-prefix
|
#:guile-extra-prefix guile-extra-prefix
|
||||||
#:shepherd-prefix shepherd-prefix))
|
#:shepherd-prefix shepherd-prefix
|
||||||
|
#:declaration-source declaration-source
|
||||||
|
#:declaration-origin declaration-origin
|
||||||
|
#:declaration-system-symbol declaration-system-symbol))
|
||||||
(target-closure-path (assoc-ref target-closure 'closure-path))
|
(target-closure-path (assoc-ref target-closure 'closure-path))
|
||||||
(installer-closure-path (assoc-ref installer-closure 'closure-path))
|
(installer-closure-path (assoc-ref installer-closure 'closure-path))
|
||||||
(target-store-items (store-reference-closure (list target-closure-path)))
|
(target-store-items (store-reference-closure (list target-closure-path)))
|
||||||
@@ -908,9 +1197,10 @@
|
|||||||
"\ninstall-metadata=\n"
|
"\ninstall-metadata=\n"
|
||||||
(object->string install-metadata)
|
(object->string install-metadata)
|
||||||
"\n"))
|
"\n"))
|
||||||
(hash (string-hash manifest))
|
(display-name (string-append "fruix-installer-image-"
|
||||||
(image-store-path (string-append store-dir "/" hash "-fruix-installer-image-"
|
(operating-system-host-name installer-os)))
|
||||||
(operating-system-host-name installer-os)))
|
(image-store-path (make-store-path store-dir display-name manifest
|
||||||
|
#:kind 'installer-image))
|
||||||
(disk-image (string-append image-store-path "/disk.img"))
|
(disk-image (string-append image-store-path "/disk.img"))
|
||||||
(esp-image (string-append image-store-path "/esp.img"))
|
(esp-image (string-append image-store-path "/esp.img"))
|
||||||
(root-image (string-append image-store-path "/root.ufs")))
|
(root-image (string-append image-store-path "/root.ufs")))
|
||||||
@@ -929,7 +1219,9 @@
|
|||||||
(lambda () #t)
|
(lambda () #t)
|
||||||
(lambda ()
|
(lambda ()
|
||||||
(populate-rootfs-from-closure installer-os installer-rootfs installer-closure-path)
|
(populate-rootfs-from-closure installer-os installer-rootfs installer-closure-path)
|
||||||
(populate-rootfs-from-closure os target-rootfs target-closure-path)
|
(populate-rootfs-from-closure os target-rootfs target-closure-path
|
||||||
|
#:install-spec target-install-spec
|
||||||
|
#:install-metadata-path "/var/lib/fruix/install.scm")
|
||||||
(copy-rootfs-for-image installer-rootfs image-rootfs)
|
(copy-rootfs-for-image installer-rootfs image-rootfs)
|
||||||
(mkdir-p plan-root)
|
(mkdir-p plan-root)
|
||||||
(mkdir-p (string-append image-rootfs "/usr/local/libexec"))
|
(mkdir-p (string-append image-rootfs "/usr/local/libexec"))
|
||||||
@@ -1029,3 +1321,307 @@
|
|||||||
(store-items . ,combined-store-items)
|
(store-items . ,combined-store-items)
|
||||||
(target-store-items . ,target-store-items)
|
(target-store-items . ,target-store-items)
|
||||||
(installer-store-items . ,installer-store-items))))
|
(installer-store-items . ,installer-store-items))))
|
||||||
|
|
||||||
|
(define (resolved-path path)
|
||||||
|
(let ((target (false-if-exception (readlink path))))
|
||||||
|
(if target
|
||||||
|
(if (string-prefix? "/" target)
|
||||||
|
target
|
||||||
|
(string-append (dirname path) "/" target))
|
||||||
|
path)))
|
||||||
|
|
||||||
|
(define (copy-resolved-node source destination)
|
||||||
|
(copy-node (resolved-path source) destination))
|
||||||
|
|
||||||
|
(define (sanitize-iso-volume-label label)
|
||||||
|
(let* ((text (if (and (string? label) (not (string-null? label)))
|
||||||
|
label
|
||||||
|
"FRUIX_INSTALLER"))
|
||||||
|
(upper (string-upcase text))
|
||||||
|
(chars (map (lambda (ch)
|
||||||
|
(if (or (char-alphabetic? ch)
|
||||||
|
(char-numeric? ch)
|
||||||
|
(memv ch '(#\_ #\-)))
|
||||||
|
ch
|
||||||
|
#\_))
|
||||||
|
(string->list upper)))
|
||||||
|
(sanitized (list->string chars)))
|
||||||
|
(if (> (string-length sanitized) 32)
|
||||||
|
(substring sanitized 0 32)
|
||||||
|
sanitized)))
|
||||||
|
|
||||||
|
(define (source-store-item? item)
|
||||||
|
(string-contains (path-basename item) "-freebsd-source-"))
|
||||||
|
|
||||||
|
(define (runtime-store-items items)
|
||||||
|
(filter (lambda (item)
|
||||||
|
(not (source-store-item? item)))
|
||||||
|
items))
|
||||||
|
|
||||||
|
(define (write-installer-iso-loader-conf source-path destination)
|
||||||
|
(let* ((mode (stat:perms (stat source-path)))
|
||||||
|
(base (call-with-input-file source-path get-string-all))
|
||||||
|
(extra (string-append
|
||||||
|
"mdroot_load=\"YES\"\n"
|
||||||
|
"mdroot_type=\"mfs_root\"\n"
|
||||||
|
"mdroot_name=\"/boot/root.img\"\n"
|
||||||
|
"vfs.root.mountfrom=\"ufs:/dev/md0\"\n"
|
||||||
|
"vfs.root.mountfrom.options=\"rw\"\n")))
|
||||||
|
(write-file destination
|
||||||
|
(string-append base
|
||||||
|
(if (or (string-null? base)
|
||||||
|
(char=? (string-ref base (- (string-length base) 1)) #\newline))
|
||||||
|
""
|
||||||
|
"\n")
|
||||||
|
extra))
|
||||||
|
(chmod destination mode)))
|
||||||
|
|
||||||
|
(define (rewrite-installer-iso-fstab image-rootfs installer-closure-path)
|
||||||
|
(let ((fstab-path (string-append image-rootfs "/frx/store/"
|
||||||
|
(path-basename installer-closure-path)
|
||||||
|
"/etc/fstab")))
|
||||||
|
(rewrite-text-file fstab-path
|
||||||
|
'(("/dev/gpt/fruix-installer-root\t/\tufs"
|
||||||
|
. "/dev/md0\t/\tufs")))))
|
||||||
|
|
||||||
|
(define* (make-ufs-image output-path source-root label #:key size)
|
||||||
|
(apply run-command
|
||||||
|
(append (list "makefs" "-t" "ffs" "-T" "0" "-B" "little")
|
||||||
|
(if size
|
||||||
|
(list "-s" size)
|
||||||
|
'())
|
||||||
|
(list "-o" (string-append "label=" label
|
||||||
|
",version=2,bsize=32768,fsize=4096,density=16384")
|
||||||
|
output-path
|
||||||
|
source-root))))
|
||||||
|
|
||||||
|
(define (make-efi-boot-image loader-efi output-path)
|
||||||
|
(let ((stage-root (mktemp-directory "/tmp/fruix-installer-iso-esp.XXXXXX")))
|
||||||
|
(dynamic-wind
|
||||||
|
(lambda () #t)
|
||||||
|
(lambda ()
|
||||||
|
(mkdir-p (string-append stage-root "/EFI/BOOT"))
|
||||||
|
(copy-regular-file loader-efi
|
||||||
|
(string-append stage-root "/EFI/BOOT/BOOTX64.EFI"))
|
||||||
|
(run-command "makefs" "-t" "msdos" "-T" "0"
|
||||||
|
"-o" "fat_type=12"
|
||||||
|
"-o" "sectors_per_cluster=1"
|
||||||
|
"-o" "volume_label=EFISYS"
|
||||||
|
"-s" "2048k"
|
||||||
|
output-path stage-root))
|
||||||
|
(lambda ()
|
||||||
|
(when (file-exists? stage-root)
|
||||||
|
(delete-file-recursively stage-root))))))
|
||||||
|
|
||||||
|
(define (populate-installer-iso-boot-tree installer-closure-path iso-root root-image-path)
|
||||||
|
(let ((boot-root (string-append iso-root "/boot")))
|
||||||
|
(mkdir-p (string-append boot-root "/kernel"))
|
||||||
|
(copy-resolved-node (string-append installer-closure-path "/boot/kernel/kernel")
|
||||||
|
(string-append boot-root "/kernel/kernel"))
|
||||||
|
(copy-resolved-node (string-append installer-closure-path "/boot/kernel/linker.hints")
|
||||||
|
(string-append boot-root "/kernel/linker.hints"))
|
||||||
|
(copy-resolved-node (string-append installer-closure-path "/boot/loader")
|
||||||
|
(string-append boot-root "/loader"))
|
||||||
|
(copy-resolved-node (string-append installer-closure-path "/boot/loader.efi")
|
||||||
|
(string-append boot-root "/loader.efi"))
|
||||||
|
(copy-resolved-node (string-append installer-closure-path "/boot/device.hints")
|
||||||
|
(string-append boot-root "/device.hints"))
|
||||||
|
(copy-resolved-node (string-append installer-closure-path "/boot/defaults")
|
||||||
|
(string-append boot-root "/defaults"))
|
||||||
|
(copy-resolved-node (string-append installer-closure-path "/boot/lua")
|
||||||
|
(string-append boot-root "/lua"))
|
||||||
|
(write-installer-iso-loader-conf (string-append installer-closure-path "/boot/loader.conf")
|
||||||
|
(string-append boot-root "/loader.conf"))
|
||||||
|
(copy-regular-file root-image-path
|
||||||
|
(string-append boot-root "/root.img"))))
|
||||||
|
|
||||||
|
(define* (materialize-installer-iso os
|
||||||
|
#:key
|
||||||
|
(store-dir "/frx/store")
|
||||||
|
(guile-prefix "/tmp/guile-freebsd-validate-install")
|
||||||
|
(guile-extra-prefix "/tmp/guile-gnutls-freebsd-validate-install")
|
||||||
|
(shepherd-prefix "/tmp/shepherd-freebsd-validate-install")
|
||||||
|
(declaration-source #f)
|
||||||
|
(declaration-origin #f)
|
||||||
|
(declaration-system-symbol #f)
|
||||||
|
(install-target-device "/dev/vtbd0")
|
||||||
|
(root-size #f)
|
||||||
|
(installer-host-name (string-append (operating-system-host-name os)
|
||||||
|
"-installer"))
|
||||||
|
(installer-root-partition-label "fruix-installer-root")
|
||||||
|
(target-efi-partition-label "efiboot")
|
||||||
|
(target-root-partition-label "fruix-root")
|
||||||
|
(serial-console "comconsole")
|
||||||
|
(iso-volume-label "FRUIX_INSTALLER"))
|
||||||
|
(let* ((installer-os (installer-operating-system os
|
||||||
|
#:host-name installer-host-name
|
||||||
|
#:root-partition-label installer-root-partition-label))
|
||||||
|
(target-closure (materialize-operating-system os
|
||||||
|
#:store-dir store-dir
|
||||||
|
#:guile-prefix guile-prefix
|
||||||
|
#:guile-extra-prefix guile-extra-prefix
|
||||||
|
#:shepherd-prefix shepherd-prefix
|
||||||
|
#:declaration-source declaration-source
|
||||||
|
#:declaration-origin declaration-origin
|
||||||
|
#:declaration-system-symbol declaration-system-symbol))
|
||||||
|
(installer-closure (materialize-operating-system installer-os
|
||||||
|
#:store-dir store-dir
|
||||||
|
#:guile-prefix guile-prefix
|
||||||
|
#:guile-extra-prefix guile-extra-prefix
|
||||||
|
#:shepherd-prefix shepherd-prefix
|
||||||
|
#:declaration-source declaration-source
|
||||||
|
#:declaration-origin declaration-origin
|
||||||
|
#:declaration-system-symbol declaration-system-symbol))
|
||||||
|
(target-closure-path (assoc-ref target-closure 'closure-path))
|
||||||
|
(installer-closure-path (assoc-ref installer-closure 'closure-path))
|
||||||
|
(target-closure-store-items (store-reference-closure (list target-closure-path)))
|
||||||
|
(target-runtime-store-items (runtime-store-items target-closure-store-items))
|
||||||
|
(installer-store-items (runtime-store-items
|
||||||
|
(store-reference-closure (list installer-closure-path))))
|
||||||
|
(combined-store-items (delete-duplicates (append installer-store-items target-runtime-store-items)))
|
||||||
|
(sanitized-iso-volume-label (sanitize-iso-volume-label iso-volume-label))
|
||||||
|
(installer-iso-spec (operating-system-installer-iso-spec os
|
||||||
|
#:install-target-device install-target-device
|
||||||
|
#:installer-host-name installer-host-name
|
||||||
|
#:root-size root-size
|
||||||
|
#:iso-volume-label sanitized-iso-volume-label
|
||||||
|
#:installer-root-partition-label installer-root-partition-label
|
||||||
|
#:target-efi-partition-label target-efi-partition-label
|
||||||
|
#:target-root-partition-label target-root-partition-label
|
||||||
|
#:serial-console serial-console))
|
||||||
|
(target-install-spec (assoc-ref installer-iso-spec 'target-install))
|
||||||
|
(install-metadata (operating-system-install-metadata-object target-install-spec
|
||||||
|
target-closure-path
|
||||||
|
target-closure-store-items))
|
||||||
|
(installer-plan-directory "/var/lib/fruix/installer")
|
||||||
|
(installer-state-path (string-append installer-plan-directory "/state"))
|
||||||
|
(installer-log-path "/var/log/fruix-installer.log")
|
||||||
|
(manifest (string-append
|
||||||
|
"installer-iso-builder-version=\n"
|
||||||
|
installer-iso-builder-version
|
||||||
|
"\ninstaller-iso-spec=\n"
|
||||||
|
(object->string installer-iso-spec)
|
||||||
|
"installer-closure-path=\n"
|
||||||
|
installer-closure-path
|
||||||
|
"\ntarget-closure-path=\n"
|
||||||
|
target-closure-path
|
||||||
|
"\ncombined-store-items=\n"
|
||||||
|
(string-join combined-store-items "\n")
|
||||||
|
"\ntarget-store-items=\n"
|
||||||
|
(string-join target-closure-store-items "\n")
|
||||||
|
"\ninstall-metadata=\n"
|
||||||
|
(object->string install-metadata)
|
||||||
|
"\n"))
|
||||||
|
(display-name (string-append "fruix-installer-iso-"
|
||||||
|
(operating-system-host-name installer-os)))
|
||||||
|
(iso-store-path (make-store-path store-dir display-name manifest
|
||||||
|
#:kind 'installer-iso))
|
||||||
|
(iso-image (string-append iso-store-path "/installer.iso"))
|
||||||
|
(boot-efi-image (string-append iso-store-path "/efiboot.img"))
|
||||||
|
(root-image (string-append iso-store-path "/root.img")))
|
||||||
|
(unless (file-exists? iso-store-path)
|
||||||
|
(let* ((build-root (mktemp-directory "/tmp/fruix-installer-iso-build.XXXXXX"))
|
||||||
|
(installer-rootfs (string-append build-root "/installer-rootfs"))
|
||||||
|
(target-rootfs (string-append build-root "/target-rootfs"))
|
||||||
|
(image-rootfs (string-append build-root "/image-rootfs"))
|
||||||
|
(iso-root (string-append build-root "/iso-root"))
|
||||||
|
(temp-output (mktemp-directory (string-append store-dir "/.fruix-installer-iso.XXXXXX")))
|
||||||
|
(temp-iso (string-append build-root "/installer.iso"))
|
||||||
|
(temp-esp (string-append build-root "/efiboot.img"))
|
||||||
|
(temp-root (string-append build-root "/root.img"))
|
||||||
|
(plan-root (string-append image-rootfs installer-plan-directory)))
|
||||||
|
(dynamic-wind
|
||||||
|
(lambda () #t)
|
||||||
|
(lambda ()
|
||||||
|
(populate-rootfs-from-closure installer-os installer-rootfs installer-closure-path)
|
||||||
|
(populate-rootfs-from-closure os target-rootfs target-closure-path
|
||||||
|
#:install-spec target-install-spec
|
||||||
|
#:install-metadata-path "/var/lib/fruix/install.scm")
|
||||||
|
(copy-rootfs-for-image installer-rootfs image-rootfs)
|
||||||
|
(mkdir-p plan-root)
|
||||||
|
(mkdir-p (string-append image-rootfs "/usr/local/libexec"))
|
||||||
|
(mkdir-p (string-append image-rootfs "/usr/local/etc/rc.d"))
|
||||||
|
(mkdir-p (string-append plan-root "/target-rootfs"))
|
||||||
|
(copy-tree-contents target-rootfs (string-append plan-root "/target-rootfs"))
|
||||||
|
(copy-store-items-into-rootfs image-rootfs store-dir combined-store-items)
|
||||||
|
(write-file (string-append plan-root "/store-items")
|
||||||
|
(string-append (string-join (map path-basename target-runtime-store-items) "\n") "\n"))
|
||||||
|
(write-file (string-append plan-root "/install.scm")
|
||||||
|
(object->string install-metadata))
|
||||||
|
(copy-regular-file (string-append target-closure-path "/boot/loader.efi")
|
||||||
|
(string-append plan-root "/loader.efi"))
|
||||||
|
(write-file (string-append plan-root "/target-device")
|
||||||
|
(string-append install-target-device "\n"))
|
||||||
|
(write-file (string-append plan-root "/efi-size") "64m\n")
|
||||||
|
(write-file (string-append plan-root "/efi-partition-label")
|
||||||
|
(string-append target-efi-partition-label "\n"))
|
||||||
|
(write-file (string-append plan-root "/root-partition-label")
|
||||||
|
(string-append target-root-partition-label "\n"))
|
||||||
|
(write-file (string-append plan-root "/state") "pending\n")
|
||||||
|
(write-file (string-append image-rootfs "/usr/local/libexec/fruix-installer-run")
|
||||||
|
(render-installer-run-script store-dir installer-plan-directory))
|
||||||
|
(write-file (string-append image-rootfs "/usr/local/etc/rc.d/fruix-installer")
|
||||||
|
(render-installer-rc-script installer-plan-directory))
|
||||||
|
(chmod (string-append image-rootfs "/usr/local/libexec/fruix-installer-run") #o555)
|
||||||
|
(chmod (string-append image-rootfs "/usr/local/etc/rc.d/fruix-installer") #o555)
|
||||||
|
(rewrite-installer-iso-fstab image-rootfs installer-closure-path)
|
||||||
|
(make-ufs-image temp-root image-rootfs installer-root-partition-label #:size root-size)
|
||||||
|
(populate-installer-iso-boot-tree installer-closure-path iso-root temp-root)
|
||||||
|
(make-efi-boot-image (resolved-path (string-append installer-closure-path "/boot/loader.efi")) temp-esp)
|
||||||
|
(run-command "makefs" "-t" "cd9660" "-T" "0"
|
||||||
|
"-o" (string-append "bootimage=efi;" temp-esp)
|
||||||
|
"-o" "no-emul-boot"
|
||||||
|
"-o" "platformid=efi"
|
||||||
|
"-o" "rockridge"
|
||||||
|
"-o" (string-append "label=" sanitized-iso-volume-label)
|
||||||
|
temp-iso iso-root)
|
||||||
|
(mkdir-p temp-output)
|
||||||
|
(copy-regular-file temp-iso (string-append temp-output "/installer.iso"))
|
||||||
|
(copy-regular-file temp-esp (string-append temp-output "/efiboot.img"))
|
||||||
|
(copy-regular-file temp-root (string-append temp-output "/root.img"))
|
||||||
|
(write-file (string-append temp-output "/installer-iso-spec.scm")
|
||||||
|
(object->string installer-iso-spec))
|
||||||
|
(write-file (string-append temp-output "/installer-closure-path") installer-closure-path)
|
||||||
|
(write-file (string-append temp-output "/target-closure-path") target-closure-path)
|
||||||
|
(write-file (string-append temp-output "/.references")
|
||||||
|
(string-join combined-store-items "\n"))
|
||||||
|
(write-file (string-append temp-output "/.fruix-package") manifest)
|
||||||
|
(chmod temp-output #o755)
|
||||||
|
(for-each (lambda (path)
|
||||||
|
(chmod path #o644))
|
||||||
|
(list (string-append temp-output "/installer.iso")
|
||||||
|
(string-append temp-output "/efiboot.img")
|
||||||
|
(string-append temp-output "/root.img")
|
||||||
|
(string-append temp-output "/installer-iso-spec.scm")
|
||||||
|
(string-append temp-output "/installer-closure-path")
|
||||||
|
(string-append temp-output "/target-closure-path")
|
||||||
|
(string-append temp-output "/.references")
|
||||||
|
(string-append temp-output "/.fruix-package")))
|
||||||
|
(rename-file temp-output iso-store-path))
|
||||||
|
(lambda ()
|
||||||
|
(when (file-exists? build-root)
|
||||||
|
(delete-file-recursively build-root))))))
|
||||||
|
`((iso-store-path . ,iso-store-path)
|
||||||
|
(iso-image . ,iso-image)
|
||||||
|
(boot-efi-image . ,boot-efi-image)
|
||||||
|
(root-image . ,root-image)
|
||||||
|
(installer-closure-path . ,installer-closure-path)
|
||||||
|
(target-closure-path . ,target-closure-path)
|
||||||
|
(closure-path . ,installer-closure-path)
|
||||||
|
(installer-iso-spec . ,installer-iso-spec)
|
||||||
|
(install-spec . ,target-install-spec)
|
||||||
|
(installer-state-path . ,installer-state-path)
|
||||||
|
(installer-log-path . ,installer-log-path)
|
||||||
|
(install-target-device . ,install-target-device)
|
||||||
|
(host-base-stores . ,(assoc-ref target-closure 'host-base-stores))
|
||||||
|
(native-base-stores . ,(assoc-ref target-closure 'native-base-stores))
|
||||||
|
(fruix-runtime-stores . ,(assoc-ref target-closure 'fruix-runtime-stores))
|
||||||
|
(freebsd-base-file . ,(assoc-ref target-closure 'freebsd-base-file))
|
||||||
|
(freebsd-source-file . ,(assoc-ref target-closure 'freebsd-source-file))
|
||||||
|
(freebsd-source-materializations-file . ,(assoc-ref target-closure 'freebsd-source-materializations-file))
|
||||||
|
(materialized-source-stores . ,(assoc-ref target-closure 'materialized-source-stores))
|
||||||
|
(host-base-provenance-file . ,(assoc-ref target-closure 'host-base-provenance-file))
|
||||||
|
(store-layout-file . ,(assoc-ref target-closure 'store-layout-file))
|
||||||
|
(store-items . ,combined-store-items)
|
||||||
|
(target-store-items . ,target-closure-store-items)
|
||||||
|
(installer-store-items . ,installer-store-items))))
|
||||||
|
|||||||
@@ -26,13 +26,22 @@
|
|||||||
file-system-type
|
file-system-type
|
||||||
file-system-options
|
file-system-options
|
||||||
file-system-needed-for-boot?
|
file-system-needed-for-boot?
|
||||||
|
make-promoted-native-build-result
|
||||||
|
promoted-native-build-result?
|
||||||
|
promoted-native-build-result-store-path
|
||||||
|
promoted-native-build-result-metadata-file
|
||||||
|
promoted-native-build-result-metadata
|
||||||
|
promoted-native-build-result-spec
|
||||||
operating-system
|
operating-system
|
||||||
operating-system?
|
operating-system?
|
||||||
operating-system-host-name
|
operating-system-host-name
|
||||||
operating-system-freebsd-base
|
operating-system-freebsd-base
|
||||||
|
operating-system-native-build-result
|
||||||
operating-system-kernel
|
operating-system-kernel
|
||||||
operating-system-bootloader
|
operating-system-bootloader
|
||||||
operating-system-base-packages
|
operating-system-base-packages
|
||||||
|
operating-system-development-packages
|
||||||
|
operating-system-build-packages
|
||||||
operating-system-users
|
operating-system-users
|
||||||
operating-system-groups
|
operating-system-groups
|
||||||
operating-system-file-systems
|
operating-system-file-systems
|
||||||
@@ -94,16 +103,71 @@
|
|||||||
(needed-for-boot? #f))
|
(needed-for-boot? #f))
|
||||||
(make-file-system device mount-point type options needed-for-boot?))
|
(make-file-system device mount-point type options needed-for-boot?))
|
||||||
|
|
||||||
|
(define-record-type <promoted-native-build-result>
|
||||||
|
(make-promoted-native-build-result store-path metadata-file metadata)
|
||||||
|
promoted-native-build-result?
|
||||||
|
(store-path promoted-native-build-result-store-path)
|
||||||
|
(metadata-file promoted-native-build-result-metadata-file)
|
||||||
|
(metadata promoted-native-build-result-metadata))
|
||||||
|
|
||||||
|
(define (promoted-native-build-result-metadata-ref metadata key default)
|
||||||
|
(match (assoc key metadata)
|
||||||
|
((_ . value) value)
|
||||||
|
(#f default)))
|
||||||
|
|
||||||
|
(define (promoted-native-build-result-artifact-spec metadata artifact-kind)
|
||||||
|
(find (lambda (entry)
|
||||||
|
(eq? (promoted-native-build-result-metadata-ref entry 'artifact-kind #f)
|
||||||
|
artifact-kind))
|
||||||
|
(promoted-native-build-result-metadata-ref metadata 'artifacts '())))
|
||||||
|
|
||||||
|
(define (promoted-native-build-result-spec result)
|
||||||
|
(let* ((metadata (promoted-native-build-result-metadata result))
|
||||||
|
(base (promoted-native-build-result-metadata-ref metadata 'freebsd-base '()))
|
||||||
|
(source (promoted-native-build-result-metadata-ref metadata 'source '())))
|
||||||
|
`((store-path . ,(promoted-native-build-result-store-path result))
|
||||||
|
(metadata-file . ,(promoted-native-build-result-metadata-file result))
|
||||||
|
(executor-kind . ,(promoted-native-build-result-metadata-ref metadata 'executor-kind #f))
|
||||||
|
(executor-name . ,(promoted-native-build-result-metadata-ref metadata 'executor-name #f))
|
||||||
|
(executor-version . ,(promoted-native-build-result-metadata-ref metadata 'executor-version #f))
|
||||||
|
(run-id . ,(promoted-native-build-result-metadata-ref metadata 'run-id #f))
|
||||||
|
(version-label . ,(promoted-native-build-result-metadata-ref base 'version-label #f))
|
||||||
|
(release . ,(promoted-native-build-result-metadata-ref base 'release #f))
|
||||||
|
(branch . ,(promoted-native-build-result-metadata-ref base 'branch #f))
|
||||||
|
(source-store . ,(promoted-native-build-result-metadata-ref source 'store-path #f))
|
||||||
|
(source-root . ,(promoted-native-build-result-metadata-ref source 'source-root #f))
|
||||||
|
(artifact-count . ,(promoted-native-build-result-metadata-ref metadata 'artifact-count 0))
|
||||||
|
(world-store . ,(promoted-native-build-result-metadata-ref
|
||||||
|
(promoted-native-build-result-artifact-spec metadata 'world)
|
||||||
|
'store-path
|
||||||
|
#f))
|
||||||
|
(kernel-store . ,(promoted-native-build-result-metadata-ref
|
||||||
|
(promoted-native-build-result-artifact-spec metadata 'kernel)
|
||||||
|
'store-path
|
||||||
|
#f))
|
||||||
|
(headers-store . ,(promoted-native-build-result-metadata-ref
|
||||||
|
(promoted-native-build-result-artifact-spec metadata 'headers)
|
||||||
|
'store-path
|
||||||
|
#f))
|
||||||
|
(bootloader-store . ,(promoted-native-build-result-metadata-ref
|
||||||
|
(promoted-native-build-result-artifact-spec metadata 'bootloader)
|
||||||
|
'store-path
|
||||||
|
#f)))))
|
||||||
|
|
||||||
(define-record-type <operating-system>
|
(define-record-type <operating-system>
|
||||||
(make-operating-system host-name freebsd-base kernel bootloader base-packages users groups
|
(make-operating-system host-name freebsd-base native-build-result kernel bootloader
|
||||||
file-systems services loader-entries rc-conf-entries
|
base-packages development-packages build-packages users groups file-systems
|
||||||
init-mode ready-marker root-authorized-keys)
|
services loader-entries rc-conf-entries init-mode ready-marker
|
||||||
|
root-authorized-keys)
|
||||||
operating-system?
|
operating-system?
|
||||||
(host-name operating-system-host-name)
|
(host-name operating-system-host-name)
|
||||||
(freebsd-base operating-system-freebsd-base)
|
(freebsd-base operating-system-freebsd-base)
|
||||||
|
(native-build-result operating-system-native-build-result)
|
||||||
(kernel operating-system-kernel)
|
(kernel operating-system-kernel)
|
||||||
(bootloader operating-system-bootloader)
|
(bootloader operating-system-bootloader)
|
||||||
(base-packages operating-system-base-packages)
|
(base-packages operating-system-base-packages)
|
||||||
|
(development-packages operating-system-development-packages)
|
||||||
|
(build-packages operating-system-build-packages)
|
||||||
(users operating-system-users)
|
(users operating-system-users)
|
||||||
(groups operating-system-groups)
|
(groups operating-system-groups)
|
||||||
(file-systems operating-system-file-systems)
|
(file-systems operating-system-file-systems)
|
||||||
@@ -117,9 +181,12 @@
|
|||||||
(define* (operating-system #:key
|
(define* (operating-system #:key
|
||||||
(host-name "fruix-freebsd")
|
(host-name "fruix-freebsd")
|
||||||
(freebsd-base %default-freebsd-base)
|
(freebsd-base %default-freebsd-base)
|
||||||
|
(native-build-result #f)
|
||||||
(kernel freebsd-kernel)
|
(kernel freebsd-kernel)
|
||||||
(bootloader freebsd-bootloader)
|
(bootloader freebsd-bootloader)
|
||||||
(base-packages %freebsd-system-packages)
|
(base-packages %freebsd-system-packages)
|
||||||
|
(development-packages '())
|
||||||
|
(build-packages '())
|
||||||
(users (list (user-account #:name "root"
|
(users (list (user-account #:name "root"
|
||||||
#:uid 0
|
#:uid 0
|
||||||
#:group "wheel"
|
#:group "wheel"
|
||||||
@@ -161,9 +228,10 @@
|
|||||||
(init-mode 'freebsd-init+rc.d-shepherd)
|
(init-mode 'freebsd-init+rc.d-shepherd)
|
||||||
(ready-marker "/var/lib/fruix/ready")
|
(ready-marker "/var/lib/fruix/ready")
|
||||||
(root-authorized-keys '()))
|
(root-authorized-keys '()))
|
||||||
(make-operating-system host-name freebsd-base kernel bootloader base-packages users groups
|
(make-operating-system host-name freebsd-base native-build-result kernel bootloader
|
||||||
file-systems services loader-entries rc-conf-entries
|
base-packages development-packages build-packages users groups file-systems
|
||||||
init-mode ready-marker root-authorized-keys))
|
services loader-entries rc-conf-entries init-mode ready-marker
|
||||||
|
root-authorized-keys))
|
||||||
|
|
||||||
(define default-minimal-operating-system (operating-system))
|
(define default-minimal-operating-system (operating-system))
|
||||||
|
|
||||||
@@ -231,6 +299,10 @@
|
|||||||
(define (validate-operating-system os)
|
(define (validate-operating-system os)
|
||||||
(let* ((host-name (operating-system-host-name os))
|
(let* ((host-name (operating-system-host-name os))
|
||||||
(base (operating-system-freebsd-base os))
|
(base (operating-system-freebsd-base os))
|
||||||
|
(native-build-result (operating-system-native-build-result os))
|
||||||
|
(base-packages (operating-system-base-packages os))
|
||||||
|
(development-packages (operating-system-development-packages os))
|
||||||
|
(build-packages (operating-system-build-packages os))
|
||||||
(users (operating-system-users os))
|
(users (operating-system-users os))
|
||||||
(groups (operating-system-groups os))
|
(groups (operating-system-groups os))
|
||||||
(file-systems (operating-system-file-systems os))
|
(file-systems (operating-system-file-systems os))
|
||||||
@@ -242,6 +314,15 @@
|
|||||||
(error "operating-system host-name must not be empty"))
|
(error "operating-system host-name must not be empty"))
|
||||||
(unless (freebsd-base? base)
|
(unless (freebsd-base? base)
|
||||||
(error "operating-system freebsd-base must be a <freebsd-base> record"))
|
(error "operating-system freebsd-base must be a <freebsd-base> record"))
|
||||||
|
(when native-build-result
|
||||||
|
(unless (promoted-native-build-result? native-build-result)
|
||||||
|
(error "operating-system native-build-result must be a <promoted-native-build-result> record")))
|
||||||
|
(unless (every freebsd-package? base-packages)
|
||||||
|
(error "operating-system base-packages must be a list of <freebsd-package> records"))
|
||||||
|
(unless (every freebsd-package? development-packages)
|
||||||
|
(error "operating-system development-packages must be a list of <freebsd-package> records"))
|
||||||
|
(unless (every freebsd-package? build-packages)
|
||||||
|
(error "operating-system build-packages must be a list of <freebsd-package> records"))
|
||||||
(validate-freebsd-source (freebsd-base-source base))
|
(validate-freebsd-source (freebsd-base-source base))
|
||||||
(let ((dups (duplicate-elements user-names)))
|
(let ((dups (duplicate-elements user-names)))
|
||||||
(unless (null? dups)
|
(unless (null? dups)
|
||||||
@@ -294,8 +375,23 @@
|
|||||||
"metadata/freebsd-base.scm"
|
"metadata/freebsd-base.scm"
|
||||||
"metadata/host-base-provenance.scm"
|
"metadata/host-base-provenance.scm"
|
||||||
"metadata/store-layout.scm"
|
"metadata/store-layout.scm"
|
||||||
|
"metadata/system-declaration.scm"
|
||||||
|
"metadata/system-declaration-info.scm"
|
||||||
|
"metadata/system-declaration-system"
|
||||||
"activate"
|
"activate"
|
||||||
"shepherd/init.scm")
|
"shepherd/init.scm"
|
||||||
|
"share/fruix/node/scripts/fruix.scm"
|
||||||
|
"usr/local/bin/fruix")
|
||||||
|
(if (operating-system-native-build-result os)
|
||||||
|
'("metadata/promoted-native-build-result.scm")
|
||||||
|
'())
|
||||||
|
(if (null? (operating-system-development-packages os))
|
||||||
|
'()
|
||||||
|
'("usr/local/bin/fruix-development-environment"))
|
||||||
|
(if (null? (operating-system-build-packages os))
|
||||||
|
'()
|
||||||
|
'("usr/local/bin/fruix-build-environment"
|
||||||
|
"usr/local/bin/fruix-self-hosted-native-build"))
|
||||||
(if (pid1-init-mode? os)
|
(if (pid1-init-mode? os)
|
||||||
'("boot/fruix-pid1")
|
'("boot/fruix-pid1")
|
||||||
'())
|
'())
|
||||||
@@ -311,10 +407,26 @@
|
|||||||
(validate-operating-system os)
|
(validate-operating-system os)
|
||||||
`((host-name . ,(operating-system-host-name os))
|
`((host-name . ,(operating-system-host-name os))
|
||||||
(freebsd-base . ,(freebsd-base-spec (operating-system-freebsd-base os)))
|
(freebsd-base . ,(freebsd-base-spec (operating-system-freebsd-base os)))
|
||||||
|
(promoted-native-build-result
|
||||||
|
. ,(and (operating-system-native-build-result os)
|
||||||
|
(promoted-native-build-result-spec
|
||||||
|
(operating-system-native-build-result os))))
|
||||||
(kernel-package . ,(freebsd-package-name (operating-system-kernel os)))
|
(kernel-package . ,(freebsd-package-name (operating-system-kernel os)))
|
||||||
(bootloader-package . ,(freebsd-package-name (operating-system-bootloader os)))
|
(bootloader-package . ,(freebsd-package-name (operating-system-bootloader os)))
|
||||||
(base-package-count . ,(length (operating-system-base-packages os)))
|
(base-package-count . ,(length (operating-system-base-packages os)))
|
||||||
(base-packages . ,(package-names (operating-system-base-packages os)))
|
(base-packages . ,(package-names (operating-system-base-packages os)))
|
||||||
|
(development-package-count . ,(length (operating-system-development-packages os)))
|
||||||
|
(development-packages . ,(package-names (operating-system-development-packages os)))
|
||||||
|
(build-package-count . ,(length (operating-system-build-packages os)))
|
||||||
|
(build-packages . ,(package-names (operating-system-build-packages os)))
|
||||||
|
(installed-system-command-surface-version . "2")
|
||||||
|
(bundled-fruix-node-cli-version . "1")
|
||||||
|
(development-environment-helper-version
|
||||||
|
. ,(if (null? (operating-system-development-packages os)) #f "1"))
|
||||||
|
(build-environment-helper-version
|
||||||
|
. ,(if (null? (operating-system-build-packages os)) #f "1"))
|
||||||
|
(self-hosted-native-build-helper-version
|
||||||
|
. ,(if (null? (operating-system-build-packages os)) #f "5"))
|
||||||
(user-count . ,(length (operating-system-users os)))
|
(user-count . ,(length (operating-system-users os)))
|
||||||
(users . ,(map user-account-name (operating-system-users os)))
|
(users . ,(map user-account-name (operating-system-users os)))
|
||||||
(group-count . ,(length (operating-system-groups os)))
|
(group-count . ,(length (operating-system-groups os)))
|
||||||
|
|||||||
@@ -4,6 +4,7 @@
|
|||||||
#:use-module (ice-9 match)
|
#:use-module (ice-9 match)
|
||||||
#:use-module (srfi srfi-1)
|
#:use-module (srfi srfi-1)
|
||||||
#:use-module (srfi srfi-13)
|
#:use-module (srfi srfi-13)
|
||||||
|
#:use-module (rnrs io ports)
|
||||||
#:export (operating-system-generated-files
|
#:export (operating-system-generated-files
|
||||||
render-activation-rc-script
|
render-activation-rc-script
|
||||||
render-rc-script))
|
render-rc-script))
|
||||||
@@ -468,6 +469,726 @@
|
|||||||
"load_rc_config $name\n"
|
"load_rc_config $name\n"
|
||||||
"run_rc_command \"$1\"\n")))
|
"run_rc_command \"$1\"\n")))
|
||||||
|
|
||||||
|
(define (path-parent path)
|
||||||
|
(let ((index (string-rindex path #\/)))
|
||||||
|
(cond
|
||||||
|
((not index) ".")
|
||||||
|
((zero? index) "/")
|
||||||
|
(else (substring path 0 index)))))
|
||||||
|
|
||||||
|
(define (read-source-file-string path)
|
||||||
|
(call-with-input-file path get-string-all))
|
||||||
|
|
||||||
|
(define (bundled-fruix-node-files)
|
||||||
|
(let* ((repo-root (or (getenv "FRUIX_PROJECT_ROOT")
|
||||||
|
(let ((render-file (current-filename)))
|
||||||
|
(and render-file
|
||||||
|
(path-parent
|
||||||
|
(path-parent
|
||||||
|
(path-parent
|
||||||
|
(path-parent
|
||||||
|
(path-parent render-file)))))))
|
||||||
|
(getcwd)))
|
||||||
|
(guix-root (or (getenv "GUIX_SOURCE_DIR")
|
||||||
|
(string-append (getenv "HOME") "/repos/guix")))
|
||||||
|
(specs `((,(string-append repo-root "/scripts/fruix.scm")
|
||||||
|
. "share/fruix/node/scripts/fruix.scm")
|
||||||
|
(,(string-append repo-root "/modules/fruix/packages/freebsd.scm")
|
||||||
|
. "share/fruix/node/modules/fruix/packages/freebsd.scm")
|
||||||
|
(,(string-append repo-root "/modules/fruix/system/freebsd.scm")
|
||||||
|
. "share/fruix/node/modules/fruix/system/freebsd.scm")
|
||||||
|
(,(string-append repo-root "/modules/fruix/system/freebsd/build.scm")
|
||||||
|
. "share/fruix/node/modules/fruix/system/freebsd/build.scm")
|
||||||
|
(,(string-append repo-root "/modules/fruix/system/freebsd/executor.scm")
|
||||||
|
. "share/fruix/node/modules/fruix/system/freebsd/executor.scm")
|
||||||
|
(,(string-append repo-root "/modules/fruix/system/freebsd/media.scm")
|
||||||
|
. "share/fruix/node/modules/fruix/system/freebsd/media.scm")
|
||||||
|
(,(string-append repo-root "/modules/fruix/system/freebsd/model.scm")
|
||||||
|
. "share/fruix/node/modules/fruix/system/freebsd/model.scm")
|
||||||
|
(,(string-append repo-root "/modules/fruix/system/freebsd/render.scm")
|
||||||
|
. "share/fruix/node/modules/fruix/system/freebsd/render.scm")
|
||||||
|
(,(string-append repo-root "/modules/fruix/system/freebsd/source.scm")
|
||||||
|
. "share/fruix/node/modules/fruix/system/freebsd/source.scm")
|
||||||
|
(,(string-append repo-root "/modules/fruix/system/freebsd/utils.scm")
|
||||||
|
. "share/fruix/node/modules/fruix/system/freebsd/utils.scm")
|
||||||
|
(,(string-append guix-root "/guix/build/utils.scm")
|
||||||
|
. "share/fruix/node/guix/guix/build/utils.scm"))))
|
||||||
|
(map (lambda (entry)
|
||||||
|
(cons (cdr entry)
|
||||||
|
(read-source-file-string (car entry))))
|
||||||
|
specs)))
|
||||||
|
|
||||||
|
(define (render-installed-system-fruix os guile-store guile-extra-store shepherd-store)
|
||||||
|
(string-append
|
||||||
|
"#!/bin/sh\n"
|
||||||
|
"set -eu\n"
|
||||||
|
"PATH=/sbin:/bin:/usr/sbin:/usr/bin:/usr/local/sbin:/usr/local/bin\n"
|
||||||
|
"tool_closure=$(readlink /run/current-system 2>/dev/null || true)\n"
|
||||||
|
"if [ -n \"$tool_closure\" ]; then\n"
|
||||||
|
" PATH=\"$tool_closure/profile/bin:$tool_closure/profile/sbin:$tool_closure/profile/usr/bin:$tool_closure/profile/usr/sbin:$PATH\"\n"
|
||||||
|
"fi\n"
|
||||||
|
"export PATH\n\n"
|
||||||
|
"system_root=/var/lib/fruix/system\n"
|
||||||
|
"generations_root=\"$system_root/generations\"\n"
|
||||||
|
"current_link=\"$system_root/current\"\n"
|
||||||
|
"current_generation_file=\"$system_root/current-generation\"\n"
|
||||||
|
"rollback_link=\"$system_root/rollback\"\n"
|
||||||
|
"rollback_generation_file=\"$system_root/rollback-generation\"\n"
|
||||||
|
"gcroots_root=/frx/var/fruix/gcroots\n"
|
||||||
|
"run_current_link=/run/current-system\n"
|
||||||
|
"node_root=/run/current-system/share/fruix/node\n"
|
||||||
|
"node_script=\"$node_root/scripts/fruix.scm\"\n"
|
||||||
|
"node_module_root=\"$node_root/modules\"\n"
|
||||||
|
"node_guix_root=\"$node_root/guix\"\n"
|
||||||
|
"declaration_file=/run/current-system/metadata/system-declaration.scm\n"
|
||||||
|
"declaration_info_file=/run/current-system/metadata/system-declaration-info.scm\n"
|
||||||
|
"declaration_system_file=/run/current-system/metadata/system-declaration-system\n"
|
||||||
|
"default_store_dir=/frx/store\n"
|
||||||
|
"guile_store='" guile-store "'\n"
|
||||||
|
"guile_extra_store='" guile-extra-store "'\n"
|
||||||
|
"shepherd_store='" shepherd-store "'\n"
|
||||||
|
"layout_version=2\n"
|
||||||
|
"host_name='" (operating-system-host-name os) "'\n"
|
||||||
|
"ready_marker='" (operating-system-ready-marker os) "'\n"
|
||||||
|
"init_mode='" (symbol->string (operating-system-init-mode os)) "'\n\n"
|
||||||
|
"usage()\n"
|
||||||
|
"{\n"
|
||||||
|
" cat <<'EOF'\n"
|
||||||
|
"Usage: fruix system status\n"
|
||||||
|
" fruix system build [DECLARATION [--system NAME] ...]\n"
|
||||||
|
" fruix system reconfigure [DECLARATION [--system NAME] ...]\n"
|
||||||
|
" fruix system switch /frx/store/...-fruix-system-...\n"
|
||||||
|
" fruix system rollback\n"
|
||||||
|
"EOF\n"
|
||||||
|
"}\n\n"
|
||||||
|
"die()\n"
|
||||||
|
"{\n"
|
||||||
|
" echo \"fruix: $*\" >&2\n"
|
||||||
|
" exit 1\n"
|
||||||
|
"}\n\n"
|
||||||
|
"read_link_maybe()\n"
|
||||||
|
"{\n"
|
||||||
|
" if [ -L \"$1\" ]; then\n"
|
||||||
|
" readlink \"$1\"\n"
|
||||||
|
" fi\n"
|
||||||
|
"}\n\n"
|
||||||
|
"read_file_maybe()\n"
|
||||||
|
"{\n"
|
||||||
|
" if [ -f \"$1\" ]; then\n"
|
||||||
|
" tr -d '\\n' < \"$1\"\n"
|
||||||
|
" fi\n"
|
||||||
|
"}\n\n"
|
||||||
|
"default_system_name()\n"
|
||||||
|
"{\n"
|
||||||
|
" read_file_maybe \"$declaration_system_file\"\n"
|
||||||
|
"}\n\n"
|
||||||
|
"symlink_force()\n"
|
||||||
|
"{\n"
|
||||||
|
" target=$1\n"
|
||||||
|
" link_name=$2\n"
|
||||||
|
" tmp_link=\"${link_name}.new.$$\"\n"
|
||||||
|
" mkdir -p \"$(dirname \"$link_name\")\"\n"
|
||||||
|
" if [ \"$link_name\" = \"$run_current_link\" ]; then\n"
|
||||||
|
" rm -f \"$tmp_link\"\n"
|
||||||
|
" ln -s \"$target\" \"$tmp_link\"\n"
|
||||||
|
" mv -h -f \"$tmp_link\" \"$link_name\"\n"
|
||||||
|
" else\n"
|
||||||
|
" rm -f \"$link_name\"\n"
|
||||||
|
" ln -s \"$target\" \"$link_name\"\n"
|
||||||
|
" fi\n"
|
||||||
|
"}\n\n"
|
||||||
|
"validate_closure()\n"
|
||||||
|
"{\n"
|
||||||
|
" closure=$1\n"
|
||||||
|
" [ -d \"$closure\" ] || die \"missing closure directory: $closure\"\n"
|
||||||
|
" [ -f \"$closure/activate\" ] || die \"closure is missing activate script: $closure\"\n"
|
||||||
|
" [ -f \"$closure/shepherd/init.scm\" ] || die \"closure is missing shepherd config: $closure\"\n"
|
||||||
|
" [ -f \"$closure/boot/loader.efi\" ] || die \"closure is missing loader.efi: $closure\"\n"
|
||||||
|
"}\n\n"
|
||||||
|
"ensure_default_declaration()\n"
|
||||||
|
"{\n"
|
||||||
|
" [ -f \"$declaration_file\" ] || die \"current declaration file is missing: $declaration_file\"\n"
|
||||||
|
" [ -f \"$declaration_info_file\" ] || die \"current declaration info file is missing: $declaration_info_file\"\n"
|
||||||
|
" current_system_name=$(default_system_name)\n"
|
||||||
|
" [ -n \"$current_system_name\" ] || die \"current declaration is missing a system variable name\"\n"
|
||||||
|
"}\n\n"
|
||||||
|
"run_node_cli()\n"
|
||||||
|
"{\n"
|
||||||
|
" [ -x \"$guile_store/bin/guile\" ] || die \"missing Guile runtime: $guile_store/bin/guile\"\n"
|
||||||
|
" [ -f \"$node_script\" ] || die \"missing bundled Fruix node CLI: $node_script\"\n"
|
||||||
|
" [ -d \"$node_module_root\" ] || die \"missing bundled Fruix modules: $node_module_root\"\n"
|
||||||
|
" [ -d \"$node_guix_root\" ] || die \"missing bundled Guix modules: $node_guix_root\"\n"
|
||||||
|
" guile_load_path=\"$node_module_root:$node_guix_root:$shepherd_store/share/guile/site/3.0:$guile_extra_store/share/guile/site/3.0\"\n"
|
||||||
|
" guile_system_path=\"$guile_store/share/guile/3.0:$guile_store/share/guile/site/3.0:$guile_store/share/guile/site:$guile_store/share/guile\"\n"
|
||||||
|
" guile_system_compiled_path=\"$guile_store/lib/guile/3.0/ccache:$guile_store/lib/guile/3.0/site-ccache\"\n"
|
||||||
|
" guile_load_compiled_path=\"$shepherd_store/lib/guile/3.0/site-ccache:$guile_extra_store/lib/guile/3.0/site-ccache\"\n"
|
||||||
|
" guile_system_extensions_path=\"$guile_store/lib/guile/3.0/extensions\"\n"
|
||||||
|
" guile_extensions_path=\"$guile_extra_store/lib/guile/3.0/extensions\"\n"
|
||||||
|
" ld_library_path=\"$guile_extra_store/lib:$guile_store/lib:/usr/local/lib\"\n"
|
||||||
|
" env \\\n"
|
||||||
|
" GUILE_AUTO_COMPILE=0 \\\n"
|
||||||
|
" GUILE_SYSTEM_PATH=\"$guile_system_path\" \\\n"
|
||||||
|
" GUILE_LOAD_PATH=\"$guile_load_path\" \\\n"
|
||||||
|
" GUILE_SYSTEM_COMPILED_PATH=\"$guile_system_compiled_path\" \\\n"
|
||||||
|
" GUILE_LOAD_COMPILED_PATH=\"$guile_load_compiled_path\" \\\n"
|
||||||
|
" GUILE_SYSTEM_EXTENSIONS_PATH=\"$guile_system_extensions_path\" \\\n"
|
||||||
|
" GUILE_EXTENSIONS_PATH=\"$guile_extensions_path\" \\\n"
|
||||||
|
" LD_LIBRARY_PATH=\"$ld_library_path\" \\\n"
|
||||||
|
" GUILE_PREFIX=\"$guile_store\" \\\n"
|
||||||
|
" GUILE_EXTRA_PREFIX=\"$guile_extra_store\" \\\n"
|
||||||
|
" SHEPHERD_PREFIX=\"$shepherd_store\" \\\n"
|
||||||
|
" FRUIX_GUILE_STORE=\"$guile_store\" \\\n"
|
||||||
|
" FRUIX_GUILE_EXTRA_STORE=\"$guile_extra_store\" \\\n"
|
||||||
|
" FRUIX_SHEPHERD_STORE=\"$shepherd_store\" \\\n"
|
||||||
|
" GUIX_SOURCE_DIR=\"$node_guix_root\" \\\n"
|
||||||
|
" FRUIX_PROJECT_ROOT=\"$node_root\" \\\n"
|
||||||
|
" \"$guile_store/bin/guile\" --no-auto-compile -s \"$node_script\" \"$@\"\n"
|
||||||
|
"}\n\n"
|
||||||
|
"system_build()\n"
|
||||||
|
"{\n"
|
||||||
|
" if [ $# -eq 0 ]; then\n"
|
||||||
|
" ensure_default_declaration\n"
|
||||||
|
" run_node_cli system build \"$declaration_file\" --system \"$current_system_name\" --store \"$default_store_dir\"\n"
|
||||||
|
" else\n"
|
||||||
|
" run_node_cli system build \"$@\"\n"
|
||||||
|
" fi\n"
|
||||||
|
"}\n\n"
|
||||||
|
"reconfigure_system()\n"
|
||||||
|
"{\n"
|
||||||
|
" build_output=$(mktemp /tmp/fruix-system-reconfigure.XXXXXX)\n"
|
||||||
|
" if [ $# -eq 0 ]; then\n"
|
||||||
|
" ensure_default_declaration\n"
|
||||||
|
" if ! run_node_cli system build \"$declaration_file\" --system \"$current_system_name\" --store \"$default_store_dir\" > \"$build_output\"; then\n"
|
||||||
|
" cat \"$build_output\" >&2 || true\n"
|
||||||
|
" rm -f \"$build_output\"\n"
|
||||||
|
" exit 1\n"
|
||||||
|
" fi\n"
|
||||||
|
" else\n"
|
||||||
|
" if ! run_node_cli system build \"$@\" > \"$build_output\"; then\n"
|
||||||
|
" cat \"$build_output\" >&2 || true\n"
|
||||||
|
" rm -f \"$build_output\"\n"
|
||||||
|
" exit 1\n"
|
||||||
|
" fi\n"
|
||||||
|
" fi\n"
|
||||||
|
" closure=$(sed -n 's/^closure_path=//p' \"$build_output\" | tail -n 1)\n"
|
||||||
|
" [ -n \"$closure\" ] || die \"failed to recover closure_path from in-system build output\"\n"
|
||||||
|
" cat \"$build_output\"\n"
|
||||||
|
" rm -f \"$build_output\"\n"
|
||||||
|
" switch_to_closure \"$closure\"\n"
|
||||||
|
" printf 'reconfigure_closure=%s\\n' \"$closure\"\n"
|
||||||
|
" printf 'reboot_required=true\\n'\n"
|
||||||
|
"}\n\n"
|
||||||
|
"max_generation_number()\n"
|
||||||
|
"{\n"
|
||||||
|
" max=0\n"
|
||||||
|
" if [ -d \"$generations_root\" ]; then\n"
|
||||||
|
" for path in \"$generations_root\"/*; do\n"
|
||||||
|
" [ -d \"$path\" ] || continue\n"
|
||||||
|
" base=$(basename \"$path\")\n"
|
||||||
|
" case \"$base\" in\n"
|
||||||
|
" ''|*[!0-9]*)\n"
|
||||||
|
" continue\n"
|
||||||
|
" ;;\n"
|
||||||
|
" esac\n"
|
||||||
|
" if [ \"$base\" -gt \"$max\" ]; then\n"
|
||||||
|
" max=$base\n"
|
||||||
|
" fi\n"
|
||||||
|
" done\n"
|
||||||
|
" fi\n"
|
||||||
|
" printf '%s\\n' \"$max\"\n"
|
||||||
|
"}\n\n"
|
||||||
|
"next_generation_number()\n"
|
||||||
|
"{\n"
|
||||||
|
" max=$(max_generation_number)\n"
|
||||||
|
" printf '%s\\n' $((max + 1))\n"
|
||||||
|
"}\n\n"
|
||||||
|
"write_generation_metadata()\n"
|
||||||
|
"{\n"
|
||||||
|
" generation=$1\n"
|
||||||
|
" closure=$2\n"
|
||||||
|
" action=$3\n"
|
||||||
|
" previous_generation=$4\n"
|
||||||
|
" previous_closure=$5\n"
|
||||||
|
" generation_dir=\"$generations_root/$generation\"\n"
|
||||||
|
" install_metadata_path=\"/var/lib/fruix/system/generations/$generation/install.scm\"\n"
|
||||||
|
" cat > \"$generation_dir/metadata.scm\" <<EOF\n"
|
||||||
|
"((system-generation-version . \"$layout_version\")\n"
|
||||||
|
" (generation-number . $generation)\n"
|
||||||
|
" (host-name . \"$host_name\")\n"
|
||||||
|
" (ready-marker . \"$ready_marker\")\n"
|
||||||
|
" (init-mode . $init_mode)\n"
|
||||||
|
" (closure-path . \"$closure\")\n"
|
||||||
|
" (parameters-file . \"$closure/parameters.scm\")\n"
|
||||||
|
" (freebsd-base-file . \"$closure/metadata/freebsd-base.scm\")\n"
|
||||||
|
" (freebsd-source-file . \"$closure/metadata/freebsd-source.scm\")\n"
|
||||||
|
" (freebsd-source-materializations-file . \"$closure/metadata/freebsd-source-materializations.scm\")\n"
|
||||||
|
" (host-base-provenance-file . \"$closure/metadata/host-base-provenance.scm\")\n"
|
||||||
|
" (store-layout-file . \"$closure/metadata/store-layout.scm\")\n"
|
||||||
|
" (install-metadata-path . \"$install_metadata_path\")\n"
|
||||||
|
" (deployment-action . \"$action\")\n"
|
||||||
|
" (previous-generation-number . \"$previous_generation\")\n"
|
||||||
|
" (previous-closure-path . \"$previous_closure\"))\n"
|
||||||
|
"EOF\n"
|
||||||
|
" chmod 644 \"$generation_dir/metadata.scm\"\n"
|
||||||
|
"}\n\n"
|
||||||
|
"write_generation_provenance()\n"
|
||||||
|
"{\n"
|
||||||
|
" generation=$1\n"
|
||||||
|
" closure=$2\n"
|
||||||
|
" generation_dir=\"$generations_root/$generation\"\n"
|
||||||
|
" cat > \"$generation_dir/provenance.scm\" <<EOF\n"
|
||||||
|
"((closure-path . \"$closure\")\n"
|
||||||
|
" (parameters-file . \"$closure/parameters.scm\")\n"
|
||||||
|
" (freebsd-base-file . \"$closure/metadata/freebsd-base.scm\")\n"
|
||||||
|
" (freebsd-source-file . \"$closure/metadata/freebsd-source.scm\")\n"
|
||||||
|
" (freebsd-source-materializations-file . \"$closure/metadata/freebsd-source-materializations.scm\")\n"
|
||||||
|
" (host-base-provenance-file . \"$closure/metadata/host-base-provenance.scm\")\n"
|
||||||
|
" (store-layout-file . \"$closure/metadata/store-layout.scm\"))\n"
|
||||||
|
"EOF\n"
|
||||||
|
" chmod 644 \"$generation_dir/provenance.scm\"\n"
|
||||||
|
"}\n\n"
|
||||||
|
"write_generation_install_metadata()\n"
|
||||||
|
"{\n"
|
||||||
|
" generation=$1\n"
|
||||||
|
" closure=$2\n"
|
||||||
|
" action=$3\n"
|
||||||
|
" previous_generation=$4\n"
|
||||||
|
" previous_closure=$5\n"
|
||||||
|
" generation_dir=\"$generations_root/$generation\"\n"
|
||||||
|
" cat > \"$generation_dir/install.scm\" <<EOF\n"
|
||||||
|
"((deployment-kind . \"$action\")\n"
|
||||||
|
" (generation-number . $generation)\n"
|
||||||
|
" (closure-path . \"$closure\")\n"
|
||||||
|
" (previous-generation-number . \"$previous_generation\")\n"
|
||||||
|
" (previous-closure-path . \"$previous_closure\")\n"
|
||||||
|
" (freebsd-base-file . \"$closure/metadata/freebsd-base.scm\")\n"
|
||||||
|
" (freebsd-source-file . \"$closure/metadata/freebsd-source.scm\")\n"
|
||||||
|
" (freebsd-source-materializations-file . \"$closure/metadata/freebsd-source-materializations.scm\")\n"
|
||||||
|
" (store-layout-file . \"$closure/metadata/store-layout.scm\"))\n"
|
||||||
|
"EOF\n"
|
||||||
|
" chmod 644 \"$generation_dir/install.scm\"\n"
|
||||||
|
"}\n\n"
|
||||||
|
"prepare_generation()\n"
|
||||||
|
"{\n"
|
||||||
|
" generation=$1\n"
|
||||||
|
" closure=$2\n"
|
||||||
|
" action=$3\n"
|
||||||
|
" previous_generation=$4\n"
|
||||||
|
" previous_closure=$5\n"
|
||||||
|
" generation_dir=\"$generations_root/$generation\"\n"
|
||||||
|
" mkdir -p \"$generation_dir\"\n"
|
||||||
|
" symlink_force \"$closure\" \"$generation_dir/closure\"\n"
|
||||||
|
" write_generation_metadata \"$generation\" \"$closure\" \"$action\" \"$previous_generation\" \"$previous_closure\"\n"
|
||||||
|
" write_generation_provenance \"$generation\" \"$closure\"\n"
|
||||||
|
" write_generation_install_metadata \"$generation\" \"$closure\" \"$action\" \"$previous_generation\" \"$previous_closure\"\n"
|
||||||
|
"}\n\n"
|
||||||
|
"update_efi_loader()\n"
|
||||||
|
"{\n"
|
||||||
|
" closure=$1\n"
|
||||||
|
" [ -e /dev/gpt/efiboot ] || return 0\n"
|
||||||
|
" esp_mount=$(mktemp -d /tmp/fruix-efiboot.XXXXXX)\n"
|
||||||
|
" if /sbin/mount -t msdosfs /dev/gpt/efiboot \"$esp_mount\" >/dev/null 2>&1; then\n"
|
||||||
|
" mkdir -p \"$esp_mount/EFI/BOOT\"\n"
|
||||||
|
" cp \"$closure/boot/loader.efi\" \"$esp_mount/EFI/BOOT/BOOTX64.EFI\"\n"
|
||||||
|
" sync\n"
|
||||||
|
" /sbin/umount \"$esp_mount\" >/dev/null 2>&1 || true\n"
|
||||||
|
" fi\n"
|
||||||
|
" rmdir \"$esp_mount\" >/dev/null 2>&1 || true\n"
|
||||||
|
"}\n\n"
|
||||||
|
"status()\n"
|
||||||
|
"{\n"
|
||||||
|
" current_generation=$(read_file_maybe \"$current_generation_file\")\n"
|
||||||
|
" current_generation_link=$(read_link_maybe \"$current_link\")\n"
|
||||||
|
" current_closure=$(read_link_maybe \"$run_current_link\")\n"
|
||||||
|
" rollback_generation=$(read_file_maybe \"$rollback_generation_file\")\n"
|
||||||
|
" rollback_generation_link=$(read_link_maybe \"$rollback_link\")\n"
|
||||||
|
" rollback_closure=\"\"\n"
|
||||||
|
" if [ -n \"$rollback_generation_link\" ] && [ -L \"$system_root/$rollback_generation_link/closure\" ]; then\n"
|
||||||
|
" rollback_closure=$(readlink \"$system_root/$rollback_generation_link/closure\")\n"
|
||||||
|
" fi\n"
|
||||||
|
" printf 'current_generation=%s\\n' \"$current_generation\"\n"
|
||||||
|
" printf 'current_link=%s\\n' \"$current_generation_link\"\n"
|
||||||
|
" printf 'current_closure=%s\\n' \"$current_closure\"\n"
|
||||||
|
" printf 'rollback_generation=%s\\n' \"$rollback_generation\"\n"
|
||||||
|
" printf 'rollback_link=%s\\n' \"$rollback_generation_link\"\n"
|
||||||
|
" printf 'rollback_closure=%s\\n' \"$rollback_closure\"\n"
|
||||||
|
"}\n\n"
|
||||||
|
"switch_to_closure()\n"
|
||||||
|
"{\n"
|
||||||
|
" target_closure=$1\n"
|
||||||
|
" validate_closure \"$target_closure\"\n"
|
||||||
|
" current_generation=$(read_file_maybe \"$current_generation_file\")\n"
|
||||||
|
" current_closure=$(read_link_maybe \"$run_current_link\")\n"
|
||||||
|
" [ -n \"$current_generation\" ] || die \"missing current generation metadata\"\n"
|
||||||
|
" [ -n \"$current_closure\" ] || die \"missing /run/current-system target\"\n"
|
||||||
|
" if [ \"$target_closure\" = \"$current_closure\" ]; then\n"
|
||||||
|
" status\n"
|
||||||
|
" return 0\n"
|
||||||
|
" fi\n"
|
||||||
|
" new_generation=$(next_generation_number)\n"
|
||||||
|
" prepare_generation \"$new_generation\" \"$target_closure\" switch \"$current_generation\" \"$current_closure\"\n"
|
||||||
|
" symlink_force \"generations/$current_generation\" \"$rollback_link\"\n"
|
||||||
|
" printf '%s\\n' \"$current_generation\" > \"$rollback_generation_file\"\n"
|
||||||
|
" symlink_force \"$current_closure\" \"$gcroots_root/rollback-system\"\n"
|
||||||
|
" symlink_force \"generations/$new_generation\" \"$current_link\"\n"
|
||||||
|
" printf '%s\\n' \"$new_generation\" > \"$current_generation_file\"\n"
|
||||||
|
" symlink_force \"$target_closure\" \"$gcroots_root/system-$new_generation\"\n"
|
||||||
|
" symlink_force \"$target_closure\" \"$gcroots_root/current-system\"\n"
|
||||||
|
" symlink_force \"$target_closure\" \"$run_current_link\"\n"
|
||||||
|
" update_efi_loader \"$target_closure\"\n"
|
||||||
|
" status\n"
|
||||||
|
"}\n\n"
|
||||||
|
"rollback_current_generation()\n"
|
||||||
|
"{\n"
|
||||||
|
" rollback_generation=$(read_file_maybe \"$rollback_generation_file\")\n"
|
||||||
|
" rollback_generation_link=$(read_link_maybe \"$rollback_link\")\n"
|
||||||
|
" [ -n \"$rollback_generation\" ] || die \"no rollback generation is recorded\"\n"
|
||||||
|
" [ -n \"$rollback_generation_link\" ] || die \"no rollback link is recorded\"\n"
|
||||||
|
" rollback_closure=$(read_link_maybe \"$system_root/$rollback_generation_link/closure\")\n"
|
||||||
|
" [ -n \"$rollback_closure\" ] || die \"rollback generation has no closure link\"\n"
|
||||||
|
" current_generation=$(read_file_maybe \"$current_generation_file\")\n"
|
||||||
|
" current_closure=$(read_link_maybe \"$run_current_link\")\n"
|
||||||
|
" [ -n \"$current_generation\" ] || die \"missing current generation metadata\"\n"
|
||||||
|
" [ -n \"$current_closure\" ] || die \"missing current closure link\"\n"
|
||||||
|
" symlink_force \"generations/$current_generation\" \"$rollback_link\"\n"
|
||||||
|
" printf '%s\\n' \"$current_generation\" > \"$rollback_generation_file\"\n"
|
||||||
|
" symlink_force \"$current_closure\" \"$gcroots_root/rollback-system\"\n"
|
||||||
|
" symlink_force \"$rollback_generation_link\" \"$current_link\"\n"
|
||||||
|
" printf '%s\\n' \"$rollback_generation\" > \"$current_generation_file\"\n"
|
||||||
|
" symlink_force \"$rollback_closure\" \"$gcroots_root/current-system\"\n"
|
||||||
|
" symlink_force \"$rollback_closure\" \"$run_current_link\"\n"
|
||||||
|
" update_efi_loader \"$rollback_closure\"\n"
|
||||||
|
" status\n"
|
||||||
|
"}\n\n"
|
||||||
|
"case \"${1:-}\" in\n"
|
||||||
|
" system)\n"
|
||||||
|
" case \"${2:-}\" in\n"
|
||||||
|
" status)\n"
|
||||||
|
" [ $# -eq 2 ] || { usage >&2; exit 1; }\n"
|
||||||
|
" status\n"
|
||||||
|
" ;;\n"
|
||||||
|
" build)\n"
|
||||||
|
" shift 2\n"
|
||||||
|
" system_build \"$@\"\n"
|
||||||
|
" ;;\n"
|
||||||
|
" reconfigure)\n"
|
||||||
|
" shift 2\n"
|
||||||
|
" reconfigure_system \"$@\"\n"
|
||||||
|
" ;;\n"
|
||||||
|
" switch)\n"
|
||||||
|
" [ $# -eq 3 ] || { usage >&2; exit 1; }\n"
|
||||||
|
" switch_to_closure \"$3\"\n"
|
||||||
|
" ;;\n"
|
||||||
|
" rollback)\n"
|
||||||
|
" [ $# -eq 2 ] || { usage >&2; exit 1; }\n"
|
||||||
|
" rollback_current_generation\n"
|
||||||
|
" ;;\n"
|
||||||
|
" --help|-h|'')\n"
|
||||||
|
" usage\n"
|
||||||
|
" ;;\n"
|
||||||
|
" *)\n"
|
||||||
|
" usage >&2\n"
|
||||||
|
" exit 1\n"
|
||||||
|
" ;;\n"
|
||||||
|
" esac\n"
|
||||||
|
" ;;\n"
|
||||||
|
" --help|-h|'')\n"
|
||||||
|
" usage\n"
|
||||||
|
" ;;\n"
|
||||||
|
" *)\n"
|
||||||
|
" usage >&2\n"
|
||||||
|
" exit 1\n"
|
||||||
|
" ;;\n"
|
||||||
|
"esac\n"))
|
||||||
|
(define (render-development-environment-script os)
|
||||||
|
(string-append
|
||||||
|
"#!/bin/sh\n"
|
||||||
|
"set -eu\n"
|
||||||
|
"profile=/run/current-system/development-profile\n"
|
||||||
|
"[ -d \"$profile\" ] || {\n"
|
||||||
|
" echo \"fruix-development-environment: development profile is not available\" >&2\n"
|
||||||
|
" exit 1\n"
|
||||||
|
"}\n"
|
||||||
|
"cat <<EOF\n"
|
||||||
|
"export FRUIX_DEVELOPMENT_PROFILE=\"$profile\"\n"
|
||||||
|
"export FRUIX_DEVELOPMENT_INCLUDE=\"$profile/usr/include\"\n"
|
||||||
|
"export FRUIX_DEVELOPMENT_LIB=\"$profile/lib\"\n"
|
||||||
|
"export FRUIX_DEVELOPMENT_SHARE_MK=\"$profile/usr/share/mk\"\n"
|
||||||
|
"export FRUIX_DEVELOPMENT_BIN=\"$profile/bin\"\n"
|
||||||
|
"export FRUIX_DEVELOPMENT_USR_BIN=\"$profile/usr/bin\"\n"
|
||||||
|
"export FRUIX_CC=\"$profile/bin/cc\"\n"
|
||||||
|
"export FRUIX_CXX=\"$profile/bin/c++\"\n"
|
||||||
|
"export FRUIX_AR=\"$profile/bin/ar\"\n"
|
||||||
|
"export FRUIX_RANLIB=\"$profile/bin/ranlib\"\n"
|
||||||
|
"export FRUIX_NM=\"$profile/bin/nm\"\n"
|
||||||
|
"export FRUIX_BMAKE=\"/usr/bin/make\"\n"
|
||||||
|
"export CC=\"$profile/bin/cc\"\n"
|
||||||
|
"export CXX=\"$profile/bin/c++\"\n"
|
||||||
|
"export AR=\"$profile/bin/ar\"\n"
|
||||||
|
"export RANLIB=\"$profile/bin/ranlib\"\n"
|
||||||
|
"export NM=\"$profile/bin/nm\"\n"
|
||||||
|
"export CPPFLAGS=\"-I$profile/usr/include\"\n"
|
||||||
|
"export CFLAGS=\"-I$profile/usr/include\"\n"
|
||||||
|
"export CXXFLAGS=\"-I$profile/usr/include\"\n"
|
||||||
|
"export LDFLAGS=\"-L$profile/lib\"\n"
|
||||||
|
"export MAKEFLAGS=\"-m $profile/usr/share/mk\"\n"
|
||||||
|
"export PATH=\"/bin:/sbin:/usr/bin:/usr/sbin:/usr/local/bin:/usr/local/sbin:$profile/bin:$profile/sbin:$profile/usr/bin:$profile/usr/sbin\"\n"
|
||||||
|
"EOF\n"))
|
||||||
|
(define (render-build-environment-script os)
|
||||||
|
(string-append
|
||||||
|
"#!/bin/sh\n"
|
||||||
|
"set -eu\n"
|
||||||
|
"profile=/run/current-system/build-profile\n"
|
||||||
|
"[ -d \"$profile\" ] || {\n"
|
||||||
|
" echo \"fruix-build-environment: build profile is not available\" >&2\n"
|
||||||
|
" exit 1\n"
|
||||||
|
"}\n"
|
||||||
|
"cat <<EOF\n"
|
||||||
|
"unset MAKEOBJDIRPREFIX MAKEFLAGS CC CXX AR RANLIB NM CPPFLAGS CFLAGS CXXFLAGS LDFLAGS\n"
|
||||||
|
"unset FRUIX_DEVELOPMENT_PROFILE FRUIX_DEVELOPMENT_INCLUDE FRUIX_DEVELOPMENT_LIB FRUIX_DEVELOPMENT_SHARE_MK\n"
|
||||||
|
"unset FRUIX_DEVELOPMENT_BIN FRUIX_DEVELOPMENT_USR_BIN FRUIX_CC FRUIX_CXX FRUIX_AR FRUIX_RANLIB FRUIX_NM FRUIX_BMAKE\n"
|
||||||
|
"export FRUIX_BUILD_PROFILE=\"$profile\"\n"
|
||||||
|
"export FRUIX_BUILD_INCLUDE=\"$profile/usr/include\"\n"
|
||||||
|
"export FRUIX_BUILD_LIB=\"$profile/lib\"\n"
|
||||||
|
"export FRUIX_BUILD_SHARE_MK=\"$profile/usr/share/mk\"\n"
|
||||||
|
"export FRUIX_BUILD_BIN=\"$profile/bin\"\n"
|
||||||
|
"export FRUIX_BUILD_USR_BIN=\"$profile/usr/bin\"\n"
|
||||||
|
"export FRUIX_BUILD_CC=\"$profile/bin/cc\"\n"
|
||||||
|
"export FRUIX_BUILD_CXX=\"$profile/bin/c++\"\n"
|
||||||
|
"export FRUIX_BUILD_AR=\"$profile/bin/ar\"\n"
|
||||||
|
"export FRUIX_BUILD_RANLIB=\"$profile/bin/ranlib\"\n"
|
||||||
|
"export FRUIX_BUILD_NM=\"$profile/bin/nm\"\n"
|
||||||
|
"export FRUIX_BMAKE=\"make\"\n"
|
||||||
|
"export PATH=\"/bin:/sbin:/usr/bin:/usr/sbin:/usr/local/bin:/usr/local/sbin:$profile/bin:$profile/sbin:$profile/usr/bin:$profile/usr/sbin\"\n"
|
||||||
|
"EOF\n"))
|
||||||
|
(define (render-self-hosted-native-build-script os)
|
||||||
|
(let* ((base-spec (freebsd-base-spec (operating-system-freebsd-base os)))
|
||||||
|
(base-name (assoc-ref base-spec 'name))
|
||||||
|
(version-label (assoc-ref base-spec 'version-label))
|
||||||
|
(release (assoc-ref base-spec 'release))
|
||||||
|
(branch (assoc-ref base-spec 'branch))
|
||||||
|
(declared-source-root (assoc-ref base-spec 'source-root))
|
||||||
|
(target (assoc-ref base-spec 'target))
|
||||||
|
(target-arch (assoc-ref base-spec 'target-arch))
|
||||||
|
(kernconf (assoc-ref base-spec 'kernconf))
|
||||||
|
(make-flags (or (assoc-ref base-spec 'make-flags) '()))
|
||||||
|
(build-common (string-join
|
||||||
|
(append (list (format #f "TARGET=~a" target)
|
||||||
|
(format #f "TARGET_ARCH=~a" target-arch)
|
||||||
|
(format #f "KERNCONF=~a" kernconf))
|
||||||
|
make-flags)
|
||||||
|
" "))
|
||||||
|
(install-common (string-append build-common " DB_FROM_SRC=yes")))
|
||||||
|
(string-append
|
||||||
|
"#!/bin/sh\n"
|
||||||
|
"set -eu\n"
|
||||||
|
"umask 022\n"
|
||||||
|
"profile=/run/current-system/build-profile\n"
|
||||||
|
"guest_host_name='" (operating-system-host-name os) "'\n"
|
||||||
|
"[ -d \"$profile\" ] || {\n"
|
||||||
|
" echo \"fruix-self-hosted-native-build: build profile is not available\" >&2\n"
|
||||||
|
" exit 1\n"
|
||||||
|
"}\n"
|
||||||
|
"[ -x /usr/local/bin/fruix-build-environment ] || {\n"
|
||||||
|
" echo \"fruix-self-hosted-native-build: build environment helper is missing\" >&2\n"
|
||||||
|
" exit 1\n"
|
||||||
|
"}\n"
|
||||||
|
"eval \"$(/usr/local/bin/fruix-build-environment)\"\n"
|
||||||
|
"[ \"${FRUIX_BUILD_PROFILE:-}\" = \"$profile\" ] || {\n"
|
||||||
|
" echo \"fruix-self-hosted-native-build: build environment helper exported an unexpected profile\" >&2\n"
|
||||||
|
" exit 1\n"
|
||||||
|
"}\n"
|
||||||
|
"[ -L /usr/include ] || {\n"
|
||||||
|
" echo \"fruix-self-hosted-native-build: /usr/include compatibility link is missing\" >&2\n"
|
||||||
|
" exit 1\n"
|
||||||
|
"}\n"
|
||||||
|
"[ \"$(readlink /usr/include)\" = \"/run/current-system/build-profile/usr/include\" ] || {\n"
|
||||||
|
" echo \"fruix-self-hosted-native-build: /usr/include points at the wrong target\" >&2\n"
|
||||||
|
" exit 1\n"
|
||||||
|
"}\n"
|
||||||
|
"[ -L /usr/share/mk ] || {\n"
|
||||||
|
" echo \"fruix-self-hosted-native-build: /usr/share/mk compatibility link is missing\" >&2\n"
|
||||||
|
" exit 1\n"
|
||||||
|
"}\n"
|
||||||
|
"[ \"$(readlink /usr/share/mk)\" = \"/run/current-system/build-profile/usr/share/mk\" ] || {\n"
|
||||||
|
" echo \"fruix-self-hosted-native-build: /usr/share/mk points at the wrong target\" >&2\n"
|
||||||
|
" exit 1\n"
|
||||||
|
"}\n"
|
||||||
|
"make_cmd=${FRUIX_BMAKE:-make}\n"
|
||||||
|
"jobs=${FRUIX_SELF_HOSTED_NATIVE_BUILD_JOBS:-$(sysctl -n hw.ncpu)}\n"
|
||||||
|
"case \"$jobs\" in\n"
|
||||||
|
" ''|*[!0-9]*)\n"
|
||||||
|
" echo \"fruix-self-hosted-native-build: invalid job count: $jobs\" >&2\n"
|
||||||
|
" exit 1\n"
|
||||||
|
" ;;\n"
|
||||||
|
"esac\n"
|
||||||
|
"run_id=${FRUIX_SELF_HOSTED_NATIVE_BUILD_ID:-$(date -u +%Y%m%dT%H%M%SZ)}\n"
|
||||||
|
"build_root_base=${FRUIX_SELF_HOSTED_NATIVE_BUILD_ROOT_BASE:-/var/tmp/fruix-self-hosted-native-builds}\n"
|
||||||
|
"result_root_base=${FRUIX_SELF_HOSTED_NATIVE_BUILD_OUTPUT_BASE:-/var/lib/fruix/native-builds}\n"
|
||||||
|
"build_root=$build_root_base/$run_id\n"
|
||||||
|
"result_root=$result_root_base/$run_id\n"
|
||||||
|
"logdir=$result_root/logs\n"
|
||||||
|
"status_file=$result_root/status\n"
|
||||||
|
"metadata_file=$result_root/metadata.txt\n"
|
||||||
|
"promotion_file=$result_root/promotion.scm\n"
|
||||||
|
"world_stage=$build_root/stage-world\n"
|
||||||
|
"kernel_stage=$build_root/stage-kernel\n"
|
||||||
|
"world_artifact=$result_root/artifacts/world\n"
|
||||||
|
"headers_artifact=$result_root/artifacts/headers\n"
|
||||||
|
"kernel_artifact=$result_root/artifacts/kernel\n"
|
||||||
|
"bootloader_artifact=$result_root/artifacts/bootloader\n"
|
||||||
|
"latest_link=$result_root_base/latest\n"
|
||||||
|
"mkdir -p \"$build_root\" \"$result_root\" \"$logdir\"\n"
|
||||||
|
"printf 'running\\n' > \"$status_file\"\n"
|
||||||
|
"fail_mark() {\n"
|
||||||
|
" rc=$?\n"
|
||||||
|
" if [ \"$rc\" -ne 0 ]; then\n"
|
||||||
|
" printf 'failed\\n' > \"$status_file\"\n"
|
||||||
|
" fi\n"
|
||||||
|
"}\n"
|
||||||
|
"trap fail_mark EXIT HUP INT TERM\n"
|
||||||
|
"closure=$(readlink /run/current-system)\n"
|
||||||
|
"store_layout=$closure/metadata/store-layout.scm\n"
|
||||||
|
"[ -f \"$store_layout\" ] || {\n"
|
||||||
|
" echo \"fruix-self-hosted-native-build: store layout metadata is missing\" >&2\n"
|
||||||
|
" exit 1\n"
|
||||||
|
"}\n"
|
||||||
|
"source_store=$(sed -n 's/.*\"\\(\\/frx\\/store\\/[^\"]*-freebsd-source-[^\"]*\\)\".*/\\1/p' \"$store_layout\" | head -n 1)\n"
|
||||||
|
"[ -n \"$source_store\" ] || {\n"
|
||||||
|
" echo \"fruix-self-hosted-native-build: failed to recover source store from store-layout.scm\" >&2\n"
|
||||||
|
" exit 1\n"
|
||||||
|
"}\n"
|
||||||
|
"source_root=$source_store/tree\n"
|
||||||
|
"[ -d \"$source_root\" ] || {\n"
|
||||||
|
" echo \"fruix-self-hosted-native-build: source root is missing: $source_root\" >&2\n"
|
||||||
|
" exit 1\n"
|
||||||
|
"}\n"
|
||||||
|
"mkdir -p \"$world_artifact\" \"$headers_artifact/usr\" \"$kernel_artifact/boot\" \"$bootloader_artifact/boot\"\n"
|
||||||
|
"export MAKEOBJDIRPREFIX=\"$build_root/obj\"\n"
|
||||||
|
"\"$make_cmd\" -j\"$jobs\" -C \"$source_root\" " build-common " buildworld > \"$logdir/buildworld.log\" 2>&1\n"
|
||||||
|
"\"$make_cmd\" -j\"$jobs\" -C \"$source_root\" " build-common " buildkernel > \"$logdir/buildkernel.log\" 2>&1\n"
|
||||||
|
"\"$make_cmd\" -C \"$source_root\" " install-common " DESTDIR=\"$world_stage\" installworld > \"$logdir/installworld.log\" 2>&1\n"
|
||||||
|
"\"$make_cmd\" -C \"$source_root\" " install-common " DESTDIR=\"$world_stage\" distribution > \"$logdir/distribution.log\" 2>&1\n"
|
||||||
|
"\"$make_cmd\" -C \"$source_root\" " install-common " DESTDIR=\"$kernel_stage\" installkernel > \"$logdir/installkernel.log\" 2>&1\n"
|
||||||
|
"cp -a \"$world_stage/.\" \"$world_artifact/\"\n"
|
||||||
|
"cp -a \"$kernel_stage/boot/kernel\" \"$kernel_artifact/boot/kernel\"\n"
|
||||||
|
"cp -a \"$world_stage/usr/include\" \"$headers_artifact/usr/include\"\n"
|
||||||
|
"mkdir -p \"$headers_artifact/usr/share\"\n"
|
||||||
|
"cp -a \"$world_stage/usr/share/mk\" \"$headers_artifact/usr/share/mk\"\n"
|
||||||
|
"cp -a \"$world_stage/boot/loader\" \"$bootloader_artifact/boot/loader\"\n"
|
||||||
|
"cp -a \"$world_stage/boot/loader.efi\" \"$bootloader_artifact/boot/loader.efi\"\n"
|
||||||
|
"cp -a \"$world_stage/boot/device.hints\" \"$bootloader_artifact/boot/device.hints\"\n"
|
||||||
|
"cp -a \"$world_stage/boot/defaults\" \"$bootloader_artifact/boot/defaults\"\n"
|
||||||
|
"cp -a \"$world_stage/boot/lua\" \"$bootloader_artifact/boot/lua\"\n"
|
||||||
|
"[ -f \"$world_artifact/bin/sh\" ]\n"
|
||||||
|
"[ -f \"$kernel_artifact/boot/kernel/kernel\" ]\n"
|
||||||
|
"[ -f \"$headers_artifact/usr/include/sys/param.h\" ]\n"
|
||||||
|
"[ -f \"$headers_artifact/usr/share/mk/bsd.prog.mk\" ]\n"
|
||||||
|
"[ -f \"$bootloader_artifact/boot/loader.efi\" ]\n"
|
||||||
|
"[ -f \"$bootloader_artifact/boot/defaults/loader.conf\" ]\n"
|
||||||
|
"[ -f \"$bootloader_artifact/boot/lua/loader.lua\" ]\n"
|
||||||
|
"sha_kernel=$(sha256 -q \"$kernel_artifact/boot/kernel/kernel\")\n"
|
||||||
|
"sha_loader=$(sha256 -q \"$bootloader_artifact/boot/loader.efi\")\n"
|
||||||
|
"sha_param=$(sha256 -q \"$headers_artifact/usr/include/sys/param.h\")\n"
|
||||||
|
"buildworld_tail=$(tail -n 20 \"$logdir/buildworld.log\" | tr '\\n' ' ')\n"
|
||||||
|
"buildkernel_tail=$(tail -n 20 \"$logdir/buildkernel.log\" | tr '\\n' ' ')\n"
|
||||||
|
"installworld_tail=$(tail -n 20 \"$logdir/installworld.log\" | tr '\\n' ' ')\n"
|
||||||
|
"distribution_tail=$(tail -n 20 \"$logdir/distribution.log\" | tr '\\n' ' ')\n"
|
||||||
|
"installkernel_tail=$(tail -n 20 \"$logdir/installkernel.log\" | tr '\\n' ' ')\n"
|
||||||
|
"root_df=$(df -h / | tail -n 1 | tr -s ' ' | tr '\\t' ' ')\n"
|
||||||
|
"build_root_size=$(du -sh \"$build_root\" | awk '{print $1}')\n"
|
||||||
|
"result_root_size=$(du -sh \"$result_root\" | awk '{print $1}')\n"
|
||||||
|
"world_artifact_size=$(du -sh \"$world_artifact\" | awk '{print $1}')\n"
|
||||||
|
"kernel_artifact_size=$(du -sh \"$kernel_artifact\" | awk '{print $1}')\n"
|
||||||
|
"headers_artifact_size=$(du -sh \"$headers_artifact\" | awk '{print $1}')\n"
|
||||||
|
"bootloader_artifact_size=$(du -sh \"$bootloader_artifact\" | awk '{print $1}')\n"
|
||||||
|
"rm -f \"$latest_link\"\n"
|
||||||
|
"ln -s \"$result_root\" \"$latest_link\"\n"
|
||||||
|
"cat >\"$promotion_file\" <<EOF\n"
|
||||||
|
"((native-build-result-version . \"1\")\n"
|
||||||
|
" (executor . ((kind . self-hosted)\n"
|
||||||
|
" (name . \"guest-self-hosted\")\n"
|
||||||
|
" (version . \"5\")\n"
|
||||||
|
" (properties . ((helper-path . \"/usr/local/bin/fruix-self-hosted-native-build\")\n"
|
||||||
|
" (guest-host-name . \"$guest_host_name\")\n"
|
||||||
|
" (build-root-base . \"$build_root_base\")\n"
|
||||||
|
" (result-root-base . \"$result_root_base\")))))\n"
|
||||||
|
" (run-id . \"$run_id\")\n"
|
||||||
|
" (guest-host-name . \"$guest_host_name\")\n"
|
||||||
|
" (closure-path . \"$closure\")\n"
|
||||||
|
" (build-profile . \"$profile\")\n"
|
||||||
|
" (freebsd-base . ((name . \"" base-name "\")\n"
|
||||||
|
" (version-label . \"" version-label "\")\n"
|
||||||
|
" (release . \"" release "\")\n"
|
||||||
|
" (branch . \"" branch "\")\n"
|
||||||
|
" (source-root . \"" declared-source-root "\")\n"
|
||||||
|
" (target . \"" target "\")\n"
|
||||||
|
" (target-arch . \"" target-arch "\")\n"
|
||||||
|
" (kernconf . \"" kernconf "\")))\n"
|
||||||
|
" (source . ((store-path . \"$source_store\")\n"
|
||||||
|
" (source-root . \"$source_root\")))\n"
|
||||||
|
" (build-policy . ((jobs . \"$jobs\")\n"
|
||||||
|
" (build-common . \"" build-common "\")\n"
|
||||||
|
" (install-common . \"" install-common "\")))\n"
|
||||||
|
" (artifacts . ((world . ((path . \"artifacts/world\")\n"
|
||||||
|
" (required-file . \"bin/sh\")))\n"
|
||||||
|
" (kernel . ((path . \"artifacts/kernel\")\n"
|
||||||
|
" (required-file . \"boot/kernel/kernel\")\n"
|
||||||
|
" (recorded-sha256 . \"$sha_kernel\")))\n"
|
||||||
|
" (headers . ((path . \"artifacts/headers\")\n"
|
||||||
|
" (required-file . \"usr/include/sys/param.h\")\n"
|
||||||
|
" (recorded-sha256 . \"$sha_param\")))\n"
|
||||||
|
" (bootloader . ((path . \"artifacts/bootloader\")\n"
|
||||||
|
" (required-file . \"boot/loader.efi\")\n"
|
||||||
|
" (recorded-sha256 . \"$sha_loader\"))))))\n"
|
||||||
|
"EOF\n"
|
||||||
|
"cat >\"$metadata_file\" <<EOF\n"
|
||||||
|
"run_id=$run_id\n"
|
||||||
|
"helper_version=5\n"
|
||||||
|
"executor_kind=self-hosted\n"
|
||||||
|
"executor_name=guest-self-hosted\n"
|
||||||
|
"executor_version=5\n"
|
||||||
|
"closure_path=$closure\n"
|
||||||
|
"guest_host_name=$guest_host_name\n"
|
||||||
|
"build_profile=$profile\n"
|
||||||
|
"source_store=$source_store\n"
|
||||||
|
"source_root=$source_root\n"
|
||||||
|
"build_jobs=$jobs\n"
|
||||||
|
"build_common=" build-common "\n"
|
||||||
|
"install_common=" install-common "\n"
|
||||||
|
"build_root=$build_root\n"
|
||||||
|
"result_root=$result_root\n"
|
||||||
|
"logdir=$logdir\n"
|
||||||
|
"status_file=$status_file\n"
|
||||||
|
"metadata_file=$metadata_file\n"
|
||||||
|
"promotion_file=$promotion_file\n"
|
||||||
|
"world_stage=$world_stage\n"
|
||||||
|
"kernel_stage=$kernel_stage\n"
|
||||||
|
"world_artifact=$world_artifact\n"
|
||||||
|
"kernel_artifact=$kernel_artifact\n"
|
||||||
|
"headers_artifact=$headers_artifact\n"
|
||||||
|
"bootloader_artifact=$bootloader_artifact\n"
|
||||||
|
"latest_link=$latest_link\n"
|
||||||
|
"root_df=$root_df\n"
|
||||||
|
"build_root_size=$build_root_size\n"
|
||||||
|
"result_root_size=$result_root_size\n"
|
||||||
|
"world_artifact_size=$world_artifact_size\n"
|
||||||
|
"kernel_artifact_size=$kernel_artifact_size\n"
|
||||||
|
"headers_artifact_size=$headers_artifact_size\n"
|
||||||
|
"bootloader_artifact_size=$bootloader_artifact_size\n"
|
||||||
|
"sha_kernel=$sha_kernel\n"
|
||||||
|
"sha_loader=$sha_loader\n"
|
||||||
|
"sha_param=$sha_param\n"
|
||||||
|
"buildworld_tail=$buildworld_tail\n"
|
||||||
|
"buildkernel_tail=$buildkernel_tail\n"
|
||||||
|
"installworld_tail=$installworld_tail\n"
|
||||||
|
"distribution_tail=$distribution_tail\n"
|
||||||
|
"installkernel_tail=$installkernel_tail\n"
|
||||||
|
"self_hosted_native_build=ok\n"
|
||||||
|
"EOF\n"
|
||||||
|
"printf 'ok\\n' > \"$status_file\"\n"
|
||||||
|
"cat \"$metadata_file\"\n")))
|
||||||
|
|
||||||
|
|
||||||
(define* (operating-system-generated-files os #:key guile-store guile-extra-store shepherd-store)
|
(define* (operating-system-generated-files os #:key guile-store guile-extra-store shepherd-store)
|
||||||
(append
|
(append
|
||||||
@@ -486,7 +1207,20 @@
|
|||||||
#:guile-store guile-store
|
#:guile-store guile-store
|
||||||
#:guile-extra-store guile-extra-store
|
#:guile-extra-store guile-extra-store
|
||||||
#:shepherd-store shepherd-store))
|
#:shepherd-store shepherd-store))
|
||||||
("shepherd/init.scm" . ,(render-shepherd-config os)))
|
("shepherd/init.scm" . ,(render-shepherd-config os))
|
||||||
|
("usr/local/bin/fruix"
|
||||||
|
. ,(render-installed-system-fruix os guile-store guile-extra-store shepherd-store)))
|
||||||
|
(bundled-fruix-node-files)
|
||||||
|
(if (null? (operating-system-development-packages os))
|
||||||
|
'()
|
||||||
|
`(("usr/local/bin/fruix-development-environment"
|
||||||
|
. ,(render-development-environment-script os))))
|
||||||
|
(if (null? (operating-system-build-packages os))
|
||||||
|
'()
|
||||||
|
`(("usr/local/bin/fruix-build-environment"
|
||||||
|
. ,(render-build-environment-script os))
|
||||||
|
("usr/local/bin/fruix-self-hosted-native-build"
|
||||||
|
. ,(render-self-hosted-native-build-script os))))
|
||||||
(if (pid1-init-mode? os)
|
(if (pid1-init-mode? os)
|
||||||
`(("boot/fruix-pid1" . ,(render-pid1-script os shepherd-store guile-store guile-extra-store)))
|
`(("boot/fruix-pid1" . ,(render-pid1-script os shepherd-store guile-store guile-extra-store)))
|
||||||
'())
|
'())
|
||||||
|
|||||||
@@ -36,7 +36,7 @@
|
|||||||
(define (ensure-git-source-cache source cache-dir)
|
(define (ensure-git-source-cache source cache-dir)
|
||||||
(let* ((url (freebsd-source-url source))
|
(let* ((url (freebsd-source-url source))
|
||||||
(repo-dir (string-append cache-dir "/git/"
|
(repo-dir (string-append cache-dir "/git/"
|
||||||
(string-hash (string-append "git:" url))
|
(sha256-string (string-append "git:" url))
|
||||||
".git")))
|
".git")))
|
||||||
(mkdir-p (dirname repo-dir))
|
(mkdir-p (dirname repo-dir))
|
||||||
(unless (file-exists? repo-dir)
|
(unless (file-exists? repo-dir)
|
||||||
@@ -84,7 +84,7 @@
|
|||||||
(expected-sha256 (or (normalize-expected-sha256 source)
|
(expected-sha256 (or (normalize-expected-sha256 source)
|
||||||
(error "src-txz freebsd source requires sha256 for materialization" source)))
|
(error "src-txz freebsd source requires sha256 for materialization" source)))
|
||||||
(archive-path (string-append cache-dir "/archives/"
|
(archive-path (string-append cache-dir "/archives/"
|
||||||
(string-hash (string-append "txz:" url))
|
(sha256-string (string-append "txz:" url))
|
||||||
"-src.txz")))
|
"-src.txz")))
|
||||||
(mkdir-p (dirname archive-path))
|
(mkdir-p (dirname archive-path))
|
||||||
(when (file-exists? archive-path)
|
(when (file-exists? archive-path)
|
||||||
@@ -150,9 +150,10 @@
|
|||||||
(effective-source (assoc-ref resolution 'effective-source))
|
(effective-source (assoc-ref resolution 'effective-source))
|
||||||
(identity (assoc-ref resolution 'identity))
|
(identity (assoc-ref resolution 'identity))
|
||||||
(manifest (freebsd-source-manifest source effective-source identity))
|
(manifest (freebsd-source-manifest source effective-source identity))
|
||||||
(hash (string-hash manifest))
|
(display-name (string-append "freebsd-source-"
|
||||||
(output-path (string-append store-dir "/" hash "-freebsd-source-"
|
(safe-name-fragment (freebsd-source-name source))))
|
||||||
(safe-name-fragment (freebsd-source-name source))))
|
(output-path (make-store-path store-dir display-name manifest
|
||||||
|
#:kind 'freebsd-source))
|
||||||
(info-file (string-append output-path "/.freebsd-source-info.scm"))
|
(info-file (string-append output-path "/.freebsd-source-info.scm"))
|
||||||
(cache-path (assoc-ref resolution 'cache-path))
|
(cache-path (assoc-ref resolution 'cache-path))
|
||||||
(populate-tree (assoc-ref resolution 'populate-tree)))
|
(populate-tree (assoc-ref resolution 'populate-tree)))
|
||||||
|
|||||||
@@ -13,10 +13,13 @@
|
|||||||
command-output
|
command-output
|
||||||
safe-command-output
|
safe-command-output
|
||||||
write-file
|
write-file
|
||||||
string-hash
|
sha256-string
|
||||||
|
store-hash-string
|
||||||
|
make-store-path
|
||||||
file-hash
|
file-hash
|
||||||
directory-entries
|
directory-entries
|
||||||
path-signature
|
path-signature
|
||||||
|
tree-content-signature
|
||||||
install-plan-signature
|
install-plan-signature
|
||||||
native-build-source-tree-sha256
|
native-build-source-tree-sha256
|
||||||
copy-regular-file
|
copy-regular-file
|
||||||
@@ -63,11 +66,45 @@
|
|||||||
(lambda (port)
|
(lambda (port)
|
||||||
(display content port))))
|
(display content port))))
|
||||||
|
|
||||||
(define (string-hash text)
|
(define (sha256-string text)
|
||||||
(let* ((tmp (string-append (getenv* "TMPDIR" "/tmp") "/fruix-system-hash.txt")))
|
(let* ((tmp (string-append (getenv* "TMPDIR" "/tmp") "/fruix-system-hash.txt")))
|
||||||
(write-file tmp text)
|
(write-file tmp text)
|
||||||
(command-output "sha256" "-q" tmp)))
|
(command-output "sha256" "-q" tmp)))
|
||||||
|
|
||||||
|
(define store-hash-visible-length 40)
|
||||||
|
(define store-hash-scheme-version "1")
|
||||||
|
|
||||||
|
(define (store-identity-field value)
|
||||||
|
(cond ((symbol? value)
|
||||||
|
(symbol->string value))
|
||||||
|
((string? value)
|
||||||
|
value)
|
||||||
|
(else
|
||||||
|
(object->string value))))
|
||||||
|
|
||||||
|
(define* (store-hash-string payload #:key (kind 'item) name (output "out"))
|
||||||
|
(let* ((identity `((scheme . "fruix-store-path")
|
||||||
|
(version . ,store-hash-scheme-version)
|
||||||
|
(kind . ,(store-identity-field kind))
|
||||||
|
(name . ,(store-identity-field (or name "")))
|
||||||
|
(output . ,(store-identity-field output))
|
||||||
|
(payload . ,payload)))
|
||||||
|
(digest (sha256-string (object->string identity))))
|
||||||
|
(string-take digest store-hash-visible-length)))
|
||||||
|
|
||||||
|
(define* (make-store-path store-dir display-name payload
|
||||||
|
#:key
|
||||||
|
(kind 'item)
|
||||||
|
name
|
||||||
|
(output "out"))
|
||||||
|
(string-append store-dir "/"
|
||||||
|
(store-hash-string payload
|
||||||
|
#:kind kind
|
||||||
|
#:name (or name display-name)
|
||||||
|
#:output output)
|
||||||
|
"-"
|
||||||
|
display-name))
|
||||||
|
|
||||||
(define (file-hash path)
|
(define (file-hash path)
|
||||||
(command-output "sha256" "-q" path))
|
(command-output "sha256" "-q" path))
|
||||||
|
|
||||||
@@ -96,6 +133,30 @@
|
|||||||
(else
|
(else
|
||||||
(string-append "other:" path ":" (symbol->string (stat:type st)))))))
|
(string-append "other:" path ":" (symbol->string (stat:type st)))))))
|
||||||
|
|
||||||
|
(define (tree-content-signature root)
|
||||||
|
(define (walk path relative)
|
||||||
|
(let ((st (lstat path)))
|
||||||
|
(case (stat:type st)
|
||||||
|
((regular)
|
||||||
|
(string-append "file:" relative ":" (file-hash path)))
|
||||||
|
((symlink)
|
||||||
|
(string-append "symlink:" relative ":" (readlink path)))
|
||||||
|
((directory)
|
||||||
|
(string-join
|
||||||
|
(cons (string-append "directory:" relative)
|
||||||
|
(apply append
|
||||||
|
(map (lambda (entry)
|
||||||
|
(let ((child-relative (if (string=? relative ".")
|
||||||
|
entry
|
||||||
|
(string-append relative "/" entry))))
|
||||||
|
(list (walk (string-append path "/" entry)
|
||||||
|
child-relative))))
|
||||||
|
(directory-entries path))))
|
||||||
|
"\n"))
|
||||||
|
(else
|
||||||
|
(string-append "other:" relative ":" (symbol->string (stat:type st)))))))
|
||||||
|
(walk root "."))
|
||||||
|
|
||||||
(define (install-plan-signature entry)
|
(define (install-plan-signature entry)
|
||||||
(match entry
|
(match entry
|
||||||
(('file source target)
|
(('file source target)
|
||||||
@@ -110,7 +171,7 @@
|
|||||||
(stable-lines (filter (lambda (line)
|
(stable-lines (filter (lambda (line)
|
||||||
(not (string-prefix? "#" line)))
|
(not (string-prefix? "#" line)))
|
||||||
(string-split mtree-output #\newline))))
|
(string-split mtree-output #\newline))))
|
||||||
(string-hash (string-join stable-lines "\n"))))
|
(sha256-string (string-join stable-lines "\n"))))
|
||||||
|
|
||||||
(define (copy-regular-file source destination)
|
(define (copy-regular-file source destination)
|
||||||
(let ((mode (stat:perms (stat source))))
|
(let ((mode (stat:perms (stat source))))
|
||||||
|
|||||||
@@ -6,7 +6,8 @@
|
|||||||
(ice-9 format)
|
(ice-9 format)
|
||||||
(ice-9 match)
|
(ice-9 match)
|
||||||
(srfi srfi-1)
|
(srfi srfi-1)
|
||||||
(srfi srfi-13))
|
(srfi srfi-13)
|
||||||
|
(rnrs io ports))
|
||||||
|
|
||||||
(define (usage code)
|
(define (usage code)
|
||||||
(format (if (= code 0) #t (current-error-port))
|
(format (if (= code 0) #t (current-error-port))
|
||||||
@@ -15,11 +16,13 @@
|
|||||||
Commands:\n\
|
Commands:\n\
|
||||||
system ACTION ... Build or materialize Fruix system artifacts.\n\
|
system ACTION ... Build or materialize Fruix system artifacts.\n\
|
||||||
source ACTION ... Fetch or snapshot declarative FreeBSD source inputs.\n\
|
source ACTION ... Fetch or snapshot declarative FreeBSD source inputs.\n\
|
||||||
|
native-build ACTION ... Promote native build results into Fruix store objects.\n\
|
||||||
\n\
|
\n\
|
||||||
System actions:\n\
|
System actions:\n\
|
||||||
build Materialize the Fruix system closure in /frx/store.\n\
|
build Materialize the Fruix system closure in /frx/store.\n\
|
||||||
image Materialize the Fruix disk image in /frx/store.\n\
|
image Materialize the Fruix disk image in /frx/store.\n\
|
||||||
installer Materialize a bootable Fruix installer image in /frx/store.\n\
|
installer Materialize a bootable Fruix installer image in /frx/store.\n\
|
||||||
|
installer-iso Materialize a bootable Fruix installer ISO in /frx/store.\n\
|
||||||
install Install the Fruix system onto --target PATH.\n\
|
install Install the Fruix system onto --target PATH.\n\
|
||||||
rootfs Materialize a rootfs tree at --rootfs DIR or ROOTFS-DIR.\n\
|
rootfs Materialize a rootfs tree at --rootfs DIR or ROOTFS-DIR.\n\
|
||||||
\n\
|
\n\
|
||||||
@@ -27,7 +30,7 @@ System options:\n\
|
|||||||
--system NAME Scheme variable holding the operating-system object.\n\
|
--system NAME Scheme variable holding the operating-system object.\n\
|
||||||
--store DIR Store directory to use (default: /frx/store).\n\
|
--store DIR Store directory to use (default: /frx/store).\n\
|
||||||
--disk-capacity SIZE Disk capacity for 'image', 'installer', or raw-file 'install' targets.\n\
|
--disk-capacity SIZE Disk capacity for 'image', 'installer', or raw-file 'install' targets.\n\
|
||||||
--root-size SIZE Root filesystem size for 'image', 'installer', or 'install' (example: 6g).\n\
|
--root-size SIZE Root filesystem size for 'image', 'installer', 'installer-iso', or 'install' (example: 6g).\n\
|
||||||
--target PATH Install target for 'install' (raw image file or /dev/... device).\n\
|
--target PATH Install target for 'install' (raw image file or /dev/... device).\n\
|
||||||
--install-target-device DEVICE\n\
|
--install-target-device DEVICE\n\
|
||||||
Target block device used by the booted 'installer' environment.\n\
|
Target block device used by the booted 'installer' environment.\n\
|
||||||
@@ -36,6 +39,12 @@ System options:\n\
|
|||||||
Source actions:\n\
|
Source actions:\n\
|
||||||
materialize Materialize a declared FreeBSD source tree in /frx/store.\n\
|
materialize Materialize a declared FreeBSD source tree in /frx/store.\n\
|
||||||
\n\
|
\n\
|
||||||
|
Native-build actions:\n\
|
||||||
|
promote Promote a native build result root into /frx/store.\n\
|
||||||
|
\n\
|
||||||
|
Native-build options:\n\
|
||||||
|
--store DIR Store directory to use (default: /frx/store).\n\
|
||||||
|
\n\
|
||||||
Source options:\n\
|
Source options:\n\
|
||||||
--source NAME Scheme variable holding the freebsd-source object.\n\
|
--source NAME Scheme variable holding the freebsd-source object.\n\
|
||||||
--store DIR Store directory to use (default: /frx/store).\n\
|
--store DIR Store directory to use (default: /frx/store).\n\
|
||||||
@@ -61,6 +70,9 @@ Common options:\n\
|
|||||||
(format #t "~a=~a~%" (car field) (stringify (cdr field))))
|
(format #t "~a=~a~%" (car field) (stringify (cdr field))))
|
||||||
fields))
|
fields))
|
||||||
|
|
||||||
|
(define (read-file-string file)
|
||||||
|
(call-with-input-file file get-string-all))
|
||||||
|
|
||||||
(define (lookup-bound-value module symbol)
|
(define (lookup-bound-value module symbol)
|
||||||
(let ((var (module-variable module symbol)))
|
(let ((var (module-variable module symbol)))
|
||||||
(and var (variable-ref var))))
|
(and var (variable-ref var))))
|
||||||
@@ -215,6 +227,28 @@ Common options:\n\
|
|||||||
((arg . tail)
|
((arg . tail)
|
||||||
(loop tail (cons arg positional) source-name store-dir cache-dir)))))
|
(loop tail (cons arg positional) source-name store-dir cache-dir)))))
|
||||||
|
|
||||||
|
(define (parse-native-build-arguments action rest)
|
||||||
|
(let loop ((args rest)
|
||||||
|
(positional '())
|
||||||
|
(store-dir "/frx/store"))
|
||||||
|
(match args
|
||||||
|
(()
|
||||||
|
(let ((positional (reverse positional)))
|
||||||
|
`((command . "native-build")
|
||||||
|
(action . ,action)
|
||||||
|
(positional . ,positional)
|
||||||
|
(store-dir . ,store-dir))))
|
||||||
|
(("--help")
|
||||||
|
(usage 0))
|
||||||
|
(((? (lambda (arg) (string-prefix? "--store=" arg)) arg) . tail)
|
||||||
|
(loop tail positional (option-value arg "--store=")))
|
||||||
|
(("--store" value . tail)
|
||||||
|
(loop tail positional value))
|
||||||
|
(((? (lambda (arg) (string-prefix? "--" arg)) arg) . _)
|
||||||
|
(error "unknown option" arg))
|
||||||
|
((arg . tail)
|
||||||
|
(loop tail (cons arg positional) store-dir)))))
|
||||||
|
|
||||||
(define (parse-arguments argv)
|
(define (parse-arguments argv)
|
||||||
(match argv
|
(match argv
|
||||||
((_)
|
((_)
|
||||||
@@ -227,10 +261,14 @@ Common options:\n\
|
|||||||
(usage 0))
|
(usage 0))
|
||||||
((_ "source" "--help")
|
((_ "source" "--help")
|
||||||
(usage 0))
|
(usage 0))
|
||||||
|
((_ "native-build" "--help")
|
||||||
|
(usage 0))
|
||||||
((_ "system" action . rest)
|
((_ "system" action . rest)
|
||||||
(parse-system-arguments action rest))
|
(parse-system-arguments action rest))
|
||||||
((_ "source" action . rest)
|
((_ "source" action . rest)
|
||||||
(parse-source-arguments action rest))
|
(parse-source-arguments action rest))
|
||||||
|
((_ "native-build" action . rest)
|
||||||
|
(parse-native-build-arguments action rest))
|
||||||
((_ . _)
|
((_ . _)
|
||||||
(usage 1))))
|
(usage 1))))
|
||||||
|
|
||||||
@@ -476,6 +514,88 @@ Common options:\n\
|
|||||||
(target_store_item_count . ,(length target-store-items))
|
(target_store_item_count . ,(length target-store-items))
|
||||||
(installer_store_item_count . ,(length installer-store-items))))))
|
(installer_store_item_count . ,(length installer-store-items))))))
|
||||||
|
|
||||||
|
(define (emit-system-installer-iso-metadata os-file resolved-symbol store-dir os result)
|
||||||
|
(let* ((installer-iso-spec (assoc-ref result 'installer-iso-spec))
|
||||||
|
(store-items (assoc-ref result 'store-items))
|
||||||
|
(target-store-items (assoc-ref result 'target-store-items))
|
||||||
|
(installer-store-items (assoc-ref result 'installer-store-items))
|
||||||
|
(host-base-stores (assoc-ref result 'host-base-stores))
|
||||||
|
(native-base-stores (assoc-ref result 'native-base-stores))
|
||||||
|
(fruix-runtime-stores (assoc-ref result 'fruix-runtime-stores))
|
||||||
|
(base (operating-system-freebsd-base os))
|
||||||
|
(source (freebsd-base-source base))
|
||||||
|
(host-provenance (call-with-input-file (assoc-ref result 'host-base-provenance-file) read)))
|
||||||
|
(emit-metadata
|
||||||
|
`((action . "installer-iso")
|
||||||
|
(os_file . ,os-file)
|
||||||
|
(system_variable . ,resolved-symbol)
|
||||||
|
(store_dir . ,store-dir)
|
||||||
|
(freebsd_base_name . ,(freebsd-base-name base))
|
||||||
|
(freebsd_base_version_label . ,(freebsd-base-version-label base))
|
||||||
|
(freebsd_base_release . ,(freebsd-base-release base))
|
||||||
|
(freebsd_base_branch . ,(freebsd-base-branch base))
|
||||||
|
(freebsd_base_source_root . ,(freebsd-base-source-root base))
|
||||||
|
(freebsd_base_target . ,(freebsd-base-target base))
|
||||||
|
(freebsd_base_target_arch . ,(freebsd-base-target-arch base))
|
||||||
|
(freebsd_base_kernconf . ,(freebsd-base-kernconf base))
|
||||||
|
(freebsd_base_file . ,(assoc-ref result 'freebsd-base-file))
|
||||||
|
(freebsd_source_name . ,(freebsd-source-name source))
|
||||||
|
(freebsd_source_kind . ,(freebsd-source-kind source))
|
||||||
|
(freebsd_source_url . ,(or (freebsd-source-url source) ""))
|
||||||
|
(freebsd_source_path . ,(or (freebsd-source-path source) ""))
|
||||||
|
(freebsd_source_ref . ,(or (freebsd-source-ref source) ""))
|
||||||
|
(freebsd_source_commit . ,(or (freebsd-source-commit source) ""))
|
||||||
|
(freebsd_source_sha256 . ,(or (freebsd-source-sha256 source) ""))
|
||||||
|
(freebsd_source_file . ,(assoc-ref result 'freebsd-source-file))
|
||||||
|
(freebsd_source_materializations_file . ,(assoc-ref result 'freebsd-source-materializations-file))
|
||||||
|
(materialized_source_store_count . ,(length (assoc-ref result 'materialized-source-stores)))
|
||||||
|
(materialized_source_stores . ,(string-join (assoc-ref result 'materialized-source-stores) ","))
|
||||||
|
(installer_host_name . ,(assoc-ref installer-iso-spec 'installer-host-name))
|
||||||
|
(install_target_device . ,(assoc-ref result 'install-target-device))
|
||||||
|
(iso_volume_label . ,(assoc-ref installer-iso-spec 'iso-volume-label))
|
||||||
|
(root_size . ,(assoc-ref installer-iso-spec 'root-size))
|
||||||
|
(installer_state_path . ,(assoc-ref result 'installer-state-path))
|
||||||
|
(installer_log_path . ,(assoc-ref result 'installer-log-path))
|
||||||
|
(iso_store_path . ,(assoc-ref result 'iso-store-path))
|
||||||
|
(iso_image . ,(assoc-ref result 'iso-image))
|
||||||
|
(boot_efi_image . ,(assoc-ref result 'boot-efi-image))
|
||||||
|
(root_image . ,(assoc-ref result 'root-image))
|
||||||
|
(installer_closure_path . ,(assoc-ref result 'installer-closure-path))
|
||||||
|
(target_closure_path . ,(assoc-ref result 'target-closure-path))
|
||||||
|
(host_base_store_count . ,(length host-base-stores))
|
||||||
|
(host_base_stores . ,(string-join host-base-stores ","))
|
||||||
|
(native_base_store_count . ,(length native-base-stores))
|
||||||
|
(native_base_stores . ,(string-join native-base-stores ","))
|
||||||
|
(fruix_runtime_store_count . ,(length fruix-runtime-stores))
|
||||||
|
(fruix_runtime_stores . ,(string-join fruix-runtime-stores ","))
|
||||||
|
(host_base_provenance_file . ,(assoc-ref result 'host-base-provenance-file))
|
||||||
|
(store_layout_file . ,(assoc-ref result 'store-layout-file))
|
||||||
|
(host_freebsd_version . ,(assoc-ref host-provenance 'freebsd-version-kru))
|
||||||
|
(host_uname . ,(assoc-ref host-provenance 'uname))
|
||||||
|
(usr_src_git_revision . ,(assoc-ref host-provenance 'usr-src-git-revision))
|
||||||
|
(usr_src_git_branch . ,(assoc-ref host-provenance 'usr-src-git-branch))
|
||||||
|
(usr_src_newvers_sha256 . ,(assoc-ref host-provenance 'usr-src-newvers-sha256))
|
||||||
|
(store_item_count . ,(length store-items))
|
||||||
|
(target_store_item_count . ,(length target-store-items))
|
||||||
|
(installer_store_item_count . ,(length installer-store-items))))))
|
||||||
|
|
||||||
|
(define (emit-native-build-promotion-metadata store-dir result-root result)
|
||||||
|
(emit-metadata
|
||||||
|
`((action . "promote")
|
||||||
|
(result_root . ,result-root)
|
||||||
|
(store_dir . ,store-dir)
|
||||||
|
(executor_kind . ,(assoc-ref result 'executor-kind))
|
||||||
|
(executor_name . ,(assoc-ref result 'executor-name))
|
||||||
|
(executor_version . ,(assoc-ref result 'executor-version))
|
||||||
|
(result_store . ,(assoc-ref result 'result-store))
|
||||||
|
(result_metadata_file . ,(assoc-ref result 'result-metadata-file))
|
||||||
|
(artifact_store_count . ,(assoc-ref result 'artifact-store-count))
|
||||||
|
(artifact_stores . ,(string-join (assoc-ref result 'artifact-stores) ","))
|
||||||
|
(world_store . ,(assoc-ref result 'world-store))
|
||||||
|
(kernel_store . ,(assoc-ref result 'kernel-store))
|
||||||
|
(headers_store . ,(assoc-ref result 'headers-store))
|
||||||
|
(bootloader_store . ,(assoc-ref result 'bootloader-store)))))
|
||||||
|
|
||||||
(define (main argv)
|
(define (main argv)
|
||||||
(let* ((parsed (parse-arguments argv))
|
(let* ((parsed (parse-arguments argv))
|
||||||
(command (assoc-ref parsed 'command))
|
(command (assoc-ref parsed 'command))
|
||||||
@@ -491,7 +611,7 @@ Common options:\n\
|
|||||||
(rootfs-opt (assoc-ref parsed 'rootfs))
|
(rootfs-opt (assoc-ref parsed 'rootfs))
|
||||||
(system-name (assoc-ref parsed 'system-name))
|
(system-name (assoc-ref parsed 'system-name))
|
||||||
(requested-symbol (and system-name (string->symbol system-name))))
|
(requested-symbol (and system-name (string->symbol system-name))))
|
||||||
(unless (member action '("build" "image" "installer" "install" "rootfs"))
|
(unless (member action '("build" "image" "installer" "installer-iso" "install" "rootfs"))
|
||||||
(error "unknown system action" action))
|
(error "unknown system action" action))
|
||||||
(let* ((os-file (match positional
|
(let* ((os-file (match positional
|
||||||
((file . _) file)
|
((file . _) file)
|
||||||
@@ -513,7 +633,11 @@ Common options:\n\
|
|||||||
(lambda (os resolved-symbol)
|
(lambda (os resolved-symbol)
|
||||||
(let* ((guile-prefix (or (getenv "GUILE_PREFIX") "/tmp/guile-freebsd-validate-install"))
|
(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"))
|
(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")))
|
(shepherd-prefix (or (getenv "SHEPHERD_PREFIX") "/tmp/shepherd-freebsd-validate-install"))
|
||||||
|
(guile-store-path (getenv "FRUIX_GUILE_STORE"))
|
||||||
|
(guile-extra-store-path (getenv "FRUIX_GUILE_EXTRA_STORE"))
|
||||||
|
(shepherd-store-path (getenv "FRUIX_SHEPHERD_STORE"))
|
||||||
|
(declaration-source (read-file-string os-file)))
|
||||||
(cond
|
(cond
|
||||||
((string=? action "build")
|
((string=? action "build")
|
||||||
(emit-system-build-metadata
|
(emit-system-build-metadata
|
||||||
@@ -522,7 +646,13 @@ Common options:\n\
|
|||||||
#:store-dir store-dir
|
#:store-dir store-dir
|
||||||
#:guile-prefix guile-prefix
|
#:guile-prefix guile-prefix
|
||||||
#:guile-extra-prefix guile-extra-prefix
|
#:guile-extra-prefix guile-extra-prefix
|
||||||
#:shepherd-prefix shepherd-prefix)))
|
#:shepherd-prefix shepherd-prefix
|
||||||
|
#:guile-store-path guile-store-path
|
||||||
|
#:guile-extra-store-path guile-extra-store-path
|
||||||
|
#:shepherd-store-path shepherd-store-path
|
||||||
|
#:declaration-source declaration-source
|
||||||
|
#:declaration-origin os-file
|
||||||
|
#:declaration-system-symbol resolved-symbol)))
|
||||||
((string=? action "rootfs")
|
((string=? action "rootfs")
|
||||||
(unless rootfs
|
(unless rootfs
|
||||||
(error "rootfs action requires ROOTFS-DIR or --rootfs DIR"))
|
(error "rootfs action requires ROOTFS-DIR or --rootfs DIR"))
|
||||||
@@ -530,7 +660,10 @@ Common options:\n\
|
|||||||
#:store-dir store-dir
|
#:store-dir store-dir
|
||||||
#:guile-prefix guile-prefix
|
#:guile-prefix guile-prefix
|
||||||
#:guile-extra-prefix guile-extra-prefix
|
#:guile-extra-prefix guile-extra-prefix
|
||||||
#:shepherd-prefix shepherd-prefix)))
|
#:shepherd-prefix shepherd-prefix
|
||||||
|
#:declaration-source declaration-source
|
||||||
|
#:declaration-origin os-file
|
||||||
|
#:declaration-system-symbol resolved-symbol)))
|
||||||
(emit-metadata
|
(emit-metadata
|
||||||
`((action . "rootfs")
|
`((action . "rootfs")
|
||||||
(os_file . ,os-file)
|
(os_file . ,os-file)
|
||||||
@@ -548,6 +681,9 @@ Common options:\n\
|
|||||||
#:guile-prefix guile-prefix
|
#:guile-prefix guile-prefix
|
||||||
#:guile-extra-prefix guile-extra-prefix
|
#:guile-extra-prefix guile-extra-prefix
|
||||||
#:shepherd-prefix shepherd-prefix
|
#:shepherd-prefix shepherd-prefix
|
||||||
|
#:declaration-source declaration-source
|
||||||
|
#:declaration-origin os-file
|
||||||
|
#:declaration-system-symbol resolved-symbol
|
||||||
#:root-size (or root-size "256m")
|
#:root-size (or root-size "256m")
|
||||||
#:disk-capacity disk-capacity)))
|
#:disk-capacity disk-capacity)))
|
||||||
((string=? action "installer")
|
((string=? action "installer")
|
||||||
@@ -558,9 +694,25 @@ Common options:\n\
|
|||||||
#:guile-prefix guile-prefix
|
#:guile-prefix guile-prefix
|
||||||
#:guile-extra-prefix guile-extra-prefix
|
#:guile-extra-prefix guile-extra-prefix
|
||||||
#:shepherd-prefix shepherd-prefix
|
#:shepherd-prefix shepherd-prefix
|
||||||
|
#:declaration-source declaration-source
|
||||||
|
#:declaration-origin os-file
|
||||||
|
#:declaration-system-symbol resolved-symbol
|
||||||
#:install-target-device (or install-target-device "/dev/vtbd1")
|
#:install-target-device (or install-target-device "/dev/vtbd1")
|
||||||
#:root-size (or root-size "10g")
|
#:root-size (or root-size "10g")
|
||||||
#:disk-capacity disk-capacity)))
|
#:disk-capacity disk-capacity)))
|
||||||
|
((string=? action "installer-iso")
|
||||||
|
(emit-system-installer-iso-metadata
|
||||||
|
os-file resolved-symbol store-dir os
|
||||||
|
(materialize-installer-iso os
|
||||||
|
#:store-dir store-dir
|
||||||
|
#:guile-prefix guile-prefix
|
||||||
|
#:guile-extra-prefix guile-extra-prefix
|
||||||
|
#:shepherd-prefix shepherd-prefix
|
||||||
|
#:declaration-source declaration-source
|
||||||
|
#:declaration-origin os-file
|
||||||
|
#:declaration-system-symbol resolved-symbol
|
||||||
|
#:install-target-device (or install-target-device "/dev/vtbd0")
|
||||||
|
#:root-size root-size)))
|
||||||
((string=? action "install")
|
((string=? action "install")
|
||||||
(unless target
|
(unless target
|
||||||
(error "install action requires TARGET or --target PATH"))
|
(error "install action requires TARGET or --target PATH"))
|
||||||
@@ -572,6 +724,9 @@ Common options:\n\
|
|||||||
#:guile-prefix guile-prefix
|
#:guile-prefix guile-prefix
|
||||||
#:guile-extra-prefix guile-extra-prefix
|
#:guile-extra-prefix guile-extra-prefix
|
||||||
#:shepherd-prefix shepherd-prefix
|
#:shepherd-prefix shepherd-prefix
|
||||||
|
#:declaration-source declaration-source
|
||||||
|
#:declaration-origin os-file
|
||||||
|
#:declaration-system-symbol resolved-symbol
|
||||||
#:root-size root-size
|
#:root-size root-size
|
||||||
#:disk-capacity disk-capacity))))))))))
|
#:disk-capacity disk-capacity))))))))))
|
||||||
((string=? command "source")
|
((string=? command "source")
|
||||||
@@ -616,6 +771,16 @@ Common options:\n\
|
|||||||
(materialized_source_ref . ,(or (assoc-ref effective 'ref) ""))
|
(materialized_source_ref . ,(or (assoc-ref effective 'ref) ""))
|
||||||
(materialized_source_commit . ,(or (assoc-ref result 'effective-commit) ""))
|
(materialized_source_commit . ,(or (assoc-ref result 'effective-commit) ""))
|
||||||
(materialized_source_sha256 . ,(or (assoc-ref result 'effective-sha256) ""))))))))))
|
(materialized_source_sha256 . ,(or (assoc-ref result 'effective-sha256) ""))))))))))
|
||||||
|
((string=? command "native-build")
|
||||||
|
(let ((positional (assoc-ref parsed 'positional)))
|
||||||
|
(unless (string=? action "promote")
|
||||||
|
(error "unknown native-build action" action))
|
||||||
|
(let ((result-root (match positional
|
||||||
|
((path . _) path)
|
||||||
|
(() (error "missing native build result root argument")))))
|
||||||
|
(emit-native-build-promotion-metadata
|
||||||
|
store-dir result-root
|
||||||
|
(promote-native-build-result result-root #:store-dir store-dir)))))
|
||||||
(else
|
(else
|
||||||
(usage 1)))))
|
(usage 1)))))
|
||||||
|
|
||||||
|
|||||||
@@ -0,0 +1,91 @@
|
|||||||
|
(use-modules (fruix system freebsd)
|
||||||
|
(fruix packages freebsd))
|
||||||
|
|
||||||
|
(define phase19-source
|
||||||
|
(freebsd-source
|
||||||
|
#:name "__SOURCE_NAME__"
|
||||||
|
#:kind 'git
|
||||||
|
#:ref "__SOURCE_REF__"
|
||||||
|
#:commit "__SOURCE_COMMIT__"))
|
||||||
|
|
||||||
|
(define phase19-base
|
||||||
|
(freebsd-base
|
||||||
|
#:name "__BASE_NAME__"
|
||||||
|
#:version-label "__BASE_VERSION_LABEL__"
|
||||||
|
#:release "__BASE_RELEASE__"
|
||||||
|
#:branch "__BASE_BRANCH__"
|
||||||
|
#:source phase19-source
|
||||||
|
#:source-root "__DECLARED_SOURCE_ROOT__"
|
||||||
|
#:target "amd64"
|
||||||
|
#:target-arch "amd64"
|
||||||
|
#:kernconf "GENERIC"))
|
||||||
|
|
||||||
|
(define phase19-operating-system
|
||||||
|
(operating-system
|
||||||
|
#:host-name "__HOST_NAME__"
|
||||||
|
#:freebsd-base phase19-base
|
||||||
|
#:kernel (freebsd-native-kernel-for phase19-base)
|
||||||
|
#:bootloader (freebsd-native-bootloader-for phase19-base)
|
||||||
|
#:base-packages (freebsd-native-system-packages-for phase19-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__")))
|
||||||
75
tests/system/phase20-development-operating-system.scm.in
Normal file
75
tests/system/phase20-development-operating-system.scm.in
Normal file
@@ -0,0 +1,75 @@
|
|||||||
|
(use-modules (fruix system freebsd)
|
||||||
|
(fruix packages freebsd))
|
||||||
|
|
||||||
|
(define phase20-operating-system
|
||||||
|
(operating-system
|
||||||
|
#:host-name "fruix-freebsd"
|
||||||
|
#:kernel freebsd-native-kernel
|
||||||
|
#:bootloader freebsd-native-bootloader
|
||||||
|
#:base-packages %freebsd-native-system-packages
|
||||||
|
#:development-packages (list freebsd-native-headers
|
||||||
|
freebsd-clang-toolchain)
|
||||||
|
#:build-packages (list freebsd-native-headers
|
||||||
|
freebsd-clang-toolchain)
|
||||||
|
#: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__")))
|
||||||
@@ -0,0 +1,73 @@
|
|||||||
|
(use-modules (fruix system freebsd)
|
||||||
|
(fruix packages freebsd))
|
||||||
|
|
||||||
|
(define phase20-promoted-native-build-result
|
||||||
|
(promoted-native-build-result
|
||||||
|
#:store-path "__PROMOTED_RESULT_STORE__"))
|
||||||
|
|
||||||
|
(define phase20-promoted-native-base-operating-system
|
||||||
|
(operating-system-from-promoted-native-build-result
|
||||||
|
phase20-promoted-native-build-result
|
||||||
|
#:host-name "fruix-freebsd"
|
||||||
|
#: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__")))
|
||||||
@@ -0,0 +1,73 @@
|
|||||||
|
(use-modules (fruix system freebsd)
|
||||||
|
(fruix packages freebsd))
|
||||||
|
|
||||||
|
(define postphase20-promoted-native-build-result
|
||||||
|
(promoted-native-build-result
|
||||||
|
#:store-path "__PROMOTED_RESULT_STORE__"))
|
||||||
|
|
||||||
|
(define postphase20-installed-node-operating-system
|
||||||
|
(operating-system-from-promoted-native-build-result
|
||||||
|
postphase20-promoted-native-build-result
|
||||||
|
#:host-name "__HOST_NAME__"
|
||||||
|
#: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__")))
|
||||||
455
tests/system/run-phase18-installer-iso-xcpng.sh
Executable file
455
tests/system/run-phase18-installer-iso-xcpng.sh
Executable file
@@ -0,0 +1,455 @@
|
|||||||
|
#!/bin/sh
|
||||||
|
set -eu
|
||||||
|
|
||||||
|
repo_root=${PROJECT_ROOT:-$(pwd)}
|
||||||
|
script_dir=$(CDPATH= cd -- "$(dirname "$0")" && pwd)
|
||||||
|
fruix_cmd=$repo_root/bin/fruix
|
||||||
|
vm_id=${VM_ID:-90490f2e-e8fc-4b7a-388e-5c26f0157289}
|
||||||
|
iso_sr_id=${ISO_SR_ID:-537a6219-8452-7cb5-8d56-5eed6910c7a2}
|
||||||
|
os_template=${OS_TEMPLATE:-$script_dir/phase18-installer-target-operating-system.scm.in}
|
||||||
|
system_name=${SYSTEM_NAME:-phase18-target-operating-system}
|
||||||
|
store_dir=${STORE_DIR:-/frx/store}
|
||||||
|
install_target_device=${INSTALL_TARGET_DEVICE:-/dev/ada0}
|
||||||
|
installer_root_size=${INSTALLER_ROOT_SIZE:-}
|
||||||
|
base_name=${BASE_NAME:-phase18-installer-iso-target}
|
||||||
|
base_version_label=${BASE_VERSION_LABEL:-15.0-STABLE-installer-iso-target}
|
||||||
|
base_release=${BASE_RELEASE:-15.0-STABLE}
|
||||||
|
base_branch=${BASE_BRANCH:-stable/15}
|
||||||
|
source_name=${SOURCE_NAME:-stable15-installer-iso-target-source}
|
||||||
|
source_ref=${SOURCE_REF:-stable/15}
|
||||||
|
source_commit=${SOURCE_COMMIT:-332708a606f6bf0841c1d4a74c0d067f5640fe89}
|
||||||
|
declared_source_root=${DECLARED_SOURCE_ROOT:-/var/empty/fruix-unused-source-root-installer-iso-target}
|
||||||
|
metadata_target=${METADATA_OUT:-}
|
||||||
|
root_authorized_key_file=${ROOT_AUTHORIZED_KEY_FILE:-$HOME/.ssh/id_ed25519.pub}
|
||||||
|
root_ssh_private_key_file=${ROOT_SSH_PRIVATE_KEY_FILE:-$HOME/.ssh/id_ed25519}
|
||||||
|
iso_http_port=${ISO_HTTP_PORT:-$(jot -r 1 18080 18999)}
|
||||||
|
keep_imported_iso=${KEEP_IMPORTED_ISO:-0}
|
||||||
|
|
||||||
|
[ -x "$fruix_cmd" ] || {
|
||||||
|
echo "fruix command is not executable: $fruix_cmd" >&2
|
||||||
|
exit 1
|
||||||
|
}
|
||||||
|
[ -f "$os_template" ] || {
|
||||||
|
echo "missing operating-system template: $os_template" >&2
|
||||||
|
exit 1
|
||||||
|
}
|
||||||
|
[ -f "$root_authorized_key_file" ] || {
|
||||||
|
echo "missing root authorized key file: $root_authorized_key_file" >&2
|
||||||
|
exit 1
|
||||||
|
}
|
||||||
|
[ -f "$root_ssh_private_key_file" ] || {
|
||||||
|
echo "missing root SSH private key file: $root_ssh_private_key_file" >&2
|
||||||
|
exit 1
|
||||||
|
}
|
||||||
|
command -v node >/dev/null 2>&1 || {
|
||||||
|
echo "node is required to serve the ISO for XO import" >&2
|
||||||
|
exit 1
|
||||||
|
}
|
||||||
|
command -v xo-cli >/dev/null 2>&1 || {
|
||||||
|
echo "xo-cli is required" >&2
|
||||||
|
exit 1
|
||||||
|
}
|
||||||
|
command -v jq >/dev/null 2>&1 || {
|
||||||
|
echo "jq is required" >&2
|
||||||
|
exit 1
|
||||||
|
}
|
||||||
|
|
||||||
|
cleanup=0
|
||||||
|
if [ -n "${WORKDIR:-}" ]; then
|
||||||
|
workdir=$WORKDIR
|
||||||
|
mkdir -p "$workdir"
|
||||||
|
else
|
||||||
|
workdir=$(mktemp -d /tmp/fruix-phase18-installer-iso-xcpng.XXXXXX)
|
||||||
|
cleanup=1
|
||||||
|
fi
|
||||||
|
if [ "${KEEP_WORKDIR:-0}" -eq 1 ]; then
|
||||||
|
cleanup=0
|
||||||
|
fi
|
||||||
|
|
||||||
|
target_os_file=$workdir/phase18-installer-iso-target-operating-system.scm
|
||||||
|
installer_out=$workdir/installer-iso.txt
|
||||||
|
metadata_file=$workdir/phase18-installer-iso-xcpng-metadata.txt
|
||||||
|
vm_info_json=$workdir/vm-info.json
|
||||||
|
vdi_info_json=$workdir/vdi-info.json
|
||||||
|
installer_log_file=$workdir/installer.log
|
||||||
|
installer_gpart_file=$workdir/installer-gpart.txt
|
||||||
|
installer_kern_disks_file=$workdir/installer-kern-disks.txt
|
||||||
|
target_install_metadata_file=$workdir/target-install.scm
|
||||||
|
server_script=$workdir/serve-iso.mjs
|
||||||
|
server_log=$workdir/serve-iso.log
|
||||||
|
arp_scan_log=$workdir/arp-scan.log
|
||||||
|
|
||||||
|
server_pid=
|
||||||
|
imported_iso_id=
|
||||||
|
imported_iso_name=
|
||||||
|
guest_ip=
|
||||||
|
vm_mac=
|
||||||
|
|
||||||
|
cleanup_workdir() {
|
||||||
|
if [ -n "$server_pid" ]; then
|
||||||
|
kill "$server_pid" >/dev/null 2>&1 || true
|
||||||
|
fi
|
||||||
|
xo-cli vm.ejectCd id=$vm_id >/dev/null 2>&1 || true
|
||||||
|
if [ -n "$imported_iso_id" ] && [ "$keep_imported_iso" -ne 1 ]; then
|
||||||
|
xo-cli vdi.delete id=$imported_iso_id >/dev/null 2>&1 || true
|
||||||
|
fi
|
||||||
|
if [ "$cleanup" -eq 1 ]; then
|
||||||
|
rm -rf "$workdir"
|
||||||
|
fi
|
||||||
|
}
|
||||||
|
trap cleanup_workdir EXIT INT TERM
|
||||||
|
|
||||||
|
root_authorized_key=$(tr -d '\n' < "$root_authorized_key_file")
|
||||||
|
sed \
|
||||||
|
-e "s|__BASE_NAME__|$base_name|g" \
|
||||||
|
-e "s|__BASE_VERSION_LABEL__|$base_version_label|g" \
|
||||||
|
-e "s|__BASE_RELEASE__|$base_release|g" \
|
||||||
|
-e "s|__BASE_BRANCH__|$base_branch|g" \
|
||||||
|
-e "s|__SOURCE_NAME__|$source_name|g" \
|
||||||
|
-e "s|__SOURCE_REF__|$source_ref|g" \
|
||||||
|
-e "s|__SOURCE_COMMIT__|$source_commit|g" \
|
||||||
|
-e "s|__DECLARED_SOURCE_ROOT__|$declared_source_root|g" \
|
||||||
|
-e "s|__ROOT_AUTHORIZED_KEY__|$root_authorized_key|g" \
|
||||||
|
"$os_template" > "$target_os_file"
|
||||||
|
|
||||||
|
action_env() {
|
||||||
|
sudo env \
|
||||||
|
HOME="$HOME" \
|
||||||
|
GUILE_AUTO_COMPILE=0 \
|
||||||
|
FRUIX_FREEBSD_BUILD_JOBS="${FRUIX_FREEBSD_BUILD_JOBS:-8}" \
|
||||||
|
GUIX_SOURCE_DIR="${GUIX_SOURCE_DIR:-$HOME/repos/guix}" \
|
||||||
|
GUILE_BIN="${GUILE_BIN:-/tmp/guile-freebsd-validate-install/bin/guile}" \
|
||||||
|
GUILE_EXTRA_PREFIX="${GUILE_EXTRA_PREFIX:-/tmp/guile-gnutls-freebsd-validate-install}" \
|
||||||
|
SHEPHERD_PREFIX="${SHEPHERD_PREFIX:-/tmp/shepherd-freebsd-validate-install}" \
|
||||||
|
"$@"
|
||||||
|
}
|
||||||
|
|
||||||
|
if [ -n "$installer_root_size" ]; then
|
||||||
|
action_env "$fruix_cmd" system installer-iso "$target_os_file" \
|
||||||
|
--system "$system_name" \
|
||||||
|
--store "$store_dir" \
|
||||||
|
--install-target-device "$install_target_device" \
|
||||||
|
--root-size "$installer_root_size" >"$installer_out"
|
||||||
|
else
|
||||||
|
action_env "$fruix_cmd" system installer-iso "$target_os_file" \
|
||||||
|
--system "$system_name" \
|
||||||
|
--store "$store_dir" \
|
||||||
|
--install-target-device "$install_target_device" >"$installer_out"
|
||||||
|
fi
|
||||||
|
|
||||||
|
field() {
|
||||||
|
sed -n "s/^$1=//p" "$installer_out" | tail -n 1
|
||||||
|
}
|
||||||
|
|
||||||
|
iso_store_path=$(field iso_store_path)
|
||||||
|
installer_iso_image=$(field iso_image)
|
||||||
|
installer_boot_efi_image=$(field boot_efi_image)
|
||||||
|
installer_root_image=$(field root_image)
|
||||||
|
installer_closure_path=$(field installer_closure_path)
|
||||||
|
target_closure_path=$(field target_closure_path)
|
||||||
|
installer_host_name=$(field installer_host_name)
|
||||||
|
install_target_device_out=$(field install_target_device)
|
||||||
|
installer_state_path=$(field installer_state_path)
|
||||||
|
installer_log_path=$(field installer_log_path)
|
||||||
|
iso_volume_label=$(field iso_volume_label)
|
||||||
|
root_size_out=$(field root_size)
|
||||||
|
freebsd_source_kind_out=$(field freebsd_source_kind)
|
||||||
|
freebsd_source_ref_out=$(field freebsd_source_ref)
|
||||||
|
freebsd_source_commit_out=$(field freebsd_source_commit)
|
||||||
|
freebsd_source_file=$(field freebsd_source_file)
|
||||||
|
freebsd_source_materializations_file=$(field freebsd_source_materializations_file)
|
||||||
|
materialized_source_store_count=$(field materialized_source_store_count)
|
||||||
|
materialized_source_stores=$(field materialized_source_stores)
|
||||||
|
host_base_store_count=$(field host_base_store_count)
|
||||||
|
native_base_store_count=$(field native_base_store_count)
|
||||||
|
native_base_stores=$(field native_base_stores)
|
||||||
|
store_item_count=$(field store_item_count)
|
||||||
|
target_store_item_count=$(field target_store_item_count)
|
||||||
|
installer_store_item_count=$(field installer_store_item_count)
|
||||||
|
store_layout_file=$(field store_layout_file)
|
||||||
|
|
||||||
|
[ -d "$iso_store_path" ] || { echo "missing installer ISO store path: $iso_store_path" >&2; exit 1; }
|
||||||
|
[ -f "$installer_iso_image" ] || { echo "missing installer ISO image: $installer_iso_image" >&2; exit 1; }
|
||||||
|
[ -f "$installer_boot_efi_image" ] || { echo "missing installer EFI boot image: $installer_boot_efi_image" >&2; exit 1; }
|
||||||
|
[ -f "$installer_root_image" ] || { echo "missing installer root image: $installer_root_image" >&2; exit 1; }
|
||||||
|
[ -n "$installer_closure_path" ] || { echo "missing installer closure path" >&2; exit 1; }
|
||||||
|
[ -n "$target_closure_path" ] || { echo "missing target closure path" >&2; exit 1; }
|
||||||
|
[ "$install_target_device_out" = "$install_target_device" ] || { echo "unexpected install target device: $install_target_device_out" >&2; exit 1; }
|
||||||
|
[ "$installer_host_name" = fruix-freebsd-installer ] || { echo "unexpected installer host name: $installer_host_name" >&2; exit 1; }
|
||||||
|
[ -n "$iso_volume_label" ] || { echo "missing ISO volume label" >&2; exit 1; }
|
||||||
|
[ "$freebsd_source_kind_out" = git ] || { echo "unexpected source kind: $freebsd_source_kind_out" >&2; exit 1; }
|
||||||
|
[ "$freebsd_source_ref_out" = "$source_ref" ] || { echo "unexpected source ref: $freebsd_source_ref_out" >&2; exit 1; }
|
||||||
|
[ "$freebsd_source_commit_out" = "$source_commit" ] || { echo "unexpected source commit: $freebsd_source_commit_out" >&2; exit 1; }
|
||||||
|
[ "$materialized_source_store_count" = 1 ] || { echo "unexpected materialized source store count: $materialized_source_store_count" >&2; exit 1; }
|
||||||
|
[ "$host_base_store_count" = 0 ] || { echo "expected zero host base stores, got: $host_base_store_count" >&2; exit 1; }
|
||||||
|
[ "$native_base_store_count" = 3 ] || { echo "expected three native base stores, got: $native_base_store_count" >&2; exit 1; }
|
||||||
|
[ -f "$freebsd_source_file" ] || { echo "missing freebsd source file: $freebsd_source_file" >&2; exit 1; }
|
||||||
|
[ -f "$freebsd_source_materializations_file" ] || { echo "missing source materializations file: $freebsd_source_materializations_file" >&2; exit 1; }
|
||||||
|
[ -f "$store_layout_file" ] || { echo "missing store layout file: $store_layout_file" >&2; exit 1; }
|
||||||
|
case "$materialized_source_stores" in
|
||||||
|
/frx/store/*-freebsd-source-$source_name) : ;;
|
||||||
|
*) echo "unexpected materialized source store path: $materialized_source_stores" >&2; exit 1 ;;
|
||||||
|
esac
|
||||||
|
[ "$store_item_count" -ge "$target_store_item_count" ] || { echo "combined store item count smaller than target store item count" >&2; exit 1; }
|
||||||
|
[ "$installer_store_item_count" -ge 1 ] || { echo "expected installer store items" >&2; exit 1; }
|
||||||
|
|
||||||
|
xo-cli list-objects id=$vm_id >"$vm_info_json"
|
||||||
|
vdi_id=$(xo-cli list-objects type=VBD | jq -r '.[] | select(.VM=="'$vm_id'" and .is_cd_drive==false and .position=="0") | .VDI' | head -n 1)
|
||||||
|
secondary_vdi_id=$(xo-cli list-objects type=VBD | jq -r '.[] | select(.VM=="'$vm_id'" and .is_cd_drive==false and .position=="1") | .VDI' | head -n 1)
|
||||||
|
[ -n "$vdi_id" ] || { echo "failed to discover primary target VDI for VM $vm_id" >&2; exit 1; }
|
||||||
|
xo-cli list-objects type=VDI | jq '[.[] | select(.id=="'$vdi_id'")]' >"$vdi_info_json"
|
||||||
|
vdi_size=$(jq -r '.[0].size' "$vdi_info_json")
|
||||||
|
[ -n "$vdi_size" ] || { echo "failed to discover VDI size for $vdi_id" >&2; exit 1; }
|
||||||
|
|
||||||
|
vif_id=$(jq -r '.[0].VIFs[0]' "$vm_info_json")
|
||||||
|
if [ -n "$vif_id" ] && [ "$vif_id" != null ]; then
|
||||||
|
vm_mac=$(xo-cli list-objects type=VIF | jq -r '.[] | select(.id=="'$vif_id'") | .MAC' | tr 'A-Z' 'a-z')
|
||||||
|
else
|
||||||
|
vm_mac=
|
||||||
|
fi
|
||||||
|
[ -n "$vm_mac" ] || { echo "failed to discover VM MAC address" >&2; exit 1; }
|
||||||
|
|
||||||
|
host_interface=$(route -n get default | awk '/interface:/{print $2; exit}')
|
||||||
|
host_ip=$(ifconfig "$host_interface" | awk '/inet /{print $2; exit}')
|
||||||
|
subnet_prefix=${host_ip%.*}
|
||||||
|
|
||||||
|
cat >"$server_script" <<'EOF'
|
||||||
|
import { createServer } from 'node:http'
|
||||||
|
import { createReadStream, statSync } from 'node:fs'
|
||||||
|
const file = process.argv[2]
|
||||||
|
const port = Number(process.argv[3])
|
||||||
|
const size = statSync(file).size
|
||||||
|
createServer((req, res) => {
|
||||||
|
if (req.url !== '/installer.iso') {
|
||||||
|
res.statusCode = 404
|
||||||
|
res.end('not found\n')
|
||||||
|
return
|
||||||
|
}
|
||||||
|
res.writeHead(200, {
|
||||||
|
'Content-Type': 'application/octet-stream',
|
||||||
|
'Content-Length': size,
|
||||||
|
'Content-Disposition': 'attachment; filename="installer.iso"'
|
||||||
|
})
|
||||||
|
createReadStream(file).pipe(res)
|
||||||
|
}).listen(port, '0.0.0.0', () => {
|
||||||
|
console.log(`listening ${port}`)
|
||||||
|
})
|
||||||
|
EOF
|
||||||
|
|
||||||
|
node "$server_script" "$installer_iso_image" "$iso_http_port" >"$server_log" 2>&1 &
|
||||||
|
server_pid=$!
|
||||||
|
for _ in $(jot 50 1 50); do
|
||||||
|
if grep -q 'listening' "$server_log"; then
|
||||||
|
break
|
||||||
|
fi
|
||||||
|
sleep 1
|
||||||
|
if ! kill -0 "$server_pid" >/dev/null 2>&1; then
|
||||||
|
echo "temporary ISO HTTP server failed to start" >&2
|
||||||
|
cat "$server_log" >&2 || true
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
done
|
||||||
|
iso_import_url="http://$host_ip:$iso_http_port/installer.iso"
|
||||||
|
imported_iso_name="fruix-installer-iso-ada0-$(date +%s).iso"
|
||||||
|
imported_iso_id=$(xo-cli disk.import name="$imported_iso_name" sr="$iso_sr_id" type=iso url="$iso_import_url")
|
||||||
|
kill "$server_pid" >/dev/null 2>&1 || true
|
||||||
|
server_pid=
|
||||||
|
|
||||||
|
refresh_guest_ip() {
|
||||||
|
guest_ip=$(arp -an | awk -v mac="$vm_mac" 'tolower($4)==mac {gsub(/[()]/,"",$2); print $2; exit}')
|
||||||
|
}
|
||||||
|
|
||||||
|
ping_sweep() {
|
||||||
|
: >"$arp_scan_log"
|
||||||
|
for host in $(jot 254 1 254); do
|
||||||
|
ip=$subnet_prefix.$host
|
||||||
|
(
|
||||||
|
ping -c 1 -W 1000 "$ip" >/dev/null 2>&1 && echo "$ip" >>"$arp_scan_log"
|
||||||
|
) &
|
||||||
|
done
|
||||||
|
wait
|
||||||
|
}
|
||||||
|
|
||||||
|
ssh_guest() {
|
||||||
|
ssh -i "$root_ssh_private_key_file" \
|
||||||
|
-o BatchMode=yes \
|
||||||
|
-o StrictHostKeyChecking=no \
|
||||||
|
-o UserKnownHostsFile=/dev/null \
|
||||||
|
-o LogLevel=ERROR \
|
||||||
|
-o ConnectTimeout=5 \
|
||||||
|
root@"$guest_ip" "$@"
|
||||||
|
}
|
||||||
|
|
||||||
|
wait_for_guest_command() {
|
||||||
|
probe=$1
|
||||||
|
attempts=$2
|
||||||
|
delay=$3
|
||||||
|
guest_ip=
|
||||||
|
for attempt in $(jot "$attempts" 1 "$attempts"); do
|
||||||
|
refresh_guest_ip
|
||||||
|
if [ -z "$guest_ip" ]; then
|
||||||
|
ping_sweep
|
||||||
|
refresh_guest_ip
|
||||||
|
fi
|
||||||
|
if [ -n "$guest_ip" ]; then
|
||||||
|
if ssh_guest "$probe" >/dev/null 2>&1; then
|
||||||
|
return 0
|
||||||
|
fi
|
||||||
|
fi
|
||||||
|
sleep "$delay"
|
||||||
|
done
|
||||||
|
return 1
|
||||||
|
}
|
||||||
|
|
||||||
|
xo-cli vm.stop id=$vm_id force=true >/dev/null 2>&1 || true
|
||||||
|
xo-cli vm.insertCd id=$vm_id cd_id=$imported_iso_id force=true >"$workdir/insert-cd.out"
|
||||||
|
xo-cli vm.setBootOrder vm=$vm_id order=dcn >"$workdir/set-boot-order.out"
|
||||||
|
xo-cli vm.start id=$vm_id >"$workdir/vm-start-installer.out"
|
||||||
|
|
||||||
|
wait_for_guest_command 'test -e /var/lib/fruix/installer/state' 90 5 || {
|
||||||
|
echo "installer ISO guest never became reachable over SSH" >&2
|
||||||
|
exit 1
|
||||||
|
}
|
||||||
|
|
||||||
|
installer_state=missing
|
||||||
|
for attempt in $(jot 180 1 180); do
|
||||||
|
if ssh_guest 'test -e /var/lib/fruix/installer/state' >/dev/null 2>&1; then
|
||||||
|
installer_state=$(ssh_guest "cat '$installer_state_path' 2>/dev/null || echo missing")
|
||||||
|
[ "$installer_state" = done ] && break
|
||||||
|
fi
|
||||||
|
sleep 5
|
||||||
|
done
|
||||||
|
|
||||||
|
installer_run_current_system=$(ssh_guest 'readlink /run/current-system')
|
||||||
|
installer_sshd_status=$(ssh_guest 'service sshd onestatus >/dev/null 2>&1 && echo running || echo stopped')
|
||||||
|
installer_log=$(ssh_guest "cat '$installer_log_path' 2>/dev/null || true")
|
||||||
|
installer_target_device=$(ssh_guest 'cat /var/lib/fruix/installer/target-device')
|
||||||
|
installer_kern_disks=$(ssh_guest 'sysctl -n kern.disks')
|
||||||
|
installer_gpart=$(ssh_guest 'gpart show ada0')
|
||||||
|
installer_esp_fstype=$(ssh_guest 'fstyp /dev/ada0p1')
|
||||||
|
installer_root_fstype=$(ssh_guest 'fstyp /dev/ada0p2')
|
||||||
|
printf '%s\n' "$installer_log" >"$installer_log_file"
|
||||||
|
printf '%s\n' "$installer_kern_disks" >"$installer_kern_disks_file"
|
||||||
|
printf '%s\n' "$installer_gpart" >"$installer_gpart_file"
|
||||||
|
|
||||||
|
[ "$installer_state" = done ] || { echo "installer ISO environment did not finish installation: $installer_state" >&2; exit 1; }
|
||||||
|
[ "$installer_run_current_system" = "/frx/store/$(basename "$installer_closure_path")" ] || { echo "unexpected installer current-system target: $installer_run_current_system" >&2; exit 1; }
|
||||||
|
[ "$installer_sshd_status" = running ] || { echo "installer sshd is not running" >&2; exit 1; }
|
||||||
|
[ "$installer_target_device" = "$install_target_device" ] || { echo "unexpected installer target device in guest: $installer_target_device" >&2; exit 1; }
|
||||||
|
[ "$installer_esp_fstype" = msdosfs ] || { echo "unexpected target ESP filesystem: $installer_esp_fstype" >&2; exit 1; }
|
||||||
|
[ "$installer_root_fstype" = ufs ] || { echo "unexpected target root filesystem: $installer_root_fstype" >&2; exit 1; }
|
||||||
|
case "$installer_log" in
|
||||||
|
*fruix-installer:done*) : ;;
|
||||||
|
*) echo "installer log does not show completion" >&2; exit 1 ;;
|
||||||
|
esac
|
||||||
|
case "$installer_kern_disks" in
|
||||||
|
*cd0*ada0*) : ;;
|
||||||
|
*) echo "unexpected installer kern.disks output: $installer_kern_disks" >&2; exit 1 ;;
|
||||||
|
esac
|
||||||
|
case "$installer_gpart" in
|
||||||
|
*ada0*GPT*) : ;;
|
||||||
|
*) echo "unexpected gpart output for ada0" >&2; exit 1 ;;
|
||||||
|
esac
|
||||||
|
|
||||||
|
xo-cli vm.ejectCd id=$vm_id >"$workdir/eject-cd.out"
|
||||||
|
xo-cli vm.restart id=$vm_id force=true >"$workdir/vm-restart-target.out"
|
||||||
|
|
||||||
|
wait_for_guest_command 'test -f /var/lib/fruix/ready' 120 5 || {
|
||||||
|
echo "installed target never became reachable over SSH" >&2
|
||||||
|
exit 1
|
||||||
|
}
|
||||||
|
|
||||||
|
target_run_current_system=$(ssh_guest 'readlink /run/current-system')
|
||||||
|
target_shepherd_status=$(ssh_guest '/usr/local/etc/rc.d/fruix-shepherd onestatus >/dev/null 2>&1 && echo running || echo stopped')
|
||||||
|
target_sshd_status=$(ssh_guest 'service sshd onestatus >/dev/null 2>&1 && echo running || echo stopped')
|
||||||
|
target_activate_log=$(ssh_guest 'cat /var/log/fruix-activate.log 2>/dev/null || true' | tr '\n' ' ')
|
||||||
|
target_install_metadata=$(ssh_guest 'cat /var/lib/fruix/install.scm')
|
||||||
|
printf '%s\n' "$target_install_metadata" >"$target_install_metadata_file"
|
||||||
|
|
||||||
|
[ "$target_run_current_system" = "/frx/store/$(basename "$target_closure_path")" ] || { echo "unexpected booted target current-system: $target_run_current_system" >&2; exit 1; }
|
||||||
|
[ "$target_shepherd_status" = running ] || { echo "fruix-shepherd is not running in booted target" >&2; exit 1; }
|
||||||
|
[ "$target_sshd_status" = running ] || { echo "sshd is not running in booted target" >&2; exit 1; }
|
||||||
|
case "$target_install_metadata" in
|
||||||
|
*"$target_closure_path"*) : ;;
|
||||||
|
*) echo "booted target metadata does not record target closure path" >&2; exit 1 ;;
|
||||||
|
esac
|
||||||
|
case "$target_install_metadata" in
|
||||||
|
*"$materialized_source_stores"*) : ;;
|
||||||
|
*) echo "booted target metadata does not record materialized source store" >&2; exit 1 ;;
|
||||||
|
esac
|
||||||
|
case "$target_install_metadata" in
|
||||||
|
*"$install_target_device"*) : ;;
|
||||||
|
*) echo "booted target metadata does not record install target device" >&2; exit 1 ;;
|
||||||
|
esac
|
||||||
|
case "$target_activate_log" in
|
||||||
|
*fruix-activate:done*) : ;;
|
||||||
|
*) echo "booted target activation log does not show success" >&2; exit 1 ;;
|
||||||
|
esac
|
||||||
|
|
||||||
|
cat >"$metadata_file" <<EOF
|
||||||
|
workdir=$workdir
|
||||||
|
vm_id=$vm_id
|
||||||
|
primary_vdi_id=$vdi_id
|
||||||
|
secondary_vdi_id=$secondary_vdi_id
|
||||||
|
vdi_size=$vdi_size
|
||||||
|
iso_sr_id=$iso_sr_id
|
||||||
|
imported_iso_id=$imported_iso_id
|
||||||
|
imported_iso_name=$imported_iso_name
|
||||||
|
guest_ip=$guest_ip
|
||||||
|
target_os_file=$target_os_file
|
||||||
|
installer_iso_store_path=$iso_store_path
|
||||||
|
installer_iso_image=$installer_iso_image
|
||||||
|
installer_boot_efi_image=$installer_boot_efi_image
|
||||||
|
installer_root_image=$installer_root_image
|
||||||
|
install_target_device=$install_target_device
|
||||||
|
installer_root_size=$root_size_out
|
||||||
|
iso_volume_label=$iso_volume_label
|
||||||
|
freebsd_source_kind=$freebsd_source_kind_out
|
||||||
|
freebsd_source_ref=$freebsd_source_ref_out
|
||||||
|
freebsd_source_commit=$freebsd_source_commit_out
|
||||||
|
freebsd_source_file=$freebsd_source_file
|
||||||
|
freebsd_source_materializations_file=$freebsd_source_materializations_file
|
||||||
|
materialized_source_store_count=$materialized_source_store_count
|
||||||
|
materialized_source_store=$materialized_source_stores
|
||||||
|
installer_closure_path=$installer_closure_path
|
||||||
|
target_closure_path=$target_closure_path
|
||||||
|
native_base_store_count=$native_base_store_count
|
||||||
|
native_base_stores=$native_base_stores
|
||||||
|
store_item_count=$store_item_count
|
||||||
|
target_store_item_count=$target_store_item_count
|
||||||
|
installer_store_item_count=$installer_store_item_count
|
||||||
|
installer_state_path=$installer_state_path
|
||||||
|
installer_log_path=$installer_log_path
|
||||||
|
installer_state=$installer_state
|
||||||
|
installer_run_current_system=$installer_run_current_system
|
||||||
|
installer_sshd_status=$installer_sshd_status
|
||||||
|
installer_target_device=$installer_target_device
|
||||||
|
installer_kern_disks=$installer_kern_disks
|
||||||
|
installer_log_file=$installer_log_file
|
||||||
|
installer_gpart_file=$installer_gpart_file
|
||||||
|
installer_kern_disks_file=$installer_kern_disks_file
|
||||||
|
target_esp_fstype=$installer_esp_fstype
|
||||||
|
target_root_fstype=$installer_root_fstype
|
||||||
|
target_run_current_system=$target_run_current_system
|
||||||
|
target_shepherd_status=$target_shepherd_status
|
||||||
|
target_sshd_status=$target_sshd_status
|
||||||
|
target_install_metadata_file=$target_install_metadata_file
|
||||||
|
boot_backend=xcp-ng-xo-cli
|
||||||
|
installer_iso_boot=ok
|
||||||
|
installer_iso_install=ok
|
||||||
|
installed_target_boot=ok
|
||||||
|
EOF
|
||||||
|
|
||||||
|
if [ -n "$metadata_target" ]; then
|
||||||
|
mkdir -p "$(dirname "$metadata_target")"
|
||||||
|
cp "$metadata_file" "$metadata_target"
|
||||||
|
fi
|
||||||
|
|
||||||
|
printf 'PASS phase18-installer-iso-xcpng\n'
|
||||||
|
printf 'Work directory: %s\n' "$workdir"
|
||||||
|
printf 'Metadata file: %s\n' "$metadata_file"
|
||||||
|
if [ -n "$metadata_target" ]; then
|
||||||
|
printf 'Copied metadata to: %s\n' "$metadata_target"
|
||||||
|
fi
|
||||||
|
printf '%s\n' '--- metadata ---'
|
||||||
|
cat "$metadata_file"
|
||||||
412
tests/system/run-phase18-installer-iso.sh
Executable file
412
tests/system/run-phase18-installer-iso.sh
Executable file
@@ -0,0 +1,412 @@
|
|||||||
|
#!/bin/sh
|
||||||
|
set -eu
|
||||||
|
|
||||||
|
project_root=${PROJECT_ROOT:-$(pwd)}
|
||||||
|
script_dir=$(CDPATH= cd -- "$(dirname "$0")" && pwd)
|
||||||
|
fruix_cmd=$project_root/bin/fruix
|
||||||
|
os_template=${OS_TEMPLATE:-$script_dir/phase18-installer-target-operating-system.scm.in}
|
||||||
|
system_name=${SYSTEM_NAME:-phase18-target-operating-system}
|
||||||
|
store_dir=${STORE_DIR:-/frx/store}
|
||||||
|
installer_root_size=${INSTALLER_ROOT_SIZE:-}
|
||||||
|
target_disk_capacity=${TARGET_DISK_CAPACITY:-12g}
|
||||||
|
install_target_device=${INSTALL_TARGET_DEVICE:-/dev/vtbd0}
|
||||||
|
qemu_smp=${QEMU_SMP:-2}
|
||||||
|
installer_memory=${INSTALLER_MEMORY:-6144}
|
||||||
|
installer_ssh_port=${INSTALLER_SSH_PORT:-10027}
|
||||||
|
target_ssh_port=${TARGET_SSH_PORT:-10028}
|
||||||
|
base_name=${BASE_NAME:-phase18-installer-iso-target}
|
||||||
|
base_version_label=${BASE_VERSION_LABEL:-15.0-STABLE-installer-iso-target}
|
||||||
|
base_release=${BASE_RELEASE:-15.0-STABLE}
|
||||||
|
base_branch=${BASE_BRANCH:-stable/15}
|
||||||
|
source_name=${SOURCE_NAME:-stable15-installer-iso-target-source}
|
||||||
|
source_ref=${SOURCE_REF:-stable/15}
|
||||||
|
source_commit=${SOURCE_COMMIT:-332708a606f6bf0841c1d4a74c0d067f5640fe89}
|
||||||
|
declared_source_root=${DECLARED_SOURCE_ROOT:-/var/empty/fruix-unused-source-root-installer-iso-target}
|
||||||
|
metadata_target=${METADATA_OUT:-}
|
||||||
|
root_authorized_key_file=${ROOT_AUTHORIZED_KEY_FILE:-$HOME/.ssh/id_ed25519.pub}
|
||||||
|
root_ssh_private_key_file=${ROOT_SSH_PRIVATE_KEY_FILE:-$HOME/.ssh/id_ed25519}
|
||||||
|
|
||||||
|
[ -x "$fruix_cmd" ] || {
|
||||||
|
echo "fruix command is not executable: $fruix_cmd" >&2
|
||||||
|
exit 1
|
||||||
|
}
|
||||||
|
[ -f "$os_template" ] || {
|
||||||
|
echo "missing operating-system template: $os_template" >&2
|
||||||
|
exit 1
|
||||||
|
}
|
||||||
|
[ -f "$root_authorized_key_file" ] || {
|
||||||
|
echo "missing root authorized key file: $root_authorized_key_file" >&2
|
||||||
|
exit 1
|
||||||
|
}
|
||||||
|
[ -f "$root_ssh_private_key_file" ] || {
|
||||||
|
echo "missing root SSH private key file: $root_ssh_private_key_file" >&2
|
||||||
|
exit 1
|
||||||
|
}
|
||||||
|
command -v qemu-system-x86_64 >/dev/null 2>&1 || {
|
||||||
|
echo "qemu-system-x86_64 is required" >&2
|
||||||
|
exit 1
|
||||||
|
}
|
||||||
|
[ -f /usr/local/share/edk2-qemu/QEMU_UEFI_CODE-x86_64.fd ] || {
|
||||||
|
echo "missing QEMU UEFI firmware" >&2
|
||||||
|
exit 1
|
||||||
|
}
|
||||||
|
|
||||||
|
cleanup=0
|
||||||
|
if [ -n "${WORKDIR:-}" ]; then
|
||||||
|
workdir=$WORKDIR
|
||||||
|
mkdir -p "$workdir"
|
||||||
|
else
|
||||||
|
workdir=$(mktemp -d /tmp/fruix-phase18-installer-iso.XXXXXX)
|
||||||
|
cleanup=1
|
||||||
|
fi
|
||||||
|
if [ "${KEEP_WORKDIR:-0}" -eq 1 ]; then
|
||||||
|
cleanup=0
|
||||||
|
fi
|
||||||
|
|
||||||
|
target_os_file=$workdir/phase18-installer-iso-target-operating-system.scm
|
||||||
|
installer_out=$workdir/installer-iso.txt
|
||||||
|
metadata_file=$workdir/phase18-installer-iso-metadata.txt
|
||||||
|
installer_serial_log=$workdir/installer-iso-serial.log
|
||||||
|
target_serial_log=$workdir/target-serial.log
|
||||||
|
installer_qemu_pidfile=$workdir/installer-qemu.pid
|
||||||
|
target_qemu_pidfile=$workdir/target-qemu.pid
|
||||||
|
installer_uefi_vars=$workdir/installer-vars.fd
|
||||||
|
target_uefi_vars=$workdir/target-vars.fd
|
||||||
|
installer_boot_iso=$workdir/installer-boot.iso
|
||||||
|
target_image=$workdir/installed-target.img
|
||||||
|
gpart_log=$workdir/gpart-show.txt
|
||||||
|
mnt_esp=$workdir/mnt-esp
|
||||||
|
mnt_root=$workdir/mnt-root
|
||||||
|
md_unit=
|
||||||
|
|
||||||
|
cleanup_workdir() {
|
||||||
|
if [ -f "$installer_qemu_pidfile" ]; then
|
||||||
|
sudo kill "$(sudo cat "$installer_qemu_pidfile")" >/dev/null 2>&1 || true
|
||||||
|
fi
|
||||||
|
if [ -f "$target_qemu_pidfile" ]; then
|
||||||
|
sudo kill "$(sudo cat "$target_qemu_pidfile")" >/dev/null 2>&1 || true
|
||||||
|
fi
|
||||||
|
if [ -n "$md_unit" ]; then
|
||||||
|
sudo umount "$mnt_esp" >/dev/null 2>&1 || true
|
||||||
|
sudo umount "$mnt_root" >/dev/null 2>&1 || true
|
||||||
|
sudo mdconfig -d -u "$md_unit" >/dev/null 2>&1 || true
|
||||||
|
fi
|
||||||
|
if [ "$cleanup" -eq 1 ]; then
|
||||||
|
rm -rf "$workdir" 2>/dev/null || sudo rm -rf "$workdir"
|
||||||
|
fi
|
||||||
|
}
|
||||||
|
trap cleanup_workdir EXIT INT TERM
|
||||||
|
|
||||||
|
root_authorized_key=$(tr -d '\n' < "$root_authorized_key_file")
|
||||||
|
sed \
|
||||||
|
-e "s|__BASE_NAME__|$base_name|g" \
|
||||||
|
-e "s|__BASE_VERSION_LABEL__|$base_version_label|g" \
|
||||||
|
-e "s|__BASE_RELEASE__|$base_release|g" \
|
||||||
|
-e "s|__BASE_BRANCH__|$base_branch|g" \
|
||||||
|
-e "s|__SOURCE_NAME__|$source_name|g" \
|
||||||
|
-e "s|__SOURCE_REF__|$source_ref|g" \
|
||||||
|
-e "s|__SOURCE_COMMIT__|$source_commit|g" \
|
||||||
|
-e "s|__DECLARED_SOURCE_ROOT__|$declared_source_root|g" \
|
||||||
|
-e "s|__ROOT_AUTHORIZED_KEY__|$root_authorized_key|g" \
|
||||||
|
"$os_template" > "$target_os_file"
|
||||||
|
|
||||||
|
cp /usr/local/share/edk2-qemu/QEMU_UEFI_VARS-x86_64.fd "$installer_uefi_vars"
|
||||||
|
cp /usr/local/share/edk2-qemu/QEMU_UEFI_VARS-x86_64.fd "$target_uefi_vars"
|
||||||
|
truncate -s "$target_disk_capacity" "$target_image"
|
||||||
|
mkdir -p "$mnt_esp" "$mnt_root"
|
||||||
|
|
||||||
|
action_env() {
|
||||||
|
sudo env \
|
||||||
|
HOME="$HOME" \
|
||||||
|
GUILE_AUTO_COMPILE=0 \
|
||||||
|
FRUIX_FREEBSD_BUILD_JOBS="${FRUIX_FREEBSD_BUILD_JOBS:-8}" \
|
||||||
|
GUIX_SOURCE_DIR="${GUIX_SOURCE_DIR:-$HOME/repos/guix}" \
|
||||||
|
GUILE_BIN="${GUILE_BIN:-/tmp/guile-freebsd-validate-install/bin/guile}" \
|
||||||
|
GUILE_EXTRA_PREFIX="${GUILE_EXTRA_PREFIX:-/tmp/guile-gnutls-freebsd-validate-install}" \
|
||||||
|
SHEPHERD_PREFIX="${SHEPHERD_PREFIX:-/tmp/shepherd-freebsd-validate-install}" \
|
||||||
|
"$@"
|
||||||
|
}
|
||||||
|
|
||||||
|
if [ -n "$installer_root_size" ]; then
|
||||||
|
action_env "$fruix_cmd" system installer-iso "$target_os_file" \
|
||||||
|
--system "$system_name" \
|
||||||
|
--store "$store_dir" \
|
||||||
|
--install-target-device "$install_target_device" \
|
||||||
|
--root-size "$installer_root_size" >"$installer_out"
|
||||||
|
else
|
||||||
|
action_env "$fruix_cmd" system installer-iso "$target_os_file" \
|
||||||
|
--system "$system_name" \
|
||||||
|
--store "$store_dir" \
|
||||||
|
--install-target-device "$install_target_device" >"$installer_out"
|
||||||
|
fi
|
||||||
|
|
||||||
|
field() {
|
||||||
|
sed -n "s/^$1=//p" "$installer_out" | tail -n 1
|
||||||
|
}
|
||||||
|
|
||||||
|
iso_store_path=$(field iso_store_path)
|
||||||
|
installer_iso_image=$(field iso_image)
|
||||||
|
installer_boot_efi_image=$(field boot_efi_image)
|
||||||
|
installer_root_image=$(field root_image)
|
||||||
|
installer_closure_path=$(field installer_closure_path)
|
||||||
|
target_closure_path=$(field target_closure_path)
|
||||||
|
installer_host_name=$(field installer_host_name)
|
||||||
|
install_target_device_out=$(field install_target_device)
|
||||||
|
installer_state_path=$(field installer_state_path)
|
||||||
|
installer_log_path=$(field installer_log_path)
|
||||||
|
iso_volume_label=$(field iso_volume_label)
|
||||||
|
root_size_out=$(field root_size)
|
||||||
|
freebsd_source_kind_out=$(field freebsd_source_kind)
|
||||||
|
freebsd_source_ref_out=$(field freebsd_source_ref)
|
||||||
|
freebsd_source_commit_out=$(field freebsd_source_commit)
|
||||||
|
freebsd_source_file=$(field freebsd_source_file)
|
||||||
|
freebsd_source_materializations_file=$(field freebsd_source_materializations_file)
|
||||||
|
materialized_source_store_count=$(field materialized_source_store_count)
|
||||||
|
materialized_source_stores=$(field materialized_source_stores)
|
||||||
|
host_base_store_count=$(field host_base_store_count)
|
||||||
|
native_base_store_count=$(field native_base_store_count)
|
||||||
|
native_base_stores=$(field native_base_stores)
|
||||||
|
store_item_count=$(field store_item_count)
|
||||||
|
target_store_item_count=$(field target_store_item_count)
|
||||||
|
installer_store_item_count=$(field installer_store_item_count)
|
||||||
|
store_layout_file=$(field store_layout_file)
|
||||||
|
|
||||||
|
[ -d "$iso_store_path" ] || { echo "missing installer ISO store path: $iso_store_path" >&2; exit 1; }
|
||||||
|
[ -f "$installer_iso_image" ] || { echo "missing installer ISO image: $installer_iso_image" >&2; exit 1; }
|
||||||
|
[ -f "$installer_boot_efi_image" ] || { echo "missing installer EFI boot image: $installer_boot_efi_image" >&2; exit 1; }
|
||||||
|
[ -f "$installer_root_image" ] || { echo "missing installer root image: $installer_root_image" >&2; exit 1; }
|
||||||
|
[ -n "$installer_closure_path" ] || { echo "missing installer closure path" >&2; exit 1; }
|
||||||
|
[ -n "$target_closure_path" ] || { echo "missing target closure path" >&2; exit 1; }
|
||||||
|
[ "$install_target_device_out" = "$install_target_device" ] || { echo "unexpected install target device: $install_target_device_out" >&2; exit 1; }
|
||||||
|
[ "$installer_host_name" = fruix-freebsd-installer ] || { echo "unexpected installer host name: $installer_host_name" >&2; exit 1; }
|
||||||
|
[ -n "$iso_volume_label" ] || { echo "missing ISO volume label" >&2; exit 1; }
|
||||||
|
[ "$freebsd_source_kind_out" = git ] || { echo "unexpected source kind: $freebsd_source_kind_out" >&2; exit 1; }
|
||||||
|
[ "$freebsd_source_ref_out" = "$source_ref" ] || { echo "unexpected source ref: $freebsd_source_ref_out" >&2; exit 1; }
|
||||||
|
[ "$freebsd_source_commit_out" = "$source_commit" ] || { echo "unexpected source commit: $freebsd_source_commit_out" >&2; exit 1; }
|
||||||
|
[ "$materialized_source_store_count" = 1 ] || { echo "unexpected materialized source store count: $materialized_source_store_count" >&2; exit 1; }
|
||||||
|
[ "$host_base_store_count" = 0 ] || { echo "expected zero host base stores, got: $host_base_store_count" >&2; exit 1; }
|
||||||
|
[ "$native_base_store_count" = 3 ] || { echo "expected three native base stores, got: $native_base_store_count" >&2; exit 1; }
|
||||||
|
[ -f "$freebsd_source_file" ] || { echo "missing freebsd source file: $freebsd_source_file" >&2; exit 1; }
|
||||||
|
[ -f "$freebsd_source_materializations_file" ] || { echo "missing source materializations file: $freebsd_source_materializations_file" >&2; exit 1; }
|
||||||
|
[ -f "$store_layout_file" ] || { echo "missing store layout file: $store_layout_file" >&2; exit 1; }
|
||||||
|
case "$materialized_source_stores" in
|
||||||
|
/frx/store/*-freebsd-source-$source_name) : ;;
|
||||||
|
*) echo "unexpected materialized source store path: $materialized_source_stores" >&2; exit 1 ;;
|
||||||
|
esac
|
||||||
|
[ "$store_item_count" -ge "$target_store_item_count" ] || { echo "combined store item count smaller than target store item count" >&2; exit 1; }
|
||||||
|
[ "$installer_store_item_count" -ge 1 ] || { echo "expected installer store items" >&2; exit 1; }
|
||||||
|
|
||||||
|
cp "$installer_iso_image" "$installer_boot_iso"
|
||||||
|
|
||||||
|
target_closure_base=$(basename "$target_closure_path")
|
||||||
|
installer_closure_base=$(basename "$installer_closure_path")
|
||||||
|
|
||||||
|
sudo qemu-system-x86_64 \
|
||||||
|
-machine q35,accel=tcg \
|
||||||
|
-cpu max \
|
||||||
|
-m "$installer_memory" \
|
||||||
|
-smp "$qemu_smp" \
|
||||||
|
-display none \
|
||||||
|
-serial "file:$installer_serial_log" \
|
||||||
|
-monitor none \
|
||||||
|
-pidfile "$installer_qemu_pidfile" \
|
||||||
|
-daemonize \
|
||||||
|
-boot d \
|
||||||
|
-drive if=pflash,format=raw,readonly=on,file=/usr/local/share/edk2-qemu/QEMU_UEFI_CODE-x86_64.fd \
|
||||||
|
-drive if=pflash,format=raw,file="$installer_uefi_vars" \
|
||||||
|
-cdrom "$installer_boot_iso" \
|
||||||
|
-drive if=virtio,format=raw,file="$target_image" \
|
||||||
|
-netdev user,id=net0,hostfwd=tcp::${installer_ssh_port}-:22 \
|
||||||
|
-device virtio-net-pci,netdev=net0
|
||||||
|
|
||||||
|
installer_guest() {
|
||||||
|
ssh -p "$installer_ssh_port" -i "$root_ssh_private_key_file" \
|
||||||
|
-o BatchMode=yes \
|
||||||
|
-o StrictHostKeyChecking=no \
|
||||||
|
-o UserKnownHostsFile=/dev/null \
|
||||||
|
-o LogLevel=ERROR \
|
||||||
|
-o ConnectTimeout=5 \
|
||||||
|
root@127.0.0.1 "$@"
|
||||||
|
}
|
||||||
|
|
||||||
|
installer_ssh_reached=0
|
||||||
|
installer_state=missing
|
||||||
|
for attempt in $(jot 180 1 180); do
|
||||||
|
if installer_guest 'service sshd onestatus >/dev/null 2>&1' >/dev/null 2>&1; then
|
||||||
|
installer_ssh_reached=1
|
||||||
|
installer_state=$(installer_guest "cat '$installer_state_path' 2>/dev/null || echo missing")
|
||||||
|
[ "$installer_state" = done ] && break
|
||||||
|
fi
|
||||||
|
sleep 2
|
||||||
|
done
|
||||||
|
|
||||||
|
[ "$installer_ssh_reached" = 1 ] || { echo "installer ISO environment never became reachable over SSH" >&2; exit 1; }
|
||||||
|
[ "$installer_state" = done ] || { echo "installer ISO environment did not finish installation: $installer_state" >&2; exit 1; }
|
||||||
|
|
||||||
|
installer_run_current_system=$(installer_guest 'readlink /run/current-system')
|
||||||
|
installer_sshd_status=$(installer_guest 'service sshd onestatus >/dev/null 2>&1 && echo running || echo stopped')
|
||||||
|
installer_activate_log=$(installer_guest 'cat /var/log/fruix-activate.log 2>/dev/null || true' | tr '\n' ' ')
|
||||||
|
installer_log=$(installer_guest "cat '$installer_log_path' 2>/dev/null || true" | tr '\n' ' ')
|
||||||
|
|
||||||
|
[ "$installer_run_current_system" = "/frx/store/$installer_closure_base" ] || { echo "unexpected installer current-system target: $installer_run_current_system" >&2; exit 1; }
|
||||||
|
[ "$installer_sshd_status" = running ] || { echo "installer sshd is not running" >&2; exit 1; }
|
||||||
|
case "$installer_activate_log" in
|
||||||
|
*fruix-activate:done*) : ;;
|
||||||
|
*) echo "installer activation log does not show success" >&2; exit 1 ;;
|
||||||
|
esac
|
||||||
|
case "$installer_log" in
|
||||||
|
*fruix-installer:done*) : ;;
|
||||||
|
*) echo "installer log does not show completion" >&2; exit 1 ;;
|
||||||
|
esac
|
||||||
|
|
||||||
|
sudo kill "$(sudo cat "$installer_qemu_pidfile")" >/dev/null 2>&1 || true
|
||||||
|
rm -f "$installer_qemu_pidfile"
|
||||||
|
sleep 2
|
||||||
|
|
||||||
|
md=$(sudo mdconfig -a -t vnode -f "$target_image")
|
||||||
|
md_unit=${md#md}
|
||||||
|
sudo gpart show -lp "/dev/$md" >"$gpart_log"
|
||||||
|
esp_fstype=$(sudo fstyp "/dev/${md}p1")
|
||||||
|
root_fstype=$(sudo fstyp "/dev/${md}p2")
|
||||||
|
[ "$esp_fstype" = msdosfs ] || { echo "unexpected target ESP filesystem: $esp_fstype" >&2; exit 1; }
|
||||||
|
[ "$root_fstype" = ufs ] || { echo "unexpected target root filesystem: $root_fstype" >&2; exit 1; }
|
||||||
|
|
||||||
|
sudo mount -t msdosfs "/dev/${md}p1" "$mnt_esp"
|
||||||
|
sudo mount -t ufs -o ro "/dev/${md}p2" "$mnt_root"
|
||||||
|
|
||||||
|
[ -f "$mnt_esp/EFI/BOOT/BOOTX64.EFI" ] || { echo "missing EFI boot file on installed target" >&2; exit 1; }
|
||||||
|
target_run_current_system=$(readlink "$mnt_root/run/current-system")
|
||||||
|
target_boot_loader=$(readlink "$mnt_root/boot/loader")
|
||||||
|
install_metadata_host=$(cat "$mnt_root/var/lib/fruix/install.scm")
|
||||||
|
[ "$target_run_current_system" = "/frx/store/$target_closure_base" ] || { echo "unexpected target /run/current-system target: $target_run_current_system" >&2; exit 1; }
|
||||||
|
[ "$target_boot_loader" = /run/current-system/boot/loader ] || { echo "unexpected target boot loader link: $target_boot_loader" >&2; exit 1; }
|
||||||
|
[ -d "$mnt_root/frx/store/$target_closure_base" ] || { echo "installed target closure missing from target root" >&2; exit 1; }
|
||||||
|
case "$install_metadata_host" in
|
||||||
|
*"$target_closure_path"*) : ;;
|
||||||
|
*) echo "installed target metadata does not record target closure path" >&2; exit 1 ;;
|
||||||
|
esac
|
||||||
|
case "$install_metadata_host" in
|
||||||
|
*"$materialized_source_stores"*) : ;;
|
||||||
|
*) echo "installed target metadata does not record materialized source store" >&2; exit 1 ;;
|
||||||
|
esac
|
||||||
|
|
||||||
|
sudo umount "$mnt_esp"
|
||||||
|
sudo umount "$mnt_root"
|
||||||
|
sudo mdconfig -d -u "$md_unit"
|
||||||
|
md_unit=
|
||||||
|
|
||||||
|
sudo qemu-system-x86_64 \
|
||||||
|
-machine q35,accel=tcg \
|
||||||
|
-cpu max \
|
||||||
|
-m 2048 \
|
||||||
|
-smp "$qemu_smp" \
|
||||||
|
-display none \
|
||||||
|
-serial "file:$target_serial_log" \
|
||||||
|
-monitor none \
|
||||||
|
-pidfile "$target_qemu_pidfile" \
|
||||||
|
-daemonize \
|
||||||
|
-drive if=pflash,format=raw,readonly=on,file=/usr/local/share/edk2-qemu/QEMU_UEFI_CODE-x86_64.fd \
|
||||||
|
-drive if=pflash,format=raw,file="$target_uefi_vars" \
|
||||||
|
-drive if=virtio,format=raw,file="$target_image" \
|
||||||
|
-netdev user,id=net0,hostfwd=tcp::${target_ssh_port}-:22 \
|
||||||
|
-device virtio-net-pci,netdev=net0
|
||||||
|
|
||||||
|
target_guest() {
|
||||||
|
ssh -p "$target_ssh_port" -i "$root_ssh_private_key_file" \
|
||||||
|
-o BatchMode=yes \
|
||||||
|
-o StrictHostKeyChecking=no \
|
||||||
|
-o UserKnownHostsFile=/dev/null \
|
||||||
|
-o LogLevel=ERROR \
|
||||||
|
-o ConnectTimeout=5 \
|
||||||
|
root@127.0.0.1 "$@"
|
||||||
|
}
|
||||||
|
|
||||||
|
for attempt in $(jot 120 1 120); do
|
||||||
|
if target_guest 'service sshd onestatus >/dev/null 2>&1' >/dev/null 2>&1; then
|
||||||
|
break
|
||||||
|
fi
|
||||||
|
sleep 2
|
||||||
|
done
|
||||||
|
|
||||||
|
target_run_current_system_guest=$(target_guest 'readlink /run/current-system')
|
||||||
|
target_shepherd_status=$(target_guest '/usr/local/etc/rc.d/fruix-shepherd onestatus >/dev/null 2>&1 && echo running || echo stopped')
|
||||||
|
target_sshd_status=$(target_guest 'service sshd onestatus >/dev/null 2>&1 && echo running || echo stopped')
|
||||||
|
target_install_metadata_guest=$(target_guest 'cat /var/lib/fruix/install.scm')
|
||||||
|
target_activate_log=$(target_guest 'cat /var/log/fruix-activate.log 2>/dev/null || true' | tr '\n' ' ')
|
||||||
|
|
||||||
|
[ "$target_run_current_system_guest" = "/frx/store/$target_closure_base" ] || { echo "unexpected booted target current-system: $target_run_current_system_guest" >&2; exit 1; }
|
||||||
|
[ "$target_shepherd_status" = running ] || { echo "fruix-shepherd is not running in booted target" >&2; exit 1; }
|
||||||
|
[ "$target_sshd_status" = running ] || { echo "sshd is not running in booted target" >&2; exit 1; }
|
||||||
|
case "$target_install_metadata_guest" in
|
||||||
|
*"$target_closure_path"*) : ;;
|
||||||
|
*) echo "booted target metadata does not record target closure path" >&2; exit 1 ;;
|
||||||
|
esac
|
||||||
|
case "$target_install_metadata_guest" in
|
||||||
|
*"$materialized_source_stores"*) : ;;
|
||||||
|
*) echo "booted target metadata does not record materialized source store" >&2; exit 1 ;;
|
||||||
|
esac
|
||||||
|
case "$target_activate_log" in
|
||||||
|
*fruix-activate:done*) : ;;
|
||||||
|
*) echo "booted target activation log does not show success" >&2; exit 1 ;;
|
||||||
|
esac
|
||||||
|
|
||||||
|
cat >"$metadata_file" <<EOF
|
||||||
|
workdir=$workdir
|
||||||
|
target_os_file=$target_os_file
|
||||||
|
installer_iso_store_path=$iso_store_path
|
||||||
|
installer_iso_image=$installer_iso_image
|
||||||
|
installer_boot_iso=$installer_boot_iso
|
||||||
|
installer_boot_efi_image=$installer_boot_efi_image
|
||||||
|
installer_root_image=$installer_root_image
|
||||||
|
installer_memory=$installer_memory
|
||||||
|
installer_root_size=$root_size_out
|
||||||
|
target_image=$target_image
|
||||||
|
target_disk_capacity=$target_disk_capacity
|
||||||
|
install_target_device=$install_target_device
|
||||||
|
qemu_smp=$qemu_smp
|
||||||
|
iso_volume_label=$iso_volume_label
|
||||||
|
freebsd_source_kind=$freebsd_source_kind_out
|
||||||
|
freebsd_source_ref=$freebsd_source_ref_out
|
||||||
|
freebsd_source_commit=$freebsd_source_commit_out
|
||||||
|
freebsd_source_file=$freebsd_source_file
|
||||||
|
freebsd_source_materializations_file=$freebsd_source_materializations_file
|
||||||
|
materialized_source_store_count=$materialized_source_store_count
|
||||||
|
materialized_source_store=$materialized_source_stores
|
||||||
|
installer_closure_path=$installer_closure_path
|
||||||
|
target_closure_path=$target_closure_path
|
||||||
|
native_base_store_count=$native_base_store_count
|
||||||
|
native_base_stores=$native_base_stores
|
||||||
|
store_item_count=$store_item_count
|
||||||
|
target_store_item_count=$target_store_item_count
|
||||||
|
installer_store_item_count=$installer_store_item_count
|
||||||
|
installer_state_path=$installer_state_path
|
||||||
|
installer_log_path=$installer_log_path
|
||||||
|
installer_state=$installer_state
|
||||||
|
installer_run_current_system=$installer_run_current_system
|
||||||
|
installer_sshd_status=$installer_sshd_status
|
||||||
|
installer_serial_log=$installer_serial_log
|
||||||
|
target_esp_fstype=$esp_fstype
|
||||||
|
target_root_fstype=$root_fstype
|
||||||
|
gpart_log=$gpart_log
|
||||||
|
target_run_current_system=$target_run_current_system_guest
|
||||||
|
target_shepherd_status=$target_shepherd_status
|
||||||
|
target_sshd_status=$target_sshd_status
|
||||||
|
target_serial_log=$target_serial_log
|
||||||
|
installer_iso_boot=ok
|
||||||
|
installer_iso_install=ok
|
||||||
|
installed_target_boot=ok
|
||||||
|
EOF
|
||||||
|
|
||||||
|
if [ -n "$metadata_target" ]; then
|
||||||
|
mkdir -p "$(dirname "$metadata_target")"
|
||||||
|
cp "$metadata_file" "$metadata_target"
|
||||||
|
fi
|
||||||
|
|
||||||
|
printf 'PASS phase18-installer-iso\n'
|
||||||
|
printf 'Work directory: %s\n' "$workdir"
|
||||||
|
printf 'Metadata file: %s\n' "$metadata_file"
|
||||||
|
if [ -n "$metadata_target" ]; then
|
||||||
|
printf 'Copied metadata to: %s\n' "$metadata_target"
|
||||||
|
fi
|
||||||
|
printf '%s\n' '--- metadata ---'
|
||||||
|
cat "$metadata_file"
|
||||||
160
tests/system/run-phase19-generation-layout-qemu.sh
Executable file
160
tests/system/run-phase19-generation-layout-qemu.sh
Executable file
@@ -0,0 +1,160 @@
|
|||||||
|
#!/bin/sh
|
||||||
|
set -eu
|
||||||
|
|
||||||
|
repo_root=${PROJECT_ROOT:-$(pwd)}
|
||||||
|
metadata_target=${METADATA_OUT:-}
|
||||||
|
cleanup=0
|
||||||
|
|
||||||
|
if [ -n "${WORKDIR:-}" ]; then
|
||||||
|
workdir=$WORKDIR
|
||||||
|
mkdir -p "$workdir"
|
||||||
|
else
|
||||||
|
workdir=$(mktemp -d /tmp/fruix-phase19-generation-layout-qemu.XXXXXX)
|
||||||
|
cleanup=1
|
||||||
|
fi
|
||||||
|
if [ "${KEEP_WORKDIR:-0}" -eq 1 ]; then
|
||||||
|
cleanup=0
|
||||||
|
fi
|
||||||
|
|
||||||
|
inner_workdir=$workdir/phase18-install
|
||||||
|
inner_metadata=$workdir/phase18-system-install-metadata.txt
|
||||||
|
metadata_file=$workdir/phase19-generation-layout-qemu-metadata.txt
|
||||||
|
mnt_esp=$workdir/mnt-esp
|
||||||
|
mnt_root=$workdir/mnt-root
|
||||||
|
md_unit=
|
||||||
|
|
||||||
|
cleanup_workdir() {
|
||||||
|
if [ -n "$md_unit" ]; then
|
||||||
|
sudo umount "$mnt_esp" >/dev/null 2>&1 || true
|
||||||
|
sudo umount "$mnt_root" >/dev/null 2>&1 || true
|
||||||
|
sudo mdconfig -d -u "$md_unit" >/dev/null 2>&1 || true
|
||||||
|
fi
|
||||||
|
if [ "$cleanup" -eq 1 ]; then
|
||||||
|
rm -rf "$workdir" 2>/dev/null || sudo rm -rf "$workdir"
|
||||||
|
fi
|
||||||
|
}
|
||||||
|
trap cleanup_workdir EXIT INT TERM
|
||||||
|
|
||||||
|
mkdir -p "$mnt_esp" "$mnt_root"
|
||||||
|
|
||||||
|
KEEP_WORKDIR=1 WORKDIR="$inner_workdir" METADATA_OUT="$inner_metadata" \
|
||||||
|
"$repo_root/tests/system/run-phase18-system-install.sh" >/dev/null
|
||||||
|
|
||||||
|
field() {
|
||||||
|
sed -n "s/^$1=//p" "$inner_metadata" | tail -n 1
|
||||||
|
}
|
||||||
|
|
||||||
|
target_image=$(field target_image)
|
||||||
|
closure_path=$(field closure_path)
|
||||||
|
materialized_source_store=$(field materialized_source_store)
|
||||||
|
install_metadata_path=$(field install_metadata_path)
|
||||||
|
run_current_system_target_reported=$(field run_current_system_target)
|
||||||
|
serial_log=$(field serial_log)
|
||||||
|
closure_base=$(basename "$closure_path")
|
||||||
|
|
||||||
|
guest_md=$(sudo mdconfig -a -t vnode -f "$target_image")
|
||||||
|
md_unit=${guest_md#md}
|
||||||
|
sudo mount -t msdosfs "/dev/${guest_md}p1" "$mnt_esp"
|
||||||
|
sudo mount -t ufs -o ro "/dev/${guest_md}p2" "$mnt_root"
|
||||||
|
|
||||||
|
system_root=$mnt_root/var/lib/fruix/system
|
||||||
|
generation_root=$system_root/generations/1
|
||||||
|
gcroots_root=$mnt_root/frx/var/fruix/gcroots
|
||||||
|
|
||||||
|
[ -d "$system_root" ] || { echo "missing explicit system generation root" >&2; exit 1; }
|
||||||
|
[ -d "$generation_root" ] || { echo "missing generation 1 directory" >&2; exit 1; }
|
||||||
|
[ -f "$system_root/current-generation" ] || { echo "missing current-generation file" >&2; exit 1; }
|
||||||
|
[ -L "$system_root/current" ] || { echo "missing current generation link" >&2; exit 1; }
|
||||||
|
[ -L "$generation_root/closure" ] || { echo "missing generation closure link" >&2; exit 1; }
|
||||||
|
[ -f "$generation_root/metadata.scm" ] || { echo "missing generation metadata file" >&2; exit 1; }
|
||||||
|
[ -f "$generation_root/provenance.scm" ] || { echo "missing generation provenance file" >&2; exit 1; }
|
||||||
|
[ -f "$generation_root/install.scm" ] || { echo "missing generation install metadata file" >&2; exit 1; }
|
||||||
|
[ -L "$gcroots_root/current-system" ] || { echo "missing current-system gc root" >&2; exit 1; }
|
||||||
|
[ -L "$gcroots_root/system-1" ] || { echo "missing system-1 gc root" >&2; exit 1; }
|
||||||
|
|
||||||
|
current_generation=$(tr -d '\n' < "$system_root/current-generation")
|
||||||
|
current_link=$(readlink "$system_root/current")
|
||||||
|
generation_closure=$(readlink "$generation_root/closure")
|
||||||
|
gcroot_current=$(readlink "$gcroots_root/current-system")
|
||||||
|
gcroot_generation=$(readlink "$gcroots_root/system-1")
|
||||||
|
run_current_system_target=$(readlink "$mnt_root/run/current-system")
|
||||||
|
generation_metadata=$(cat "$generation_root/metadata.scm")
|
||||||
|
generation_provenance=$(cat "$generation_root/provenance.scm")
|
||||||
|
generation_install=$(cat "$generation_root/install.scm")
|
||||||
|
install_metadata_host=$(cat "$mnt_root$install_metadata_path")
|
||||||
|
|
||||||
|
[ "$current_generation" = 1 ] || { echo "unexpected current generation: $current_generation" >&2; exit 1; }
|
||||||
|
[ "$current_link" = generations/1 ] || { echo "unexpected current link target: $current_link" >&2; exit 1; }
|
||||||
|
[ "$generation_closure" = "/frx/store/$closure_base" ] || { echo "unexpected generation closure target: $generation_closure" >&2; exit 1; }
|
||||||
|
[ "$gcroot_current" = "/frx/store/$closure_base" ] || { echo "unexpected current-system gc root: $gcroot_current" >&2; exit 1; }
|
||||||
|
[ "$gcroot_generation" = "/frx/store/$closure_base" ] || { echo "unexpected system-1 gc root: $gcroot_generation" >&2; exit 1; }
|
||||||
|
[ "$run_current_system_target" = "/frx/store/$closure_base" ] || { echo "unexpected /run/current-system target: $run_current_system_target" >&2; exit 1; }
|
||||||
|
[ "$run_current_system_target_reported" = "/frx/store/$closure_base" ] || { echo "unexpected reported guest current-system target: $run_current_system_target_reported" >&2; exit 1; }
|
||||||
|
|
||||||
|
case "$generation_metadata" in
|
||||||
|
*"$closure_path"*) : ;;
|
||||||
|
*) echo "generation metadata does not record closure path" >&2; exit 1 ;;
|
||||||
|
esac
|
||||||
|
case "$generation_metadata" in
|
||||||
|
*"$install_metadata_path"*) : ;;
|
||||||
|
*) echo "generation metadata does not record install metadata path" >&2; exit 1 ;;
|
||||||
|
esac
|
||||||
|
case "$generation_provenance" in
|
||||||
|
*"$closure_path/metadata/store-layout.scm"*) : ;;
|
||||||
|
*) echo "generation provenance does not reference store layout metadata" >&2; exit 1 ;;
|
||||||
|
esac
|
||||||
|
case "$generation_install" in
|
||||||
|
*"$closure_path"*) : ;;
|
||||||
|
*) echo "generation install metadata does not record closure path" >&2; exit 1 ;;
|
||||||
|
esac
|
||||||
|
case "$generation_install" in
|
||||||
|
*"$materialized_source_store"*) : ;;
|
||||||
|
*) echo "generation install metadata does not record materialized source store" >&2; exit 1 ;;
|
||||||
|
esac
|
||||||
|
case "$install_metadata_host" in
|
||||||
|
*"$closure_path"*) : ;;
|
||||||
|
*) echo "installed target metadata does not record closure path" >&2; exit 1 ;;
|
||||||
|
esac
|
||||||
|
|
||||||
|
sudo umount "$mnt_esp"
|
||||||
|
sudo umount "$mnt_root"
|
||||||
|
sudo mdconfig -d -u "$md_unit"
|
||||||
|
md_unit=
|
||||||
|
|
||||||
|
cat >"$metadata_file" <<EOF
|
||||||
|
workdir=$workdir
|
||||||
|
inner_workdir=$inner_workdir
|
||||||
|
inner_metadata=$inner_metadata
|
||||||
|
target_image=$target_image
|
||||||
|
closure_path=$closure_path
|
||||||
|
closure_base=$closure_base
|
||||||
|
materialized_source_store=$materialized_source_store
|
||||||
|
install_metadata_path=$install_metadata_path
|
||||||
|
system_root=$system_root
|
||||||
|
generation_root=$generation_root
|
||||||
|
gcroots_root=$gcroots_root
|
||||||
|
current_generation=$current_generation
|
||||||
|
current_link=$current_link
|
||||||
|
generation_closure=$generation_closure
|
||||||
|
gcroot_current=$gcroot_current
|
||||||
|
gcroot_generation=$gcroot_generation
|
||||||
|
run_current_system_target=$run_current_system_target
|
||||||
|
serial_log=$serial_log
|
||||||
|
generation_layout=explicit
|
||||||
|
boot_backend=qemu-uefi-tcg
|
||||||
|
generation_layout_validation=ok
|
||||||
|
EOF
|
||||||
|
|
||||||
|
if [ -n "$metadata_target" ]; then
|
||||||
|
mkdir -p "$(dirname "$metadata_target")"
|
||||||
|
cp "$metadata_file" "$metadata_target"
|
||||||
|
fi
|
||||||
|
|
||||||
|
printf 'PASS phase19-generation-layout-qemu\n'
|
||||||
|
printf 'Work directory: %s\n' "$workdir"
|
||||||
|
printf 'Metadata file: %s\n' "$metadata_file"
|
||||||
|
if [ -n "$metadata_target" ]; then
|
||||||
|
printf 'Copied metadata to: %s\n' "$metadata_target"
|
||||||
|
fi
|
||||||
|
printf '%s\n' '--- metadata ---'
|
||||||
|
cat "$metadata_file"
|
||||||
388
tests/system/run-phase19-installed-system-rollback-qemu.sh
Executable file
388
tests/system/run-phase19-installed-system-rollback-qemu.sh
Executable file
@@ -0,0 +1,388 @@
|
|||||||
|
#!/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/phase19-generation-rollback-operating-system.scm.in}
|
||||||
|
system_name=${SYSTEM_NAME:-phase19-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:-phase19-generation-layout}
|
||||||
|
base_version_label=${BASE_VERSION_LABEL:-15.0-STABLE-generation-layout}
|
||||||
|
base_release=${BASE_RELEASE:-15.0-STABLE}
|
||||||
|
base_branch=${BASE_BRANCH:-stable/15}
|
||||||
|
source_name=${SOURCE_NAME:-stable15-generation-layout-source}
|
||||||
|
source_ref=${SOURCE_REF:-stable/15}
|
||||||
|
source_commit=${SOURCE_COMMIT:-332708a606f6bf0841c1d4a74c0d067f5640fe89}
|
||||||
|
declared_source_root=${DECLARED_SOURCE_ROOT:-/var/empty/fruix-unused-source-root-generation-layout}
|
||||||
|
current_host_name=${CURRENT_HOST_NAME:-fruix-freebsd-current}
|
||||||
|
candidate_host_name=${CANDIDATE_HOST_NAME:-fruix-freebsd-canary}
|
||||||
|
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}
|
||||||
|
cleanup=0
|
||||||
|
|
||||||
|
[ -x "$fruix_cmd" ] || {
|
||||||
|
echo "fruix command is not executable: $fruix_cmd" >&2
|
||||||
|
exit 1
|
||||||
|
}
|
||||||
|
[ -f "$os_template" ] || {
|
||||||
|
echo "missing operating-system template: $os_template" >&2
|
||||||
|
exit 1
|
||||||
|
}
|
||||||
|
[ -f "$root_authorized_key_file" ] || {
|
||||||
|
echo "missing root authorized key file: $root_authorized_key_file" >&2
|
||||||
|
exit 1
|
||||||
|
}
|
||||||
|
[ -f "$root_ssh_private_key_file" ] || {
|
||||||
|
echo "missing root SSH private key file: $root_ssh_private_key_file" >&2
|
||||||
|
exit 1
|
||||||
|
}
|
||||||
|
command -v 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
|
||||||
|
}
|
||||||
|
|
||||||
|
if [ -n "${WORKDIR:-}" ]; then
|
||||||
|
workdir=$WORKDIR
|
||||||
|
mkdir -p "$workdir"
|
||||||
|
else
|
||||||
|
workdir=$(mktemp -d /tmp/fruix-phase19-installed-system-rollback-qemu.XXXXXX)
|
||||||
|
cleanup=1
|
||||||
|
fi
|
||||||
|
if [ "${KEEP_WORKDIR:-0}" -eq 1 ]; then
|
||||||
|
cleanup=0
|
||||||
|
fi
|
||||||
|
|
||||||
|
current_os_file=$workdir/current-operating-system.scm
|
||||||
|
candidate_os_file=$workdir/candidate-operating-system.scm
|
||||||
|
current_install_out=$workdir/current-install.txt
|
||||||
|
candidate_build_out=$workdir/candidate-build.txt
|
||||||
|
target_image=$workdir/installed.img
|
||||||
|
candidate_store_items=$workdir/candidate-store-items.txt
|
||||||
|
stage_log=$workdir/stage-candidate-store.txt
|
||||||
|
serial_log=$workdir/serial.log
|
||||||
|
qemu_pidfile=$workdir/qemu.pid
|
||||||
|
uefi_vars=$workdir/QEMU_UEFI_VARS.fd
|
||||||
|
metadata_file=$workdir/phase19-installed-system-rollback-qemu-metadata.txt
|
||||||
|
switch_status_file=$workdir/switch-status.txt
|
||||||
|
rollback_status_file=$workdir/rollback-status.txt
|
||||||
|
boot_current_status_file=$workdir/boot-current-status.txt
|
||||||
|
boot_candidate_status_file=$workdir/boot-candidate-status.txt
|
||||||
|
boot_rollback_status_file=$workdir/boot-rollback-status.txt
|
||||||
|
generation2_metadata_file=$workdir/generation2-metadata.scm
|
||||||
|
generation2_install_file=$workdir/generation2-install.scm
|
||||||
|
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_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
|
||||||
|
|
||||||
|
render_os() {
|
||||||
|
output=$1
|
||||||
|
host_name=$2
|
||||||
|
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|__HOST_NAME__|$host_name|g" \
|
||||||
|
-e "s|__ROOT_AUTHORIZED_KEY__|$root_authorized_key|g" \
|
||||||
|
"$os_template" > "$output"
|
||||||
|
}
|
||||||
|
|
||||||
|
render_os "$current_os_file" "$current_host_name"
|
||||||
|
render_os "$candidate_os_file" "$candidate_host_name"
|
||||||
|
cp /usr/local/share/edk2-qemu/QEMU_UEFI_VARS-x86_64.fd "$uefi_vars"
|
||||||
|
mkdir -p "$mnt_root"
|
||||||
|
|
||||||
|
action_env() {
|
||||||
|
sudo env \
|
||||||
|
HOME="$HOME" \
|
||||||
|
GUILE_AUTO_COMPILE=0 \
|
||||||
|
FRUIX_FREEBSD_BUILD_JOBS="${FRUIX_FREEBSD_BUILD_JOBS:-8}" \
|
||||||
|
GUIX_SOURCE_DIR="${GUIX_SOURCE_DIR:-$HOME/repos/guix}" \
|
||||||
|
GUILE_BIN="${GUILE_BIN:-/tmp/guile-freebsd-validate-install/bin/guile}" \
|
||||||
|
GUILE_EXTRA_PREFIX="${GUILE_EXTRA_PREFIX:-/tmp/guile-gnutls-freebsd-validate-install}" \
|
||||||
|
SHEPHERD_PREFIX="${SHEPHERD_PREFIX:-/tmp/shepherd-freebsd-validate-install}" \
|
||||||
|
"$@"
|
||||||
|
}
|
||||||
|
|
||||||
|
field() {
|
||||||
|
name=$1
|
||||||
|
file=$2
|
||||||
|
sed -n "s/^$name=//p" "$file" | tail -n 1
|
||||||
|
}
|
||||||
|
|
||||||
|
status_field() {
|
||||||
|
name=$1
|
||||||
|
file=$2
|
||||||
|
sed -n "s/^$name=//p" "$file" | tail -n 1
|
||||||
|
}
|
||||||
|
|
||||||
|
wait_for_ssh() {
|
||||||
|
for attempt in $(jot 120 1 120); do
|
||||||
|
if ssh_guest 'service sshd onestatus >/dev/null 2>&1' >/dev/null 2>&1; then
|
||||||
|
return 0
|
||||||
|
fi
|
||||||
|
sleep 2
|
||||||
|
done
|
||||||
|
return 1
|
||||||
|
}
|
||||||
|
|
||||||
|
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 "$@"
|
||||||
|
}
|
||||||
|
|
||||||
|
reboot_guest() {
|
||||||
|
ssh_guest 'shutdown -r now >/dev/null 2>&1 || reboot >/dev/null 2>&1 || true' >/dev/null 2>&1 || true
|
||||||
|
sleep 5
|
||||||
|
wait_for_ssh || {
|
||||||
|
echo "guest did not return over SSH after reboot" >&2
|
||||||
|
exit 1
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
capture_status() {
|
||||||
|
output_file=$1
|
||||||
|
ssh_guest '/usr/local/bin/fruix system status' > "$output_file"
|
||||||
|
}
|
||||||
|
|
||||||
|
action_env "$fruix_cmd" system install "$current_os_file" \
|
||||||
|
--system "$system_name" \
|
||||||
|
--store "$store_dir" \
|
||||||
|
--target "$target_image" \
|
||||||
|
--disk-capacity "$disk_capacity" \
|
||||||
|
--root-size "$root_size" > "$current_install_out"
|
||||||
|
|
||||||
|
current_closure_path=$(field closure_path "$current_install_out")
|
||||||
|
install_metadata_path=$(field install_metadata_path "$current_install_out")
|
||||||
|
materialized_source_store=$(field materialized_source_stores "$current_install_out")
|
||||||
|
[ -n "$current_closure_path" ] || { echo "missing current closure path" >&2; exit 1; }
|
||||||
|
[ "$install_metadata_path" = /var/lib/fruix/install.scm ] || { echo "unexpected install metadata path: $install_metadata_path" >&2; exit 1; }
|
||||||
|
|
||||||
|
action_env "$fruix_cmd" system build "$candidate_os_file" \
|
||||||
|
--system "$system_name" \
|
||||||
|
--store "$store_dir" > "$candidate_build_out"
|
||||||
|
|
||||||
|
candidate_closure_path=$(field closure_path "$candidate_build_out")
|
||||||
|
[ -n "$candidate_closure_path" ] || { echo "missing candidate closure path" >&2; exit 1; }
|
||||||
|
[ "$candidate_closure_path" != "$current_closure_path" ] || { echo "candidate closure unexpectedly matches current closure" >&2; exit 1; }
|
||||||
|
|
||||||
|
{
|
||||||
|
printf '%s\n' "$candidate_closure_path"
|
||||||
|
cat "$candidate_closure_path/.references"
|
||||||
|
} | awk 'NF { print }' | sort -u > "$candidate_store_items"
|
||||||
|
|
||||||
|
candidate_store_item_count=$(wc -l < "$candidate_store_items" | tr -d ' ')
|
||||||
|
|
||||||
|
md=$(sudo mdconfig -a -t vnode -f "$target_image")
|
||||||
|
md_unit=${md#md}
|
||||||
|
sudo mount -t ufs "/dev/${md}p2" "$mnt_root"
|
||||||
|
sudo mkdir -p "$mnt_root/frx/store"
|
||||||
|
: > "$stage_log"
|
||||||
|
while IFS= read -r item; do
|
||||||
|
[ -n "$item" ] || continue
|
||||||
|
item_base=$(basename "$item")
|
||||||
|
if [ -e "$mnt_root/frx/store/$item_base" ]; then
|
||||||
|
printf 'already-present=%s\n' "$item_base" >> "$stage_log"
|
||||||
|
continue
|
||||||
|
fi
|
||||||
|
printf 'copy=%s\n' "$item_base" >> "$stage_log"
|
||||||
|
sudo sh -c "cd '$store_dir' && pax -rw -pe '$item_base' '$mnt_root/frx/store'"
|
||||||
|
done < "$candidate_store_items"
|
||||||
|
sudo sync
|
||||||
|
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
|
||||||
|
|
||||||
|
wait_for_ssh || {
|
||||||
|
echo "guest never became reachable over SSH" >&2
|
||||||
|
exit 1
|
||||||
|
}
|
||||||
|
|
||||||
|
capture_status "$boot_current_status_file"
|
||||||
|
boot_current_closure=$(ssh_guest 'readlink /run/current-system')
|
||||||
|
boot_current_hostname=$(ssh_guest 'hostname')
|
||||||
|
boot_current_generation=$(status_field current_generation "$boot_current_status_file")
|
||||||
|
boot_current_link=$(status_field current_link "$boot_current_status_file")
|
||||||
|
boot_current_rollback_generation=$(status_field rollback_generation "$boot_current_status_file")
|
||||||
|
[ "$boot_current_closure" = "$current_closure_path" ] || { echo "unexpected current closure after initial boot: $boot_current_closure" >&2; exit 1; }
|
||||||
|
[ "$boot_current_hostname" = "$current_host_name" ] || { echo "unexpected hostname after initial boot: $boot_current_hostname" >&2; exit 1; }
|
||||||
|
[ "$boot_current_generation" = 1 ] || { echo "unexpected initial generation: $boot_current_generation" >&2; exit 1; }
|
||||||
|
[ "$boot_current_link" = generations/1 ] || { echo "unexpected initial current link: $boot_current_link" >&2; exit 1; }
|
||||||
|
[ -z "$boot_current_rollback_generation" ] || { echo "rollback generation should be empty before switch" >&2; exit 1; }
|
||||||
|
ssh_guest 'test -x /usr/local/bin/fruix'
|
||||||
|
|
||||||
|
ssh_guest "/usr/local/bin/fruix system switch $candidate_closure_path" > "$switch_status_file"
|
||||||
|
switch_current_generation=$(status_field current_generation "$switch_status_file")
|
||||||
|
switch_current_link=$(status_field current_link "$switch_status_file")
|
||||||
|
switch_current_closure=$(status_field current_closure "$switch_status_file")
|
||||||
|
switch_rollback_generation=$(status_field rollback_generation "$switch_status_file")
|
||||||
|
switch_rollback_link=$(status_field rollback_link "$switch_status_file")
|
||||||
|
switch_rollback_closure=$(status_field rollback_closure "$switch_status_file")
|
||||||
|
[ "$switch_current_generation" = 2 ] || { echo "unexpected generation after switch: $switch_current_generation" >&2; exit 1; }
|
||||||
|
[ "$switch_current_link" = generations/2 ] || { echo "unexpected current link after switch: $switch_current_link" >&2; exit 1; }
|
||||||
|
[ "$switch_current_closure" = "$candidate_closure_path" ] || { echo "unexpected current closure after switch: $switch_current_closure" >&2; exit 1; }
|
||||||
|
[ "$switch_rollback_generation" = 1 ] || { echo "unexpected rollback generation after switch: $switch_rollback_generation" >&2; exit 1; }
|
||||||
|
[ "$switch_rollback_link" = generations/1 ] || { echo "unexpected rollback link after switch: $switch_rollback_link" >&2; exit 1; }
|
||||||
|
[ "$switch_rollback_closure" = "$current_closure_path" ] || { echo "unexpected rollback closure after switch: $switch_rollback_closure" >&2; exit 1; }
|
||||||
|
[ "$(ssh_guest 'readlink /frx/var/fruix/gcroots/current-system')" = "$candidate_closure_path" ] || { echo "unexpected current-system gc root after switch" >&2; exit 1; }
|
||||||
|
[ "$(ssh_guest 'readlink /frx/var/fruix/gcroots/rollback-system')" = "$current_closure_path" ] || { echo "unexpected rollback-system gc root after switch" >&2; exit 1; }
|
||||||
|
[ "$(ssh_guest 'readlink /frx/var/fruix/gcroots/system-2')" = "$candidate_closure_path" ] || { echo "unexpected system-2 gc root after switch" >&2; exit 1; }
|
||||||
|
ssh_guest 'test -f /var/lib/fruix/system/generations/2/metadata.scm'
|
||||||
|
ssh_guest 'test -f /var/lib/fruix/system/generations/2/provenance.scm'
|
||||||
|
ssh_guest 'test -f /var/lib/fruix/system/generations/2/install.scm'
|
||||||
|
ssh_guest "cat /var/lib/fruix/system/generations/2/metadata.scm" > "$generation2_metadata_file"
|
||||||
|
ssh_guest "cat /var/lib/fruix/system/generations/2/install.scm" > "$generation2_install_file"
|
||||||
|
case "$(cat "$generation2_metadata_file")" in
|
||||||
|
*"$candidate_closure_path"*"$current_closure_path"*) : ;;
|
||||||
|
*) echo "generation 2 metadata does not record both candidate and previous closure paths" >&2; exit 1 ;;
|
||||||
|
esac
|
||||||
|
case "$(cat "$generation2_install_file")" in
|
||||||
|
*"(deployment-kind . \"switch\")"*"$candidate_closure_path"*) : ;;
|
||||||
|
*) echo "generation 2 install metadata does not record switch provenance" >&2; exit 1 ;;
|
||||||
|
esac
|
||||||
|
|
||||||
|
reboot_guest
|
||||||
|
capture_status "$boot_candidate_status_file"
|
||||||
|
boot_candidate_closure=$(ssh_guest 'readlink /run/current-system')
|
||||||
|
boot_candidate_hostname=$(ssh_guest 'hostname')
|
||||||
|
boot_candidate_generation=$(status_field current_generation "$boot_candidate_status_file")
|
||||||
|
boot_candidate_rollback_generation=$(status_field rollback_generation "$boot_candidate_status_file")
|
||||||
|
boot_candidate_rollback_closure=$(status_field rollback_closure "$boot_candidate_status_file")
|
||||||
|
[ "$boot_candidate_closure" = "$candidate_closure_path" ] || { echo "unexpected closure after switch reboot: $boot_candidate_closure" >&2; exit 1; }
|
||||||
|
[ "$boot_candidate_hostname" = "$candidate_host_name" ] || { echo "unexpected hostname after switch reboot: $boot_candidate_hostname" >&2; exit 1; }
|
||||||
|
[ "$boot_candidate_generation" = 2 ] || { echo "unexpected generation after switch reboot: $boot_candidate_generation" >&2; exit 1; }
|
||||||
|
[ "$boot_candidate_rollback_generation" = 1 ] || { echo "unexpected rollback generation after switch reboot: $boot_candidate_rollback_generation" >&2; exit 1; }
|
||||||
|
[ "$boot_candidate_rollback_closure" = "$current_closure_path" ] || { echo "unexpected rollback closure after switch reboot: $boot_candidate_rollback_closure" >&2; exit 1; }
|
||||||
|
|
||||||
|
ssh_guest '/usr/local/bin/fruix system rollback' > "$rollback_status_file"
|
||||||
|
rollback_current_generation=$(status_field current_generation "$rollback_status_file")
|
||||||
|
rollback_current_link=$(status_field current_link "$rollback_status_file")
|
||||||
|
rollback_current_closure=$(status_field current_closure "$rollback_status_file")
|
||||||
|
rollback_rollback_generation=$(status_field rollback_generation "$rollback_status_file")
|
||||||
|
rollback_rollback_link=$(status_field rollback_link "$rollback_status_file")
|
||||||
|
rollback_rollback_closure=$(status_field rollback_closure "$rollback_status_file")
|
||||||
|
[ "$rollback_current_generation" = 1 ] || { echo "unexpected generation after rollback: $rollback_current_generation" >&2; exit 1; }
|
||||||
|
[ "$rollback_current_link" = generations/1 ] || { echo "unexpected current link after rollback: $rollback_current_link" >&2; exit 1; }
|
||||||
|
[ "$rollback_current_closure" = "$current_closure_path" ] || { echo "unexpected current closure after rollback: $rollback_current_closure" >&2; exit 1; }
|
||||||
|
[ "$rollback_rollback_generation" = 2 ] || { echo "unexpected rollback generation after rollback: $rollback_rollback_generation" >&2; exit 1; }
|
||||||
|
[ "$rollback_rollback_link" = generations/2 ] || { echo "unexpected rollback link after rollback: $rollback_rollback_link" >&2; exit 1; }
|
||||||
|
[ "$rollback_rollback_closure" = "$candidate_closure_path" ] || { echo "unexpected rollback closure after rollback: $rollback_rollback_closure" >&2; exit 1; }
|
||||||
|
[ "$(ssh_guest 'readlink /frx/var/fruix/gcroots/current-system')" = "$current_closure_path" ] || { echo "unexpected current-system gc root after rollback" >&2; exit 1; }
|
||||||
|
[ "$(ssh_guest 'readlink /frx/var/fruix/gcroots/rollback-system')" = "$candidate_closure_path" ] || { echo "unexpected rollback-system gc root after rollback" >&2; exit 1; }
|
||||||
|
|
||||||
|
reboot_guest
|
||||||
|
capture_status "$boot_rollback_status_file"
|
||||||
|
boot_rollback_closure=$(ssh_guest 'readlink /run/current-system')
|
||||||
|
boot_rollback_hostname=$(ssh_guest 'hostname')
|
||||||
|
boot_rollback_generation=$(status_field current_generation "$boot_rollback_status_file")
|
||||||
|
boot_rollback_rollback_generation=$(status_field rollback_generation "$boot_rollback_status_file")
|
||||||
|
boot_rollback_rollback_closure=$(status_field rollback_closure "$boot_rollback_status_file")
|
||||||
|
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')
|
||||||
|
activate_log=$(ssh_guest 'cat /var/log/fruix-activate.log 2>/dev/null || true' | tr '\n' ' ')
|
||||||
|
[ "$boot_rollback_closure" = "$current_closure_path" ] || { echo "unexpected closure after rollback reboot: $boot_rollback_closure" >&2; exit 1; }
|
||||||
|
[ "$boot_rollback_hostname" = "$current_host_name" ] || { echo "unexpected hostname after rollback reboot: $boot_rollback_hostname" >&2; exit 1; }
|
||||||
|
[ "$boot_rollback_generation" = 1 ] || { echo "unexpected generation after rollback reboot: $boot_rollback_generation" >&2; exit 1; }
|
||||||
|
[ "$boot_rollback_rollback_generation" = 2 ] || { echo "unexpected rollback generation after rollback reboot: $boot_rollback_rollback_generation" >&2; exit 1; }
|
||||||
|
[ "$boot_rollback_rollback_closure" = "$candidate_closure_path" ] || { echo "unexpected rollback closure after rollback reboot: $boot_rollback_rollback_closure" >&2; exit 1; }
|
||||||
|
[ "$shepherd_status" = running ] || { echo "fruix-shepherd is not running after rollback reboot" >&2; exit 1; }
|
||||||
|
[ "$sshd_status" = running ] || { echo "sshd is not running after rollback reboot" >&2; exit 1; }
|
||||||
|
case "$activate_log" in
|
||||||
|
*fruix-activate:done*) : ;;
|
||||||
|
*) echo "activation log does not show success after rollback workflow" >&2; exit 1 ;;
|
||||||
|
esac
|
||||||
|
|
||||||
|
cat >"$metadata_file" <<EOF
|
||||||
|
workdir=$workdir
|
||||||
|
current_os_file=$current_os_file
|
||||||
|
candidate_os_file=$candidate_os_file
|
||||||
|
target_image=$target_image
|
||||||
|
current_closure_path=$current_closure_path
|
||||||
|
candidate_closure_path=$candidate_closure_path
|
||||||
|
current_host_name=$current_host_name
|
||||||
|
candidate_host_name=$candidate_host_name
|
||||||
|
candidate_store_item_count=$candidate_store_item_count
|
||||||
|
candidate_store_items=$candidate_store_items
|
||||||
|
stage_log=$stage_log
|
||||||
|
serial_log=$serial_log
|
||||||
|
install_metadata_path=$install_metadata_path
|
||||||
|
materialized_source_store=$materialized_source_store
|
||||||
|
switch_status_file=$switch_status_file
|
||||||
|
rollback_status_file=$rollback_status_file
|
||||||
|
boot_current_status_file=$boot_current_status_file
|
||||||
|
boot_candidate_status_file=$boot_candidate_status_file
|
||||||
|
boot_rollback_status_file=$boot_rollback_status_file
|
||||||
|
generation2_metadata_file=$generation2_metadata_file
|
||||||
|
generation2_install_file=$generation2_install_file
|
||||||
|
final_current_generation=$boot_rollback_generation
|
||||||
|
final_current_closure=$boot_rollback_closure
|
||||||
|
final_rollback_generation=$boot_rollback_rollback_generation
|
||||||
|
final_rollback_closure=$boot_rollback_rollback_closure
|
||||||
|
shepherd_status=$shepherd_status
|
||||||
|
sshd_status=$sshd_status
|
||||||
|
boot_backend=qemu-uefi-tcg
|
||||||
|
installed_system_switch=ok
|
||||||
|
installed_system_rollback=ok
|
||||||
|
EOF
|
||||||
|
|
||||||
|
if [ -n "$metadata_target" ]; then
|
||||||
|
mkdir -p "$(dirname "$metadata_target")"
|
||||||
|
cp "$metadata_file" "$metadata_target"
|
||||||
|
fi
|
||||||
|
|
||||||
|
printf 'PASS phase19-installed-system-rollback-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"
|
||||||
260
tests/system/run-phase20-development-environment-xcpng.sh
Executable file
260
tests/system/run-phase20-development-environment-xcpng.sh
Executable file
@@ -0,0 +1,260 @@
|
|||||||
|
#!/bin/sh
|
||||||
|
set -eu
|
||||||
|
|
||||||
|
repo_root=${PROJECT_ROOT:-$(pwd)}
|
||||||
|
os_template=${OS_TEMPLATE:-$repo_root/tests/system/phase20-development-operating-system.scm.in}
|
||||||
|
system_name=${SYSTEM_NAME:-phase20-operating-system}
|
||||||
|
root_size=${ROOT_SIZE:-8g}
|
||||||
|
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}
|
||||||
|
cleanup=0
|
||||||
|
|
||||||
|
if [ -n "${WORKDIR:-}" ]; then
|
||||||
|
workdir=$WORKDIR
|
||||||
|
mkdir -p "$workdir"
|
||||||
|
else
|
||||||
|
workdir=$(mktemp -d /tmp/fruix-phase20-development-xcpng.XXXXXX)
|
||||||
|
cleanup=1
|
||||||
|
fi
|
||||||
|
if [ "${KEEP_WORKDIR:-0}" -eq 1 ]; then
|
||||||
|
cleanup=0
|
||||||
|
fi
|
||||||
|
|
||||||
|
inner_metadata=$workdir/phase20-development-inner-metadata.txt
|
||||||
|
metadata_file=$workdir/phase20-development-environment-xcpng-metadata.txt
|
||||||
|
|
||||||
|
action_cleanup() {
|
||||||
|
if [ "$cleanup" -eq 1 ]; then
|
||||||
|
rm -rf "$workdir"
|
||||||
|
fi
|
||||||
|
}
|
||||||
|
trap action_cleanup EXIT INT TERM
|
||||||
|
|
||||||
|
KEEP_WORKDIR=1 WORKDIR="$workdir/inner" METADATA_OUT="$inner_metadata" \
|
||||||
|
ROOT_AUTHORIZED_KEY_FILE="$root_authorized_key_file" \
|
||||||
|
ROOT_SSH_PRIVATE_KEY_FILE="$root_ssh_private_key_file" \
|
||||||
|
OS_TEMPLATE="$os_template" SYSTEM_NAME="$system_name" ROOT_SIZE="$root_size" \
|
||||||
|
"$repo_root/tests/system/run-phase11-shepherd-pid1-xcpng.sh"
|
||||||
|
|
||||||
|
phase8_metadata=$(sed -n 's/^phase8_metadata=//p' "$inner_metadata")
|
||||||
|
closure_path=$(sed -n 's/^closure_path=//p' "$inner_metadata")
|
||||||
|
closure_base=$(sed -n 's/^closure_base=//p' "$inner_metadata")
|
||||||
|
guest_ip=$(sed -n 's/^guest_ip=//p' "$inner_metadata")
|
||||||
|
vm_id=$(sed -n 's/^vm_id=//p' "$inner_metadata")
|
||||||
|
vdi_id=$(sed -n 's/^vdi_id=//p' "$inner_metadata")
|
||||||
|
shepherd_pid=$(sed -n 's/^shepherd_pid=//p' "$inner_metadata")
|
||||||
|
sshd_status=$(sed -n 's/^sshd_status=//p' "$inner_metadata")
|
||||||
|
compat_prefix_shims=$(sed -n 's/^compat_prefix_shims=//p' "$inner_metadata")
|
||||||
|
guile_module_smoke=$(sed -n 's/^guile_module_smoke=//p' "$inner_metadata")
|
||||||
|
activate_log=$(sed -n 's/^activate_log=//p' "$inner_metadata")
|
||||||
|
|
||||||
|
development_profile_path=$closure_path/development-profile
|
||||||
|
build_profile_path=$closure_path/build-profile
|
||||||
|
runtime_profile_path=$closure_path/profile
|
||||||
|
development_env_script=$closure_path/usr/local/bin/fruix-development-environment
|
||||||
|
build_env_script=$closure_path/usr/local/bin/fruix-build-environment
|
||||||
|
|
||||||
|
[ "$shepherd_pid" = 1 ] || { echo "shepherd was not PID 1" >&2; exit 1; }
|
||||||
|
[ "$sshd_status" = running ] || { echo "sshd is not running" >&2; exit 1; }
|
||||||
|
[ "$compat_prefix_shims" = absent ] || { echo "compatibility prefix shims reappeared" >&2; exit 1; }
|
||||||
|
[ "$guile_module_smoke" = ok ] || { echo "guest Guile module smoke failed" >&2; exit 1; }
|
||||||
|
case "$activate_log" in
|
||||||
|
*fruix-activate:done*) : ;;
|
||||||
|
*) echo "activation log does not show success" >&2; exit 1 ;;
|
||||||
|
esac
|
||||||
|
|
||||||
|
for path in \
|
||||||
|
"$development_profile_path/bin/cc" \
|
||||||
|
"$development_profile_path/bin/c++" \
|
||||||
|
"$development_profile_path/bin/ar" \
|
||||||
|
"$development_profile_path/usr/include/sys/param.h" \
|
||||||
|
"$development_profile_path/usr/share/mk/bsd.prog.mk" \
|
||||||
|
"$build_profile_path/bin/cc" \
|
||||||
|
"$build_profile_path/usr/include/sys/param.h" \
|
||||||
|
"$build_profile_path/usr/share/mk/bsd.prog.mk" \
|
||||||
|
"$development_env_script" \
|
||||||
|
"$build_env_script"
|
||||||
|
do
|
||||||
|
[ -e "$path" ] || {
|
||||||
|
echo "required development environment path missing: $path" >&2
|
||||||
|
exit 1
|
||||||
|
}
|
||||||
|
done
|
||||||
|
[ ! -e "$runtime_profile_path/include" ] || { echo "runtime profile unexpectedly contains headers" >&2; exit 1; }
|
||||||
|
[ ! -e "$runtime_profile_path/usr/share/mk" ] || { echo "runtime profile unexpectedly contains /usr/share/mk" >&2; exit 1; }
|
||||||
|
[ ! -e "$runtime_profile_path/bin/cc" ] || { echo "runtime profile unexpectedly contains cc" >&2; exit 1; }
|
||||||
|
|
||||||
|
ssh_guest() {
|
||||||
|
ssh -i "$root_ssh_private_key_file" \
|
||||||
|
-o BatchMode=yes \
|
||||||
|
-o StrictHostKeyChecking=no \
|
||||||
|
-o UserKnownHostsFile=/dev/null \
|
||||||
|
-o ConnectTimeout=5 \
|
||||||
|
root@"$guest_ip" "$@"
|
||||||
|
}
|
||||||
|
|
||||||
|
guest_dev_metadata=$(ssh -i "$root_ssh_private_key_file" \
|
||||||
|
-o BatchMode=yes \
|
||||||
|
-o StrictHostKeyChecking=no \
|
||||||
|
-o UserKnownHostsFile=/dev/null \
|
||||||
|
-o ConnectTimeout=5 \
|
||||||
|
root@"$guest_ip" 'sh -s' <<'EOF'
|
||||||
|
set -eu
|
||||||
|
[ -x /usr/local/bin/fruix-development-environment ]
|
||||||
|
[ -x /usr/local/bin/fruix-build-environment ]
|
||||||
|
[ -L /run/current-development ]
|
||||||
|
[ "$(readlink /run/current-development)" = "/run/current-system/development-profile" ]
|
||||||
|
[ -L /run/current-build ]
|
||||||
|
[ "$(readlink /run/current-build)" = "/run/current-system/build-profile" ]
|
||||||
|
exports=$(/usr/local/bin/fruix-development-environment)
|
||||||
|
printf '%s\n' "$exports" | grep '^export FRUIX_DEVELOPMENT_PROFILE="/run/current-system/development-profile"$' >/dev/null
|
||||||
|
printf '%s\n' "$exports" | grep '^export MAKEFLAGS="-m /run/current-system/development-profile/usr/share/mk"$' >/dev/null
|
||||||
|
eval "$exports"
|
||||||
|
[ -d "$FRUIX_DEVELOPMENT_PROFILE" ]
|
||||||
|
[ -x "$FRUIX_CC" ]
|
||||||
|
[ -x "$FRUIX_CXX" ]
|
||||||
|
[ -x "$FRUIX_AR" ]
|
||||||
|
[ -f "$FRUIX_DEVELOPMENT_INCLUDE/sys/param.h" ]
|
||||||
|
[ -f "$FRUIX_DEVELOPMENT_SHARE_MK/bsd.prog.mk" ]
|
||||||
|
cc_version=$($FRUIX_CC --version | awk 'NR==1 { print; exit }')
|
||||||
|
build_exports=$(/usr/local/bin/fruix-build-environment)
|
||||||
|
printf '%s\n' "$build_exports" | grep '^unset MAKEOBJDIRPREFIX MAKEFLAGS CC CXX AR RANLIB NM CPPFLAGS CFLAGS CXXFLAGS LDFLAGS$' >/dev/null
|
||||||
|
printf '%s\n' "$build_exports" | grep '^export FRUIX_BUILD_PROFILE="/run/current-system/build-profile"$' >/dev/null
|
||||||
|
eval "$build_exports"
|
||||||
|
[ -d "$FRUIX_BUILD_PROFILE" ]
|
||||||
|
[ -f "$FRUIX_BUILD_INCLUDE/sys/param.h" ]
|
||||||
|
[ -f "$FRUIX_BUILD_SHARE_MK/bsd.prog.mk" ]
|
||||||
|
[ "${MAKEFLAGS-unset}" = unset ]
|
||||||
|
[ "${CPPFLAGS-unset}" = unset ]
|
||||||
|
[ "${CFLAGS-unset}" = unset ]
|
||||||
|
[ "${LDFLAGS-unset}" = unset ]
|
||||||
|
[ "$FRUIX_BMAKE" = make ]
|
||||||
|
build_profile_value=$FRUIX_BUILD_PROFILE
|
||||||
|
eval "$exports"
|
||||||
|
tmp=/tmp/fruix-phase20-development-env
|
||||||
|
rm -rf "$tmp"
|
||||||
|
mkdir -p "$tmp/direct" "$tmp/mk"
|
||||||
|
cat > "$tmp/direct/hello.c" <<'EOS'
|
||||||
|
#include <stdio.h>
|
||||||
|
int main(void){puts("hello-from-direct-dev-env");return 0;}
|
||||||
|
EOS
|
||||||
|
$FRUIX_CC $CPPFLAGS $LDFLAGS "$tmp/direct/hello.c" -o "$tmp/direct/hello"
|
||||||
|
hello_direct=$($tmp/direct/hello)
|
||||||
|
cat > "$tmp/mk/hello.c" <<'EOS'
|
||||||
|
#include <stdio.h>
|
||||||
|
int main(void){puts("hello-from-make-dev-env");return 0;}
|
||||||
|
EOS
|
||||||
|
cat > "$tmp/mk/Makefile" <<'EOS'
|
||||||
|
PROG=hello
|
||||||
|
SRCS=hello.c
|
||||||
|
NO_MAN=yes
|
||||||
|
.include <bsd.prog.mk>
|
||||||
|
EOS
|
||||||
|
cat > "$tmp/mk/hello.1" <<'EOS'
|
||||||
|
.Dd April 5, 2026
|
||||||
|
.Dt HELLO 1
|
||||||
|
.Sh NAME
|
||||||
|
.Nm hello
|
||||||
|
.Nd phase20 development environment smoke binary
|
||||||
|
EOS
|
||||||
|
cd "$tmp/mk"
|
||||||
|
make clean >/dev/null 2>&1 || true
|
||||||
|
make > "$tmp/mk/make.log" 2>&1
|
||||||
|
hello_make=$(./hello)
|
||||||
|
make_log_tail=$(tail -n 20 "$tmp/mk/make.log" | tr '\n' ' ')
|
||||||
|
exports_flat=$(printf '%s' "$exports" | tr '\n' ' ')
|
||||||
|
printf 'development_profile=%s\n' "$FRUIX_DEVELOPMENT_PROFILE"
|
||||||
|
printf 'build_profile=%s\n' "$build_profile_value"
|
||||||
|
printf 'cc_version=%s\n' "$cc_version"
|
||||||
|
printf 'hello_direct=%s\n' "$hello_direct"
|
||||||
|
printf 'hello_make=%s\n' "$hello_make"
|
||||||
|
build_exports_flat=$(printf '%s' "$build_exports" | tr '\n' ' ')
|
||||||
|
printf 'exports=%s\n' "$exports_flat"
|
||||||
|
printf 'build_exports=%s\n' "$build_exports_flat"
|
||||||
|
printf 'make_log_tail=%s\n' "$make_log_tail"
|
||||||
|
EOF
|
||||||
|
)
|
||||||
|
|
||||||
|
development_profile=$(printf '%s\n' "$guest_dev_metadata" | sed -n 's/^development_profile=//p')
|
||||||
|
build_profile=$(printf '%s\n' "$guest_dev_metadata" | sed -n 's/^build_profile=//p')
|
||||||
|
cc_version=$(printf '%s\n' "$guest_dev_metadata" | sed -n 's/^cc_version=//p')
|
||||||
|
hello_direct=$(printf '%s\n' "$guest_dev_metadata" | sed -n 's/^hello_direct=//p')
|
||||||
|
hello_make=$(printf '%s\n' "$guest_dev_metadata" | sed -n 's/^hello_make=//p')
|
||||||
|
development_exports=$(printf '%s\n' "$guest_dev_metadata" | sed -n 's/^exports=//p')
|
||||||
|
build_exports=$(printf '%s\n' "$guest_dev_metadata" | sed -n 's/^build_exports=//p')
|
||||||
|
make_log_tail=$(printf '%s\n' "$guest_dev_metadata" | sed -n 's/^make_log_tail=//p')
|
||||||
|
|
||||||
|
[ "$development_profile" = "/run/current-system/development-profile" ] || {
|
||||||
|
echo "unexpected guest development profile path: $development_profile" >&2
|
||||||
|
exit 1
|
||||||
|
}
|
||||||
|
[ "$build_profile" = "/run/current-system/build-profile" ] || {
|
||||||
|
echo "unexpected guest build profile path: $build_profile" >&2
|
||||||
|
exit 1
|
||||||
|
}
|
||||||
|
case "$cc_version" in
|
||||||
|
*"FreeBSD clang version"*) : ;;
|
||||||
|
*) echo "unexpected cc version output: $cc_version" >&2; exit 1 ;;
|
||||||
|
esac
|
||||||
|
[ "$hello_direct" = hello-from-direct-dev-env ] || {
|
||||||
|
echo "unexpected direct compile output: $hello_direct" >&2
|
||||||
|
exit 1
|
||||||
|
}
|
||||||
|
[ "$hello_make" = hello-from-make-dev-env ] || {
|
||||||
|
echo "unexpected make-based compile output: $hello_make" >&2
|
||||||
|
exit 1
|
||||||
|
}
|
||||||
|
case "$development_exports" in
|
||||||
|
*'export FRUIX_CC="/run/current-system/development-profile/bin/cc"'*) : ;;
|
||||||
|
*) echo "development environment exports do not include FRUIX_CC" >&2; exit 1 ;;
|
||||||
|
esac
|
||||||
|
case "$build_exports" in
|
||||||
|
*'export FRUIX_BUILD_PROFILE="/run/current-system/build-profile"'*) : ;;
|
||||||
|
*) echo "build environment exports do not include FRUIX_BUILD_PROFILE" >&2; exit 1 ;;
|
||||||
|
esac
|
||||||
|
|
||||||
|
cat >"$metadata_file" <<EOF
|
||||||
|
workdir=$workdir
|
||||||
|
inner_metadata=$inner_metadata
|
||||||
|
phase8_metadata=$phase8_metadata
|
||||||
|
closure_path=$closure_path
|
||||||
|
closure_base=$closure_base
|
||||||
|
vm_id=$vm_id
|
||||||
|
vdi_id=$vdi_id
|
||||||
|
guest_ip=$guest_ip
|
||||||
|
root_size=$root_size
|
||||||
|
development_profile_path=$development_profile_path
|
||||||
|
build_profile_path=$build_profile_path
|
||||||
|
development_env_script=$development_env_script
|
||||||
|
build_env_script=$build_env_script
|
||||||
|
shepherd_pid=$shepherd_pid
|
||||||
|
sshd_status=$sshd_status
|
||||||
|
compat_prefix_shims=$compat_prefix_shims
|
||||||
|
guile_module_smoke=$guile_module_smoke
|
||||||
|
development_profile_guest=$development_profile
|
||||||
|
build_profile_guest=$build_profile
|
||||||
|
cc_version=$cc_version
|
||||||
|
hello_direct=$hello_direct
|
||||||
|
hello_make=$hello_make
|
||||||
|
development_exports=$development_exports
|
||||||
|
build_exports=$build_exports
|
||||||
|
make_log_tail=$make_log_tail
|
||||||
|
boot_backend=xcp-ng-xo-cli
|
||||||
|
init_mode=shepherd-pid1
|
||||||
|
development_environment=ok
|
||||||
|
EOF
|
||||||
|
|
||||||
|
if [ -n "$metadata_target" ]; then
|
||||||
|
mkdir -p "$(dirname "$metadata_target")"
|
||||||
|
cp "$metadata_file" "$metadata_target"
|
||||||
|
fi
|
||||||
|
|
||||||
|
printf 'PASS phase20-development-environment-xcpng\n'
|
||||||
|
printf 'Work directory: %s\n' "$workdir"
|
||||||
|
printf 'Metadata file: %s\n' "$metadata_file"
|
||||||
|
if [ -n "$metadata_target" ]; then
|
||||||
|
printf 'Copied metadata to: %s\n' "$metadata_target"
|
||||||
|
fi
|
||||||
|
printf '%s\n' '--- metadata ---'
|
||||||
|
cat "$metadata_file"
|
||||||
239
tests/system/run-phase20-host-initiated-native-build-store-promotion-xcpng.sh
Executable file
239
tests/system/run-phase20-host-initiated-native-build-store-promotion-xcpng.sh
Executable file
@@ -0,0 +1,239 @@
|
|||||||
|
#!/bin/sh
|
||||||
|
set -eu
|
||||||
|
|
||||||
|
repo_root=${PROJECT_ROOT:-$(pwd)}
|
||||||
|
os_template=${OS_TEMPLATE:-$repo_root/tests/system/phase20-development-operating-system.scm.in}
|
||||||
|
system_name=${SYSTEM_NAME:-phase20-operating-system}
|
||||||
|
root_size=${ROOT_SIZE:-20g}
|
||||||
|
store_dir=${STORE_DIR:-/frx/store}
|
||||||
|
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}
|
||||||
|
cleanup=0
|
||||||
|
|
||||||
|
if [ -n "${WORKDIR:-}" ]; then
|
||||||
|
workdir=$WORKDIR
|
||||||
|
mkdir -p "$workdir"
|
||||||
|
else
|
||||||
|
workdir=$(mktemp -d /tmp/fruix-phase20-host-initiated-native-build-store-promotion-xcpng.XXXXXX)
|
||||||
|
cleanup=1
|
||||||
|
fi
|
||||||
|
if [ "${KEEP_WORKDIR:-0}" -eq 1 ]; then
|
||||||
|
cleanup=0
|
||||||
|
fi
|
||||||
|
|
||||||
|
inner_metadata=$workdir/phase20-host-initiated-native-build-store-promotion-inner-metadata.txt
|
||||||
|
promotion_out=$workdir/native-build-promote.txt
|
||||||
|
metadata_file=$workdir/phase20-host-initiated-native-build-store-promotion-xcpng-metadata.txt
|
||||||
|
import_root=$workdir/import
|
||||||
|
|
||||||
|
action_cleanup() {
|
||||||
|
if [ "$cleanup" -eq 1 ]; then
|
||||||
|
rm -rf "$workdir"
|
||||||
|
fi
|
||||||
|
}
|
||||||
|
trap action_cleanup EXIT INT TERM
|
||||||
|
|
||||||
|
KEEP_WORKDIR=1 WORKDIR="$workdir/inner" METADATA_OUT="$inner_metadata" \
|
||||||
|
ROOT_AUTHORIZED_KEY_FILE="$root_authorized_key_file" \
|
||||||
|
ROOT_SSH_PRIVATE_KEY_FILE="$root_ssh_private_key_file" \
|
||||||
|
OS_TEMPLATE="$os_template" SYSTEM_NAME="$system_name" ROOT_SIZE="$root_size" \
|
||||||
|
"$repo_root/tests/system/run-phase20-host-initiated-native-build-xcpng.sh"
|
||||||
|
|
||||||
|
guest_ip=$(sed -n 's/^guest_ip=//p' "$inner_metadata")
|
||||||
|
vm_id=$(sed -n 's/^vm_id=//p' "$inner_metadata")
|
||||||
|
vdi_id=$(sed -n 's/^vdi_id=//p' "$inner_metadata")
|
||||||
|
closure_path=$(sed -n 's/^closure_path=//p' "$inner_metadata")
|
||||||
|
closure_base=$(sed -n 's/^closure_base=//p' "$inner_metadata")
|
||||||
|
run_id=$(sed -n 's/^run_id=//p' "$inner_metadata")
|
||||||
|
source_store=$(sed -n 's/^source_store=//p' "$inner_metadata")
|
||||||
|
result_root=$(sed -n 's/^result_root=//p' "$inner_metadata")
|
||||||
|
promotion_file=$(sed -n 's/^promotion_file=//p' "$inner_metadata")
|
||||||
|
world_artifact=$(sed -n 's/^world_artifact=//p' "$inner_metadata")
|
||||||
|
kernel_artifact=$(sed -n 's/^kernel_artifact=//p' "$inner_metadata")
|
||||||
|
headers_artifact=$(sed -n 's/^headers_artifact=//p' "$inner_metadata")
|
||||||
|
bootloader_artifact=$(sed -n 's/^bootloader_artifact=//p' "$inner_metadata")
|
||||||
|
sha_kernel=$(sed -n 's/^sha_kernel=//p' "$inner_metadata")
|
||||||
|
sha_loader=$(sed -n 's/^sha_loader=//p' "$inner_metadata")
|
||||||
|
sha_param=$(sed -n 's/^sha_param=//p' "$inner_metadata")
|
||||||
|
|
||||||
|
ssh_guest() {
|
||||||
|
ssh -i "$root_ssh_private_key_file" \
|
||||||
|
-o BatchMode=yes \
|
||||||
|
-o StrictHostKeyChecking=no \
|
||||||
|
-o UserKnownHostsFile=/dev/null \
|
||||||
|
-o ConnectTimeout=5 \
|
||||||
|
root@"$guest_ip" "$@"
|
||||||
|
}
|
||||||
|
|
||||||
|
mkdir -p "$import_root"
|
||||||
|
result_base=$(basename "$result_root")
|
||||||
|
ssh -i "$root_ssh_private_key_file" \
|
||||||
|
-o BatchMode=yes \
|
||||||
|
-o StrictHostKeyChecking=no \
|
||||||
|
-o UserKnownHostsFile=/dev/null \
|
||||||
|
-o ConnectTimeout=5 \
|
||||||
|
root@"$guest_ip" "tar -C '$(dirname "$result_root")' -cf - '$result_base'" | tar -C "$import_root" -xf -
|
||||||
|
local_result_root=$import_root/$result_base
|
||||||
|
[ -d "$local_result_root" ] || { echo "failed to import native build result root" >&2; exit 1; }
|
||||||
|
[ -f "$local_result_root/promotion.scm" ] || { echo "imported result is missing promotion.scm" >&2; exit 1; }
|
||||||
|
[ -f "$local_result_root/artifacts/world/bin/sh" ] || { echo "imported result is missing world artifact" >&2; exit 1; }
|
||||||
|
[ -f "$local_result_root/artifacts/kernel/boot/kernel/kernel" ] || { echo "imported result is missing kernel artifact" >&2; exit 1; }
|
||||||
|
[ -f "$local_result_root/artifacts/headers/usr/include/sys/param.h" ] || { echo "imported result is missing headers artifact" >&2; exit 1; }
|
||||||
|
[ -f "$local_result_root/artifacts/bootloader/boot/loader.efi" ] || { echo "imported result is missing bootloader artifact" >&2; exit 1; }
|
||||||
|
|
||||||
|
action_env() {
|
||||||
|
sudo env \
|
||||||
|
HOME="$HOME" \
|
||||||
|
GUILE_AUTO_COMPILE=0 \
|
||||||
|
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 "$repo_root/bin/fruix" native-build promote "$local_result_root" --store "$store_dir" >"$promotion_out"
|
||||||
|
|
||||||
|
field() {
|
||||||
|
sed -n "s/^$1=//p" "$promotion_out" | tail -n 1
|
||||||
|
}
|
||||||
|
|
||||||
|
executor_kind=$(field executor_kind)
|
||||||
|
executor_name=$(field executor_name)
|
||||||
|
executor_version=$(field executor_version)
|
||||||
|
result_store=$(field result_store)
|
||||||
|
result_metadata_file=$(field result_metadata_file)
|
||||||
|
artifact_store_count=$(field artifact_store_count)
|
||||||
|
artifact_stores=$(field artifact_stores)
|
||||||
|
world_store=$(field world_store)
|
||||||
|
kernel_store=$(field kernel_store)
|
||||||
|
headers_store=$(field headers_store)
|
||||||
|
bootloader_store=$(field bootloader_store)
|
||||||
|
|
||||||
|
[ "$executor_kind" = ssh-guest ] || { echo "unexpected executor kind: $executor_kind" >&2; exit 1; }
|
||||||
|
[ "$executor_name" = ssh-guest ] || { echo "unexpected executor name: $executor_name" >&2; exit 1; }
|
||||||
|
[ "$executor_version" = 1 ] || { echo "unexpected executor version: $executor_version" >&2; exit 1; }
|
||||||
|
[ "$artifact_store_count" = 4 ] || { echo "unexpected artifact store count: $artifact_store_count" >&2; exit 1; }
|
||||||
|
case "$result_store" in
|
||||||
|
/frx/store/*-fruix-native-build-result-*-ssh-guest) : ;;
|
||||||
|
*) echo "unexpected result store path: $result_store" >&2; exit 1 ;;
|
||||||
|
esac
|
||||||
|
case "$world_store" in
|
||||||
|
/frx/store/*-fruix-native-world-*-ssh-guest) : ;;
|
||||||
|
*) echo "unexpected world store path: $world_store" >&2; exit 1 ;;
|
||||||
|
esac
|
||||||
|
case "$kernel_store" in
|
||||||
|
/frx/store/*-fruix-native-kernel-*-ssh-guest) : ;;
|
||||||
|
*) echo "unexpected kernel store path: $kernel_store" >&2; exit 1 ;;
|
||||||
|
esac
|
||||||
|
case "$headers_store" in
|
||||||
|
/frx/store/*-fruix-native-headers-*-ssh-guest) : ;;
|
||||||
|
*) echo "unexpected headers store path: $headers_store" >&2; exit 1 ;;
|
||||||
|
esac
|
||||||
|
case "$bootloader_store" in
|
||||||
|
/frx/store/*-fruix-native-bootloader-*-ssh-guest) : ;;
|
||||||
|
*) echo "unexpected bootloader store path: $bootloader_store" >&2; exit 1 ;;
|
||||||
|
esac
|
||||||
|
|
||||||
|
[ -f "$result_metadata_file" ] || { echo "missing result metadata file: $result_metadata_file" >&2; exit 1; }
|
||||||
|
[ -f "$world_store/.fruix-native-build-object.scm" ] || { echo "missing world store metadata" >&2; exit 1; }
|
||||||
|
[ -f "$kernel_store/.fruix-native-build-object.scm" ] || { echo "missing kernel store metadata" >&2; exit 1; }
|
||||||
|
[ -f "$headers_store/.fruix-native-build-object.scm" ] || { echo "missing headers store metadata" >&2; exit 1; }
|
||||||
|
[ -f "$bootloader_store/.fruix-native-build-object.scm" ] || { echo "missing bootloader store metadata" >&2; exit 1; }
|
||||||
|
[ -L "$result_store/artifacts/world" ] || { echo "missing promoted world artifact link" >&2; exit 1; }
|
||||||
|
[ -L "$result_store/artifacts/kernel" ] || { echo "missing promoted kernel artifact link" >&2; exit 1; }
|
||||||
|
[ -L "$result_store/artifacts/headers" ] || { echo "missing promoted headers artifact link" >&2; exit 1; }
|
||||||
|
[ -L "$result_store/artifacts/bootloader" ] || { echo "missing promoted bootloader artifact link" >&2; exit 1; }
|
||||||
|
[ "$(readlink "$result_store/artifacts/world")" = "$world_store" ] || { echo "world artifact link mismatch" >&2; exit 1; }
|
||||||
|
[ "$(readlink "$result_store/artifacts/kernel")" = "$kernel_store" ] || { echo "kernel artifact link mismatch" >&2; exit 1; }
|
||||||
|
[ "$(readlink "$result_store/artifacts/headers")" = "$headers_store" ] || { echo "headers artifact link mismatch" >&2; exit 1; }
|
||||||
|
[ "$(readlink "$result_store/artifacts/bootloader")" = "$bootloader_store" ] || { echo "bootloader artifact link mismatch" >&2; exit 1; }
|
||||||
|
[ -f "$world_store/bin/sh" ] || { echo "promoted world store missing /bin/sh" >&2; exit 1; }
|
||||||
|
[ -f "$kernel_store/boot/kernel/kernel" ] || { echo "promoted kernel store missing kernel" >&2; exit 1; }
|
||||||
|
[ -f "$headers_store/usr/include/sys/param.h" ] || { echo "promoted headers store missing param.h" >&2; exit 1; }
|
||||||
|
[ -f "$bootloader_store/boot/loader.efi" ] || { echo "promoted bootloader store missing loader.efi" >&2; exit 1; }
|
||||||
|
|
||||||
|
promoted_kernel_sha=$(sha256 -q "$kernel_store/boot/kernel/kernel")
|
||||||
|
promoted_loader_sha=$(sha256 -q "$bootloader_store/boot/loader.efi")
|
||||||
|
promoted_param_sha=$(sha256 -q "$headers_store/usr/include/sys/param.h")
|
||||||
|
[ "$promoted_kernel_sha" = "$sha_kernel" ] || { echo "kernel sha mismatch after promotion" >&2; exit 1; }
|
||||||
|
[ "$promoted_loader_sha" = "$sha_loader" ] || { echo "loader sha mismatch after promotion" >&2; exit 1; }
|
||||||
|
[ "$promoted_param_sha" = "$sha_param" ] || { echo "param.h sha mismatch after promotion" >&2; exit 1; }
|
||||||
|
|
||||||
|
grep -F '(executor-kind . ssh-guest)' "$result_metadata_file" >/dev/null || {
|
||||||
|
echo "result metadata file is missing ssh-guest executor kind" >&2
|
||||||
|
exit 1
|
||||||
|
}
|
||||||
|
grep -F '(executor-name . "ssh-guest")' "$result_metadata_file" >/dev/null || {
|
||||||
|
echo "result metadata file is missing ssh-guest executor name" >&2
|
||||||
|
exit 1
|
||||||
|
}
|
||||||
|
grep -F "$source_store" "$result_metadata_file" >/dev/null || {
|
||||||
|
echo "result metadata file is missing source store provenance" >&2
|
||||||
|
exit 1
|
||||||
|
}
|
||||||
|
grep -F '(artifact-kind . kernel)' "$kernel_store/.fruix-native-build-object.scm" >/dev/null || {
|
||||||
|
echo "kernel store metadata is missing artifact kind" >&2
|
||||||
|
exit 1
|
||||||
|
}
|
||||||
|
grep -F '(artifact-kind . world)' "$world_store/.fruix-native-build-object.scm" >/dev/null || {
|
||||||
|
echo "world store metadata is missing artifact kind" >&2
|
||||||
|
exit 1
|
||||||
|
}
|
||||||
|
|
||||||
|
cat >"$metadata_file" <<EOF
|
||||||
|
workdir=$workdir
|
||||||
|
inner_metadata=$inner_metadata
|
||||||
|
promotion_out=$promotion_out
|
||||||
|
closure_path=$closure_path
|
||||||
|
closure_base=$closure_base
|
||||||
|
vm_id=$vm_id
|
||||||
|
vdi_id=$vdi_id
|
||||||
|
guest_ip=$guest_ip
|
||||||
|
root_size=$root_size
|
||||||
|
run_id=$run_id
|
||||||
|
source_store=$source_store
|
||||||
|
guest_result_root=$result_root
|
||||||
|
guest_promotion_file=$promotion_file
|
||||||
|
guest_world_artifact=$world_artifact
|
||||||
|
guest_kernel_artifact=$kernel_artifact
|
||||||
|
guest_headers_artifact=$headers_artifact
|
||||||
|
guest_bootloader_artifact=$bootloader_artifact
|
||||||
|
local_result_root=$local_result_root
|
||||||
|
store_dir=$store_dir
|
||||||
|
executor_kind=$executor_kind
|
||||||
|
executor_name=$executor_name
|
||||||
|
executor_version=$executor_version
|
||||||
|
result_store=$result_store
|
||||||
|
result_metadata_file=$result_metadata_file
|
||||||
|
artifact_store_count=$artifact_store_count
|
||||||
|
artifact_stores=$artifact_stores
|
||||||
|
world_store=$world_store
|
||||||
|
kernel_store=$kernel_store
|
||||||
|
headers_store=$headers_store
|
||||||
|
bootloader_store=$bootloader_store
|
||||||
|
sha_kernel=$sha_kernel
|
||||||
|
sha_loader=$sha_loader
|
||||||
|
sha_param=$sha_param
|
||||||
|
promoted_kernel_sha=$promoted_kernel_sha
|
||||||
|
promoted_loader_sha=$promoted_loader_sha
|
||||||
|
promoted_param_sha=$promoted_param_sha
|
||||||
|
boot_backend=xcp-ng-xo-cli
|
||||||
|
init_mode=shepherd-pid1
|
||||||
|
host_initiated_native_build_store_promotion=ok
|
||||||
|
EOF
|
||||||
|
|
||||||
|
if [ -n "$metadata_target" ]; then
|
||||||
|
mkdir -p "$(dirname "$metadata_target")"
|
||||||
|
cp "$metadata_file" "$metadata_target"
|
||||||
|
fi
|
||||||
|
|
||||||
|
printf 'PASS phase20-host-initiated-native-build-store-promotion-xcpng\n'
|
||||||
|
printf 'Work directory: %s\n' "$workdir"
|
||||||
|
printf 'Metadata file: %s\n' "$metadata_file"
|
||||||
|
if [ -n "$metadata_target" ]; then
|
||||||
|
printf 'Copied metadata to: %s\n' "$metadata_target"
|
||||||
|
fi
|
||||||
|
printf '%s\n' '--- metadata ---'
|
||||||
|
cat "$metadata_file"
|
||||||
435
tests/system/run-phase20-host-initiated-native-build-xcpng.sh
Executable file
435
tests/system/run-phase20-host-initiated-native-build-xcpng.sh
Executable file
@@ -0,0 +1,435 @@
|
|||||||
|
#!/bin/sh
|
||||||
|
set -eu
|
||||||
|
|
||||||
|
repo_root=${PROJECT_ROOT:-$(pwd)}
|
||||||
|
os_template=${OS_TEMPLATE:-$repo_root/tests/system/phase20-development-operating-system.scm.in}
|
||||||
|
system_name=${SYSTEM_NAME:-phase20-operating-system}
|
||||||
|
root_size=${ROOT_SIZE:-20g}
|
||||||
|
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}
|
||||||
|
cleanup=0
|
||||||
|
|
||||||
|
if [ -n "${WORKDIR:-}" ]; then
|
||||||
|
workdir=$WORKDIR
|
||||||
|
mkdir -p "$workdir"
|
||||||
|
else
|
||||||
|
workdir=$(mktemp -d /tmp/fruix-phase20-native-build-xcpng.XXXXXX)
|
||||||
|
cleanup=1
|
||||||
|
fi
|
||||||
|
if [ "${KEEP_WORKDIR:-0}" -eq 1 ]; then
|
||||||
|
cleanup=0
|
||||||
|
fi
|
||||||
|
|
||||||
|
inner_metadata=$workdir/phase20-native-build-inner-metadata.txt
|
||||||
|
metadata_file=$workdir/phase20-host-initiated-native-build-xcpng-metadata.txt
|
||||||
|
|
||||||
|
action_cleanup() {
|
||||||
|
if [ "$cleanup" -eq 1 ]; then
|
||||||
|
rm -rf "$workdir"
|
||||||
|
fi
|
||||||
|
}
|
||||||
|
trap action_cleanup EXIT INT TERM
|
||||||
|
|
||||||
|
KEEP_WORKDIR=1 WORKDIR="$workdir/inner" METADATA_OUT="$inner_metadata" \
|
||||||
|
ROOT_AUTHORIZED_KEY_FILE="$root_authorized_key_file" \
|
||||||
|
ROOT_SSH_PRIVATE_KEY_FILE="$root_ssh_private_key_file" \
|
||||||
|
OS_TEMPLATE="$os_template" SYSTEM_NAME="$system_name" ROOT_SIZE="$root_size" \
|
||||||
|
"$repo_root/tests/system/run-phase20-development-environment-xcpng.sh"
|
||||||
|
|
||||||
|
phase8_metadata=$(sed -n 's/^phase8_metadata=//p' "$inner_metadata")
|
||||||
|
closure_path=$(sed -n 's/^closure_path=//p' "$inner_metadata")
|
||||||
|
closure_base=$(sed -n 's/^closure_base=//p' "$inner_metadata")
|
||||||
|
guest_ip=$(sed -n 's/^guest_ip=//p' "$inner_metadata")
|
||||||
|
vm_id=$(sed -n 's/^vm_id=//p' "$inner_metadata")
|
||||||
|
vdi_id=$(sed -n 's/^vdi_id=//p' "$inner_metadata")
|
||||||
|
shepherd_pid=$(sed -n 's/^shepherd_pid=//p' "$inner_metadata")
|
||||||
|
sshd_status=$(sed -n 's/^sshd_status=//p' "$inner_metadata")
|
||||||
|
compat_prefix_shims=$(sed -n 's/^compat_prefix_shims=//p' "$inner_metadata")
|
||||||
|
guile_module_smoke=$(sed -n 's/^guile_module_smoke=//p' "$inner_metadata")
|
||||||
|
|
||||||
|
[ "$shepherd_pid" = 1 ] || { echo "shepherd was not PID 1" >&2; exit 1; }
|
||||||
|
[ "$sshd_status" = running ] || { echo "sshd is not running" >&2; exit 1; }
|
||||||
|
[ "$compat_prefix_shims" = absent ] || { echo "compatibility prefix shims reappeared" >&2; exit 1; }
|
||||||
|
[ "$guile_module_smoke" = ok ] || { echo "guest Guile module smoke failed" >&2; exit 1; }
|
||||||
|
|
||||||
|
ssh_guest() {
|
||||||
|
ssh -i "$root_ssh_private_key_file" \
|
||||||
|
-o BatchMode=yes \
|
||||||
|
-o StrictHostKeyChecking=no \
|
||||||
|
-o UserKnownHostsFile=/dev/null \
|
||||||
|
-o ConnectTimeout=5 \
|
||||||
|
root@"$guest_ip" "$@"
|
||||||
|
}
|
||||||
|
|
||||||
|
guest_build_jobs=${GUEST_BUILD_JOBS:-$(ssh_guest 'sysctl -n hw.ncpu')}
|
||||||
|
case "$guest_build_jobs" in
|
||||||
|
''|*[!0-9]*)
|
||||||
|
echo "invalid guest build job count: $guest_build_jobs" >&2
|
||||||
|
exit 1
|
||||||
|
;;
|
||||||
|
esac
|
||||||
|
|
||||||
|
native_build_metadata=$(ssh_guest env BUILD_JOBS="$guest_build_jobs" GUEST_IP="$guest_ip" VM_ID="$vm_id" VDI_ID="$vdi_id" sh -s <<'EOF'
|
||||||
|
set -eu
|
||||||
|
[ -L /run/current-development ]
|
||||||
|
[ -L /run/current-build ]
|
||||||
|
[ -L /usr/include ]
|
||||||
|
[ "$(readlink /usr/include)" = "/run/current-system/build-profile/usr/include" ]
|
||||||
|
[ -L /usr/share/mk ]
|
||||||
|
[ "$(readlink /usr/share/mk)" = "/run/current-system/build-profile/usr/share/mk" ]
|
||||||
|
guest_host_name=$(hostname)
|
||||||
|
closure=$(readlink /run/current-system)
|
||||||
|
source_store=$(sed -n 's/.*"\(\/frx\/store\/[^"]*-freebsd-source-[^"]*\)".*/\1/p' "$closure/metadata/store-layout.scm" | head -n 1)
|
||||||
|
source_root="$source_store/tree"
|
||||||
|
build_common='TARGET=amd64 TARGET_ARCH=amd64 KERNCONF=GENERIC __MAKE_CONF=/dev/null SRCCONF=/dev/null SRC_ENV_CONF=/dev/null MK_DEBUG_FILES=no MK_TESTS=no'
|
||||||
|
install_common="$build_common DB_FROM_SRC=yes"
|
||||||
|
build_root=/var/tmp/fruix-phase20-native-build
|
||||||
|
run_id=${FRUIX_HOST_INITIATED_NATIVE_BUILD_ID:-$(date -u +%Y%m%dT%H%M%SZ)}
|
||||||
|
result_root_base=/var/lib/fruix/native-builds
|
||||||
|
result_root=$result_root_base/$run_id
|
||||||
|
logdir=$build_root/logs
|
||||||
|
status_file=$result_root/status
|
||||||
|
guest_metadata_file=$result_root/metadata.txt
|
||||||
|
promotion_file=$result_root/promotion.scm
|
||||||
|
world_stage=$build_root/stage-world
|
||||||
|
kernel_stage=$build_root/stage-kernel
|
||||||
|
headers_stage=$build_root/artifact-headers
|
||||||
|
bootloader_stage=$build_root/artifact-bootloader
|
||||||
|
world_artifact=$result_root/artifacts/world
|
||||||
|
kernel_artifact=$result_root/artifacts/kernel
|
||||||
|
headers_artifact=$result_root/artifacts/headers
|
||||||
|
bootloader_artifact=$result_root/artifacts/bootloader
|
||||||
|
latest_link=$result_root_base/latest
|
||||||
|
rm -rf "$build_root" "$result_root"
|
||||||
|
mkdir -p "$logdir" "$result_root" "$world_artifact" "$kernel_artifact/boot" "$headers_artifact/usr" "$bootloader_artifact/boot"
|
||||||
|
printf 'running\n' > "$status_file"
|
||||||
|
fail_mark() {
|
||||||
|
rc=$?
|
||||||
|
if [ "$rc" -ne 0 ]; then
|
||||||
|
printf 'failed\n' > "$status_file"
|
||||||
|
fi
|
||||||
|
}
|
||||||
|
trap fail_mark EXIT HUP INT TERM
|
||||||
|
export MAKEOBJDIRPREFIX="$build_root/obj"
|
||||||
|
make -j"$BUILD_JOBS" -C "$source_root" TARGET=amd64 TARGET_ARCH=amd64 KERNCONF=GENERIC __MAKE_CONF=/dev/null SRCCONF=/dev/null SRC_ENV_CONF=/dev/null MK_DEBUG_FILES=no MK_TESTS=no buildworld > "$logdir/buildworld.log" 2>&1
|
||||||
|
make -j"$BUILD_JOBS" -C "$source_root" TARGET=amd64 TARGET_ARCH=amd64 KERNCONF=GENERIC __MAKE_CONF=/dev/null SRCCONF=/dev/null SRC_ENV_CONF=/dev/null MK_DEBUG_FILES=no MK_TESTS=no buildkernel > "$logdir/buildkernel.log" 2>&1
|
||||||
|
make -C "$source_root" $install_common DESTDIR="$world_stage" installworld > "$logdir/installworld.log" 2>&1
|
||||||
|
make -C "$source_root" $install_common DESTDIR="$world_stage" distribution > "$logdir/distribution.log" 2>&1
|
||||||
|
make -C "$source_root" $install_common DESTDIR="$kernel_stage" installkernel > "$logdir/installkernel.log" 2>&1
|
||||||
|
mkdir -p "$headers_stage/usr" "$bootloader_stage/boot"
|
||||||
|
cp -a "$world_stage/." "$world_artifact/"
|
||||||
|
cp -a "$kernel_stage/boot/kernel" "$kernel_artifact/boot/kernel"
|
||||||
|
cp -a "$world_stage/usr/include" "$headers_stage/usr/include"
|
||||||
|
mkdir -p "$headers_stage/usr/share"
|
||||||
|
cp -a "$world_stage/usr/share/mk" "$headers_stage/usr/share/mk"
|
||||||
|
cp -a "$headers_stage/usr/." "$headers_artifact/usr/"
|
||||||
|
cp -a "$world_stage/boot/loader" "$bootloader_stage/boot/loader"
|
||||||
|
cp -a "$world_stage/boot/loader.efi" "$bootloader_stage/boot/loader.efi"
|
||||||
|
cp -a "$world_stage/boot/device.hints" "$bootloader_stage/boot/device.hints"
|
||||||
|
cp -a "$world_stage/boot/defaults" "$bootloader_stage/boot/defaults"
|
||||||
|
cp -a "$world_stage/boot/lua" "$bootloader_stage/boot/lua"
|
||||||
|
cp -a "$bootloader_stage/boot/." "$bootloader_artifact/boot/"
|
||||||
|
[ -f "$world_artifact/bin/sh" ]
|
||||||
|
[ -f "$kernel_artifact/boot/kernel/kernel" ]
|
||||||
|
[ -f "$headers_artifact/usr/include/sys/param.h" ]
|
||||||
|
[ -f "$headers_artifact/usr/share/mk/bsd.prog.mk" ]
|
||||||
|
[ -f "$bootloader_artifact/boot/loader.efi" ]
|
||||||
|
[ -f "$bootloader_artifact/boot/defaults/loader.conf" ]
|
||||||
|
[ -f "$bootloader_artifact/boot/lua/loader.lua" ]
|
||||||
|
sha_kernel=$(sha256 -q "$kernel_artifact/boot/kernel/kernel")
|
||||||
|
sha_loader=$(sha256 -q "$bootloader_artifact/boot/loader.efi")
|
||||||
|
sha_param=$(sha256 -q "$headers_artifact/usr/include/sys/param.h")
|
||||||
|
buildworld_tail=$(tail -n 20 "$logdir/buildworld.log" | tr '\n' ' ')
|
||||||
|
buildkernel_tail=$(tail -n 20 "$logdir/buildkernel.log" | tr '\n' ' ')
|
||||||
|
installworld_tail=$(tail -n 20 "$logdir/installworld.log" | tr '\n' ' ')
|
||||||
|
distribution_tail=$(tail -n 20 "$logdir/distribution.log" | tr '\n' ' ')
|
||||||
|
installkernel_tail=$(tail -n 20 "$logdir/installkernel.log" | tr '\n' ' ')
|
||||||
|
root_df=$(df -h / | tail -n 1 | tr -s ' ' | tr '\t' ' ')
|
||||||
|
build_root_size=$(du -sh "$build_root" | awk '{print $1}')
|
||||||
|
result_root_size=$(du -sh "$result_root" | awk '{print $1}')
|
||||||
|
world_stage_size=$(du -sh "$world_stage" | awk '{print $1}')
|
||||||
|
kernel_stage_size=$(du -sh "$kernel_stage" | awk '{print $1}')
|
||||||
|
headers_stage_size=$(du -sh "$headers_stage" | awk '{print $1}')
|
||||||
|
bootloader_stage_size=$(du -sh "$bootloader_stage" | awk '{print $1}')
|
||||||
|
world_artifact_size=$(du -sh "$world_artifact" | awk '{print $1}')
|
||||||
|
kernel_artifact_size=$(du -sh "$kernel_artifact" | awk '{print $1}')
|
||||||
|
headers_artifact_size=$(du -sh "$headers_artifact" | awk '{print $1}')
|
||||||
|
bootloader_artifact_size=$(du -sh "$bootloader_artifact" | awk '{print $1}')
|
||||||
|
rm -f "$latest_link"
|
||||||
|
ln -s "$result_root" "$latest_link"
|
||||||
|
cat >"$promotion_file" <<EOF2
|
||||||
|
((native-build-result-version . "1")
|
||||||
|
(executor . ((kind . ssh-guest)
|
||||||
|
(name . "ssh-guest")
|
||||||
|
(version . "1")
|
||||||
|
(properties . ((transport . "ssh")
|
||||||
|
(orchestrator . "host")
|
||||||
|
(guest-host-name . "$guest_host_name")
|
||||||
|
(guest-ip . "$GUEST_IP")
|
||||||
|
(vm-id . "$VM_ID")
|
||||||
|
(vdi-id . "$VDI_ID")))))
|
||||||
|
(run-id . "$run_id")
|
||||||
|
(guest-host-name . "$guest_host_name")
|
||||||
|
(closure-path . "$closure")
|
||||||
|
(build-profile . "/run/current-system/build-profile")
|
||||||
|
(freebsd-base . ((name . "default")
|
||||||
|
(version-label . "15.0-STABLE")
|
||||||
|
(release . "15.0-STABLE")
|
||||||
|
(branch . "stable/15")
|
||||||
|
(source-root . "/usr/src")
|
||||||
|
(target . "amd64")
|
||||||
|
(target-arch . "amd64")
|
||||||
|
(kernconf . "GENERIC")))
|
||||||
|
(source . ((store-path . "$source_store")
|
||||||
|
(source-root . "$source_root")))
|
||||||
|
(build-policy . ((jobs . "$BUILD_JOBS")
|
||||||
|
(build-common . "$build_common")
|
||||||
|
(install-common . "$install_common")))
|
||||||
|
(artifacts . ((world . ((path . "artifacts/world")
|
||||||
|
(required-file . "bin/sh")))
|
||||||
|
(kernel . ((path . "artifacts/kernel")
|
||||||
|
(required-file . "boot/kernel/kernel")
|
||||||
|
(recorded-sha256 . "$sha_kernel")))
|
||||||
|
(headers . ((path . "artifacts/headers")
|
||||||
|
(required-file . "usr/include/sys/param.h")
|
||||||
|
(recorded-sha256 . "$sha_param")))
|
||||||
|
(bootloader . ((path . "artifacts/bootloader")
|
||||||
|
(required-file . "boot/loader.efi")
|
||||||
|
(recorded-sha256 . "$sha_loader"))))))
|
||||||
|
EOF2
|
||||||
|
cat >"$guest_metadata_file" <<EOF2
|
||||||
|
run_id=$run_id
|
||||||
|
executor_kind=ssh-guest
|
||||||
|
executor_name=ssh-guest
|
||||||
|
executor_version=1
|
||||||
|
guest_host_name=$guest_host_name
|
||||||
|
closure_path=$closure
|
||||||
|
source_store=$source_store
|
||||||
|
source_root=$source_root
|
||||||
|
build_jobs=$BUILD_JOBS
|
||||||
|
build_common=$build_common
|
||||||
|
install_common=$install_common
|
||||||
|
build_root=$build_root
|
||||||
|
result_root=$result_root
|
||||||
|
logdir=$logdir
|
||||||
|
status_file=$status_file
|
||||||
|
metadata_file=$guest_metadata_file
|
||||||
|
promotion_file=$promotion_file
|
||||||
|
world_stage=$world_stage
|
||||||
|
kernel_stage=$kernel_stage
|
||||||
|
headers_stage=$headers_stage
|
||||||
|
bootloader_stage=$bootloader_stage
|
||||||
|
world_artifact=$world_artifact
|
||||||
|
kernel_artifact=$kernel_artifact
|
||||||
|
headers_artifact=$headers_artifact
|
||||||
|
bootloader_artifact=$bootloader_artifact
|
||||||
|
latest_link=$latest_link
|
||||||
|
root_df=$root_df
|
||||||
|
build_root_size=$build_root_size
|
||||||
|
result_root_size=$result_root_size
|
||||||
|
world_stage_size=$world_stage_size
|
||||||
|
kernel_stage_size=$kernel_stage_size
|
||||||
|
headers_stage_size=$headers_stage_size
|
||||||
|
bootloader_stage_size=$bootloader_stage_size
|
||||||
|
world_artifact_size=$world_artifact_size
|
||||||
|
kernel_artifact_size=$kernel_artifact_size
|
||||||
|
headers_artifact_size=$headers_artifact_size
|
||||||
|
bootloader_artifact_size=$bootloader_artifact_size
|
||||||
|
buildworld_log=$logdir/buildworld.log
|
||||||
|
buildkernel_log=$logdir/buildkernel.log
|
||||||
|
installworld_log=$logdir/installworld.log
|
||||||
|
distribution_log=$logdir/distribution.log
|
||||||
|
installkernel_log=$logdir/installkernel.log
|
||||||
|
sha_kernel=$sha_kernel
|
||||||
|
sha_loader=$sha_loader
|
||||||
|
sha_param=$sha_param
|
||||||
|
buildworld_tail=$buildworld_tail
|
||||||
|
buildkernel_tail=$buildkernel_tail
|
||||||
|
installworld_tail=$installworld_tail
|
||||||
|
distribution_tail=$distribution_tail
|
||||||
|
installkernel_tail=$installkernel_tail
|
||||||
|
host_initiated_native_build=ok
|
||||||
|
EOF2
|
||||||
|
printf 'ok\n' > "$status_file"
|
||||||
|
cat "$guest_metadata_file"
|
||||||
|
EOF
|
||||||
|
)
|
||||||
|
|
||||||
|
run_id=$(printf '%s\n' "$native_build_metadata" | sed -n 's/^run_id=//p')
|
||||||
|
executor_kind=$(printf '%s\n' "$native_build_metadata" | sed -n 's/^executor_kind=//p')
|
||||||
|
executor_name=$(printf '%s\n' "$native_build_metadata" | sed -n 's/^executor_name=//p')
|
||||||
|
executor_version=$(printf '%s\n' "$native_build_metadata" | sed -n 's/^executor_version=//p')
|
||||||
|
guest_host_name=$(printf '%s\n' "$native_build_metadata" | sed -n 's/^guest_host_name=//p')
|
||||||
|
source_store=$(printf '%s\n' "$native_build_metadata" | sed -n 's/^source_store=//p')
|
||||||
|
source_root=$(printf '%s\n' "$native_build_metadata" | sed -n 's/^source_root=//p')
|
||||||
|
build_jobs=$(printf '%s\n' "$native_build_metadata" | sed -n 's/^build_jobs=//p')
|
||||||
|
build_common=$(printf '%s\n' "$native_build_metadata" | sed -n 's/^build_common=//p')
|
||||||
|
install_common=$(printf '%s\n' "$native_build_metadata" | sed -n 's/^install_common=//p')
|
||||||
|
build_root=$(printf '%s\n' "$native_build_metadata" | sed -n 's/^build_root=//p')
|
||||||
|
result_root=$(printf '%s\n' "$native_build_metadata" | sed -n 's/^result_root=//p')
|
||||||
|
logdir=$(printf '%s\n' "$native_build_metadata" | sed -n 's/^logdir=//p')
|
||||||
|
status_file=$(printf '%s\n' "$native_build_metadata" | sed -n 's/^status_file=//p')
|
||||||
|
guest_metadata_file=$(printf '%s\n' "$native_build_metadata" | sed -n 's/^metadata_file=//p')
|
||||||
|
promotion_file=$(printf '%s\n' "$native_build_metadata" | sed -n 's/^promotion_file=//p')
|
||||||
|
buildworld_log=$(printf '%s\n' "$native_build_metadata" | sed -n 's/^buildworld_log=//p')
|
||||||
|
buildkernel_log=$(printf '%s\n' "$native_build_metadata" | sed -n 's/^buildkernel_log=//p')
|
||||||
|
installworld_log=$(printf '%s\n' "$native_build_metadata" | sed -n 's/^installworld_log=//p')
|
||||||
|
distribution_log=$(printf '%s\n' "$native_build_metadata" | sed -n 's/^distribution_log=//p')
|
||||||
|
installkernel_log=$(printf '%s\n' "$native_build_metadata" | sed -n 's/^installkernel_log=//p')
|
||||||
|
world_stage=$(printf '%s\n' "$native_build_metadata" | sed -n 's/^world_stage=//p')
|
||||||
|
kernel_stage=$(printf '%s\n' "$native_build_metadata" | sed -n 's/^kernel_stage=//p')
|
||||||
|
headers_stage=$(printf '%s\n' "$native_build_metadata" | sed -n 's/^headers_stage=//p')
|
||||||
|
bootloader_stage=$(printf '%s\n' "$native_build_metadata" | sed -n 's/^bootloader_stage=//p')
|
||||||
|
world_artifact=$(printf '%s\n' "$native_build_metadata" | sed -n 's/^world_artifact=//p')
|
||||||
|
kernel_artifact=$(printf '%s\n' "$native_build_metadata" | sed -n 's/^kernel_artifact=//p')
|
||||||
|
headers_artifact=$(printf '%s\n' "$native_build_metadata" | sed -n 's/^headers_artifact=//p')
|
||||||
|
bootloader_artifact=$(printf '%s\n' "$native_build_metadata" | sed -n 's/^bootloader_artifact=//p')
|
||||||
|
latest_link=$(printf '%s\n' "$native_build_metadata" | sed -n 's/^latest_link=//p')
|
||||||
|
root_df=$(printf '%s\n' "$native_build_metadata" | sed -n 's/^root_df=//p')
|
||||||
|
build_root_size=$(printf '%s\n' "$native_build_metadata" | sed -n 's/^build_root_size=//p')
|
||||||
|
result_root_size=$(printf '%s\n' "$native_build_metadata" | sed -n 's/^result_root_size=//p')
|
||||||
|
world_stage_size=$(printf '%s\n' "$native_build_metadata" | sed -n 's/^world_stage_size=//p')
|
||||||
|
kernel_stage_size=$(printf '%s\n' "$native_build_metadata" | sed -n 's/^kernel_stage_size=//p')
|
||||||
|
headers_stage_size=$(printf '%s\n' "$native_build_metadata" | sed -n 's/^headers_stage_size=//p')
|
||||||
|
bootloader_stage_size=$(printf '%s\n' "$native_build_metadata" | sed -n 's/^bootloader_stage_size=//p')
|
||||||
|
world_artifact_size=$(printf '%s\n' "$native_build_metadata" | sed -n 's/^world_artifact_size=//p')
|
||||||
|
kernel_artifact_size=$(printf '%s\n' "$native_build_metadata" | sed -n 's/^kernel_artifact_size=//p')
|
||||||
|
headers_artifact_size=$(printf '%s\n' "$native_build_metadata" | sed -n 's/^headers_artifact_size=//p')
|
||||||
|
bootloader_artifact_size=$(printf '%s\n' "$native_build_metadata" | sed -n 's/^bootloader_artifact_size=//p')
|
||||||
|
sha_kernel=$(printf '%s\n' "$native_build_metadata" | sed -n 's/^sha_kernel=//p')
|
||||||
|
sha_loader=$(printf '%s\n' "$native_build_metadata" | sed -n 's/^sha_loader=//p')
|
||||||
|
sha_param=$(printf '%s\n' "$native_build_metadata" | sed -n 's/^sha_param=//p')
|
||||||
|
buildworld_tail=$(printf '%s\n' "$native_build_metadata" | sed -n 's/^buildworld_tail=//p')
|
||||||
|
buildkernel_tail=$(printf '%s\n' "$native_build_metadata" | sed -n 's/^buildkernel_tail=//p')
|
||||||
|
installworld_tail=$(printf '%s\n' "$native_build_metadata" | sed -n 's/^installworld_tail=//p')
|
||||||
|
distribution_tail=$(printf '%s\n' "$native_build_metadata" | sed -n 's/^distribution_tail=//p')
|
||||||
|
installkernel_tail=$(printf '%s\n' "$native_build_metadata" | sed -n 's/^installkernel_tail=//p')
|
||||||
|
|
||||||
|
status_value=$(ssh_guest "cat '$status_file'")
|
||||||
|
latest_target=$(ssh_guest "readlink '$latest_link'")
|
||||||
|
ssh_guest "[ -f '$promotion_file' ]"
|
||||||
|
ssh_guest "[ -f '$world_artifact/bin/sh' ]"
|
||||||
|
|
||||||
|
[ "$executor_kind" = ssh-guest ] || { echo "unexpected executor kind: $executor_kind" >&2; exit 1; }
|
||||||
|
[ "$executor_name" = ssh-guest ] || { echo "unexpected executor name: $executor_name" >&2; exit 1; }
|
||||||
|
[ "$executor_version" = 1 ] || { echo "unexpected executor version: $executor_version" >&2; exit 1; }
|
||||||
|
[ "$status_value" = ok ] || { echo "host-initiated build status is not ok: $status_value" >&2; exit 1; }
|
||||||
|
[ "$latest_target" = "$result_root" ] || { echo "latest link target mismatch: $latest_target" >&2; exit 1; }
|
||||||
|
|
||||||
|
case "$source_store" in
|
||||||
|
/frx/store/*-freebsd-source-*) : ;;
|
||||||
|
*) echo "unexpected source store path: $source_store" >&2; exit 1 ;;
|
||||||
|
esac
|
||||||
|
case "$source_root" in
|
||||||
|
/frx/store/*-freebsd-source-*/*) : ;;
|
||||||
|
*) echo "unexpected source root path: $source_root" >&2; exit 1 ;;
|
||||||
|
esac
|
||||||
|
case "$build_root" in
|
||||||
|
/var/tmp/fruix-phase20-native-build) : ;;
|
||||||
|
*) echo "unexpected build root: $build_root" >&2; exit 1 ;;
|
||||||
|
esac
|
||||||
|
case "$result_root" in
|
||||||
|
/var/lib/fruix/native-builds/*) : ;;
|
||||||
|
*) echo "unexpected result root: $result_root" >&2; exit 1 ;;
|
||||||
|
esac
|
||||||
|
printf '%s\n' "$sha_kernel" | grep -E '^[0-9a-f]{64}$' >/dev/null || {
|
||||||
|
echo "invalid kernel sha256: $sha_kernel" >&2
|
||||||
|
exit 1
|
||||||
|
}
|
||||||
|
printf '%s\n' "$sha_loader" | grep -E '^[0-9a-f]{64}$' >/dev/null || {
|
||||||
|
echo "invalid loader sha256: $sha_loader" >&2
|
||||||
|
exit 1
|
||||||
|
}
|
||||||
|
printf '%s\n' "$sha_param" | grep -E '^[0-9a-f]{64}$' >/dev/null || {
|
||||||
|
echo "invalid param.h sha256: $sha_param" >&2
|
||||||
|
exit 1
|
||||||
|
}
|
||||||
|
case "$buildworld_tail" in
|
||||||
|
*'World build completed on'*) : ;;
|
||||||
|
*) echo "buildworld log does not show completion" >&2; exit 1 ;;
|
||||||
|
esac
|
||||||
|
case "$buildkernel_tail" in
|
||||||
|
*'Kernel(s) GENERIC built in'*) : ;;
|
||||||
|
*) echo "buildkernel log does not show successful kernel build" >&2; exit 1 ;;
|
||||||
|
esac
|
||||||
|
|
||||||
|
cat >"$metadata_file" <<EOF
|
||||||
|
workdir=$workdir
|
||||||
|
inner_metadata=$inner_metadata
|
||||||
|
phase8_metadata=$phase8_metadata
|
||||||
|
closure_path=$closure_path
|
||||||
|
closure_base=$closure_base
|
||||||
|
vm_id=$vm_id
|
||||||
|
vdi_id=$vdi_id
|
||||||
|
guest_ip=$guest_ip
|
||||||
|
root_size=$root_size
|
||||||
|
run_id=$run_id
|
||||||
|
executor_kind=$executor_kind
|
||||||
|
executor_name=$executor_name
|
||||||
|
executor_version=$executor_version
|
||||||
|
guest_host_name=$guest_host_name
|
||||||
|
build_jobs=$build_jobs
|
||||||
|
source_store=$source_store
|
||||||
|
source_root=$source_root
|
||||||
|
build_common=$build_common
|
||||||
|
install_common=$install_common
|
||||||
|
build_root=$build_root
|
||||||
|
result_root=$result_root
|
||||||
|
logdir=$logdir
|
||||||
|
status_file=$status_file
|
||||||
|
guest_metadata_file=$guest_metadata_file
|
||||||
|
promotion_file=$promotion_file
|
||||||
|
buildworld_log=$buildworld_log
|
||||||
|
buildkernel_log=$buildkernel_log
|
||||||
|
installworld_log=$installworld_log
|
||||||
|
distribution_log=$distribution_log
|
||||||
|
installkernel_log=$installkernel_log
|
||||||
|
world_stage=$world_stage
|
||||||
|
kernel_stage=$kernel_stage
|
||||||
|
headers_stage=$headers_stage
|
||||||
|
bootloader_stage=$bootloader_stage
|
||||||
|
world_artifact=$world_artifact
|
||||||
|
kernel_artifact=$kernel_artifact
|
||||||
|
headers_artifact=$headers_artifact
|
||||||
|
bootloader_artifact=$bootloader_artifact
|
||||||
|
latest_link=$latest_link
|
||||||
|
latest_target=$latest_target
|
||||||
|
status_value=$status_value
|
||||||
|
root_df=$root_df
|
||||||
|
build_root_size=$build_root_size
|
||||||
|
result_root_size=$result_root_size
|
||||||
|
world_stage_size=$world_stage_size
|
||||||
|
kernel_stage_size=$kernel_stage_size
|
||||||
|
headers_stage_size=$headers_stage_size
|
||||||
|
bootloader_stage_size=$bootloader_stage_size
|
||||||
|
world_artifact_size=$world_artifact_size
|
||||||
|
kernel_artifact_size=$kernel_artifact_size
|
||||||
|
headers_artifact_size=$headers_artifact_size
|
||||||
|
bootloader_artifact_size=$bootloader_artifact_size
|
||||||
|
sha_kernel=$sha_kernel
|
||||||
|
sha_loader=$sha_loader
|
||||||
|
sha_param=$sha_param
|
||||||
|
buildworld_tail=$buildworld_tail
|
||||||
|
buildkernel_tail=$buildkernel_tail
|
||||||
|
installworld_tail=$installworld_tail
|
||||||
|
distribution_tail=$distribution_tail
|
||||||
|
installkernel_tail=$installkernel_tail
|
||||||
|
boot_backend=xcp-ng-xo-cli
|
||||||
|
init_mode=shepherd-pid1
|
||||||
|
host_initiated_native_build=ok
|
||||||
|
EOF
|
||||||
|
|
||||||
|
if [ -n "$metadata_target" ]; then
|
||||||
|
mkdir -p "$(dirname "$metadata_target")"
|
||||||
|
cp "$metadata_file" "$metadata_target"
|
||||||
|
fi
|
||||||
|
|
||||||
|
printf 'PASS phase20-host-initiated-native-build-xcpng\n'
|
||||||
|
printf 'Work directory: %s\n' "$workdir"
|
||||||
|
printf 'Metadata file: %s\n' "$metadata_file"
|
||||||
|
if [ -n "$metadata_target" ]; then
|
||||||
|
printf 'Copied metadata to: %s\n' "$metadata_target"
|
||||||
|
fi
|
||||||
|
printf '%s\n' '--- metadata ---'
|
||||||
|
cat "$metadata_file"
|
||||||
248
tests/system/run-phase20-native-build-store-promotion-xcpng.sh
Executable file
248
tests/system/run-phase20-native-build-store-promotion-xcpng.sh
Executable file
@@ -0,0 +1,248 @@
|
|||||||
|
#!/bin/sh
|
||||||
|
set -eu
|
||||||
|
|
||||||
|
repo_root=${PROJECT_ROOT:-$(pwd)}
|
||||||
|
os_template=${OS_TEMPLATE:-$repo_root/tests/system/phase20-development-operating-system.scm.in}
|
||||||
|
system_name=${SYSTEM_NAME:-phase20-operating-system}
|
||||||
|
root_size=${ROOT_SIZE:-20g}
|
||||||
|
store_dir=${STORE_DIR:-/frx/store}
|
||||||
|
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}
|
||||||
|
cleanup=0
|
||||||
|
|
||||||
|
if [ -n "${WORKDIR:-}" ]; then
|
||||||
|
workdir=$WORKDIR
|
||||||
|
mkdir -p "$workdir"
|
||||||
|
else
|
||||||
|
workdir=$(mktemp -d /tmp/fruix-phase20-native-build-store-promotion-xcpng.XXXXXX)
|
||||||
|
cleanup=1
|
||||||
|
fi
|
||||||
|
if [ "${KEEP_WORKDIR:-0}" -eq 1 ]; then
|
||||||
|
cleanup=0
|
||||||
|
fi
|
||||||
|
|
||||||
|
inner_metadata=$workdir/phase20-native-build-store-promotion-inner-metadata.txt
|
||||||
|
existing_inner_metadata=${EXISTING_INNER_METADATA:-}
|
||||||
|
promotion_out=$workdir/native-build-promote.txt
|
||||||
|
metadata_file=$workdir/phase20-native-build-store-promotion-xcpng-metadata.txt
|
||||||
|
import_root=$workdir/import
|
||||||
|
|
||||||
|
action_cleanup() {
|
||||||
|
if [ "$cleanup" -eq 1 ]; then
|
||||||
|
rm -rf "$workdir"
|
||||||
|
fi
|
||||||
|
}
|
||||||
|
trap action_cleanup EXIT INT TERM
|
||||||
|
|
||||||
|
if [ -n "$existing_inner_metadata" ]; then
|
||||||
|
cp "$existing_inner_metadata" "$inner_metadata"
|
||||||
|
else
|
||||||
|
KEEP_WORKDIR=1 WORKDIR="$workdir/inner" METADATA_OUT="$inner_metadata" \
|
||||||
|
ROOT_AUTHORIZED_KEY_FILE="$root_authorized_key_file" \
|
||||||
|
ROOT_SSH_PRIVATE_KEY_FILE="$root_ssh_private_key_file" \
|
||||||
|
OS_TEMPLATE="$os_template" SYSTEM_NAME="$system_name" ROOT_SIZE="$root_size" \
|
||||||
|
"$repo_root/tests/system/run-phase20-self-hosted-native-build-xcpng.sh"
|
||||||
|
fi
|
||||||
|
|
||||||
|
guest_ip=$(sed -n 's/^guest_ip=//p' "$inner_metadata")
|
||||||
|
vm_id=$(sed -n 's/^vm_id=//p' "$inner_metadata")
|
||||||
|
vdi_id=$(sed -n 's/^vdi_id=//p' "$inner_metadata")
|
||||||
|
closure_path=$(sed -n 's/^closure_path=//p' "$inner_metadata")
|
||||||
|
closure_base=$(sed -n 's/^closure_base=//p' "$inner_metadata")
|
||||||
|
run_id=$(sed -n 's/^run_id=//p' "$inner_metadata")
|
||||||
|
source_store=$(sed -n 's/^source_store=//p' "$inner_metadata")
|
||||||
|
result_root=$(sed -n 's/^result_root=//p' "$inner_metadata")
|
||||||
|
promotion_file=$(sed -n 's/^promotion_file=//p' "$inner_metadata")
|
||||||
|
world_artifact=$(sed -n 's/^world_artifact=//p' "$inner_metadata")
|
||||||
|
kernel_artifact=$(sed -n 's/^kernel_artifact=//p' "$inner_metadata")
|
||||||
|
headers_artifact=$(sed -n 's/^headers_artifact=//p' "$inner_metadata")
|
||||||
|
bootloader_artifact=$(sed -n 's/^bootloader_artifact=//p' "$inner_metadata")
|
||||||
|
sha_kernel=$(sed -n 's/^sha_kernel=//p' "$inner_metadata")
|
||||||
|
sha_loader=$(sed -n 's/^sha_loader=//p' "$inner_metadata")
|
||||||
|
sha_param=$(sed -n 's/^sha_param=//p' "$inner_metadata")
|
||||||
|
|
||||||
|
ssh_guest() {
|
||||||
|
ssh -i "$root_ssh_private_key_file" \
|
||||||
|
-o BatchMode=yes \
|
||||||
|
-o StrictHostKeyChecking=no \
|
||||||
|
-o UserKnownHostsFile=/dev/null \
|
||||||
|
-o ConnectTimeout=5 \
|
||||||
|
root@"$guest_ip" "$@"
|
||||||
|
}
|
||||||
|
|
||||||
|
mkdir -p "$import_root"
|
||||||
|
result_base=$(basename "$result_root")
|
||||||
|
ssh -i "$root_ssh_private_key_file" \
|
||||||
|
-o BatchMode=yes \
|
||||||
|
-o StrictHostKeyChecking=no \
|
||||||
|
-o UserKnownHostsFile=/dev/null \
|
||||||
|
-o ConnectTimeout=5 \
|
||||||
|
root@"$guest_ip" "tar -C '$(dirname "$result_root")' -cf - '$result_base'" | tar -C "$import_root" -xf -
|
||||||
|
local_result_root=$import_root/$result_base
|
||||||
|
[ -d "$local_result_root" ] || { echo "failed to import native build result root" >&2; exit 1; }
|
||||||
|
[ -f "$local_result_root/promotion.scm" ] || { echo "imported result is missing promotion.scm" >&2; exit 1; }
|
||||||
|
[ -f "$local_result_root/artifacts/world/bin/sh" ] || { echo "imported result is missing world artifact" >&2; exit 1; }
|
||||||
|
[ -f "$local_result_root/artifacts/kernel/boot/kernel/kernel" ] || { echo "imported result is missing kernel artifact" >&2; exit 1; }
|
||||||
|
[ -f "$local_result_root/artifacts/headers/usr/include/sys/param.h" ] || { echo "imported result is missing headers artifact" >&2; exit 1; }
|
||||||
|
[ -f "$local_result_root/artifacts/bootloader/boot/loader.efi" ] || { echo "imported result is missing bootloader artifact" >&2; exit 1; }
|
||||||
|
|
||||||
|
action_env() {
|
||||||
|
sudo env \
|
||||||
|
HOME="$HOME" \
|
||||||
|
GUILE_AUTO_COMPILE=0 \
|
||||||
|
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 "$repo_root/bin/fruix" native-build promote "$local_result_root" --store "$store_dir" >"$promotion_out"
|
||||||
|
|
||||||
|
field() {
|
||||||
|
sed -n "s/^$1=//p" "$promotion_out" | tail -n 1
|
||||||
|
}
|
||||||
|
|
||||||
|
executor_kind=$(field executor_kind)
|
||||||
|
executor_name=$(field executor_name)
|
||||||
|
executor_version=$(field executor_version)
|
||||||
|
result_store=$(field result_store)
|
||||||
|
result_metadata_file=$(field result_metadata_file)
|
||||||
|
artifact_store_count=$(field artifact_store_count)
|
||||||
|
artifact_stores=$(field artifact_stores)
|
||||||
|
world_store=$(field world_store)
|
||||||
|
kernel_store=$(field kernel_store)
|
||||||
|
headers_store=$(field headers_store)
|
||||||
|
bootloader_store=$(field bootloader_store)
|
||||||
|
|
||||||
|
[ "$executor_kind" = self-hosted ] || { echo "unexpected executor kind: $executor_kind" >&2; exit 1; }
|
||||||
|
[ "$executor_name" = guest-self-hosted ] || { echo "unexpected executor name: $executor_name" >&2; exit 1; }
|
||||||
|
[ "$executor_version" = 5 ] || { echo "unexpected executor version: $executor_version" >&2; exit 1; }
|
||||||
|
[ "$artifact_store_count" = 4 ] || { echo "unexpected artifact store count: $artifact_store_count" >&2; exit 1; }
|
||||||
|
case "$result_store" in
|
||||||
|
/frx/store/*-fruix-native-build-result-*-guest-self-hosted) : ;;
|
||||||
|
*) echo "unexpected result store path: $result_store" >&2; exit 1 ;;
|
||||||
|
esac
|
||||||
|
case "$world_store" in
|
||||||
|
/frx/store/*-fruix-native-world-*-guest-self-hosted) : ;;
|
||||||
|
*) echo "unexpected world store path: $world_store" >&2; exit 1 ;;
|
||||||
|
esac
|
||||||
|
case "$kernel_store" in
|
||||||
|
/frx/store/*-fruix-native-kernel-*-guest-self-hosted) : ;;
|
||||||
|
*) echo "unexpected kernel store path: $kernel_store" >&2; exit 1 ;;
|
||||||
|
esac
|
||||||
|
case "$headers_store" in
|
||||||
|
/frx/store/*-fruix-native-headers-*-guest-self-hosted) : ;;
|
||||||
|
*) echo "unexpected headers store path: $headers_store" >&2; exit 1 ;;
|
||||||
|
esac
|
||||||
|
case "$bootloader_store" in
|
||||||
|
/frx/store/*-fruix-native-bootloader-*-guest-self-hosted) : ;;
|
||||||
|
*) echo "unexpected bootloader store path: $bootloader_store" >&2; exit 1 ;;
|
||||||
|
esac
|
||||||
|
|
||||||
|
[ -f "$result_metadata_file" ] || { echo "missing result metadata file: $result_metadata_file" >&2; exit 1; }
|
||||||
|
[ -f "$world_store/.fruix-native-build-object.scm" ] || { echo "missing world store metadata" >&2; exit 1; }
|
||||||
|
[ -f "$kernel_store/.fruix-native-build-object.scm" ] || { echo "missing kernel store metadata" >&2; exit 1; }
|
||||||
|
[ -f "$headers_store/.fruix-native-build-object.scm" ] || { echo "missing headers store metadata" >&2; exit 1; }
|
||||||
|
[ -f "$bootloader_store/.fruix-native-build-object.scm" ] || { echo "missing bootloader store metadata" >&2; exit 1; }
|
||||||
|
[ -L "$result_store/artifacts/world" ] || { echo "missing promoted world artifact link" >&2; exit 1; }
|
||||||
|
[ -L "$result_store/artifacts/kernel" ] || { echo "missing promoted kernel artifact link" >&2; exit 1; }
|
||||||
|
[ -L "$result_store/artifacts/headers" ] || { echo "missing promoted headers artifact link" >&2; exit 1; }
|
||||||
|
[ -L "$result_store/artifacts/bootloader" ] || { echo "missing promoted bootloader artifact link" >&2; exit 1; }
|
||||||
|
[ "$(readlink "$result_store/artifacts/world")" = "$world_store" ] || { echo "world artifact link mismatch" >&2; exit 1; }
|
||||||
|
[ "$(readlink "$result_store/artifacts/kernel")" = "$kernel_store" ] || { echo "kernel artifact link mismatch" >&2; exit 1; }
|
||||||
|
[ "$(readlink "$result_store/artifacts/headers")" = "$headers_store" ] || { echo "headers artifact link mismatch" >&2; exit 1; }
|
||||||
|
[ "$(readlink "$result_store/artifacts/bootloader")" = "$bootloader_store" ] || { echo "bootloader artifact link mismatch" >&2; exit 1; }
|
||||||
|
[ -f "$world_store/bin/sh" ] || { echo "promoted world store missing /bin/sh" >&2; exit 1; }
|
||||||
|
[ -f "$kernel_store/boot/kernel/kernel" ] || { echo "promoted kernel store missing kernel" >&2; exit 1; }
|
||||||
|
[ -f "$headers_store/usr/include/sys/param.h" ] || { echo "promoted headers store missing param.h" >&2; exit 1; }
|
||||||
|
[ -f "$bootloader_store/boot/loader.efi" ] || { echo "promoted bootloader store missing loader.efi" >&2; exit 1; }
|
||||||
|
|
||||||
|
promoted_kernel_sha=$(sha256 -q "$kernel_store/boot/kernel/kernel")
|
||||||
|
promoted_loader_sha=$(sha256 -q "$bootloader_store/boot/loader.efi")
|
||||||
|
promoted_param_sha=$(sha256 -q "$headers_store/usr/include/sys/param.h")
|
||||||
|
[ "$promoted_kernel_sha" = "$sha_kernel" ] || { echo "kernel sha mismatch after promotion" >&2; exit 1; }
|
||||||
|
[ "$promoted_loader_sha" = "$sha_loader" ] || { echo "loader sha mismatch after promotion" >&2; exit 1; }
|
||||||
|
[ "$promoted_param_sha" = "$sha_param" ] || { echo "param.h sha mismatch after promotion" >&2; exit 1; }
|
||||||
|
|
||||||
|
grep -F '(executor-kind . self-hosted)' "$result_metadata_file" >/dev/null || {
|
||||||
|
echo "result metadata file is missing self-hosted executor kind" >&2
|
||||||
|
exit 1
|
||||||
|
}
|
||||||
|
grep -F '(executor-name . "guest-self-hosted")' "$result_metadata_file" >/dev/null || {
|
||||||
|
echo "result metadata file is missing guest-self-hosted executor name" >&2
|
||||||
|
exit 1
|
||||||
|
}
|
||||||
|
grep -F "$source_store" "$result_metadata_file" >/dev/null || {
|
||||||
|
echo "result metadata file is missing source store provenance" >&2
|
||||||
|
exit 1
|
||||||
|
}
|
||||||
|
grep -F '(build-profile . "/run/current-system/build-profile")' "$result_metadata_file" >/dev/null || {
|
||||||
|
echo "result metadata file is missing build-profile provenance" >&2
|
||||||
|
exit 1
|
||||||
|
}
|
||||||
|
grep -F '(artifact-kind . kernel)' "$kernel_store/.fruix-native-build-object.scm" >/dev/null || {
|
||||||
|
echo "kernel store metadata is missing artifact kind" >&2
|
||||||
|
exit 1
|
||||||
|
}
|
||||||
|
grep -F '(artifact-kind . world)' "$world_store/.fruix-native-build-object.scm" >/dev/null || {
|
||||||
|
echo "world store metadata is missing artifact kind" >&2
|
||||||
|
exit 1
|
||||||
|
}
|
||||||
|
|
||||||
|
cat >"$metadata_file" <<EOF
|
||||||
|
workdir=$workdir
|
||||||
|
inner_metadata=$inner_metadata
|
||||||
|
promotion_out=$promotion_out
|
||||||
|
closure_path=$closure_path
|
||||||
|
closure_base=$closure_base
|
||||||
|
vm_id=$vm_id
|
||||||
|
vdi_id=$vdi_id
|
||||||
|
guest_ip=$guest_ip
|
||||||
|
root_size=$root_size
|
||||||
|
run_id=$run_id
|
||||||
|
source_store=$source_store
|
||||||
|
guest_result_root=$result_root
|
||||||
|
guest_promotion_file=$promotion_file
|
||||||
|
guest_world_artifact=$world_artifact
|
||||||
|
guest_kernel_artifact=$kernel_artifact
|
||||||
|
guest_headers_artifact=$headers_artifact
|
||||||
|
guest_bootloader_artifact=$bootloader_artifact
|
||||||
|
local_result_root=$local_result_root
|
||||||
|
store_dir=$store_dir
|
||||||
|
executor_kind=$executor_kind
|
||||||
|
executor_name=$executor_name
|
||||||
|
executor_version=$executor_version
|
||||||
|
result_store=$result_store
|
||||||
|
result_metadata_file=$result_metadata_file
|
||||||
|
artifact_store_count=$artifact_store_count
|
||||||
|
artifact_stores=$artifact_stores
|
||||||
|
world_store=$world_store
|
||||||
|
kernel_store=$kernel_store
|
||||||
|
headers_store=$headers_store
|
||||||
|
bootloader_store=$bootloader_store
|
||||||
|
sha_kernel=$sha_kernel
|
||||||
|
sha_loader=$sha_loader
|
||||||
|
sha_param=$sha_param
|
||||||
|
promoted_kernel_sha=$promoted_kernel_sha
|
||||||
|
promoted_loader_sha=$promoted_loader_sha
|
||||||
|
promoted_param_sha=$promoted_param_sha
|
||||||
|
boot_backend=xcp-ng-xo-cli
|
||||||
|
init_mode=shepherd-pid1
|
||||||
|
native_build_store_promotion=ok
|
||||||
|
EOF
|
||||||
|
|
||||||
|
if [ -n "$metadata_target" ]; then
|
||||||
|
mkdir -p "$(dirname "$metadata_target")"
|
||||||
|
cp "$metadata_file" "$metadata_target"
|
||||||
|
fi
|
||||||
|
|
||||||
|
printf 'PASS phase20-native-build-store-promotion-xcpng\n'
|
||||||
|
printf 'Work directory: %s\n' "$workdir"
|
||||||
|
printf 'Metadata file: %s\n' "$metadata_file"
|
||||||
|
if [ -n "$metadata_target" ]; then
|
||||||
|
printf 'Copied metadata to: %s\n' "$metadata_target"
|
||||||
|
fi
|
||||||
|
printf '%s\n' '--- metadata ---'
|
||||||
|
cat "$metadata_file"
|
||||||
227
tests/system/run-phase20-promoted-native-base-declaration-xcpng.sh
Executable file
227
tests/system/run-phase20-promoted-native-base-declaration-xcpng.sh
Executable file
@@ -0,0 +1,227 @@
|
|||||||
|
#!/bin/sh
|
||||||
|
set -eu
|
||||||
|
|
||||||
|
repo_root=${PROJECT_ROOT:-$(pwd)}
|
||||||
|
os_template=${OS_TEMPLATE:-$repo_root/tests/system/phase20-promoted-native-base-operating-system.scm.in}
|
||||||
|
system_name=${SYSTEM_NAME:-phase20-promoted-native-base-operating-system}
|
||||||
|
root_size=${ROOT_SIZE:-12g}
|
||||||
|
result_store=${RESULT_STORE:-}
|
||||||
|
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}
|
||||||
|
cleanup=0
|
||||||
|
|
||||||
|
if [ -n "${WORKDIR:-}" ]; then
|
||||||
|
workdir=$WORKDIR
|
||||||
|
mkdir -p "$workdir"
|
||||||
|
else
|
||||||
|
workdir=$(mktemp -d /tmp/fruix-phase20-promoted-native-base-xcpng.XXXXXX)
|
||||||
|
cleanup=1
|
||||||
|
fi
|
||||||
|
if [ "${KEEP_WORKDIR:-0}" -eq 1 ]; then
|
||||||
|
cleanup=0
|
||||||
|
fi
|
||||||
|
|
||||||
|
promotion_metadata=$workdir/phase20-promoted-native-base-promotion-metadata.txt
|
||||||
|
prepared_template=$workdir/phase20-promoted-native-base-operating-system.scm.in
|
||||||
|
inner_metadata=$workdir/phase20-promoted-native-base-inner-metadata.txt
|
||||||
|
metadata_file=$workdir/phase20-promoted-native-base-declaration-xcpng-metadata.txt
|
||||||
|
|
||||||
|
cleanup_workdir() {
|
||||||
|
if [ "$cleanup" -eq 1 ]; then
|
||||||
|
rm -rf "$workdir"
|
||||||
|
fi
|
||||||
|
}
|
||||||
|
trap cleanup_workdir EXIT INT TERM
|
||||||
|
|
||||||
|
if [ -z "$result_store" ]; then
|
||||||
|
KEEP_WORKDIR=1 WORKDIR="$workdir/promotion" METADATA_OUT="$promotion_metadata" \
|
||||||
|
ROOT_AUTHORIZED_KEY_FILE="$root_authorized_key_file" \
|
||||||
|
ROOT_SSH_PRIVATE_KEY_FILE="$root_ssh_private_key_file" \
|
||||||
|
ROOT_SIZE=20g \
|
||||||
|
"$repo_root/tests/system/run-phase20-host-initiated-native-build-store-promotion-xcpng.sh"
|
||||||
|
result_store=$(sed -n 's/^result_store=//p' "$promotion_metadata")
|
||||||
|
fi
|
||||||
|
|
||||||
|
[ -n "$result_store" ] || { echo "missing promoted result store" >&2; exit 1; }
|
||||||
|
[ -d "$result_store" ] || { echo "promoted result store does not exist: $result_store" >&2; exit 1; }
|
||||||
|
[ -f "$result_store/.fruix-native-build-result.scm" ] || {
|
||||||
|
echo "promoted result store is missing .fruix-native-build-result.scm" >&2
|
||||||
|
exit 1
|
||||||
|
}
|
||||||
|
[ -L "$result_store/artifacts/world" ] || { echo "promoted result store is missing world artifact link" >&2; exit 1; }
|
||||||
|
[ -L "$result_store/artifacts/kernel" ] || { echo "promoted result store is missing kernel artifact link" >&2; exit 1; }
|
||||||
|
[ -L "$result_store/artifacts/headers" ] || { echo "promoted result store is missing headers artifact link" >&2; exit 1; }
|
||||||
|
[ -L "$result_store/artifacts/bootloader" ] || { echo "promoted result store is missing bootloader artifact link" >&2; exit 1; }
|
||||||
|
|
||||||
|
world_store=$(readlink "$result_store/artifacts/world")
|
||||||
|
kernel_store=$(readlink "$result_store/artifacts/kernel")
|
||||||
|
headers_store=$(readlink "$result_store/artifacts/headers")
|
||||||
|
bootloader_store=$(readlink "$result_store/artifacts/bootloader")
|
||||||
|
result_metadata_file=$result_store/.fruix-native-build-result.scm
|
||||||
|
executor_kind=$(grep -o '(executor-kind \. [^)]*)' "$result_metadata_file" | head -n 1 | sed 's/(executor-kind \. //; s/)$//')
|
||||||
|
executor_name=$(grep -o '(executor-name \. "[^"]*")' "$result_metadata_file" | head -n 1 | sed 's/(executor-name \. "//; s/")$//')
|
||||||
|
executor_version=$(grep -o '(executor-version \. "[^"]*")' "$result_metadata_file" | head -n 1 | sed 's/(executor-version \. "//; s/")$//')
|
||||||
|
|
||||||
|
[ -f "$world_store/bin/sh" ] || { echo "promoted world store is missing /bin/sh" >&2; exit 1; }
|
||||||
|
[ -f "$kernel_store/boot/kernel/kernel" ] || { echo "promoted kernel store is missing kernel" >&2; exit 1; }
|
||||||
|
[ -f "$headers_store/usr/include/sys/param.h" ] || { echo "promoted headers store is missing param.h" >&2; exit 1; }
|
||||||
|
[ -f "$bootloader_store/boot/loader.efi" ] || { echo "promoted bootloader store is missing loader.efi" >&2; exit 1; }
|
||||||
|
|
||||||
|
sed "s|__PROMOTED_RESULT_STORE__|$result_store|g" "$os_template" > "$prepared_template"
|
||||||
|
|
||||||
|
action_metadata=${promotion_metadata:-}
|
||||||
|
ROOT_SIZE="$root_size" KEEP_WORKDIR=1 WORKDIR="$workdir/boot" METADATA_OUT="$inner_metadata" \
|
||||||
|
ROOT_AUTHORIZED_KEY_FILE="$root_authorized_key_file" \
|
||||||
|
ROOT_SSH_PRIVATE_KEY_FILE="$root_ssh_private_key_file" \
|
||||||
|
OS_TEMPLATE="$prepared_template" SYSTEM_NAME="$system_name" \
|
||||||
|
"$repo_root/tests/system/run-phase11-shepherd-pid1-xcpng.sh"
|
||||||
|
|
||||||
|
phase8_metadata=$(sed -n 's/^phase8_metadata=//p' "$inner_metadata")
|
||||||
|
closure_path=$(sed -n 's/^closure_path=//p' "$inner_metadata")
|
||||||
|
closure_base=$(sed -n 's/^closure_base=//p' "$inner_metadata")
|
||||||
|
guest_ip=$(sed -n 's/^guest_ip=//p' "$inner_metadata")
|
||||||
|
vm_id=$(sed -n 's/^vm_id=//p' "$inner_metadata")
|
||||||
|
vdi_id=$(sed -n 's/^vdi_id=//p' "$inner_metadata")
|
||||||
|
shepherd_pid=$(sed -n 's/^shepherd_pid=//p' "$inner_metadata")
|
||||||
|
sshd_status=$(sed -n 's/^sshd_status=//p' "$inner_metadata")
|
||||||
|
compat_prefix_shims=$(sed -n 's/^compat_prefix_shims=//p' "$inner_metadata")
|
||||||
|
guile_module_smoke=$(sed -n 's/^guile_module_smoke=//p' "$inner_metadata")
|
||||||
|
activate_log=$(sed -n 's/^activate_log=//p' "$inner_metadata")
|
||||||
|
|
||||||
|
promoted_result_file=$closure_path/metadata/promoted-native-build-result.scm
|
||||||
|
store_layout_file=$closure_path/metadata/store-layout.scm
|
||||||
|
|
||||||
|
[ "$shepherd_pid" = 1 ] || { echo "shepherd was not PID 1" >&2; exit 1; }
|
||||||
|
[ "$sshd_status" = running ] || { echo "sshd is not running" >&2; exit 1; }
|
||||||
|
[ "$compat_prefix_shims" = absent ] || { echo "compatibility prefix shims reappeared" >&2; exit 1; }
|
||||||
|
[ "$guile_module_smoke" = ok ] || { echo "guest Guile module smoke failed" >&2; exit 1; }
|
||||||
|
case "$activate_log" in
|
||||||
|
*fruix-activate:done*) : ;;
|
||||||
|
*) echo "activation log does not show success" >&2; exit 1 ;;
|
||||||
|
esac
|
||||||
|
|
||||||
|
[ -f "$promoted_result_file" ] || { echo "closure is missing promoted native build result metadata" >&2; exit 1; }
|
||||||
|
[ -f "$store_layout_file" ] || { echo "closure is missing store layout metadata" >&2; exit 1; }
|
||||||
|
grep -F "$result_store" "$promoted_result_file" >/dev/null || {
|
||||||
|
echo "closure promoted result metadata does not reference the selected result store" >&2
|
||||||
|
exit 1
|
||||||
|
}
|
||||||
|
grep -F "$result_store" "$closure_path/.references" >/dev/null || {
|
||||||
|
echo "closure references do not retain the promoted result store" >&2
|
||||||
|
exit 1
|
||||||
|
}
|
||||||
|
grep -F "$kernel_store" "$promoted_result_file" >/dev/null || {
|
||||||
|
echo "closure promoted result metadata does not reference the promoted kernel store" >&2
|
||||||
|
exit 1
|
||||||
|
}
|
||||||
|
grep -F "$bootloader_store" "$promoted_result_file" >/dev/null || {
|
||||||
|
echo "closure promoted result metadata does not reference the promoted bootloader store" >&2
|
||||||
|
exit 1
|
||||||
|
}
|
||||||
|
grep -F "$result_store" "$store_layout_file" >/dev/null || {
|
||||||
|
echo "store layout metadata does not record the promoted result store" >&2
|
||||||
|
exit 1
|
||||||
|
}
|
||||||
|
|
||||||
|
kernel_link=$(readlink "$closure_path/boot/kernel/kernel")
|
||||||
|
bootloader_link=$(readlink "$closure_path/boot/loader.efi")
|
||||||
|
[ "$kernel_link" = "$kernel_store/boot/kernel/kernel" ] || {
|
||||||
|
echo "closure kernel link does not target the promoted kernel store" >&2
|
||||||
|
exit 1
|
||||||
|
}
|
||||||
|
[ "$bootloader_link" = "$bootloader_store/boot/loader.efi" ] || {
|
||||||
|
echo "closure bootloader link does not target the promoted bootloader store" >&2
|
||||||
|
exit 1
|
||||||
|
}
|
||||||
|
|
||||||
|
ssh_guest() {
|
||||||
|
ssh -i "$root_ssh_private_key_file" \
|
||||||
|
-o BatchMode=yes \
|
||||||
|
-o StrictHostKeyChecking=no \
|
||||||
|
-o UserKnownHostsFile=/dev/null \
|
||||||
|
-o ConnectTimeout=5 \
|
||||||
|
root@"$guest_ip" "$@"
|
||||||
|
}
|
||||||
|
|
||||||
|
guest_promoted_metadata=$(ssh -i "$root_ssh_private_key_file" \
|
||||||
|
-o BatchMode=yes \
|
||||||
|
-o StrictHostKeyChecking=no \
|
||||||
|
-o UserKnownHostsFile=/dev/null \
|
||||||
|
-o ConnectTimeout=5 \
|
||||||
|
root@"$guest_ip" 'sh -s' <<EOF
|
||||||
|
set -eu
|
||||||
|
[ -f /run/current-system/metadata/promoted-native-build-result.scm ]
|
||||||
|
grep -F '$result_store' /run/current-system/metadata/promoted-native-build-result.scm >/dev/null
|
||||||
|
grep -F '$result_store' /run/current-system/.references >/dev/null
|
||||||
|
kernel_link=$(readlink /run/current-system/boot/kernel/kernel)
|
||||||
|
bootloader_link=$(readlink /run/current-system/boot/loader.efi)
|
||||||
|
[ "$kernel_link" = "$kernel_store/boot/kernel/kernel" ]
|
||||||
|
[ "$bootloader_link" = "$bootloader_store/boot/loader.efi" ]
|
||||||
|
[ -x /bin/sh ]
|
||||||
|
[ -x /usr/sbin/sshd ]
|
||||||
|
printf 'kernel_link=%s\n' "$kernel_link"
|
||||||
|
printf 'bootloader_link=%s\n' "$bootloader_link"
|
||||||
|
printf 'uname=%s\n' "$(uname -sr)"
|
||||||
|
printf 'promoted_result_store=%s\n' '$result_store'
|
||||||
|
EOF
|
||||||
|
)
|
||||||
|
|
||||||
|
guest_kernel_link=$(printf '%s\n' "$guest_promoted_metadata" | sed -n 's/^kernel_link=//p')
|
||||||
|
guest_bootloader_link=$(printf '%s\n' "$guest_promoted_metadata" | sed -n 's/^bootloader_link=//p')
|
||||||
|
guest_uname=$(printf '%s\n' "$guest_promoted_metadata" | sed -n 's/^uname=//p')
|
||||||
|
|
||||||
|
[ "$guest_kernel_link" = "$kernel_store/boot/kernel/kernel" ] || {
|
||||||
|
echo "guest kernel link does not target the promoted kernel store" >&2
|
||||||
|
exit 1
|
||||||
|
}
|
||||||
|
[ "$guest_bootloader_link" = "$bootloader_store/boot/loader.efi" ] || {
|
||||||
|
echo "guest bootloader link does not target the promoted bootloader store" >&2
|
||||||
|
exit 1
|
||||||
|
}
|
||||||
|
|
||||||
|
cat >"$metadata_file" <<EOF
|
||||||
|
workdir=$workdir
|
||||||
|
promotion_metadata=$promotion_metadata
|
||||||
|
inner_metadata=$inner_metadata
|
||||||
|
phase8_metadata=$phase8_metadata
|
||||||
|
closure_path=$closure_path
|
||||||
|
closure_base=$closure_base
|
||||||
|
vm_id=$vm_id
|
||||||
|
vdi_id=$vdi_id
|
||||||
|
guest_ip=$guest_ip
|
||||||
|
root_size=$root_size
|
||||||
|
result_store=$result_store
|
||||||
|
result_metadata_file=$result_metadata_file
|
||||||
|
executor_kind=$executor_kind
|
||||||
|
executor_name=$executor_name
|
||||||
|
executor_version=$executor_version
|
||||||
|
world_store=$world_store
|
||||||
|
kernel_store=$kernel_store
|
||||||
|
headers_store=$headers_store
|
||||||
|
bootloader_store=$bootloader_store
|
||||||
|
promoted_result_file=$promoted_result_file
|
||||||
|
store_layout_file=$store_layout_file
|
||||||
|
kernel_link=$kernel_link
|
||||||
|
bootloader_link=$bootloader_link
|
||||||
|
guest_kernel_link=$guest_kernel_link
|
||||||
|
guest_bootloader_link=$guest_bootloader_link
|
||||||
|
guest_uname=$guest_uname
|
||||||
|
boot_backend=xcp-ng-xo-cli
|
||||||
|
init_mode=shepherd-pid1
|
||||||
|
promoted_native_base_declaration=ok
|
||||||
|
EOF
|
||||||
|
|
||||||
|
if [ -n "$metadata_target" ]; then
|
||||||
|
mkdir -p "$(dirname "$metadata_target")"
|
||||||
|
cp "$metadata_file" "$metadata_target"
|
||||||
|
fi
|
||||||
|
|
||||||
|
printf 'PASS phase20-promoted-native-base-declaration-xcpng\n'
|
||||||
|
printf 'Work directory: %s\n' "$workdir"
|
||||||
|
printf 'Metadata file: %s\n' "$metadata_file"
|
||||||
|
if [ -n "$metadata_target" ]; then
|
||||||
|
printf 'Copied metadata to: %s\n' "$metadata_target"
|
||||||
|
fi
|
||||||
|
printf '%s\n' '--- metadata ---'
|
||||||
|
cat "$metadata_file"
|
||||||
266
tests/system/run-phase20-self-hosted-native-build-xcpng.sh
Executable file
266
tests/system/run-phase20-self-hosted-native-build-xcpng.sh
Executable file
@@ -0,0 +1,266 @@
|
|||||||
|
#!/bin/sh
|
||||||
|
set -eu
|
||||||
|
|
||||||
|
repo_root=${PROJECT_ROOT:-$(pwd)}
|
||||||
|
os_template=${OS_TEMPLATE:-$repo_root/tests/system/phase20-development-operating-system.scm.in}
|
||||||
|
system_name=${SYSTEM_NAME:-phase20-operating-system}
|
||||||
|
root_size=${ROOT_SIZE:-20g}
|
||||||
|
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}
|
||||||
|
cleanup=0
|
||||||
|
|
||||||
|
if [ -n "${WORKDIR:-}" ]; then
|
||||||
|
workdir=$WORKDIR
|
||||||
|
mkdir -p "$workdir"
|
||||||
|
else
|
||||||
|
workdir=$(mktemp -d /tmp/fruix-phase20-self-hosted-native-build-xcpng.XXXXXX)
|
||||||
|
cleanup=1
|
||||||
|
fi
|
||||||
|
if [ "${KEEP_WORKDIR:-0}" -eq 1 ]; then
|
||||||
|
cleanup=0
|
||||||
|
fi
|
||||||
|
|
||||||
|
inner_metadata=$workdir/phase20-self-hosted-inner-metadata.txt
|
||||||
|
metadata_file=$workdir/phase20-self-hosted-native-build-xcpng-metadata.txt
|
||||||
|
|
||||||
|
action_cleanup() {
|
||||||
|
if [ "$cleanup" -eq 1 ]; then
|
||||||
|
rm -rf "$workdir"
|
||||||
|
fi
|
||||||
|
}
|
||||||
|
trap action_cleanup EXIT INT TERM
|
||||||
|
|
||||||
|
KEEP_WORKDIR=1 WORKDIR="$workdir/inner" METADATA_OUT="$inner_metadata" \
|
||||||
|
ROOT_AUTHORIZED_KEY_FILE="$root_authorized_key_file" \
|
||||||
|
ROOT_SSH_PRIVATE_KEY_FILE="$root_ssh_private_key_file" \
|
||||||
|
OS_TEMPLATE="$os_template" SYSTEM_NAME="$system_name" ROOT_SIZE="$root_size" \
|
||||||
|
"$repo_root/tests/system/run-phase20-development-environment-xcpng.sh"
|
||||||
|
|
||||||
|
phase8_metadata=$(sed -n 's/^phase8_metadata=//p' "$inner_metadata")
|
||||||
|
closure_path=$(sed -n 's/^closure_path=//p' "$inner_metadata")
|
||||||
|
closure_base=$(sed -n 's/^closure_base=//p' "$inner_metadata")
|
||||||
|
guest_ip=$(sed -n 's/^guest_ip=//p' "$inner_metadata")
|
||||||
|
vm_id=$(sed -n 's/^vm_id=//p' "$inner_metadata")
|
||||||
|
vdi_id=$(sed -n 's/^vdi_id=//p' "$inner_metadata")
|
||||||
|
shepherd_pid=$(sed -n 's/^shepherd_pid=//p' "$inner_metadata")
|
||||||
|
sshd_status=$(sed -n 's/^sshd_status=//p' "$inner_metadata")
|
||||||
|
compat_prefix_shims=$(sed -n 's/^compat_prefix_shims=//p' "$inner_metadata")
|
||||||
|
guile_module_smoke=$(sed -n 's/^guile_module_smoke=//p' "$inner_metadata")
|
||||||
|
|
||||||
|
[ "$shepherd_pid" = 1 ] || { echo "shepherd was not PID 1" >&2; exit 1; }
|
||||||
|
[ "$sshd_status" = running ] || { echo "sshd is not running" >&2; exit 1; }
|
||||||
|
[ "$compat_prefix_shims" = absent ] || { echo "compatibility prefix shims reappeared" >&2; exit 1; }
|
||||||
|
[ "$guile_module_smoke" = ok ] || { echo "guest Guile module smoke failed" >&2; exit 1; }
|
||||||
|
|
||||||
|
ssh_guest() {
|
||||||
|
ssh -i "$root_ssh_private_key_file" \
|
||||||
|
-o BatchMode=yes \
|
||||||
|
-o StrictHostKeyChecking=no \
|
||||||
|
-o UserKnownHostsFile=/dev/null \
|
||||||
|
-o ConnectTimeout=5 \
|
||||||
|
root@"$guest_ip" "$@"
|
||||||
|
}
|
||||||
|
|
||||||
|
guest_build_jobs=${GUEST_BUILD_JOBS:-$(ssh_guest 'sysctl -n hw.ncpu')}
|
||||||
|
case "$guest_build_jobs" in
|
||||||
|
''|*[!0-9]*)
|
||||||
|
echo "invalid guest build job count: $guest_build_jobs" >&2
|
||||||
|
exit 1
|
||||||
|
;;
|
||||||
|
esac
|
||||||
|
|
||||||
|
ssh_guest '[ -x /usr/local/bin/fruix-build-environment ]'
|
||||||
|
ssh_guest '[ -x /usr/local/bin/fruix-self-hosted-native-build ]'
|
||||||
|
ssh_guest '[ -L /run/current-build ]'
|
||||||
|
ssh_guest '[ -L /usr/include ]'
|
||||||
|
ssh_guest '[ -L /usr/share/mk ]'
|
||||||
|
|
||||||
|
self_hosted_metadata=$(ssh_guest env FRUIX_SELF_HOSTED_NATIVE_BUILD_JOBS="$guest_build_jobs" /usr/local/bin/fruix-self-hosted-native-build)
|
||||||
|
|
||||||
|
run_id=$(printf '%s\n' "$self_hosted_metadata" | sed -n 's/^run_id=//p')
|
||||||
|
helper_version=$(printf '%s\n' "$self_hosted_metadata" | sed -n 's/^helper_version=//p')
|
||||||
|
executor_kind=$(printf '%s\n' "$self_hosted_metadata" | sed -n 's/^executor_kind=//p')
|
||||||
|
executor_name=$(printf '%s\n' "$self_hosted_metadata" | sed -n 's/^executor_name=//p')
|
||||||
|
executor_version=$(printf '%s\n' "$self_hosted_metadata" | sed -n 's/^executor_version=//p')
|
||||||
|
build_profile=$(printf '%s\n' "$self_hosted_metadata" | sed -n 's/^build_profile=//p')
|
||||||
|
source_store=$(printf '%s\n' "$self_hosted_metadata" | sed -n 's/^source_store=//p')
|
||||||
|
source_root=$(printf '%s\n' "$self_hosted_metadata" | sed -n 's/^source_root=//p')
|
||||||
|
build_jobs=$(printf '%s\n' "$self_hosted_metadata" | sed -n 's/^build_jobs=//p')
|
||||||
|
build_common=$(printf '%s\n' "$self_hosted_metadata" | sed -n 's/^build_common=//p')
|
||||||
|
install_common=$(printf '%s\n' "$self_hosted_metadata" | sed -n 's/^install_common=//p')
|
||||||
|
build_root=$(printf '%s\n' "$self_hosted_metadata" | sed -n 's/^build_root=//p')
|
||||||
|
result_root=$(printf '%s\n' "$self_hosted_metadata" | sed -n 's/^result_root=//p')
|
||||||
|
logdir=$(printf '%s\n' "$self_hosted_metadata" | sed -n 's/^logdir=//p')
|
||||||
|
status_file=$(printf '%s\n' "$self_hosted_metadata" | sed -n 's/^status_file=//p')
|
||||||
|
guest_metadata_file=$(printf '%s\n' "$self_hosted_metadata" | sed -n 's/^metadata_file=//p')
|
||||||
|
promotion_file=$(printf '%s\n' "$self_hosted_metadata" | sed -n 's/^promotion_file=//p')
|
||||||
|
world_stage=$(printf '%s\n' "$self_hosted_metadata" | sed -n 's/^world_stage=//p')
|
||||||
|
kernel_stage=$(printf '%s\n' "$self_hosted_metadata" | sed -n 's/^kernel_stage=//p')
|
||||||
|
world_artifact=$(printf '%s\n' "$self_hosted_metadata" | sed -n 's/^world_artifact=//p')
|
||||||
|
kernel_artifact=$(printf '%s\n' "$self_hosted_metadata" | sed -n 's/^kernel_artifact=//p')
|
||||||
|
headers_artifact=$(printf '%s\n' "$self_hosted_metadata" | sed -n 's/^headers_artifact=//p')
|
||||||
|
bootloader_artifact=$(printf '%s\n' "$self_hosted_metadata" | sed -n 's/^bootloader_artifact=//p')
|
||||||
|
latest_link=$(printf '%s\n' "$self_hosted_metadata" | sed -n 's/^latest_link=//p')
|
||||||
|
root_df=$(printf '%s\n' "$self_hosted_metadata" | sed -n 's/^root_df=//p')
|
||||||
|
build_root_size=$(printf '%s\n' "$self_hosted_metadata" | sed -n 's/^build_root_size=//p')
|
||||||
|
result_root_size=$(printf '%s\n' "$self_hosted_metadata" | sed -n 's/^result_root_size=//p')
|
||||||
|
world_artifact_size=$(printf '%s\n' "$self_hosted_metadata" | sed -n 's/^world_artifact_size=//p')
|
||||||
|
kernel_artifact_size=$(printf '%s\n' "$self_hosted_metadata" | sed -n 's/^kernel_artifact_size=//p')
|
||||||
|
headers_artifact_size=$(printf '%s\n' "$self_hosted_metadata" | sed -n 's/^headers_artifact_size=//p')
|
||||||
|
bootloader_artifact_size=$(printf '%s\n' "$self_hosted_metadata" | sed -n 's/^bootloader_artifact_size=//p')
|
||||||
|
sha_kernel=$(printf '%s\n' "$self_hosted_metadata" | sed -n 's/^sha_kernel=//p')
|
||||||
|
sha_loader=$(printf '%s\n' "$self_hosted_metadata" | sed -n 's/^sha_loader=//p')
|
||||||
|
sha_param=$(printf '%s\n' "$self_hosted_metadata" | sed -n 's/^sha_param=//p')
|
||||||
|
buildworld_tail=$(printf '%s\n' "$self_hosted_metadata" | sed -n 's/^buildworld_tail=//p')
|
||||||
|
buildkernel_tail=$(printf '%s\n' "$self_hosted_metadata" | sed -n 's/^buildkernel_tail=//p')
|
||||||
|
installworld_tail=$(printf '%s\n' "$self_hosted_metadata" | sed -n 's/^installworld_tail=//p')
|
||||||
|
distribution_tail=$(printf '%s\n' "$self_hosted_metadata" | sed -n 's/^distribution_tail=//p')
|
||||||
|
installkernel_tail=$(printf '%s\n' "$self_hosted_metadata" | sed -n 's/^installkernel_tail=//p')
|
||||||
|
self_hosted_native_build=$(printf '%s\n' "$self_hosted_metadata" | sed -n 's/^self_hosted_native_build=//p')
|
||||||
|
|
||||||
|
status_value=$(ssh_guest "cat '$status_file'")
|
||||||
|
latest_target=$(ssh_guest "readlink '$latest_link'")
|
||||||
|
ssh_guest "[ -f '$promotion_file' ]"
|
||||||
|
ssh_guest "[ -f '$world_artifact/bin/sh' ]"
|
||||||
|
|
||||||
|
[ "$helper_version" = 5 ] || { echo "unexpected helper version: $helper_version" >&2; exit 1; }
|
||||||
|
[ "$executor_kind" = self-hosted ] || { echo "unexpected executor kind: $executor_kind" >&2; exit 1; }
|
||||||
|
[ "$executor_name" = guest-self-hosted ] || { echo "unexpected executor name: $executor_name" >&2; exit 1; }
|
||||||
|
[ "$executor_version" = 5 ] || { echo "unexpected executor version: $executor_version" >&2; exit 1; }
|
||||||
|
[ "$build_profile" = /run/current-system/build-profile ] || { echo "unexpected build profile: $build_profile" >&2; exit 1; }
|
||||||
|
[ "$build_jobs" = "$guest_build_jobs" ] || { echo "unexpected build job count: $build_jobs" >&2; exit 1; }
|
||||||
|
[ "$status_value" = ok ] || { echo "self-hosted build status is not ok: $status_value" >&2; exit 1; }
|
||||||
|
[ "$latest_target" = "$result_root" ] || { echo "latest link target mismatch: $latest_target" >&2; exit 1; }
|
||||||
|
[ "$self_hosted_native_build" = ok ] || { echo "self-hosted build marker missing" >&2; exit 1; }
|
||||||
|
|
||||||
|
case "$source_store" in
|
||||||
|
/frx/store/*-freebsd-source-*) : ;;
|
||||||
|
*) echo "unexpected source store path: $source_store" >&2; exit 1 ;;
|
||||||
|
esac
|
||||||
|
case "$source_root" in
|
||||||
|
/frx/store/*-freebsd-source-*/*) : ;;
|
||||||
|
*) echo "unexpected source root path: $source_root" >&2; exit 1 ;;
|
||||||
|
esac
|
||||||
|
case "$build_root" in
|
||||||
|
/var/tmp/fruix-self-hosted-native-builds/*) : ;;
|
||||||
|
*) echo "unexpected build root: $build_root" >&2; exit 1 ;;
|
||||||
|
esac
|
||||||
|
case "$result_root" in
|
||||||
|
/var/lib/fruix/native-builds/*) : ;;
|
||||||
|
*) echo "unexpected result root: $result_root" >&2; exit 1 ;;
|
||||||
|
esac
|
||||||
|
case "$latest_link" in
|
||||||
|
/var/lib/fruix/native-builds/latest) : ;;
|
||||||
|
*) echo "unexpected latest link path: $latest_link" >&2; exit 1 ;;
|
||||||
|
esac
|
||||||
|
printf '%s\n' "$run_id" | grep -E '^[0-9]{8}T[0-9]{6}Z$' >/dev/null || {
|
||||||
|
echo "unexpected run id: $run_id" >&2
|
||||||
|
exit 1
|
||||||
|
}
|
||||||
|
printf '%s\n' "$sha_kernel" | grep -E '^[0-9a-f]{64}$' >/dev/null || {
|
||||||
|
echo "invalid kernel sha256: $sha_kernel" >&2
|
||||||
|
exit 1
|
||||||
|
}
|
||||||
|
printf '%s\n' "$sha_loader" | grep -E '^[0-9a-f]{64}$' >/dev/null || {
|
||||||
|
echo "invalid loader sha256: $sha_loader" >&2
|
||||||
|
exit 1
|
||||||
|
}
|
||||||
|
printf '%s\n' "$sha_param" | grep -E '^[0-9a-f]{64}$' >/dev/null || {
|
||||||
|
echo "invalid param.h sha256: $sha_param" >&2
|
||||||
|
exit 1
|
||||||
|
}
|
||||||
|
case "$buildworld_tail" in
|
||||||
|
*'World build completed on'*) : ;;
|
||||||
|
*) echo "buildworld log does not show completion" >&2; exit 1 ;;
|
||||||
|
esac
|
||||||
|
case "$buildkernel_tail" in
|
||||||
|
*'Kernel(s) GENERIC built in'*) : ;;
|
||||||
|
*) echo "buildkernel log does not show successful kernel build" >&2; exit 1 ;;
|
||||||
|
esac
|
||||||
|
case "$installworld_tail" in
|
||||||
|
*'Install world completed in'*) : ;;
|
||||||
|
*) echo "installworld log does not show successful completion" >&2; exit 1 ;;
|
||||||
|
esac
|
||||||
|
case "$installkernel_tail" in
|
||||||
|
*'Install kernel(s) GENERIC completed in'*) : ;;
|
||||||
|
*) echo "installkernel log does not show successful completion" >&2; exit 1 ;;
|
||||||
|
esac
|
||||||
|
printf '%s\n' "$build_common" | grep -F 'TARGET=amd64 TARGET_ARCH=amd64 KERNCONF=GENERIC' >/dev/null || {
|
||||||
|
echo "unexpected build_common flags: $build_common" >&2
|
||||||
|
exit 1
|
||||||
|
}
|
||||||
|
printf '%s\n' "$install_common" | grep -F 'DB_FROM_SRC=yes' >/dev/null || {
|
||||||
|
echo "install_common is missing DB_FROM_SRC=yes" >&2
|
||||||
|
exit 1
|
||||||
|
}
|
||||||
|
|
||||||
|
cat >"$metadata_file" <<EOF
|
||||||
|
workdir=$workdir
|
||||||
|
inner_metadata=$inner_metadata
|
||||||
|
phase8_metadata=$phase8_metadata
|
||||||
|
closure_path=$closure_path
|
||||||
|
closure_base=$closure_base
|
||||||
|
vm_id=$vm_id
|
||||||
|
vdi_id=$vdi_id
|
||||||
|
guest_ip=$guest_ip
|
||||||
|
root_size=$root_size
|
||||||
|
run_id=$run_id
|
||||||
|
helper_version=$helper_version
|
||||||
|
executor_kind=$executor_kind
|
||||||
|
executor_name=$executor_name
|
||||||
|
executor_version=$executor_version
|
||||||
|
build_jobs=$build_jobs
|
||||||
|
build_profile=$build_profile
|
||||||
|
source_store=$source_store
|
||||||
|
source_root=$source_root
|
||||||
|
build_common=$build_common
|
||||||
|
install_common=$install_common
|
||||||
|
build_root=$build_root
|
||||||
|
result_root=$result_root
|
||||||
|
logdir=$logdir
|
||||||
|
status_file=$status_file
|
||||||
|
guest_metadata_file=$guest_metadata_file
|
||||||
|
promotion_file=$promotion_file
|
||||||
|
world_stage=$world_stage
|
||||||
|
kernel_stage=$kernel_stage
|
||||||
|
world_artifact=$world_artifact
|
||||||
|
kernel_artifact=$kernel_artifact
|
||||||
|
headers_artifact=$headers_artifact
|
||||||
|
bootloader_artifact=$bootloader_artifact
|
||||||
|
latest_link=$latest_link
|
||||||
|
latest_target=$latest_target
|
||||||
|
status_value=$status_value
|
||||||
|
root_df=$root_df
|
||||||
|
build_root_size=$build_root_size
|
||||||
|
result_root_size=$result_root_size
|
||||||
|
world_artifact_size=$world_artifact_size
|
||||||
|
kernel_artifact_size=$kernel_artifact_size
|
||||||
|
headers_artifact_size=$headers_artifact_size
|
||||||
|
bootloader_artifact_size=$bootloader_artifact_size
|
||||||
|
sha_kernel=$sha_kernel
|
||||||
|
sha_loader=$sha_loader
|
||||||
|
sha_param=$sha_param
|
||||||
|
buildworld_tail=$buildworld_tail
|
||||||
|
buildkernel_tail=$buildkernel_tail
|
||||||
|
installworld_tail=$installworld_tail
|
||||||
|
distribution_tail=$distribution_tail
|
||||||
|
installkernel_tail=$installkernel_tail
|
||||||
|
boot_backend=xcp-ng-xo-cli
|
||||||
|
init_mode=shepherd-pid1
|
||||||
|
self_hosted_native_build=ok
|
||||||
|
EOF
|
||||||
|
|
||||||
|
if [ -n "$metadata_target" ]; then
|
||||||
|
mkdir -p "$(dirname "$metadata_target")"
|
||||||
|
cp "$metadata_file" "$metadata_target"
|
||||||
|
fi
|
||||||
|
|
||||||
|
printf 'PASS phase20-self-hosted-native-build-xcpng\n'
|
||||||
|
printf 'Work directory: %s\n' "$workdir"
|
||||||
|
printf 'Metadata file: %s\n' "$metadata_file"
|
||||||
|
if [ -n "$metadata_target" ]; then
|
||||||
|
printf 'Copied metadata to: %s\n' "$metadata_target"
|
||||||
|
fi
|
||||||
|
printf '%s\n' '--- metadata ---'
|
||||||
|
cat "$metadata_file"
|
||||||
@@ -104,11 +104,11 @@ image_size_bytes=$(stat -f '%z' "$disk_image")
|
|||||||
closure_base=$(basename "$closure_path")
|
closure_base=$(basename "$closure_path")
|
||||||
|
|
||||||
case "$image_store_path" in
|
case "$image_store_path" in
|
||||||
/frx/store/*-fruix-bhyve-image-fruix-freebsd) : ;;
|
/frx/store/*-fruix-bhyve-image-*) : ;;
|
||||||
*) echo "unexpected image store path: $image_store_path" >&2; exit 1 ;;
|
*) echo "unexpected image store path: $image_store_path" >&2; exit 1 ;;
|
||||||
esac
|
esac
|
||||||
case "$disk_image" in
|
case "$disk_image" in
|
||||||
/frx/store/*-fruix-bhyve-image-fruix-freebsd/disk.img) : ;;
|
/frx/store/*-fruix-bhyve-image-*/disk.img) : ;;
|
||||||
*) echo "unexpected disk image path: $disk_image" >&2; exit 1 ;;
|
*) echo "unexpected disk image path: $disk_image" >&2; exit 1 ;;
|
||||||
esac
|
esac
|
||||||
|
|
||||||
@@ -142,7 +142,7 @@ if [ -L "$mnt_root/etc/master.passwd" ]; then master_passwd_kind=symlink; elif [
|
|||||||
loader_conf_image=$mnt_root/frx/store/$closure_base/boot/loader.conf
|
loader_conf_image=$mnt_root/frx/store/$closure_base/boot/loader.conf
|
||||||
rc_conf_image=$mnt_root/frx/store/$closure_base/etc/rc.conf
|
rc_conf_image=$mnt_root/frx/store/$closure_base/etc/rc.conf
|
||||||
grep -F 'comconsole' "$loader_conf_image" >/dev/null || { echo "loader.conf is missing serial console config" >&2; exit 1; }
|
grep -F 'comconsole' "$loader_conf_image" >/dev/null || { echo "loader.conf is missing serial console config" >&2; exit 1; }
|
||||||
grep -F 'hostname="fruix-freebsd"' "$rc_conf_image" >/dev/null || { echo "rc.conf is missing hostname" >&2; exit 1; }
|
grep -E '^hostname=".+"$' "$rc_conf_image" >/dev/null || { echo "rc.conf is missing hostname" >&2; exit 1; }
|
||||||
[ -f "$host_base_provenance_file" ] || { echo "missing host base provenance file: $host_base_provenance_file" >&2; exit 1; }
|
[ -f "$host_base_provenance_file" ] || { echo "missing host base provenance file: $host_base_provenance_file" >&2; exit 1; }
|
||||||
[ -f "$store_layout_file" ] || { echo "missing store layout file: $store_layout_file" >&2; exit 1; }
|
[ -f "$store_layout_file" ] || { echo "missing store layout file: $store_layout_file" >&2; exit 1; }
|
||||||
[ -n "$host_freebsd_version" ] || { echo "missing host freebsd version provenance" >&2; exit 1; }
|
[ -n "$host_freebsd_version" ] || { echo "missing host freebsd version provenance" >&2; exit 1; }
|
||||||
|
|||||||
271
tests/system/run-postphase20-installed-node-build-reconfigure-xcpng.sh
Executable file
271
tests/system/run-postphase20-installed-node-build-reconfigure-xcpng.sh
Executable file
@@ -0,0 +1,271 @@
|
|||||||
|
#!/bin/sh
|
||||||
|
set -eu
|
||||||
|
|
||||||
|
repo_root=${PROJECT_ROOT:-$(pwd)}
|
||||||
|
os_template=${OS_TEMPLATE:-$repo_root/tests/system/postphase20-installed-node-operating-system.scm.in}
|
||||||
|
system_name=${SYSTEM_NAME:-postphase20-installed-node-operating-system}
|
||||||
|
result_store=${RESULT_STORE:-/frx/store/ffe44f5d1ba576e1f811ad3fe3a526a242b5c4a5-fruix-native-build-result-15.0-STABLE-ssh-guest}
|
||||||
|
root_size=${ROOT_SIZE:-12g}
|
||||||
|
current_host_name=${CURRENT_HOST_NAME:-fruix-node-current}
|
||||||
|
candidate_host_name=${CANDIDATE_HOST_NAME:-fruix-node-canary}
|
||||||
|
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}
|
||||||
|
cleanup=0
|
||||||
|
|
||||||
|
if [ -n "${WORKDIR:-}" ]; then
|
||||||
|
workdir=$WORKDIR
|
||||||
|
mkdir -p "$workdir"
|
||||||
|
else
|
||||||
|
workdir=$(mktemp -d /tmp/fruix-postphase20-installed-node-xcpng.XXXXXX)
|
||||||
|
cleanup=1
|
||||||
|
fi
|
||||||
|
if [ "${KEEP_WORKDIR:-0}" -eq 1 ]; then
|
||||||
|
cleanup=0
|
||||||
|
fi
|
||||||
|
|
||||||
|
current_os_file=$workdir/current-operating-system.scm
|
||||||
|
candidate_os_file=$workdir/candidate-operating-system.scm
|
||||||
|
inner_metadata=$workdir/postphase20-installed-node-inner-metadata.txt
|
||||||
|
current_build_out=$workdir/current-build.txt
|
||||||
|
candidate_build_out=$workdir/candidate-build.txt
|
||||||
|
reconfigure_out=$workdir/reconfigure.txt
|
||||||
|
rollback_out=$workdir/rollback.txt
|
||||||
|
post_reconfigure_status=$workdir/post-reconfigure-status.txt
|
||||||
|
post_boot_candidate_status=$workdir/post-boot-candidate-status.txt
|
||||||
|
post_boot_rollback_status=$workdir/post-boot-rollback-status.txt
|
||||||
|
metadata_file=$workdir/postphase20-installed-node-build-reconfigure-xcpng-metadata.txt
|
||||||
|
|
||||||
|
cleanup_workdir() {
|
||||||
|
if [ "$cleanup" -eq 1 ]; then
|
||||||
|
rm -rf "$workdir"
|
||||||
|
fi
|
||||||
|
}
|
||||||
|
trap cleanup_workdir EXIT INT TERM
|
||||||
|
|
||||||
|
[ -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; }
|
||||||
|
[ -d "$result_store" ] || { echo "promoted result store does not exist: $result_store" >&2; exit 1; }
|
||||||
|
|
||||||
|
root_authorized_key=$(tr -d '\n' < "$root_authorized_key_file")
|
||||||
|
|
||||||
|
render_os() {
|
||||||
|
output=$1
|
||||||
|
host_name=$2
|
||||||
|
sed \
|
||||||
|
-e "s|__PROMOTED_RESULT_STORE__|$result_store|g" \
|
||||||
|
-e "s|__HOST_NAME__|$host_name|g" \
|
||||||
|
-e "s|__ROOT_AUTHORIZED_KEY__|$root_authorized_key|g" \
|
||||||
|
"$os_template" > "$output"
|
||||||
|
}
|
||||||
|
|
||||||
|
render_os "$current_os_file" "$current_host_name"
|
||||||
|
render_os "$candidate_os_file" "$candidate_host_name"
|
||||||
|
|
||||||
|
KEEP_WORKDIR=1 WORKDIR="$workdir/boot" METADATA_OUT="$inner_metadata" \
|
||||||
|
ROOT_AUTHORIZED_KEY_FILE="$root_authorized_key_file" \
|
||||||
|
ROOT_SSH_PRIVATE_KEY_FILE="$root_ssh_private_key_file" \
|
||||||
|
OS_TEMPLATE="$current_os_file" SYSTEM_NAME="$system_name" ROOT_SIZE="$root_size" \
|
||||||
|
"$repo_root/tests/system/run-phase11-shepherd-pid1-xcpng.sh"
|
||||||
|
|
||||||
|
phase8_metadata=$(sed -n 's/^phase8_metadata=//p' "$inner_metadata")
|
||||||
|
closure_path=$(sed -n 's/^closure_path=//p' "$inner_metadata")
|
||||||
|
closure_base=$(sed -n 's/^closure_base=//p' "$inner_metadata")
|
||||||
|
guest_ip=$(sed -n 's/^guest_ip=//p' "$inner_metadata")
|
||||||
|
vm_id=$(sed -n 's/^vm_id=//p' "$inner_metadata")
|
||||||
|
vdi_id=$(sed -n 's/^vdi_id=//p' "$inner_metadata")
|
||||||
|
shepherd_pid=$(sed -n 's/^shepherd_pid=//p' "$inner_metadata")
|
||||||
|
sshd_status=$(sed -n 's/^sshd_status=//p' "$inner_metadata")
|
||||||
|
compat_prefix_shims=$(sed -n 's/^compat_prefix_shims=//p' "$inner_metadata")
|
||||||
|
guile_module_smoke=$(sed -n 's/^guile_module_smoke=//p' "$inner_metadata")
|
||||||
|
activate_log=$(sed -n 's/^activate_log=//p' "$inner_metadata")
|
||||||
|
|
||||||
|
[ "$shepherd_pid" = 1 ] || { echo "shepherd was not PID 1" >&2; exit 1; }
|
||||||
|
[ "$sshd_status" = running ] || { echo "sshd is not running" >&2; exit 1; }
|
||||||
|
[ "$compat_prefix_shims" = absent ] || { echo "compatibility prefix shims reappeared" >&2; exit 1; }
|
||||||
|
[ "$guile_module_smoke" = ok ] || { echo "guest Guile module smoke failed" >&2; exit 1; }
|
||||||
|
case "$activate_log" in
|
||||||
|
*fruix-activate:done*) : ;;
|
||||||
|
*) echo "activation log does not show success" >&2; exit 1 ;;
|
||||||
|
esac
|
||||||
|
|
||||||
|
for path in \
|
||||||
|
"$closure_path/metadata/system-declaration.scm" \
|
||||||
|
"$closure_path/metadata/system-declaration-info.scm" \
|
||||||
|
"$closure_path/metadata/system-declaration-system" \
|
||||||
|
"$closure_path/share/fruix/node/scripts/fruix.scm" \
|
||||||
|
"$closure_path/share/fruix/node/modules/fruix/system/freebsd/render.scm" \
|
||||||
|
"$closure_path/share/fruix/node/guix/guix/build/utils.scm"
|
||||||
|
do
|
||||||
|
[ -f "$path" ] || {
|
||||||
|
echo "required installed-node path missing: $path" >&2
|
||||||
|
exit 1
|
||||||
|
}
|
||||||
|
done
|
||||||
|
|
||||||
|
grep -F "$current_host_name" "$closure_path/metadata/system-declaration.scm" >/dev/null || {
|
||||||
|
echo "embedded declaration does not mention current host name" >&2
|
||||||
|
exit 1
|
||||||
|
}
|
||||||
|
grep -F "$system_name" "$closure_path/metadata/system-declaration-system" >/dev/null || {
|
||||||
|
echo "embedded declaration system name is missing" >&2
|
||||||
|
exit 1
|
||||||
|
}
|
||||||
|
|
||||||
|
ssh_guest() {
|
||||||
|
ssh -i "$root_ssh_private_key_file" \
|
||||||
|
-o BatchMode=yes \
|
||||||
|
-o StrictHostKeyChecking=no \
|
||||||
|
-o UserKnownHostsFile=/dev/null \
|
||||||
|
-o ConnectTimeout=5 \
|
||||||
|
root@"$guest_ip" "$@"
|
||||||
|
}
|
||||||
|
|
||||||
|
scp_guest() {
|
||||||
|
scp -O -i "$root_ssh_private_key_file" \
|
||||||
|
-o BatchMode=yes \
|
||||||
|
-o StrictHostKeyChecking=no \
|
||||||
|
-o UserKnownHostsFile=/dev/null \
|
||||||
|
-o ConnectTimeout=5 \
|
||||||
|
"$@"
|
||||||
|
}
|
||||||
|
|
||||||
|
wait_for_ssh() {
|
||||||
|
for attempt in $(jot 120 1 120); do
|
||||||
|
if ssh_guest 'service sshd onestatus >/dev/null 2>&1' >/dev/null 2>&1; then
|
||||||
|
return 0
|
||||||
|
fi
|
||||||
|
sleep 2
|
||||||
|
done
|
||||||
|
return 1
|
||||||
|
}
|
||||||
|
|
||||||
|
reboot_guest() {
|
||||||
|
ssh_guest 'shutdown -r now >/dev/null 2>&1 || reboot >/dev/null 2>&1 || true' >/dev/null 2>&1 || true
|
||||||
|
sleep 5
|
||||||
|
wait_for_ssh || {
|
||||||
|
echo "guest did not return over SSH after reboot" >&2
|
||||||
|
exit 1
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
ssh_guest 'sh -s' <<EOF
|
||||||
|
set -eu
|
||||||
|
[ -x /usr/local/bin/fruix ]
|
||||||
|
[ -f /run/current-system/metadata/system-declaration.scm ]
|
||||||
|
[ -f /run/current-system/metadata/system-declaration-info.scm ]
|
||||||
|
[ -f /run/current-system/metadata/system-declaration-system ]
|
||||||
|
[ -f /run/current-system/share/fruix/node/scripts/fruix.scm ]
|
||||||
|
[ -f /run/current-system/share/fruix/node/modules/fruix/system/freebsd/media.scm ]
|
||||||
|
[ -f /run/current-system/share/fruix/node/guix/guix/build/utils.scm ]
|
||||||
|
grep -F '$system_name' /run/current-system/metadata/system-declaration-system >/dev/null
|
||||||
|
EOF
|
||||||
|
|
||||||
|
ssh_guest '/usr/local/bin/fruix system build' > "$current_build_out"
|
||||||
|
current_built_closure=$(sed -n 's/^closure_path=//p' "$current_build_out" | tail -n 1)
|
||||||
|
[ -n "$current_built_closure" ] || { echo "missing closure_path from in-system build output" >&2; exit 1; }
|
||||||
|
case "$current_built_closure" in
|
||||||
|
/frx/store/*-fruix-system-$current_host_name) : ;;
|
||||||
|
*)
|
||||||
|
echo "in-system build of current declaration produced an unexpected closure path: $current_built_closure" >&2
|
||||||
|
exit 1
|
||||||
|
;;
|
||||||
|
esac
|
||||||
|
|
||||||
|
scp_guest "$candidate_os_file" root@"$guest_ip":/root/candidate.scm >/dev/null
|
||||||
|
ssh_guest "/usr/local/bin/fruix system build /root/candidate.scm --system $system_name" > "$candidate_build_out"
|
||||||
|
candidate_closure=$(sed -n 's/^closure_path=//p' "$candidate_build_out" | tail -n 1)
|
||||||
|
[ -n "$candidate_closure" ] || { echo "missing candidate closure_path from in-system build output" >&2; exit 1; }
|
||||||
|
[ "$candidate_closure" != "$closure_path" ] || {
|
||||||
|
echo "candidate closure unexpectedly matches current closure" >&2
|
||||||
|
exit 1
|
||||||
|
}
|
||||||
|
|
||||||
|
ssh_guest "/usr/local/bin/fruix system reconfigure /root/candidate.scm --system $system_name" > "$reconfigure_out"
|
||||||
|
reconfigure_closure=$(sed -n 's/^reconfigure_closure=//p' "$reconfigure_out" | tail -n 1)
|
||||||
|
reconfigure_current_generation=$(sed -n 's/^current_generation=//p' "$reconfigure_out" | tail -n 1)
|
||||||
|
reconfigure_current_closure=$(sed -n 's/^current_closure=//p' "$reconfigure_out" | tail -n 1)
|
||||||
|
reconfigure_rollback_generation=$(sed -n 's/^rollback_generation=//p' "$reconfigure_out" | tail -n 1)
|
||||||
|
reconfigure_rollback_closure=$(sed -n 's/^rollback_closure=//p' "$reconfigure_out" | tail -n 1)
|
||||||
|
[ "$reconfigure_closure" = "$candidate_closure" ] || { echo "reconfigure closure mismatch" >&2; exit 1; }
|
||||||
|
[ "$reconfigure_current_generation" = 2 ] || { echo "unexpected current generation after reconfigure: $reconfigure_current_generation" >&2; exit 1; }
|
||||||
|
[ "$reconfigure_current_closure" = "$candidate_closure" ] || { echo "unexpected current closure after reconfigure" >&2; exit 1; }
|
||||||
|
[ "$reconfigure_rollback_generation" = 1 ] || { echo "unexpected rollback generation after reconfigure: $reconfigure_rollback_generation" >&2; exit 1; }
|
||||||
|
[ "$reconfigure_rollback_closure" = "$closure_path" ] || { echo "unexpected rollback closure after reconfigure" >&2; exit 1; }
|
||||||
|
ssh_guest '/usr/local/bin/fruix system status' > "$post_reconfigure_status"
|
||||||
|
|
||||||
|
reboot_guest
|
||||||
|
candidate_hostname=$(ssh_guest 'hostname')
|
||||||
|
candidate_run_current=$(ssh_guest 'readlink /run/current-system')
|
||||||
|
ssh_guest '/usr/local/bin/fruix system status' > "$post_boot_candidate_status"
|
||||||
|
[ "$candidate_hostname" = "$candidate_host_name" ] || { echo "unexpected host name after candidate boot: $candidate_hostname" >&2; exit 1; }
|
||||||
|
[ "$candidate_run_current" = "$candidate_closure" ] || { echo "unexpected current closure after candidate boot: $candidate_run_current" >&2; exit 1; }
|
||||||
|
|
||||||
|
ssh_guest '/usr/local/bin/fruix system rollback' > "$rollback_out"
|
||||||
|
rollback_current_generation=$(sed -n 's/^current_generation=//p' "$rollback_out" | tail -n 1)
|
||||||
|
rollback_current_closure=$(sed -n 's/^current_closure=//p' "$rollback_out" | tail -n 1)
|
||||||
|
rollback_rollback_generation=$(sed -n 's/^rollback_generation=//p' "$rollback_out" | tail -n 1)
|
||||||
|
rollback_rollback_closure=$(sed -n 's/^rollback_closure=//p' "$rollback_out" | tail -n 1)
|
||||||
|
[ "$rollback_current_generation" = 1 ] || { echo "unexpected current generation after rollback: $rollback_current_generation" >&2; exit 1; }
|
||||||
|
[ "$rollback_current_closure" = "$closure_path" ] || { echo "unexpected current closure after rollback" >&2; exit 1; }
|
||||||
|
[ "$rollback_rollback_generation" = 2 ] || { echo "unexpected rollback generation after rollback: $rollback_rollback_generation" >&2; exit 1; }
|
||||||
|
[ "$rollback_rollback_closure" = "$candidate_closure" ] || { echo "unexpected rollback closure after rollback" >&2; exit 1; }
|
||||||
|
|
||||||
|
reboot_guest
|
||||||
|
rollback_hostname=$(ssh_guest 'hostname')
|
||||||
|
rollback_run_current=$(ssh_guest 'readlink /run/current-system')
|
||||||
|
ssh_guest '/usr/local/bin/fruix system status' > "$post_boot_rollback_status"
|
||||||
|
[ "$rollback_hostname" = "$current_host_name" ] || { echo "unexpected host name after rollback boot: $rollback_hostname" >&2; exit 1; }
|
||||||
|
[ "$rollback_run_current" = "$closure_path" ] || { echo "unexpected current closure after rollback boot: $rollback_run_current" >&2; exit 1; }
|
||||||
|
|
||||||
|
cat > "$metadata_file" <<EOF
|
||||||
|
workdir=$workdir
|
||||||
|
inner_metadata=$inner_metadata
|
||||||
|
phase8_metadata=$phase8_metadata
|
||||||
|
closure_path=$closure_path
|
||||||
|
closure_base=$closure_base
|
||||||
|
vm_id=$vm_id
|
||||||
|
vdi_id=$vdi_id
|
||||||
|
guest_ip=$guest_ip
|
||||||
|
root_size=$root_size
|
||||||
|
result_store=$result_store
|
||||||
|
current_host_name=$current_host_name
|
||||||
|
candidate_host_name=$candidate_host_name
|
||||||
|
current_build_out=$current_build_out
|
||||||
|
current_built_closure=$current_built_closure
|
||||||
|
candidate_build_out=$candidate_build_out
|
||||||
|
candidate_closure=$candidate_closure
|
||||||
|
reconfigure_out=$reconfigure_out
|
||||||
|
reconfigure_closure=$reconfigure_closure
|
||||||
|
reconfigure_current_generation=$reconfigure_current_generation
|
||||||
|
reconfigure_current_closure=$reconfigure_current_closure
|
||||||
|
reconfigure_rollback_generation=$reconfigure_rollback_generation
|
||||||
|
reconfigure_rollback_closure=$reconfigure_rollback_closure
|
||||||
|
candidate_hostname=$candidate_hostname
|
||||||
|
candidate_run_current=$candidate_run_current
|
||||||
|
rollback_out=$rollback_out
|
||||||
|
rollback_current_generation=$rollback_current_generation
|
||||||
|
rollback_current_closure=$rollback_current_closure
|
||||||
|
rollback_rollback_generation=$rollback_rollback_generation
|
||||||
|
rollback_rollback_closure=$rollback_rollback_closure
|
||||||
|
rollback_hostname=$rollback_hostname
|
||||||
|
rollback_run_current=$rollback_run_current
|
||||||
|
boot_backend=xcp-ng-xo-cli
|
||||||
|
init_mode=shepherd-pid1
|
||||||
|
installed_node_build_reconfigure=ok
|
||||||
|
EOF
|
||||||
|
|
||||||
|
if [ -n "$metadata_target" ]; then
|
||||||
|
mkdir -p "$(dirname "$metadata_target")"
|
||||||
|
cp "$metadata_file" "$metadata_target"
|
||||||
|
fi
|
||||||
|
|
||||||
|
printf 'PASS postphase20-installed-node-build-reconfigure-xcpng\n'
|
||||||
|
printf 'Work directory: %s\n' "$workdir"
|
||||||
|
printf 'Metadata file: %s\n' "$metadata_file"
|
||||||
|
if [ -n "$metadata_target" ]; then
|
||||||
|
printf 'Copied metadata to: %s\n' "$metadata_target"
|
||||||
|
fi
|
||||||
|
printf '%s\n' '--- metadata ---'
|
||||||
|
cat "$metadata_file"
|
||||||
Reference in New Issue
Block a user