Compare commits
54 Commits
6e01eb9fc8
...
master
| Author | SHA1 | Date | |
|---|---|---|---|
| 2a1a6c3b81 | |||
| cb9e7332f4 | |||
| db4d5bdf4c | |||
| 0e8b30434f | |||
| f41a916f45 | |||
| 006ffee615 | |||
| 4614592a25 | |||
| a3dd5556ae | |||
| 9e9a0b59fc | |||
| 4975084baa | |||
| 9dae4e5c84 | |||
| b3b1ba2489 | |||
| e86f74af97 | |||
| 43c155bb9f | |||
| 604ad82f4f | |||
| 1970c5c181 | |||
| ebe064a652 | |||
| 56d9d6a54b | |||
| 1d0090752d | |||
| 2517710282 | |||
| 02a02e365d | |||
| 865012ea0c | |||
| 8150508676 | |||
| 5cbf5b90ed | |||
| 3f1793607d | |||
| d89225fe11 | |||
| 390bfb248f | |||
| 03fbd9bf08 | |||
| 72f89c51b5 | |||
| 3b95ced578 | |||
| f163a63b1f | |||
| 04b6ade095 | |||
| 94e498f57d | |||
| 4def04a357 | |||
| 6021a57c38 | |||
| 1108153277 | |||
| 901d0a8448 | |||
| a04e650326 | |||
| 4b786c356f | |||
| 1b3e49fbf7 | |||
| 377a6e49ff | |||
| f5ffd111ee | |||
| c62c89b078 | |||
| 7db5c76541 | |||
| b037427c22 | |||
| a69864cc0c | |||
| e933bf3fd1 | |||
| 43677ffd78 | |||
| 4b69118d06 | |||
| d465264b5e | |||
| b70d1fb12a | |||
| 514ec97f6b | |||
| e4288dc330 | |||
| 13963e7f62 |
1
.gitignore
vendored
Normal file
1
.gitignore
vendored
Normal file
@@ -0,0 +1 @@
|
||||
/TODO.md
|
||||
17
README.md
Normal file
17
README.md
Normal file
@@ -0,0 +1,17 @@
|
||||
# Fruix
|
||||
|
||||
Fruix is a Guix-like system running on FreeBSD but not GNU, using Shepherd, building GNU packages, with a BSD userland, and a functional store similar to Nix but not Nix.
|
||||
|
||||
In Fruix, the FreeBSD platform is represented as foundational store artifacts and updated through the same generation mechanism as the rest of the system.
|
||||
|
||||
Fruix is a system where everything that exists on the machine exists for a reason that can be explained.
|
||||
|
||||
Every Fruix system must remain fully understandable and recoverable using only text files, a shell, and standard system tools.
|
||||
|
||||
- Every host has a local config repository.
|
||||
- Every host has a persistent system identity key.
|
||||
- Every applied change corresponds to a commit and a generation.
|
||||
- Secrets are declared in config but realized only at runtime.
|
||||
- Secrets are encrypted to explicit recipients derived from host/user identity.
|
||||
- Services explicitly declare their secret dependencies.
|
||||
- The orchestration layer operates only through these primitives.
|
||||
51
bin/fruix
Executable file
51
bin/fruix
Executable file
@@ -0,0 +1,51 @@
|
||||
#!/bin/sh
|
||||
set -eu
|
||||
|
||||
project_root=$(CDPATH= cd -- "$(dirname "$0")/.." && pwd)
|
||||
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}
|
||||
script=$project_root/scripts/fruix.scm
|
||||
|
||||
if [ ! -x "$guile_bin" ]; then
|
||||
echo "Guile binary is not executable: $guile_bin" >&2
|
||||
exit 1
|
||||
fi
|
||||
|
||||
ensure_built() {
|
||||
if [ ! -d "$guile_extra_prefix/share/guile/site" ] || \
|
||||
! GUILE_LOAD_PATH="$guile_extra_prefix/share/guile/site/3.0${GUILE_LOAD_PATH:+:$GUILE_LOAD_PATH}" \
|
||||
GUILE_LOAD_COMPILED_PATH="$guile_extra_prefix/lib/guile/3.0/site-ccache${GUILE_LOAD_COMPILED_PATH:+:$GUILE_LOAD_COMPILED_PATH}" \
|
||||
GUILE_EXTENSIONS_PATH="$guile_extra_prefix/lib/guile/3.0/extensions${GUILE_EXTENSIONS_PATH:+:$GUILE_EXTENSIONS_PATH}" \
|
||||
LD_LIBRARY_PATH="$guile_extra_prefix/lib:/tmp/guile-freebsd-validate-install/lib:/usr/local/lib${LD_LIBRARY_PATH:+:$LD_LIBRARY_PATH}" \
|
||||
"$guile_bin" -c '(catch #t (lambda () (use-modules (fibers)) (display "ok") (newline)) (lambda _ (display "missing") (newline)))' | grep -qx ok; then
|
||||
METADATA_OUT= ENV_OUT= "$project_root/tests/shepherd/build-local-guile-fibers.sh"
|
||||
fi
|
||||
|
||||
if [ ! -x "$shepherd_prefix/bin/shepherd" ] || [ ! -x "$shepherd_prefix/bin/herd" ]; then
|
||||
METADATA_OUT= ENV_OUT= GUILE_EXTRA_PREFIX="$guile_extra_prefix" "$project_root/tests/shepherd/build-local-shepherd.sh"
|
||||
fi
|
||||
}
|
||||
|
||||
ensure_built
|
||||
|
||||
guile_prefix=$(CDPATH= cd -- "$(dirname "$guile_bin")/.." && pwd)
|
||||
guile_lib_dir=$guile_prefix/lib
|
||||
|
||||
if [ -n "${GUILE_LOAD_PATH:-}" ]; then
|
||||
guile_load_path="$project_root/modules:$guix_source_dir:$GUILE_LOAD_PATH"
|
||||
else
|
||||
guile_load_path="$project_root/modules:$guix_source_dir"
|
||||
fi
|
||||
|
||||
exec env \
|
||||
GUILE_AUTO_COMPILE=0 \
|
||||
GUILE_LOAD_PATH="$guile_load_path" \
|
||||
LD_LIBRARY_PATH="$guile_lib_dir${LD_LIBRARY_PATH:+:$LD_LIBRARY_PATH}" \
|
||||
GUILE_PREFIX="$guile_prefix" \
|
||||
GUILE_EXTRA_PREFIX="$guile_extra_prefix" \
|
||||
SHEPHERD_PREFIX="$shepherd_prefix" \
|
||||
GUIX_SOURCE_DIR="$guix_source_dir" \
|
||||
FRUIX_PROJECT_ROOT="$project_root" \
|
||||
"$guile_bin" --no-auto-compile -s "$script" "$@"
|
||||
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
|
||||
466
docs/LLM_UI.md
Normal file
466
docs/LLM_UI.md
Normal file
@@ -0,0 +1,466 @@
|
||||
Dual output is exactly the right instinct. If you want a system to be self-documenting for both humans and LLMs, the key is this:
|
||||
|
||||
> **Do not treat documentation as prose attached afterward. Treat it as structured semantic data emitted by the same machinery that defines the system.**
|
||||
|
||||
Otherwise the docs drift, the machine lies, the humans improvise, and the model confidently invents nonsense. A beloved industry tradition.
|
||||
|
||||
## My preference, if Fruix wanted to be maximally legible to me
|
||||
|
||||
I would want **three layers at once** for every meaningful object in the system:
|
||||
|
||||
### 1. Operational output
|
||||
|
||||
The normal thing a human expects.
|
||||
|
||||
Example:
|
||||
|
||||
```text
|
||||
$ fruix service status sshd
|
||||
running since 2026-04-02T09:14:33Z
|
||||
pid 812
|
||||
generation 42
|
||||
store item /frx/store/...-openssh-service
|
||||
```
|
||||
|
||||
### 2. Structured machine-readable output
|
||||
|
||||
Explicit, stable, schema-like, with no pretty nonsense.
|
||||
|
||||
Example:
|
||||
|
||||
```json
|
||||
{
|
||||
"kind": "service-status",
|
||||
"name": "sshd",
|
||||
"state": "running",
|
||||
"since": "2026-04-02T09:14:33Z",
|
||||
"pid": 812,
|
||||
"generation": 42,
|
||||
"store_path": "/frx/store/...-openssh-service",
|
||||
"definition_ref": "services.sshd"
|
||||
}
|
||||
```
|
||||
|
||||
### 3. Embedded semantic help/doc layer
|
||||
|
||||
Not a wall of text. Short, attached meaning.
|
||||
|
||||
Example:
|
||||
|
||||
```json
|
||||
{
|
||||
"doc": {
|
||||
"summary": "OpenSSH daemon providing remote login access.",
|
||||
"purpose": "Accepts inbound SSH connections for remote administration.",
|
||||
"inputs": ["host keys", "network", "authorized keys config"],
|
||||
"depends_on": ["network-online", "host-keys"],
|
||||
"used_by": ["admin access", "remote deployment"],
|
||||
"failure_modes": [
|
||||
"missing host keys",
|
||||
"port unavailable",
|
||||
"invalid configuration"
|
||||
],
|
||||
"see_also": [
|
||||
"fruix service logs sshd",
|
||||
"fruix config explain services.sshd"
|
||||
]
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
That combination is ideal. The command answers the immediate question, but also emits enough structure that an LLM can keep asking intelligent follow-ups without having to guess what “sshd” even is in Fruix-world.
|
||||
|
||||
---
|
||||
|
||||
## The most important design principle
|
||||
|
||||
Every object in Fruix should ideally answer these questions about itself:
|
||||
|
||||
* what are you?
|
||||
* why do you exist?
|
||||
* what created you?
|
||||
* what do you depend on?
|
||||
* what depends on you?
|
||||
* how do I inspect you?
|
||||
* how do I change you?
|
||||
* what breaks if you fail?
|
||||
* where is your definition?
|
||||
* what commands are relevant next?
|
||||
|
||||
That is the difference between “a system with documentation” and “a system that can explain itself.”
|
||||
|
||||
## The command model I would love
|
||||
|
||||
I would strongly prefer **one canonical plain output mode**, plus **a structured explain mode**, rather than mixing prose into every default command.
|
||||
|
||||
So:
|
||||
|
||||
```text
|
||||
fruix service status sshd
|
||||
fruix service status sshd --json
|
||||
fruix service explain sshd
|
||||
```
|
||||
|
||||
Or maybe:
|
||||
|
||||
```text
|
||||
fruix inspect service sshd
|
||||
fruix inspect service sshd --json
|
||||
fruix explain service sshd
|
||||
```
|
||||
|
||||
This separation matters.
|
||||
|
||||
### Why not always dual-output in the same stream?
|
||||
|
||||
Because it becomes annoying for humans, brittle for scripts, and ugly for terminals. People say they want hybrid output until they actually have to read it every day.
|
||||
|
||||
Better pattern:
|
||||
|
||||
* default output for operators
|
||||
* stable machine output for tooling/LLMs
|
||||
* explicit explanation output for context
|
||||
|
||||
That said, you *can* still have a dual channel if done cleanly.
|
||||
|
||||
---
|
||||
|
||||
## My favorite model: result + attached explain block
|
||||
|
||||
Something like this:
|
||||
|
||||
```text
|
||||
$ fruix service status sshd --with-doc
|
||||
|
||||
sshd: running
|
||||
pid: 812
|
||||
since: 2026-04-02T09:14:33Z
|
||||
generation: 42
|
||||
|
||||
Explanation:
|
||||
SSH daemon for remote login and administration.
|
||||
Depends on network-online and host key material.
|
||||
Configured from services.sshd in the system definition.
|
||||
|
||||
Relevant commands:
|
||||
fruix service logs sshd
|
||||
fruix config explain services.sshd
|
||||
fruix service restart sshd
|
||||
```
|
||||
|
||||
That is excellent for humans and still digestible for an LLM if the formatting is predictable.
|
||||
|
||||
But for automation, I would still want:
|
||||
|
||||
```text
|
||||
fruix service status sshd --json
|
||||
fruix service explain sshd --json
|
||||
```
|
||||
|
||||
So in practice I’d want **two parallel interfaces**, not one muddled one.
|
||||
|
||||
---
|
||||
|
||||
## What should be self-documenting in Fruix?
|
||||
|
||||
Not just commands. The **actual nouns** of the system.
|
||||
|
||||
### 1. Store paths
|
||||
|
||||
A store item should be explainable.
|
||||
|
||||
```text
|
||||
fruix store explain /frx/store/845bd...-freebsd-bash-5.3.9
|
||||
```
|
||||
|
||||
Should answer:
|
||||
|
||||
* package/runtime/component name
|
||||
* version
|
||||
* origin
|
||||
* build inputs
|
||||
* purpose
|
||||
* references
|
||||
* whether it is part of current system
|
||||
* whether it is GC-rooted
|
||||
* whether it is user-requested or dependency-only
|
||||
|
||||
### 2. Services
|
||||
|
||||
Explain:
|
||||
|
||||
* purpose
|
||||
* definition origin
|
||||
* dependencies
|
||||
* restart policy
|
||||
* logs
|
||||
* ports/files touched
|
||||
* activation behavior
|
||||
|
||||
### 3. System definitions
|
||||
|
||||
Explain:
|
||||
|
||||
* what modules/options contributed
|
||||
* what options are set
|
||||
* defaults vs overrides
|
||||
* resulting closures
|
||||
* affected services
|
||||
* resulting boot entries/generations
|
||||
|
||||
### 4. Configuration options
|
||||
|
||||
This one matters a lot.
|
||||
|
||||
A good system should let me ask:
|
||||
|
||||
```text
|
||||
fruix config explain services.sshd.enable
|
||||
fruix config explain system.network.hostname
|
||||
fruix config search ssh
|
||||
```
|
||||
|
||||
And get:
|
||||
|
||||
* type
|
||||
* default
|
||||
* current value
|
||||
* description
|
||||
* examples
|
||||
* constraints
|
||||
* source module
|
||||
* related options
|
||||
|
||||
This is one of the best things NixOS/Guix-style systems can do, and Fruix should absolutely lean into it.
|
||||
|
||||
### 5. Commands themselves
|
||||
|
||||
CLI subcommands should be introspectable too.
|
||||
|
||||
```text
|
||||
fruix help gc
|
||||
fruix help gc --json
|
||||
fruix explain command gc
|
||||
```
|
||||
|
||||
Because LLMs are often dropped into a black box and forced to infer the command surface. A command that can describe itself structurally is vastly easier to use safely.
|
||||
|
||||
---
|
||||
|
||||
## What would help me most as an LLM?
|
||||
|
||||
If Fruix wanted to be unusually legible to a model, I’d want these properties.
|
||||
|
||||
### Stable schemas
|
||||
|
||||
If `--json` exists, make it boring and stable. No whimsical field names, no gratuitous nesting, no surprise changes. Humans love creative naming, then wonder why tooling breaks.
|
||||
|
||||
### Uniform “kind” fields
|
||||
|
||||
Every structured output should say what it is.
|
||||
|
||||
```json
|
||||
{ "kind": "service-status", ... }
|
||||
{ "kind": "config-option", ... }
|
||||
{ "kind": "store-item", ... }
|
||||
{ "kind": "system-generation", ... }
|
||||
```
|
||||
|
||||
This helps tremendously.
|
||||
|
||||
### Embedded references
|
||||
|
||||
Objects should point to related objects.
|
||||
|
||||
For example, service status should include:
|
||||
|
||||
* definition reference
|
||||
* related config path
|
||||
* related store path
|
||||
* dependency names
|
||||
|
||||
This lets a model traverse the system instead of hallucinating relationships.
|
||||
|
||||
### Short summaries written for machines and humans
|
||||
|
||||
Not marketing copy. Not essay paragraphs. Tight explanatory text.
|
||||
|
||||
Good:
|
||||
|
||||
> "Enables the OpenSSH daemon for inbound remote shell access."
|
||||
|
||||
Bad:
|
||||
|
||||
> "This module offers a powerful and flexible way to control secure access to your Fruix machine."
|
||||
|
||||
Nobody needs brochure language except people trying to justify headcount.
|
||||
|
||||
### Explicit provenance
|
||||
|
||||
For anything important, say where it came from:
|
||||
|
||||
* built from which derivation
|
||||
* declared in which module
|
||||
* set by user vs default
|
||||
* inherited from which profile/system generation
|
||||
|
||||
That makes the system explainable instead of mystical.
|
||||
|
||||
---
|
||||
|
||||
## I would absolutely add a “why” command
|
||||
|
||||
This is the killer feature.
|
||||
|
||||
Examples:
|
||||
|
||||
```text
|
||||
fruix why /frx/store/...-freebsd-bash-5.3.9
|
||||
fruix why service sshd
|
||||
fruix why config services.sshd.enable
|
||||
```
|
||||
|
||||
Answers like:
|
||||
|
||||
```text
|
||||
freebsd-bash-5.3.9 is present because:
|
||||
- required by fruix-system-fruix-freebsd generation 42
|
||||
- referenced by interactive-shell-runtime
|
||||
- pulled in by admin profile
|
||||
```
|
||||
|
||||
Or:
|
||||
|
||||
```text
|
||||
services.sshd.enable is true because:
|
||||
- set explicitly in hosts/fruix-freebsd.scm
|
||||
- module openssh-service.scm contributes service definition
|
||||
```
|
||||
|
||||
This is huge for both humans and LLMs. “Why is this here?” is half of systems debugging.
|
||||
|
||||
---
|
||||
|
||||
## My preferred architecture for integrated docs
|
||||
|
||||
I would not store docs only in markdown files. I would make them part of the typed object/module/option definitions themselves.
|
||||
|
||||
For each module/option/service/package, attach fields like:
|
||||
|
||||
* `summary`
|
||||
* `description`
|
||||
* `type`
|
||||
* `default`
|
||||
* `examples`
|
||||
* `see-also`
|
||||
* `danger-level`
|
||||
* `depends-on`
|
||||
* `used-by`
|
||||
* `introduced-in`
|
||||
* `notes`
|
||||
|
||||
Then generate from that:
|
||||
|
||||
* terminal help
|
||||
* handbook pages
|
||||
* man pages
|
||||
* JSON explain output
|
||||
* web docs
|
||||
* maybe even shell completions with hints
|
||||
|
||||
That way there is **one source of truth**, many renderings.
|
||||
|
||||
This is the real win.
|
||||
|
||||
---
|
||||
|
||||
## A concrete Fruix interaction model I’d love
|
||||
|
||||
Something like this command family:
|
||||
|
||||
```text
|
||||
fruix inspect <object> [name]
|
||||
fruix explain <object> [name]
|
||||
fruix search <term>
|
||||
fruix why <object> [name]
|
||||
fruix graph <object> [name]
|
||||
```
|
||||
|
||||
Examples:
|
||||
|
||||
```text
|
||||
fruix inspect service sshd
|
||||
fruix explain service sshd
|
||||
fruix why service sshd
|
||||
fruix graph service sshd
|
||||
|
||||
fruix inspect store /frx/store/...-freebsd-bash-5.3.9
|
||||
fruix explain store /frx/store/...-freebsd-bash-5.3.9
|
||||
fruix why store /frx/store/...-freebsd-bash-5.3.9
|
||||
|
||||
fruix inspect config services.sshd.enable
|
||||
fruix explain config services.sshd.enable
|
||||
fruix why config services.sshd.enable
|
||||
```
|
||||
|
||||
That is clean, memorable, and composable.
|
||||
|
||||
---
|
||||
|
||||
## If you insist on dual output in one invocation
|
||||
|
||||
Then I would make it a structured container, not mixed prose spaghetti.
|
||||
|
||||
Maybe:
|
||||
|
||||
```text
|
||||
fruix service status sshd --format annotated
|
||||
```
|
||||
|
||||
Output:
|
||||
|
||||
```yaml
|
||||
result:
|
||||
name: sshd
|
||||
state: running
|
||||
pid: 812
|
||||
since: 2026-04-02T09:14:33Z
|
||||
generation: 42
|
||||
|
||||
doc:
|
||||
summary: OpenSSH daemon for remote administration.
|
||||
depends_on:
|
||||
- network-online
|
||||
- host-keys
|
||||
config_path: services.sshd
|
||||
see_also:
|
||||
- fruix service logs sshd
|
||||
- fruix config explain services.sshd
|
||||
```
|
||||
|
||||
YAML is readable enough for humans and parseable enough for tools. Though for machine use I’d still pick JSON.
|
||||
|
||||
---
|
||||
|
||||
## The single biggest preference of all
|
||||
|
||||
If I had to choose only one thing, it would be this:
|
||||
|
||||
> Every meaningful Fruix object should be introspectable by name or path, with both a stable machine schema and a concise semantic explanation.
|
||||
|
||||
That alone would make the system unusually legible to me, to users, and to future maintainers who have not yet been spiritually damaged by it.
|
||||
|
||||
## My concrete recommendation
|
||||
|
||||
Build this into Fruix from the start:
|
||||
|
||||
* `--json` on all important commands
|
||||
* `explain` subcommands for commands, config, services, store items, generations
|
||||
* `why` subcommands for dependency/provenance tracing
|
||||
* structured doc fields embedded in module and option definitions
|
||||
* generated docs from the same source
|
||||
* concise summaries, not essay sludge
|
||||
|
||||
That is the sweet spot. Self-documenting, machine-friendly, human-usable, and hard to let drift.
|
||||
|
||||
If Fruix gets that right, an LLM dropped cold into the system could become useful very quickly instead of fumbling around like a junior admin in an unfamiliar datacenter.
|
||||
316
docs/PLAN_3.md
Normal file
316
docs/PLAN_3.md
Normal file
@@ -0,0 +1,316 @@
|
||||
# Fruix on FreeBSD, Path C: Short Hardening Pass, Then Native FreeBSD Base Builds
|
||||
|
||||
This document extends `docs/PLAN_2.md` after the completion of Phases 1 through 10 and the post-Phase-10 Shepherd/PID-1 and runtime-prefix cleanup work.
|
||||
|
||||
The project has already crossed the main feasibility threshold:
|
||||
|
||||
- Fruix can build declarative FreeBSD system artifacts
|
||||
- those artifacts boot on the real XCP-ng VM
|
||||
- the guest reaches DHCP and SSH
|
||||
- both `freebsd-init+rc.d-shepherd` and `shepherd-pid1` have been validated
|
||||
- the guest no longer depends on `/tmp/*-validate-install` runtime-compatibility shims
|
||||
|
||||
The next architectural question is about the **FreeBSD base itself**.
|
||||
|
||||
Today, Fruix still assembles the kernel, bootloader, libraries, rc assets, and userland largely by **copying curated artifacts from the build host** into `/frx/store`. That was the right bootstrap move, but it is now the largest remaining non-native part of the system.
|
||||
|
||||
Long-term, the desired direction is clear:
|
||||
|
||||
- build FreeBSD kernel/world as Fruix-managed artifacts
|
||||
- keep them in `/frx/store`
|
||||
- make system upgrades a declarative rebuild/redeploy story instead of a host-copy refresh
|
||||
|
||||
However, this plan does **not** recommend jumping directly into full self-hosted FreeBSD base builds inside the guest. Instead, it takes a narrower and safer sequence:
|
||||
|
||||
1. do a **short hardening pass** on the current working system
|
||||
2. then begin **Option B** immediately as the next major architecture phase
|
||||
3. start Option B on the **builder side** from `/usr/src`, not with guest self-hosting
|
||||
|
||||
This keeps momentum while avoiding two failure modes:
|
||||
|
||||
- over-polishing the current host-copy pipeline as if it were the end state
|
||||
- mixing base-system build mechanics, runtime polish, boot debugging, and self-hosting into one oversized step
|
||||
|
||||
Throughout this plan, the canonical Fruix roots remain:
|
||||
|
||||
- `/frx/store`
|
||||
- `/frx/var`
|
||||
- `/frx/etc`
|
||||
|
||||
The user-facing system remains **Fruix**, with CLI `fruix`, while internal upstream-derived names continue to be renamed selectively rather than mechanically.
|
||||
|
||||
---
|
||||
|
||||
## Current State at the Start of Plan 3
|
||||
|
||||
Fruix on FreeBSD already has:
|
||||
|
||||
- a functioning content-addressed store and derivation path
|
||||
- daemon-mediated builds with FreeBSD-aware isolation prototypes
|
||||
- real package outputs in `/frx/store`
|
||||
- a declarative FreeBSD operating-system model
|
||||
- rootfs and image generation driven by `fruix system ...`
|
||||
- real XCP-ng validation using the approved VM/VDI path
|
||||
- a working FreeBSD guest with DHCP, SSH, activation, Shepherd, and ready markers
|
||||
- validated `shepherd-pid1` boot on both local QEMU/TCG and the real XCP-ng VM
|
||||
- removal of the guest runtime's dependence on `/tmp` compatibility-prefix symlinks
|
||||
|
||||
The main remaining architectural compromise is that the FreeBSD base layer is still defined mostly as a set of curated host copies:
|
||||
|
||||
- kernel from `/boot/kernel`
|
||||
- bootloader and boot assets from `/boot`
|
||||
- userland/runtime pieces from `/bin`, `/sbin`, `/usr/bin`, `/usr/sbin`, `/lib`, `/usr/lib`, and `/etc`
|
||||
- headers from `/usr/src`
|
||||
|
||||
That means Fruix can currently rebuild and redeploy a working system, but it does **not** yet have a truly native upgrade story for the FreeBSD base itself.
|
||||
|
||||
---
|
||||
|
||||
## Guiding Decision for the Next Milestone
|
||||
|
||||
The next milestone should be:
|
||||
|
||||
- **just enough hardening** to make the current system a reliable platform for deeper work,
|
||||
- followed immediately by the first **host-built FreeBSD base artifacts** in `/frx/store`.
|
||||
|
||||
This means the next work should optimize for:
|
||||
|
||||
- debuggability
|
||||
- provenance
|
||||
- repeatable deployment/validation
|
||||
- clean separation between host-staged base artifacts and Fruix-built artifacts
|
||||
- incremental replacement of the copy-based base layer
|
||||
|
||||
It should **not** optimize for:
|
||||
|
||||
- turning the current host-copy path into a heavily polished long-term abstraction
|
||||
- immediate self-hosted `buildworld`/`buildkernel` inside the Fruix guest
|
||||
- solving every usability concern before starting native base work
|
||||
|
||||
---
|
||||
|
||||
## Phase 12: Short Hardening Pass on the Existing Working Pipeline
|
||||
|
||||
The purpose of this phase is not to beautify the bootstrap path. It is to make the existing validated system reliable enough that FreeBSD base-build work can proceed without avoidable confusion.
|
||||
|
||||
### Intermediate Goal 12.1: Improve Deployment Provenance and Failure Diagnosis
|
||||
|
||||
**Verification Goal 12.1:** Make the current pipeline record exactly what FreeBSD base inputs and deployment artifacts were used for each generated system/image.
|
||||
|
||||
This should include at least:
|
||||
|
||||
- host `freebsd-version` / `uname` provenance
|
||||
- `/usr/src` revision or identifying metadata when available
|
||||
- the exact staged base-package store paths used in the system closure
|
||||
- image metadata that clearly distinguishes:
|
||||
- host-copied FreeBSD base artifacts
|
||||
- Fruix-built Guile/Shepherd/system artifacts
|
||||
- better collection of boot/runtime logs where possible from the existing QEMU and XCP-ng harnesses
|
||||
|
||||
**Success Criteria:** a generated Fruix system/image can be traced back to its host-side FreeBSD base inputs and runtime validation logs without ad hoc investigation.
|
||||
|
||||
### Intermediate Goal 12.2: Tighten Basic Runtime Completeness and Operator Diagnostics
|
||||
|
||||
**Verification Goal 12.2:** Close the most important remaining “prototype rough edges” in the current guest so it remains a dependable validation target while Option B begins.
|
||||
|
||||
This should focus on small, high-value improvements such as:
|
||||
|
||||
- clearer boot/service logs
|
||||
- fewer known noisy runtime warnings where reasonably fixable
|
||||
- more explicit validation of essential services and files
|
||||
- better failure surfacing from activation and service startup
|
||||
|
||||
This phase should avoid broad feature creep. The target is not a polished distribution; it is a sharper debugging and validation baseline.
|
||||
|
||||
**Success Criteria:** the current FreeBSD guest remains reproducibly bootable and easier to debug, with fewer ambiguous failures during rebuild/redeploy cycles.
|
||||
|
||||
### Intermediate Goal 12.3: Make the Host-Staged FreeBSD Base Boundary Explicit
|
||||
|
||||
**Verification Goal 12.3:** Refine the current package/model layer so the “host-staged FreeBSD base” is treated as an explicit transitional boundary rather than an implicit permanent design.
|
||||
|
||||
This should include:
|
||||
|
||||
- clearer grouping or manifesting of host-staged base packages
|
||||
- documentation of which components are currently copied from the host
|
||||
- explicit notes on which ones are planned to be replaced first by native base builds
|
||||
|
||||
**Success Criteria:** the repo documents and code clearly separate transitional host-copy FreeBSD base handling from genuine Fruix-built artifacts.
|
||||
|
||||
---
|
||||
|
||||
## Phase 13: Option B Begins — Host-Built FreeBSD Kernel and World Artifacts in `/frx/store`
|
||||
|
||||
This is the real architectural pivot.
|
||||
|
||||
The goal is **not** yet to self-host FreeBSD base builds inside a Fruix guest. The goal is to teach Fruix to produce FreeBSD base artifacts as build outputs under `/frx/store`, using the builder host and `/usr/src` as the source of truth.
|
||||
|
||||
### Intermediate Goal 13.1: Model FreeBSD World and Kernel as Fruix Build Artifacts
|
||||
|
||||
**Verification Goal 13.1:** Introduce Fruix build descriptions for the FreeBSD base that can represent at least:
|
||||
|
||||
- a `freebsd-world` artifact
|
||||
- a `freebsd-kernel` artifact
|
||||
- required boot assets associated with the selected kernel/world build
|
||||
|
||||
The first version may still be specialized and FreeBSD-specific rather than fully generalized.
|
||||
|
||||
This step should determine and document:
|
||||
|
||||
- how `/usr/src` is treated as an input
|
||||
- what build parameters affect output identity
|
||||
- how kernel/world configuration is hashed into the output model
|
||||
- how these outputs are split or grouped in `/frx/store`
|
||||
|
||||
**Success Criteria:** Fruix can describe native FreeBSD world/kernel build outputs as real store artifacts rather than only host-copy packages.
|
||||
|
||||
### Intermediate Goal 13.2: Build and Stage Minimal FreeBSD World/Kernel Outputs From `/usr/src`
|
||||
|
||||
**Verification Goal 13.2:** Produce the first concrete world/kernel build outputs from `/usr/src` and stage them into `/frx/store`.
|
||||
|
||||
The first target should be intentionally narrow:
|
||||
|
||||
- build a kernel matching the validated VM target
|
||||
- build a minimal world sufficient for the current Fruix guest to boot
|
||||
- stage install trees under content-addressed store paths
|
||||
|
||||
This phase should prefer correctness and repeatability over completeness. A minimal successful output split is acceptable if it is documented.
|
||||
|
||||
**Success Criteria:** `/frx/store` contains native Fruix-managed FreeBSD world/kernel outputs built from `/usr/src`, and their contents can be inspected as build results rather than copied host snapshots.
|
||||
|
||||
### Intermediate Goal 13.3: Boot a Fruix System Using Store-Built FreeBSD Base Artifacts
|
||||
|
||||
**Verification Goal 13.3:** Wire the operating-system/image pipeline so a generated system can boot using the newly built world/kernel outputs instead of the corresponding host-copy packages.
|
||||
|
||||
This should preserve the existing validation strategy:
|
||||
|
||||
- local QEMU/TCG + UEFI where useful
|
||||
- real XCP-ng validation on the approved VM and existing VDI
|
||||
|
||||
The first boot target may still leave some secondary base components on the older copy path if necessary, as long as the transition boundary is explicit.
|
||||
|
||||
**Success Criteria:** a Fruix system/image boots successfully using native store-built FreeBSD base artifacts for at least the kernel and core world runtime.
|
||||
|
||||
---
|
||||
|
||||
## Phase 14: Incrementally Replace the Host-Copy FreeBSD Base Layer
|
||||
|
||||
Once a minimal native world/kernel path works, the rest of the host-copy base layer should be retired incrementally rather than in one giant switch.
|
||||
|
||||
### Intermediate Goal 14.1: Replace Kernel and Boot Assets First
|
||||
|
||||
**Verification Goal 14.1:** Move the system closure and image generator to prefer native store-built kernel and boot assets over host-copied `/boot/...` material.
|
||||
|
||||
This should include:
|
||||
|
||||
- kernel
|
||||
- loader/boot assets as appropriate
|
||||
- any required linker or boot metadata
|
||||
|
||||
**Success Criteria:** the image no longer relies on host-copied kernel/boot components for the validated boot path.
|
||||
|
||||
### Intermediate Goal 14.2: Replace the Core Runtime World Slice
|
||||
|
||||
**Verification Goal 14.2:** Move the essential userland/runtime components to the native world outputs, including the files required for:
|
||||
|
||||
- boot
|
||||
- activation
|
||||
- networking
|
||||
- SSH
|
||||
- service startup
|
||||
- basic operator access
|
||||
|
||||
**Success Criteria:** the booted Fruix system reaches ready state using a native store-built core FreeBSD runtime rather than a hand-curated host copy set.
|
||||
|
||||
### Intermediate Goal 14.3: Revisit Headers, Toolchain, and Development Splits
|
||||
|
||||
**Verification Goal 14.3:** Define cleaner boundaries between:
|
||||
|
||||
- runtime world outputs
|
||||
- development headers
|
||||
- toolchain artifacts
|
||||
- optional build/developer profiles
|
||||
|
||||
This step should reduce accidental coupling between “what the guest needs to boot” and “what the host needs to build software”.
|
||||
|
||||
**Success Criteria:** Fruix has a clearer and more maintainable model for FreeBSD runtime vs. development artifacts in `/frx/store`.
|
||||
|
||||
---
|
||||
|
||||
## Phase 15: Establish a Real Fruix Upgrade Story for the FreeBSD Base
|
||||
|
||||
After native base artifacts exist and are used by the system closure, Fruix can move from “rebuild from current host copies” toward a more honest upgrade story.
|
||||
|
||||
### Intermediate Goal 15.1: Make the FreeBSD Base Version a Declarative Input
|
||||
|
||||
**Verification Goal 15.1:** Define how a Fruix system declares which FreeBSD base/kernel source version it targets.
|
||||
|
||||
This may initially remain tied to locally available `/usr/src`, but it should move the model toward a declarative notion of:
|
||||
|
||||
- target FreeBSD branch/release
|
||||
- kernel configuration
|
||||
- world configuration
|
||||
- boot/runtime variant
|
||||
|
||||
**Success Criteria:** FreeBSD base versioning becomes an explicit part of the Fruix system model rather than an implicit property of the builder host.
|
||||
|
||||
### Intermediate Goal 15.2: Support Rebuild/Redeploy/Rollback Across Base Versions
|
||||
|
||||
**Verification Goal 15.2:** Demonstrate that at least two distinct FreeBSD base builds can coexist in `/frx/store` and that the generated system can switch between them through the normal rebuild/redeploy flow.
|
||||
|
||||
This should preserve the Guix-inspired strengths:
|
||||
|
||||
- content-addressed outputs
|
||||
- side-by-side versions
|
||||
- rollback-friendly system closures
|
||||
|
||||
**Success Criteria:** Fruix can rebuild and redeploy a system against a newer FreeBSD base without mutating the old one in place.
|
||||
|
||||
### Intermediate Goal 15.3: Decide When to Pursue Self-Hosted Base Builds
|
||||
|
||||
**Verification Goal 15.3:** Reassess whether the next step should be:
|
||||
|
||||
- building FreeBSD base artifacts inside a Fruix-managed environment, or
|
||||
- continuing to use the host builder while improving reproducibility and source acquisition
|
||||
|
||||
This decision should be made only after native world/kernel artifacts are already working in `/frx/store`.
|
||||
|
||||
**Success Criteria:** the project has a documented, evidence-based decision on whether and when to pursue self-hosted FreeBSD base builds.
|
||||
|
||||
---
|
||||
|
||||
## Strategic Notes
|
||||
|
||||
### Why this order?
|
||||
|
||||
This sequence is intended to preserve momentum and architectural clarity.
|
||||
|
||||
The short hardening pass prevents the next phase from being undermined by avoidable debugging ambiguity. But the hardening phase is intentionally short so the project does not over-invest in the transitional host-copy design.
|
||||
|
||||
Then Option B begins immediately in a way that is ambitious but still controlled:
|
||||
|
||||
- build FreeBSD base artifacts on the host
|
||||
- store them in `/frx/store`
|
||||
- boot from them
|
||||
- replace the host-copy model incrementally
|
||||
|
||||
### What this plan does **not** require yet
|
||||
|
||||
This plan does **not** require, in its first native-base stages:
|
||||
|
||||
- guest self-hosting
|
||||
- in-place `freebsd-update` integration
|
||||
- complete replacement of every base file in one step
|
||||
- abandoning the validated XCP-ng/QEMU test harnesses
|
||||
- immediate adoption of `shepherd-pid1` as the only supported boot path
|
||||
|
||||
### What success will mean
|
||||
|
||||
Success under this plan means the project moves from:
|
||||
|
||||
- “Fruix assembles a FreeBSD system by copying a curated slice of the host”
|
||||
|
||||
into:
|
||||
|
||||
- “Fruix builds and stores FreeBSD base artifacts itself, then assembles and deploys systems from those declared outputs.”
|
||||
|
||||
That is the real bridge from the current prototype to a more genuinely native Fruix system on FreeBSD.
|
||||
406
docs/PLAN_4.md
Normal file
406
docs/PLAN_4.md
Normal file
@@ -0,0 +1,406 @@
|
||||
# Fruix on FreeBSD, Path D: Declarative Source Acquisition, Installation Artifacts, and the Road to Self-Hosting
|
||||
|
||||
This document extends `docs/PLAN_3.md` after the completion of Phases 1 through 15.
|
||||
|
||||
The project has now crossed another important threshold:
|
||||
|
||||
- Fruix builds native FreeBSD base artifacts from `/usr/src` into `/frx/store`
|
||||
- the validated boot/runtime path is host-base-free at the deployed system layer
|
||||
- the FreeBSD base is now an explicit declarative Fruix input
|
||||
- side-by-side base versions can coexist in `/frx/store`
|
||||
- Fruix can rebuild, redeploy, and roll back between declared base versions
|
||||
|
||||
That means the next question is no longer whether Fruix can assemble and boot a native FreeBSD system. It can.
|
||||
|
||||
The next question is how to make that native base path **more reproducible, more source-declarative, and more installable**.
|
||||
|
||||
The biggest remaining impurity is now clear:
|
||||
|
||||
- the native base build path still relies on the builder host's ambient `/usr/src`
|
||||
|
||||
That was the right bridge through Phases 13 to 15, but it should not be the long-term source story.
|
||||
|
||||
Accordingly, Plan 4 focuses on three connected goals:
|
||||
|
||||
1. make FreeBSD source acquisition and source identity more declarative
|
||||
2. turn the current image-generation path into a real installation story
|
||||
3. only then move carefully toward self-hosted base builds
|
||||
|
||||
This preserves the Guix-inspired core while continuing to embrace FreeBSD semantics rather than trying to imitate Linux-specific workflows.
|
||||
|
||||
Throughout this plan, the canonical Fruix roots remain:
|
||||
|
||||
- `/frx/store`
|
||||
- `/frx/var`
|
||||
- `/frx/etc`
|
||||
|
||||
The user-facing system remains **Fruix**, with CLI `fruix`, while internal upstream-derived names continue to be renamed selectively rather than mechanically.
|
||||
|
||||
The current real-VM validation constraints still apply unless explicitly relaxed later:
|
||||
|
||||
- approved VM: `90490f2e-e8fc-4b7a-388e-5c26f0157289`
|
||||
- approved A/B VDIs:
|
||||
- `0f1f90d3-48ca-4fa2-91d8-fc6339b95743`
|
||||
- `7061d761-3639-4bec-87f7-2ba1af924eaa`
|
||||
|
||||
---
|
||||
|
||||
## Current State at the Start of Plan 4
|
||||
|
||||
Fruix on FreeBSD already has:
|
||||
|
||||
- a functioning content-addressed store and derivation path under `/frx/store`
|
||||
- daemon-mediated builds with validated FreeBSD isolation concepts
|
||||
- a working declarative FreeBSD operating-system model
|
||||
- validated system closure, rootfs, and image generation through `fruix system ...`
|
||||
- real QEMU and XCP-ng boot validation
|
||||
- a host-base-free native boot/runtime composition built from:
|
||||
- `freebsd-native-kernel`
|
||||
- `freebsd-native-bootloader`
|
||||
- `freebsd-native-runtime`
|
||||
- a declarative `freebsd-base` model that records:
|
||||
- base identity
|
||||
- version label
|
||||
- branch
|
||||
- source root
|
||||
- target and kernel configuration
|
||||
- side-by-side native base versions in `/frx/store`
|
||||
- rollback-friendly redeploy across those declared base versions
|
||||
- a documented decision to continue using host-built native base artifacts for now rather than jumping immediately to guest self-hosting
|
||||
|
||||
The main remaining architectural compromise is now not the runtime/boot artifact split, but the **source boundary**:
|
||||
|
||||
- native FreeBSD base builds still use ambient `/usr/src`
|
||||
- source acquisition is not yet a first-class Fruix-managed input
|
||||
- therefore reproducibility and provenance still stop short of a fully declared source story
|
||||
|
||||
At the same time, Fruix still lacks a real installation workflow beyond image generation and validation harnesses.
|
||||
|
||||
---
|
||||
|
||||
## Guiding Decision for the Next Milestone
|
||||
|
||||
The next major milestone should be:
|
||||
|
||||
- **declarative FreeBSD source acquisition and source pinning first**,
|
||||
- then **native base builds from fetched or materialized source inputs**,
|
||||
- then a **real installation story**,
|
||||
- and only after that, a controlled re-evaluation of self-hosted base builds.
|
||||
|
||||
This means the next work should optimize for:
|
||||
|
||||
- explicit source identity
|
||||
- stronger provenance
|
||||
- side-by-side source revisions
|
||||
- repeatable rebuilds from fetched or materialized source trees
|
||||
- installation artifacts that are operator-usable beyond current validation harnesses
|
||||
|
||||
It should **not** optimize for:
|
||||
|
||||
- jumping straight into guest self-hosting while source identity is still weak
|
||||
- building a large interactive installer before the installation primitives are clear
|
||||
- replacing the current validated host-built native-base path prematurely
|
||||
|
||||
---
|
||||
|
||||
## Phase 16: Make FreeBSD Source Acquisition Declarative
|
||||
|
||||
This is the next architectural pivot after the native base/runtime split.
|
||||
|
||||
The goal is to move from:
|
||||
|
||||
- “build from whatever `/usr/src` is on the host”
|
||||
|
||||
into:
|
||||
|
||||
- “build from a declared FreeBSD source input with explicit provenance.”
|
||||
|
||||
### Intermediate Goal 16.1: Model FreeBSD Source Inputs Explicitly
|
||||
|
||||
**Verification Goal 16.1:** Introduce a first-class Fruix model for FreeBSD source inputs.
|
||||
|
||||
This can be a new `freebsd-source` record, an extension of `freebsd-base`, or another explicit source object, but it should represent at least:
|
||||
|
||||
- source kind:
|
||||
- local checkout snapshot
|
||||
- fetched Git checkout
|
||||
- fetched `src.txz` archive
|
||||
- source URL or local path
|
||||
- branch or release line
|
||||
- commit, tag, or revision when applicable
|
||||
- expected tree or archive hash
|
||||
- any intended patch queue or source transformation layer
|
||||
|
||||
This step should also document how that source object relates to:
|
||||
|
||||
- `freebsd-base`
|
||||
- native kernel/world/runtime/bootloader/headers builds
|
||||
- output identity in `/frx/store`
|
||||
|
||||
**Success Criteria:** Fruix can describe FreeBSD source inputs as explicit declared objects rather than only relying on ambient `/usr/src`.
|
||||
|
||||
### Intermediate Goal 16.2: Fetch or Materialize Source Trees Under Fruix Control
|
||||
|
||||
**Verification Goal 16.2:** Materialize a clean FreeBSD source tree from a declared source input using Fruix-managed fetch/materialization logic.
|
||||
|
||||
The first implementation may be intentionally modest, such as:
|
||||
|
||||
- fetching a Git checkout at a pinned revision from `https://git.FreeBSD.org/src.git`
|
||||
- or downloading a `src.txz` archive and verifying its hash
|
||||
- for example from `https://download.freebsd.org/releases/amd64/15.0-RELEASE/src.txz`
|
||||
- or from snapshot paths such as `https://download.freebsd.org/snapshots/amd64/15.0-STABLE/src.txz`
|
||||
- or snapshotting a local source tree into a content-addressed source artifact with explicit metadata
|
||||
|
||||
This phase should also define where downloaded or temporary source material lives, for example under:
|
||||
|
||||
- `/frx/var/cache/fruix/freebsd-source`
|
||||
|
||||
The important point is that the eventual build input should no longer be “the current mutable host tree” but a **materialized source snapshot with recorded identity**.
|
||||
|
||||
**Success Criteria:** Fruix can fetch or materialize a pinned FreeBSD source tree with recorded provenance and a stable identity suitable for native base builds.
|
||||
|
||||
### Intermediate Goal 16.3: Build Native Base Artifacts From the Declared Source Input
|
||||
|
||||
**Verification Goal 16.3:** Teach the native FreeBSD base build path to consume the declared source artifact rather than ambient `/usr/src`.
|
||||
|
||||
This should preserve the validated Plan-3/Plan-4 deployment story:
|
||||
|
||||
- native kernel
|
||||
- native bootloader slice
|
||||
- native runtime slice
|
||||
- host-base-free validated path
|
||||
|
||||
The goal is not yet to change the successful boot/runtime model, only the **source input boundary**.
|
||||
|
||||
**Success Criteria:** Fruix builds native FreeBSD base artifacts from a declared source input, and the resulting store artifacts record that source identity explicitly.
|
||||
|
||||
---
|
||||
|
||||
## Phase 17: Support Multiple FreeBSD Source Revisions Side by Side
|
||||
|
||||
Once source acquisition is explicit, Fruix should prove that it can treat source revisions the way it already treats declared base versions: as side-by-side inputs rather than in-place mutations.
|
||||
|
||||
### Intermediate Goal 17.1: Support Side-by-Side FreeBSD Source Revisions in `/frx/store`
|
||||
|
||||
**Verification Goal 17.1:** Demonstrate that at least two distinct declared FreeBSD source inputs can coexist and produce distinct native base artifacts in `/frx/store`.
|
||||
|
||||
These can differ by:
|
||||
|
||||
- Git revision
|
||||
- source archive hash
|
||||
- local source snapshot hash
|
||||
- or another explicit source identity boundary
|
||||
|
||||
The goal is to prove that the source declaration, not ambient host state, now drives the base build outputs.
|
||||
|
||||
**Success Criteria:** Fruix can hold at least two distinct FreeBSD source revisions side by side as meaningful native base inputs and outputs.
|
||||
|
||||
### Intermediate Goal 17.2: Rebuild and Boot From Two Distinct Source Revisions
|
||||
|
||||
**Verification Goal 17.2:** Build and boot systems from at least two distinct declared source revisions using the validated native base path.
|
||||
|
||||
This should preserve the current validation strategy:
|
||||
|
||||
- local QEMU/UEFI/TCG where useful
|
||||
- real XCP-ng validation on the approved VM and approved A/B VDI path
|
||||
|
||||
The booted systems do not necessarily need to differ in obvious runtime behavior; the important point is that their source identity and native base outputs differ and are tracked correctly.
|
||||
|
||||
**Success Criteria:** Fruix boots systems built from two distinct declared FreeBSD source revisions and records those revisions in system metadata.
|
||||
|
||||
### Intermediate Goal 17.3: Clarify Source Provenance, Caching, and Update Policy
|
||||
|
||||
**Verification Goal 17.3:** Document and encode the intended policy for:
|
||||
|
||||
- source caching
|
||||
- source refresh/invalidation
|
||||
- patch application if any
|
||||
- tree hashing rules
|
||||
- when a new source identity should or should not invalidate native base outputs
|
||||
|
||||
This step should reduce ambiguity before installation artifacts and later self-hosting experiments depend on these source objects.
|
||||
|
||||
**Success Criteria:** the repo clearly explains how FreeBSD source objects are fetched, cached, identified, invalidated, and consumed by native base builds.
|
||||
|
||||
---
|
||||
|
||||
## Phase 18: Turn System Images Into a Real Installation Story
|
||||
|
||||
Fruix can already produce bootable images. The next step is to make installation itself a first-class Fruix story rather than a manual consequence of image generation.
|
||||
|
||||
### Intermediate Goal 18.1: Define a Minimal Non-Interactive Installation Flow
|
||||
|
||||
**Verification Goal 18.1:** Introduce a minimal installation workflow that can take a Fruix system artifact and install it to a target disk or image in a repeatable way.
|
||||
|
||||
The first version should prefer clarity and automation over interactivity. For example, it may:
|
||||
|
||||
- partition a target disk
|
||||
- create required filesystems
|
||||
- copy or materialize the selected system closure
|
||||
- install boot assets
|
||||
- stage activation/runtime metadata
|
||||
- configure root SSH key or basic operator access
|
||||
|
||||
This first step should be VM-friendly and serial-console-friendly.
|
||||
|
||||
**Success Criteria:** Fruix can perform a repeatable installation of a declarative system onto a target disk or image without relying on ad hoc manual assembly.
|
||||
|
||||
### Intermediate Goal 18.2: Produce a Minimal Installer Environment
|
||||
|
||||
**Verification Goal 18.2:** Produce a small installer environment that can boot into a Fruix-managed install context and perform the installation workflow.
|
||||
|
||||
This environment may initially be one of:
|
||||
|
||||
- a special-purpose disk image
|
||||
- a minimal recovery/install rootfs
|
||||
- a proto-installer medium with only the tools needed for partitioning, filesystem creation, and deployment
|
||||
|
||||
The target here is not a polished live environment. It is a reliable installer substrate.
|
||||
|
||||
**Success Criteria:** Fruix can boot a minimal installer environment and use it to install a selected Fruix system to a target disk.
|
||||
|
||||
### Intermediate Goal 18.3: Build a Bootable Installer ISO
|
||||
|
||||
**Verification Goal 18.3:** Produce a bootable installer ISO for UEFI systems.
|
||||
|
||||
The first ISO can be intentionally narrow in scope. It should be enough to:
|
||||
|
||||
- boot under QEMU and, where practical, on the validated virtualization path
|
||||
- expose the install workflow
|
||||
- install a target Fruix system
|
||||
- leave the machine bootable into the installed system
|
||||
|
||||
A polished graphical or highly interactive installer is not required here.
|
||||
|
||||
**Success Criteria:** Fruix can build a bootable installer ISO that installs a declarative Fruix-on-FreeBSD system.
|
||||
|
||||
---
|
||||
|
||||
## Phase 19: Strengthen System Deployment and Generation Management
|
||||
|
||||
The current deployment model already has the important Guix-like semantic properties at the closure/store layer. This phase is about making that story more operator-facing and less harness-specific.
|
||||
|
||||
### Intermediate Goal 19.1: Define a First-Class Fruix Deployment Workflow
|
||||
|
||||
**Verification Goal 19.1:** Define and document the canonical user-facing deployment workflow for system rebuild, image generation, installation, and rollback.
|
||||
|
||||
This may introduce or refine user-facing commands such as:
|
||||
|
||||
- `fruix system build`
|
||||
- `fruix system image`
|
||||
- future deployment/install/switch subcommands if justified
|
||||
|
||||
The key goal is clarity: operators should have an obvious Fruix way to move between generations and deployments.
|
||||
|
||||
**Success Criteria:** the repo documents a coherent user-facing deployment workflow for system build, install, roll-forward, and rollback.
|
||||
|
||||
### Intermediate Goal 19.2: Model System Generations More Explicitly
|
||||
|
||||
**Verification Goal 19.2:** Make the system-generation story more explicit in metadata and deployment roots.
|
||||
|
||||
This should include deciding how Fruix wants to represent:
|
||||
|
||||
- current system generation
|
||||
- previous system generation
|
||||
- rollback target
|
||||
- GC roots associated with installed systems
|
||||
|
||||
This does not have to copy Guix System mechanically, but it should preserve the same important properties.
|
||||
|
||||
**Success Criteria:** Fruix has a clearer model for installed system generations and rollback roots rather than relying mainly on test-harness knowledge.
|
||||
|
||||
### Intermediate Goal 19.3: Validate Installed-System Rollback as an Operator Workflow
|
||||
|
||||
**Verification Goal 19.3:** Validate rollback through the intended installed-system workflow, not only through build/image test harnesses.
|
||||
|
||||
This step should prove that the installation and generation model work together coherently.
|
||||
|
||||
**Success Criteria:** an installed Fruix system can move between generations using the intended operator-facing deployment model.
|
||||
|
||||
---
|
||||
|
||||
## Phase 20: Controlled Steps Toward Self-Hosted Base Builds
|
||||
|
||||
Only after source identity and installation/deployment boundaries are stronger should Fruix seriously revisit self-hosted base builds.
|
||||
|
||||
### Intermediate Goal 20.1: Validate a Fruix-Managed Development Environment for Native Base Work
|
||||
|
||||
**Verification Goal 20.1:** Ensure that a Fruix-managed system can expose the development/runtime/toolchain environment needed for deeper FreeBSD-native build work.
|
||||
|
||||
This should build on the cleaner runtime/development split already established in Phase 14.
|
||||
|
||||
The goal is not yet full self-hosting; it is to prove that the system can host the tools and profiles needed for that work in a controlled way.
|
||||
|
||||
**Success Criteria:** a Fruix-managed system can expose a usable development environment for native FreeBSD build tasks.
|
||||
|
||||
### Intermediate Goal 20.2: Run Host-Initiated Native Base Builds Inside a Fruix-Managed Environment
|
||||
|
||||
**Verification Goal 20.2:** As an intermediate step, perform native base builds inside a Fruix-managed environment or jail while still using the host as the outer orchestrator.
|
||||
|
||||
This narrows the remaining gap without immediately demanding full guest self-hosting.
|
||||
|
||||
**Success Criteria:** Fruix can build native FreeBSD base artifacts from inside a Fruix-managed build environment, with the host still orchestrating the outer loop.
|
||||
|
||||
### Intermediate Goal 20.3: Reassess and Potentially Prototype Guest Self-Hosted Base Builds
|
||||
|
||||
**Verification Goal 20.3:** Revisit guest self-hosting only after the earlier source/install/deployment goals are complete enough to make that experiment meaningful.
|
||||
|
||||
At that point, the question should be answered with real evidence:
|
||||
|
||||
- what exactly self-hosting would improve
|
||||
- what it would cost in complexity
|
||||
- how it would fit with the source/deployment model already established
|
||||
|
||||
**Success Criteria:** the project either:
|
||||
|
||||
- validates a first controlled guest self-hosted base build, or
|
||||
- records a clear evidence-based decision to continue preferring host-orchestrated native builds.
|
||||
|
||||
---
|
||||
|
||||
## Strategic Notes
|
||||
|
||||
### Why this order?
|
||||
|
||||
This sequence is intended to preserve the most important win from Plan 3 and Phase 15:
|
||||
|
||||
- Fruix already has a native, store-based, rollback-friendly FreeBSD base path
|
||||
|
||||
The next improvement should therefore be to make the **source input** as explicit as the **deployment output** already is.
|
||||
|
||||
After that, Fruix can turn its successful image-generation path into a real installation story. Only then will self-hosting be judged in the right context.
|
||||
|
||||
### Why not jump straight to self-hosting?
|
||||
|
||||
Because at the end of Phase 15, the biggest remaining weakness is not “where the build runs”, but “how explicitly the source is declared and acquired”.
|
||||
|
||||
If self-hosting were attempted immediately, it would mix together:
|
||||
|
||||
- source acquisition problems
|
||||
- toolchain/profile maturity problems
|
||||
- build-environment questions
|
||||
- deployment/installation questions
|
||||
- and virtualization/operator constraints
|
||||
|
||||
That would make debugging and design less clear.
|
||||
|
||||
### Why add installation work before self-hosting?
|
||||
|
||||
Because Fruix should become more usable as a system even if self-hosting remains a later milestone.
|
||||
|
||||
A real installation story would:
|
||||
|
||||
- make the current system model more operator-meaningful
|
||||
- improve validation of generation/deployment semantics
|
||||
- prepare the project for broader VM and eventually hardware use
|
||||
- make later self-hosting experiments easier to stage and reproduce
|
||||
|
||||
### What success will mean
|
||||
|
||||
Success under this plan means the project moves from:
|
||||
|
||||
- “Fruix builds native FreeBSD base artifacts from the host’s current `/usr/src` and boots them successfully”
|
||||
|
||||
into:
|
||||
|
||||
- “Fruix acquires FreeBSD sources declaratively, builds native base artifacts from pinned source inputs, installs systems through a real Fruix workflow, and approaches self-hosting from a much cleaner architectural position.”
|
||||
2069
docs/PROGRESS.md
2069
docs/PROGRESS.md
File diff suppressed because it is too large
Load Diff
58
docs/PROG_SUMMARY.md
Normal file
58
docs/PROG_SUMMARY.md
Normal file
@@ -0,0 +1,58 @@
|
||||
# Fruix FreeBSD Progress Summary
|
||||
|
||||
## What we have achieved so far
|
||||
|
||||
Fruix now has a working end-to-end prototype of a Guix-inspired system on FreeBSD, while preserving the Fruix product boundary (`fruix`, `/frx`) and the core model: declarative builds, content-addressed store paths, daemon-mediated privilege separation, garbage-collection roots, derivations, and reproducible system artifacts.
|
||||
|
||||
Completed milestones include:
|
||||
|
||||
- **Core runtime/build foundation**: validated Guile on FreeBSD, diagnosed the packaged-Guile subprocess crash, and established a working local Guile path with the upstream FreeBSD fix.
|
||||
- **Store and daemon path**: built the first Fruix daemon/store workflow on FreeBSD, including jail/build-user isolation, store population under `/frx/store`, derivation generation, and minimal RPC integration.
|
||||
- **Real package builds**: adapted enough of the GNU build/package path to build real package outputs into `/frx/store` and install a minimal profile.
|
||||
- **System model**: introduced a declarative FreeBSD `operating-system` model, reproducible system closures, rootfs generation, and raw image generation as first-class system artifacts.
|
||||
- **Bootable VM images**: built FreeBSD images that boot reproducibly and stage the full `/frx/store` into the guest image.
|
||||
- **Real VM validation on XCP-ng**: proved the system boots on the real target VM, gets DHCP, starts SSH, and exposes a usable guest for validation.
|
||||
- **User-facing CLI**: added `bin/fruix` with `fruix system build|rootfs|image`, making system artifact generation a real Fruix workflow instead of a phase-specific prototype.
|
||||
- **Boot architecture progress**: validated both boot modes on the real XCP-ng VM:
|
||||
- `freebsd-init+rc.d-shepherd`
|
||||
- `shepherd-pid1`
|
||||
- **Native runtime cleanup**: removed the guest runtime’s dependence on `/tmp/*-validate-install` compatibility-prefix symlinks for Guile, guile-extra, and Shepherd. The guest now boots and runs Guile/Shepherd from the store-backed runtime layout without those temporary aliases.
|
||||
- **Native FreeBSD base artifacts**: replaced the validated host-copy boot/runtime path with native `/usr/src`-built artifacts in `/frx/store` for:
|
||||
- `freebsd-native-kernel`
|
||||
- `freebsd-native-bootloader`
|
||||
- `freebsd-native-runtime`
|
||||
- **Declarative FreeBSD base model**: the FreeBSD base is now an explicit system input via `freebsd-base`, not just an ambient property of the builder host.
|
||||
- **Declarative FreeBSD source model and materialization**: Fruix can now describe FreeBSD sources explicitly via `freebsd-source` and materialize them from:
|
||||
- local source trees
|
||||
- `https://git.FreeBSD.org/src.git`
|
||||
- official `src.txz` archives such as `https://download.freebsd.org/releases/amd64/15.0-RELEASE/src.txz`
|
||||
into `/frx/store`, with cache-backed provenance under `/frx/var/cache/fruix/freebsd-source`.
|
||||
- **Source-driven native base builds**: native FreeBSD kernel/bootloader/runtime artifacts now consume those materialized source snapshots rather than ambient `/usr/src`, and their build metadata records both the declared source and the effective materialized source identity.
|
||||
- **Side-by-side source revisions**: Fruix can now keep distinct FreeBSD source identities side by side in `/frx/store` and produce distinct native base outputs from them, even when the visible base version label is held constant.
|
||||
- **Source-driven boot validation**: Fruix can now also boot systems built from distinct declared FreeBSD source revisions while preserving those source identities in image/build metadata.
|
||||
- **Explicit source policy**: the repo now records how FreeBSD source objects are fetched, cached, identified, invalidated, and consumed by native base builds in `docs/freebsd-source-policy.md`.
|
||||
- **Minimal installation workflow**: Fruix now has a non-interactive `fruix system install` path that can partition, format, populate, and boot a target image or disk from a declarative system closure.
|
||||
- **Minimal installer environment**: Fruix can now also build and boot a dedicated installer image that carries a selected target closure, installs it onto a second disk from inside the guest, and leaves the installed target bootable.
|
||||
- **Base upgrade story**: Fruix can now keep distinct declared base versions side by side in `/frx/store` and roll forward / back between them through the normal system deployment flow.
|
||||
|
||||
## Major pain points now behind us
|
||||
|
||||
- **Guile subprocess instability on FreeBSD**: root-caused and bypassed with the validated local Guile build.
|
||||
- **“Prototype only” store/build flow**: replaced with real derivations, store outputs, and system artifacts under `/frx/store`.
|
||||
- **Non-booting early images**: fixed rootfs/image composition, GPT sizing issues for XCP-ng, and early rc/fstab problems.
|
||||
- **No practical VM validation path**: when local bhyve proved impossible under Xen, validation successfully pivoted to the real XCP-ng VM and existing VDI.
|
||||
- **Guest runtime crashes from locale/prefix issues**: fixed enough locale/runtime staging and later removed dependence on guest-side compatibility-prefix shims.
|
||||
- **Shepherd only as a bridged service**: Fruix has now proven Shepherd can run as PID 1 on FreeBSD, not just behind FreeBSD `init` + `rc.d`.
|
||||
|
||||
## Major pain points still ahead
|
||||
|
||||
- **True store-native runtime artifacts**: some historical build/install prefixes are still embedded in binaries and metadata. They are no longer required at runtime, but the local Guile/guile-extra/Shepherd build/install flow should still be moved to a genuinely store-native prefix from the start.
|
||||
- **Installer media beyond disk images**: Fruix now has both a host-driven install path and a bootable installer environment, but it still lacks a UEFI installer ISO and a more polished operator-facing installation medium.
|
||||
- **Boot-path simplification**: Fruix now supports both the legacy `freebsd-init+rc.d-shepherd` path and the more Guix-like `shepherd-pid1` path. We still need to decide whether Shepherd PID 1 becomes the preferred/default architecture.
|
||||
- **Reduce transitional FreeBSD glue**: more of the current bootstrap/activation/runtime setup should become cleaner and less prototype-specific over time.
|
||||
- **Tooling and platform constraints**: local bhyve remains blocked by missing nested virtualization under Xen, and XO permissions still prevent creating/importing new VDIs; current validation must keep reusing the approved VM/VDI path.
|
||||
- **Remaining ecosystem gaps**: some deeper Guix-like features are still immature on FreeBSD, especially around native package-management ergonomics and fully polished runtime/deployment behavior.
|
||||
|
||||
## Bottom line
|
||||
|
||||
Fruix has crossed the most important threshold: it is no longer just a collection of isolated FreeBSD experiments. It can now build declarative FreeBSD system artifacts, boot them on the real target VM, reach the network, serve SSH, run Shepherd as PID 1, operate from `/frx` without depending on temporary runtime-prefix shims, build native FreeBSD base artifacts into `/frx/store`, roll forward / back between declared base versions, materialize declared FreeBSD source inputs into `/frx/store`, drive native base builds from those materialized source snapshots, boot systems from distinct source revisions, explain the source provenance/invalidation rules explicitly, install a declarative system onto a target image through a repeatable Fruix workflow, and boot a dedicated Fruix-managed installer environment that performs that installation from inside the guest. The biggest remaining work is no longer “can this build/install at all?” but “how does this become a fuller installer/deployment/generation/installer-media story?”
|
||||
@@ -1,4 +1,4 @@
|
||||
Your task is described in ./docs/PLAN_2.md. Current progress is stored in ./docs/PROGRESS.md.
|
||||
Your task is described in ./docs/PLAN_3.md. Current progress is stored in ./docs/PROGRESS.md.
|
||||
|
||||
Perform the next step towards the final goal. Update the progress file and `git commit` after each subphase (or even in between, if adequate).
|
||||
|
||||
|
||||
330
docs/freebsd-source-policy.md
Normal file
330
docs/freebsd-source-policy.md
Normal file
@@ -0,0 +1,330 @@
|
||||
# Fruix FreeBSD Source Policy
|
||||
|
||||
This document records the current intended policy for how Fruix models, fetches, caches, identifies, invalidates, and consumes FreeBSD source inputs.
|
||||
|
||||
It reflects the behavior implemented through Plan 4 Phases 16 and 17.
|
||||
|
||||
## 1. Source object model
|
||||
|
||||
Fruix represents FreeBSD source inputs explicitly with `freebsd-source`.
|
||||
|
||||
Currently supported source kinds are:
|
||||
|
||||
- `local-tree`
|
||||
- `git`
|
||||
- `src-txz`
|
||||
|
||||
A source object records some combination of:
|
||||
|
||||
- `name`
|
||||
- `kind`
|
||||
- `url`
|
||||
- `path`
|
||||
- `ref`
|
||||
- `commit`
|
||||
- `sha256`
|
||||
|
||||
Fruix distinguishes between:
|
||||
|
||||
- the **declared source**
|
||||
- what the operator asked for
|
||||
- the **effective source**
|
||||
- what Fruix actually resolved/materialized and built from
|
||||
|
||||
That distinction matters most for Git refs and local-tree snapshots.
|
||||
|
||||
## 2. Source identity boundaries
|
||||
|
||||
### `local-tree`
|
||||
|
||||
Declared selector:
|
||||
|
||||
- local filesystem path
|
||||
|
||||
Effective identity boundary:
|
||||
|
||||
- filtered source-tree hash computed from `mtree`
|
||||
|
||||
Current rule:
|
||||
|
||||
- Fruix computes source identity from:
|
||||
- `mtree -c -k type,link,size,mode,sha256digest`
|
||||
- comment lines are removed before hashing
|
||||
|
||||
This avoids unstable `mtree` comment headers such as:
|
||||
|
||||
- date
|
||||
- user
|
||||
- machine
|
||||
|
||||
So the effective local-tree identity is content-oriented rather than host-comment-oriented.
|
||||
|
||||
### `git`
|
||||
|
||||
Declared selectors:
|
||||
|
||||
- `commit`, or
|
||||
- `ref`
|
||||
|
||||
Effective identity boundary:
|
||||
|
||||
- resolved commit
|
||||
|
||||
Current rule:
|
||||
|
||||
- if `commit` is present, Fruix uses it as the fetch selector
|
||||
- otherwise, Fruix uses `ref`
|
||||
- after fetch, Fruix records the resolved commit as the effective Git identity
|
||||
|
||||
Policy consequence:
|
||||
|
||||
- a Git **ref** is a convenience selector
|
||||
- a Git **commit** is the reproducibility boundary
|
||||
|
||||
Therefore:
|
||||
|
||||
- use `ref` alone for exploratory/latest tracking when drift is acceptable
|
||||
- use `commit` for reproducible builds, side-by-side comparisons, installation artifacts, and rollback-sensitive workflows
|
||||
- it is valid to record both:
|
||||
- `ref` for human provenance
|
||||
- `commit` for stable identity
|
||||
|
||||
### `src-txz`
|
||||
|
||||
Declared selectors:
|
||||
|
||||
- URL
|
||||
- expected `sha256`
|
||||
|
||||
Effective identity boundary:
|
||||
|
||||
- verified archive `sha256`
|
||||
|
||||
Policy consequence:
|
||||
|
||||
- `src-txz` materialization is only valid when `sha256` is declared
|
||||
- archive URL alone is not enough
|
||||
|
||||
## 3. Cache policy
|
||||
|
||||
Default cache root:
|
||||
|
||||
- `/frx/var/cache/fruix/freebsd-source`
|
||||
|
||||
Current cache layout:
|
||||
|
||||
- Git:
|
||||
- `/frx/var/cache/fruix/freebsd-source/git/<hash>.git`
|
||||
- release/snapshot archives:
|
||||
- `/frx/var/cache/fruix/freebsd-source/archives/<hash>-src.txz`
|
||||
|
||||
### Git cache behavior
|
||||
|
||||
Fruix keeps a bare repository cache keyed by source URL.
|
||||
|
||||
Current behavior:
|
||||
|
||||
- initialize bare repo if absent
|
||||
- ensure `origin` matches the declared URL
|
||||
- fetch the requested selector from `origin`
|
||||
- archive the resolved commit into the materialized store object
|
||||
|
||||
Policy:
|
||||
|
||||
- the cache is a transport/proxy optimization, not the identity boundary
|
||||
- the identity boundary is the resolved commit recorded in the effective source and materialization metadata
|
||||
|
||||
### `src-txz` cache behavior
|
||||
|
||||
Fruix keeps the downloaded archive under the cache root.
|
||||
|
||||
Current behavior:
|
||||
|
||||
- if a cached archive exists, hash it
|
||||
- if its hash does not match the declared `sha256`, delete it
|
||||
- fetch the archive if missing
|
||||
- verify the downloaded archive hash
|
||||
- fail if the verified hash does not match the declared `sha256`
|
||||
|
||||
Policy:
|
||||
|
||||
- the cache is reusable only when the declared hash still matches
|
||||
- hash mismatch invalidates the cached archive immediately
|
||||
|
||||
### `local-tree`
|
||||
|
||||
Current behavior:
|
||||
|
||||
- no separate network cache
|
||||
- Fruix snapshots the local tree into a materialized store object
|
||||
|
||||
Policy:
|
||||
|
||||
- the local path is only a selector to a mutable host tree
|
||||
- the materialized snapshot and its filtered tree hash are the meaningful Fruix identity boundary
|
||||
|
||||
## 4. Materialized source store policy
|
||||
|
||||
Materialized FreeBSD sources are stored in:
|
||||
|
||||
- `/frx/store/*-freebsd-source-*`
|
||||
|
||||
Current manifest inputs for a materialized source object include:
|
||||
|
||||
- materializer version
|
||||
- declared source
|
||||
- effective source
|
||||
- source identity tuple
|
||||
- for example resolved commit, archive sha256, or local tree hash
|
||||
|
||||
Policy consequence:
|
||||
|
||||
- changing any of those identity inputs should produce a distinct source store path
|
||||
- changing materialization semantics should bump the materializer version
|
||||
|
||||
Current materializer version:
|
||||
|
||||
- `freebsd-source-materializer-version = "2"`
|
||||
|
||||
## 5. Effective source root policy
|
||||
|
||||
Not every source unpacks to the same top-level directory shape.
|
||||
|
||||
Current behavior:
|
||||
|
||||
- if `tree/Makefile` exists, effective root is:
|
||||
- `tree`
|
||||
- else if `tree/usr/src/Makefile` exists, effective root is:
|
||||
- `tree/usr/src`
|
||||
|
||||
This is why:
|
||||
|
||||
- Git exports materialize effectively at `.../tree`
|
||||
- official `src.txz` archives materialize effectively at `.../tree/usr/src`
|
||||
|
||||
Policy:
|
||||
|
||||
- native builds must consume the detected effective source root, not assume `/usr/src`
|
||||
- the declared transitional `source-root` may still be recorded for provenance, but it is not the effective build root once materialization is in use
|
||||
|
||||
## 6. Native build invalidation policy
|
||||
|
||||
Native FreeBSD kernel/world/runtime/bootloader outputs must be invalidated by source identity, not just by package name/version.
|
||||
|
||||
Current behavior:
|
||||
|
||||
- native package materialization rewrites the install plan to use the materialized source root
|
||||
- native manifests record both:
|
||||
- `declared-source`
|
||||
- `materialized-source`
|
||||
- package materialization caching keys on the full manifest identity rather than only package name/version
|
||||
- native build common metadata includes:
|
||||
- `source-root`
|
||||
- `source-tree-sha256`
|
||||
- kernconf hash
|
||||
- target metadata
|
||||
|
||||
Policy consequence:
|
||||
|
||||
- two builds with the same visible base version label but different source identities must still produce different native output store paths
|
||||
- that behavior is intentional and required
|
||||
|
||||
## 7. Closure provenance policy
|
||||
|
||||
System closures and images should preserve enough source metadata to explain exactly what source snapshot was used.
|
||||
|
||||
Current closure/image metadata includes:
|
||||
|
||||
- `metadata/freebsd-source.scm`
|
||||
- `metadata/freebsd-source-materializations.scm`
|
||||
- `materialized_source_store_count`
|
||||
- `materialized_source_stores`
|
||||
|
||||
Native `.freebsd-native-build-info.scm` records:
|
||||
|
||||
- declared source
|
||||
- materialized source store path
|
||||
- materialized source root
|
||||
- materialized source tree hash
|
||||
- effective Git commit or archive hash when applicable
|
||||
|
||||
Policy:
|
||||
|
||||
- source provenance is part of the closure boundary, not just a transient fetch detail
|
||||
|
||||
## 8. Update policy
|
||||
|
||||
### Recommended operator policy
|
||||
|
||||
For reproducible and rollback-sensitive workflows:
|
||||
|
||||
- prefer Git sources pinned by `commit`
|
||||
- prefer archive sources pinned by `sha256`
|
||||
- treat `local-tree` as a development/debugging input unless the resulting materialized snapshot is itself the explicit artifact being compared
|
||||
|
||||
### Moving refs
|
||||
|
||||
A moving Git ref is expected to drift over time.
|
||||
|
||||
Policy:
|
||||
|
||||
- if only `ref` is declared, Fruix may legitimately produce a new materialized source store later
|
||||
- that new materialized source identity should then invalidate native outputs
|
||||
- this is expected behavior, not a cache bug
|
||||
|
||||
### Installed/deployed systems
|
||||
|
||||
For installation artifacts and generation management, the source identity should be stable enough to answer:
|
||||
|
||||
- what exact source revision produced this system?
|
||||
- can it be rebuilt later?
|
||||
- should this update be considered a new generation boundary?
|
||||
|
||||
That means later installation/deployment work should prefer:
|
||||
|
||||
- Git commit-pinned sources
|
||||
- hash-pinned archives
|
||||
|
||||
rather than floating refs alone.
|
||||
|
||||
## 9. Patch/transformation policy
|
||||
|
||||
Fruix does not yet expose a first-class patch queue or transformation layer on top of `freebsd-source`.
|
||||
|
||||
Current policy until that exists:
|
||||
|
||||
- the declared source object should be treated as the full upstream-source identity boundary
|
||||
- when a patch/transformation layer is introduced later, it must become part of the materialized source identity and manifest versioning
|
||||
|
||||
In other words:
|
||||
|
||||
- applying patches without changing source identity metadata would be wrong
|
||||
|
||||
## 10. Relation to Guix-inspired semantics
|
||||
|
||||
This policy follows the same important high-level idea as Guix:
|
||||
|
||||
- selectors are not enough
|
||||
- resolved, content-stable identities matter
|
||||
- cached transport objects are not the same as reproducible store identities
|
||||
|
||||
Fruix applies that idea in FreeBSD-specific form:
|
||||
|
||||
- `src.txz` handling
|
||||
- FreeBSD source-tree effective-root detection
|
||||
- mtree-based FreeBSD tree identity
|
||||
- native FreeBSD base builds driven from materialized source snapshots under `/frx/store`
|
||||
|
||||
## 11. Practical summary
|
||||
|
||||
Use these rules:
|
||||
|
||||
- **Want reproducibility?**
|
||||
- pin Git by `commit`
|
||||
- pin archives by `sha256`
|
||||
- **Want side-by-side comparison?**
|
||||
- keep distinct source objects and let Fruix materialize them separately
|
||||
- **Want native base outputs to differ only when they should?**
|
||||
- rely on materialized source identity, not mutable `/usr/src`
|
||||
- **Want stable deployment provenance?**
|
||||
- preserve the closure metadata files and materialized source store references
|
||||
163
docs/reports/phase10-canonical-system-workflows-freebsd.md
Normal file
163
docs/reports/phase10-canonical-system-workflows-freebsd.md
Normal file
@@ -0,0 +1,163 @@
|
||||
# Phase 10 completion: canonical FreeBSD system workflows now use `fruix system`
|
||||
|
||||
Date: 2026-04-02
|
||||
|
||||
## Goal
|
||||
|
||||
Complete the current Optional Phase 10 track by moving the existing FreeBSD system workflows away from phase-specific direct materializer entry points and toward the real Fruix command surface introduced in Phase 10.1.
|
||||
|
||||
Concretely, this subphase targeted three things:
|
||||
|
||||
- make the existing system validation harnesses call `bin/fruix` as their canonical frontend,
|
||||
- keep the deeper static and boot validations intact after that refactor,
|
||||
- and prove that the real XCP-ng deployment path still works when the image is produced through the `fruix system` path.
|
||||
|
||||
## Result
|
||||
|
||||
This refactor succeeded.
|
||||
|
||||
The main static system harnesses no longer invoke the Scheme materializer runners directly as their primary frontend. They now call the real Fruix command and then perform their existing validation logic on the resulting artifacts.
|
||||
|
||||
Updated harnesses:
|
||||
|
||||
- `tests/system/run-phase7-system-closure.sh`
|
||||
- `tests/system/run-phase7-rootfs.sh`
|
||||
- `tests/system/run-phase8-system-image.sh`
|
||||
|
||||
These harnesses now use:
|
||||
|
||||
- `bin/fruix system build`
|
||||
- `bin/fruix system rootfs`
|
||||
- `bin/fruix system image`
|
||||
|
||||
The Phase 9 XCP-ng boot path benefits from this automatically because:
|
||||
|
||||
- `tests/system/run-phase9-xcpng-boot.sh`
|
||||
still calls `tests/system/run-phase8-system-image.sh`,
|
||||
- and that Phase 8 harness now builds the image through `bin/fruix`.
|
||||
|
||||
## Code-level changes
|
||||
|
||||
### `scripts/fruix.scm`
|
||||
|
||||
Extended the `fruix system build` metadata so callers can recover more of the closure structure without having to re-enter the Scheme materializer directly.
|
||||
|
||||
Added emitted fields for:
|
||||
|
||||
- `ready_marker`
|
||||
- `base_package_store_count`
|
||||
- `base_package_stores`
|
||||
|
||||
That made it practical for the refactored shell harnesses to keep their old validation/reporting fidelity while using the new CLI frontend.
|
||||
|
||||
### `tests/system/run-phase7-system-closure.sh`
|
||||
|
||||
Reworked to:
|
||||
|
||||
- call `bin/fruix system build` twice,
|
||||
- validate closure reproducibility through the command frontend,
|
||||
- validate closure contents and key symlink targets,
|
||||
- and record metadata showing `frontend_invocation=/.../bin/fruix system build`.
|
||||
|
||||
### `tests/system/run-phase7-rootfs.sh`
|
||||
|
||||
Reworked to:
|
||||
|
||||
- call `bin/fruix system rootfs`,
|
||||
- validate the generated rootfs symlink structure and content expectations,
|
||||
- and record metadata showing `frontend_invocation=/.../bin/fruix system rootfs`.
|
||||
|
||||
### `tests/system/run-phase8-system-image.sh`
|
||||
|
||||
Reworked to:
|
||||
|
||||
- call `bin/fruix system image`,
|
||||
- preserve the existing GPT/filesystem/mount/static-layout validation logic,
|
||||
- and record metadata showing `frontend_invocation=/.../bin/fruix system image`.
|
||||
|
||||
## Validation
|
||||
|
||||
### Static workflow validation
|
||||
|
||||
Successful closure validation:
|
||||
|
||||
- `PASS phase7-system-closure`
|
||||
- workdir: `/tmp/phase10-canon-closure2-1775119728`
|
||||
|
||||
Successful rootfs validation:
|
||||
|
||||
- `PASS phase7-rootfs`
|
||||
- workdir: `/tmp/phase10-canon-rootfs3-1775120391`
|
||||
|
||||
Successful image validation:
|
||||
|
||||
- `PASS phase8-system-image`
|
||||
- workdir: `/tmp/phase10-canon-image-1775120548`
|
||||
|
||||
The resulting metadata confirms that all of those paths are now driven by `bin/fruix`.
|
||||
|
||||
### Real XCP-ng regression validation
|
||||
|
||||
To ensure this was not merely a static refactor, the full real-VM path was rerun after the harness changes.
|
||||
|
||||
Successful real boot validation:
|
||||
|
||||
- `PASS phase9-xcpng-boot`
|
||||
- workdir: `/tmp/phase10-canon-xcpng-1775120869`
|
||||
|
||||
That successful rerun confirmed that the `fruix system image`-driven path still produces an image that can:
|
||||
|
||||
- be converted to dynamic VHD,
|
||||
- be imported into the existing XCP-ng VDI,
|
||||
- boot on VM `90490f2e-e8fc-4b7a-388e-5c26f0157289`,
|
||||
- reach DHCP and SSH,
|
||||
- keep Shepherd running,
|
||||
- and reach the ready marker.
|
||||
|
||||
Representative final guest state from that pass:
|
||||
|
||||
```text
|
||||
ready_marker=ready
|
||||
shepherd_status=running
|
||||
sshd_status=running
|
||||
run_current_system_target=/frx/store/0fe459ea22156510e64cea794b7a001151b59625bd5f12a488d6851e1c6d2198-fruix-system-fruix-freebsd
|
||||
boot_backend=xcp-ng-xo-cli
|
||||
```
|
||||
|
||||
## Why this completes Phase 10 on the current track
|
||||
|
||||
By the end of Phase 9, Fruix already had:
|
||||
|
||||
- a declarative system model,
|
||||
- real closure and image outputs in `/frx/store`,
|
||||
- and a booted FreeBSD VM with Shepherd and SSH.
|
||||
|
||||
What was still transitional was the operator/tooling layer: too much of the system workflow was still centered on phase-specific scripts invoking internal materializers directly.
|
||||
|
||||
After Phase 10.1 and this completion step, the current track now has:
|
||||
|
||||
- a user-facing Fruix command: `bin/fruix`
|
||||
- real system actions under that command:
|
||||
- `system build`
|
||||
- `system rootfs`
|
||||
- `system image`
|
||||
- existing validation/deployment workflows using that command as their canonical frontend
|
||||
- and successful regression validation on the real XCP-ng guest path
|
||||
|
||||
That is enough to treat Optional Phase 10 as complete for the current FreeBSD prototype track.
|
||||
|
||||
## Remaining follow-up work beyond Phase 10
|
||||
|
||||
There is still good follow-up work available, but it is no longer required to say that Fruix has crossed from “prototype scripts only” into a real OS-tooling shape.
|
||||
|
||||
Useful future cleanup includes:
|
||||
|
||||
- replacing the current Guile/Shepherd compatibility-prefix shims with a more native runtime arrangement,
|
||||
- polishing residual boot noise and base-service rough edges,
|
||||
- and extending `fruix` with richer deploy/vm-oriented commands.
|
||||
|
||||
## Conclusion
|
||||
|
||||
Optional Phase 10 is now complete on the current track.
|
||||
|
||||
Fruix no longer just has a declarative FreeBSD system implementation internally; it now has a real Fruix command surface that is used by the canonical closure/rootfs/image workflows, and that command path has been validated all the way through a successful real XCP-ng boot.
|
||||
149
docs/reports/phase10-fruix-system-command-freebsd.md
Normal file
149
docs/reports/phase10-fruix-system-command-freebsd.md
Normal file
@@ -0,0 +1,149 @@
|
||||
# Phase 10.1: Add a real `fruix system` command for FreeBSD system artifacts
|
||||
|
||||
Date: 2026-04-02
|
||||
|
||||
## Goal
|
||||
|
||||
Start Optional Phase 10 by replacing one of the remaining prototype-only workflows with a real user-facing Fruix command.
|
||||
|
||||
The concrete target for this subphase was:
|
||||
|
||||
- expose the declarative FreeBSD operating-system machinery through a true `fruix` CLI entry point,
|
||||
- stop relying solely on ad hoc phase-specific harness scripts for closure/image materialization,
|
||||
- and validate that the new command can build the existing declarative system outputs.
|
||||
|
||||
## Result
|
||||
|
||||
A minimal user-facing `fruix` command now exists in this repository.
|
||||
|
||||
New entry points:
|
||||
|
||||
- `bin/fruix`
|
||||
- `scripts/fruix.scm`
|
||||
|
||||
Supported commands in this first cut:
|
||||
|
||||
- `fruix system build OS-FILE`
|
||||
- `fruix system image OS-FILE`
|
||||
- `fruix system rootfs OS-FILE ROOTFS-DIR`
|
||||
|
||||
Supported options:
|
||||
|
||||
- `--system NAME`
|
||||
- `--store DIR`
|
||||
- `--disk-capacity SIZE`
|
||||
- `--rootfs DIR`
|
||||
- `--help`
|
||||
|
||||
The command loads a declarative operating-system definition from a Scheme file, resolves the selected operating-system binding, and then calls the existing Fruix FreeBSD system materializers directly:
|
||||
|
||||
- `materialize-operating-system`
|
||||
- `materialize-rootfs`
|
||||
- `materialize-bhyve-image`
|
||||
|
||||
The command prints machine-readable `key=value` metadata for the produced artifacts.
|
||||
|
||||
## Why this matters
|
||||
|
||||
Up through Phase 9, the project had already achieved a real booted Fruix guest, but the operator-facing path for producing those artifacts still lived mostly in phase-specific test harnesses such as:
|
||||
|
||||
- `tests/system/run-phase7-system-closure.sh`
|
||||
- `tests/system/run-phase8-system-image.sh`
|
||||
- `tests/system/run-phase9-xcpng-boot.sh`
|
||||
|
||||
Those were valuable validation tools, but they were not yet a real Fruix interface.
|
||||
|
||||
This subphase moves the project one step closer to a genuine OS workflow by making system artifact generation available through a Fruix CLI surface.
|
||||
|
||||
## Implementation details
|
||||
|
||||
### `bin/fruix`
|
||||
|
||||
Added a shell wrapper that:
|
||||
|
||||
- locates the repository root,
|
||||
- locates the Guile and Guile-extra validation prefixes already used elsewhere in the project,
|
||||
- ensures the required local Guile/Fibers/Shepherd pieces exist,
|
||||
- prepares `GUILE_LOAD_PATH` for:
|
||||
- `modules/`
|
||||
- `~/repos/guix`
|
||||
- and runs the Guile entry point.
|
||||
|
||||
This keeps the new command aligned with the currently validated local FreeBSD Guile toolchain rather than inventing a separate runtime path.
|
||||
|
||||
### `scripts/fruix.scm`
|
||||
|
||||
Added the actual CLI implementation.
|
||||
|
||||
It currently:
|
||||
|
||||
- parses `fruix system ...` actions,
|
||||
- supports `--help`,
|
||||
- loads an operating-system definition file,
|
||||
- resolves the selected OS variable via `--system NAME` or a small fallback list of conventional names,
|
||||
- validates the operating-system object,
|
||||
- dispatches to the existing FreeBSD system materializers,
|
||||
- and emits stable `key=value` metadata.
|
||||
|
||||
### `tests/system/run-phase10-fruix-system-command.sh`
|
||||
|
||||
Added a dedicated validation harness for the new command.
|
||||
|
||||
The test verifies that:
|
||||
|
||||
1. `fruix system build` materializes a closure under `/frx/store`
|
||||
2. the returned closure contains the generated activation path
|
||||
3. `fruix system image` materializes a disk image under `/frx/store`
|
||||
4. the command emits the expected metadata fields for both operations
|
||||
|
||||
## Validation
|
||||
|
||||
Successful validation run:
|
||||
|
||||
- `PASS phase10-fruix-system-command`
|
||||
- workdir: `/tmp/phase10-fruix-cmd-1775117490`
|
||||
|
||||
The new command successfully produced both:
|
||||
|
||||
- a Fruix system closure path under `/frx/store/*-fruix-system-fruix-freebsd`
|
||||
- a Fruix image path under `/frx/store/*-fruix-bhyve-image-fruix-freebsd/disk.img`
|
||||
|
||||
Example usage:
|
||||
|
||||
```sh
|
||||
sudo env HOME="$HOME" ./bin/fruix system build \
|
||||
tests/system/phase7-minimal-operating-system.scm \
|
||||
--system phase7-operating-system
|
||||
```
|
||||
|
||||
```sh
|
||||
sudo env HOME="$HOME" ./bin/fruix system image \
|
||||
tests/system/phase7-minimal-operating-system.scm \
|
||||
--system phase7-operating-system \
|
||||
--disk-capacity 5g
|
||||
```
|
||||
|
||||
## Current limitations
|
||||
|
||||
This is intentionally a first Phase 10 step, not the final Fruix command surface.
|
||||
|
||||
Notable current limitations:
|
||||
|
||||
- system/image materialization still expects the currently validated local Guile/Shepherd build prefixes
|
||||
- writing into `/frx/store` and building disk images still typically requires `sudo`
|
||||
- the command currently targets only the FreeBSD system prototype path already implemented in `modules/fruix/system/freebsd.scm`
|
||||
- it does not yet integrate with the larger upstream Guix command framework; it is a Fruix-native CLI wrapper in this repo
|
||||
|
||||
## Assessment
|
||||
|
||||
This subphase successfully replaces an important transitional layer with a more OS-like Fruix interface.
|
||||
|
||||
The project can now say not only that it can declaratively define and boot a Fruix FreeBSD system, but also that it has a direct user-facing command for materializing the resulting system artifacts.
|
||||
|
||||
## Recommended next step
|
||||
|
||||
Continue Phase 10 by reducing another transitional seam behind `fruix system`, most likely one of:
|
||||
|
||||
1. add a `fruix system vm` / deploy-oriented flow on top of the now-working image path
|
||||
2. replace the current compatibility symlink handling for locally built Guile/Shepherd prefixes with a more native store-path-aware runtime arrangement
|
||||
3. factor the phase-specific system test harnesses to call `bin/fruix` directly where practical, so the command becomes the canonical operator path rather than just an additional wrapper
|
||||
129
docs/reports/phase12-host-staged-base-boundary-freebsd.md
Normal file
129
docs/reports/phase12-host-staged-base-boundary-freebsd.md
Normal file
@@ -0,0 +1,129 @@
|
||||
# Phase 12.3: made the host-staged FreeBSD base boundary explicit
|
||||
|
||||
Date: 2026-04-02
|
||||
|
||||
## Goal
|
||||
|
||||
The current Fruix FreeBSD system still uses a transitional base layer assembled by copying selected files from the builder host. That was already true in practice, but the code needed to make this boundary explicit so the later move toward native `world`/`kernel` artifacts is easier to reason about.
|
||||
|
||||
The goal of this subphase was therefore not to change how the system boots, but to clarify in both code and generated metadata:
|
||||
|
||||
- which package sets are still host-staged FreeBSD base packages
|
||||
- that this is an explicit transitional boundary
|
||||
- what the first intended replacement order is for native base-build work
|
||||
|
||||
## Implementation
|
||||
|
||||
### 1. Explicit host-staged package sets in `modules/fruix/packages/freebsd.scm`
|
||||
|
||||
The package module now exports named transitional sets instead of only generic lists:
|
||||
|
||||
- `%freebsd-host-staged-all-packages`
|
||||
- `%freebsd-host-staged-core-packages`
|
||||
- `%freebsd-host-staged-development-profile-packages`
|
||||
- `%freebsd-host-staged-system-packages`
|
||||
- `%freebsd-host-staged-replacement-order`
|
||||
- `freebsd-host-staged-package?`
|
||||
|
||||
The older names remain as compatibility aliases:
|
||||
|
||||
- `%freebsd-core-packages`
|
||||
- `%freebsd-development-profile-packages`
|
||||
- `%freebsd-system-packages`
|
||||
|
||||
This preserves existing callers while making the transitional nature of the current base model visible in the code.
|
||||
|
||||
### 2. Replacement order is now encoded directly in the package layer
|
||||
|
||||
The new `%freebsd-host-staged-replacement-order` documents the intended first replacement sequence:
|
||||
|
||||
1. `freebsd-kernel`, `freebsd-bootloader`
|
||||
2. `freebsd-runtime`, `freebsd-libc`, `freebsd-userland`, `freebsd-rc-scripts`
|
||||
3. `freebsd-networking`, `freebsd-openssh`
|
||||
4. `freebsd-kernel-headers`, `freebsd-clang-toolchain`
|
||||
5. `freebsd-gmake`, `freebsd-autotools`, `freebsd-openssl`, `freebsd-zlib`, `freebsd-sh`, `freebsd-bash`
|
||||
|
||||
This is not yet native base-build implementation, but it makes the intended transition path concrete.
|
||||
|
||||
### 3. The closure metadata now carries that boundary too
|
||||
|
||||
`modules/fruix/system/freebsd.scm` now records the replacement order directly in:
|
||||
|
||||
- `metadata/store-layout.scm`
|
||||
|
||||
alongside:
|
||||
|
||||
- host-staged base store paths
|
||||
- Fruix runtime store paths
|
||||
- init mode
|
||||
|
||||
This means the generated closure itself now explains both:
|
||||
|
||||
- what part of the current system still comes from the host-staged FreeBSD base, and
|
||||
- what part is expected to be replaced first by future native base artifacts.
|
||||
|
||||
## Validation
|
||||
|
||||
### Package-layer validation
|
||||
|
||||
Confirmed directly with Guile:
|
||||
|
||||
```scheme
|
||||
(use-modules (fruix packages freebsd))
|
||||
(freebsd-host-staged-package? freebsd-runtime) => #t
|
||||
%freebsd-host-staged-replacement-order
|
||||
```
|
||||
|
||||
Observed output included:
|
||||
|
||||
```text
|
||||
#t
|
||||
((first-wave freebsd-kernel freebsd-bootloader)
|
||||
(second-wave freebsd-runtime freebsd-libc freebsd-userland freebsd-rc-scripts)
|
||||
...)
|
||||
```
|
||||
|
||||
### System-closure validation
|
||||
|
||||
Passing run:
|
||||
|
||||
- `PASS phase7-system-closure`
|
||||
- workdir: `/tmp/phase12-3-closure-1775162784`
|
||||
|
||||
The generated closure metadata file:
|
||||
|
||||
- `/frx/store/25ae9bb85da60b8c77971325e0e11d5390a064132a35e1bab0866cabb802a606-fruix-system-fruix-freebsd/metadata/store-layout.scm`
|
||||
|
||||
now records:
|
||||
|
||||
- `host-base-stores`
|
||||
- `fruix-runtime-stores`
|
||||
- `host-base-replacement-order`
|
||||
|
||||
with content including:
|
||||
|
||||
```text
|
||||
(host-base-replacement-order
|
||||
(first-wave freebsd-kernel freebsd-bootloader)
|
||||
(second-wave freebsd-runtime freebsd-libc freebsd-userland freebsd-rc-scripts)
|
||||
...)
|
||||
```
|
||||
|
||||
## Assessment
|
||||
|
||||
This subphase makes the current architectural compromise explicit instead of leaving it implicit in naming and package-list accidents.
|
||||
|
||||
That matters because Fruix is now moving into the phase where native FreeBSD base artifacts should start displacing the host-copy model. The system now has a clearer answer to the question:
|
||||
|
||||
- what exactly is still transitional,
|
||||
- and in what order should it be retired?
|
||||
|
||||
## Next recommended step
|
||||
|
||||
Phase 12 is now complete.
|
||||
|
||||
The next major step is Phase 13:
|
||||
|
||||
- start modeling and producing native FreeBSD `world`/`kernel` artifacts from `/usr/src`
|
||||
- stage them in `/frx/store`
|
||||
- and begin replacing the current host-staged base boundary with those new outputs
|
||||
161
docs/reports/phase12-provenance-diagnostics-freebsd.md
Normal file
161
docs/reports/phase12-provenance-diagnostics-freebsd.md
Normal file
@@ -0,0 +1,161 @@
|
||||
# Phase 12.1: deployment provenance and diagnostic metadata for the current FreeBSD pipeline
|
||||
|
||||
Date: 2026-04-02
|
||||
|
||||
## Goal
|
||||
|
||||
Before starting native FreeBSD base builds from `/usr/src`, the current host-staged pipeline needed better provenance and easier diagnosis.
|
||||
|
||||
The specific targets for this subphase were:
|
||||
|
||||
- record which host-side FreeBSD base inputs were used for a generated closure/image
|
||||
- make the closure itself carry that provenance
|
||||
- distinguish host-staged FreeBSD base stores from Fruix-built runtime stores
|
||||
- improve the validation harnesses so later failures are easier to investigate
|
||||
|
||||
## Implementation
|
||||
|
||||
### 1. Closure-level provenance files
|
||||
|
||||
`modules/fruix/system/freebsd.scm` now generates and stores two explicit metadata files in the system closure:
|
||||
|
||||
- `metadata/host-base-provenance.scm`
|
||||
- `metadata/store-layout.scm`
|
||||
|
||||
These record:
|
||||
|
||||
- host `freebsd-version -kru`
|
||||
- host `uname -a`
|
||||
- `/usr/src` path
|
||||
- `/usr/src` git revision/branch when available
|
||||
- `newvers.sh` SHA256 as a stable source-tree identifier fallback
|
||||
- exact host-staged FreeBSD base store paths used by the closure
|
||||
- exact Fruix runtime store paths used by the closure
|
||||
- selected init mode
|
||||
|
||||
This makes the generated closure self-describing instead of requiring ad hoc host inspection.
|
||||
|
||||
### 2. `fruix system` metadata became more explicit
|
||||
|
||||
`scripts/fruix.scm` now emits additional machine-readable metadata for both:
|
||||
|
||||
- `fruix system build`
|
||||
- `fruix system image`
|
||||
|
||||
New fields include:
|
||||
|
||||
- `host_base_store_count`
|
||||
- `host_base_stores`
|
||||
- `fruix_runtime_store_count`
|
||||
- `fruix_runtime_stores`
|
||||
- `host_base_provenance_file`
|
||||
- `store_layout_file`
|
||||
- `host_freebsd_version`
|
||||
- `host_uname`
|
||||
- `usr_src_git_revision`
|
||||
- `usr_src_git_branch`
|
||||
- `usr_src_newvers_sha256`
|
||||
|
||||
This gives later harnesses and operators a clean way to identify exactly what kind of system/image was produced.
|
||||
|
||||
### 3. Validation harnesses now assert provenance availability
|
||||
|
||||
Updated harnesses:
|
||||
|
||||
- `tests/system/run-phase7-system-closure.sh`
|
||||
- `tests/system/run-phase8-system-image.sh`
|
||||
|
||||
They now validate that provenance fields are present and that the generated closure contains the new metadata files.
|
||||
|
||||
### 4. Runtime-diagnostic capture was expanded in the VM harnesses
|
||||
|
||||
Updated harnesses:
|
||||
|
||||
- `tests/system/run-phase9-xcpng-boot.sh`
|
||||
- `tests/system/run-phase11-shepherd-pid1-xcpng.sh`
|
||||
- `tests/system/run-phase11-shepherd-pid1-qemu.sh`
|
||||
|
||||
These now collect additional guest-side diagnostic tails where available, including:
|
||||
|
||||
- `shepherd-bootstrap.out`
|
||||
- `shepherd.log`
|
||||
- recent `dmesg`
|
||||
|
||||
This does not change the boot architecture, but it improves post-failure visibility.
|
||||
|
||||
### 5. Host-side validation became more stable
|
||||
|
||||
The host-side closure/image harnesses now force:
|
||||
|
||||
- `GUILE_AUTO_COMPILE=0`
|
||||
|
||||
when invoking `fruix` under `sudo env`.
|
||||
|
||||
This avoided a misleading one-time validation drift caused by mixed compiled/source host state during back-to-back reproducibility checks immediately after source edits.
|
||||
|
||||
## Validation
|
||||
|
||||
### Phase 7 system closure
|
||||
|
||||
Passing run:
|
||||
|
||||
- `PASS phase7-system-closure`
|
||||
- workdir: `/tmp/phase12-1b-closure-1775157039`
|
||||
|
||||
Key new metadata included:
|
||||
|
||||
```text
|
||||
host_base_store_count=8
|
||||
fruix_runtime_store_count=3
|
||||
host_base_provenance_file=/frx/store/.../metadata/host-base-provenance.scm
|
||||
store_layout_file=/frx/store/.../metadata/store-layout.scm
|
||||
host_freebsd_version=15.0-STABLE
|
||||
usr_src_newvers_sha256=d6f6e9ab352d3f6281e788c78a63ac311ab7a3a4bb5dfc0016ed0aadb90b5d9d
|
||||
```
|
||||
|
||||
### Phase 8 system image
|
||||
|
||||
Passing run:
|
||||
|
||||
- `PASS phase8-system-image`
|
||||
- workdir: `/tmp/phase12-1b-image-1775157039`
|
||||
|
||||
Key new metadata included:
|
||||
|
||||
```text
|
||||
host_base_store_count=8
|
||||
fruix_runtime_store_count=3
|
||||
host_base_provenance_file=/frx/store/.../metadata/host-base-provenance.scm
|
||||
store_layout_file=/frx/store/.../metadata/store-layout.scm
|
||||
host_freebsd_version=15.0-STABLE
|
||||
host_uname=FreeBSD fruixdev 15.0-STABLE ...
|
||||
```
|
||||
|
||||
### Harness validation
|
||||
|
||||
Syntax-checked successfully:
|
||||
|
||||
- `tests/system/run-phase9-xcpng-boot.sh`
|
||||
- `tests/system/run-phase11-shepherd-pid1-xcpng.sh`
|
||||
- `tests/system/run-phase11-shepherd-pid1-qemu.sh`
|
||||
|
||||
## Assessment
|
||||
|
||||
This subphase does not replace the host-staged FreeBSD base model yet. Instead, it makes that transitional model much easier to reason about.
|
||||
|
||||
Generated systems/images now answer the key questions directly:
|
||||
|
||||
- which FreeBSD base inputs came from the host?
|
||||
- which runtime artifacts came from Fruix-built Guile/Shepherd outputs?
|
||||
- what `/usr/src` identity was available at build time?
|
||||
- where should an operator look first when the VM boot/runtime path misbehaves?
|
||||
|
||||
That is sufficient to support the next hardening step and then the later move toward native FreeBSD world/kernel artifacts in `/frx/store`.
|
||||
|
||||
## Next recommended step
|
||||
|
||||
Proceed to Phase 12.2:
|
||||
|
||||
- improve runtime/operator diagnostics inside the guest itself
|
||||
- reduce the remaining noisy boot rough edges where the fixes are small and local
|
||||
- keep the validated deployment path stable while preparing for native base-build work
|
||||
210
docs/reports/phase12-runtime-diagnostics-freebsd.md
Normal file
210
docs/reports/phase12-runtime-diagnostics-freebsd.md
Normal file
@@ -0,0 +1,210 @@
|
||||
# Phase 12.2: tightened guest runtime diagnostics and reduced early-boot rough edges
|
||||
|
||||
Date: 2026-04-02
|
||||
|
||||
## Goal
|
||||
|
||||
The current Fruix FreeBSD guest already booted, reached the network, and ran Shepherd. The next hardening step was to make the guest easier to diagnose and to remove a few distracting runtime rough edges that were still coming from the prototype-style `/etc` handling.
|
||||
|
||||
The main targets were:
|
||||
|
||||
- reduce early-boot/login-class noise around `/etc/login.conf`
|
||||
- ensure the FreeBSD password/login databases are created in writable guest state, not implicitly in store-backed paths
|
||||
- add a clear activation log inside the guest
|
||||
- teach the validation harnesses to assert the new behavior directly
|
||||
|
||||
## Root cause addressed
|
||||
|
||||
The old rootfs layout symlinked several generated `/etc` files directly to `/run/current-system/etc/...`, including:
|
||||
|
||||
- `/etc/login.conf`
|
||||
- `/etc/master.passwd`
|
||||
- `/etc/passwd`
|
||||
- `/etc/group`
|
||||
|
||||
That worked for static lookup, but it was a poor fit for FreeBSD tools such as:
|
||||
|
||||
- `cap_mkdb`
|
||||
- `pwd_mkdb`
|
||||
- early login-class lookups
|
||||
|
||||
because those tools expect to work with regular files and adjacent generated database files such as:
|
||||
|
||||
- `/etc/login.conf.db`
|
||||
- `/etc/pwd.db`
|
||||
- `/etc/spwd.db`
|
||||
|
||||
With symlink-backed inputs, the system could still limp along, but it produced avoidable warnings and less clear runtime behavior.
|
||||
|
||||
## Implementation
|
||||
|
||||
### 1. Selected `/etc` files now become regular files in the guest rootfs
|
||||
|
||||
`modules/fruix/system/freebsd.scm` now materializes these files as regular files in the rootfs instead of symlinks:
|
||||
|
||||
- `/etc/passwd`
|
||||
- `/etc/master.passwd`
|
||||
- `/etc/group`
|
||||
- `/etc/login.conf`
|
||||
|
||||
Other generated configuration files that do not need this treatment remain symlinked to `/run/current-system/...`.
|
||||
|
||||
This gives FreeBSD's database tools writable, regular inputs in the guest filesystem while keeping the current system closure as the source of truth.
|
||||
|
||||
### 2. Activation now refreshes those files from `/run/current-system/etc`
|
||||
|
||||
The generated activation script now refreshes the regular guest copies from the currently selected system closure before rebuilding databases.
|
||||
|
||||
That means rebuild/redeploy still works coherently even though these specific files are no longer left as symlinks in the booted guest.
|
||||
|
||||
### 3. Activation now records a guest-visible log
|
||||
|
||||
Activation now writes:
|
||||
|
||||
- `/var/log/fruix-activate.log`
|
||||
|
||||
with explicit markers such as:
|
||||
|
||||
- `fruix-activate:start`
|
||||
- `fruix-activate:cap_mkdb=ok`
|
||||
- `fruix-activate:pwd_mkdb=ok`
|
||||
- `fruix-activate:done`
|
||||
- exit status marker from the shell trap
|
||||
|
||||
This gives a direct guest-side indicator of whether activation actually completed.
|
||||
|
||||
### 4. Closure permissions were tightened slightly
|
||||
|
||||
The generated closure now explicitly sets:
|
||||
|
||||
- `etc/master.passwd` => `0600`
|
||||
|
||||
before the rootfs/image path copies it into the guest.
|
||||
|
||||
### 5. Validation harnesses were upgraded
|
||||
|
||||
#### Local image validation
|
||||
|
||||
`tests/system/run-phase8-system-image.sh` now asserts that the mounted image contains:
|
||||
|
||||
- `/etc/login.conf` as a regular file
|
||||
- `/etc/master.passwd` as a regular file
|
||||
|
||||
#### VM validation
|
||||
|
||||
These harnesses now check for the new runtime behavior:
|
||||
|
||||
- `tests/system/run-phase9-xcpng-boot.sh`
|
||||
- `tests/system/run-phase11-shepherd-pid1-xcpng.sh`
|
||||
- `tests/system/run-phase11-shepherd-pid1-qemu.sh`
|
||||
|
||||
New checks include:
|
||||
|
||||
- `login_conf_kind=regular`
|
||||
- `login_conf_db=present`
|
||||
- `pwd_dbs=present`
|
||||
- activation log contains `fruix-activate:done`
|
||||
|
||||
They also capture the activation log into metadata.
|
||||
|
||||
### 6. Small follow-up fix
|
||||
|
||||
The first activation-log implementation briefly used `touch`, which is not staged in the minimal guest userland. This was corrected by switching to shell redirection:
|
||||
|
||||
- `: >> "$logfile"`
|
||||
|
||||
so the activation path no longer depends on an extra utility for log-file creation.
|
||||
|
||||
## Validation
|
||||
|
||||
### Local image structure
|
||||
|
||||
Passing run:
|
||||
|
||||
- `PASS phase8-system-image`
|
||||
- workdir: `/tmp/phase12-2-image-1775159011`
|
||||
|
||||
Key metadata:
|
||||
|
||||
```text
|
||||
login_conf_kind=regular
|
||||
master_passwd_kind=regular
|
||||
```
|
||||
|
||||
### Local QEMU Shepherd PID 1
|
||||
|
||||
Passing run:
|
||||
|
||||
- `PASS phase11-shepherd-pid1-qemu`
|
||||
- workdir: `/tmp/phase12-2b-qemu-1775161367`
|
||||
|
||||
Key metadata:
|
||||
|
||||
```text
|
||||
activate_log=fruix-activate:start fruix-activate:cap_mkdb=ok fruix-activate:pwd_mkdb=ok fruix-activate:done fruix-activate:exit status=0
|
||||
login_conf_kind=regular
|
||||
login_conf_db=present
|
||||
pwd_dbs=present
|
||||
shepherd_pid=1
|
||||
sshd_status=running
|
||||
```
|
||||
|
||||
### Real VM, `freebsd-init+rc.d-shepherd`
|
||||
|
||||
Passing run:
|
||||
|
||||
- `PASS phase9-xcpng-boot`
|
||||
- workdir: `/tmp/phase12-2b-phase9-1775161731`
|
||||
|
||||
Key metadata:
|
||||
|
||||
```text
|
||||
activate_log=fruix-activate:start fruix-activate:cap_mkdb=ok fruix-activate:pwd_mkdb=ok fruix-activate:done fruix-activate:exit status=0
|
||||
login_conf_kind=regular
|
||||
login_conf_db=present
|
||||
pwd_dbs=present
|
||||
compat_prefix_shims=absent
|
||||
guile_module_smoke=ok
|
||||
shepherd_status=running
|
||||
sshd_status=running
|
||||
```
|
||||
|
||||
### Real VM, `shepherd-pid1`
|
||||
|
||||
Passing run:
|
||||
|
||||
- `PASS phase11-shepherd-pid1-xcpng`
|
||||
- workdir: `/tmp/phase12-2b-phase11-1775162210`
|
||||
|
||||
Key metadata:
|
||||
|
||||
```text
|
||||
activate_log=fruix-activate:start fruix-activate:cap_mkdb=ok fruix-activate:pwd_mkdb=ok fruix-activate:done fruix-activate:exit status=0
|
||||
login_conf_kind=regular
|
||||
login_conf_db=present
|
||||
pwd_dbs=present
|
||||
shepherd_pid=1
|
||||
compat_prefix_shims=absent
|
||||
guile_module_smoke=ok
|
||||
sshd_status=running
|
||||
```
|
||||
|
||||
## Assessment
|
||||
|
||||
This was a small but high-value hardening step.
|
||||
|
||||
The guest now behaves more like a real FreeBSD system in one of the places where a store-backed prototype can otherwise feel awkward: password/login database management and early `/etc` expectations.
|
||||
|
||||
The important result is not cosmetic; it is operational:
|
||||
|
||||
- activation success is now visible inside the guest
|
||||
- the login/password database inputs live as regular guest files where FreeBSD expects them
|
||||
- both validated boot modes still work locally and on the real XCP-ng VM
|
||||
|
||||
## Next recommended step
|
||||
|
||||
Proceed to Phase 12.3:
|
||||
|
||||
- make the host-staged FreeBSD base boundary explicit in the code/documentation model
|
||||
- group the transitional host-copy base packages more clearly
|
||||
- document the first intended replacement order for native world/kernel artifacts in `/frx/store`
|
||||
191
docs/reports/phase13-native-base-boot-freebsd.md
Normal file
191
docs/reports/phase13-native-base-boot-freebsd.md
Normal file
@@ -0,0 +1,191 @@
|
||||
# Phase 13.3: booted Fruix using native store-built FreeBSD base artifacts
|
||||
|
||||
Date: 2026-04-03
|
||||
|
||||
## Goal
|
||||
|
||||
Phase 13.3 was the first end-to-end boot validation of the new native FreeBSD base path.
|
||||
|
||||
The target was a Fruix system/image that boots using:
|
||||
|
||||
- native `/usr/src`-built kernel output in `/frx/store`
|
||||
- native `/usr/src`-built world output in `/frx/store`
|
||||
- host-staged bootloader only, as the remaining explicit transitional boundary
|
||||
|
||||
Validation still followed the established strategy:
|
||||
|
||||
- local QEMU/UEFI/TCG
|
||||
- real XCP-ng on the approved VM/VDI path
|
||||
|
||||
## Issues found and fixed
|
||||
|
||||
### 1. The old fixed 256 MiB root filesystem image was too small
|
||||
|
||||
The first native-base image attempt failed during `makefs` with:
|
||||
|
||||
```text
|
||||
size of .../image-rootfs is larger than the maxsize of 268435456
|
||||
```
|
||||
|
||||
That was expected once the image started carrying a native world instead of the earlier curated host-copy runtime slice.
|
||||
|
||||
To fix this cleanly, Fruix image generation gained an explicit root filesystem size parameter:
|
||||
|
||||
- `scripts/fruix.scm` now accepts:
|
||||
- `--root-size SIZE`
|
||||
- image metadata now emits:
|
||||
- `root_size=...`
|
||||
- `tests/system/run-phase8-system-image.sh` now accepts:
|
||||
- `ROOT_SIZE`
|
||||
and records the reported root size in metadata
|
||||
|
||||
For the native-base boot validation, the working values were:
|
||||
|
||||
- local QEMU:
|
||||
- `ROOT_SIZE=6g`
|
||||
- `DISK_CAPACITY=8g`
|
||||
- real XCP-ng:
|
||||
- `ROOT_SIZE=6g`
|
||||
- disk capacity kept matched to the fixed 30 GiB VDI as before
|
||||
|
||||
### 2. `materialize-bhyve-image` needed to propagate native-base metadata
|
||||
|
||||
After adding the new `native_base_*` metadata path, the first native image run hit a Scheme error because `materialize-bhyve-image` still returned:
|
||||
|
||||
- `host-base-stores`
|
||||
- `fruix-runtime-stores`
|
||||
|
||||
but not:
|
||||
|
||||
- `native-base-stores`
|
||||
|
||||
That was corrected so image-generation results now carry the native-base store set too.
|
||||
|
||||
## Harness updates
|
||||
|
||||
### Reused/extended existing Phase 11 boot harnesses
|
||||
|
||||
- `tests/system/run-phase11-shepherd-pid1-qemu.sh`
|
||||
- now accepts `OS_TEMPLATE` like the XCP-ng PID1 harness already did
|
||||
- `tests/system/run-phase8-system-image.sh`
|
||||
- now records:
|
||||
- `native_base_store_count`
|
||||
- `native_base_stores`
|
||||
- `root_size`
|
||||
|
||||
### Added native-base boot wrappers
|
||||
|
||||
New wrappers:
|
||||
|
||||
- `tests/system/run-phase13-native-base-qemu.sh`
|
||||
- `tests/system/run-phase13-native-base-xcpng.sh`
|
||||
|
||||
These wrappers reuse the already-validated PID1 boot path but require the image metadata to confirm that the booted system really uses the intended Phase-13 base split:
|
||||
|
||||
- `native_base_store_count=2`
|
||||
- native kernel present
|
||||
- native world present
|
||||
- `host_base_store_count=1`
|
||||
- host base reduced to bootloader only
|
||||
|
||||
## Validation
|
||||
|
||||
### Local QEMU / UEFI / TCG
|
||||
|
||||
Passing run:
|
||||
|
||||
- `PASS phase13-native-base-qemu`
|
||||
- workdir: `/tmp/phase13-3-qemu3-1775174863`
|
||||
|
||||
Key metadata:
|
||||
|
||||
```text
|
||||
disk_capacity=8g
|
||||
root_size=6g
|
||||
native_base_store_count=2
|
||||
host_base_store_count=1
|
||||
shepherd_pid=1
|
||||
sshd_status=running
|
||||
native_base_boot=ok
|
||||
```
|
||||
|
||||
The wrapped underlying Phase 11 PID1 harness also confirmed the full guest runtime path:
|
||||
|
||||
- ready marker present
|
||||
- `/run/current-system` correct
|
||||
- Shepherd socket present
|
||||
- Shepherd PID is `1`
|
||||
- `sshd` running
|
||||
- activation completed successfully
|
||||
- `/etc/login.conf` regular + databases present
|
||||
|
||||
### Real XCP-ng VM
|
||||
|
||||
Passing run:
|
||||
|
||||
- `PASS phase13-native-base-xcpng`
|
||||
- workdir: `/tmp/phase13-3-xcpng-1775175086`
|
||||
|
||||
Key metadata:
|
||||
|
||||
```text
|
||||
vm_id=90490f2e-e8fc-4b7a-388e-5c26f0157289
|
||||
vdi_id=0f1f90d3-48ca-4fa2-91d8-fc6339b95743
|
||||
guest_ip=192.168.213.62
|
||||
root_size=6g
|
||||
native_base_store_count=2
|
||||
host_base_store_count=1
|
||||
shepherd_pid=1
|
||||
sshd_status=running
|
||||
compat_prefix_shims=absent
|
||||
guile_module_smoke=ok
|
||||
native_base_boot=ok
|
||||
```
|
||||
|
||||
The dynamic VHD upload remained workable even with the larger native-world image payload, though naturally much larger than the earlier minimal image path:
|
||||
|
||||
- upload size: `4740784128` bytes (~4.42 GiB)
|
||||
|
||||
## What booted
|
||||
|
||||
The validated native-base system closure used:
|
||||
|
||||
- native kernel store:
|
||||
- `/frx/store/93f35ddcb9a03f63f83c9e8ae29788685d339789da664f881822b4a1914f5ff6-freebsd-native-kernel-15.0-STABLE`
|
||||
- native world store:
|
||||
- `/frx/store/3f6f7f8c06ed8dad4cae21a1e8ac8ba4823bdb7cf54328c9bbcccaeb858beb77-freebsd-native-world-15.0-STABLE`
|
||||
- remaining host base store:
|
||||
- `/frx/store/8ffcfe0356fea815726b610514a1280a11266851c2acb870047d559795569f0e-freebsd-bootloader-15.0-STABLE`
|
||||
|
||||
That means Phase 13 has now crossed its main success boundary:
|
||||
|
||||
- the kernel is native
|
||||
- the core world runtime is native
|
||||
- the system actually boots from those outputs
|
||||
|
||||
## Assessment
|
||||
|
||||
Phase 13 is complete.
|
||||
|
||||
Fruix has now moved from:
|
||||
|
||||
- building a FreeBSD system from curated host copies
|
||||
|
||||
into:
|
||||
|
||||
- building kernel/world artifacts from `/usr/src` into `/frx/store`, then booting a declarative Fruix system from those outputs.
|
||||
|
||||
The remaining major transitional boundary is now much narrower and explicit:
|
||||
|
||||
- host-staged bootloader/boot assets remain
|
||||
- native kernel + native core world runtime are already in place
|
||||
|
||||
That is a real architectural shift, not just more documentation.
|
||||
|
||||
## Next recommended step
|
||||
|
||||
Proceed to Phase 14:
|
||||
|
||||
1. replace the remaining host-copy boot assets first
|
||||
2. then keep reducing the older host-staged FreeBSD base slice around the now-working native world/kernel path
|
||||
3. revisit cleaner runtime vs. development splits once the boot asset transition is done
|
||||
144
docs/reports/phase13-native-base-model-freebsd.md
Normal file
144
docs/reports/phase13-native-base-model-freebsd.md
Normal file
@@ -0,0 +1,144 @@
|
||||
# Phase 13.1: model FreeBSD world and kernel as native Fruix build artifacts
|
||||
|
||||
Date: 2026-04-02
|
||||
|
||||
## Goal
|
||||
|
||||
Phase 13 begins the Option B pivot from host-copy FreeBSD base packages toward builder-produced FreeBSD base artifacts in `/frx/store`.
|
||||
|
||||
This first subphase focused on the modeling and package/materialization layer:
|
||||
|
||||
- define native Fruix package objects for FreeBSD kernel and world outputs
|
||||
- make their output identity depend on `/usr/src` and explicit build parameters
|
||||
- teach the system metadata to distinguish host-staged base stores from native base stores
|
||||
|
||||
The purpose of this step was not yet to prove a full boot from those new outputs. It was to land the description and materialization model that Phase 13.2 can exercise concretely.
|
||||
|
||||
## Implementation
|
||||
|
||||
### 1. Added native package definitions in `modules/fruix/packages/freebsd.scm`
|
||||
|
||||
New package objects:
|
||||
|
||||
- `freebsd-native-kernel`
|
||||
- `freebsd-native-world`
|
||||
|
||||
New predicate:
|
||||
|
||||
- `freebsd-native-build-package?`
|
||||
|
||||
The native packages are intentionally FreeBSD-specific and currently parameterized with:
|
||||
|
||||
- `source-root` => `/usr/src`
|
||||
- `target` => `amd64`
|
||||
- `target-arch` => `amd64`
|
||||
- `kernconf` => `GENERIC`
|
||||
- `make-flags` =>
|
||||
- `__MAKE_CONF=/dev/null`
|
||||
- `SRCCONF=/dev/null`
|
||||
- `SRC_ENV_CONF=/dev/null`
|
||||
- `MK_DEBUG_FILES=no`
|
||||
- `MK_TESTS=no`
|
||||
|
||||
The first native world artifact also carries an explicit runtime-oriented prune list:
|
||||
|
||||
- `usr/share/doc`
|
||||
- `usr/share/examples`
|
||||
- `usr/share/info`
|
||||
- `usr/share/man`
|
||||
- `usr/tests`
|
||||
|
||||
That keeps the first native world target narrower and aligned with the current boot/runtime goal.
|
||||
|
||||
### 2. Added native build-system support in `modules/fruix/system/freebsd.scm`
|
||||
|
||||
`materialize-freebsd-package` now understands three classes of package build system:
|
||||
|
||||
- `copy-build-system`
|
||||
- `freebsd-world-build-system`
|
||||
- `freebsd-kernel-build-system`
|
||||
|
||||
For the native FreeBSD path, the materializer now records identity from:
|
||||
|
||||
- `/usr/src`
|
||||
- explicit build parameters
|
||||
- the selected kernel configuration
|
||||
|
||||
The `/usr/src` source identity is currently represented by an `mtree`-based tree digest using:
|
||||
|
||||
- `type`
|
||||
- `link`
|
||||
- `size`
|
||||
- `mode`
|
||||
- `sha256digest`
|
||||
|
||||
The kernel configuration is also hashed explicitly via its resolved `KERNCONF` path.
|
||||
|
||||
The code now has dedicated helpers for:
|
||||
|
||||
- native source identity
|
||||
- native build-root identity
|
||||
- buildworld/buildkernel stamp handling
|
||||
- staged installworld/installkernel materialization
|
||||
- writing native build metadata beside the resulting store outputs
|
||||
|
||||
### 3. Store-layout metadata now separates native and host-staged base stores
|
||||
|
||||
The closure metadata and CLI metadata now distinguish:
|
||||
|
||||
- `host_base_stores`
|
||||
- `native_base_stores`
|
||||
- `fruix_runtime_stores`
|
||||
|
||||
This is important for the architecture transition: a system can now say explicitly which parts of its FreeBSD base still come from transitional host-copy packages and which parts come from native `/usr/src`-driven artifacts.
|
||||
|
||||
### 4. Merged output trees now skip private dotfile metadata
|
||||
|
||||
The profile/tree merge logic now ignores private dotfile metadata in store outputs. This keeps internal build metadata files from leaking into the merged runtime tree when native base outputs start being consumed by real system closures.
|
||||
|
||||
## Validation
|
||||
|
||||
### Package/model load
|
||||
|
||||
Verified that the package/system modules still load after the native build-model additions, and that the new native package objects are present.
|
||||
|
||||
Observed output included:
|
||||
|
||||
```text
|
||||
freebsd-kernel-build-system
|
||||
#t
|
||||
```
|
||||
|
||||
for:
|
||||
|
||||
- `freebsd-native-kernel` build-system lookup
|
||||
- `freebsd-native-build-package? freebsd-native-world`
|
||||
|
||||
### Existing closure path regression check
|
||||
|
||||
To make sure the modeling work did not break the already validated host-copy path, re-ran:
|
||||
|
||||
- `tests/system/run-phase7-system-closure.sh`
|
||||
|
||||
Passing run:
|
||||
|
||||
- `PASS phase7-system-closure`
|
||||
- workdir: `/tmp/phase13-1-closure-1775164392`
|
||||
|
||||
This confirmed that the existing FreeBSD system closure path still works while the new native base model is being introduced.
|
||||
|
||||
## Assessment
|
||||
|
||||
Phase 13.1 is complete.
|
||||
|
||||
Fruix now has a real representation for native FreeBSD base artifacts in its package/materialization model rather than only the older host-copy package set. Just as importantly, the output identity story is no longer purely implicit: `/usr/src`, `KERNCONF`, and the selected build parameters are now part of the native artifact definition.
|
||||
|
||||
This is the necessary bridge into Phase 13.2, where those new package definitions need to be exercised to produce concrete kernel/world outputs in `/frx/store`.
|
||||
|
||||
## Next recommended step
|
||||
|
||||
Proceed to Phase 13.2:
|
||||
|
||||
- build the first concrete `freebsd-native-kernel` and `freebsd-native-world` outputs from `/usr/src`
|
||||
- inspect their staged contents in `/frx/store`
|
||||
- document the exact split that the first native runtime target provides
|
||||
176
docs/reports/phase13-native-world-kernel-build-freebsd.md
Normal file
176
docs/reports/phase13-native-world-kernel-build-freebsd.md
Normal file
@@ -0,0 +1,176 @@
|
||||
# Phase 13.2: built the first native FreeBSD world and kernel artifacts from `/usr/src`
|
||||
|
||||
Date: 2026-04-03
|
||||
|
||||
## Goal
|
||||
|
||||
After Phase 13.1 introduced native Fruix package/materialization support for FreeBSD base artifacts, the next step was to exercise that path for real:
|
||||
|
||||
- build a kernel from `/usr/src`
|
||||
- build and stage a minimal runtime-oriented world from `/usr/src`
|
||||
- place both outputs under `/frx/store`
|
||||
- document the exact split of this first native base target
|
||||
|
||||
## Implementation
|
||||
|
||||
### 1. Fixed two important builder issues found during the first real run
|
||||
|
||||
#### `MAKEOBJDIRPREFIX` had to move to the environment
|
||||
|
||||
The first real `buildworld` attempt failed with:
|
||||
|
||||
```text
|
||||
MAKEOBJDIRPREFIX can only be set in environment or src-env.conf(5)
|
||||
```
|
||||
|
||||
The native build path originally passed `MAKEOBJDIRPREFIX=...` as a make command-line variable. That was corrected so the generated build command now uses:
|
||||
|
||||
- `env MAKEOBJDIRPREFIX=... make ...`
|
||||
|
||||
instead.
|
||||
|
||||
#### The initial `/usr/src` tree hash was accidentally unstable
|
||||
|
||||
The first source-tree hashing attempt hashed the raw `mtree -c` output. That was subtly wrong because `mtree` includes header comments such as:
|
||||
|
||||
- date
|
||||
- user
|
||||
- machine
|
||||
- tree path banner
|
||||
|
||||
As a result, successive hash computations differed even when `/usr/src` had not changed.
|
||||
|
||||
This was corrected by stripping the leading `# ...` comment lines before hashing the `mtree` content. After that fix:
|
||||
|
||||
- native world and native kernel reported the same `source-tree-sha256`
|
||||
- both reused the same native build root
|
||||
- output identity stopped drifting on each run
|
||||
|
||||
## First concrete native outputs
|
||||
|
||||
A native Fruix system definition using:
|
||||
|
||||
- `freebsd-native-kernel`
|
||||
- `freebsd-native-world`
|
||||
- host-staged `freebsd-bootloader`
|
||||
- `shepherd-pid1`
|
||||
|
||||
was built successfully.
|
||||
|
||||
Resulting store outputs:
|
||||
|
||||
- kernel:
|
||||
- `/frx/store/93f35ddcb9a03f63f83c9e8ae29788685d339789da664f881822b4a1914f5ff6-freebsd-native-kernel-15.0-STABLE`
|
||||
- world:
|
||||
- `/frx/store/3f6f7f8c06ed8dad4cae21a1e8ac8ba4823bdb7cf54328c9bbcccaeb858beb77-freebsd-native-world-15.0-STABLE`
|
||||
|
||||
Native build metadata records for both outputs show:
|
||||
|
||||
- `source-root=/usr/src`
|
||||
- the same `source-tree-sha256`
|
||||
- `kernconf=GENERIC`
|
||||
- the same shared `build-root`
|
||||
|
||||
Shared build root:
|
||||
|
||||
- `/var/tmp/fruix-freebsd-native-build-c59b1b8128b305d9bad9cf3d654771c941c4e8b6a2732f6bc959df96d1d32f58`
|
||||
|
||||
Key log files recorded in the output metadata:
|
||||
|
||||
- `buildworld.log`
|
||||
- `buildkernel-GENERIC.log`
|
||||
- `install-freebsd-native-world.log`
|
||||
- `install-freebsd-native-kernel.log`
|
||||
|
||||
## First native world split
|
||||
|
||||
The first `freebsd-native-world` output is intentionally runtime-oriented. Validation confirmed that it contains the core pieces needed by the existing Fruix guest model, including:
|
||||
|
||||
- `/bin/sh`
|
||||
- `/sbin/init`
|
||||
- `/etc/rc`
|
||||
- `/usr/sbin/sshd`
|
||||
- `/sbin/dhclient`
|
||||
- `/usr/bin/cap_mkdb`
|
||||
- `/usr/sbin/pwd_mkdb`
|
||||
- `/usr/share/locale/C.UTF-8/LC_CTYPE`
|
||||
|
||||
The first prune list is also active. Validation confirmed the world output does **not** contain:
|
||||
|
||||
- `/usr/share/man`
|
||||
- `/usr/tests`
|
||||
|
||||
This keeps the first native world output closer to the current boot/runtime target instead of a fully unpruned base install tree.
|
||||
|
||||
## Store-boundary result
|
||||
|
||||
The generated closure metadata now shows the intended transitional boundary clearly:
|
||||
|
||||
- `host_base_store_count=1`
|
||||
- `host_base_stores=` bootloader only
|
||||
- `native_base_store_count=2`
|
||||
- `native_base_stores=` native kernel + native world
|
||||
|
||||
That means Fruix now has a mixed but explicit Phase-13 system boundary:
|
||||
|
||||
- bootloader still comes from the old host-staged path
|
||||
- kernel and core runtime world now come from native `/usr/src`-built store outputs
|
||||
|
||||
## Validation
|
||||
|
||||
### Direct build through `fruix system build`
|
||||
|
||||
A real native-base system build succeeded and produced:
|
||||
|
||||
- native kernel store output
|
||||
- native world store output
|
||||
- a system closure referencing those outputs
|
||||
|
||||
### Dedicated Phase 13.2 harness
|
||||
|
||||
Added:
|
||||
|
||||
- `tests/system/phase13-native-base-pid1-operating-system.scm.in`
|
||||
- `tests/system/run-phase13-native-base-build.sh`
|
||||
|
||||
Passing run:
|
||||
|
||||
- `PASS phase13-native-base-build`
|
||||
- workdir: `/tmp/phase13-2-build-1775173551`
|
||||
|
||||
Key metadata from that run:
|
||||
|
||||
```text
|
||||
kernel_store=/frx/store/93f35ddcb9a03f63f83c9e8ae29788685d339789da664f881822b4a1914f5ff6-freebsd-native-kernel-15.0-STABLE
|
||||
world_store=/frx/store/3f6f7f8c06ed8dad4cae21a1e8ac8ba4823bdb7cf54328c9bbcccaeb858beb77-freebsd-native-world-15.0-STABLE
|
||||
host_base_store_count=1
|
||||
native_base_store_count=2
|
||||
world_source_tree_sha256=72a451e2ea47c4c4777035f797dc35f8d905eaabb2a717bc1fd71019f3021f72
|
||||
kernel_source_tree_sha256=72a451e2ea47c4c4777035f797dc35f8d905eaabb2a717bc1fd71019f3021f72
|
||||
world_build_root=/var/tmp/fruix-freebsd-native-build-c59b1b8128b305d9bad9cf3d654771c941c4e8b6a2732f6bc959df96d1d32f58
|
||||
kernel_build_root=/var/tmp/fruix-freebsd-native-build-c59b1b8128b305d9bad9cf3d654771c941c4e8b6a2732f6bc959df96d1d32f58
|
||||
```
|
||||
|
||||
The harness also verified:
|
||||
|
||||
- closure rebuild path reproducibility
|
||||
- existence of required kernel/world runtime files
|
||||
- absence of the pruned world paths
|
||||
- presence of native build metadata and logs
|
||||
- presence of `native-base-stores` in closure store-layout metadata
|
||||
|
||||
## Assessment
|
||||
|
||||
Phase 13.2 is complete.
|
||||
|
||||
Fruix now does more than describe native FreeBSD base artifacts: it actually builds them from `/usr/src`, stores them under `/frx/store`, and records enough metadata to inspect how they were produced.
|
||||
|
||||
This is the first point where Fruix can honestly say that part of the FreeBSD base is no longer just a curated copy of the builder host.
|
||||
|
||||
## Next recommended step
|
||||
|
||||
Proceed to Phase 13.3:
|
||||
|
||||
- wire the operating-system/image path to boot using these native kernel/world outputs
|
||||
- validate locally with QEMU/UEFI
|
||||
- validate on the approved XCP-ng VM/VDI path
|
||||
126
docs/reports/phase14-native-boot-assets-freebsd.md
Normal file
126
docs/reports/phase14-native-boot-assets-freebsd.md
Normal file
@@ -0,0 +1,126 @@
|
||||
# Phase 14.1: validated native FreeBSD boot assets without host-copied `/boot`
|
||||
|
||||
Date: 2026-04-03
|
||||
|
||||
## Goal
|
||||
|
||||
The goal of Phase 14.1 was to stop relying on the host-staged `freebsd-bootloader` package for the validated boot path.
|
||||
|
||||
Phase 13 had already proved that Fruix could boot with:
|
||||
|
||||
- native kernel
|
||||
- native world
|
||||
- host-staged bootloader only
|
||||
|
||||
Phase 14.1 moved the remaining boot assets onto the native side as well.
|
||||
|
||||
## Approach
|
||||
|
||||
Before introducing a cleaner package split, Fruix first reused the already-built native world output as the source of boot assets.
|
||||
|
||||
The Phase 14.1 operating-system template now uses:
|
||||
|
||||
- `#:kernel freebsd-native-kernel`
|
||||
- `#:bootloader freebsd-native-world`
|
||||
- `#:base-packages (list freebsd-native-world)`
|
||||
|
||||
That means the validated image now gets both:
|
||||
|
||||
- runtime files
|
||||
- loader/boot assets
|
||||
|
||||
from the native `/usr/src`-built world output already staged in `/frx/store`.
|
||||
|
||||
This is slightly redundant at the model layer, but it is a clean way to prove the architectural point first:
|
||||
|
||||
- the boot path no longer needs host-copied `/boot/...` material
|
||||
|
||||
## Additional QEMU harness fix
|
||||
|
||||
During repeated local validation, the existing QEMU PID1 harness was found to boot the raw store image directly from `/frx/store`.
|
||||
|
||||
That meant each local boot mutated the supposedly immutable image artifact and could leave the filesystem dirty for later runs.
|
||||
|
||||
To avoid that, `tests/system/run-phase11-shepherd-pid1-qemu.sh` now copies the built raw image to a temporary writable workdir file before launching QEMU:
|
||||
|
||||
- source image remains in `/frx/store`
|
||||
- QEMU now writes to `boot-disk.img` in the workdir
|
||||
|
||||
This keeps the store image stable across repeated local boots.
|
||||
|
||||
## New files
|
||||
|
||||
Added:
|
||||
|
||||
- `tests/system/phase14-native-boot-pid1-operating-system.scm.in`
|
||||
- `tests/system/run-phase14-native-boot-qemu.sh`
|
||||
- `tests/system/run-phase14-native-boot-xcpng.sh`
|
||||
|
||||
These wrappers reuse the proven PID1 boot harnesses and assert the new boundary:
|
||||
|
||||
- `host_base_store_count=0`
|
||||
- native kernel present
|
||||
- native world present
|
||||
- boot assets come from native world
|
||||
|
||||
## Validation
|
||||
|
||||
### Local QEMU / UEFI / TCG
|
||||
|
||||
Passing run:
|
||||
|
||||
- `PASS phase14-native-boot-qemu`
|
||||
- workdir: `/tmp/phase14-1-qemu2-1775188371`
|
||||
|
||||
Confirmed:
|
||||
|
||||
```text
|
||||
native_base_store_count=2
|
||||
host_base_store_count=0
|
||||
shepherd_pid=1
|
||||
sshd_status=running
|
||||
native_boot_assets=freebsd-native-world
|
||||
native_base_boot=ok
|
||||
```
|
||||
|
||||
### Real XCP-ng VM
|
||||
|
||||
Passing run:
|
||||
|
||||
- `PASS phase14-native-boot-xcpng`
|
||||
- workdir: `/tmp/phase14-1-xcpng-1775188701`
|
||||
|
||||
Confirmed:
|
||||
|
||||
```text
|
||||
vm_id=90490f2e-e8fc-4b7a-388e-5c26f0157289
|
||||
vdi_id=0f1f90d3-48ca-4fa2-91d8-fc6339b95743
|
||||
guest_ip=192.168.213.62
|
||||
native_base_store_count=2
|
||||
host_base_store_count=0
|
||||
shepherd_pid=1
|
||||
sshd_status=running
|
||||
compat_prefix_shims=absent
|
||||
guile_module_smoke=ok
|
||||
native_boot_assets=freebsd-native-world
|
||||
native_base_boot=ok
|
||||
```
|
||||
|
||||
## Result
|
||||
|
||||
Phase 14.1 is complete.
|
||||
|
||||
For the validated boot path, Fruix no longer depends on a host-copied `freebsd-bootloader` store item.
|
||||
|
||||
The currently validated native boot boundary is now:
|
||||
|
||||
- native kernel
|
||||
- native world providing boot assets
|
||||
- native world providing runtime
|
||||
- Fruix runtime stores for Guile/Shepherd
|
||||
|
||||
That is already enough to say the image no longer relies on host-copied kernel/boot material.
|
||||
|
||||
## Next step
|
||||
|
||||
Phase 14.2 should remove the remaining model redundancy by introducing a clearer native runtime slice, so the booted guest reaches ready state using an explicit native runtime output rather than reusing the broader native world output for both boot and runtime roles.
|
||||
139
docs/reports/phase14-native-runtime-freebsd.md
Normal file
139
docs/reports/phase14-native-runtime-freebsd.md
Normal file
@@ -0,0 +1,139 @@
|
||||
# Phase 14.2: validated an explicit native FreeBSD runtime slice
|
||||
|
||||
Date: 2026-04-03
|
||||
|
||||
## Goal
|
||||
|
||||
Phase 14.1 proved that Fruix could boot without host-copied `/boot` material by sourcing boot assets from the existing native world output.
|
||||
|
||||
Phase 14.2 removed the remaining model ambiguity on the runtime side:
|
||||
|
||||
- the guest should now boot and reach ready state using an explicit native runtime output
|
||||
- not by reusing the broader native world artifact for both boot and runtime roles
|
||||
|
||||
## Changes
|
||||
|
||||
Added a new native package:
|
||||
|
||||
- `freebsd-native-runtime`
|
||||
|
||||
This package is built from `/usr/src` via the existing native world build path and prunes at least:
|
||||
|
||||
- `boot`
|
||||
- `usr/include`
|
||||
- `usr/share/doc`
|
||||
- `usr/share/examples`
|
||||
- `usr/share/info`
|
||||
- `usr/share/man`
|
||||
- `usr/share/mk`
|
||||
- `usr/tests`
|
||||
|
||||
The validated Phase 14.2 operating-system template now uses:
|
||||
|
||||
- `#:kernel freebsd-native-kernel`
|
||||
- `#:bootloader freebsd-native-world`
|
||||
- `#:base-packages (list freebsd-native-runtime)`
|
||||
|
||||
That means the model is now explicit:
|
||||
|
||||
- native world provides boot assets
|
||||
- native runtime provides the guest runtime slice
|
||||
- host base stores are no longer part of the validated path
|
||||
|
||||
## Practical sizing finding
|
||||
|
||||
This Phase 14.2 layout still duplicates some native world/runtime content in the closure because the boot assets still come from the broader native world output.
|
||||
|
||||
As a result, the Phase 13 image sizes were no longer large enough.
|
||||
|
||||
Working values were:
|
||||
|
||||
- local QEMU:
|
||||
- `DISK_CAPACITY=12g`
|
||||
- `ROOT_SIZE=10g`
|
||||
- real XCP-ng:
|
||||
- `ROOT_SIZE=10g`
|
||||
- disk capacity still matched to the fixed 30 GiB VDI
|
||||
|
||||
The wrappers were updated to use those larger defaults.
|
||||
|
||||
This is acceptable for Phase 14.2 because the next subphase is specifically about cleaning up the runtime/development/boot boundary further.
|
||||
|
||||
## New files
|
||||
|
||||
Added:
|
||||
|
||||
- `tests/system/phase14-native-runtime-pid1-operating-system.scm.in`
|
||||
- `tests/system/run-phase14-native-runtime-qemu.sh`
|
||||
- `tests/system/run-phase14-native-runtime-xcpng.sh`
|
||||
|
||||
These wrappers assert:
|
||||
|
||||
- `host_base_store_count=0`
|
||||
- native kernel present
|
||||
- native world present as the current boot-source artifact
|
||||
- native runtime present
|
||||
- runtime store still contains the files needed for boot-to-ready
|
||||
- runtime store no longer contains `/boot`
|
||||
- runtime store no longer contains `/usr/include`
|
||||
|
||||
## Validation
|
||||
|
||||
### Local QEMU / UEFI / TCG
|
||||
|
||||
Passing run:
|
||||
|
||||
- `PASS phase14-native-runtime-qemu`
|
||||
- workdir: `/tmp/phase14-2-qemu2-1775189802`
|
||||
|
||||
Confirmed:
|
||||
|
||||
```text
|
||||
disk_capacity=12g
|
||||
root_size=10g
|
||||
runtime_store=/frx/store/684a82aeed2c9a353e3a09d2cbf5358274d758005e0bfa9b1025d101bc166f79-freebsd-native-runtime-15.0-STABLE
|
||||
native_base_store_count=3
|
||||
host_base_store_count=0
|
||||
shepherd_pid=1
|
||||
sshd_status=running
|
||||
native_runtime_ready=ok
|
||||
```
|
||||
|
||||
### Real XCP-ng VM
|
||||
|
||||
Passing run:
|
||||
|
||||
- `PASS phase14-native-runtime-xcpng`
|
||||
- workdir: `/tmp/phase14-2-xcpng-1775190184`
|
||||
|
||||
Confirmed:
|
||||
|
||||
```text
|
||||
vm_id=90490f2e-e8fc-4b7a-388e-5c26f0157289
|
||||
vdi_id=0f1f90d3-48ca-4fa2-91d8-fc6339b95743
|
||||
guest_ip=192.168.213.62
|
||||
root_size=10g
|
||||
runtime_store=/frx/store/684a82aeed2c9a353e3a09d2cbf5358274d758005e0bfa9b1025d101bc166f79-freebsd-native-runtime-15.0-STABLE
|
||||
native_base_store_count=3
|
||||
host_base_store_count=0
|
||||
shepherd_pid=1
|
||||
sshd_status=running
|
||||
compat_prefix_shims=absent
|
||||
guile_module_smoke=ok
|
||||
native_runtime_ready=ok
|
||||
```
|
||||
|
||||
## Result
|
||||
|
||||
Phase 14.2 is complete.
|
||||
|
||||
The validated Fruix guest now reaches ready state using an explicit native runtime artifact:
|
||||
|
||||
- native kernel
|
||||
- native world for boot assets
|
||||
- native runtime for the guest runtime slice
|
||||
- no host-staged FreeBSD base stores in the validated path
|
||||
|
||||
## Next step
|
||||
|
||||
Phase 14.3 should clean up the remaining redundancy by defining clearer runtime vs. development boundaries and, ideally, replacing the temporary use of the broad native world artifact as the boot-source package with a narrower native boot asset package.
|
||||
225
docs/reports/phase14-native-splits-freebsd.md
Normal file
225
docs/reports/phase14-native-splits-freebsd.md
Normal file
@@ -0,0 +1,225 @@
|
||||
# Phase 14.3: split native FreeBSD boot, runtime, and development artifacts
|
||||
|
||||
Date: 2026-04-03
|
||||
|
||||
## Goal
|
||||
|
||||
Phase 14.3 finished the Phase 14 cleanup work by replacing the temporary broad-world reuse with narrower native artifacts and by making the runtime/development boundary explicit in code.
|
||||
|
||||
The target was:
|
||||
|
||||
- no host-staged FreeBSD base stores in the validated path
|
||||
- no need to keep the broad `freebsd-native-world` artifact in the final validated system closure
|
||||
- explicit native boot and runtime slices
|
||||
- clearer development-profile boundaries
|
||||
|
||||
## Implementation
|
||||
|
||||
### New native packages
|
||||
|
||||
Added in `modules/fruix/packages/freebsd.scm`:
|
||||
|
||||
- `freebsd-native-bootloader`
|
||||
- `freebsd-native-headers`
|
||||
|
||||
Also refined:
|
||||
|
||||
- `freebsd-native-runtime`
|
||||
|
||||
### Narrower native runtime slice
|
||||
|
||||
`freebsd-native-runtime` now prunes more obviously non-runtime content, including at least:
|
||||
|
||||
- `boot`
|
||||
- `rescue`
|
||||
- `usr/include`
|
||||
- `usr/lib/debug`
|
||||
- `usr/lib32`
|
||||
- `usr/obj`
|
||||
- `usr/src`
|
||||
- `usr/share/doc`
|
||||
- `usr/share/examples`
|
||||
- `usr/share/info`
|
||||
- `usr/share/man`
|
||||
- `usr/share/mk`
|
||||
- `usr/tests`
|
||||
|
||||
This shrank the runtime output substantially and made the runtime/development distinction much cleaner.
|
||||
|
||||
### Native bootloader slice
|
||||
|
||||
`freebsd-native-bootloader` is now a native world-derived slice that keeps only the validated boot assets Fruix currently needs:
|
||||
|
||||
- `boot/loader`
|
||||
- `boot/loader.efi`
|
||||
- `boot/device.hints`
|
||||
- `boot/defaults`
|
||||
- `boot/lua`
|
||||
|
||||
### Native headers slice
|
||||
|
||||
`freebsd-native-headers` now captures the development-facing header/mk boundary explicitly:
|
||||
|
||||
- `usr/include`
|
||||
- `usr/share/mk`
|
||||
|
||||
### Build-system support for sliced native outputs
|
||||
|
||||
`modules/fruix/system/freebsd.scm` gained support for `keep-paths` on native world-derived packages.
|
||||
|
||||
That means a native package can now:
|
||||
|
||||
- install full world/distribution into a staging tree
|
||||
- keep only selected subtrees/files for its final store output
|
||||
- still participate in the same `/usr/src`-based identity/build-root story
|
||||
|
||||
Native output metadata now records both:
|
||||
|
||||
- `keep-paths`
|
||||
- `prune-paths`
|
||||
|
||||
## New package-set boundaries
|
||||
|
||||
Added explicit package sets:
|
||||
|
||||
- `%freebsd-native-system-packages`
|
||||
- currently centered on `freebsd-native-runtime`
|
||||
- `%freebsd-native-development-profile-packages`
|
||||
- `freebsd-native-runtime`
|
||||
- `freebsd-native-headers`
|
||||
- explicit toolchain/dev helpers that remain separate artifacts
|
||||
|
||||
This makes the boundary clearer:
|
||||
|
||||
- runtime world output
|
||||
- headers artifact
|
||||
- toolchain artifact(s)
|
||||
- optional development profile composition
|
||||
|
||||
## Final validated Phase 14 system model
|
||||
|
||||
The final Phase 14 PID1 system template now uses:
|
||||
|
||||
- `#:kernel freebsd-native-kernel`
|
||||
- `#:bootloader freebsd-native-bootloader`
|
||||
- `#:base-packages %freebsd-native-system-packages`
|
||||
|
||||
That means the validated system closure now contains:
|
||||
|
||||
- native kernel
|
||||
- native bootloader slice
|
||||
- native runtime slice
|
||||
- Fruix runtime stores
|
||||
|
||||
and no longer needs the broad `freebsd-native-world` artifact in the final system closure.
|
||||
|
||||
## New files
|
||||
|
||||
Added:
|
||||
|
||||
- `tests/system/phase14-native-split-pid1-operating-system.scm.in`
|
||||
- `tests/system/run-phase14-native-split-qemu.sh`
|
||||
- `tests/system/run-phase14-native-split-xcpng.sh`
|
||||
- `tests/system/run-phase14-native-development-split.sh`
|
||||
|
||||
## Validation
|
||||
|
||||
### Development split validation
|
||||
|
||||
Passing run:
|
||||
|
||||
- `PASS phase14-native-development-split`
|
||||
- workdir: `/tmp/phase14-3-dev5-1775191195`
|
||||
|
||||
Confirmed:
|
||||
|
||||
```text
|
||||
bootloader_store=/frx/store/71aa3ba5dd9a02f7d2710bfc3624cbf5e3cd18f1fbff0744c82df36901b10ec0-freebsd-native-bootloader-15.0-STABLE
|
||||
headers_store=/frx/store/aab09122d37962e6d479c17172ce4b8ea85e5ff33c98aa76424ada2fa1a82617-freebsd-native-headers-15.0-STABLE
|
||||
native_system_packages=freebsd-native-runtime
|
||||
native_development_packages=freebsd-native-runtime,freebsd-native-headers,freebsd-clang-toolchain,freebsd-gmake,freebsd-autotools,freebsd-openssl,freebsd-zlib,freebsd-sh,freebsd-bash
|
||||
runtime_vs_development_split=ok
|
||||
```
|
||||
|
||||
The harness also confirmed:
|
||||
|
||||
- native bootloader slice contains only expected boot assets
|
||||
- native headers slice contains headers/mk files
|
||||
- native headers slice does not contain `/boot` or runtime binaries
|
||||
|
||||
### Local QEMU / UEFI / TCG
|
||||
|
||||
Passing run:
|
||||
|
||||
- `PASS phase14-native-split-qemu`
|
||||
- workdir: `/tmp/phase14-3-qemu-1775191337`
|
||||
|
||||
Confirmed:
|
||||
|
||||
```text
|
||||
disk_capacity=8g
|
||||
root_size=6g
|
||||
bootloader_store=/frx/store/71aa3ba5dd9a02f7d2710bfc3624cbf5e3cd18f1fbff0744c82df36901b10ec0-freebsd-native-bootloader-15.0-STABLE
|
||||
runtime_store=/frx/store/1b4b8774d0df36df2635fe1c35367a2c5fa7790e303f0aaa26eabfe3cce667f2-freebsd-native-runtime-15.0-STABLE
|
||||
native_base_store_count=3
|
||||
host_base_store_count=0
|
||||
shepherd_pid=1
|
||||
sshd_status=running
|
||||
native_split_boot=ok
|
||||
```
|
||||
|
||||
### Real XCP-ng VM
|
||||
|
||||
Passing run:
|
||||
|
||||
- `PASS phase14-native-split-xcpng`
|
||||
- workdir: `/tmp/phase14-3-xcpng-1775191743`
|
||||
|
||||
Confirmed:
|
||||
|
||||
```text
|
||||
vm_id=90490f2e-e8fc-4b7a-388e-5c26f0157289
|
||||
vdi_id=0f1f90d3-48ca-4fa2-91d8-fc6339b95743
|
||||
guest_ip=192.168.213.62
|
||||
root_size=6g
|
||||
bootloader_store=/frx/store/71aa3ba5dd9a02f7d2710bfc3624cbf5e3cd18f1fbff0744c82df36901b10ec0-freebsd-native-bootloader-15.0-STABLE
|
||||
runtime_store=/frx/store/1b4b8774d0df36df2635fe1c35367a2c5fa7790e303f0aaa26eabfe3cce667f2-freebsd-native-runtime-15.0-STABLE
|
||||
native_base_store_count=3
|
||||
host_base_store_count=0
|
||||
shepherd_pid=1
|
||||
sshd_status=running
|
||||
compat_prefix_shims=absent
|
||||
guile_module_smoke=ok
|
||||
native_split_boot=ok
|
||||
```
|
||||
|
||||
Notably, after the narrower native split, the XCP-ng upload size dropped back down dramatically:
|
||||
|
||||
- dynamic VHD upload: `1560725504` bytes (~1.45 GiB)
|
||||
|
||||
That is much better than the temporary broad-world + runtime duplication in Phase 14.2.
|
||||
|
||||
## Result
|
||||
|
||||
Phase 14 is complete.
|
||||
|
||||
Fruix now has a validated, host-base-free FreeBSD system path built from native artifacts in `/frx/store`:
|
||||
|
||||
- `freebsd-native-kernel`
|
||||
- `freebsd-native-bootloader`
|
||||
- `freebsd-native-runtime`
|
||||
|
||||
with a clearer development boundary via:
|
||||
|
||||
- `freebsd-native-headers`
|
||||
- `%freebsd-native-development-profile-packages`
|
||||
|
||||
This completes the Phase 14 objective of incrementally replacing the host-copy FreeBSD base layer for the validated boot/runtime path.
|
||||
|
||||
## Next step
|
||||
|
||||
Proceed to Phase 15:
|
||||
|
||||
1. make the FreeBSD base version a declarative input
|
||||
2. demonstrate side-by-side base versions in `/frx/store`
|
||||
3. establish rebuild/redeploy/rollback across those base versions
|
||||
179
docs/reports/phase15-base-upgrades-freebsd.md
Normal file
179
docs/reports/phase15-base-upgrades-freebsd.md
Normal file
@@ -0,0 +1,179 @@
|
||||
# Phase 15.2: side-by-side native base versions and rollback-friendly redeploy
|
||||
|
||||
Date: 2026-04-03
|
||||
|
||||
## Goal
|
||||
|
||||
Phase 15.2 demonstrated that Fruix can keep at least two distinct declarative FreeBSD base builds in `/frx/store` at the same time and switch between them through the normal system rebuild/image/boot flow.
|
||||
|
||||
For this first upgrade-story validation, both declared bases still point at the same local `/usr/src`, but they carry distinct declarative version labels:
|
||||
|
||||
- current base: `15.0-STABLE`
|
||||
- candidate base: `15.0-STABLE-p1`
|
||||
|
||||
That is enough to prove the Fruix properties needed here:
|
||||
|
||||
- distinct content-addressed outputs
|
||||
- side-by-side coexistence
|
||||
- no in-place mutation of the older base closure
|
||||
- rollback to the earlier closure using the normal deployment path
|
||||
|
||||
## New files
|
||||
|
||||
Added:
|
||||
|
||||
- `tests/system/run-phase15-base-coexistence.sh`
|
||||
- `tests/system/run-phase15-base-rollback-qemu.sh`
|
||||
- `tests/system/run-phase15-base-rollback-xcpng.sh`
|
||||
|
||||
## Validation model
|
||||
|
||||
### Current base declaration
|
||||
|
||||
```scheme
|
||||
(freebsd-base
|
||||
#:name "stable-default"
|
||||
#:version-label "15.0-STABLE"
|
||||
#:release "15.0-STABLE"
|
||||
#:branch "stable/15"
|
||||
...)
|
||||
```
|
||||
|
||||
### Candidate base declaration
|
||||
|
||||
```scheme
|
||||
(freebsd-base
|
||||
#:name "stable-canary"
|
||||
#:version-label "15.0-STABLE-p1"
|
||||
#:release "15.0-STABLE"
|
||||
#:branch "stable/15"
|
||||
...)
|
||||
```
|
||||
|
||||
Both declarations use the same validated native Phase 14 package composition:
|
||||
|
||||
- kernel from `freebsd-native-kernel-for`
|
||||
- bootloader from `freebsd-native-bootloader-for`
|
||||
- runtime from `freebsd-native-system-packages-for`
|
||||
- `shepherd-pid1`
|
||||
|
||||
## Side-by-side build validation
|
||||
|
||||
Passing run:
|
||||
|
||||
- `PASS phase15-base-coexistence`
|
||||
- workdir: `/tmp/phase15-2-coexist-1775202833`
|
||||
|
||||
Confirmed:
|
||||
|
||||
```text
|
||||
current_closure=/frx/store/9f57ecc6481e271811ceb53ac21a3b2aef4ef329f82b7d4788622315db1f0e43-fruix-system-fruix-freebsd
|
||||
candidate_closure=/frx/store/dc40b1b7a76084e140d0457f3b7f6c5d4acc185f0d6cee0b161c9775d5fb3bec-fruix-system-fruix-freebsd
|
||||
current_base_version_label=15.0-STABLE
|
||||
candidate_base_version_label=15.0-STABLE-p1
|
||||
side_by_side_base_versions=ok
|
||||
rollback_rebuild_path=ok
|
||||
```
|
||||
|
||||
Current native base stores:
|
||||
|
||||
```text
|
||||
/frx/store/d9785661ea4829d51fbf545c2607a5691af2cc33c8ef3cd44de7ad5626685098-freebsd-native-kernel-15.0-STABLE
|
||||
/frx/store/b448c822302ccdfb2f06da811fb224a044c51a9935bbfcd77a71a25d02f228f1-freebsd-native-bootloader-15.0-STABLE
|
||||
/frx/store/ac3ba684020e70d3c76e593fd687cef8ab5e148958baabb477b7ef3d2647c5cd-freebsd-native-runtime-15.0-STABLE
|
||||
```
|
||||
|
||||
Candidate native base stores:
|
||||
|
||||
```text
|
||||
/frx/store/05bee8ffbe8c43242ffd97da4dc305f2921612a660cbcb48c3a3536bfac07079-freebsd-native-kernel-15.0-STABLE-p1
|
||||
/frx/store/8955f1bfe89321e6e1e628c59376f2092547523f48a773974cc259963adac184-freebsd-native-bootloader-15.0-STABLE-p1
|
||||
/frx/store/30314f17fd8ff4a1a3eff31c8c5048f15f67c46d1132d5b8c45fd9768742665e-freebsd-native-runtime-15.0-STABLE-p1
|
||||
```
|
||||
|
||||
Important result:
|
||||
|
||||
- the older current closure stayed in `/frx/store`
|
||||
- the candidate closure appeared beside it
|
||||
- rebuilding the current declaration returned the exact original current closure path again
|
||||
|
||||
## Local QEMU rollback validation
|
||||
|
||||
Passing run:
|
||||
|
||||
- `PASS phase15-base-rollback-qemu`
|
||||
- workdir: `/tmp/phase15-2-qemu2-1775204321`
|
||||
|
||||
Validation sequence:
|
||||
|
||||
1. boot current base
|
||||
2. boot candidate base
|
||||
3. boot current base again
|
||||
|
||||
Confirmed:
|
||||
|
||||
```text
|
||||
current_first_closure=/frx/store/9f57ecc6481e271811ceb53ac21a3b2aef4ef329f82b7d4788622315db1f0e43-fruix-system-fruix-freebsd
|
||||
candidate_closure=/frx/store/dc40b1b7a76084e140d0457f3b7f6c5d4acc185f0d6cee0b161c9775d5fb3bec-fruix-system-fruix-freebsd
|
||||
rollback_closure=/frx/store/9f57ecc6481e271811ceb53ac21a3b2aef4ef329f82b7d4788622315db1f0e43-fruix-system-fruix-freebsd
|
||||
current_base_version_label=15.0-STABLE
|
||||
candidate_base_version_label=15.0-STABLE-p1
|
||||
rollback_base_version_label=15.0-STABLE
|
||||
base_rollforward_and_rollback=ok
|
||||
```
|
||||
|
||||
This showed that the booted system could move forward to the candidate base and then return to the earlier closure without mutating it in place.
|
||||
|
||||
## Real XCP-ng rollback validation
|
||||
|
||||
Passing run:
|
||||
|
||||
- `PASS phase15-base-rollback-xcpng`
|
||||
- workdir: `/tmp/phase15-2-xcpng-1775204839`
|
||||
|
||||
Validation sequence:
|
||||
|
||||
1. boot candidate base on the approved VM/VDI
|
||||
2. boot current base again on the same approved VM/VDI
|
||||
|
||||
Confirmed:
|
||||
|
||||
```text
|
||||
candidate_closure=/frx/store/dc40b1b7a76084e140d0457f3b7f6c5d4acc185f0d6cee0b161c9775d5fb3bec-fruix-system-fruix-freebsd
|
||||
rollback_closure=/frx/store/9f57ecc6481e271811ceb53ac21a3b2aef4ef329f82b7d4788622315db1f0e43-fruix-system-fruix-freebsd
|
||||
candidate_base_version_label=15.0-STABLE-p1
|
||||
rollback_base_version_label=15.0-STABLE
|
||||
vm_id=90490f2e-e8fc-4b7a-388e-5c26f0157289
|
||||
vdi_id=0f1f90d3-48ca-4fa2-91d8-fc6339b95743
|
||||
base_rollforward_and_rollback=ok
|
||||
```
|
||||
|
||||
Both boots also preserved the already-hardened guest properties:
|
||||
|
||||
- `shepherd_pid=1`
|
||||
- `sshd_status=running`
|
||||
- `compat_prefix_shims=absent`
|
||||
- `guile_module_smoke=ok`
|
||||
|
||||
## Result
|
||||
|
||||
Phase 15.2 is complete.
|
||||
|
||||
Fruix now has a real declarative rebuild/redeploy/rollback story for the FreeBSD base at the store/model layer:
|
||||
|
||||
- two declared base versions can coexist side by side in `/frx/store`
|
||||
- the candidate deployment does not overwrite the current one in place
|
||||
- rebuilding the earlier declaration returns to the earlier closure path
|
||||
- the same story works both locally under QEMU and on the approved XCP-ng VM/VDI path
|
||||
|
||||
## Scope note
|
||||
|
||||
This first upgrade-story validation still uses the same local `/usr/src` as the underlying source tree for both declarations. What changed is the declared base identity and therefore the store/model/deployment identity.
|
||||
|
||||
That is sufficient for this phase because the requirement was to establish the upgrade semantics:
|
||||
|
||||
- explicit base declaration
|
||||
- side-by-side outputs
|
||||
- rollback-friendly closures
|
||||
|
||||
The next improvement beyond Phase 15 would be to make acquiring or selecting distinct source trees/releases more reproducible and less tied to a single host checkout.
|
||||
141
docs/reports/phase15-declarative-base-freebsd.md
Normal file
141
docs/reports/phase15-declarative-base-freebsd.md
Normal file
@@ -0,0 +1,141 @@
|
||||
# Phase 15.1: make the FreeBSD base version a declarative Fruix input
|
||||
|
||||
Date: 2026-04-03
|
||||
|
||||
## Goal
|
||||
|
||||
Phase 15.1 made the FreeBSD base an explicit declarative part of the Fruix system model instead of leaving it as an implicit property of whatever `/usr/src` happened to be present on the builder host.
|
||||
|
||||
## Implementation
|
||||
|
||||
### New declarative base record
|
||||
|
||||
Added in `modules/fruix/packages/freebsd.scm`:
|
||||
|
||||
- `freebsd-base`
|
||||
- `freebsd-base?`
|
||||
- accessors for:
|
||||
- `name`
|
||||
- `version-label`
|
||||
- `release`
|
||||
- `branch`
|
||||
- `source-root`
|
||||
- `target`
|
||||
- `target-arch`
|
||||
- `kernconf`
|
||||
- `make-flags`
|
||||
- `%default-freebsd-base`
|
||||
|
||||
This gives Fruix an explicit model for the base input used by native FreeBSD artifacts.
|
||||
|
||||
### Native package constructors now accept a declared base
|
||||
|
||||
Added package constructors:
|
||||
|
||||
- `freebsd-native-kernel-for`
|
||||
- `freebsd-native-world-for`
|
||||
- `freebsd-native-runtime-for`
|
||||
- `freebsd-native-bootloader-for`
|
||||
- `freebsd-native-headers-for`
|
||||
- `freebsd-native-system-packages-for`
|
||||
- `freebsd-native-development-profile-packages-for`
|
||||
|
||||
The existing exported package variables remain as the `%default-freebsd-base` instances:
|
||||
|
||||
- `freebsd-native-kernel`
|
||||
- `freebsd-native-world`
|
||||
- `freebsd-native-runtime`
|
||||
- `freebsd-native-bootloader`
|
||||
- `freebsd-native-headers`
|
||||
|
||||
That preserves the validated Phase 14 path while making alternative declared bases possible.
|
||||
|
||||
### Operating-system model now records the declared base
|
||||
|
||||
Added to `modules/fruix/system/freebsd.scm`:
|
||||
|
||||
- `operating-system-freebsd-base`
|
||||
- new `#:freebsd-base` field on `operating-system`
|
||||
|
||||
The declared base is now recorded in:
|
||||
|
||||
- `operating-system-closure-spec`
|
||||
- `operating-system-image-spec`
|
||||
- `metadata/freebsd-base.scm`
|
||||
- `metadata/store-layout.scm`
|
||||
|
||||
### CLI metadata now exposes the declared base
|
||||
|
||||
`scripts/fruix.scm` now emits, for `fruix system build` and `image`:
|
||||
|
||||
- `freebsd_base_name`
|
||||
- `freebsd_base_version_label`
|
||||
- `freebsd_base_release`
|
||||
- `freebsd_base_branch`
|
||||
- `freebsd_base_source_root`
|
||||
- `freebsd_base_target`
|
||||
- `freebsd_base_target_arch`
|
||||
- `freebsd_base_kernconf`
|
||||
- `freebsd_base_file`
|
||||
|
||||
### Native build metadata now records the declared base
|
||||
|
||||
Native build manifests and `.freebsd-native-build-info.scm` now carry a `declared-base` block so the native artifacts themselves record the declarative base choice.
|
||||
|
||||
## New files
|
||||
|
||||
Added:
|
||||
|
||||
- `tests/system/phase15-declarative-base-pid1-operating-system.scm.in`
|
||||
- `tests/system/run-phase15-declarative-base-build.sh`
|
||||
|
||||
## Validation
|
||||
|
||||
Passing run:
|
||||
|
||||
- `PASS phase15-declarative-base-build`
|
||||
- workdir: `/tmp/phase15-1-build-1775202535`
|
||||
|
||||
The harness used an explicit declared base:
|
||||
|
||||
```scheme
|
||||
(freebsd-base
|
||||
#:name "stable-default"
|
||||
#:version-label "15.0-STABLE-declarative"
|
||||
#:release "15.0-STABLE"
|
||||
#:branch "stable/15"
|
||||
#:source-root "/usr/src"
|
||||
#:target "amd64"
|
||||
#:target-arch "amd64"
|
||||
#:kernconf "GENERIC")
|
||||
```
|
||||
|
||||
Confirmed:
|
||||
|
||||
```text
|
||||
kernel_store=/frx/store/8fcef04c7e507e86ea5e92f251fe3c6ac1aa3bcf4809fa77ddd8b92854bfcde0-freebsd-native-kernel-15.0-STABLE-declarative
|
||||
bootloader_store=/frx/store/7a0ba431e487dc35a8f6318108da16a37c8426c43e77e7a7f91404ba1d980eef-freebsd-native-bootloader-15.0-STABLE-declarative
|
||||
runtime_store=/frx/store/17c24ad20ddcb136c39352b68e758deae0b480258ba0128a5546f696a7eba0a6-freebsd-native-runtime-15.0-STABLE-declarative
|
||||
native_base_store_count=3
|
||||
host_base_store_count=0
|
||||
freebsd_base_name=stable-default
|
||||
freebsd_base_version_label=15.0-STABLE-declarative
|
||||
freebsd_base_release=15.0-STABLE
|
||||
freebsd_base_branch=stable/15
|
||||
freebsd_base_source_root=/usr/src
|
||||
freebsd_base_kernconf=GENERIC
|
||||
declarative_base_input=ok
|
||||
```
|
||||
|
||||
The harness also confirmed:
|
||||
|
||||
- `metadata/freebsd-base.scm` exists
|
||||
- `parameters.scm` records the declared base
|
||||
- `metadata/store-layout.scm` records the declared base
|
||||
- native build info files record the declared base version/branch
|
||||
|
||||
## Result
|
||||
|
||||
Phase 15.1 is complete.
|
||||
|
||||
Fruix now has an explicit declarative FreeBSD base input in the system model, while still allowing the current validated `/usr/src`-based path to work unchanged by default.
|
||||
120
docs/reports/phase15-self-hosting-decision-freebsd.md
Normal file
120
docs/reports/phase15-self-hosting-decision-freebsd.md
Normal file
@@ -0,0 +1,120 @@
|
||||
# Phase 15.3: decision on self-hosted FreeBSD base builds
|
||||
|
||||
Date: 2026-04-03
|
||||
|
||||
## Question
|
||||
|
||||
After Phases 13 through 15, should Fruix immediately pursue self-hosted FreeBSD base builds inside a Fruix-managed guest, or should it continue using the host builder while tightening source/reproducibility boundaries?
|
||||
|
||||
## Current evidence
|
||||
|
||||
What is now already true:
|
||||
|
||||
- Fruix builds native FreeBSD base artifacts from `/usr/src` into `/frx/store`
|
||||
- Fruix has validated a host-base-free boot/runtime path composed from:
|
||||
- `freebsd-native-kernel`
|
||||
- `freebsd-native-bootloader`
|
||||
- `freebsd-native-runtime`
|
||||
- that path boots locally under QEMU/UEFI and on the approved real XCP-ng VM/VDI
|
||||
- the FreeBSD base is now an explicit declarative system input through `freebsd-base`
|
||||
- Fruix can keep distinct declared base versions side by side in `/frx/store`
|
||||
- Fruix can roll forward to a candidate base declaration and roll back to the earlier closure without mutating it in place
|
||||
|
||||
So the main Guix-inspired value proposition is already present at the system-deployment layer:
|
||||
|
||||
- declarative system descriptions
|
||||
- content-addressed outputs
|
||||
- side-by-side versions
|
||||
- rollback-friendly closures
|
||||
|
||||
## What is still not strong enough for self-hosting to be the best next step
|
||||
|
||||
### 1. Source acquisition is still too host-local
|
||||
|
||||
The native base path still depends on the host's local `/usr/src` tree as the source of truth.
|
||||
|
||||
That means the next reproducibility win is not "build inside the guest" first; it is "make the source tree selection/acquisition more explicit and reproducible" first.
|
||||
|
||||
### 2. The host-built path is already validated and useful
|
||||
|
||||
The current host-built native base flow is not hypothetical anymore. It has already proven:
|
||||
|
||||
- native artifacts in `/frx/store`
|
||||
- host-base-free boot/runtime composition
|
||||
- side-by-side closures
|
||||
- rollback behavior
|
||||
|
||||
That makes it the productive path to continue refining.
|
||||
|
||||
### 3. Self-hosting would currently mix several problems at once
|
||||
|
||||
Jumping to guest self-hosting now would combine:
|
||||
|
||||
- source acquisition/reproducibility questions
|
||||
- toolchain/profile maturity questions
|
||||
- build-environment modeling questions
|
||||
- large build-resource/runtime questions inside the guest
|
||||
- deployment/debugging questions
|
||||
|
||||
That would make failure analysis worse just after the project finally obtained a clean validated deployment path.
|
||||
|
||||
### 4. Platform constraints still argue for caution
|
||||
|
||||
Current operator/environment constraints still matter:
|
||||
|
||||
- local bhyve remains blocked under Xen due to missing nested VT-x exposure
|
||||
- real VM validation still reuses a single approved XCP-ng VM/VDI pair
|
||||
- XCP-ng storage permissions still prevent creating fresh VDIs on demand
|
||||
|
||||
Those constraints do not block the current host-built native-base path, but they do make an early self-hosting pivot less attractive.
|
||||
|
||||
## Decision
|
||||
|
||||
**Decision:** do **not** pursue self-hosted FreeBSD base builds as the next immediate milestone.
|
||||
|
||||
The near-term direction should remain:
|
||||
|
||||
1. continue building native FreeBSD base artifacts on the host
|
||||
2. keep storing them in `/frx/store`
|
||||
3. improve declarative source-tree/version selection and provenance
|
||||
4. tighten reproducibility around source inputs and build parameters
|
||||
5. only revisit self-hosting after those pieces are stronger
|
||||
|
||||
## Why this is the right decision now
|
||||
|
||||
This preserves the important architectural win already achieved:
|
||||
|
||||
- Fruix, not ad hoc host copying, now defines the deployed FreeBSD base through declared store artifacts
|
||||
|
||||
It also follows the same spirit as Guix generation/rollback semantics: the crucial user-facing property is not where the build happened first, but that the result is declarative, content-addressed, side-by-side, and rollback-friendly.
|
||||
|
||||
At this point Fruix already has those properties for the FreeBSD base path.
|
||||
|
||||
## Conditions for revisiting self-hosting later
|
||||
|
||||
Self-hosted base builds should be reconsidered only after at least most of the following are true:
|
||||
|
||||
- Fruix can select or acquire distinct FreeBSD source trees more reproducibly than a single ambient `/usr/src`
|
||||
- the native development/toolchain side is modeled cleanly enough for a Fruix-managed build environment
|
||||
- operator/deployment tooling can handle larger iterative validation loops without excessive friction
|
||||
- there is a concrete benefit to guest-side base builds beyond what host-built store artifacts already provide
|
||||
|
||||
## Recommended next focus after Phase 15
|
||||
|
||||
The next work should likely focus on improving the declared source/reproducibility boundary around the now-working native base path, rather than on guest self-hosting.
|
||||
|
||||
A good next phase would be centered on things like:
|
||||
|
||||
- declarative source-tree selection/acquisition for the FreeBSD base
|
||||
- stronger provenance for declared source inputs
|
||||
- cleaner rebuilds across multiple source trees or revisions
|
||||
- continued refinement of deployment/generation management around the existing store-based base artifacts
|
||||
|
||||
## Result
|
||||
|
||||
Phase 15.3 is complete.
|
||||
|
||||
The project now has an evidence-based decision:
|
||||
|
||||
- **near-term path:** host-built native FreeBSD base artifacts in `/frx/store`
|
||||
- **not yet:** self-hosted FreeBSD base builds inside the Fruix guest
|
||||
202
docs/reports/phase16-declarative-source-model-freebsd.md
Normal file
202
docs/reports/phase16-declarative-source-model-freebsd.md
Normal file
@@ -0,0 +1,202 @@
|
||||
# Phase 16.1: model FreeBSD source inputs explicitly
|
||||
|
||||
Date: 2026-04-03
|
||||
|
||||
## Goal
|
||||
|
||||
Phase 16.1 introduces a first-class Fruix model for FreeBSD source inputs so the native base path is no longer described only as "whatever `/usr/src` is on the host".
|
||||
|
||||
This step does **not** yet fetch or materialize remote source trees. It makes the source declaration explicit and records it through the package, system, and CLI metadata layers so later phases can replace ambient `/usr/src` with fetched or materialized inputs cleanly.
|
||||
|
||||
## Implementation
|
||||
|
||||
### New declarative FreeBSD source record
|
||||
|
||||
Added in `modules/fruix/packages/freebsd.scm`:
|
||||
|
||||
- `freebsd-source`
|
||||
- `freebsd-source?`
|
||||
- accessors for:
|
||||
- `name`
|
||||
- `kind`
|
||||
- `url`
|
||||
- `path`
|
||||
- `ref`
|
||||
- `commit`
|
||||
- `sha256`
|
||||
- `%default-freebsd-source`
|
||||
|
||||
Supported source kinds are now modeled explicitly as:
|
||||
|
||||
- `local-tree`
|
||||
- `git`
|
||||
- `src-txz`
|
||||
|
||||
The default source remains a local-tree declaration for:
|
||||
|
||||
- `/usr/src`
|
||||
|
||||
### FreeBSD bases now carry a declared source object
|
||||
|
||||
Extended `freebsd-base` so it now records:
|
||||
|
||||
- the transitional `source-root` still used by the current native build path
|
||||
- a new `source` record describing the declared FreeBSD source input
|
||||
|
||||
Added/exported:
|
||||
|
||||
- `freebsd-base-source`
|
||||
|
||||
This keeps the validated `/usr/src` path working while giving the base a source object that can later point at fetched Git or `src.txz` materializations.
|
||||
|
||||
### Native package plans and metadata now record the declared source
|
||||
|
||||
`modules/fruix/packages/freebsd.scm` now threads source fields into native package install plans.
|
||||
|
||||
`modules/fruix/system/freebsd.scm` now records a `declared-source` block in:
|
||||
|
||||
- native package manifests
|
||||
- `.freebsd-native-build-info.scm`
|
||||
|
||||
This means native kernel, bootloader, and runtime outputs now remember both:
|
||||
|
||||
- the declared base
|
||||
- the declared source input
|
||||
|
||||
### Operating-system validation now checks source declarations
|
||||
|
||||
Added source validation in `modules/fruix/system/freebsd.scm`.
|
||||
|
||||
Current accepted source declarations are:
|
||||
|
||||
- `local-tree`
|
||||
- requires a path
|
||||
- `git`
|
||||
- requires a URL and at least a ref or commit
|
||||
- `src-txz`
|
||||
- requires a URL
|
||||
|
||||
This is intentionally enough for Phase 16.1 modeling without yet enforcing the later fetch/materialization policy.
|
||||
|
||||
### Closure metadata now includes a dedicated source file
|
||||
|
||||
System closures now generate:
|
||||
|
||||
- `metadata/freebsd-source.scm`
|
||||
|
||||
and embed source information in:
|
||||
|
||||
- `metadata/freebsd-base.scm`
|
||||
- `metadata/store-layout.scm`
|
||||
- `parameters.scm`
|
||||
|
||||
### CLI metadata now exposes the declared FreeBSD source
|
||||
|
||||
`scripts/fruix.scm` now emits the following for `fruix system build` and `image`:
|
||||
|
||||
- `freebsd_source_name`
|
||||
- `freebsd_source_kind`
|
||||
- `freebsd_source_url`
|
||||
- `freebsd_source_path`
|
||||
- `freebsd_source_ref`
|
||||
- `freebsd_source_commit`
|
||||
- `freebsd_source_sha256`
|
||||
- `freebsd_source_file`
|
||||
|
||||
## Guix comparison
|
||||
|
||||
This follows the same broad idea as Guix source modeling:
|
||||
|
||||
- Guix uses `origin` in `guix/packages.scm`
|
||||
- Git-backed sources are modeled with `git-reference` in `guix/git-download.scm`
|
||||
|
||||
Phase 16.1 does not copy Guix mechanically, but it adopts the same useful architectural boundary:
|
||||
|
||||
- source identity should be a declarative object
|
||||
- builds should record that source object explicitly
|
||||
|
||||
For Fruix, the source kinds are FreeBSD-oriented:
|
||||
|
||||
- local source tree snapshots for development
|
||||
- FreeBSD Git refs/commits
|
||||
- official FreeBSD `src.txz` archives
|
||||
|
||||
## Upstream source forms verified during this step
|
||||
|
||||
Verified usable upstream source forms include:
|
||||
|
||||
- Git:
|
||||
- `https://git.FreeBSD.org/src.git`
|
||||
- release archive:
|
||||
- `https://download.freebsd.org/releases/amd64/15.0-RELEASE/src.txz`
|
||||
- snapshot archive:
|
||||
- `https://download.freebsd.org/snapshots/amd64/15.0-STABLE/src.txz`
|
||||
|
||||
The shorter release URL form above is treated as the canonical example in this phase.
|
||||
|
||||
## New files
|
||||
|
||||
Added:
|
||||
|
||||
- `docs/PLAN_4.md`
|
||||
- `tests/system/phase16-declarative-source-operating-system.scm.in`
|
||||
- `tests/system/run-phase16-declarative-source-build.sh`
|
||||
- `tests/system/validate-phase16-freebsd-source.scm`
|
||||
|
||||
## Validation
|
||||
|
||||
Passing run:
|
||||
|
||||
- `PASS phase16-declarative-source-build`
|
||||
- workdir:
|
||||
- `/tmp/fruix-phase16-declarative-source.0LRvaC`
|
||||
|
||||
The source model probe confirmed:
|
||||
|
||||
```text
|
||||
local_kind=local-tree
|
||||
local_path=/usr/src
|
||||
git_kind=git
|
||||
git_url=https://git.FreeBSD.org/src.git
|
||||
git_ref=stable/15
|
||||
txz_kind=src-txz
|
||||
txz_url=https://download.freebsd.org/releases/amd64/15.0-RELEASE/src.txz
|
||||
txz_sha256=example-sha256
|
||||
base_source_accessor=ok
|
||||
```
|
||||
|
||||
The closure build confirmed:
|
||||
|
||||
```text
|
||||
closure_path=/frx/store/ac73a2a89c3c3f794462ccde0d9f0952362dc2ae32e631a7e99e07e7363e6118-fruix-system-fruix-freebsd
|
||||
kernel_store=/frx/store/c2f771a794f94cf168ae26a421ffab33e0ae4765f23882257d5bb7b2947d493d-freebsd-native-kernel-15.0-STABLE-source-model
|
||||
bootloader_store=/frx/store/a146c9c2fcacdc02454fe3bfd3afb5c5dbea54b6514242d53df0783ef4ee34fd-freebsd-native-bootloader-15.0-STABLE-source-model
|
||||
runtime_store=/frx/store/530e96440a518821a701db03f9437d7646c0447f8ea55c5df08417e9274dead1-freebsd-native-runtime-15.0-STABLE-source-model
|
||||
native_base_store_count=3
|
||||
host_base_store_count=0
|
||||
freebsd_source_name=host-usr-src
|
||||
freebsd_source_kind=local-tree
|
||||
freebsd_source_path=/usr/src
|
||||
freebsd_source_file=/frx/store/ac73a2a89c3c3f794462ccde0d9f0952362dc2ae32e631a7e99e07e7363e6118-fruix-system-fruix-freebsd/metadata/freebsd-source.scm
|
||||
declarative_source_model=ok
|
||||
```
|
||||
|
||||
The harness also verified that:
|
||||
|
||||
- `metadata/freebsd-source.scm` exists
|
||||
- `metadata/freebsd-base.scm` contains a nested source block
|
||||
- `metadata/store-layout.scm` records the declared source explicitly
|
||||
- native build info files contain a `declared-source` block with:
|
||||
- `kind . local-tree`
|
||||
- `path . "/usr/src"`
|
||||
|
||||
## Result
|
||||
|
||||
Phase 16.1 is complete.
|
||||
|
||||
Fruix now has an explicit source declaration layer for FreeBSD bases while preserving the current validated `/usr/src`-driven native build path.
|
||||
|
||||
That means Phase 16.2 can focus narrowly on the next real boundary:
|
||||
|
||||
- fetching or materializing declared FreeBSD source inputs under Fruix control
|
||||
- instead of just describing them
|
||||
182
docs/reports/phase16-source-driven-native-builds-freebsd.md
Normal file
182
docs/reports/phase16-source-driven-native-builds-freebsd.md
Normal file
@@ -0,0 +1,182 @@
|
||||
# Phase 16.3: build native FreeBSD base artifacts from materialized source inputs
|
||||
|
||||
Date: 2026-04-03
|
||||
|
||||
## Goal
|
||||
|
||||
Phase 16.3 closes the immediate gap between:
|
||||
|
||||
- declarative/materialized FreeBSD source inputs, and
|
||||
- the native kernel/world/runtime/bootloader build path.
|
||||
|
||||
Before this step, Fruix could already:
|
||||
|
||||
- describe FreeBSD sources explicitly, and
|
||||
- materialize them into `/frx/store`
|
||||
|
||||
but native base builds still consumed the ambient host source tree through the package plan's `source-root`.
|
||||
|
||||
After this step, native FreeBSD base artifacts are driven by the declared source input's **materialized store snapshot** instead.
|
||||
|
||||
## Implementation
|
||||
|
||||
### Native package materialization now injects materialized source inputs
|
||||
|
||||
`modules/fruix/system/freebsd.scm` now prepares native FreeBSD packages differently from copy-based packages.
|
||||
|
||||
For native packages, Fruix now:
|
||||
|
||||
1. reconstructs the declared `freebsd-source` from the package plan
|
||||
2. materializes that source under Fruix control
|
||||
3. rewrites the native build plan to use the materialized source root
|
||||
4. records both the declared source and the materialized/effective source in the output metadata
|
||||
5. adds the materialized source store path to the package references
|
||||
|
||||
This means native package output identity now depends on:
|
||||
|
||||
- the declared source object
|
||||
- the resolved/materialized source identity
|
||||
- the materialized source root and tree hash
|
||||
|
||||
rather than on an ambient host tree path.
|
||||
|
||||
### Native build manifests now record both declared and materialized source identity
|
||||
|
||||
Native package manifests now include:
|
||||
|
||||
- `declared-source`
|
||||
- `materialized-source`
|
||||
- the `native-build-common` block derived from the materialized source root
|
||||
|
||||
Native `.freebsd-native-build-info.scm` files now record:
|
||||
|
||||
- declared source
|
||||
- materialized source store path
|
||||
- materialized source root
|
||||
- materialized source tree hash
|
||||
- effective source details such as resolved Git commit or verified archive SHA256
|
||||
|
||||
### Source tree hashing now reuses materialized-source metadata when available
|
||||
|
||||
The native build common manifest now prefers the materialized source tree hash when one is already known, rather than recomputing it unnecessarily.
|
||||
|
||||
### Package caching is now manifest-driven rather than name/version-only
|
||||
|
||||
The in-process package materialization cache for native builds now keys on the computed manifest identity rather than only:
|
||||
|
||||
- package name
|
||||
- package version
|
||||
|
||||
That is important once multiple source-driven variants of the same native package version can coexist.
|
||||
|
||||
### System closures now record source materializations explicitly
|
||||
|
||||
`materialize-operating-system` now keeps a dedicated source-materialization cache while building native packages.
|
||||
|
||||
Closures now record:
|
||||
|
||||
- `metadata/freebsd-source-materializations.scm`
|
||||
- `materialized-source-store-count`
|
||||
- `materialized-source-stores`
|
||||
|
||||
and system references now include the materialized source stores explicitly.
|
||||
|
||||
This makes the source artifacts themselves part of the closure provenance boundary.
|
||||
|
||||
### `fruix system` metadata now exposes materialized source stores
|
||||
|
||||
`scripts/fruix.scm` now emits for `fruix system build` and `image`:
|
||||
|
||||
- `freebsd_source_materializations_file`
|
||||
- `materialized_source_store_count`
|
||||
- `materialized_source_stores`
|
||||
|
||||
This makes it easy for harnesses and operators to see which source store snapshot was actually used for a build.
|
||||
|
||||
## New files
|
||||
|
||||
Added:
|
||||
|
||||
- `tests/system/phase16-git-materialized-source-operating-system.scm.in`
|
||||
- `tests/system/run-phase16-source-driven-native-build.sh`
|
||||
- `docs/reports/phase16-source-driven-native-builds-freebsd.md`
|
||||
|
||||
## Validation
|
||||
|
||||
Passing runs:
|
||||
|
||||
- `PASS phase16-source-driven-native-build`
|
||||
- `PASS phase16-source-materialization`
|
||||
- `PASS phase16-declarative-source-build`
|
||||
|
||||
### Source-driven native build validation
|
||||
|
||||
Validated a native system build from a declared Git source:
|
||||
|
||||
- source kind:
|
||||
- `git`
|
||||
- source URL:
|
||||
- `https://git.FreeBSD.org/src.git`
|
||||
- source ref:
|
||||
- `stable/15`
|
||||
- resolved commit during validation:
|
||||
- `332708a606f6bf0841c1d4a74c0d067f5640fe89`
|
||||
|
||||
The test intentionally declared an **unused transitional source-root**:
|
||||
|
||||
- `/var/empty/fruix-unused-source-root`
|
||||
|
||||
and still built successfully, proving the native build path now uses the materialized source snapshot rather than the ambient declared root.
|
||||
|
||||
Confirmed build metadata:
|
||||
|
||||
```text
|
||||
closure_path=/frx/store/efed0b8ee31b4f3f97b8ae04317ccd2f3f8a88e332172e2bf0bb4295271c6023-fruix-system-fruix-freebsd
|
||||
kernel_store=/frx/store/c7e094a450f017a35c2d4d66451dcda893bc4ac27d810295fa491e41fdae8551-freebsd-native-kernel-15.0-STABLE-git-materialized
|
||||
bootloader_store=/frx/store/36432acf23cdff7eb7601bd314ea82d75a5e7fbcd3480d753117f4c5f9a7c7db-freebsd-native-bootloader-15.0-STABLE-git-materialized
|
||||
runtime_store=/frx/store/b6f9ebb5c844aedc3d4395ad08542eb67965a19fc28f68fb0f4b55f91aef1c8f-freebsd-native-runtime-15.0-STABLE-git-materialized
|
||||
materialized_source_store=/frx/store/bcbf7554ce43d40b1bdc98823eb450ce050529b34bbe2dae2a9df48688004c4b-freebsd-source-stable15-network-source
|
||||
materialized_source_root=/frx/store/bcbf7554ce43d40b1bdc98823eb450ce050529b34bbe2dae2a9df48688004c4b-freebsd-source-stable15-network-source/tree
|
||||
resolved_commit=332708a606f6bf0841c1d4a74c0d067f5640fe89
|
||||
source_driven_native_build=ok
|
||||
```
|
||||
|
||||
The harness also verified that:
|
||||
|
||||
- closure metadata records exactly one materialized source store
|
||||
- `metadata/freebsd-source-materializations.scm` exists
|
||||
- `metadata/store-layout.scm` records the materialized source store count/path
|
||||
- native build info files for kernel, bootloader, and runtime all record:
|
||||
- a `declared-source` block with `ref . "stable/15"`
|
||||
- a `materialized-source` block
|
||||
- the materialized source store path
|
||||
- the materialized source root
|
||||
- the resolved Git commit
|
||||
- native build info files do **not** use the unused declared source root as their actual `source-root`
|
||||
|
||||
### Regression checks
|
||||
|
||||
Re-ran:
|
||||
|
||||
- `PASS phase16-source-materialization`
|
||||
- `PASS phase16-declarative-source-build`
|
||||
|
||||
The local-tree declarative source build now also drives native base outputs from a materialized local source snapshot in `/frx/store`, while preserving the same declared source metadata interface.
|
||||
|
||||
## Result
|
||||
|
||||
Phase 16.3 is complete.
|
||||
|
||||
Fruix native FreeBSD base artifacts are now built from the declared source input's materialized store snapshot rather than directly from ambient `/usr/src`.
|
||||
|
||||
That means Fruix has crossed the key source boundary planned for Phase 16:
|
||||
|
||||
- source inputs can be declared
|
||||
- source inputs can be fetched/materialized
|
||||
- native base artifacts can now consume those materialized source inputs
|
||||
|
||||
The next logical phase is no longer source modeling itself, but what to do with that stronger source boundary:
|
||||
|
||||
- side-by-side source revisions
|
||||
- boot validation from distinct source revisions
|
||||
- and then installation/deployment ergonomics on top of those source-driven native builds
|
||||
256
docs/reports/phase16-source-materialization-freebsd.md
Normal file
256
docs/reports/phase16-source-materialization-freebsd.md
Normal file
@@ -0,0 +1,256 @@
|
||||
# Phase 16.2: materialize declarative FreeBSD source inputs under Fruix control
|
||||
|
||||
Date: 2026-04-03
|
||||
|
||||
## Goal
|
||||
|
||||
Phase 16.2 moves from merely *describing* FreeBSD source inputs to actually *materializing* them under Fruix control.
|
||||
|
||||
The objective in this subphase was not yet to switch the native base build path away from ambient `/usr/src`. Instead, it was to establish the missing fetch/materialization layer that later phases can consume.
|
||||
|
||||
That means Fruix now knows how to:
|
||||
|
||||
- fetch a Git-backed FreeBSD source declaration
|
||||
- download and verify a `src.txz` declaration
|
||||
- materialize the resulting source tree into `/frx/store`
|
||||
- cache downloaded source state under `/frx/var/cache/fruix/freebsd-source`
|
||||
- record stable source metadata for later native builds
|
||||
|
||||
## Implementation
|
||||
|
||||
### New source materializer in `modules/fruix/system/freebsd.scm`
|
||||
|
||||
Added:
|
||||
|
||||
- `materialize-freebsd-source`
|
||||
|
||||
and exported it for use by the Fruix CLI.
|
||||
|
||||
This materializer now supports all currently modeled source kinds:
|
||||
|
||||
- `local-tree`
|
||||
- `git`
|
||||
- `src-txz`
|
||||
|
||||
### Source artifacts are now first-class store objects
|
||||
|
||||
Materialized source outputs are now stored in paths of the form:
|
||||
|
||||
- `/frx/store/<hash>-freebsd-source-<name>`
|
||||
|
||||
Each source output contains:
|
||||
|
||||
- `tree/` or an auto-detected nested source root beneath it
|
||||
- `.fruix-source`
|
||||
- `.freebsd-source-info.scm`
|
||||
- `.references`
|
||||
|
||||
The source info file records at least:
|
||||
|
||||
- declared source
|
||||
- effective/resolved source
|
||||
- materialized store path
|
||||
- effective source root
|
||||
- source tree SHA256
|
||||
- cache path used to produce it
|
||||
|
||||
### Cache layout added under `/frx/var/cache/fruix/freebsd-source`
|
||||
|
||||
The new materializer caches downloaded source state under:
|
||||
|
||||
- `/frx/var/cache/fruix/freebsd-source/git/...`
|
||||
- `/frx/var/cache/fruix/freebsd-source/archives/...`
|
||||
|
||||
Current behavior:
|
||||
|
||||
- Git sources use a cached bare repository
|
||||
- `src.txz` sources use a cached archive file
|
||||
- repeated materialization of the same resolved source identity reuses the same store output path
|
||||
|
||||
### Git source handling
|
||||
|
||||
Git materialization now:
|
||||
|
||||
- uses `https://git.FreeBSD.org/src.git`
|
||||
- supports declarations by ref and/or commit
|
||||
- fetches the selected ref/commit into a cached bare repository
|
||||
- resolves refs to a concrete commit
|
||||
- exports that commit into a store materialization
|
||||
|
||||
This means Fruix can now represent moving refs declaratively while still recording the exact resolved commit used for a given materialized source tree.
|
||||
|
||||
### `src.txz` source handling
|
||||
|
||||
`src.txz` materialization now:
|
||||
|
||||
- downloads the declared archive URL with `fetch`
|
||||
- requires and verifies SHA256 for materialization
|
||||
- extracts the archive into a store materialization
|
||||
- records both the declared hash and the resulting source tree hash
|
||||
|
||||
For release archives, the canonical shorter URL form is now used in validation and documentation, for example:
|
||||
|
||||
- `https://download.freebsd.org/releases/amd64/15.0-RELEASE/src.txz`
|
||||
|
||||
rather than the longer doubled-architecture variant.
|
||||
|
||||
### Auto-detect the effective source root inside materialized trees
|
||||
|
||||
Git exports place the source tree directly at the materialized root.
|
||||
|
||||
Official `src.txz` archives instead unpack as:
|
||||
|
||||
- `usr/src/...`
|
||||
|
||||
inside the extracted directory tree.
|
||||
|
||||
To make this usable for later native builds, the materializer now auto-detects the effective source root and records it explicitly. For example:
|
||||
|
||||
- Git materialization root:
|
||||
- `/frx/store/...-freebsd-source-stable15-git-ref/tree`
|
||||
- `src.txz` materialization root:
|
||||
- `/frx/store/...-freebsd-source-release15-src-txz/tree/usr/src`
|
||||
|
||||
### New user-facing CLI command
|
||||
|
||||
Added a new user-facing command path in `scripts/fruix.scm`:
|
||||
|
||||
- `fruix source materialize SOURCE-FILE`
|
||||
|
||||
with options:
|
||||
|
||||
- `--source NAME`
|
||||
- `--store DIR`
|
||||
- `--cache DIR`
|
||||
- `--help`
|
||||
|
||||
The command emits machine-readable metadata including:
|
||||
|
||||
- declared source fields
|
||||
- materialized store path
|
||||
- effective source root
|
||||
- source tree hash
|
||||
- cache path
|
||||
- resolved Git commit if applicable
|
||||
- verified archive hash if applicable
|
||||
|
||||
This gives Phase 16.2 an operator-usable entry point rather than limiting it to internal Scheme helpers.
|
||||
|
||||
### Validation tightened for archive-backed sources
|
||||
|
||||
`src-txz` source validation now requires:
|
||||
|
||||
- URL
|
||||
- SHA256
|
||||
|
||||
This is the right reproducibility boundary for archive downloads.
|
||||
|
||||
## Guix comparison
|
||||
|
||||
This step continues to mirror the most useful Guix source boundary without copying it mechanically:
|
||||
|
||||
- Guix models source objects with `origin`
|
||||
- Git-backed origins use `git-reference`
|
||||
|
||||
Fruix's source materializer now plays a similar role for FreeBSD-specific source inputs:
|
||||
|
||||
- local tree snapshots
|
||||
- FreeBSD Git refs/commits
|
||||
- official `src.txz` archives
|
||||
|
||||
The key preserved idea is the same: source identity should become an explicit, recorded, materialized input rather than ambient host state.
|
||||
|
||||
## New files
|
||||
|
||||
Added:
|
||||
|
||||
- `tests/system/phase16-git-freebsd-source.scm.in`
|
||||
- `tests/system/phase16-txz-freebsd-source.scm.in`
|
||||
- `tests/system/run-phase16-source-materialization.sh`
|
||||
|
||||
## Validation
|
||||
|
||||
Passing run:
|
||||
|
||||
- `PASS phase16-source-materialization`
|
||||
- workdir:
|
||||
- `/tmp/fruix-phase16-source-materialization.QGuXi1`
|
||||
|
||||
### Git validation
|
||||
|
||||
Validated a Git declaration:
|
||||
|
||||
- name:
|
||||
- `stable15-git-ref`
|
||||
- URL:
|
||||
- `https://git.FreeBSD.org/src.git`
|
||||
- ref:
|
||||
- `stable/15`
|
||||
|
||||
Resolved/materialized result:
|
||||
|
||||
```text
|
||||
materialized_source_store=/frx/store/dd1cc6b5ffa95b4d0c0f269522d5739da05e0f4ae81b1b314221d28b49d1981f-freebsd-source-stable15-git-ref
|
||||
materialized_source_root=/frx/store/dd1cc6b5ffa95b4d0c0f269522d5739da05e0f4ae81b1b314221d28b49d1981f-freebsd-source-stable15-git-ref/tree
|
||||
materialized_source_tree_sha256=d0d8e085d913a511d7fa1ba410040eb697a4cef800f354a092c65249ab3c4eb4
|
||||
materialized_source_cache_path=/frx/var/cache/fruix/freebsd-source/git/9d432c47301c356bd2cede3400de40870e0b541b276888e34c68b882b9b894c7.git
|
||||
materialized_source_commit=332708a606f6bf0841c1d4a74c0d067f5640fe89
|
||||
```
|
||||
|
||||
The harness also confirmed:
|
||||
|
||||
- repeated materialization returned the same store path
|
||||
- the cached Git repository exists
|
||||
- the materialized tree contains:
|
||||
- `Makefile`
|
||||
- `sys/conf/newvers.sh`
|
||||
|
||||
### `src.txz` validation
|
||||
|
||||
Validated an archive declaration:
|
||||
|
||||
- name:
|
||||
- `release15-src-txz`
|
||||
- URL:
|
||||
- `https://download.freebsd.org/releases/amd64/15.0-RELEASE/src.txz`
|
||||
- SHA256:
|
||||
- `83c3e8157b6d7afcae57167fda75693bf1e5f581ca149a6ecb2d398b71bdfab0`
|
||||
|
||||
Resolved/materialized result:
|
||||
|
||||
```text
|
||||
materialized_source_store=/frx/store/2e7857fb2c067b32acb482d048b8d1c2eeffdecd213108b3b0a4b2a87d56bc68-freebsd-source-release15-src-txz
|
||||
materialized_source_root=/frx/store/2e7857fb2c067b32acb482d048b8d1c2eeffdecd213108b3b0a4b2a87d56bc68-freebsd-source-release15-src-txz/tree/usr/src
|
||||
materialized_source_tree_sha256=afbe26f2213a19685fc2c3b875d26fab67e2cfcd605716cc66f669dabeaf7572
|
||||
materialized_source_cache_path=/frx/var/cache/fruix/freebsd-source/archives/64ac7cc7d27435406995d63ef0b87ed0c485ce953ee8e9126127ca8f2a451d98-src.txz
|
||||
materialized_source_sha256=83c3e8157b6d7afcae57167fda75693bf1e5f581ca149a6ecb2d398b71bdfab0
|
||||
```
|
||||
|
||||
The harness also confirmed:
|
||||
|
||||
- repeated materialization returned the same store path
|
||||
- the cached archive exists
|
||||
- the effective materialized source root was detected as `tree/usr/src`
|
||||
- that root contains:
|
||||
- `Makefile`
|
||||
- `sys/conf/newvers.sh`
|
||||
|
||||
### Regression check
|
||||
|
||||
Also re-ran:
|
||||
|
||||
- `PASS phase16-declarative-source-build`
|
||||
|
||||
This confirmed the new source materialization work did not break the earlier Phase 16.1 declarative source model path.
|
||||
|
||||
## Result
|
||||
|
||||
Phase 16.2 is complete.
|
||||
|
||||
Fruix can now fetch or materialize declared FreeBSD source inputs into `/frx/store` with cache-backed provenance under `/frx/var/cache/fruix/freebsd-source`.
|
||||
|
||||
The next step is now clear and narrower:
|
||||
|
||||
- teach native FreeBSD kernel/world/runtime builds to consume these materialized source artifacts instead of ambient `/usr/src`
|
||||
|
||||
That will be the real handoff from source acquisition to source-driven native base builds.
|
||||
141
docs/reports/phase17-side-by-side-source-revisions-freebsd.md
Normal file
141
docs/reports/phase17-side-by-side-source-revisions-freebsd.md
Normal file
@@ -0,0 +1,141 @@
|
||||
# Phase 17.1: side-by-side FreeBSD source revisions in `/frx/store`
|
||||
|
||||
Date: 2026-04-03
|
||||
|
||||
## Goal
|
||||
|
||||
Phase 17.1 verifies that Fruix can treat FreeBSD source revisions the same way it already treats other declarative inputs:
|
||||
|
||||
- as explicit inputs,
|
||||
- as side-by-side store objects,
|
||||
- and as drivers of distinct native base outputs.
|
||||
|
||||
The key question was no longer whether Fruix could materialize a source tree at all. Phase 16 already established that.
|
||||
|
||||
The question here was stricter:
|
||||
|
||||
- can two distinct FreeBSD source inputs coexist,
|
||||
- can they both drive native base builds,
|
||||
- and can they do so even when the user-facing base version label is the same?
|
||||
|
||||
That last point matters because it proves the result is driven by source identity rather than by an arbitrary version-string rename.
|
||||
|
||||
## Implementation
|
||||
|
||||
Added two Phase 17 operating-system templates:
|
||||
|
||||
- `tests/system/phase17-git-source-operating-system.scm.in`
|
||||
- `tests/system/phase17-txz-source-operating-system.scm.in`
|
||||
|
||||
These model two distinct source identities:
|
||||
|
||||
- a Git source with both:
|
||||
- ref: `stable/15`
|
||||
- pinned commit: `332708a606f6bf0841c1d4a74c0d067f5640fe89`
|
||||
- an official release archive source:
|
||||
- `https://download.freebsd.org/releases/amd64/15.0-RELEASE/src.txz`
|
||||
- sha256:
|
||||
- `83c3e8157b6d7afcae57167fda75693bf1e5f581ca149a6ecb2d398b71bdfab0`
|
||||
|
||||
Added validation harness:
|
||||
|
||||
- `tests/system/run-phase17-source-coexistence.sh`
|
||||
|
||||
## Validation design
|
||||
|
||||
The harness builds three times:
|
||||
|
||||
1. Git-backed system build
|
||||
2. `src.txz`-backed system build
|
||||
3. Git-backed rebuild
|
||||
|
||||
The Git and `src.txz` systems intentionally share the same:
|
||||
|
||||
- base name:
|
||||
- `source-side-by-side`
|
||||
- base version label:
|
||||
- `15.0-source-side-by-side`
|
||||
|
||||
while differing only in their declared source/release metadata.
|
||||
|
||||
This means distinct outputs cannot be explained away by a version-label rename.
|
||||
|
||||
The harness verifies:
|
||||
|
||||
- Git rebuild stability when the Git source is pinned by commit
|
||||
- distinct closure paths for Git vs `src.txz`
|
||||
- distinct materialized source store paths
|
||||
- distinct native kernel/bootloader/runtime store paths
|
||||
- zero host-base stores in both builds
|
||||
- one materialized source store in each closure
|
||||
- correct closure metadata for:
|
||||
- declared source
|
||||
- materialized source
|
||||
- materialized source store count/path
|
||||
- correct native build info for:
|
||||
- kernel
|
||||
- runtime
|
||||
- effective source roots:
|
||||
- Git: `.../tree`
|
||||
- `src.txz`: `.../tree/usr/src`
|
||||
- the continued separation between:
|
||||
- declared transitional `source-root`
|
||||
- actual materialized source root used by the native build
|
||||
|
||||
## Results
|
||||
|
||||
Passing validation:
|
||||
|
||||
- `PASS phase17-source-coexistence`
|
||||
|
||||
Observed side-by-side closures:
|
||||
|
||||
```text
|
||||
git_closure=/frx/store/d6cbcc76f57fa9c392a80fe20e7499f7a837aab4fb96ea056e624cde95bc70c8-fruix-system-fruix-freebsd
|
||||
git_closure_rebuild=/frx/store/d6cbcc76f57fa9c392a80fe20e7499f7a837aab4fb96ea056e624cde95bc70c8-fruix-system-fruix-freebsd
|
||||
txz_closure=/frx/store/02268e19930facb32e12b6ec191f2e5704d1e81033baf3637a889ad15924ff88-fruix-system-fruix-freebsd
|
||||
base_version_label=15.0-source-side-by-side
|
||||
same_base_version_label_distinct_sources=ok
|
||||
```
|
||||
|
||||
Observed source identities:
|
||||
|
||||
```text
|
||||
git_source_kind=git
|
||||
git_source_ref=stable/15
|
||||
git_source_commit=332708a606f6bf0841c1d4a74c0d067f5640fe89
|
||||
git_materialized_source_store=/frx/store/c9928605fa906b90a600dafeebe5005dd18ad3b8e62b7111d9d13ad60ee56490-freebsd-source-stable15-side-a
|
||||
git_materialized_source_root=/frx/store/c9928605fa906b90a600dafeebe5005dd18ad3b8e62b7111d9d13ad60ee56490-freebsd-source-stable15-side-a/tree
|
||||
|
||||
txz_source_kind=src-txz
|
||||
txz_source_url=https://download.freebsd.org/releases/amd64/15.0-RELEASE/src.txz
|
||||
txz_source_sha256=83c3e8157b6d7afcae57167fda75693bf1e5f581ca149a6ecb2d398b71bdfab0
|
||||
txz_materialized_source_store=/frx/store/5eaeff5c6c55a95b6531d9cf2e1824cd4368d81c614608426bee1a5d2a664dc5-freebsd-source-release15-side-b
|
||||
txz_materialized_source_root=/frx/store/5eaeff5c6c55a95b6531d9cf2e1824cd4368d81c614608426bee1a5d2a664dc5-freebsd-source-release15-side-b/tree/usr/src
|
||||
```
|
||||
|
||||
Observed native base outputs for the same version label:
|
||||
|
||||
```text
|
||||
git_native_base_stores=/frx/store/4b615431ec25c500a3bf0ed70ce39e2ebf4f584994a53756268e4383962bc86b-freebsd-native-kernel-15.0-source-side-by-side,/frx/store/3a5a0b2b88b4757cf9cb4e3040f992d8fdb5bd9a7f1b186da983854cd95392c5-freebsd-native-bootloader-15.0-source-side-by-side,/frx/store/177f78e7f2932986a380187eb09dc34cc2cd9a146c5ed1fe1f00aae15ddf78d9-freebsd-native-runtime-15.0-source-side-by-side
|
||||
|
||||
txz_native_base_stores=/frx/store/0c5141a86fa9c1974102f2bd8766eb3ab787b97dcccb71f17d80aefbe8ed4f3e-freebsd-native-kernel-15.0-source-side-by-side,/frx/store/3de6592f50a735d8461662cb393fc413325ce24ded45d4bb494525896f8cb5eb-freebsd-native-bootloader-15.0-source-side-by-side,/frx/store/46d256305198ee7d745b9032c71085aba97d55fdf7a0d3d2017dd4455173205d-freebsd-native-runtime-15.0-source-side-by-side
|
||||
```
|
||||
|
||||
Those outputs differ only by content-address prefix, not by the human-readable version suffix. That is exactly the property Phase 17.1 needed.
|
||||
|
||||
## Key observation
|
||||
|
||||
A moving Git ref by itself is not a reproducibility boundary. For the reproducible half of this validation, the Git source was pinned by commit while still retaining its `stable/15` ref metadata.
|
||||
|
||||
This fits the intended Fruix model:
|
||||
|
||||
- refs are useful selectors,
|
||||
- commits are the stable Git identity boundary,
|
||||
- archive SHA256 values are the stable `src.txz` identity boundary.
|
||||
|
||||
## Result
|
||||
|
||||
Phase 17.1 is complete.
|
||||
|
||||
Fruix can now hold at least two distinct FreeBSD source revisions side by side in `/frx/store` and build distinct native FreeBSD base artifacts from them, even when the visible base version label is kept the same.
|
||||
77
docs/reports/phase17-source-policy-freebsd.md
Normal file
77
docs/reports/phase17-source-policy-freebsd.md
Normal file
@@ -0,0 +1,77 @@
|
||||
# Phase 17.3: clarify FreeBSD source provenance, caching, and update policy
|
||||
|
||||
Date: 2026-04-03
|
||||
|
||||
## Goal
|
||||
|
||||
Phase 17.3 turns the source behavior implemented in Phases 16 through 17.2 into an explicit repo-level policy.
|
||||
|
||||
The main question was no longer whether Fruix *can* fetch and boot from declared FreeBSD sources. It can.
|
||||
|
||||
The question here is:
|
||||
|
||||
- how should operators think about source selectors versus stable source identity?
|
||||
- what exactly lives in cache versus store?
|
||||
- when should native outputs be invalidated?
|
||||
- what is the intended policy for moving Git refs, pinned commits, and archive hashes?
|
||||
|
||||
## Added documentation
|
||||
|
||||
Added:
|
||||
|
||||
- `docs/freebsd-source-policy.md`
|
||||
|
||||
This document explains:
|
||||
|
||||
- supported source kinds:
|
||||
- `local-tree`
|
||||
- `git`
|
||||
- `src-txz`
|
||||
- declared source vs effective source
|
||||
- cache locations under:
|
||||
- `/frx/var/cache/fruix/freebsd-source`
|
||||
- materialized source outputs under:
|
||||
- `/frx/store/*-freebsd-source-*`
|
||||
- effective identity rules for:
|
||||
- local-tree snapshots
|
||||
- Git refs/commits
|
||||
- verified `src.txz` archives
|
||||
- effective source root detection rules:
|
||||
- `tree`
|
||||
- `tree/usr/src`
|
||||
- native build invalidation policy
|
||||
- closure provenance policy
|
||||
- update policy for moving refs vs pinned commits
|
||||
- the current no-hidden-patch-layer rule
|
||||
|
||||
## Key policy conclusions
|
||||
|
||||
The repo now states clearly that:
|
||||
|
||||
- Git refs are selectors, not stable reproducibility boundaries
|
||||
- resolved Git commits are the effective Git identity boundary
|
||||
- `src.txz` URLs are not enough by themselves; `sha256` is required
|
||||
- local trees are mutable selectors; the materialized snapshot and filtered tree hash are the meaningful Fruix identity
|
||||
- native FreeBSD base outputs must invalidate when the materialized source identity changes, even if the visible base version label does not
|
||||
- cache objects are transport optimizations, not the final identity boundary
|
||||
|
||||
## Relation to validated behavior
|
||||
|
||||
The new policy document matches the validated Phase 17 behavior:
|
||||
|
||||
- Phase 17.1 proved that distinct source identities can coexist side by side and produce different native outputs for the same visible base version label
|
||||
- Phase 17.2 proved that systems built from those distinct source identities can boot successfully through the validated native path
|
||||
|
||||
## Result
|
||||
|
||||
Phase 17.3 is complete.
|
||||
|
||||
The repo now clearly explains how FreeBSD source objects are:
|
||||
|
||||
- fetched
|
||||
- cached
|
||||
- identified
|
||||
- invalidated
|
||||
- and consumed by native FreeBSD base builds
|
||||
|
||||
That completes Phase 17 and leaves Fruix in a better position to begin the installation/deployment work in Phase 18.
|
||||
98
docs/reports/phase17-source-revision-boots-freebsd.md
Normal file
98
docs/reports/phase17-source-revision-boots-freebsd.md
Normal file
@@ -0,0 +1,98 @@
|
||||
# Phase 17.2: boot systems from distinct declared FreeBSD source revisions
|
||||
|
||||
Date: 2026-04-03
|
||||
|
||||
## Goal
|
||||
|
||||
Phase 17.2 extends Phase 17.1 from:
|
||||
|
||||
- side-by-side source-driven builds
|
||||
|
||||
to:
|
||||
|
||||
- side-by-side source-driven **boots**.
|
||||
|
||||
The important requirement was not visible runtime behavior differences between the guests. The requirement was that Fruix should be able to:
|
||||
|
||||
- build bootable systems from at least two distinct declared source revisions
|
||||
- boot both systems with the validated native base path
|
||||
- preserve source identity in the resulting system/image metadata
|
||||
|
||||
## Implementation
|
||||
|
||||
Added boot validation harness:
|
||||
|
||||
- `tests/system/run-phase17-source-revisions-qemu.sh`
|
||||
|
||||
This script renders the Phase 17 source templates from Phase 17.1 and then boots two systems under the already-validated QEMU/UEFI/TCG path:
|
||||
|
||||
- Git-backed source:
|
||||
- ref: `stable/15`
|
||||
- pinned commit: `332708a606f6bf0841c1d4a74c0d067f5640fe89`
|
||||
- release archive source:
|
||||
- `https://download.freebsd.org/releases/amd64/15.0-RELEASE/src.txz`
|
||||
- sha256:
|
||||
- `83c3e8157b6d7afcae57167fda75693bf1e5f581ca149a6ecb2d398b71bdfab0`
|
||||
|
||||
The harness reuses:
|
||||
|
||||
- `tests/system/run-phase11-shepherd-pid1-qemu.sh`
|
||||
|
||||
and checks both guest runtime behavior and image/build provenance metadata.
|
||||
|
||||
## Validation
|
||||
|
||||
Passing run:
|
||||
|
||||
- `PASS phase17-source-revisions-qemu`
|
||||
|
||||
Confirmed booted systems from two distinct source identities:
|
||||
|
||||
```text
|
||||
git_closure=/frx/store/d6cbcc76f57fa9c392a80fe20e7499f7a837aab4fb96ea056e624cde95bc70c8-fruix-system-fruix-freebsd
|
||||
txz_closure=/frx/store/02268e19930facb32e12b6ec191f2e5704d1e81033baf3637a889ad15924ff88-fruix-system-fruix-freebsd
|
||||
```
|
||||
|
||||
Confirmed source metadata recorded in image/build artifacts:
|
||||
|
||||
```text
|
||||
git_source_kind=git
|
||||
git_source_ref=stable/15
|
||||
git_source_commit=332708a606f6bf0841c1d4a74c0d067f5640fe89
|
||||
git_materialized_source_store=/frx/store/c9928605fa906b90a600dafeebe5005dd18ad3b8e62b7111d9d13ad60ee56490-freebsd-source-stable15-side-a
|
||||
|
||||
txz_source_kind=src-txz
|
||||
txz_source_url=https://download.freebsd.org/releases/amd64/15.0-RELEASE/src.txz
|
||||
txz_source_sha256=83c3e8157b6d7afcae57167fda75693bf1e5f581ca149a6ecb2d398b71bdfab0
|
||||
txz_materialized_source_store=/frx/store/5eaeff5c6c55a95b6531d9cf2e1824cd4368d81c614608426bee1a5d2a664dc5-freebsd-source-release15-side-b
|
||||
```
|
||||
|
||||
Confirmed distinct native base outputs used by the two boots:
|
||||
|
||||
```text
|
||||
git_native_base_stores=/frx/store/4b615431ec25c500a3bf0ed70ce39e2ebf4f584994a53756268e4383962bc86b-freebsd-native-kernel-15.0-source-side-by-side,/frx/store/3a5a0b2b88b4757cf9cb4e3040f992d8fdb5bd9a7f1b186da983854cd95392c5-freebsd-native-bootloader-15.0-source-side-by-side,/frx/store/177f78e7f2932986a380187eb09dc34cc2cd9a146c5ed1fe1f00aae15ddf78d9-freebsd-native-runtime-15.0-source-side-by-side
|
||||
|
||||
txz_native_base_stores=/frx/store/0c5141a86fa9c1974102f2bd8766eb3ab787b97dcccb71f17d80aefbe8ed4f3e-freebsd-native-kernel-15.0-source-side-by-side,/frx/store/3de6592f50a735d8461662cb393fc413325ce24ded45d4bb494525896f8cb5eb-freebsd-native-bootloader-15.0-source-side-by-side,/frx/store/46d256305198ee7d745b9032c71085aba97d55fdf7a0d3d2017dd4455173205d-freebsd-native-runtime-15.0-source-side-by-side
|
||||
```
|
||||
|
||||
Confirmed both guests booted successfully through the validated PID 1 path:
|
||||
|
||||
- Shepherd ran as PID 1 in both boots
|
||||
- `sshd` was running in both boots
|
||||
- boot backend:
|
||||
- `qemu-uefi-tcg`
|
||||
|
||||
Validation artifacts:
|
||||
|
||||
- Git serial log:
|
||||
- `/tmp/fruix-phase17-source-qemu.7Za50q/git/serial.log`
|
||||
- `src.txz` serial log:
|
||||
- `/tmp/fruix-phase17-source-qemu.7Za50q/txz/serial.log`
|
||||
|
||||
## Result
|
||||
|
||||
Phase 17.2 is complete.
|
||||
|
||||
Fruix now boots systems built from at least two distinct declared FreeBSD source revisions while preserving those source identities in system/image metadata.
|
||||
|
||||
That means Phase 17 is no longer just about build-time coexistence. The validated native boot path now also works across distinct source identities.
|
||||
191
docs/reports/phase18-installer-environment-freebsd.md
Normal file
191
docs/reports/phase18-installer-environment-freebsd.md
Normal file
@@ -0,0 +1,191 @@
|
||||
# Phase 18.2: minimal Fruix-managed installer environment on FreeBSD
|
||||
|
||||
Date: 2026-04-04
|
||||
|
||||
## Goal
|
||||
|
||||
Phase 18.2 builds on the Phase 18.1 host-driven install primitive.
|
||||
|
||||
The goal here is not a polished live installer. The goal is a small Fruix-managed environment that can:
|
||||
|
||||
- boot as its own Fruix system,
|
||||
- carry a selected target Fruix system closure and rootfs payload,
|
||||
- install that target system onto a second disk from inside the booted environment,
|
||||
- and leave the installed target bootable.
|
||||
|
||||
## Implementation
|
||||
|
||||
### New installer-environment API
|
||||
|
||||
Added in `modules/fruix/system/freebsd.scm`:
|
||||
|
||||
- `installer-operating-system`
|
||||
- `operating-system-installer-image-spec`
|
||||
- `materialize-installer-image`
|
||||
|
||||
The installer environment is derived from the selected target operating system, but with installer-specific behavior:
|
||||
|
||||
- host name defaults to:
|
||||
- `<target-host-name>-installer`
|
||||
- init mode is kept on the currently most stable installer path:
|
||||
- `freebsd-init+rc.d-shepherd`
|
||||
- the installer image root label is distinct:
|
||||
- `fruix-installer-root`
|
||||
- `sshd` is enabled for operator/debug access
|
||||
- installer accounts needed for SSH/DHCP are ensured if absent:
|
||||
- `sshd`
|
||||
- `_dhcp`
|
||||
|
||||
### Bootable installer image contents
|
||||
|
||||
`materialize-installer-image` now produces a bootable image that contains:
|
||||
|
||||
- the installer system closure and its runtime store closure
|
||||
- the selected target system closure
|
||||
- the selected target system's referenced store items
|
||||
- a prebuilt target rootfs tree staged under:
|
||||
- `/var/lib/fruix/installer/target-rootfs`
|
||||
- installer plan/state files under:
|
||||
- `/var/lib/fruix/installer`
|
||||
- installer helper scripts:
|
||||
- `/usr/local/libexec/fruix-installer-run`
|
||||
- `/usr/local/etc/rc.d/fruix-installer`
|
||||
|
||||
The booted installer environment runs a background rc.d job that:
|
||||
|
||||
- partitions the selected target disk
|
||||
- creates EFI + UFS filesystems
|
||||
- copies the staged target rootfs onto the target
|
||||
- copies only the target system's required store items into the target `/frx/store`
|
||||
- installs the target's `loader.efi`
|
||||
- writes `/var/lib/fruix/install.scm` on the target
|
||||
- records installer state in:
|
||||
- `/var/lib/fruix/installer/state`
|
||||
- logs to:
|
||||
- `/var/log/fruix-installer.log`
|
||||
|
||||
### New CLI action
|
||||
|
||||
Added in `scripts/fruix.scm`:
|
||||
|
||||
- `fruix system installer`
|
||||
|
||||
Added option:
|
||||
|
||||
- `--install-target-device DEVICE`
|
||||
|
||||
This action materializes a bootable installer image in `/frx/store` and emits metadata for:
|
||||
|
||||
- installer image paths
|
||||
- installer closure path
|
||||
- target closure path
|
||||
- target install device
|
||||
- installer state/log paths
|
||||
- declared/materialized FreeBSD source metadata
|
||||
- target/native/runtime store metadata
|
||||
|
||||
### FreeBSD virtio target-device detail
|
||||
|
||||
A practical detail surfaced during validation:
|
||||
|
||||
- the correct FreeBSD virtio block device node for the second QEMU disk is:
|
||||
- `/dev/vtbd1`
|
||||
|
||||
The earlier Linux-flavored guess:
|
||||
|
||||
- `/dev/vtblk1`
|
||||
|
||||
was wrong for the actual FreeBSD device node namespace in this environment.
|
||||
|
||||
The installer defaults were updated accordingly.
|
||||
|
||||
### Small image-builder correctness fix
|
||||
|
||||
While doing this work I also fixed `materialize-bhyve-image` so its generated UFS filesystem label respects the requested:
|
||||
|
||||
- `root-partition-label`
|
||||
|
||||
instead of always hardcoding:
|
||||
|
||||
- `fruix-root`
|
||||
|
||||
This matters for the installer image because it needs a distinct root label while the target disk still uses the normal target label.
|
||||
|
||||
## Validation
|
||||
|
||||
Added validation artifacts:
|
||||
|
||||
- `tests/system/phase18-installer-target-operating-system.scm.in`
|
||||
- `tests/system/run-phase18-installer-environment.sh`
|
||||
|
||||
Passing validations:
|
||||
|
||||
- `PASS phase18-installer-environment`
|
||||
- regression re-check:
|
||||
- `PASS phase18-system-install`
|
||||
- regression re-check:
|
||||
- `PASS phase17-source-revisions-qemu`
|
||||
|
||||
Validated installer-environment result:
|
||||
|
||||
```text
|
||||
installer_image_store_path=/frx/store/fb038dbf5dac2ad1bb767a264d3a268915f489b936dc5dd32425645102d3da48-fruix-installer-image-fruix-freebsd-installer
|
||||
installer_disk_image=/frx/store/fb038dbf5dac2ad1bb767a264d3a268915f489b936dc5dd32425645102d3da48-fruix-installer-image-fruix-freebsd-installer/disk.img
|
||||
installer_disk_capacity=16g
|
||||
installer_root_size=14g
|
||||
target_disk_capacity=12g
|
||||
install_target_device=/dev/vtbd1
|
||||
installer_closure_path=/frx/store/ea821f20b579684877fdc86a2a1e80485cf2b12d9d32f74f42e368d738c2ad4d-fruix-system-fruix-freebsd-installer
|
||||
target_closure_path=/frx/store/7ee225db532b6973e385f8507d2d61aec3cd3aeb0864f983c2ae4b6e149ef3b0-fruix-system-fruix-freebsd
|
||||
freebsd_source_kind=git
|
||||
freebsd_source_ref=stable/15
|
||||
freebsd_source_commit=332708a606f6bf0841c1d4a74c0d067f5640fe89
|
||||
materialized_source_store=/frx/store/7563df2714ae7fa9bd40b83c74512ffe2cb2ad91b297915591b55c76edbb2fcb-freebsd-source-stable15-installer-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_environment_boot=ok
|
||||
installer_environment_install=ok
|
||||
installed_target_boot=ok
|
||||
```
|
||||
|
||||
The harness verified all of the following:
|
||||
|
||||
1. `fruix system installer` produces a bootable installer image in `/frx/store`
|
||||
2. validation boots a workdir copy of that installer disk image so the store artifact itself is not mutated during the boot/install run
|
||||
3. the installer environment boots successfully under QEMU/UEFI/TCG
|
||||
4. the installer environment becomes reachable over SSH
|
||||
5. `/run/current-system` inside the installer environment points at the installer closure
|
||||
6. the installer rc.d job reaches:
|
||||
- `state=done`
|
||||
7. the installer log records:
|
||||
- `fruix-installer:done`
|
||||
8. the target raw disk is transformed into a valid GPT-installed Fruix target with:
|
||||
- EFI filesystem: `msdosfs`
|
||||
- root filesystem: `ufs`
|
||||
- `EFI/BOOT/BOOTX64.EFI` present
|
||||
- `/var/lib/fruix/install.scm` present
|
||||
9. the installed target then boots successfully as its own Fruix system under QEMU/UEFI/TCG
|
||||
10. after target boot:
|
||||
- `/run/current-system` points at the target closure
|
||||
- `/usr/local/etc/rc.d/fruix-shepherd onestatus` reports running
|
||||
- `sshd` is running
|
||||
- activation completed successfully
|
||||
|
||||
## Result
|
||||
|
||||
Phase 18.2 is complete.
|
||||
|
||||
Fruix now has a real installer substrate on FreeBSD:
|
||||
|
||||
- a bootable Fruix-managed installer image
|
||||
- a target closure bundled inside that installer environment
|
||||
- in-guest non-interactive installation onto a second disk
|
||||
- validated boot of the installed result
|
||||
|
||||
The next step is Phase 18.3:
|
||||
|
||||
- produce a bootable installer ISO for UEFI systems, rather than only a disk-image-style installer environment.
|
||||
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
|
||||
167
docs/reports/phase18-minimal-installation-flow-freebsd.md
Normal file
167
docs/reports/phase18-minimal-installation-flow-freebsd.md
Normal file
@@ -0,0 +1,167 @@
|
||||
# Phase 18.1: minimal non-interactive Fruix installation flow on FreeBSD
|
||||
|
||||
Date: 2026-04-03
|
||||
|
||||
## Goal
|
||||
|
||||
Phase 18.1 turns Fruix's existing closure/rootfs/image machinery into a real installation workflow.
|
||||
|
||||
The goal is not a polished installer yet. The goal is a repeatable, non-interactive install path that can:
|
||||
|
||||
- take a declarative Fruix system,
|
||||
- partition and format a target disk or image,
|
||||
- populate it with the selected system closure,
|
||||
- install boot assets,
|
||||
- and leave the target bootable.
|
||||
|
||||
## Implementation
|
||||
|
||||
### New install spec and installer entry point
|
||||
|
||||
Added in `modules/fruix/system/freebsd.scm`:
|
||||
|
||||
- `operating-system-install-spec`
|
||||
- `install-operating-system`
|
||||
|
||||
The installer currently supports:
|
||||
|
||||
- raw image-file targets
|
||||
- `/dev/...` block-device targets
|
||||
|
||||
For raw image-file targets, Fruix now:
|
||||
|
||||
- creates/truncates the target image
|
||||
- attaches it with `mdconfig`
|
||||
- creates a GPT layout
|
||||
- adds:
|
||||
- an EFI partition
|
||||
- a FreeBSD UFS root partition
|
||||
- formats them with:
|
||||
- `newfs_msdos`
|
||||
- `newfs`
|
||||
- mounts them
|
||||
- stages the declarative Fruix rootfs
|
||||
- copies the closure and referenced `/frx/store` items into the installed root
|
||||
- installs `loader.efi` to `EFI/BOOT/BOOTX64.EFI`
|
||||
- writes install metadata to:
|
||||
- `/var/lib/fruix/install.scm`
|
||||
|
||||
### Rootfs staging was factored for reuse
|
||||
|
||||
Added internal helper:
|
||||
|
||||
- `populate-rootfs-from-closure`
|
||||
|
||||
This lets image generation and installation reuse the same rootfs staging logic while differing in how the final target is created.
|
||||
|
||||
### New CLI action
|
||||
|
||||
Added user-facing command support in `scripts/fruix.scm`:
|
||||
|
||||
- `fruix system install`
|
||||
|
||||
New system option:
|
||||
|
||||
- `--target PATH`
|
||||
|
||||
Install metadata now emits machine-readable fields including:
|
||||
|
||||
- `target`
|
||||
- `target_kind`
|
||||
- `target_device`
|
||||
- `esp_device`
|
||||
- `root_device`
|
||||
- `install_metadata_path`
|
||||
- `disk_capacity`
|
||||
- `root_size`
|
||||
- declared/materialized FreeBSD source metadata
|
||||
- closure/native/runtime store metadata
|
||||
|
||||
### Validation harnesses
|
||||
|
||||
Added:
|
||||
|
||||
- `tests/system/phase18-install-operating-system.scm.in`
|
||||
- `tests/system/run-phase18-system-install.sh`
|
||||
|
||||
The Phase 18 install validation uses the already-validated boot mode:
|
||||
|
||||
- `freebsd-init+rc.d-shepherd`
|
||||
|
||||
This keeps the install-flow validation focused on installation mechanics rather than on the separate Shepherd-as-PID-1 boot path.
|
||||
|
||||
## Validation
|
||||
|
||||
Passing validation:
|
||||
|
||||
- `PASS phase18-system-install`
|
||||
- regression re-check:
|
||||
- `PASS phase17-source-revisions-qemu`
|
||||
|
||||
Validated install result:
|
||||
|
||||
```text
|
||||
target_image=/tmp/fruix-phase18-install.CyrgKc/installed.img
|
||||
target_kind=raw-file
|
||||
disk_capacity=12g
|
||||
root_size=10g
|
||||
closure_path=/frx/store/ee486985797103aa5d3eeeef7f2cf066bcbd6839cd81083dbe626a594e71a703-fruix-system-fruix-freebsd
|
||||
freebsd_source_kind=git
|
||||
freebsd_source_ref=stable/15
|
||||
freebsd_source_commit=332708a606f6bf0841c1d4a74c0d067f5640fe89
|
||||
materialized_source_store=/frx/store/a892afb425235de71c9da38884e2ebdba5dafd3a1993f432fe7c446f5af2151f-freebsd-source-stable15-install-source
|
||||
native_base_store_count=3
|
||||
install_metadata_path=/var/lib/fruix/install.scm
|
||||
esp_fstype=msdosfs
|
||||
root_fstype=ufs
|
||||
shepherd_status=running
|
||||
sshd_status=running
|
||||
install_flow=non_interactive
|
||||
init_mode=freebsd-init+rc.d-shepherd
|
||||
install_target_boot=ok
|
||||
```
|
||||
|
||||
The harness verified all of the following:
|
||||
|
||||
- GPT partitioning is created on the target image
|
||||
- the installed ESP is a valid `msdosfs` filesystem
|
||||
- the installed root partition is `ufs`
|
||||
- `EFI/BOOT/BOOTX64.EFI` exists on the target
|
||||
- `/run/current-system` points at the installed closure in `/frx/store`
|
||||
- the installed closure exists under the target's `/frx/store`
|
||||
- `/var/lib/fruix/install.scm` exists and records:
|
||||
- closure path
|
||||
- store items
|
||||
- install spec
|
||||
- materialized source provenance via the referenced closure/store items
|
||||
- the installed system boots under local QEMU/UEFI/TCG
|
||||
- after boot:
|
||||
- `sshd` is running
|
||||
- `/usr/local/etc/rc.d/fruix-shepherd onestatus` reports running
|
||||
- activation completed successfully
|
||||
|
||||
## QEMU SMP note
|
||||
|
||||
I tested the idea of defaulting local QEMU validation to 8 vCPUs.
|
||||
|
||||
Result:
|
||||
|
||||
- for local `qemu-system-x86_64` under **TCG**, higher SMP did not help this validation path and in practice regressed boot responsiveness
|
||||
- the harnesses therefore keep `QEMU_SMP` configurable but retain a conservative default of `2` for TCG-based local validation
|
||||
|
||||
This keeps the validated path reliable while still allowing manual override when useful.
|
||||
|
||||
## Result
|
||||
|
||||
Phase 18.1 is complete.
|
||||
|
||||
Fruix now has a real, repeatable, non-interactive installation workflow for FreeBSD systems:
|
||||
|
||||
- declarative system input
|
||||
- native/source-driven closure output
|
||||
- partition/format/install to a target image or disk
|
||||
- bootable installed result
|
||||
|
||||
The next step is Phase 18.2:
|
||||
|
||||
- a minimal Fruix-managed installer environment that can boot into an install context and run this workflow from within that environment.
|
||||
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.
|
||||
86
docs/reports/phase7-operating-system-model-freebsd.md
Normal file
86
docs/reports/phase7-operating-system-model-freebsd.md
Normal file
@@ -0,0 +1,86 @@
|
||||
# Phase 7.1: Minimal Fruix operating-system model for FreeBSD
|
||||
|
||||
Date: 2026-04-01
|
||||
|
||||
## Summary
|
||||
|
||||
This step introduces the first declarative Fruix operating-system model for the FreeBSD track. The goal is not full upstream Guix System integration yet, but a coherent Fruix-owned system description that can drive later closure and rootfs generation work.
|
||||
|
||||
Added files:
|
||||
|
||||
- `modules/fruix/system/freebsd.scm`
|
||||
- `tests/system/phase7-minimal-operating-system.scm`
|
||||
- `tests/system/validate-phase7-operating-system.scm`
|
||||
- `tests/system/run-phase7-operating-system-model.sh`
|
||||
|
||||
Updated file:
|
||||
|
||||
- `modules/fruix/packages/freebsd.scm`
|
||||
|
||||
## Validation command
|
||||
|
||||
Run command:
|
||||
|
||||
```sh
|
||||
METADATA_OUT=/tmp/phase7-os-model-metadata.txt \
|
||||
./tests/system/run-phase7-operating-system-model.sh
|
||||
```
|
||||
|
||||
## What the model describes
|
||||
|
||||
The new module defines records and constructors for:
|
||||
|
||||
- users
|
||||
- groups
|
||||
- file systems
|
||||
- a FreeBSD-oriented operating-system object
|
||||
|
||||
The minimal system description currently covers:
|
||||
|
||||
- host identity
|
||||
- kernel package
|
||||
- bootloader package
|
||||
- base packages
|
||||
- users/groups
|
||||
- file-system declarations
|
||||
- generated `/etc` files
|
||||
- a generated activation script
|
||||
- a generated Shepherd configuration payload
|
||||
- a declared init mode of:
|
||||
- `freebsd-init+rc.d-shepherd`
|
||||
|
||||
## Observed results
|
||||
|
||||
Observed metadata included:
|
||||
|
||||
- `host_name=fruix-freebsd`
|
||||
- `kernel_package=freebsd-kernel`
|
||||
- `bootloader_package=freebsd-bootloader`
|
||||
- `base_packages=freebsd-runtime,freebsd-userland,freebsd-libc,freebsd-rc-scripts,freebsd-sh,freebsd-bash`
|
||||
- `users=root,operator`
|
||||
- `groups=wheel,operator`
|
||||
- `file_system_count=3`
|
||||
- `services=shepherd,ready-marker`
|
||||
- `generated_files=boot/loader.conf,etc/rc.conf,etc/fstab,etc/hosts,etc/passwd,etc/group,etc/shells,etc/motd,activate,shepherd/init.scm`
|
||||
- `init_mode=freebsd-init+rc.d-shepherd`
|
||||
- `ready_marker=/var/lib/fruix/ready`
|
||||
|
||||
## Important findings
|
||||
|
||||
- the FreeBSD track now has a concrete declarative system object rather than only ad hoc package lists and shell prototypes
|
||||
- the model is explicitly Fruix-owned and FreeBSD-oriented; it does not attempt a premature blanket rename or premature full Guix System integration
|
||||
- the package layer was extended with additional system-oriented prototype packages needed by later Phase 7 work, including:
|
||||
- `freebsd-bootloader`
|
||||
- `freebsd-rc-scripts`
|
||||
- `freebsd-runtime`
|
||||
- `%freebsd-system-packages`
|
||||
- the chosen first init strategy is now explicit in the model:
|
||||
- FreeBSD init with declaratively generated `rc.d` launch of Shepherd
|
||||
|
||||
## Conclusion
|
||||
|
||||
Phase 7.1 is satisfied on the current FreeBSD prototype track:
|
||||
|
||||
- a minimal Fruix operating-system model now exists for FreeBSD
|
||||
- that model evaluates into a coherent system-closure specification
|
||||
- the next step is to materialize that specification into a real system closure under `/frx/store`
|
||||
78
docs/reports/phase7-rootfs-freebsd.md
Normal file
78
docs/reports/phase7-rootfs-freebsd.md
Normal file
@@ -0,0 +1,78 @@
|
||||
# Phase 7.3: Installable FreeBSD rootfs tree materialized from the Fruix system closure
|
||||
|
||||
Date: 2026-04-01
|
||||
|
||||
## Summary
|
||||
|
||||
This step takes the Phase 7.2 system closure and materializes a root filesystem tree suitable for later image-construction work.
|
||||
|
||||
Added files:
|
||||
|
||||
- `tests/system/materialize-phase7-rootfs.scm`
|
||||
- `tests/system/run-phase7-rootfs.sh`
|
||||
|
||||
## Validation command
|
||||
|
||||
Run command:
|
||||
|
||||
```sh
|
||||
METADATA_OUT=/tmp/phase7-rootfs-metadata.txt \
|
||||
./tests/system/run-phase7-rootfs.sh
|
||||
```
|
||||
|
||||
## What the harness does
|
||||
|
||||
The harness:
|
||||
|
||||
1. ensures the local Guile/Fibers/Shepherd runtime is available
|
||||
2. reuses the declarative Phase 7 operating-system model
|
||||
3. materializes the referenced system closure under `/frx/store`
|
||||
4. creates a root filesystem tree that points at that closure through:
|
||||
- `/run/current-system`
|
||||
- boot symlinks
|
||||
- `/bin`, `/sbin`, `/lib`, and `/usr/*` links into the system profile
|
||||
- generated `/etc` links into the system closure
|
||||
- generated Shepherd `rc.d` launch integration
|
||||
5. performs static validation of the generated rootfs structure
|
||||
|
||||
## Observed results
|
||||
|
||||
Observed metadata included:
|
||||
|
||||
- `rootfs=/tmp/.../rootfs`
|
||||
- `closure_path=/frx/store/...-fruix-system-fruix-freebsd`
|
||||
- `run_current_system_target=/frx/store/...-fruix-system-fruix-freebsd`
|
||||
- `activate_target=/run/current-system/activate`
|
||||
- `bin_target=/run/current-system/profile/bin`
|
||||
- `sbin_target=/run/current-system/profile/sbin`
|
||||
- `boot_kernel_target=/run/current-system/boot/kernel`
|
||||
- `boot_loader_target=/run/current-system/boot/loader`
|
||||
- `boot_loader_efi_target=/run/current-system/boot/loader.efi`
|
||||
- `rc_conf_target=/run/current-system/etc/rc.conf`
|
||||
- `rc_script_target=/run/current-system/usr/local/etc/rc.d/fruix-shepherd`
|
||||
- `ready_marker=/var/lib/fruix/ready`
|
||||
- `validation_mode=static-rootfs-check`
|
||||
- `ready_state_mode=freebsd-init+rc.d-shepherd`
|
||||
|
||||
## Important findings
|
||||
|
||||
- the current FreeBSD Fruix track now has a concrete rootfs tree derived from the declarative system model and closure rather than only a closure directory in the store
|
||||
- the rootfs uses a Guix-like `/run/current-system` anchor so that generated configuration and system profile content remain tied back to the declarative closure
|
||||
- static validation confirmed:
|
||||
- boot asset linkage
|
||||
- generated `/etc` linkage
|
||||
- activation payload presence
|
||||
- Shepherd launch integration
|
||||
- declared filesystem content
|
||||
- declared user/group provisioning in the activation path
|
||||
- the deterministic ready-marker path for the first boot target
|
||||
- the chosen ready state for the first integrated FreeBSD system remains:
|
||||
- FreeBSD init + `rc.d` + Shepherd-managed ready marker
|
||||
|
||||
## Conclusion
|
||||
|
||||
Phase 7.3 is satisfied on the current FreeBSD prototype track:
|
||||
|
||||
- a root filesystem tree can now be materialized from the declarative Fruix system closure
|
||||
- the rootfs is internally coherent enough for the next image-construction phase
|
||||
- Phase 7 as a whole is now complete on the active FreeBSD amd64 prototype path
|
||||
74
docs/reports/phase7-system-closure-freebsd.md
Normal file
74
docs/reports/phase7-system-closure-freebsd.md
Normal file
@@ -0,0 +1,74 @@
|
||||
# Phase 7.2: Minimal FreeBSD system closure generated from the Fruix system model
|
||||
|
||||
Date: 2026-04-01
|
||||
|
||||
## Summary
|
||||
|
||||
This step materializes the Phase 7.1 Fruix operating-system description into a reproducible system closure under `/frx/store`.
|
||||
|
||||
Added files:
|
||||
|
||||
- `tests/system/materialize-phase7-system-closure.scm`
|
||||
- `tests/system/run-phase7-system-closure.sh`
|
||||
|
||||
## Validation command
|
||||
|
||||
Run command:
|
||||
|
||||
```sh
|
||||
METADATA_OUT=/tmp/phase7-system-closure-metadata.txt \
|
||||
./tests/system/run-phase7-system-closure.sh
|
||||
```
|
||||
|
||||
## What the harness does
|
||||
|
||||
The harness:
|
||||
|
||||
1. ensures the local Guile/Fibers/Shepherd runtime needed for the generated system is available
|
||||
2. loads the declarative Phase 7 operating-system object
|
||||
3. materializes the referenced FreeBSD base packages into `/frx/store`
|
||||
4. materializes local Guile and Shepherd prefixes into `/frx/store`
|
||||
5. generates a Fruix system closure store item containing:
|
||||
- boot assets
|
||||
- a merged system profile
|
||||
- generated `/etc` files
|
||||
- an activation script
|
||||
- a Shepherd configuration
|
||||
- a generated FreeBSD `rc.d` launcher for Shepherd
|
||||
6. reruns the same materialization a second time and verifies that the same closure path is produced
|
||||
|
||||
## Observed results
|
||||
|
||||
Observed metadata included:
|
||||
|
||||
- `closure_path=/frx/store/...-fruix-system-fruix-freebsd`
|
||||
- `closure_rebuild_path=/frx/store/...-fruix-system-fruix-freebsd`
|
||||
- `kernel_store=/frx/store/...-freebsd-kernel-15.0-STABLE`
|
||||
- `bootloader_store=/frx/store/...-freebsd-bootloader-15.0-STABLE`
|
||||
- `guile_store=/frx/store/...-fruix-guile-runtime-3.0`
|
||||
- `guile_extra_store=/frx/store/...-fruix-guile-extra-3.0`
|
||||
- `shepherd_store=/frx/store/...-fruix-shepherd-runtime-1.0.9`
|
||||
- `profile_bin_sh=/frx/store/...-fruix-system-fruix-freebsd/profile/bin/sh`
|
||||
- `profile_sbin_init=/frx/store/...-fruix-system-fruix-freebsd/profile/sbin/init`
|
||||
- `profile_rc=/frx/store/...-fruix-system-fruix-freebsd/profile/etc/rc`
|
||||
- `init_integration=freebsd-init+rc.d-shepherd`
|
||||
|
||||
## Important findings
|
||||
|
||||
- the FreeBSD system definition now materializes into a single reproducible system closure store item rather than remaining only a logical specification
|
||||
- the closure uses a Guix-like split between:
|
||||
- referenced store items for package/runtime content
|
||||
- generated configuration and activation payload in the system closure itself
|
||||
- the first integrated init strategy is now concretely encoded in the closure contents:
|
||||
- FreeBSD init and rc infrastructure from the base system profile
|
||||
- a generated `fruix_shepherd` rc script
|
||||
- a generated Shepherd configuration that writes a deterministic ready marker
|
||||
- rerunning the same materialization produced the same closure path, which is the current prototype proof of reproducible closure generation for this phase
|
||||
|
||||
## Conclusion
|
||||
|
||||
Phase 7.2 is satisfied on the current FreeBSD prototype track:
|
||||
|
||||
- a declarative Fruix system description now generates a real system closure under `/frx/store`
|
||||
- that closure contains boot files, `/etc` payloads, activation payload, and Shepherd launch integration
|
||||
- the next step is to materialize a root filesystem tree from this closure and statically validate it for later image construction work
|
||||
84
docs/reports/phase8-bhyve-image-freebsd.md
Normal file
84
docs/reports/phase8-bhyve-image-freebsd.md
Normal file
@@ -0,0 +1,84 @@
|
||||
# Phase 8.1: Reproducible bhyve-compatible disk image generation on FreeBSD
|
||||
|
||||
Date: 2026-04-01
|
||||
|
||||
## Summary
|
||||
|
||||
This step adds the first reproducible raw disk-image build path for the FreeBSD Fruix prototype. The target format is intentionally simple and debuggable:
|
||||
|
||||
- raw disk image
|
||||
- GPT partition map
|
||||
- UEFI boot
|
||||
- FreeBSD UFS root partition
|
||||
- serial-console-friendly loader configuration
|
||||
|
||||
Added file:
|
||||
|
||||
- `tests/system/run-phase8-bhyve-image.sh`
|
||||
|
||||
## Validation command
|
||||
|
||||
Run command:
|
||||
|
||||
```sh
|
||||
METADATA_OUT=/tmp/phase8-bhyve-image-metadata.txt \
|
||||
./tests/system/run-phase8-bhyve-image.sh
|
||||
```
|
||||
|
||||
## What the harness does
|
||||
|
||||
The harness:
|
||||
|
||||
1. reuses the Phase 7 rootfs materialization path
|
||||
2. discovers the generated system closure under `/frx/store`
|
||||
3. creates an image-oriented staging rootfs
|
||||
4. copies the closure and its recursively declared store references into:
|
||||
- `rootfs/frx/store`
|
||||
5. builds:
|
||||
- a FAT EFI system partition image containing `EFI/BOOT/BOOTX64.EFI`
|
||||
- a UFS root partition image from the staged rootfs
|
||||
- a final GPT raw disk image with labeled partitions
|
||||
6. rebuilds the same image a second time with fixed timestamps and filesystem parameters
|
||||
7. verifies that both resulting raw images have the same SHA256
|
||||
8. attaches the raw image through `mdconfig` and validates its partition/filesystem structure
|
||||
|
||||
## Observed results
|
||||
|
||||
Observed metadata included:
|
||||
|
||||
- `raw_sha256=08605d738021cb6fb5b87c270e1eafde57e1acb5159d3a2257aad4c560e2efc5`
|
||||
- `image_size_bytes=335578624`
|
||||
- `esp_fstype=msdosfs`
|
||||
- `root_fstype=ufs`
|
||||
- `run_current_system_target=/frx/store/...-fruix-system-fruix-freebsd`
|
||||
- `boot_loader_target=/run/current-system/boot/loader`
|
||||
- `boot_loader_conf_target=/run/current-system/boot/loader.conf`
|
||||
- `rc_conf_target=/run/current-system/etc/rc.conf`
|
||||
- `rc_script_target=/run/current-system/usr/local/etc/rc.d/fruix-shepherd`
|
||||
- `store_item_count=13`
|
||||
- `boot_mode=uefi`
|
||||
- `image_format=raw`
|
||||
- `partition_scheme=gpt`
|
||||
- `root_partition_label=fruix-root`
|
||||
- `efi_partition_label=efiboot`
|
||||
- `serial_console=comconsole`
|
||||
|
||||
## Important findings
|
||||
|
||||
- the current Fruix FreeBSD track now has a reproducible raw disk-image build path suitable for later bhyve work
|
||||
- the earlier Phase 7 rootfs tree was not sufficient by itself for an installable image because it referenced `/frx/store` content that was still only present on the host; the image builder therefore had to stage the closure and its store references inside the image rootfs under `/frx/store`
|
||||
- fixed timestamps and explicit filesystem parameters were sufficient to make repeated image builds byte-for-byte reproducible on this host
|
||||
- boot-structure sanity checks succeeded for:
|
||||
- GPT partitioning
|
||||
- EFI partition population
|
||||
- UFS root partition creation
|
||||
- serial-console loader configuration
|
||||
- preserved `run/current-system` system-link topology
|
||||
|
||||
## Conclusion
|
||||
|
||||
Phase 8.1 is satisfied on the current FreeBSD prototype track:
|
||||
|
||||
- a reproducible bhyve-compatible raw disk image can now be generated from the Fruix system outputs
|
||||
- the resulting image passes static boot-structure sanity checks
|
||||
- the next step is to move this image builder into the Fruix system-composition layer so image generation becomes an output of the declarative system description itself
|
||||
82
docs/reports/phase8-system-image-freebsd.md
Normal file
82
docs/reports/phase8-system-image-freebsd.md
Normal file
@@ -0,0 +1,82 @@
|
||||
# Phase 8.2: Image generation integrated with the Fruix system definition layer
|
||||
|
||||
Date: 2026-04-01
|
||||
|
||||
## Summary
|
||||
|
||||
This step moves bhyve-image generation out of a detached shell-only path and into the declarative Fruix FreeBSD system-composition module.
|
||||
|
||||
Added files:
|
||||
|
||||
- `tests/system/materialize-phase8-system-image.scm`
|
||||
- `tests/system/run-phase8-system-image.sh`
|
||||
|
||||
Updated file:
|
||||
|
||||
- `modules/fruix/system/freebsd.scm`
|
||||
|
||||
## Validation command
|
||||
|
||||
Run command:
|
||||
|
||||
```sh
|
||||
METADATA_OUT=/tmp/phase8-system-image-metadata.txt \
|
||||
./tests/system/run-phase8-system-image.sh
|
||||
```
|
||||
|
||||
## What changed in the system layer
|
||||
|
||||
The FreeBSD system module now exports image-oriented operations including:
|
||||
|
||||
- `operating-system-image-spec`
|
||||
- `materialize-bhyve-image`
|
||||
|
||||
The integrated image path now:
|
||||
|
||||
1. starts from a declarative Fruix operating-system object
|
||||
2. materializes the system closure under `/frx/store`
|
||||
3. materializes a rootfs from that closure
|
||||
4. stages the closure and its reference closure into `rootfs/frx/store`
|
||||
5. builds:
|
||||
- `esp.img`
|
||||
- `root.ufs`
|
||||
- `disk.img`
|
||||
6. stores the resulting image artifact as a content-addressed store item under `/frx/store`
|
||||
|
||||
## Observed results
|
||||
|
||||
Observed metadata included:
|
||||
|
||||
- `image_store_path=/frx/store/...-fruix-bhyve-image-fruix-freebsd`
|
||||
- `disk_image=/frx/store/...-fruix-bhyve-image-fruix-freebsd/disk.img`
|
||||
- `closure_path=/frx/store/...-fruix-system-fruix-freebsd`
|
||||
- `raw_sha256=ac57d4c694ea3cf6b1bd24be48982090a6cfcfa301d052c1f903636a46f2d56e`
|
||||
- `image_size_bytes=335578624`
|
||||
- `store_item_count=13`
|
||||
- `esp_fstype=msdosfs`
|
||||
- `root_fstype=ufs`
|
||||
- `run_current_system_target=/frx/store/...-fruix-system-fruix-freebsd`
|
||||
- `boot_loader_target=/run/current-system/boot/loader`
|
||||
- `rc_conf_target=/run/current-system/etc/rc.conf`
|
||||
- `rc_script_target=/run/current-system/usr/local/etc/rc.d/fruix-shepherd`
|
||||
- `image_generation_mode=declarative-system-layer`
|
||||
|
||||
## Important findings
|
||||
|
||||
- image generation is now a direct output of the Fruix FreeBSD system-definition layer rather than an external follow-up script around Phase 7 artifacts
|
||||
- the resulting image artifact is itself stored under `/frx/store`, preserving the project’s store-centered composition story as the work moves from closures to VM images
|
||||
- rerunning `materialize-bhyve-image` for the same operating-system description produced the same image store path, which is the current prototype proof that the declarative system object can drive image generation end-to-end
|
||||
- the integrated image still passes the same static boot-structure checks used in Phase 8.1:
|
||||
- GPT layout
|
||||
- EFI partition contents
|
||||
- UFS root partition
|
||||
- serial-console loader configuration
|
||||
- `run/current-system` topology
|
||||
|
||||
## Conclusion
|
||||
|
||||
Phase 8.2 is satisfied on the current FreeBSD prototype track:
|
||||
|
||||
- a single declarative Fruix operating-system description can now drive image generation end-to-end
|
||||
- the result is a bhyve-oriented raw image artifact stored under `/frx/store`
|
||||
- Phase 8 as a whole is now complete on the active FreeBSD amd64 prototype path
|
||||
227
docs/reports/phase9-xcpng-ready-boot-freebsd.md
Normal file
227
docs/reports/phase9-xcpng-ready-boot-freebsd.md
Normal file
@@ -0,0 +1,227 @@
|
||||
# Phase 9 completion: Fruix FreeBSD reached the ready marker on XCP-ng
|
||||
|
||||
Date: 2026-04-02
|
||||
|
||||
## Goal
|
||||
|
||||
Complete the first minimal Phase 9 boot milestone on the active FreeBSD track.
|
||||
|
||||
Because local bhyve is unavailable in this Xen environment, the active validation target remained the operator-approved XCP-ng VM and its existing VDI:
|
||||
|
||||
- VM: `90490f2e-e8fc-4b7a-388e-5c26f0157289`
|
||||
- VDI: `0f1f90d3-48ca-4fa2-91d8-fc6339b95743`
|
||||
|
||||
The required Phase 9 outcomes for this completion step were:
|
||||
|
||||
- boot the generated Fruix image on the real VM,
|
||||
- reach the generated ready marker,
|
||||
- keep Shepherd running in the guest,
|
||||
- keep SSH available for operator access,
|
||||
- and validate the declared system closure from inside the guest.
|
||||
|
||||
## Result
|
||||
|
||||
This phase now succeeds on the active XCP-ng path.
|
||||
|
||||
`tests/system/run-phase9-xcpng-boot.sh` now passes end-to-end and verifies:
|
||||
|
||||
- boot on the real XCP-ng VM,
|
||||
- DHCP on the guest NIC,
|
||||
- root SSH access via the injected key,
|
||||
- `/run/current-system` pointing at the generated Fruix closure under `/frx/store`,
|
||||
- the ready marker at `/var/lib/fruix/ready`,
|
||||
- `fruix-shepherd` running,
|
||||
- `sshd` running,
|
||||
- and a minimal operator-facing home directory for the declared `operator` account.
|
||||
|
||||
Successful run metadata:
|
||||
|
||||
- workdir: `/tmp/phase9-xcpng-pass-1775113189`
|
||||
- guest IP: `192.168.213.62`
|
||||
- closure path:
|
||||
- `/frx/store/0fe459ea22156510e64cea794b7a001151b59625bd5f12a488d6851e1c6d2198-fruix-system-fruix-freebsd`
|
||||
- image path:
|
||||
- `/frx/store/73f5757f8b58cf15fd97fc9a9704664d4b1d390d547fffff68c129a85d6cc368-fruix-bhyve-image-fruix-freebsd/disk.img`
|
||||
|
||||
Representative successful metadata values from the passing XCP-ng run:
|
||||
|
||||
```text
|
||||
ready_marker=ready
|
||||
run_current_system_target=/frx/store/0fe459ea22156510e64cea794b7a001151b59625bd5f12a488d6851e1c6d2198-fruix-system-fruix-freebsd
|
||||
shepherd_status=running
|
||||
sshd_status=running
|
||||
operator_home_listing=/home/operator
|
||||
uname_output=FreeBSD 15.0-STABLE
|
||||
logger_log=fruix-shepherd-started
|
||||
```
|
||||
|
||||
## Root causes resolved
|
||||
|
||||
The remaining Phase 9 blocker turned out not to be a single Guile bug, but a chain of runtime integration gaps.
|
||||
|
||||
### 1. Guile needed a usable UTF-8 locale in the guest
|
||||
|
||||
With the minimal image as originally staged, guest Guile started in a plain `C` locale and crashed very early in locale-string conversion paths.
|
||||
|
||||
The fix for the current prototype track was to stage the minimal locale data needed for `C.UTF-8`:
|
||||
|
||||
- `/usr/share/locale/C.UTF-8/LC_CTYPE`
|
||||
|
||||
and to start Shepherd with:
|
||||
|
||||
- `LANG=C.UTF-8`
|
||||
- `LC_ALL=C.UTF-8`
|
||||
|
||||
### 2. The copied Guile / Shepherd runtimes still contained baked-in source-prefix paths
|
||||
|
||||
The locally built Guile, guile-gnutls/fibers, and Shepherd artifacts were originally installed under temporary validation prefixes such as:
|
||||
|
||||
- `/tmp/guile-freebsd-validate-install`
|
||||
- `/tmp/guile-gnutls-freebsd-validate-install`
|
||||
- `/tmp/shepherd-freebsd-validate-install`
|
||||
|
||||
Even after those trees were copied into `/frx/store`, a number of runtime references still pointed back to the original prefixes:
|
||||
|
||||
- Guile system load paths compiled into `libguile`
|
||||
- Shepherd launcher scripts
|
||||
- Fibers and GnuTLS Scheme modules
|
||||
- Shepherd configuration module paths
|
||||
|
||||
The immediate prototype fix was to make activation recreate compatibility symlinks at those original prefixes, but pointing at the actual store items in the guest.
|
||||
|
||||
This keeps the running system store-backed while unblocking the existing locally built Guile/Shepherd artifacts.
|
||||
|
||||
### 3. The Shepherd process needed to be detached correctly from rc startup
|
||||
|
||||
Starting Shepherd with a simple shell background `&` was not sufficient on the real boot path. The process could exit when the invoking shell/session disappeared, which made the ready marker appear transiently while Shepherd itself did not remain up.
|
||||
|
||||
The fix was to launch Shepherd through FreeBSD `daemon(8)`:
|
||||
|
||||
- `/usr/sbin/daemon -c -f -p "$pidfile" -o /var/log/shepherd-bootstrap.out ...`
|
||||
|
||||
This gave the guest a stable long-lived Shepherd daemon process and made `onestatus`/socket checks reliable.
|
||||
|
||||
### 4. The initial Shepherd config used helper APIs that were not actually present in the guest runtime
|
||||
|
||||
The generated Shepherd config originally used:
|
||||
|
||||
- `mkdir-p`
|
||||
- `call-with-output-file` with `#:append`
|
||||
|
||||
Those choices were too optimistic for the minimal Scheme environment being staged.
|
||||
|
||||
The fix was to replace them with simpler portable logic:
|
||||
|
||||
- a local recursive directory-creation helper based on `mkdir`
|
||||
- explicit append-mode logging via `open-file "a"`
|
||||
|
||||
### 5. The XCP-ng harness itself had an SSH-key bug
|
||||
|
||||
The first end-to-end rerun of `tests/system/run-phase9-xcpng-boot.sh` failed because it used the public key file as the SSH identity file.
|
||||
|
||||
The harness now distinguishes:
|
||||
|
||||
- `ROOT_AUTHORIZED_KEY_FILE` for guest key injection
|
||||
- `ROOT_SSH_PRIVATE_KEY_FILE` for the host-side SSH login
|
||||
|
||||
with the private key defaulting to:
|
||||
|
||||
- `~/.ssh/id_ed25519`
|
||||
|
||||
## Code-level changes that closed the blocker
|
||||
|
||||
### `modules/fruix/packages/freebsd.scm`
|
||||
|
||||
Extended the minimal runtime again to support the final ready-state path:
|
||||
|
||||
- staged `/usr/sbin/daemon`
|
||||
- staged `/usr/share/locale/C.UTF-8/LC_CTYPE`
|
||||
|
||||
### `modules/fruix/system/freebsd.scm`
|
||||
|
||||
Completed the Guile/Shepherd guest runtime integration by:
|
||||
|
||||
- generating activation that recreates compatibility symlinks from the historical build prefixes to the real `/frx/store` items
|
||||
- exporting locale and Guile runtime path variables in the Shepherd rc script:
|
||||
- `LANG`
|
||||
- `LC_ALL`
|
||||
- `GUILE_SYSTEM_PATH`
|
||||
- `GUILE_SYSTEM_COMPILED_PATH`
|
||||
- `GUILE_SYSTEM_EXTENSIONS_PATH`
|
||||
- the existing site path variables
|
||||
- starting Shepherd through `daemon(8)` instead of a fragile shell background job
|
||||
- fixing the generated Shepherd config so the logger and ready-marker services work in the guest
|
||||
- exposing the staged locale data into the rootfs via `/usr/share/locale`
|
||||
|
||||
### `tests/system/run-phase9-xcpng-boot.sh`
|
||||
|
||||
Improved the real XCP-ng harness by:
|
||||
|
||||
- separating the injected public key from the SSH private key actually used for login
|
||||
- preserving the successful passing metadata for the full ready-marker path
|
||||
|
||||
## Validation details
|
||||
|
||||
### Local reproduction and verification
|
||||
|
||||
Before re-testing on XCP-ng, the failure was reproduced and fixed in two faster environments:
|
||||
|
||||
1. a host-side chroot into the generated image root partition
|
||||
2. local QEMU/TCG boots with UEFI and SSH forwarding
|
||||
|
||||
That produced a much tighter debug loop for:
|
||||
|
||||
- locale staging,
|
||||
- baked-prefix compatibility,
|
||||
- and Shepherd daemon lifetime.
|
||||
|
||||
### Real XCP-ng validation
|
||||
|
||||
The final proof remained the real VM.
|
||||
|
||||
The passing XCP-ng run verified all of the following from the booted guest over SSH:
|
||||
|
||||
- `cat /var/lib/fruix/ready` returns `ready`
|
||||
- `/usr/local/etc/rc.d/fruix-shepherd onestatus` succeeds
|
||||
- `service sshd onestatus` succeeds
|
||||
- `readlink /run/current-system` matches the generated Fruix closure
|
||||
- `/home/operator` exists
|
||||
|
||||
## Assessment against Phase 9 goals
|
||||
|
||||
### 9.1 deterministic ready state
|
||||
|
||||
Satisfied on the active XCP-ng track.
|
||||
|
||||
The guest now boots to a deterministic ready marker:
|
||||
|
||||
- `/var/lib/fruix/ready`
|
||||
|
||||
### 9.2 in-guest Shepherd and core-service validation
|
||||
|
||||
Satisfied on the active XCP-ng track.
|
||||
|
||||
The guest now validates:
|
||||
|
||||
- Shepherd active
|
||||
- generated configuration in effect
|
||||
- system closure mounted through `/run/current-system`
|
||||
- `sshd` available for remote operator access
|
||||
|
||||
### 9.3 minimal operator usability
|
||||
|
||||
Satisfied on the active XCP-ng track.
|
||||
|
||||
A human operator can now:
|
||||
|
||||
- discover the DHCP address,
|
||||
- log in over SSH with the injected root key,
|
||||
- inspect `/run/current-system`,
|
||||
- inspect the ready marker,
|
||||
- and inspect Shepherd/log state in the guest.
|
||||
|
||||
## Conclusion
|
||||
|
||||
Phase 9 is complete for the current FreeBSD prototype track, using the active XCP-ng replacement path in place of unavailable local bhyve.
|
||||
|
||||
The Fruix image now boots as a real FreeBSD VM, reaches the generated ready state, runs Shepherd successfully, and supports a minimal operator workflow over SSH.
|
||||
213
docs/reports/phase9-xcpng-ssh-boot-freebsd.md
Normal file
213
docs/reports/phase9-xcpng-ssh-boot-freebsd.md
Normal file
@@ -0,0 +1,213 @@
|
||||
# Phase 9 checkpoint: XCP-ng boot reached DHCP and SSH on FreeBSD
|
||||
|
||||
Date: 2026-04-02
|
||||
|
||||
## Goal
|
||||
|
||||
Advance Phase 9 from a static image-generation milestone to a real booted Fruix guest on the active FreeBSD/XCP-ng track, using the operator-approved VM:
|
||||
|
||||
- VM: `90490f2e-e8fc-4b7a-388e-5c26f0157289`
|
||||
- existing target VDI: `0f1f90d3-48ca-4fa2-91d8-fc6339b95743`
|
||||
|
||||
The immediate objective for this checkpoint was narrower than full Phase 9 completion:
|
||||
|
||||
- boot the generated image under XCP-ng,
|
||||
- obtain DHCP,
|
||||
- and reach SSH access with the injected root key.
|
||||
|
||||
## Summary
|
||||
|
||||
This checkpoint succeeded.
|
||||
|
||||
The current Fruix FreeBSD image now:
|
||||
|
||||
- boots on the target XCP-ng VM,
|
||||
- mounts the generated root filesystem,
|
||||
- completes enough of FreeBSD `rc` startup to configure networking,
|
||||
- obtains a DHCP lease on the Xen NIC,
|
||||
- starts `sshd`,
|
||||
- and accepts root public-key authentication over the network.
|
||||
|
||||
Validated guest details from the successful XCP-ng boot:
|
||||
|
||||
- guest IP: `192.168.213.62`
|
||||
- hostname: `fruix-freebsd`
|
||||
- kernel string:
|
||||
- `FreeBSD 15.0-STABLE stable/15-n282801-29dce45d8c50 GENERIC amd64`
|
||||
|
||||
Representative successful SSH validation output:
|
||||
|
||||
```text
|
||||
FreeBSD fruix-freebsd 15.0-STABLE FreeBSD 15.0-STABLE stable/15-n282801-29dce45d8c50 GENERIC amd64
|
||||
fruix-freebsd
|
||||
192.168.213.62
|
||||
```
|
||||
|
||||
Successful XCP-ng work directory:
|
||||
|
||||
- `/tmp/phase9-xcpng-ssh-1775097470`
|
||||
|
||||
## Important boot/debugging findings
|
||||
|
||||
The first decisive breakthrough came from running the generated image locally under QEMU/TCG with serial capture. That made the previously opaque early-boot failure visible.
|
||||
|
||||
### 1. The original early boot abort was not an XCP-ng image-format problem anymore
|
||||
|
||||
After the earlier switch from raw uploads to dynamic VHD uploads, the remaining boot failure was inside the guest boot process, not in the XO import path.
|
||||
|
||||
### 2. FreeBSD `fstab` handling for pseudo-filesystems was wrong
|
||||
|
||||
The serial log showed that boot aborted during filesystem checks because the generated `fstab` gave non-zero fsck fields to non-UFS mounts such as `devfs`.
|
||||
|
||||
Representative failure:
|
||||
|
||||
```text
|
||||
Starting file system checks:
|
||||
/dev/gpt/fruix-root: FILE SYSTEM CLEAN; SKIPPING CHECKS
|
||||
/dev/gpt/fruix-root: clean, ...
|
||||
fsck: exec fsck_devfs for devfs in /sbin:/usr/sbin: No such file or directory
|
||||
Unknown error 1; help!
|
||||
ERROR: ABORTING BOOT (sending SIGTERM to parent)!
|
||||
```
|
||||
|
||||
The fix was to generate fsck pass fields only for UFS entries and emit `0 0` for pseudo-filesystems.
|
||||
|
||||
### 3. The minimal image was still missing many base files and commands expected by `rc`
|
||||
|
||||
Once `rc` ran further, QEMU serial logs exposed a long tail of missing runtime pieces that had not been visible from the earlier static validations alone.
|
||||
|
||||
Examples included:
|
||||
|
||||
- missing base commands:
|
||||
- `dd`
|
||||
- `expr`
|
||||
- `rmdir`
|
||||
- `sort`
|
||||
- `mktemp`
|
||||
- `egrep`
|
||||
- `fsync`
|
||||
- `kldload`
|
||||
- `kldstat`
|
||||
- `devfs`
|
||||
- `devctl`
|
||||
- `newsyslog`
|
||||
- `ip6addrctl`
|
||||
- missing base config files:
|
||||
- `/etc/network.subr`
|
||||
- `/etc/devd.conf`
|
||||
- `/etc/newsyslog.conf`
|
||||
- `/etc/syslog.conf`
|
||||
- missing runtime directories:
|
||||
- `/var/db`
|
||||
- `/var/cron`
|
||||
- missing libraries needed by later boot helpers:
|
||||
- `libgeom.so.5`
|
||||
- `libdevctl.so.5`
|
||||
- `libcap_net.so.1`
|
||||
- C++ runtime pieces used by `devd`
|
||||
|
||||
These were staged into the current FreeBSD package layer and linked into the generated rootfs.
|
||||
|
||||
### 4. SSH auth initially failed because the image relied on PAM without a complete PAM runtime/configuration
|
||||
|
||||
`sshd` would start, but root public-key authentication still failed. A direct in-guest debug run showed:
|
||||
|
||||
```text
|
||||
PAM: initialisation failed
|
||||
```
|
||||
|
||||
For the minimal Phase 9 guest, the practical fix was to make the generated `sshd_config` use:
|
||||
|
||||
- `UsePAM no`
|
||||
|
||||
while still keeping key-only login enabled.
|
||||
|
||||
That was sufficient to unlock real SSH access on both the local QEMU debug guest and the XCP-ng guest.
|
||||
|
||||
## Current code-level outcomes
|
||||
|
||||
The current checkpoint work materially expanded the minimal FreeBSD runtime staged into Fruix images.
|
||||
|
||||
Highlights:
|
||||
|
||||
- `modules/fruix/packages/freebsd.scm`
|
||||
- added dedicated runtime packages for:
|
||||
- `freebsd-networking`
|
||||
- `freebsd-openssh`
|
||||
- expanded staged base runtime coverage substantially for `rc`, networking, and SSH
|
||||
- added required config files and shared libraries used during real boot
|
||||
- `modules/fruix/system/freebsd.scm`
|
||||
- added root authorized-key support to the operating-system model
|
||||
- generated static account databases and supporting files:
|
||||
- `/etc/passwd`
|
||||
- `/etc/master.passwd`
|
||||
- `/etc/group`
|
||||
- `/etc/login.conf`
|
||||
- `/etc/ttys`
|
||||
- activation now runs:
|
||||
- `cap_mkdb`
|
||||
- `pwd_mkdb`
|
||||
- activation creates required directories and SSH host keys
|
||||
- generated `sshd_config` now disables PAM for the current minimal key-only Phase 9 path
|
||||
- `fstab` generation now avoids fsck pass numbers for pseudo-filesystems
|
||||
- rootfs generation now links the additional `/etc` files needed by real boot
|
||||
- `tests/system/phase9-minimal-operating-system.scm.in`
|
||||
- enables DHCP on the relevant NIC names for the current tracks:
|
||||
- `xn0`
|
||||
- `em0`
|
||||
- `vtnet0`
|
||||
- injects the root authorized key
|
||||
- includes the SSH/network runtime packages and required system users/groups
|
||||
- `tests/system/run-phase8-system-image.sh`
|
||||
- now accepts `OS_FILE`
|
||||
- now accepts/passes `DISK_CAPACITY`
|
||||
- serial-console validation was relaxed from an exact loader string to a `comconsole` presence check
|
||||
|
||||
## Verified current state
|
||||
|
||||
The current validated Phase 9 state is:
|
||||
|
||||
- XCP-ng VHD upload path works against the existing VDI
|
||||
- the guest boots far enough for normal `rc` networking and `sshd`
|
||||
- DHCP works on the Xen NIC
|
||||
- SSH key injection works
|
||||
- root login over SSH works
|
||||
|
||||
This means the project has crossed an important Phase 9 boundary:
|
||||
|
||||
- the first boot validation no longer depends on local bhyve serial automation,
|
||||
- and the real XCP-ng target can now be exercised over the network.
|
||||
|
||||
## Remaining blocker
|
||||
|
||||
Phase 9 is not complete yet because the Fruix-specific readiness path still fails.
|
||||
|
||||
Current remaining blocker:
|
||||
|
||||
- Guile still crashes in the guest
|
||||
- therefore `fruix-shepherd` does not start
|
||||
- therefore `/var/lib/fruix/ready` is still absent
|
||||
|
||||
Representative guest evidence:
|
||||
|
||||
```text
|
||||
pid 262 (guile), jid 0, uid 0: exited on signal 11 (core dumped)
|
||||
```
|
||||
|
||||
Over SSH on the real XCP-ng guest:
|
||||
|
||||
- `sshd` is running
|
||||
- DHCP is active
|
||||
- `fruix-shepherd` is stopped
|
||||
- `/var/lib/fruix/ready` is missing
|
||||
|
||||
A retrieved core dump and local `lldb` analysis show the Guile crash occurs extremely early during initialization, in the locale/string conversion path while building Guile load/build info. This remains the next debugging target.
|
||||
|
||||
## Assessment
|
||||
|
||||
This checkpoint satisfies a meaningful Phase 9 intermediate milestone on the active FreeBSD/XCP-ng track:
|
||||
|
||||
- the generated Fruix image now boots as a network-reachable FreeBSD guest,
|
||||
- and minimal operator access via SSH is working.
|
||||
|
||||
However, the full Fruix boot milestone is still blocked by in-guest Guile/Shepherd failure, so the overall Phase 9 milestone remains open.
|
||||
160
docs/reports/postphase10-runtime-prefix-shims-freebsd.md
Normal file
160
docs/reports/postphase10-runtime-prefix-shims-freebsd.md
Normal file
@@ -0,0 +1,160 @@
|
||||
# Post-Phase-10: removed runtime dependence on `/tmp` Guile/Shepherd compatibility-prefix shims
|
||||
|
||||
Date: 2026-04-02
|
||||
|
||||
## Goal
|
||||
|
||||
After validating both the `rc.d` bridge path and the new Shepherd-as-PID-1 path, the next priority was the remaining runtime-artifact problem:
|
||||
|
||||
- the guest still carried activation-time `/tmp/...` compatibility symlinks for locally built Guile, guile-extra, and Shepherd prefixes
|
||||
- those shims were compensating for baked-in historical paths in staged runtime artifacts
|
||||
|
||||
The goal of this subphase was narrower than fully rebuilding Guile/Shepherd in a store-native way. The immediate target was:
|
||||
|
||||
- remove the guest's *runtime dependence* on those `/tmp` compatibility-prefix symlinks,
|
||||
- keep both validated boot modes working,
|
||||
- and prove that Guile can still load the relevant modules from the store-backed runtime layout without those `/tmp` aliases.
|
||||
|
||||
## Result
|
||||
|
||||
This subphase succeeded.
|
||||
|
||||
The generated guest no longer needs activation to create:
|
||||
|
||||
- `/tmp/guile-freebsd-validate-install`
|
||||
- `/tmp/guile-gnutls-freebsd-validate-install`
|
||||
- `/tmp/shepherd-freebsd-validate-install`
|
||||
|
||||
Those shims are now absent in the booted guest, while both of these boot paths still pass on the real XCP-ng VM:
|
||||
|
||||
1. `freebsd-init+rc.d-shepherd`
|
||||
2. `shepherd-pid1`
|
||||
|
||||
In both cases, the guest now also passes an explicit in-guest Guile module smoke test using the staged store paths and *without* the `/tmp` prefix aliases.
|
||||
|
||||
## What changed
|
||||
|
||||
### 1. Activation no longer creates `/tmp` compatibility-prefix symlinks
|
||||
|
||||
`modules/fruix/system/freebsd.scm` no longer emits activation logic that recreates the old `/tmp/...` prefix trees.
|
||||
|
||||
That means the guest is no longer depending on those aliases as part of ordinary first boot.
|
||||
|
||||
### 2. The staged runtime prefixes are sanitized as they are materialized into `/frx/store`
|
||||
|
||||
The prefix materializer now post-processes the copied runtime trees so the most relevant runtime-facing Scheme modules no longer require those `/tmp` prefix aliases.
|
||||
|
||||
This required a new prefix-materializer revision and deterministic post-copy sanitation.
|
||||
|
||||
### 3. Guile-extra runtime fixes
|
||||
|
||||
The staged guile-extra runtime now:
|
||||
|
||||
- patches `fibers/config.scm` so it can use `GUILE_EXTENSIONS_PATH` instead of falling back to the old `/tmp/guile-gnutls-freebsd-validate-install/...` path
|
||||
- patches `gnutls.scm` so it can fall back to `GUILE_EXTENSIONS_PATH`
|
||||
- removes stale compiled cache files that still embedded the old prefix values and would otherwise override the corrected source modules:
|
||||
- `fibers/config.go`
|
||||
- `gnutls.go`
|
||||
|
||||
### 4. Shepherd runtime fixes
|
||||
|
||||
The staged Shepherd runtime now:
|
||||
|
||||
- patches `share/guile/site/3.0/shepherd/config.scm` to use real runtime-facing paths instead of the old temporary install prefix:
|
||||
- `Prefix-dir` => `/frx`
|
||||
- `%localstatedir` => `/var`
|
||||
- `%runstatedir` => `/var/run`
|
||||
- `%sysconfdir` => `/etc`
|
||||
- `%localedir` => `/usr/share/locale`
|
||||
- `%pkglibdir` => `/usr/local/lib/shepherd`
|
||||
- removes the stale compiled cache file that still embedded the old prefix values:
|
||||
- `shepherd/config.go`
|
||||
|
||||
### 5. Real-VM validation harnesses now assert shim absence directly
|
||||
|
||||
The main real-VM validation scripts now check that the old `/tmp` compatibility-prefix trees are absent in the guest:
|
||||
|
||||
- `tests/system/run-phase9-xcpng-boot.sh`
|
||||
- `tests/system/run-phase11-shepherd-pid1-xcpng.sh`
|
||||
|
||||
They also run an in-guest Guile module smoke test using the store-backed runtime environment and require:
|
||||
|
||||
- `guile_module_smoke=ok`
|
||||
|
||||
## Validation
|
||||
|
||||
### Real VM, `freebsd-init+rc.d-shepherd`
|
||||
|
||||
Passing run:
|
||||
|
||||
- `PASS phase9-xcpng-boot`
|
||||
- workdir: `/tmp/noshim-phase9-smoke-1775143001`
|
||||
|
||||
Key metadata:
|
||||
|
||||
```text
|
||||
compat_prefix_shims=absent
|
||||
guile_module_smoke=ok
|
||||
ready_marker=ready
|
||||
shepherd_status=running
|
||||
sshd_status=running
|
||||
```
|
||||
|
||||
### Real VM, `shepherd-pid1`
|
||||
|
||||
Passing run:
|
||||
|
||||
- `PASS phase11-shepherd-pid1-xcpng`
|
||||
- workdir: `/tmp/noshim-phase11-smoke-1775142712`
|
||||
|
||||
Key metadata:
|
||||
|
||||
```text
|
||||
compat_prefix_shims=absent
|
||||
guile_module_smoke=ok
|
||||
ready_marker=ready
|
||||
shepherd_pid=1
|
||||
shepherd_status=running
|
||||
sshd_status=running
|
||||
```
|
||||
|
||||
### Manual guest confirmation
|
||||
|
||||
A direct SSH probe on the real guest confirmed that all three historical `/tmp` compatibility prefixes are absent while Guile can still load:
|
||||
|
||||
- `(fibers config)`
|
||||
- `(gnutls)`
|
||||
- `(shepherd config)`
|
||||
|
||||
with output including:
|
||||
|
||||
```text
|
||||
ABSENT:/tmp/guile-freebsd-validate-install
|
||||
ABSENT:/tmp/guile-gnutls-freebsd-validate-install
|
||||
ABSENT:/tmp/shepherd-freebsd-validate-install
|
||||
1.4.2
|
||||
/var/run
|
||||
```
|
||||
|
||||
## Assessment
|
||||
|
||||
This removes an important transitional runtime crutch.
|
||||
|
||||
Fruix is no longer merely *booting despite* historical `/tmp` prefix aliases; it now boots and runs Guile/Shepherd services from the staged store-backed runtime arrangement without those guest-side compatibility symlinks.
|
||||
|
||||
## Remaining limitation
|
||||
|
||||
This does **not** mean every historical prefix string has disappeared from every staged artifact.
|
||||
|
||||
Some compiled binaries and libraries still contain old build/install strings internally, especially in places such as:
|
||||
|
||||
- `libguile`
|
||||
- libtool metadata
|
||||
- pkg-config metadata
|
||||
- extension shared objects built upstream with the original prefixes
|
||||
|
||||
However, the important practical milestone is that the guest no longer depends on `/tmp` compatibility-prefix symlinks at runtime.
|
||||
|
||||
## Recommended next step
|
||||
|
||||
The next, deeper cleanup would be to eliminate the remaining baked prefix strings from the runtime artifacts themselves by moving the local Guile/guile-extra/Shepherd build/install flow closer to a genuinely store-native prefix from the start, rather than relying on post-copy sanitation.
|
||||
178
docs/reports/postphase10-shepherd-pid1-qemu-freebsd.md
Normal file
178
docs/reports/postphase10-shepherd-pid1-qemu-freebsd.md
Normal file
@@ -0,0 +1,178 @@
|
||||
# Post-Phase-10: local Shepherd-as-PID-1 boot prototype on FreeBSD
|
||||
|
||||
Date: 2026-04-02
|
||||
|
||||
## Goal
|
||||
|
||||
Begin the next post-Phase-10 runtime-integration pass by exploring two related directions:
|
||||
|
||||
- reduce reliance on the `freebsd-init + rc.d + shepherd` bridge, and
|
||||
- compare Fruix's boot path with how Guix boots Shepherd as PID 1.
|
||||
|
||||
The concrete goal for this subphase was not yet a full real-VM migration of the main boot track, but a first validated local prototype where the generated FreeBSD image boots with Shepherd as PID 1 under QEMU/UEFI.
|
||||
|
||||
## Comparison with Guix
|
||||
|
||||
Guix's system model treats Shepherd as a first-class root service.
|
||||
|
||||
In `~/repos/guix/gnu/services/shepherd.scm`, the key pattern is:
|
||||
|
||||
- `shepherd-root-service-type` extends the boot service graph
|
||||
- `shepherd-boot-gexp` ultimately does an `execl` of Shepherd as PID 1
|
||||
- higher-level system services extend that root Shepherd instance declaratively
|
||||
|
||||
In other words, Guix does not merely start Shepherd from a late init script; it composes the system boot graph around Shepherd directly.
|
||||
|
||||
Fruix is not at that level of native service composition yet, but this subphase adopts the same basic architectural direction:
|
||||
|
||||
- boot into a generated Shepherd config directly,
|
||||
- let Shepherd own the service graph from PID 1,
|
||||
- and keep the imperative compatibility/bootstrap logic as small as possible.
|
||||
|
||||
## Implementation
|
||||
|
||||
### 1. Added an explicit init-mode to the FreeBSD operating-system model
|
||||
|
||||
`modules/fruix/system/freebsd.scm` now has an `init-mode` field on the declarative operating-system record.
|
||||
|
||||
Supported values are currently:
|
||||
|
||||
- `freebsd-init+rc.d-shepherd`
|
||||
- `shepherd-pid1`
|
||||
|
||||
The existing boot path remains the default.
|
||||
|
||||
### 2. Added a generated PID 1 launcher for the `shepherd-pid1` mode
|
||||
|
||||
For `shepherd-pid1`, the generated system now contains:
|
||||
|
||||
- `boot/fruix-pid1`
|
||||
|
||||
and the generated loader configuration adds:
|
||||
|
||||
- `init_exec="/run/current-system/boot/fruix-pid1"`
|
||||
|
||||
That means FreeBSD `init(8)` directly `exec`s the generated Fruix launcher as its very first action, replacing itself as PID 1.
|
||||
|
||||
The launcher currently performs the minimum bootstrap steps needed before turning control over to Shepherd:
|
||||
|
||||
- remount `/` read-write on this very-early path
|
||||
- mount declared non-root filesystems such as:
|
||||
- `devfs` on `/dev`
|
||||
- `tmpfs` on `/tmp`
|
||||
- set the hostname
|
||||
- run `/run/current-system/activate`
|
||||
- export the Guile/Shepherd runtime environment
|
||||
- `exec` Shepherd directly
|
||||
|
||||
Because Shepherd is a Guile script, the actual PID 1 process image is the Guile interpreter running Shepherd. The important validation point is that Shepherd's own pidfile records PID 1 and the service socket is owned by that process.
|
||||
|
||||
### 3. Generated a different Shepherd config for PID 1 mode
|
||||
|
||||
For `shepherd-pid1`, the generated `shepherd/init.scm` now includes the minimal helper procedures it needs inline, rather than importing the repo-side `(fruix shepherd freebsd)` module at runtime.
|
||||
|
||||
This avoids depending on checkout-only Scheme modules being present in the guest.
|
||||
|
||||
The PID 1 config currently starts a minimal service graph:
|
||||
|
||||
- `fruix-logger`
|
||||
- `netif` through FreeBSD `service(8)`
|
||||
- `sshd` through FreeBSD `service(8)`
|
||||
- `fruix-ready`
|
||||
|
||||
So this prototype still uses some FreeBSD rc scripts as service implementations, but now under Shepherd control rather than under `/etc/rc` as the primary boot manager.
|
||||
|
||||
### 4. Made activation more store-friendly for this early-boot path
|
||||
|
||||
The generated activation script now treats:
|
||||
|
||||
- `cap_mkdb /etc/login.conf`
|
||||
- `pwd_mkdb -p /etc/master.passwd`
|
||||
|
||||
as best-effort operations.
|
||||
|
||||
That matters because Fruix currently symlinks these files from the immutable system closure, and on the very early PID 1 path they should not be allowed to abort the whole boot.
|
||||
|
||||
## Validation
|
||||
|
||||
### New PID 1 template
|
||||
|
||||
Added:
|
||||
|
||||
- `tests/system/phase11-shepherd-pid1-operating-system.scm.in`
|
||||
|
||||
This declares the same minimal FreeBSD Fruix guest shape as the current Phase 9 system, but with:
|
||||
|
||||
- `#:init-mode 'shepherd-pid1`
|
||||
|
||||
### New local QEMU validation harness
|
||||
|
||||
Added:
|
||||
|
||||
- `tests/system/run-phase11-shepherd-pid1-qemu.sh`
|
||||
|
||||
This harness:
|
||||
|
||||
- builds the image through the canonical `fruix system image` path
|
||||
- boots it locally with QEMU/TCG + UEFI
|
||||
- injects the root SSH key
|
||||
- waits for the ready marker over forwarded SSH
|
||||
- verifies that Shepherd is running and that Shepherd's pidfile says PID 1
|
||||
|
||||
### Successful run
|
||||
|
||||
Passing validation run:
|
||||
|
||||
- `PASS phase11-shepherd-pid1-qemu`
|
||||
- workdir: `/tmp/pid1-qemu6-1775128407`
|
||||
|
||||
Key validated results:
|
||||
|
||||
```text
|
||||
ready_marker=ready
|
||||
run_current_system_target=/frx/store/8b44506c37da85cebf265c813ed3a9d2a42408b077ac85854e7d6209d2f910ec-fruix-system-fruix-freebsd
|
||||
shepherd_pid=1
|
||||
shepherd_socket=present
|
||||
shepherd_status=running
|
||||
sshd_status=running
|
||||
pid1_command=[guile]
|
||||
boot_backend=qemu-uefi-tcg
|
||||
init_mode=shepherd-pid1
|
||||
```
|
||||
|
||||
The important detail is:
|
||||
|
||||
- `shepherd_pid=1`
|
||||
|
||||
which shows that the running Shepherd instance in the guest is the system's PID 1 process.
|
||||
|
||||
## Assessment
|
||||
|
||||
This is a meaningful architectural step beyond the earlier `rc.d` bridge milestone.
|
||||
|
||||
Fruix now has a validated local boot path where:
|
||||
|
||||
- the generated image boots on FreeBSD,
|
||||
- the generated launcher becomes PID 1 via `init_exec`,
|
||||
- Shepherd itself owns PID 1,
|
||||
- networking and SSH come up under Shepherd-managed service ordering,
|
||||
- and the ready marker still appears.
|
||||
|
||||
## Remaining limitations
|
||||
|
||||
This is still a prototype, not yet the replacement for the main boot path.
|
||||
|
||||
Notable current limitations:
|
||||
|
||||
- the PID 1 path still relies on a small generated shell launcher before entering Shepherd
|
||||
- some early boot/runtime actions are still expressed imperatively there
|
||||
- the Guile/Shepherd local-runtime compatibility-prefix shims are not eliminated yet; they remain part of activation for the currently locally built runtime artifacts
|
||||
- this subphase validated the path locally under QEMU/TCG, not yet on the real XCP-ng VM
|
||||
|
||||
## Recommended next step
|
||||
|
||||
Use this validated local PID 1 prototype as the base for the next subphase:
|
||||
|
||||
1. try the `shepherd-pid1` image on the real XCP-ng VM
|
||||
2. if that succeeds, decide whether `shepherd-pid1` should become a selectable supported boot mode rather than just a prototype
|
||||
3. continue reducing the remaining compatibility-prefix shims by moving the Guile/Shepherd runtime artifacts toward a more native store-aware arrangement
|
||||
114
docs/reports/postphase10-shepherd-pid1-xcpng-freebsd.md
Normal file
114
docs/reports/postphase10-shepherd-pid1-xcpng-freebsd.md
Normal file
@@ -0,0 +1,114 @@
|
||||
# Post-Phase-10: Shepherd-as-PID-1 boot validated on the real XCP-ng FreeBSD VM
|
||||
|
||||
Date: 2026-04-02
|
||||
|
||||
## Goal
|
||||
|
||||
Take the locally validated Shepherd-as-PID-1 Fruix boot prototype and test it on the real operator-approved XCP-ng VM.
|
||||
|
||||
Target objects remained the same constrained deployment path used for Phase 9:
|
||||
|
||||
- VM: `90490f2e-e8fc-4b7a-388e-5c26f0157289`
|
||||
- VDI: `0f1f90d3-48ca-4fa2-91d8-fc6339b95743`
|
||||
|
||||
The concrete goal for this subphase was to confirm that the new `shepherd-pid1` init mode was not merely a local QEMU curiosity, but could also:
|
||||
|
||||
- boot on the real Xen guest,
|
||||
- reach DHCP and SSH,
|
||||
- keep Shepherd running as PID 1,
|
||||
- and still reach the Fruix ready marker.
|
||||
|
||||
## Result
|
||||
|
||||
The real XCP-ng boot succeeded.
|
||||
|
||||
A new deployment/validation harness was added:
|
||||
|
||||
- `tests/system/run-phase11-shepherd-pid1-xcpng.sh`
|
||||
|
||||
This harness reuses the existing real-VM deployment method:
|
||||
|
||||
- build a full-size image matching the existing VDI
|
||||
- convert it to dynamic VHD
|
||||
- overwrite the existing VDI
|
||||
- boot the real VM
|
||||
- rediscover the guest by MAC/IP
|
||||
- validate the booted guest over SSH
|
||||
|
||||
The new Shepherd-PID-1 image passes that full path.
|
||||
|
||||
## Validation
|
||||
|
||||
Passing real-VM run:
|
||||
|
||||
- `PASS phase11-shepherd-pid1-xcpng`
|
||||
- workdir: `/tmp/pid1-xcpng-1775129768`
|
||||
|
||||
Validated metadata from the real guest:
|
||||
|
||||
```text
|
||||
ready_marker=ready
|
||||
run_current_system_target=/frx/store/2940c952e9d35e47f98fe62f296be2b6ab4fceb3eee8248d6a7823decd42a305-fruix-system-fruix-freebsd
|
||||
pid1_command=[guile]
|
||||
shepherd_pid=1
|
||||
shepherd_socket=present
|
||||
shepherd_status=running
|
||||
sshd_status=running
|
||||
guest_ip=192.168.213.62
|
||||
boot_backend=xcp-ng-xo-cli
|
||||
init_mode=shepherd-pid1
|
||||
```
|
||||
|
||||
The key architectural confirmation is:
|
||||
|
||||
- `shepherd_pid=1`
|
||||
|
||||
That shows the running Shepherd instance in the real guest is PID 1.
|
||||
|
||||
As in the local QEMU prototype, the process image is Guile because Shepherd is launched as a Guile script; however, the service manager itself is the PID 1 process according to Shepherd's own pidfile and control socket state.
|
||||
|
||||
## What changed to make the real VM pass
|
||||
|
||||
The most important refinement after the first local PID 1 work was making the generated activation path more tolerant of immutable store-backed configuration files during very early boot.
|
||||
|
||||
Specifically, the generated activation script now treats these as best-effort:
|
||||
|
||||
- `cap_mkdb /etc/login.conf`
|
||||
- `pwd_mkdb -p /etc/master.passwd`
|
||||
|
||||
That matters because on the PID 1 path they happen earlier and should not abort the system if the current `/etc` representation is not suitable for in-place database regeneration.
|
||||
|
||||
The Shepherd-PID-1 operating-system template was also expanded to keep the NIC configuration broad enough for both local virtio and the real Xen path:
|
||||
|
||||
- `ifconfig_xn0=SYNCDHCP`
|
||||
- `ifconfig_em0=SYNCDHCP`
|
||||
- `ifconfig_vtnet0=SYNCDHCP`
|
||||
|
||||
## Assessment
|
||||
|
||||
This is a stronger result than the earlier local-only prototype.
|
||||
|
||||
Fruix now has a real deployment-validated FreeBSD boot mode where:
|
||||
|
||||
- FreeBSD `init(8)` hands off immediately via `init_exec`
|
||||
- the generated Fruix launcher performs the minimal bootstrap
|
||||
- Shepherd becomes PID 1
|
||||
- networking and SSH still work on the real XCP-ng VM
|
||||
- and the system still reaches the Fruix ready marker
|
||||
|
||||
That means the project has now validated both of these boot architectures on the real VM:
|
||||
|
||||
1. `freebsd-init+rc.d-shepherd`
|
||||
2. `shepherd-pid1`
|
||||
|
||||
## Remaining limitations
|
||||
|
||||
This does not yet eliminate the current locally built Guile/Shepherd compatibility-prefix shims.
|
||||
|
||||
Those shims are still needed because the locally staged runtime artifacts continue to embed historical build prefixes. The current result proves that the broader init/boot-manager dependency can be removed, but it does not yet fully solve the store-native runtime-prefix problem.
|
||||
|
||||
## Conclusion
|
||||
|
||||
The Shepherd-as-PID-1 Fruix boot mode now works not only under local QEMU/UEFI, but also on the real operator-approved XCP-ng VM.
|
||||
|
||||
This substantially strengthens the case that Fruix can move beyond the transitional `rc.d` bridge design and toward a more Guix-like PID-1-centered system architecture on FreeBSD.
|
||||
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.
|
||||
4227
docs/reports/progress-log-through-phase18-2-freebsd.md
Normal file
4227
docs/reports/progress-log-through-phase18-2-freebsd.md
Normal file
File diff suppressed because it is too large
Load Diff
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,7 +1,32 @@
|
||||
(define-module (fruix packages freebsd)
|
||||
#:use-module (srfi srfi-1)
|
||||
#:use-module (srfi srfi-9)
|
||||
#:use-module (srfi srfi-13)
|
||||
#:export (freebsd-release
|
||||
freebsd-source
|
||||
freebsd-source?
|
||||
freebsd-source-name
|
||||
freebsd-source-kind
|
||||
freebsd-source-url
|
||||
freebsd-source-path
|
||||
freebsd-source-ref
|
||||
freebsd-source-commit
|
||||
freebsd-source-sha256
|
||||
%default-freebsd-source
|
||||
freebsd-base
|
||||
freebsd-base?
|
||||
freebsd-base-name
|
||||
freebsd-base-version-label
|
||||
freebsd-base-release
|
||||
freebsd-base-branch
|
||||
freebsd-base-source-root
|
||||
freebsd-base-source
|
||||
freebsd-base-target
|
||||
freebsd-base-target-arch
|
||||
freebsd-base-kernconf
|
||||
freebsd-base-make-flags
|
||||
%default-freebsd-base
|
||||
freebsd-package
|
||||
freebsd-package?
|
||||
freebsd-package-name
|
||||
freebsd-package-version
|
||||
@@ -15,6 +40,11 @@
|
||||
freebsd-kernel
|
||||
freebsd-kernel-headers
|
||||
freebsd-libc
|
||||
freebsd-bootloader
|
||||
freebsd-rc-scripts
|
||||
freebsd-runtime
|
||||
freebsd-networking
|
||||
freebsd-openssh
|
||||
freebsd-userland
|
||||
freebsd-clang-toolchain
|
||||
freebsd-gmake
|
||||
@@ -23,8 +53,30 @@
|
||||
freebsd-zlib
|
||||
freebsd-sh
|
||||
freebsd-bash
|
||||
freebsd-native-kernel
|
||||
freebsd-native-world
|
||||
freebsd-native-runtime
|
||||
freebsd-native-bootloader
|
||||
freebsd-native-headers
|
||||
freebsd-native-kernel-for
|
||||
freebsd-native-world-for
|
||||
freebsd-native-runtime-for
|
||||
freebsd-native-bootloader-for
|
||||
freebsd-native-headers-for
|
||||
freebsd-native-system-packages-for
|
||||
freebsd-native-development-profile-packages-for
|
||||
freebsd-native-build-package?
|
||||
freebsd-host-staged-package?
|
||||
%freebsd-native-system-packages
|
||||
%freebsd-native-development-profile-packages
|
||||
%freebsd-host-staged-all-packages
|
||||
%freebsd-host-staged-core-packages
|
||||
%freebsd-host-staged-development-profile-packages
|
||||
%freebsd-host-staged-system-packages
|
||||
%freebsd-host-staged-replacement-order
|
||||
%freebsd-core-packages
|
||||
%freebsd-development-profile-packages))
|
||||
%freebsd-development-profile-packages
|
||||
%freebsd-system-packages))
|
||||
|
||||
(define-record-type <freebsd-package>
|
||||
(make-freebsd-package name version build-system inputs home-page synopsis
|
||||
@@ -47,6 +99,83 @@
|
||||
|
||||
(define freebsd-release "15.0-STABLE")
|
||||
|
||||
(define-record-type <freebsd-source>
|
||||
(make-freebsd-source name kind url path ref commit sha256)
|
||||
freebsd-source?
|
||||
(name freebsd-source-name)
|
||||
(kind freebsd-source-kind)
|
||||
(url freebsd-source-url)
|
||||
(path freebsd-source-path)
|
||||
(ref freebsd-source-ref)
|
||||
(commit freebsd-source-commit)
|
||||
(sha256 freebsd-source-sha256))
|
||||
|
||||
(define* (freebsd-source #:key
|
||||
(name "default")
|
||||
(kind 'local-tree)
|
||||
(url (and (eq? kind 'git) "https://git.FreeBSD.org/src.git"))
|
||||
(path (and (eq? kind 'local-tree) "/usr/src"))
|
||||
(ref #f)
|
||||
(commit #f)
|
||||
(sha256 #f))
|
||||
(make-freebsd-source name kind url path ref commit sha256))
|
||||
|
||||
(define %default-freebsd-source
|
||||
(freebsd-source))
|
||||
|
||||
(define-record-type <freebsd-base>
|
||||
(make-freebsd-base name version-label release branch source-root source target
|
||||
target-arch kernconf make-flags)
|
||||
freebsd-base?
|
||||
(name freebsd-base-name)
|
||||
(version-label freebsd-base-version-label)
|
||||
(release freebsd-base-release)
|
||||
(branch freebsd-base-branch)
|
||||
(source-root freebsd-base-source-root)
|
||||
(source freebsd-base-source)
|
||||
(target freebsd-base-target)
|
||||
(target-arch freebsd-base-target-arch)
|
||||
(kernconf freebsd-base-kernconf)
|
||||
(make-flags freebsd-base-make-flags))
|
||||
|
||||
(define default-native-make-flags
|
||||
'("__MAKE_CONF=/dev/null"
|
||||
"SRCCONF=/dev/null"
|
||||
"SRC_ENV_CONF=/dev/null"
|
||||
"MK_DEBUG_FILES=no"
|
||||
"MK_TESTS=no"))
|
||||
|
||||
(define (default-freebsd-branch release)
|
||||
(let ((major (car (string-split release #\.))))
|
||||
(cond
|
||||
((string-contains release "STABLE")
|
||||
(string-append "stable/" major))
|
||||
((string-contains release "RELEASE")
|
||||
(string-append "releng/" major))
|
||||
(else
|
||||
"unknown"))))
|
||||
|
||||
(define* (freebsd-base #:key
|
||||
(name "default")
|
||||
(version-label freebsd-release)
|
||||
(release freebsd-release)
|
||||
(branch (default-freebsd-branch release))
|
||||
(source #f)
|
||||
(source-root #f)
|
||||
(target "amd64")
|
||||
(target-arch "amd64")
|
||||
(kernconf "GENERIC")
|
||||
(make-flags default-native-make-flags))
|
||||
(let* ((source (or source %default-freebsd-source))
|
||||
(source-root (or source-root
|
||||
(freebsd-source-path source)
|
||||
"/usr/src")))
|
||||
(make-freebsd-base name version-label release branch source-root source target
|
||||
target-arch kernconf make-flags)))
|
||||
|
||||
(define %default-freebsd-base
|
||||
(freebsd-base))
|
||||
|
||||
(define freebsd-kernel
|
||||
(freebsd-package
|
||||
#:name "freebsd-kernel"
|
||||
@@ -91,8 +220,69 @@ and the userland C headers needed for development profiles."
|
||||
#:install-plan
|
||||
'((file "/lib/libc.so.7" "lib/libc.so.7")
|
||||
(file "/lib/libsys.so.7" "lib/libsys.so.7")
|
||||
(file "/lib/libthr.so.3" "lib/libthr.so.3")
|
||||
(file "/lib/libutil.so.10" "lib/libutil.so.10")
|
||||
(file "/lib/libxo.so.0" "lib/libxo.so.0")
|
||||
(file "/lib/libgeom.so.5" "lib/libgeom.so.5")
|
||||
(file "/lib/libc++.so.1" "lib/libc++.so.1")
|
||||
(file "/lib/libcxxrt.so.1" "lib/libcxxrt.so.1")
|
||||
(file "/lib/libgcc_s.so.1" "lib/libgcc_s.so.1")
|
||||
(file "/lib/libm.so.5" "lib/libm.so.5")
|
||||
(file "/lib/libelf.so.2" "lib/libelf.so.2")
|
||||
(file "/lib/libkvm.so.7" "lib/libkvm.so.7")
|
||||
(file "/lib/lib80211.so.1" "lib/lib80211.so.1")
|
||||
(file "/lib/libjail.so.1" "lib/libjail.so.1")
|
||||
(file "/lib/libnv.so.1" "lib/libnv.so.1")
|
||||
(file "/lib/libsbuf.so.6" "lib/libsbuf.so.6")
|
||||
(file "/lib/libbsdxml.so.4" "lib/libbsdxml.so.4")
|
||||
(file "/lib/libcrypt.so.5" "lib/libcrypt.so.5")
|
||||
(file "/lib/libmd.so.7" "lib/libmd.so.7")
|
||||
(file "/lib/libedit.so.8" "lib/libedit.so.8")
|
||||
(file "/lib/libtinfow.so.9" "lib/libtinfow.so.9")
|
||||
(file "/lib/libcasper.so.1" "lib/libcasper.so.1")
|
||||
(file "/lib/libcap_syslog.so.1" "lib/libcap_syslog.so.1")
|
||||
(file "/lib/libcap_fileargs.so.1" "lib/libcap_fileargs.so.1")
|
||||
(file "/lib/libcap_net.so.1" "lib/libcap_net.so.1")
|
||||
(file "/lib/libufs.so.8" "lib/libufs.so.8")
|
||||
(file "/usr/lib/libdevinfo.so.7" "usr/lib/libdevinfo.so.7")
|
||||
(file "/usr/lib/libdevctl.so.5" "usr/lib/libdevctl.so.5")
|
||||
(file "/lib/libz.so.6" "lib/libz.so.6")
|
||||
(file "/lib/libcrypto.so.35" "lib/libcrypto.so.35")
|
||||
(file "/usr/lib/libssl.so.35" "usr/lib/libssl.so.35")
|
||||
(file "/usr/lib/libdl.so.1" "usr/lib/libdl.so.1")
|
||||
(file "/usr/lib/libpam.so.6" "usr/lib/libpam.so.6")
|
||||
(file "/usr/lib/libbsm.so.3" "usr/lib/libbsm.so.3")
|
||||
(file "/usr/lib/libblocklist.so.0" "usr/lib/libblocklist.so.0")
|
||||
(file "/usr/lib/libregex.so.1" "usr/lib/libregex.so.1")
|
||||
(file "/usr/lib/libprivatessh.so.5" "usr/lib/libprivatessh.so.5")
|
||||
(file "/usr/lib/libprivateldns.so.5" "usr/lib/libprivateldns.so.5")
|
||||
(file "/usr/lib/libwrap.so.6" "usr/lib/libwrap.so.6")
|
||||
(file "/usr/lib/libgssapi_krb5.so.122" "usr/lib/libgssapi_krb5.so.122")
|
||||
(file "/usr/lib/libkrb5.so.122" "usr/lib/libkrb5.so.122")
|
||||
(file "/usr/lib/libk5crypto.so.122" "usr/lib/libk5crypto.so.122")
|
||||
(file "/usr/lib/libcom_err.so.122" "usr/lib/libcom_err.so.122")
|
||||
(file "/usr/lib/libkrb5support.so.122" "usr/lib/libkrb5support.so.122")
|
||||
(file "/libexec/ld-elf.so.1" "libexec/ld-elf.so.1"))))
|
||||
|
||||
(define freebsd-bootloader
|
||||
(freebsd-package
|
||||
#:name "freebsd-bootloader"
|
||||
#:version freebsd-release
|
||||
#:build-system 'copy-build-system
|
||||
#:inputs (list freebsd-libc)
|
||||
#:home-page "https://www.freebsd.org/"
|
||||
#:synopsis "Prototype package for FreeBSD loader and boot assets"
|
||||
#:description
|
||||
"Prototype package definition that stages the FreeBSD boot loader and the
|
||||
minimal loader support tree needed for declarative system-closure experiments."
|
||||
#:license 'bsd-2
|
||||
#:install-plan
|
||||
'((file "/boot/loader" "boot/loader")
|
||||
(file "/boot/loader.efi" "boot/loader.efi")
|
||||
(file "/boot/device.hints" "boot/device.hints")
|
||||
(directory "/boot/defaults" "boot/defaults")
|
||||
(directory "/boot/lua" "boot/lua"))))
|
||||
|
||||
(define freebsd-sh
|
||||
(freebsd-package
|
||||
#:name "freebsd-sh"
|
||||
@@ -122,18 +312,161 @@ userland commands needed for development and build experiments."
|
||||
#:license 'bsd-2
|
||||
#:install-plan
|
||||
'((file "/bin/cat" "bin/cat")
|
||||
(file "/bin/chflags" "bin/chflags")
|
||||
(file "/bin/chmod" "bin/chmod")
|
||||
(file "/bin/cp" "bin/cp")
|
||||
(file "/bin/date" "bin/date")
|
||||
(file "/bin/dd" "bin/dd")
|
||||
(file "/bin/echo" "bin/echo")
|
||||
(file "/bin/expr" "bin/expr")
|
||||
(file "/bin/ln" "bin/ln")
|
||||
(file "/bin/ls" "bin/ls")
|
||||
(file "/bin/mkdir" "bin/mkdir")
|
||||
(file "/bin/mv" "bin/mv")
|
||||
(file "/bin/ps" "bin/ps")
|
||||
(file "/bin/pwd" "bin/pwd")
|
||||
(file "/bin/rmdir" "bin/rmdir")
|
||||
(file "/bin/rm" "bin/rm")
|
||||
(file "/bin/sleep" "bin/sleep")
|
||||
(file "/bin/stty" "bin/stty")
|
||||
(file "/bin/sync" "bin/sync")
|
||||
(file "/usr/bin/awk" "usr/bin/awk")
|
||||
(file "/usr/bin/basename" "usr/bin/basename")
|
||||
(file "/usr/bin/cap_mkdb" "usr/bin/cap_mkdb")
|
||||
(file "/usr/bin/cut" "usr/bin/cut")
|
||||
(file "/usr/bin/dirname" "usr/bin/dirname")
|
||||
(file "/usr/bin/egrep" "usr/bin/egrep")
|
||||
(file "/usr/bin/env" "usr/bin/env")
|
||||
(file "/usr/bin/find" "bin/find")
|
||||
(file "/usr/bin/fsync" "usr/bin/fsync")
|
||||
(file "/usr/bin/grep" "usr/bin/grep")
|
||||
(file "/usr/bin/mktemp" "usr/bin/mktemp")
|
||||
(file "/usr/bin/head" "usr/bin/head")
|
||||
(file "/usr/bin/install" "usr/bin/install")
|
||||
(file "/usr/bin/limits" "usr/bin/limits")
|
||||
(file "/usr/bin/logger" "usr/bin/logger")
|
||||
(file "/usr/bin/readlink" "usr/bin/readlink")
|
||||
(file "/usr/bin/sed" "usr/bin/sed")
|
||||
(file "/usr/bin/sort" "usr/bin/sort")
|
||||
(file "/usr/bin/tar" "bin/tar")
|
||||
(file "/usr/bin/tr" "usr/bin/tr")
|
||||
(file "/usr/bin/uname" "usr/bin/uname")
|
||||
(file "/usr/bin/xargs" "bin/xargs"))))
|
||||
|
||||
(define freebsd-rc-scripts
|
||||
(freebsd-package
|
||||
#:name "freebsd-rc-scripts"
|
||||
#:version freebsd-release
|
||||
#:build-system 'copy-build-system
|
||||
#:inputs (list freebsd-sh)
|
||||
#:home-page "https://www.freebsd.org/"
|
||||
#:synopsis "Prototype package for FreeBSD init and rc script assets"
|
||||
#:description
|
||||
"Prototype package definition that stages the FreeBSD init and rc support
|
||||
files needed by the first Fruix system-closure experiments."
|
||||
#:license 'bsd-2
|
||||
#:install-plan
|
||||
'((file "/etc/rc" "etc/rc")
|
||||
(file "/etc/rc.subr" "etc/rc.subr")
|
||||
(file "/etc/rc.shutdown" "etc/rc.shutdown")
|
||||
(file "/etc/devd.conf" "etc/devd.conf")
|
||||
(file "/etc/network.subr" "etc/network.subr")
|
||||
(file "/etc/newsyslog.conf" "etc/newsyslog.conf")
|
||||
(file "/etc/syslog.conf" "etc/syslog.conf")
|
||||
(directory "/etc/rc.d" "etc/rc.d")
|
||||
(directory "/etc/defaults" "etc/defaults"))))
|
||||
|
||||
(define freebsd-runtime
|
||||
(freebsd-package
|
||||
#:name "freebsd-runtime"
|
||||
#:version freebsd-release
|
||||
#:build-system 'copy-build-system
|
||||
#:inputs (list freebsd-libc freebsd-sh freebsd-userland freebsd-rc-scripts)
|
||||
#:home-page "https://www.freebsd.org/"
|
||||
#:synopsis "Prototype package for the minimal FreeBSD runtime"
|
||||
#:description
|
||||
"Prototype package definition that stages the minimal FreeBSD runtime
|
||||
commands needed by the first declarative Fruix system and activation payload
|
||||
experiments."
|
||||
#:license 'bsd-2
|
||||
#:install-plan
|
||||
'((file "/sbin/adjkerntz" "sbin/adjkerntz")
|
||||
(file "/usr/sbin/daemon" "usr/sbin/daemon")
|
||||
(file "/sbin/devd" "sbin/devd")
|
||||
(file "/sbin/devmatch" "sbin/devmatch")
|
||||
(file "/sbin/dmesg" "sbin/dmesg")
|
||||
(file "/sbin/fsck" "sbin/fsck")
|
||||
(file "/sbin/fsck_ufs" "sbin/fsck_ufs")
|
||||
(file "/sbin/gpart" "sbin/gpart")
|
||||
(file "/sbin/init" "sbin/init")
|
||||
(file "/sbin/ifconfig" "sbin/ifconfig")
|
||||
(file "/sbin/md5" "sbin/md5")
|
||||
(file "/sbin/mount" "sbin/mount")
|
||||
(file "/sbin/rcorder" "sbin/rcorder")
|
||||
(file "/sbin/reboot" "sbin/reboot")
|
||||
(file "/sbin/sha256" "sbin/sha256")
|
||||
(file "/sbin/shutdown" "sbin/shutdown")
|
||||
(file "/sbin/swapon" "sbin/swapon")
|
||||
(file "/sbin/sysctl" "sbin/sysctl")
|
||||
(file "/usr/sbin/chown" "usr/sbin/chown")
|
||||
(file "/usr/sbin/cron" "usr/sbin/cron")
|
||||
(file "/usr/sbin/devctl" "usr/sbin/devctl")
|
||||
(file "/usr/sbin/nologin" "usr/sbin/nologin")
|
||||
(file "/usr/sbin/pwd_mkdb" "usr/sbin/pwd_mkdb")
|
||||
(file "/usr/sbin/service" "usr/sbin/service")
|
||||
(file "/usr/sbin/ip6addrctl" "usr/sbin/ip6addrctl")
|
||||
(file "/usr/sbin/newsyslog" "usr/sbin/newsyslog")
|
||||
(file "/usr/sbin/syslogd" "usr/sbin/syslogd")
|
||||
(file "/usr/sbin/utx" "usr/sbin/utx")
|
||||
(file "/usr/bin/id" "usr/bin/id")
|
||||
(file "/sbin/kldload" "sbin/kldload")
|
||||
(file "/sbin/kldstat" "sbin/kldstat")
|
||||
(file "/sbin/devfs" "sbin/devfs")
|
||||
(file "/bin/freebsd-version" "bin/freebsd-version")
|
||||
(file "/bin/hostname" "bin/hostname")
|
||||
(file "/bin/kenv" "bin/kenv")
|
||||
(file "/usr/share/locale/C.UTF-8/LC_CTYPE" "usr/share/locale/C.UTF-8/LC_CTYPE"))))
|
||||
|
||||
(define freebsd-networking
|
||||
(freebsd-package
|
||||
#:name "freebsd-networking"
|
||||
#:version freebsd-release
|
||||
#:build-system 'copy-build-system
|
||||
#:inputs (list freebsd-libc freebsd-runtime freebsd-sh)
|
||||
#:home-page "https://www.freebsd.org/"
|
||||
#:synopsis "Prototype package for FreeBSD network runtime tools"
|
||||
#:description
|
||||
"Prototype package definition that stages the minimal FreeBSD networking
|
||||
runtime needed for DHCP-based boot validation in virtual machines."
|
||||
#:license 'bsd-2
|
||||
#:install-plan
|
||||
'((file "/sbin/dhclient" "sbin/dhclient")
|
||||
(file "/sbin/dhclient-script" "sbin/dhclient-script")
|
||||
(file "/sbin/route" "sbin/route")
|
||||
(file "/usr/bin/netstat" "usr/bin/netstat")
|
||||
(file "/usr/sbin/arp" "usr/sbin/arp"))))
|
||||
|
||||
(define freebsd-openssh
|
||||
(freebsd-package
|
||||
#:name "freebsd-openssh"
|
||||
#:version freebsd-release
|
||||
#:build-system 'copy-build-system
|
||||
#:inputs (list freebsd-libc freebsd-runtime freebsd-sh)
|
||||
#:home-page "https://www.freebsd.org/"
|
||||
#:synopsis "Prototype package for the FreeBSD OpenSSH runtime"
|
||||
#:description
|
||||
"Prototype package definition that stages the FreeBSD OpenSSH server and
|
||||
client tools needed for first-boot operator access on the Fruix prototype
|
||||
track."
|
||||
#:license 'bsd-2
|
||||
#:install-plan
|
||||
'((file "/usr/sbin/sshd" "usr/sbin/sshd")
|
||||
(file "/usr/bin/ssh" "usr/bin/ssh")
|
||||
(file "/usr/bin/ssh-keygen" "usr/bin/ssh-keygen")
|
||||
(file "/usr/libexec/sshd-auth" "usr/libexec/sshd-auth")
|
||||
(file "/usr/libexec/sshd-session" "usr/libexec/sshd-session")
|
||||
(file "/etc/ssh/moduli" "etc/ssh/moduli"))))
|
||||
|
||||
(define freebsd-clang-toolchain
|
||||
(freebsd-package
|
||||
#:name "freebsd-clang-toolchain"
|
||||
@@ -245,7 +578,163 @@ library for profile experiments."
|
||||
#:install-plan
|
||||
'((file "/lib/libz.so.6" "lib/libz.so.6"))))
|
||||
|
||||
(define %freebsd-core-packages
|
||||
(define default-native-world-prune-paths
|
||||
'("usr/share/doc"
|
||||
"usr/share/examples"
|
||||
"usr/share/info"
|
||||
"usr/share/man"
|
||||
"usr/tests"))
|
||||
|
||||
(define default-native-runtime-prune-paths
|
||||
'("boot"
|
||||
"rescue"
|
||||
"usr/include"
|
||||
"usr/lib/debug"
|
||||
"usr/lib32"
|
||||
"usr/obj"
|
||||
"usr/src"
|
||||
"usr/share/doc"
|
||||
"usr/share/examples"
|
||||
"usr/share/info"
|
||||
"usr/share/man"
|
||||
"usr/share/mk"
|
||||
"usr/tests"))
|
||||
|
||||
(define default-native-bootloader-keep-paths
|
||||
'("boot/loader"
|
||||
"boot/loader.efi"
|
||||
"boot/device.hints"
|
||||
"boot/defaults"
|
||||
"boot/lua"))
|
||||
|
||||
(define default-native-headers-keep-paths
|
||||
'("usr/include"
|
||||
"usr/share/mk"))
|
||||
|
||||
(define (freebsd-base-native-plan base)
|
||||
`((base-name . ,(freebsd-base-name base))
|
||||
(base-version-label . ,(freebsd-base-version-label base))
|
||||
(base-release . ,(freebsd-base-release base))
|
||||
(base-branch . ,(freebsd-base-branch base))
|
||||
(base-source-name . ,(freebsd-source-name (freebsd-base-source base)))
|
||||
(base-source-kind . ,(freebsd-source-kind (freebsd-base-source base)))
|
||||
(base-source-url . ,(freebsd-source-url (freebsd-base-source base)))
|
||||
(base-source-path . ,(freebsd-source-path (freebsd-base-source base)))
|
||||
(base-source-ref . ,(freebsd-source-ref (freebsd-base-source base)))
|
||||
(base-source-commit . ,(freebsd-source-commit (freebsd-base-source base)))
|
||||
(base-source-sha256 . ,(freebsd-source-sha256 (freebsd-base-source base)))
|
||||
(source-root . ,(freebsd-base-source-root base))
|
||||
(target . ,(freebsd-base-target base))
|
||||
(target-arch . ,(freebsd-base-target-arch base))
|
||||
(kernconf . ,(freebsd-base-kernconf base))
|
||||
(make-flags . ,(freebsd-base-make-flags base))))
|
||||
|
||||
(define (freebsd-native-plan base extra-fields)
|
||||
(append (freebsd-base-native-plan base) extra-fields))
|
||||
|
||||
(define (freebsd-native-kernel-for base)
|
||||
(freebsd-package
|
||||
#:name "freebsd-native-kernel"
|
||||
#:version (freebsd-base-version-label base)
|
||||
#:build-system 'freebsd-kernel-build-system
|
||||
#:home-page "https://www.freebsd.org/"
|
||||
#:synopsis "Native Fruix-managed FreeBSD kernel artifact"
|
||||
#:description
|
||||
"FreeBSD-specific package definition that builds a kernel from a declared
|
||||
FreeBSD base input and stages the resulting boot/kernel tree as a real Fruix
|
||||
store artifact."
|
||||
#:license 'bsd-2
|
||||
#:install-plan
|
||||
(freebsd-native-plan base '())))
|
||||
|
||||
(define (freebsd-native-world-for base)
|
||||
(freebsd-package
|
||||
#:name "freebsd-native-world"
|
||||
#:version (freebsd-base-version-label base)
|
||||
#:build-system 'freebsd-world-build-system
|
||||
#:home-page "https://www.freebsd.org/"
|
||||
#:synopsis "Native Fruix-managed FreeBSD world artifact"
|
||||
#:description
|
||||
"FreeBSD-specific package definition that builds and installs a broad
|
||||
native world from a declared FreeBSD base input into a real Fruix store
|
||||
artifact."
|
||||
#:license 'bsd-2
|
||||
#:install-plan
|
||||
(freebsd-native-plan base
|
||||
`((prune-paths . ,default-native-world-prune-paths)))))
|
||||
|
||||
(define (freebsd-native-runtime-for base)
|
||||
(freebsd-package
|
||||
#:name "freebsd-native-runtime"
|
||||
#:version (freebsd-base-version-label base)
|
||||
#:build-system 'freebsd-world-build-system
|
||||
#:home-page "https://www.freebsd.org/"
|
||||
#:synopsis "Native Fruix-managed FreeBSD runtime slice"
|
||||
#:description
|
||||
"FreeBSD-specific package definition that stages a runtime-focused slice of
|
||||
installworld/distribution from a declared FreeBSD base input."
|
||||
#:license 'bsd-2
|
||||
#:install-plan
|
||||
(freebsd-native-plan base
|
||||
`((prune-paths . ,default-native-runtime-prune-paths)))))
|
||||
|
||||
(define (freebsd-native-bootloader-for base)
|
||||
(freebsd-package
|
||||
#:name "freebsd-native-bootloader"
|
||||
#:version (freebsd-base-version-label base)
|
||||
#:build-system 'freebsd-world-build-system
|
||||
#:home-page "https://www.freebsd.org/"
|
||||
#:synopsis "Native Fruix-managed FreeBSD boot asset slice"
|
||||
#:description
|
||||
"FreeBSD-specific package definition that stages only the loader and boot
|
||||
support assets needed by the validated Fruix image path from a declared
|
||||
FreeBSD base input."
|
||||
#:license 'bsd-2
|
||||
#:install-plan
|
||||
(freebsd-native-plan base
|
||||
`((keep-paths . ,default-native-bootloader-keep-paths)))))
|
||||
|
||||
(define (freebsd-native-headers-for base)
|
||||
(freebsd-package
|
||||
#:name "freebsd-native-headers"
|
||||
#:version (freebsd-base-version-label base)
|
||||
#:build-system 'freebsd-world-build-system
|
||||
#:home-page "https://www.freebsd.org/"
|
||||
#:synopsis "Native Fruix-managed FreeBSD headers slice"
|
||||
#:description
|
||||
"FreeBSD-specific package definition that stages the userland header set and
|
||||
build-system support files from installworld/distribution for a declared
|
||||
FreeBSD base input."
|
||||
#:license 'bsd-2
|
||||
#:install-plan
|
||||
(freebsd-native-plan base
|
||||
`((keep-paths . ,default-native-headers-keep-paths)))))
|
||||
|
||||
(define freebsd-native-kernel
|
||||
(freebsd-native-kernel-for %default-freebsd-base))
|
||||
|
||||
(define freebsd-native-world
|
||||
(freebsd-native-world-for %default-freebsd-base))
|
||||
|
||||
(define freebsd-native-runtime
|
||||
(freebsd-native-runtime-for %default-freebsd-base))
|
||||
|
||||
(define freebsd-native-bootloader
|
||||
(freebsd-native-bootloader-for %default-freebsd-base))
|
||||
|
||||
(define freebsd-native-headers
|
||||
(freebsd-native-headers-for %default-freebsd-base))
|
||||
|
||||
(define (freebsd-native-build-package? package)
|
||||
(not (not (memq (freebsd-package-build-system package)
|
||||
'(freebsd-kernel-build-system freebsd-world-build-system)))))
|
||||
|
||||
;; Transitional boundary: the FreeBSD base layer below is still staged by
|
||||
;; copying selected artifacts from the builder host. Plan 3 keeps these
|
||||
;; package sets explicit so they can be replaced incrementally by native
|
||||
;; FreeBSD world/kernel build outputs in /frx/store.
|
||||
|
||||
(define %freebsd-host-staged-core-packages
|
||||
(list freebsd-kernel
|
||||
freebsd-kernel-headers
|
||||
freebsd-libc
|
||||
@@ -258,7 +747,7 @@ library for profile experiments."
|
||||
freebsd-sh
|
||||
freebsd-bash))
|
||||
|
||||
(define %freebsd-development-profile-packages
|
||||
(define %freebsd-host-staged-development-profile-packages
|
||||
(list freebsd-kernel
|
||||
freebsd-kernel-headers
|
||||
freebsd-libc
|
||||
@@ -270,3 +759,59 @@ library for profile experiments."
|
||||
freebsd-zlib
|
||||
freebsd-sh
|
||||
freebsd-bash))
|
||||
|
||||
(define %freebsd-host-staged-system-packages
|
||||
(list freebsd-kernel
|
||||
freebsd-bootloader
|
||||
freebsd-libc
|
||||
freebsd-rc-scripts
|
||||
freebsd-runtime
|
||||
freebsd-networking
|
||||
freebsd-openssh
|
||||
freebsd-userland
|
||||
freebsd-sh
|
||||
freebsd-bash))
|
||||
|
||||
(define %freebsd-host-staged-all-packages
|
||||
(delete-duplicates
|
||||
(append %freebsd-host-staged-core-packages
|
||||
%freebsd-host-staged-development-profile-packages
|
||||
%freebsd-host-staged-system-packages)))
|
||||
|
||||
(define (freebsd-host-staged-package? package)
|
||||
(and (not (freebsd-native-build-package? package))
|
||||
(any (lambda (candidate)
|
||||
(string=? (freebsd-package-name candidate)
|
||||
(freebsd-package-name package)))
|
||||
%freebsd-host-staged-all-packages)))
|
||||
|
||||
(define %freebsd-host-staged-replacement-order
|
||||
'((first-wave . (freebsd-kernel freebsd-bootloader))
|
||||
(second-wave . (freebsd-runtime freebsd-libc freebsd-userland freebsd-rc-scripts))
|
||||
(third-wave . (freebsd-networking freebsd-openssh))
|
||||
(fourth-wave . (freebsd-kernel-headers freebsd-clang-toolchain))
|
||||
(fifth-wave . (freebsd-gmake freebsd-autotools freebsd-openssl freebsd-zlib freebsd-sh freebsd-bash))))
|
||||
|
||||
(define (freebsd-native-system-packages-for base)
|
||||
(list (freebsd-native-runtime-for base)))
|
||||
|
||||
(define (freebsd-native-development-profile-packages-for base)
|
||||
(list (freebsd-native-runtime-for base)
|
||||
(freebsd-native-headers-for base)
|
||||
freebsd-clang-toolchain
|
||||
freebsd-gmake
|
||||
freebsd-autotools
|
||||
freebsd-openssl
|
||||
freebsd-zlib
|
||||
freebsd-sh
|
||||
freebsd-bash))
|
||||
|
||||
(define %freebsd-native-system-packages
|
||||
(freebsd-native-system-packages-for %default-freebsd-base))
|
||||
|
||||
(define %freebsd-native-development-profile-packages
|
||||
(freebsd-native-development-profile-packages-for %default-freebsd-base))
|
||||
|
||||
(define %freebsd-core-packages %freebsd-host-staged-core-packages)
|
||||
(define %freebsd-development-profile-packages %freebsd-host-staged-development-profile-packages)
|
||||
(define %freebsd-system-packages %freebsd-host-staged-system-packages)
|
||||
|
||||
87
modules/fruix/system/freebsd.scm
Normal file
87
modules/fruix/system/freebsd.scm
Normal file
@@ -0,0 +1,87 @@
|
||||
(define-module (fruix system freebsd)
|
||||
#:use-module (fruix system freebsd model)
|
||||
#:use-module (fruix system freebsd source)
|
||||
#:use-module (fruix system freebsd executor)
|
||||
#:use-module (fruix system freebsd build)
|
||||
#:use-module (fruix system freebsd media)
|
||||
#:re-export (user-group
|
||||
user-group?
|
||||
user-group-name
|
||||
user-group-gid
|
||||
user-group-system?
|
||||
user-account
|
||||
user-account?
|
||||
user-account-name
|
||||
user-account-uid
|
||||
user-account-group
|
||||
user-account-supplementary-groups
|
||||
user-account-comment
|
||||
user-account-home
|
||||
user-account-shell
|
||||
user-account-system?
|
||||
file-system
|
||||
file-system?
|
||||
file-system-device
|
||||
file-system-mount-point
|
||||
file-system-type
|
||||
file-system-options
|
||||
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-host-name
|
||||
operating-system-freebsd-base
|
||||
operating-system-native-build-result
|
||||
operating-system-kernel
|
||||
operating-system-bootloader
|
||||
operating-system-base-packages
|
||||
operating-system-development-packages
|
||||
operating-system-build-packages
|
||||
operating-system-users
|
||||
operating-system-groups
|
||||
operating-system-file-systems
|
||||
operating-system-services
|
||||
operating-system-loader-entries
|
||||
operating-system-rc-conf-entries
|
||||
operating-system-init-mode
|
||||
operating-system-ready-marker
|
||||
operating-system-root-authorized-keys
|
||||
validate-operating-system
|
||||
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-install-spec
|
||||
operating-system-image-spec
|
||||
operating-system-installer-image-spec
|
||||
operating-system-installer-iso-spec
|
||||
installer-operating-system
|
||||
materialize-operating-system
|
||||
materialize-rootfs
|
||||
install-operating-system
|
||||
materialize-bhyve-image
|
||||
materialize-installer-image
|
||||
materialize-installer-iso
|
||||
default-minimal-operating-system))
|
||||
933
modules/fruix/system/freebsd/build.scm
Normal file
933
modules/fruix/system/freebsd/build.scm
Normal file
@@ -0,0 +1,933 @@
|
||||
(define-module (fruix system freebsd build)
|
||||
#:use-module (fruix packages freebsd)
|
||||
#:use-module (fruix system freebsd model)
|
||||
#:use-module (fruix system freebsd source)
|
||||
#:use-module (fruix system freebsd executor)
|
||||
#:use-module (fruix system freebsd utils)
|
||||
#:use-module (guix build utils)
|
||||
#:use-module (ice-9 format)
|
||||
#:use-module (ice-9 hash-table)
|
||||
#:use-module (ice-9 match)
|
||||
#:use-module (srfi srfi-1)
|
||||
#:use-module (srfi srfi-13)
|
||||
#: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
|
||||
promote-native-build-result
|
||||
materialize-prefix))
|
||||
|
||||
(define (host-freebsd-provenance)
|
||||
(let ((src-git? (file-exists? "/usr/src/.git"))
|
||||
(newvers "/usr/src/sys/conf/newvers.sh"))
|
||||
`((freebsd-release . ,freebsd-release)
|
||||
(freebsd-version-kru . ,(or (safe-command-output "freebsd-version" "-kru") "unknown"))
|
||||
(uname . ,(or (safe-command-output "uname" "-a") "unknown"))
|
||||
(usr-src-path . "/usr/src")
|
||||
(usr-src-git-revision . ,(or (and src-git?
|
||||
(safe-command-output "git" "-C" "/usr/src" "rev-parse" "HEAD"))
|
||||
"absent"))
|
||||
(usr-src-git-branch . ,(or (and src-git?
|
||||
(safe-command-output "git" "-C" "/usr/src" "rev-parse" "--abbrev-ref" "HEAD"))
|
||||
"absent"))
|
||||
(usr-src-newvers-sha256 . ,(if (file-exists? newvers)
|
||||
(file-hash newvers)
|
||||
"absent")))))
|
||||
|
||||
|
||||
(define native-freebsd-build-version "1")
|
||||
|
||||
(define (freebsd-native-build-system? build-system)
|
||||
(not (not (memq build-system '(freebsd-kernel-build-system freebsd-world-build-system)))))
|
||||
|
||||
(define (build-plan-ref plan key default)
|
||||
(match (assoc key plan)
|
||||
((_ . value) value)
|
||||
(#f default)))
|
||||
|
||||
(define (make-flag->pair flag)
|
||||
(match (string-split flag #\=)
|
||||
((name value ...) (cons name (string-join value "=")))
|
||||
((name) (cons name "yes"))
|
||||
(_ (error (format #f "invalid make flag: ~a" flag)))))
|
||||
|
||||
(define (native-build-kernconf-path plan)
|
||||
(or (build-plan-ref plan 'kernconf-path #f)
|
||||
(string-append (build-plan-ref plan 'source-root "/usr/src")
|
||||
"/sys/"
|
||||
(build-plan-ref plan 'target-arch "amd64")
|
||||
"/conf/"
|
||||
(build-plan-ref plan 'kernconf "GENERIC"))))
|
||||
|
||||
(define (native-build-common-manifest plan)
|
||||
(let* ((source-root (build-plan-ref plan 'source-root "/usr/src"))
|
||||
(target (build-plan-ref plan 'target "amd64"))
|
||||
(target-arch (build-plan-ref plan 'target-arch "amd64"))
|
||||
(kernconf (build-plan-ref plan 'kernconf "GENERIC"))
|
||||
(make-flags (build-plan-ref plan 'make-flags '()))
|
||||
(kernconf-path (native-build-kernconf-path plan)))
|
||||
(unless (file-exists? source-root)
|
||||
(error (format #f "native FreeBSD source root does not exist: ~a" source-root)))
|
||||
(unless (file-exists? kernconf-path)
|
||||
(error (format #f "native FreeBSD kernconf does not exist: ~a" kernconf-path)))
|
||||
`((build-version . ,native-freebsd-build-version)
|
||||
(source-root . ,source-root)
|
||||
(source-tree-identity-mode . "mtree:type,link,size,mode,sha256digest")
|
||||
(source-tree-sha256 . ,(or (build-plan-ref plan 'materialized-source-tree-sha256 #f)
|
||||
(native-build-source-tree-sha256 source-root)))
|
||||
(target . ,target)
|
||||
(target-arch . ,target-arch)
|
||||
(kernconf . ,kernconf)
|
||||
(kernconf-path . ,kernconf-path)
|
||||
(kernconf-sha256 . ,(file-hash kernconf-path))
|
||||
(make-flags . ,make-flags))))
|
||||
|
||||
(define (native-build-declared-base plan)
|
||||
`((name . ,(build-plan-ref plan 'base-name "default"))
|
||||
(version-label . ,(build-plan-ref plan 'base-version-label freebsd-release))
|
||||
(release . ,(build-plan-ref plan 'base-release freebsd-release))
|
||||
(branch . ,(build-plan-ref plan 'base-branch "unknown"))))
|
||||
|
||||
(define (native-build-declared-source plan)
|
||||
`((name . ,(build-plan-ref plan 'base-source-name "default"))
|
||||
(kind . ,(build-plan-ref plan 'base-source-kind 'local-tree))
|
||||
(url . ,(build-plan-ref plan 'base-source-url #f))
|
||||
(path . ,(build-plan-ref plan 'base-source-path #f))
|
||||
(ref . ,(build-plan-ref plan 'base-source-ref #f))
|
||||
(commit . ,(build-plan-ref plan 'base-source-commit #f))
|
||||
(sha256 . ,(build-plan-ref plan 'base-source-sha256 #f))))
|
||||
|
||||
(define (native-build-materialized-source plan)
|
||||
`((store-path . ,(build-plan-ref plan 'materialized-source-store #f))
|
||||
(source-root . ,(build-plan-ref plan 'source-root "/usr/src"))
|
||||
(info-file . ,(build-plan-ref plan 'materialized-source-info-file #f))
|
||||
(tree-sha256 . ,(build-plan-ref plan 'materialized-source-tree-sha256 #f))
|
||||
(cache-path . ,(build-plan-ref plan 'materialized-source-cache-path #f))
|
||||
(effective-source . ((kind . ,(build-plan-ref plan 'effective-source-kind #f))
|
||||
(url . ,(build-plan-ref plan 'effective-source-url #f))
|
||||
(path . ,(build-plan-ref plan 'effective-source-path #f))
|
||||
(ref . ,(build-plan-ref plan 'effective-source-ref #f))
|
||||
(commit . ,(build-plan-ref plan 'effective-source-commit #f))
|
||||
(sha256 . ,(build-plan-ref plan 'effective-source-sha256 #f))))))
|
||||
|
||||
(define (native-build-manifest-string package input-paths)
|
||||
(let* ((plan (freebsd-package-install-plan package))
|
||||
(common (native-build-common-manifest plan))
|
||||
(declared-base (native-build-declared-base plan))
|
||||
(declared-source (native-build-declared-source plan))
|
||||
(materialized-source (native-build-materialized-source plan))
|
||||
(keep-paths (build-plan-ref plan 'keep-paths '()))
|
||||
(prune-paths (build-plan-ref plan 'prune-paths '())))
|
||||
(string-append
|
||||
"name=" (freebsd-package-name package) "\n"
|
||||
"version=" (freebsd-package-version package) "\n"
|
||||
"build-system=" (symbol->string (freebsd-package-build-system package)) "\n"
|
||||
"inputs=" (string-join input-paths ",") "\n"
|
||||
"declared-base=\n"
|
||||
(object->string declared-base)
|
||||
"\ndeclared-source=\n"
|
||||
(object->string declared-source)
|
||||
"\nmaterialized-source=\n"
|
||||
(object->string materialized-source)
|
||||
"\nnative-build-common=\n"
|
||||
(object->string common)
|
||||
"\nkeep-paths=\n"
|
||||
(object->string keep-paths)
|
||||
"\nprune-paths=\n"
|
||||
(object->string prune-paths))))
|
||||
|
||||
(define (copy-build-manifest-string package input-paths)
|
||||
(string-append
|
||||
"name=" (freebsd-package-name package) "\n"
|
||||
"version=" (freebsd-package-version package) "\n"
|
||||
"build-system=" (symbol->string (freebsd-package-build-system package)) "\n"
|
||||
"inputs=" (string-join input-paths ",") "\n"
|
||||
"install-plan-signature=\n"
|
||||
(string-join (map install-plan-signature
|
||||
(freebsd-package-install-plan package))
|
||||
"\n")))
|
||||
|
||||
(define (package-manifest-string package input-paths)
|
||||
(if (freebsd-native-build-system? (freebsd-package-build-system package))
|
||||
(native-build-manifest-string package input-paths)
|
||||
(copy-build-manifest-string package input-paths)))
|
||||
|
||||
(define (current-build-jobs)
|
||||
(or (getenv "FRUIX_FREEBSD_BUILD_JOBS")
|
||||
(safe-command-output "sysctl" "-n" "hw.ncpu")
|
||||
"1"))
|
||||
|
||||
(define (native-build-root common)
|
||||
(string-append "/var/tmp/fruix-freebsd-native-build-"
|
||||
(sha256-string (object->string common))))
|
||||
|
||||
(define (native-make-arguments common _build-root)
|
||||
(append
|
||||
(list "-C" (assoc-ref common 'source-root)
|
||||
(string-append "TARGET=" (assoc-ref common 'target))
|
||||
(string-append "TARGET_ARCH=" (assoc-ref common 'target-arch))
|
||||
(string-append "KERNCONF=" (assoc-ref common 'kernconf)))
|
||||
(assoc-ref common 'make-flags)))
|
||||
|
||||
(define* (make-command-string common build-root target #:key (parallel? #f) (destdir #f))
|
||||
(string-join
|
||||
(append
|
||||
(list "env" (string-append "MAKEOBJDIRPREFIX=" build-root "/obj") "make")
|
||||
(if parallel?
|
||||
(list (string-append "-j" (current-build-jobs)))
|
||||
'())
|
||||
(native-make-arguments common build-root)
|
||||
(if destdir
|
||||
(list (string-append "DESTDIR=" destdir))
|
||||
'())
|
||||
(list target))
|
||||
" "))
|
||||
|
||||
(define (run-command/log log-file command)
|
||||
(mkdir-p (dirname log-file))
|
||||
(let ((status (system* "sh" "-c" (string-append command " >" log-file " 2>&1"))))
|
||||
(unless (zero? status)
|
||||
(error (format #f "command failed; see ~a: ~a" log-file command)))))
|
||||
|
||||
(define (ensure-native-build-root common build-root)
|
||||
(mkdir-p build-root)
|
||||
(mkdir-p (string-append build-root "/logs"))
|
||||
(mkdir-p (string-append build-root "/stamps"))
|
||||
(write-file (string-append build-root "/build-parameters.scm")
|
||||
(object->string common)))
|
||||
|
||||
(define (ensure-native-buildworld common build-root)
|
||||
(let ((stamp (string-append build-root "/stamps/buildworld.done")))
|
||||
(ensure-native-build-root common build-root)
|
||||
(unless (file-exists? stamp)
|
||||
(run-command/log (string-append build-root "/logs/buildworld.log")
|
||||
(make-command-string common build-root "buildworld" #:parallel? #t))
|
||||
(write-file stamp "ok\n"))))
|
||||
|
||||
(define (ensure-native-buildkernel common build-root)
|
||||
(let ((stamp (string-append build-root "/stamps/buildkernel-" (assoc-ref common 'kernconf) ".done")))
|
||||
(ensure-native-buildworld common build-root)
|
||||
(unless (file-exists? stamp)
|
||||
(run-command/log (string-append build-root "/logs/buildkernel-" (assoc-ref common 'kernconf) ".log")
|
||||
(make-command-string common build-root "buildkernel" #:parallel? #t))
|
||||
(write-file stamp "ok\n"))))
|
||||
|
||||
(define (prune-stage-paths stage-root paths)
|
||||
(for-each (lambda (path)
|
||||
(delete-path-if-exists (string-append stage-root "/" path)))
|
||||
paths))
|
||||
|
||||
(define (select-stage-paths stage-root paths)
|
||||
(let ((selected-root (string-append stage-root ".selected")))
|
||||
(delete-path-if-exists selected-root)
|
||||
(mkdir-p selected-root)
|
||||
(for-each (lambda (path)
|
||||
(let ((source (string-append stage-root "/" path))
|
||||
(target (string-append selected-root "/" path)))
|
||||
(unless (or (file-exists? source)
|
||||
(false-if-exception (readlink source)))
|
||||
(error (format #f "native stage path is missing: ~a" source)))
|
||||
(copy-node source target)))
|
||||
paths)
|
||||
selected-root))
|
||||
(define (native-build-output-metadata package common build-root stage-root)
|
||||
(let ((plan (freebsd-package-install-plan package)))
|
||||
`((package . ,(freebsd-package-name package))
|
||||
(version . ,(freebsd-package-version package))
|
||||
(declared-base . ,(native-build-declared-base plan))
|
||||
(declared-source . ,(native-build-declared-source plan))
|
||||
(materialized-source . ,(native-build-materialized-source plan))
|
||||
(build-system . ,(freebsd-package-build-system package))
|
||||
(source-root . ,(assoc-ref common 'source-root))
|
||||
(source-tree-sha256 . ,(assoc-ref common 'source-tree-sha256))
|
||||
(target . ,(assoc-ref common 'target))
|
||||
(target-arch . ,(assoc-ref common 'target-arch))
|
||||
(kernconf . ,(assoc-ref common 'kernconf))
|
||||
(kernconf-path . ,(assoc-ref common 'kernconf-path))
|
||||
(kernconf-sha256 . ,(assoc-ref common 'kernconf-sha256))
|
||||
(make-flags . ,(assoc-ref common 'make-flags))
|
||||
(keep-paths . ,(build-plan-ref plan 'keep-paths '()))
|
||||
(prune-paths . ,(build-plan-ref plan 'prune-paths '()))
|
||||
(build-root . ,build-root)
|
||||
(stage-root . ,stage-root)
|
||||
(buildworld-log . ,(string-append build-root "/logs/buildworld.log"))
|
||||
(buildkernel-log . ,(string-append build-root "/logs/buildkernel-" (assoc-ref common 'kernconf) ".log"))
|
||||
(install-log . ,(string-append build-root "/logs/install-" (freebsd-package-name package) ".log")))))
|
||||
|
||||
(define (materialize-native-freebsd-package package input-paths manifest output-path)
|
||||
(let* ((plan (freebsd-package-install-plan package))
|
||||
(common (native-build-common-manifest plan))
|
||||
(build-root (native-build-root common))
|
||||
(stage-root (string-append build-root "/stage-" (freebsd-package-name package) "-" (sha256-string manifest)))
|
||||
(install-log (string-append build-root "/logs/install-" (freebsd-package-name package) ".log"))
|
||||
(final-stage-root
|
||||
(case (freebsd-package-build-system package)
|
||||
((freebsd-world-build-system)
|
||||
(ensure-native-buildworld common build-root)
|
||||
(delete-path-if-exists stage-root)
|
||||
(mkdir-p stage-root)
|
||||
(run-command/log install-log
|
||||
(string-append (make-command-string common build-root "installworld" #:destdir stage-root)
|
||||
" && "
|
||||
(make-command-string common build-root "distribution" #:destdir stage-root)))
|
||||
(let* ((keep-paths (build-plan-ref plan 'keep-paths '()))
|
||||
(selected-root (if (null? keep-paths)
|
||||
stage-root
|
||||
(select-stage-paths stage-root keep-paths))))
|
||||
(prune-stage-paths selected-root (build-plan-ref plan 'prune-paths '()))
|
||||
selected-root))
|
||||
((freebsd-kernel-build-system)
|
||||
(ensure-native-buildkernel common build-root)
|
||||
(delete-path-if-exists stage-root)
|
||||
(mkdir-p stage-root)
|
||||
(run-command/log install-log
|
||||
(make-command-string common build-root "installkernel" #:destdir stage-root))
|
||||
stage-root)
|
||||
(else
|
||||
(error (format #f "unsupported native FreeBSD build system: ~a"
|
||||
(freebsd-package-build-system package)))))))
|
||||
(mkdir-p output-path)
|
||||
(stage-tree-into-output final-stage-root output-path)
|
||||
(write-file (string-append output-path "/.references")
|
||||
(string-join input-paths "\n"))
|
||||
(write-file (string-append output-path "/.fruix-package") manifest)
|
||||
(write-file (string-append output-path "/.freebsd-native-build-info.scm")
|
||||
(object->string (native-build-output-metadata package common build-root final-stage-root)))))
|
||||
|
||||
(define (package-with-install-plan package install-plan)
|
||||
(freebsd-package
|
||||
#:name (freebsd-package-name package)
|
||||
#:version (freebsd-package-version package)
|
||||
#:build-system (freebsd-package-build-system package)
|
||||
#:inputs (freebsd-package-inputs package)
|
||||
#:home-page (freebsd-package-home-page package)
|
||||
#:synopsis (freebsd-package-synopsis package)
|
||||
#:description (freebsd-package-description package)
|
||||
#:license (freebsd-package-license package)
|
||||
#:install-plan install-plan))
|
||||
|
||||
(define (plan-freebsd-source plan)
|
||||
(freebsd-source #:name (build-plan-ref plan 'base-source-name "default")
|
||||
#:kind (build-plan-ref plan 'base-source-kind 'local-tree)
|
||||
#:url (build-plan-ref plan 'base-source-url #f)
|
||||
#:path (build-plan-ref plan 'base-source-path #f)
|
||||
#:ref (build-plan-ref plan 'base-source-ref #f)
|
||||
#:commit (build-plan-ref plan 'base-source-commit #f)
|
||||
#:sha256 (build-plan-ref plan 'base-source-sha256 #f)))
|
||||
|
||||
(define (source-cache-key source)
|
||||
(sha256-string (object->string (freebsd-source-spec source))))
|
||||
|
||||
(define (materialize-freebsd-source/cached source store-dir source-cache)
|
||||
(let* ((key (source-cache-key source))
|
||||
(cached (hash-ref source-cache key #f)))
|
||||
(or cached
|
||||
(let ((result (materialize-freebsd-source source #:store-dir store-dir)))
|
||||
(hash-set! source-cache key result)
|
||||
result))))
|
||||
|
||||
(define (plan-with-materialized-source plan source-result)
|
||||
(let* ((effective (assoc-ref source-result 'effective-source))
|
||||
(overrides
|
||||
`((source-root . ,(assoc-ref source-result 'source-root))
|
||||
(materialized-source-store . ,(assoc-ref source-result 'source-store-path))
|
||||
(materialized-source-info-file . ,(assoc-ref source-result 'source-info-file))
|
||||
(materialized-source-tree-sha256 . ,(assoc-ref source-result 'source-tree-sha256))
|
||||
(materialized-source-cache-path . ,(assoc-ref source-result 'cache-path))
|
||||
(effective-source-kind . ,(assoc-ref effective 'kind))
|
||||
(effective-source-url . ,(assoc-ref effective 'url))
|
||||
(effective-source-path . ,(assoc-ref effective 'path))
|
||||
(effective-source-ref . ,(assoc-ref effective 'ref))
|
||||
(effective-source-commit . ,(assoc-ref effective 'commit))
|
||||
(effective-source-sha256 . ,(assoc-ref effective 'sha256)))))
|
||||
(append overrides plan)))
|
||||
|
||||
(define* (materialize-freebsd-package package store-dir cache #:optional source-cache)
|
||||
(if (existing-store-package? package)
|
||||
(validate-existing-store-package package)
|
||||
(let* ((source-cache (or source-cache (make-hash-table)))
|
||||
(input-paths (map (lambda (input)
|
||||
(materialize-freebsd-package input store-dir cache source-cache))
|
||||
(freebsd-package-inputs package)))
|
||||
(prepared-package
|
||||
(if (freebsd-native-build-package? package)
|
||||
(let* ((source (plan-freebsd-source (freebsd-package-install-plan package)))
|
||||
(source-result (materialize-freebsd-source/cached source store-dir source-cache))
|
||||
(plan (plan-with-materialized-source (freebsd-package-install-plan package)
|
||||
source-result)))
|
||||
(package-with-install-plan package plan))
|
||||
package))
|
||||
(effective-input-paths
|
||||
(if (freebsd-native-build-package? package)
|
||||
(cons (build-plan-ref (freebsd-package-install-plan prepared-package)
|
||||
'materialized-source-store
|
||||
#f)
|
||||
input-paths)
|
||||
input-paths))
|
||||
(effective-input-paths (filter identity effective-input-paths))
|
||||
(manifest (package-manifest-string prepared-package effective-input-paths))
|
||||
(cache-key (sha256-string manifest))
|
||||
(cached (hash-ref cache cache-key #f)))
|
||||
(if cached
|
||||
cached
|
||||
(let* ((display-name (string-append (freebsd-package-name prepared-package)
|
||||
"-"
|
||||
(freebsd-package-version prepared-package)))
|
||||
(output-path (make-store-path store-dir display-name manifest
|
||||
#:kind 'freebsd-package)))
|
||||
(unless (file-exists? output-path)
|
||||
(case (freebsd-package-build-system prepared-package)
|
||||
((copy-build-system)
|
||||
(mkdir-p output-path)
|
||||
(for-each (lambda (entry)
|
||||
(materialize-plan-entry output-path entry))
|
||||
(freebsd-package-install-plan prepared-package))
|
||||
(write-file (string-append output-path "/.references")
|
||||
(string-join effective-input-paths "\n"))
|
||||
(write-file (string-append output-path "/.fruix-package") manifest))
|
||||
((freebsd-world-build-system freebsd-kernel-build-system)
|
||||
(materialize-native-freebsd-package prepared-package effective-input-paths manifest output-path))
|
||||
(else
|
||||
(error (format #f "unsupported package build system: ~a"
|
||||
(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)
|
||||
(cond
|
||||
((string=? name "fruix-guile-extra")
|
||||
(rewrite-text-file
|
||||
(string-append output-path "/share/guile/site/3.0/fibers/config.scm")
|
||||
'(("((getenv \"FIBERS_BUILD_DIR\")\n => (lambda (builddir) (in-vicinity builddir \".libs\")))\n (else \"/tmp/guile-gnutls-freebsd-validate-install/lib/guile/3.0/extensions\"))"
|
||||
. "((getenv \"FIBERS_BUILD_DIR\")\n => (lambda (builddir) (in-vicinity builddir \".libs\")))\n ((getenv \"GUILE_EXTENSIONS_PATH\"))\n (else \"/usr/local/lib/guile/3.0/extensions\"))")))
|
||||
(rewrite-text-file
|
||||
(string-append output-path "/share/guile/site/3.0/gnutls.scm")
|
||||
'(("\"/tmp/guile-gnutls-freebsd-validate-install/lib/guile/3.0/extensions\""
|
||||
. "(or (getenv \"GUILE_EXTENSIONS_PATH\") \"/usr/local/lib/guile/3.0/extensions\")")))
|
||||
(delete-file-if-exists (string-append output-path "/lib/guile/3.0/site-ccache/fibers/config.go"))
|
||||
(delete-file-if-exists (string-append output-path "/lib/guile/3.0/site-ccache/gnutls.go")))
|
||||
((string=? name "fruix-shepherd-runtime")
|
||||
(rewrite-text-file
|
||||
(string-append output-path "/share/guile/site/3.0/shepherd/config.scm")
|
||||
'(("(define Prefix-dir \"/tmp/shepherd-freebsd-validate-install\")"
|
||||
. "(define Prefix-dir \"/frx\")")
|
||||
("(define %localstatedir \"/tmp/shepherd-freebsd-validate-install/var\")"
|
||||
. "(define %localstatedir \"/var\")")
|
||||
("(define %runstatedir \"/tmp/shepherd-freebsd-validate-install/var/run\")"
|
||||
. "(define %runstatedir \"/var/run\")")
|
||||
("(define %sysconfdir \"/tmp/shepherd-freebsd-validate-install/etc\")"
|
||||
. "(define %sysconfdir \"/etc\")")
|
||||
("(define %localedir \"/tmp/shepherd-freebsd-validate-install/share/locale\")"
|
||||
. "(define %localedir \"/usr/share/locale\")")
|
||||
("(define %pkglibdir \"/tmp/shepherd-freebsd-validate-install/lib/shepherd\")"
|
||||
. "(define %pkglibdir \"/usr/local/lib/shepherd\")")))
|
||||
(delete-file-if-exists (string-append output-path "/lib/guile/3.0/site-ccache/shepherd/config.go"))))
|
||||
#t)
|
||||
|
||||
(define prefix-materializer-version "3")
|
||||
|
||||
(define (prefix-manifest-string source-path extra-files)
|
||||
(string-append
|
||||
"prefix-materializer-version=" prefix-materializer-version "\n"
|
||||
"prefix-source=" source-path "\n"
|
||||
(path-signature source-path)
|
||||
(if (null? extra-files)
|
||||
""
|
||||
(string-append
|
||||
"\nextra-files=\n"
|
||||
(string-join
|
||||
(map (lambda (entry)
|
||||
(string-append (cdr entry) "\n" (path-signature (car entry))))
|
||||
extra-files)
|
||||
"\n")))))
|
||||
|
||||
(define (copy-extra-node source destination)
|
||||
(let ((kind (stat:type (lstat source))))
|
||||
(mkdir-p (dirname destination))
|
||||
(case kind
|
||||
((symlink)
|
||||
(unless (or (file-exists? destination)
|
||||
(false-if-exception (readlink destination)))
|
||||
(let ((target (readlink source)))
|
||||
(symlink target destination)
|
||||
(unless (string-prefix? "/" target)
|
||||
(copy-extra-node (string-append (dirname source) "/" target)
|
||||
(string-append (dirname destination) "/" target))))))
|
||||
(else
|
||||
(unless (file-exists? destination)
|
||||
(copy-node source destination))))))
|
||||
|
||||
(define* (materialize-prefix source-path name version store-dir #:key (extra-files '()))
|
||||
(let* ((manifest (prefix-manifest-string source-path extra-files))
|
||||
(display-name (string-append name "-" version))
|
||||
(output-path (make-store-path store-dir display-name manifest
|
||||
#:kind 'prefix)))
|
||||
(unless (file-exists? output-path)
|
||||
(mkdir-p output-path)
|
||||
(for-each (lambda (entry)
|
||||
(copy-node (string-append source-path "/" entry)
|
||||
(string-append output-path "/" entry)))
|
||||
(directory-entries source-path))
|
||||
(for-each (lambda (entry)
|
||||
(copy-extra-node (car entry)
|
||||
(string-append output-path "/" (cdr entry))))
|
||||
extra-files)
|
||||
(sanitize-materialized-prefix name output-path)
|
||||
(write-file (string-append output-path "/.fruix-package") manifest))
|
||||
output-path))
|
||||
|
||||
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)))))
|
||||
1627
modules/fruix/system/freebsd/media.scm
Normal file
1627
modules/fruix/system/freebsd/media.scm
Normal file
File diff suppressed because it is too large
Load Diff
446
modules/fruix/system/freebsd/model.scm
Normal file
446
modules/fruix/system/freebsd/model.scm
Normal file
@@ -0,0 +1,446 @@
|
||||
(define-module (fruix system freebsd model)
|
||||
#:use-module (fruix packages freebsd)
|
||||
#:use-module (ice-9 match)
|
||||
#:use-module (srfi srfi-1)
|
||||
#:use-module (srfi srfi-9)
|
||||
#:use-module (srfi srfi-13)
|
||||
#:export (user-group
|
||||
user-group?
|
||||
user-group-name
|
||||
user-group-gid
|
||||
user-group-system?
|
||||
user-account
|
||||
user-account?
|
||||
user-account-name
|
||||
user-account-uid
|
||||
user-account-group
|
||||
user-account-supplementary-groups
|
||||
user-account-comment
|
||||
user-account-home
|
||||
user-account-shell
|
||||
user-account-system?
|
||||
file-system
|
||||
file-system?
|
||||
file-system-device
|
||||
file-system-mount-point
|
||||
file-system-type
|
||||
file-system-options
|
||||
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-host-name
|
||||
operating-system-freebsd-base
|
||||
operating-system-native-build-result
|
||||
operating-system-kernel
|
||||
operating-system-bootloader
|
||||
operating-system-base-packages
|
||||
operating-system-development-packages
|
||||
operating-system-build-packages
|
||||
operating-system-users
|
||||
operating-system-groups
|
||||
operating-system-file-systems
|
||||
operating-system-services
|
||||
operating-system-loader-entries
|
||||
operating-system-rc-conf-entries
|
||||
operating-system-init-mode
|
||||
operating-system-ready-marker
|
||||
operating-system-root-authorized-keys
|
||||
default-minimal-operating-system
|
||||
freebsd-source-spec
|
||||
freebsd-base-spec
|
||||
validate-freebsd-source
|
||||
validate-operating-system
|
||||
pid1-init-mode?
|
||||
effective-loader-entries
|
||||
rc-conf-entry-value
|
||||
sshd-enabled?
|
||||
operating-system-generated-file-names
|
||||
operating-system-closure-spec))
|
||||
|
||||
(define-record-type <user-group>
|
||||
(make-user-group name gid system?)
|
||||
user-group?
|
||||
(name user-group-name)
|
||||
(gid user-group-gid)
|
||||
(system? user-group-system?))
|
||||
|
||||
(define* (user-group #:key name gid (system? #t))
|
||||
(make-user-group name gid system?))
|
||||
|
||||
(define-record-type <user-account>
|
||||
(make-user-account name uid group supplementary-groups comment home shell system?)
|
||||
user-account?
|
||||
(name user-account-name)
|
||||
(uid user-account-uid)
|
||||
(group user-account-group)
|
||||
(supplementary-groups user-account-supplementary-groups)
|
||||
(comment user-account-comment)
|
||||
(home user-account-home)
|
||||
(shell user-account-shell)
|
||||
(system? user-account-system?))
|
||||
|
||||
(define* (user-account #:key name uid group (supplementary-groups '())
|
||||
(comment "Fruix user") (home "/nonexistent")
|
||||
(shell "/usr/sbin/nologin") (system? #t))
|
||||
(make-user-account name uid group supplementary-groups comment home shell system?))
|
||||
|
||||
(define-record-type <file-system>
|
||||
(make-file-system device mount-point type options needed-for-boot?)
|
||||
file-system?
|
||||
(device file-system-device)
|
||||
(mount-point file-system-mount-point)
|
||||
(type file-system-type)
|
||||
(options file-system-options)
|
||||
(needed-for-boot? file-system-needed-for-boot?))
|
||||
|
||||
(define* (file-system #:key device mount-point type (options "rw")
|
||||
(needed-for-boot? #f))
|
||||
(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>
|
||||
(make-operating-system host-name freebsd-base native-build-result kernel bootloader
|
||||
base-packages development-packages build-packages users groups file-systems
|
||||
services loader-entries rc-conf-entries init-mode ready-marker
|
||||
root-authorized-keys)
|
||||
operating-system?
|
||||
(host-name operating-system-host-name)
|
||||
(freebsd-base operating-system-freebsd-base)
|
||||
(native-build-result operating-system-native-build-result)
|
||||
(kernel operating-system-kernel)
|
||||
(bootloader operating-system-bootloader)
|
||||
(base-packages operating-system-base-packages)
|
||||
(development-packages operating-system-development-packages)
|
||||
(build-packages operating-system-build-packages)
|
||||
(users operating-system-users)
|
||||
(groups operating-system-groups)
|
||||
(file-systems operating-system-file-systems)
|
||||
(services operating-system-services)
|
||||
(loader-entries operating-system-loader-entries)
|
||||
(rc-conf-entries operating-system-rc-conf-entries)
|
||||
(init-mode operating-system-init-mode)
|
||||
(ready-marker operating-system-ready-marker)
|
||||
(root-authorized-keys operating-system-root-authorized-keys))
|
||||
|
||||
(define* (operating-system #:key
|
||||
(host-name "fruix-freebsd")
|
||||
(freebsd-base %default-freebsd-base)
|
||||
(native-build-result #f)
|
||||
(kernel freebsd-kernel)
|
||||
(bootloader freebsd-bootloader)
|
||||
(base-packages %freebsd-system-packages)
|
||||
(development-packages '())
|
||||
(build-packages '())
|
||||
(users (list (user-account #:name "root"
|
||||
#:uid 0
|
||||
#:group "wheel"
|
||||
#:comment "Charlie &"
|
||||
#:home "/root"
|
||||
#:shell "/bin/sh"
|
||||
#:system? #t)
|
||||
(user-account #:name "operator"
|
||||
#:uid 1000
|
||||
#:group "operator"
|
||||
#:supplementary-groups '("wheel")
|
||||
#:comment "Fruix Operator"
|
||||
#:home "/home/operator"
|
||||
#:shell "/bin/sh"
|
||||
#:system? #f)))
|
||||
(groups (list (user-group #:name "wheel" #:gid 0 #:system? #t)
|
||||
(user-group #:name "operator" #:gid 1000 #:system? #f)))
|
||||
(file-systems (list (file-system #:device "/dev/ufs/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"
|
||||
#:needed-for-boot? #f)))
|
||||
(services '(shepherd ready-marker))
|
||||
(loader-entries '(("autoboot_delay" . "1")
|
||||
("console" . "comconsole")))
|
||||
(rc-conf-entries '(("clear_tmp_enable" . "YES")
|
||||
("sendmail_enable" . "NONE")
|
||||
("sshd_enable" . "NO")))
|
||||
(init-mode 'freebsd-init+rc.d-shepherd)
|
||||
(ready-marker "/var/lib/fruix/ready")
|
||||
(root-authorized-keys '()))
|
||||
(make-operating-system host-name freebsd-base native-build-result kernel bootloader
|
||||
base-packages development-packages build-packages users groups file-systems
|
||||
services loader-entries rc-conf-entries init-mode ready-marker
|
||||
root-authorized-keys))
|
||||
|
||||
(define default-minimal-operating-system (operating-system))
|
||||
|
||||
(define (package-names packages)
|
||||
(map freebsd-package-name packages))
|
||||
|
||||
(define (freebsd-source-spec source)
|
||||
`((name . ,(freebsd-source-name source))
|
||||
(kind . ,(freebsd-source-kind source))
|
||||
(url . ,(freebsd-source-url source))
|
||||
(path . ,(freebsd-source-path source))
|
||||
(ref . ,(freebsd-source-ref source))
|
||||
(commit . ,(freebsd-source-commit source))
|
||||
(sha256 . ,(freebsd-source-sha256 source))))
|
||||
|
||||
(define (freebsd-base-spec base)
|
||||
`((name . ,(freebsd-base-name base))
|
||||
(version-label . ,(freebsd-base-version-label base))
|
||||
(release . ,(freebsd-base-release base))
|
||||
(branch . ,(freebsd-base-branch base))
|
||||
(source-root . ,(freebsd-base-source-root base))
|
||||
(source . ,(freebsd-source-spec (freebsd-base-source base)))
|
||||
(target . ,(freebsd-base-target base))
|
||||
(target-arch . ,(freebsd-base-target-arch base))
|
||||
(kernconf . ,(freebsd-base-kernconf base))
|
||||
(make-flags . ,(freebsd-base-make-flags base))))
|
||||
|
||||
|
||||
(define (duplicate-elements values)
|
||||
(let loop ((rest values) (seen '()) (duplicates '()))
|
||||
(match rest
|
||||
(() (reverse duplicates))
|
||||
((head . tail)
|
||||
(if (member head seen)
|
||||
(loop tail seen (if (member head duplicates) duplicates (cons head duplicates)))
|
||||
(loop tail (cons head seen) duplicates))))))
|
||||
|
||||
(define (non-empty-string? value)
|
||||
(and (string? value)
|
||||
(not (string-null? value))))
|
||||
|
||||
(define (validate-freebsd-source source)
|
||||
(unless (freebsd-source? source)
|
||||
(error "freebsd base source must be a <freebsd-source> record"))
|
||||
(let ((kind (freebsd-source-kind source)))
|
||||
(unless (member kind '(local-tree git src-txz))
|
||||
(error "unsupported freebsd source kind" kind))
|
||||
(case kind
|
||||
((local-tree)
|
||||
(unless (non-empty-string? (freebsd-source-path source))
|
||||
(error "local-tree freebsd source must declare a path" source)))
|
||||
((git)
|
||||
(unless (non-empty-string? (freebsd-source-url source))
|
||||
(error "git freebsd source must declare a URL" source))
|
||||
(unless (or (non-empty-string? (freebsd-source-ref source))
|
||||
(non-empty-string? (freebsd-source-commit source)))
|
||||
(error "git freebsd source must declare a ref or commit" source)))
|
||||
((src-txz)
|
||||
(unless (non-empty-string? (freebsd-source-url source))
|
||||
(error "src-txz freebsd source must declare a URL" source))
|
||||
(unless (non-empty-string? (freebsd-source-sha256 source))
|
||||
(error "src-txz freebsd source must declare a sha256" source)))))
|
||||
#t)
|
||||
|
||||
(define (validate-operating-system os)
|
||||
(let* ((host-name (operating-system-host-name 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))
|
||||
(groups (operating-system-groups os))
|
||||
(file-systems (operating-system-file-systems os))
|
||||
(user-names (map user-account-name users))
|
||||
(group-names (map user-group-name groups))
|
||||
(mount-points (map file-system-mount-point file-systems))
|
||||
(init-mode (operating-system-init-mode os)))
|
||||
(when (string-null? host-name)
|
||||
(error "operating-system host-name must not be empty"))
|
||||
(unless (freebsd-base? base)
|
||||
(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))
|
||||
(let ((dups (duplicate-elements user-names)))
|
||||
(unless (null? dups)
|
||||
(error "duplicate user names in operating-system" dups)))
|
||||
(let ((dups (duplicate-elements group-names)))
|
||||
(unless (null? dups)
|
||||
(error "duplicate group names in operating-system" dups)))
|
||||
(unless (member "/" mount-points)
|
||||
(error "operating-system must declare a root file-system"))
|
||||
(unless (member "root" user-names)
|
||||
(error "operating-system must declare a root user"))
|
||||
(unless (member "wheel" group-names)
|
||||
(error "operating-system must declare a wheel group"))
|
||||
(unless (member init-mode '(freebsd-init+rc.d-shepherd shepherd-pid1))
|
||||
(error "unsupported operating-system init-mode" init-mode))
|
||||
#t))
|
||||
|
||||
(define (pid1-init-mode? os)
|
||||
(eq? (operating-system-init-mode os) 'shepherd-pid1))
|
||||
|
||||
(define (effective-loader-entries os)
|
||||
(append (if (pid1-init-mode? os)
|
||||
'(("init_exec" . "/run/current-system/boot/fruix-pid1"))
|
||||
'())
|
||||
(operating-system-loader-entries os)))
|
||||
|
||||
(define (rc-conf-entry-value os key)
|
||||
(let ((entry (assoc key (operating-system-rc-conf-entries os))))
|
||||
(and entry (cdr entry))))
|
||||
|
||||
(define (sshd-enabled? os)
|
||||
(let ((value (rc-conf-entry-value os "sshd_enable")))
|
||||
(and value
|
||||
(member (string-upcase value) '("YES" "TRUE" "1")))))
|
||||
|
||||
|
||||
(define (operating-system-generated-file-names os)
|
||||
(append
|
||||
'("boot/loader.conf"
|
||||
"etc/rc.conf"
|
||||
"etc/fstab"
|
||||
"etc/hosts"
|
||||
"etc/passwd"
|
||||
"etc/master.passwd"
|
||||
"etc/group"
|
||||
"etc/login.conf"
|
||||
"etc/shells"
|
||||
"etc/motd"
|
||||
"etc/ttys"
|
||||
"metadata/freebsd-base.scm"
|
||||
"metadata/host-base-provenance.scm"
|
||||
"metadata/store-layout.scm"
|
||||
"metadata/system-declaration.scm"
|
||||
"metadata/system-declaration-info.scm"
|
||||
"metadata/system-declaration-system"
|
||||
"activate"
|
||||
"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)
|
||||
'("boot/fruix-pid1")
|
||||
'())
|
||||
(if (sshd-enabled? os)
|
||||
'("etc/ssh/sshd_config")
|
||||
'())
|
||||
(if (null? (operating-system-root-authorized-keys os))
|
||||
'()
|
||||
'("root/.ssh/authorized_keys"))))
|
||||
|
||||
|
||||
(define (operating-system-closure-spec os)
|
||||
(validate-operating-system os)
|
||||
`((host-name . ,(operating-system-host-name 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)))
|
||||
(bootloader-package . ,(freebsd-package-name (operating-system-bootloader os)))
|
||||
(base-package-count . ,(length (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)))
|
||||
(users . ,(map user-account-name (operating-system-users os)))
|
||||
(group-count . ,(length (operating-system-groups os)))
|
||||
(groups . ,(map user-group-name (operating-system-groups os)))
|
||||
(file-system-count . ,(length (operating-system-file-systems os)))
|
||||
(file-systems . ,(map (lambda (fs)
|
||||
`((device . ,(file-system-device fs))
|
||||
(mount-point . ,(file-system-mount-point fs))
|
||||
(type . ,(file-system-type fs))
|
||||
(options . ,(file-system-options fs))
|
||||
(needed-for-boot? . ,(file-system-needed-for-boot? fs))))
|
||||
(operating-system-file-systems os)))
|
||||
(services . ,(operating-system-services os))
|
||||
(generated-files . ,(operating-system-generated-file-names os))
|
||||
(init-mode . ,(operating-system-init-mode os))
|
||||
(ready-marker . ,(operating-system-ready-marker os))))
|
||||
|
||||
1233
modules/fruix/system/freebsd/render.scm
Normal file
1233
modules/fruix/system/freebsd/render.scm
Normal file
File diff suppressed because it is too large
Load Diff
204
modules/fruix/system/freebsd/source.scm
Normal file
204
modules/fruix/system/freebsd/source.scm
Normal file
@@ -0,0 +1,204 @@
|
||||
(define-module (fruix system freebsd source)
|
||||
#:use-module (fruix packages freebsd)
|
||||
#:use-module (fruix system freebsd model)
|
||||
#:use-module (fruix system freebsd utils)
|
||||
#:use-module (guix build utils)
|
||||
#:use-module (srfi srfi-13)
|
||||
#:export (materialize-freebsd-source
|
||||
freebsd-source-materialization-spec))
|
||||
|
||||
(define freebsd-source-materializer-version "2")
|
||||
|
||||
(define (string-downcase* value)
|
||||
(list->string (map char-downcase (string->list value))))
|
||||
|
||||
(define (safe-name-fragment value)
|
||||
(let* ((text (if (and (string? value) (not (string-null? value))) value "source"))
|
||||
(chars (map (lambda (ch)
|
||||
(if (or (char-alphabetic? ch)
|
||||
(char-numeric? ch)
|
||||
(memv ch '(#\- #\_ #\.)))
|
||||
ch
|
||||
#\-))
|
||||
(string->list text))))
|
||||
(list->string chars)))
|
||||
|
||||
(define (freebsd-source-manifest source effective-source identity)
|
||||
(string-append
|
||||
"materializer-version=" freebsd-source-materializer-version "\n"
|
||||
"declared-source=\n"
|
||||
(object->string (freebsd-source-spec source))
|
||||
"\neffective-source=\n"
|
||||
(object->string (freebsd-source-spec effective-source))
|
||||
"\nidentity=\n"
|
||||
(object->string identity)))
|
||||
|
||||
(define (ensure-git-source-cache source cache-dir)
|
||||
(let* ((url (freebsd-source-url source))
|
||||
(repo-dir (string-append cache-dir "/git/"
|
||||
(sha256-string (string-append "git:" url))
|
||||
".git")))
|
||||
(mkdir-p (dirname repo-dir))
|
||||
(unless (file-exists? repo-dir)
|
||||
(unless (zero? (system* "git" "init" "--quiet" "--bare" repo-dir))
|
||||
(error "failed to initialize git source cache" repo-dir))
|
||||
(unless (zero? (system* "git" "-C" repo-dir "remote" "add" "origin" url))
|
||||
(error "failed to add git source remote" url)))
|
||||
(let ((current-url (safe-command-output "git" "-C" repo-dir "remote" "get-url" "origin")))
|
||||
(unless (and current-url (string=? current-url url))
|
||||
(unless (zero? (system* "git" "-C" repo-dir "remote" "set-url" "origin" url))
|
||||
(error "failed to update git source remote" url))))
|
||||
repo-dir))
|
||||
|
||||
(define (resolve-git-freebsd-source source cache-dir)
|
||||
(let* ((selector (or (freebsd-source-commit source)
|
||||
(freebsd-source-ref source)
|
||||
(error "git freebsd source requires a ref or commit" source)))
|
||||
(repo-dir (ensure-git-source-cache source cache-dir)))
|
||||
(unless (zero? (system* "git" "-C" repo-dir "fetch" "--quiet" "--depth" "1" "origin" selector))
|
||||
(error "failed to fetch git freebsd source" selector))
|
||||
(let ((resolved-commit (command-output "git" "-C" repo-dir "rev-parse" "FETCH_HEAD")))
|
||||
`((cache-path . ,repo-dir)
|
||||
(effective-source . ,(freebsd-source #:name (freebsd-source-name source)
|
||||
#:kind 'git
|
||||
#:url (freebsd-source-url source)
|
||||
#:ref (freebsd-source-ref source)
|
||||
#:commit resolved-commit
|
||||
#:sha256 #f))
|
||||
(identity . ((resolved-commit . ,resolved-commit)))
|
||||
(populate-tree . ,(lambda (tree-root)
|
||||
(let ((archive-path (string-append (dirname tree-root) "/git-export.tar")))
|
||||
(unless (zero? (system* "git" "-C" repo-dir "archive"
|
||||
"--format=tar" "-o" archive-path resolved-commit))
|
||||
(error "failed to archive git freebsd source" resolved-commit))
|
||||
(unless (zero? (system* "tar" "-xpf" archive-path "-C" tree-root))
|
||||
(error "failed to extract archived git freebsd source" archive-path))
|
||||
(delete-path-if-exists archive-path))))))))
|
||||
|
||||
(define (normalize-expected-sha256 source)
|
||||
(let ((sha256 (freebsd-source-sha256 source)))
|
||||
(and sha256 (string-downcase* sha256))))
|
||||
|
||||
(define (resolve-txz-freebsd-source source cache-dir)
|
||||
(let* ((url (freebsd-source-url source))
|
||||
(expected-sha256 (or (normalize-expected-sha256 source)
|
||||
(error "src-txz freebsd source requires sha256 for materialization" source)))
|
||||
(archive-path (string-append cache-dir "/archives/"
|
||||
(sha256-string (string-append "txz:" url))
|
||||
"-src.txz")))
|
||||
(mkdir-p (dirname archive-path))
|
||||
(when (file-exists? archive-path)
|
||||
(let ((actual (string-downcase* (file-hash archive-path))))
|
||||
(unless (string=? actual expected-sha256)
|
||||
(delete-file archive-path))))
|
||||
(unless (file-exists? archive-path)
|
||||
(unless (zero? (system* "fetch" "-q" "-o" archive-path url))
|
||||
(error "failed to download FreeBSD src.txz source" url)))
|
||||
(let ((actual-sha256 (string-downcase* (file-hash archive-path))))
|
||||
(unless (string=? actual-sha256 expected-sha256)
|
||||
(error "downloaded src.txz hash mismatch" url expected-sha256 actual-sha256))
|
||||
`((cache-path . ,archive-path)
|
||||
(effective-source . ,(freebsd-source #:name (freebsd-source-name source)
|
||||
#:kind 'src-txz
|
||||
#:url url
|
||||
#:path #f
|
||||
#:ref #f
|
||||
#:commit #f
|
||||
#:sha256 actual-sha256))
|
||||
(identity . ((archive-sha256 . ,actual-sha256)))
|
||||
(populate-tree . ,(lambda (tree-root)
|
||||
(unless (zero? (system* "tar" "-xpf" archive-path "-C" tree-root))
|
||||
(error "failed to extract FreeBSD src.txz source" archive-path))))))))
|
||||
|
||||
(define (resolve-local-freebsd-source source)
|
||||
(let* ((path (freebsd-source-path source))
|
||||
(tree-sha256 (native-build-source-tree-sha256 path)))
|
||||
`((cache-path . #f)
|
||||
(effective-source . ,(freebsd-source #:name (freebsd-source-name source)
|
||||
#:kind 'local-tree
|
||||
#:url #f
|
||||
#:path path
|
||||
#:ref #f
|
||||
#:commit #f
|
||||
#:sha256 tree-sha256))
|
||||
(identity . ((tree-sha256 . ,tree-sha256)))
|
||||
(populate-tree . ,(lambda (tree-root)
|
||||
(copy-tree-contents path tree-root))))))
|
||||
|
||||
(define (detect-materialized-source-relative-root tree-root)
|
||||
(cond
|
||||
((file-exists? (string-append tree-root "/Makefile"))
|
||||
"tree")
|
||||
((file-exists? (string-append tree-root "/usr/src/Makefile"))
|
||||
"tree/usr/src")
|
||||
(else
|
||||
"tree")))
|
||||
|
||||
(define* (materialize-freebsd-source source #:key
|
||||
(store-dir "/frx/store")
|
||||
(cache-dir "/frx/var/cache/fruix/freebsd-source"))
|
||||
(validate-freebsd-source source)
|
||||
(let* ((resolution (case (freebsd-source-kind source)
|
||||
((local-tree)
|
||||
(resolve-local-freebsd-source source))
|
||||
((git)
|
||||
(resolve-git-freebsd-source source cache-dir))
|
||||
((src-txz)
|
||||
(resolve-txz-freebsd-source source cache-dir))
|
||||
(else
|
||||
(error "unsupported freebsd source kind" (freebsd-source-kind source)))))
|
||||
(effective-source (assoc-ref resolution 'effective-source))
|
||||
(identity (assoc-ref resolution 'identity))
|
||||
(manifest (freebsd-source-manifest source effective-source identity))
|
||||
(display-name (string-append "freebsd-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"))
|
||||
(cache-path (assoc-ref resolution 'cache-path))
|
||||
(populate-tree (assoc-ref resolution 'populate-tree)))
|
||||
(unless (file-exists? output-path)
|
||||
(let* ((temp-output (string-append output-path ".tmp"))
|
||||
(temp-tree-root (string-append temp-output "/tree")))
|
||||
(delete-path-if-exists temp-output)
|
||||
(mkdir-p temp-tree-root)
|
||||
(populate-tree temp-tree-root)
|
||||
(let* ((relative-root (detect-materialized-source-relative-root temp-tree-root))
|
||||
(source-root (string-append output-path "/" relative-root))
|
||||
(temp-source-root (string-append temp-output "/" relative-root))
|
||||
(tree-sha256 (native-build-source-tree-sha256 temp-source-root)))
|
||||
(write-file (string-append temp-output "/.references") "")
|
||||
(write-file (string-append temp-output "/.fruix-source") manifest)
|
||||
(write-file (string-append temp-output "/.freebsd-source-info.scm")
|
||||
(object->string
|
||||
`((materializer-version . ,freebsd-source-materializer-version)
|
||||
(declared-source . ,(freebsd-source-spec source))
|
||||
(effective-source . ,(freebsd-source-spec effective-source))
|
||||
(identity . ,identity)
|
||||
(source-store . ,output-path)
|
||||
(source-root . ,source-root)
|
||||
(source-tree-sha256 . ,tree-sha256)
|
||||
(cache-path . ,cache-path)))))
|
||||
(rename-file temp-output output-path)))
|
||||
(call-with-input-file info-file
|
||||
(lambda (port)
|
||||
(let* ((info (read port))
|
||||
(effective (assoc-ref info 'effective-source)))
|
||||
`((source-store-path . ,output-path)
|
||||
(source-root . ,(assoc-ref info 'source-root))
|
||||
(source-info-file . ,info-file)
|
||||
(source-tree-sha256 . ,(assoc-ref info 'source-tree-sha256))
|
||||
(cache-path . ,(assoc-ref info 'cache-path))
|
||||
(effective-source . ,effective)
|
||||
(effective-commit . ,(assoc-ref effective 'commit))
|
||||
(effective-sha256 . ,(assoc-ref effective 'sha256))))))))
|
||||
|
||||
|
||||
(define (freebsd-source-materialization-spec result)
|
||||
`((source-store-path . ,(assoc-ref result 'source-store-path))
|
||||
(source-root . ,(assoc-ref result 'source-root))
|
||||
(source-info-file . ,(assoc-ref result 'source-info-file))
|
||||
(source-tree-sha256 . ,(assoc-ref result 'source-tree-sha256))
|
||||
(cache-path . ,(assoc-ref result 'cache-path))
|
||||
(effective-source . ,(assoc-ref result 'effective-source))))
|
||||
|
||||
304
modules/fruix/system/freebsd/utils.scm
Normal file
304
modules/fruix/system/freebsd/utils.scm
Normal file
@@ -0,0 +1,304 @@
|
||||
(define-module (fruix system freebsd utils)
|
||||
#:use-module (guix build utils)
|
||||
#:use-module (ice-9 ftw)
|
||||
#:use-module (ice-9 format)
|
||||
#:use-module (ice-9 match)
|
||||
#:use-module (ice-9 popen)
|
||||
#:use-module (ice-9 hash-table)
|
||||
#:use-module (srfi srfi-1)
|
||||
#:use-module (srfi srfi-13)
|
||||
#:use-module (rnrs io ports)
|
||||
#:export (getenv*
|
||||
trim-trailing-newlines
|
||||
command-output
|
||||
safe-command-output
|
||||
write-file
|
||||
sha256-string
|
||||
store-hash-string
|
||||
make-store-path
|
||||
file-hash
|
||||
directory-entries
|
||||
path-signature
|
||||
tree-content-signature
|
||||
install-plan-signature
|
||||
native-build-source-tree-sha256
|
||||
copy-regular-file
|
||||
copy-node
|
||||
materialize-plan-entry
|
||||
delete-path-if-exists
|
||||
stage-tree-into-output
|
||||
string-replace-all
|
||||
rewrite-text-file
|
||||
delete-file-if-exists
|
||||
copy-tree-contents
|
||||
path-basename
|
||||
read-lines
|
||||
run-command
|
||||
store-reference-closure
|
||||
copy-store-items-into-rootfs
|
||||
copy-rootfs-for-image
|
||||
mktemp-directory))
|
||||
|
||||
(define (getenv* name default)
|
||||
(or (getenv name) default))
|
||||
|
||||
(define (trim-trailing-newlines str)
|
||||
(let loop ((len (string-length str)))
|
||||
(if (and (> len 0)
|
||||
(char=? (string-ref str (- len 1)) #\newline))
|
||||
(loop (- len 1))
|
||||
(substring str 0 len))))
|
||||
|
||||
(define (command-output program . args)
|
||||
(let* ((port (apply open-pipe* OPEN_READ program args))
|
||||
(output (get-string-all port))
|
||||
(status (close-pipe port)))
|
||||
(unless (zero? status)
|
||||
(error (format #f "command failed: ~a ~s => ~a" program args status)))
|
||||
(trim-trailing-newlines output)))
|
||||
|
||||
(define (safe-command-output program . args)
|
||||
(false-if-exception (apply command-output program args)))
|
||||
|
||||
(define (write-file path content)
|
||||
(mkdir-p (dirname path))
|
||||
(call-with-output-file path
|
||||
(lambda (port)
|
||||
(display content port))))
|
||||
|
||||
(define (sha256-string text)
|
||||
(let* ((tmp (string-append (getenv* "TMPDIR" "/tmp") "/fruix-system-hash.txt")))
|
||||
(write-file tmp text)
|
||||
(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)
|
||||
(command-output "sha256" "-q" path))
|
||||
|
||||
|
||||
(define (directory-entries path)
|
||||
(sort (filter (lambda (entry)
|
||||
(not (member entry '("." ".."))))
|
||||
(scandir path))
|
||||
string<?))
|
||||
|
||||
(define (path-signature path)
|
||||
(let ((st (lstat path)))
|
||||
(case (stat:type st)
|
||||
((regular)
|
||||
(string-append "file:" path ":" (file-hash path)))
|
||||
((symlink)
|
||||
(string-append "symlink:" path ":" (readlink path)))
|
||||
((directory)
|
||||
(string-join
|
||||
(cons (string-append "directory:" path)
|
||||
(apply append
|
||||
(map (lambda (entry)
|
||||
(list (path-signature (string-append path "/" entry))))
|
||||
(directory-entries path))))
|
||||
"\n"))
|
||||
(else
|
||||
(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)
|
||||
(match entry
|
||||
(('file source target)
|
||||
(string-append "file-target:" target "\n" (path-signature source)))
|
||||
(('directory source target)
|
||||
(string-append "directory-target:" target "\n" (path-signature source)))
|
||||
(_
|
||||
(error (format #f "unsupported install plan entry: ~s" entry)))))
|
||||
|
||||
(define (native-build-source-tree-sha256 source-root)
|
||||
(let* ((mtree-output (command-output "mtree" "-c" "-k" "type,link,size,mode,sha256digest" "-p" source-root))
|
||||
(stable-lines (filter (lambda (line)
|
||||
(not (string-prefix? "#" line)))
|
||||
(string-split mtree-output #\newline))))
|
||||
(sha256-string (string-join stable-lines "\n"))))
|
||||
|
||||
(define (copy-regular-file source destination)
|
||||
(let ((mode (stat:perms (stat source))))
|
||||
(copy-file source destination)
|
||||
(chmod destination mode)))
|
||||
|
||||
(define (copy-node source destination)
|
||||
(let ((kind (stat:type (lstat source))))
|
||||
(mkdir-p (dirname destination))
|
||||
(case kind
|
||||
((directory)
|
||||
(mkdir-p destination)
|
||||
(for-each (lambda (entry)
|
||||
(copy-node (string-append source "/" entry)
|
||||
(string-append destination "/" entry)))
|
||||
(directory-entries source)))
|
||||
((symlink)
|
||||
(symlink (readlink source) destination))
|
||||
(else
|
||||
(copy-regular-file source destination)))))
|
||||
|
||||
(define (materialize-plan-entry output-path entry)
|
||||
(match entry
|
||||
(('file source target)
|
||||
(copy-node source (string-append output-path "/" target)))
|
||||
(('directory source target)
|
||||
(copy-node source (string-append output-path "/" target)))
|
||||
(_
|
||||
(error (format #f "unsupported install plan entry: ~s" entry)))))
|
||||
|
||||
(define (clear-file-flags path)
|
||||
(false-if-exception (system* "chflags" "-R" "noschg,nouchg" path)))
|
||||
|
||||
(define (delete-path-if-exists path)
|
||||
(when (or (file-exists? path) (false-if-exception (readlink path)))
|
||||
(clear-file-flags path)
|
||||
(let ((kind (stat:type (lstat path))))
|
||||
(case kind
|
||||
((directory) (delete-file-recursively path))
|
||||
(else (delete-file path))))))
|
||||
|
||||
(define (stage-tree-into-output stage-root output-path)
|
||||
(mkdir-p output-path)
|
||||
(for-each (lambda (entry)
|
||||
(copy-node (string-append stage-root "/" entry)
|
||||
(string-append output-path "/" entry)))
|
||||
(directory-entries stage-root)))
|
||||
|
||||
|
||||
(define (string-replace-all str old new)
|
||||
(let ((old-len (string-length old)))
|
||||
(let loop ((start 0) (chunks '()))
|
||||
(let ((index (string-contains str old start)))
|
||||
(if index
|
||||
(loop (+ index old-len)
|
||||
(cons new
|
||||
(cons (substring str start index) chunks)))
|
||||
(apply string-append
|
||||
(reverse (cons (substring str start) chunks))))))))
|
||||
|
||||
(define (rewrite-text-file path replacements)
|
||||
(when (file-exists? path)
|
||||
(let* ((mode (stat:perms (stat path)))
|
||||
(original (call-with-input-file path get-string-all))
|
||||
(updated (fold (lambda (replacement text)
|
||||
(string-replace-all text (car replacement) (cdr replacement)))
|
||||
original
|
||||
replacements)))
|
||||
(unless (string=? original updated)
|
||||
(write-file path updated)
|
||||
(chmod path mode)))))
|
||||
|
||||
(define (delete-file-if-exists path)
|
||||
(when (file-exists? path)
|
||||
(delete-file path)))
|
||||
|
||||
|
||||
(define (copy-tree-contents source-root target-root)
|
||||
(mkdir-p target-root)
|
||||
(for-each (lambda (entry)
|
||||
(copy-node (string-append source-root "/" entry)
|
||||
(string-append target-root "/" entry)))
|
||||
(directory-entries source-root)))
|
||||
|
||||
|
||||
(define (path-basename path)
|
||||
(let ((parts (filter (lambda (part) (not (string-null? part)))
|
||||
(string-split path #\/))))
|
||||
(if (null? parts)
|
||||
path
|
||||
(last parts))))
|
||||
|
||||
(define (read-lines path)
|
||||
(if (file-exists? path)
|
||||
(filter (lambda (line) (not (string-null? line)))
|
||||
(string-split (call-with-input-file path get-string-all) #\newline))
|
||||
'()))
|
||||
|
||||
(define (run-command . args)
|
||||
(let ((status (apply system* args)))
|
||||
(unless (zero? status)
|
||||
(error "command failed" args status))
|
||||
#t))
|
||||
|
||||
(define (store-reference-closure roots)
|
||||
(let ((seen (make-hash-table))
|
||||
(result '()))
|
||||
(define (visit item)
|
||||
(unless (hash-ref seen item #f)
|
||||
(hash-set! seen item #t)
|
||||
(set! result (cons item result))
|
||||
(for-each visit (read-lines (string-append item "/.references")))))
|
||||
(for-each visit roots)
|
||||
(reverse result)))
|
||||
|
||||
(define (copy-store-items-into-rootfs rootfs store-dir items)
|
||||
(let ((store-root (string-append rootfs store-dir)))
|
||||
(mkdir-p store-root)
|
||||
(for-each (lambda (item)
|
||||
(copy-node item (string-append store-root "/" (path-basename item))))
|
||||
items)))
|
||||
|
||||
(define (copy-rootfs-for-image source-rootfs image-rootfs)
|
||||
(when (file-exists? image-rootfs)
|
||||
(delete-file-recursively image-rootfs))
|
||||
(copy-node source-rootfs image-rootfs))
|
||||
|
||||
(define (mktemp-directory pattern)
|
||||
(command-output "mktemp" "-d" pattern))
|
||||
|
||||
787
scripts/fruix.scm
Normal file
787
scripts/fruix.scm
Normal file
@@ -0,0 +1,787 @@
|
||||
#!/tmp/guile-freebsd-validate-install/bin/guile -s
|
||||
!#
|
||||
|
||||
(use-modules (fruix system freebsd)
|
||||
(fruix packages freebsd)
|
||||
(ice-9 format)
|
||||
(ice-9 match)
|
||||
(srfi srfi-1)
|
||||
(srfi srfi-13)
|
||||
(rnrs io ports))
|
||||
|
||||
(define (usage code)
|
||||
(format (if (= code 0) #t (current-error-port))
|
||||
"Usage: fruix COMMAND ...\n\
|
||||
\n\
|
||||
Commands:\n\
|
||||
system ACTION ... Build or materialize Fruix system artifacts.\n\
|
||||
source ACTION ... Fetch or snapshot declarative FreeBSD source inputs.\n\
|
||||
native-build ACTION ... Promote native build results into Fruix store objects.\n\
|
||||
\n\
|
||||
System actions:\n\
|
||||
build Materialize the Fruix system closure in /frx/store.\n\
|
||||
image Materialize the Fruix disk image in /frx/store.\n\
|
||||
installer Materialize a bootable Fruix installer image in /frx/store.\n\
|
||||
installer-iso Materialize a bootable Fruix installer ISO in /frx/store.\n\
|
||||
install Install the Fruix system onto --target PATH.\n\
|
||||
rootfs Materialize a rootfs tree at --rootfs DIR or ROOTFS-DIR.\n\
|
||||
\n\
|
||||
System options:\n\
|
||||
--system NAME Scheme variable holding the operating-system object.\n\
|
||||
--store DIR Store directory to use (default: /frx/store).\n\
|
||||
--disk-capacity SIZE Disk capacity for 'image', 'installer', or raw-file 'install' targets.\n\
|
||||
--root-size SIZE Root filesystem size for 'image', 'installer', 'installer-iso', or 'install' (example: 6g).\n\
|
||||
--target PATH Install target for 'install' (raw image file or /dev/... device).\n\
|
||||
--install-target-device DEVICE\n\
|
||||
Target block device used by the booted 'installer' environment.\n\
|
||||
--rootfs DIR Rootfs target for 'rootfs'.\n\
|
||||
\n\
|
||||
Source actions:\n\
|
||||
materialize Materialize a declared FreeBSD source tree in /frx/store.\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 NAME Scheme variable holding the freebsd-source object.\n\
|
||||
--store DIR Store directory to use (default: /frx/store).\n\
|
||||
--cache DIR Cache directory to use (default: /frx/var/cache/fruix/freebsd-source).\n\
|
||||
\n\
|
||||
Common options:\n\
|
||||
--help Show this help.\n")
|
||||
(exit code))
|
||||
|
||||
(define (option-value arg prefix)
|
||||
(and (string-prefix? prefix arg)
|
||||
(substring arg (string-length prefix))))
|
||||
|
||||
(define (stringify value)
|
||||
(cond ((string? value) value)
|
||||
((symbol? value) (symbol->string value))
|
||||
((number? value) (number->string value))
|
||||
((boolean? value) (if value "true" "false"))
|
||||
(else (call-with-output-string (lambda (port) (write value port))))))
|
||||
|
||||
(define (emit-metadata fields)
|
||||
(for-each (lambda (field)
|
||||
(format #t "~a=~a~%" (car field) (stringify (cdr field))))
|
||||
fields))
|
||||
|
||||
(define (read-file-string file)
|
||||
(call-with-input-file file get-string-all))
|
||||
|
||||
(define (lookup-bound-value module symbol)
|
||||
(let ((var (module-variable module symbol)))
|
||||
(and var (variable-ref var))))
|
||||
|
||||
(define candidate-operating-system-symbols
|
||||
'(operating-system
|
||||
phase16-operating-system
|
||||
phase15-operating-system
|
||||
phase10-operating-system
|
||||
phase9-operating-system
|
||||
phase8-operating-system
|
||||
phase7-operating-system
|
||||
default-operating-system
|
||||
os))
|
||||
|
||||
(define candidate-freebsd-source-symbols
|
||||
'(phase16-source
|
||||
declared-source
|
||||
source
|
||||
src))
|
||||
|
||||
(define (resolve-operating-system-symbol module requested)
|
||||
(or requested
|
||||
(find (lambda (symbol)
|
||||
(let ((value (lookup-bound-value module symbol)))
|
||||
(and value (operating-system? value))))
|
||||
candidate-operating-system-symbols)
|
||||
(error "could not infer operating-system variable; use --system NAME")))
|
||||
|
||||
(define (resolve-freebsd-source-symbol module requested)
|
||||
(or requested
|
||||
(find (lambda (symbol)
|
||||
(let ((value (lookup-bound-value module symbol)))
|
||||
(and value (freebsd-source? value))))
|
||||
candidate-freebsd-source-symbols)
|
||||
(error "could not infer freebsd-source variable; use --source NAME")))
|
||||
|
||||
(define (load-operating-system-from-file file requested-symbol)
|
||||
(unless (file-exists? file)
|
||||
(error "operating-system file does not exist" file))
|
||||
(primitive-load file)
|
||||
(let* ((module (current-module))
|
||||
(symbol (resolve-operating-system-symbol module requested-symbol))
|
||||
(value (lookup-bound-value module symbol)))
|
||||
(unless (and value (operating-system? value))
|
||||
(error "resolved variable is not an operating-system" symbol))
|
||||
(validate-operating-system value)
|
||||
(values value symbol)))
|
||||
|
||||
(define (load-freebsd-source-from-file file requested-symbol)
|
||||
(unless (file-exists? file)
|
||||
(error "freebsd-source file does not exist" file))
|
||||
(primitive-load file)
|
||||
(let* ((module (current-module))
|
||||
(symbol (resolve-freebsd-source-symbol module requested-symbol))
|
||||
(value (lookup-bound-value module symbol)))
|
||||
(unless (and value (freebsd-source? value))
|
||||
(error "resolved variable is not a freebsd-source" symbol))
|
||||
(values value symbol)))
|
||||
|
||||
(define (parse-system-arguments action rest)
|
||||
(let loop ((args rest)
|
||||
(positional '())
|
||||
(system-name #f)
|
||||
(store-dir "/frx/store")
|
||||
(disk-capacity #f)
|
||||
(root-size #f)
|
||||
(target #f)
|
||||
(install-target-device #f)
|
||||
(rootfs #f))
|
||||
(match args
|
||||
(()
|
||||
(let ((positional (reverse positional)))
|
||||
`((command . "system")
|
||||
(action . ,action)
|
||||
(positional . ,positional)
|
||||
(system-name . ,system-name)
|
||||
(store-dir . ,store-dir)
|
||||
(disk-capacity . ,disk-capacity)
|
||||
(root-size . ,root-size)
|
||||
(target . ,target)
|
||||
(install-target-device . ,install-target-device)
|
||||
(rootfs . ,rootfs))))
|
||||
(("--help")
|
||||
(usage 0))
|
||||
(((? (lambda (arg) (string-prefix? "--system=" arg)) arg) . tail)
|
||||
(loop tail positional (option-value arg "--system=") store-dir disk-capacity root-size target install-target-device rootfs))
|
||||
(("--system" value . tail)
|
||||
(loop tail positional value store-dir disk-capacity root-size target install-target-device rootfs))
|
||||
(((? (lambda (arg) (string-prefix? "--store=" arg)) arg) . tail)
|
||||
(loop tail positional system-name (option-value arg "--store=") disk-capacity root-size target install-target-device rootfs))
|
||||
(("--store" value . tail)
|
||||
(loop tail positional system-name value disk-capacity root-size target install-target-device rootfs))
|
||||
(((? (lambda (arg) (string-prefix? "--disk-capacity=" arg)) arg) . tail)
|
||||
(loop tail positional system-name store-dir (option-value arg "--disk-capacity=") root-size target install-target-device rootfs))
|
||||
(("--disk-capacity" value . tail)
|
||||
(loop tail positional system-name store-dir value root-size target install-target-device rootfs))
|
||||
(((? (lambda (arg) (string-prefix? "--root-size=" arg)) arg) . tail)
|
||||
(loop tail positional system-name store-dir disk-capacity (option-value arg "--root-size=") target install-target-device rootfs))
|
||||
(("--root-size" value . tail)
|
||||
(loop tail positional system-name store-dir disk-capacity value target install-target-device rootfs))
|
||||
(((? (lambda (arg) (string-prefix? "--target=" arg)) arg) . tail)
|
||||
(loop tail positional system-name store-dir disk-capacity root-size (option-value arg "--target=") install-target-device rootfs))
|
||||
(("--target" value . tail)
|
||||
(loop tail positional system-name store-dir disk-capacity root-size value install-target-device rootfs))
|
||||
(((? (lambda (arg) (string-prefix? "--install-target-device=" arg)) arg) . tail)
|
||||
(loop tail positional system-name store-dir disk-capacity root-size target
|
||||
(option-value arg "--install-target-device=") rootfs))
|
||||
(("--install-target-device" value . tail)
|
||||
(loop tail positional system-name store-dir disk-capacity root-size target value rootfs))
|
||||
(((? (lambda (arg) (string-prefix? "--rootfs=" arg)) arg) . tail)
|
||||
(loop tail positional system-name store-dir disk-capacity root-size target install-target-device
|
||||
(option-value arg "--rootfs=")))
|
||||
(("--rootfs" value . tail)
|
||||
(loop tail positional system-name store-dir disk-capacity root-size target install-target-device value))
|
||||
(((? (lambda (arg) (string-prefix? "--" arg)) arg) . _)
|
||||
(error "unknown option" arg))
|
||||
((arg . tail)
|
||||
(loop tail (cons arg positional) system-name store-dir disk-capacity root-size target install-target-device rootfs)))))
|
||||
|
||||
(define (parse-source-arguments action rest)
|
||||
(let loop ((args rest)
|
||||
(positional '())
|
||||
(source-name #f)
|
||||
(store-dir "/frx/store")
|
||||
(cache-dir "/frx/var/cache/fruix/freebsd-source"))
|
||||
(match args
|
||||
(()
|
||||
(let ((positional (reverse positional)))
|
||||
`((command . "source")
|
||||
(action . ,action)
|
||||
(positional . ,positional)
|
||||
(source-name . ,source-name)
|
||||
(store-dir . ,store-dir)
|
||||
(cache-dir . ,cache-dir))))
|
||||
(("--help")
|
||||
(usage 0))
|
||||
(((? (lambda (arg) (string-prefix? "--source=" arg)) arg) . tail)
|
||||
(loop tail positional (option-value arg "--source=") store-dir cache-dir))
|
||||
(("--source" value . tail)
|
||||
(loop tail positional value store-dir cache-dir))
|
||||
(((? (lambda (arg) (string-prefix? "--store=" arg)) arg) . tail)
|
||||
(loop tail positional source-name (option-value arg "--store=") cache-dir))
|
||||
(("--store" value . tail)
|
||||
(loop tail positional source-name value cache-dir))
|
||||
(((? (lambda (arg) (string-prefix? "--cache=" arg)) arg) . tail)
|
||||
(loop tail positional source-name store-dir (option-value arg "--cache=")))
|
||||
(("--cache" value . tail)
|
||||
(loop tail positional source-name store-dir value))
|
||||
(((? (lambda (arg) (string-prefix? "--" arg)) arg) . _)
|
||||
(error "unknown option" arg))
|
||||
((arg . tail)
|
||||
(loop tail (cons arg positional) source-name store-dir cache-dir)))))
|
||||
|
||||
(define (parse-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)
|
||||
(match argv
|
||||
((_)
|
||||
(usage 1))
|
||||
((_ "--help")
|
||||
(usage 0))
|
||||
((_ "help")
|
||||
(usage 0))
|
||||
((_ "system" "--help")
|
||||
(usage 0))
|
||||
((_ "source" "--help")
|
||||
(usage 0))
|
||||
((_ "native-build" "--help")
|
||||
(usage 0))
|
||||
((_ "system" action . rest)
|
||||
(parse-system-arguments action rest))
|
||||
((_ "source" action . rest)
|
||||
(parse-source-arguments action rest))
|
||||
((_ "native-build" action . rest)
|
||||
(parse-native-build-arguments action rest))
|
||||
((_ . _)
|
||||
(usage 1))))
|
||||
|
||||
(define (emit-system-build-metadata os-file resolved-symbol store-dir os result)
|
||||
(let* ((closure-path (assoc-ref result 'closure-path))
|
||||
(generated-files (assoc-ref result 'generated-files))
|
||||
(references (assoc-ref result 'references))
|
||||
(base-package-stores (assoc-ref result 'base-package-stores))
|
||||
(host-base-stores (assoc-ref result 'host-base-stores))
|
||||
(native-base-stores (assoc-ref result 'native-base-stores))
|
||||
(fruix-runtime-stores (assoc-ref result 'fruix-runtime-stores))
|
||||
(base (operating-system-freebsd-base os))
|
||||
(source (freebsd-base-source base))
|
||||
(host-provenance (call-with-input-file (assoc-ref result 'host-base-provenance-file) read)))
|
||||
(emit-metadata
|
||||
`((action . "build")
|
||||
(os_file . ,os-file)
|
||||
(system_variable . ,resolved-symbol)
|
||||
(store_dir . ,store-dir)
|
||||
(closure_path . ,closure-path)
|
||||
(freebsd_base_name . ,(freebsd-base-name base))
|
||||
(freebsd_base_version_label . ,(freebsd-base-version-label base))
|
||||
(freebsd_base_release . ,(freebsd-base-release base))
|
||||
(freebsd_base_branch . ,(freebsd-base-branch base))
|
||||
(freebsd_base_source_root . ,(freebsd-base-source-root base))
|
||||
(freebsd_base_target . ,(freebsd-base-target base))
|
||||
(freebsd_base_target_arch . ,(freebsd-base-target-arch base))
|
||||
(freebsd_base_kernconf . ,(freebsd-base-kernconf base))
|
||||
(freebsd_base_file . ,(assoc-ref result 'freebsd-base-file))
|
||||
(freebsd_source_name . ,(freebsd-source-name source))
|
||||
(freebsd_source_kind . ,(freebsd-source-kind source))
|
||||
(freebsd_source_url . ,(or (freebsd-source-url source) ""))
|
||||
(freebsd_source_path . ,(or (freebsd-source-path source) ""))
|
||||
(freebsd_source_ref . ,(or (freebsd-source-ref source) ""))
|
||||
(freebsd_source_commit . ,(or (freebsd-source-commit source) ""))
|
||||
(freebsd_source_sha256 . ,(or (freebsd-source-sha256 source) ""))
|
||||
(freebsd_source_file . ,(assoc-ref result 'freebsd-source-file))
|
||||
(freebsd_source_materializations_file . ,(assoc-ref result 'freebsd-source-materializations-file))
|
||||
(materialized_source_store_count . ,(length (assoc-ref result 'materialized-source-stores)))
|
||||
(materialized_source_stores . ,(string-join (assoc-ref result 'materialized-source-stores) ","))
|
||||
(ready_marker . ,(operating-system-ready-marker os))
|
||||
(kernel_store . ,(assoc-ref result 'kernel-store))
|
||||
(bootloader_store . ,(assoc-ref result 'bootloader-store))
|
||||
(guile_store . ,(assoc-ref result 'guile-store))
|
||||
(guile_extra_store . ,(assoc-ref result 'guile-extra-store))
|
||||
(shepherd_store . ,(assoc-ref result 'shepherd-store))
|
||||
(base_package_store_count . ,(length base-package-stores))
|
||||
(base_package_stores . ,(string-join base-package-stores ","))
|
||||
(host_base_store_count . ,(length host-base-stores))
|
||||
(host_base_stores . ,(string-join host-base-stores ","))
|
||||
(native_base_store_count . ,(length native-base-stores))
|
||||
(native_base_stores . ,(string-join native-base-stores ","))
|
||||
(fruix_runtime_store_count . ,(length fruix-runtime-stores))
|
||||
(fruix_runtime_stores . ,(string-join fruix-runtime-stores ","))
|
||||
(host_base_provenance_file . ,(assoc-ref result 'host-base-provenance-file))
|
||||
(store_layout_file . ,(assoc-ref result 'store-layout-file))
|
||||
(host_freebsd_version . ,(assoc-ref host-provenance 'freebsd-version-kru))
|
||||
(host_uname . ,(assoc-ref host-provenance 'uname))
|
||||
(usr_src_git_revision . ,(assoc-ref host-provenance 'usr-src-git-revision))
|
||||
(usr_src_git_branch . ,(assoc-ref host-provenance 'usr-src-git-branch))
|
||||
(usr_src_newvers_sha256 . ,(assoc-ref host-provenance 'usr-src-newvers-sha256))
|
||||
(generated_file_count . ,(length generated-files))
|
||||
(reference_count . ,(length references))))))
|
||||
|
||||
(define (emit-system-install-metadata os-file resolved-symbol store-dir os result)
|
||||
(let* ((install-spec (assoc-ref result 'install-spec))
|
||||
(store-items (assoc-ref result 'store-items))
|
||||
(host-base-stores (assoc-ref result 'host-base-stores))
|
||||
(native-base-stores (assoc-ref result 'native-base-stores))
|
||||
(fruix-runtime-stores (assoc-ref result 'fruix-runtime-stores))
|
||||
(base (operating-system-freebsd-base os))
|
||||
(source (freebsd-base-source base))
|
||||
(host-provenance (call-with-input-file (assoc-ref result 'host-base-provenance-file) read)))
|
||||
(emit-metadata
|
||||
`((action . "install")
|
||||
(os_file . ,os-file)
|
||||
(system_variable . ,resolved-symbol)
|
||||
(store_dir . ,store-dir)
|
||||
(target . ,(assoc-ref result 'target))
|
||||
(target_kind . ,(assoc-ref result 'target-kind))
|
||||
(target_device . ,(assoc-ref result 'target-device))
|
||||
(esp_device . ,(assoc-ref result 'esp-device))
|
||||
(root_device . ,(assoc-ref result 'root-device))
|
||||
(install_metadata_path . ,(assoc-ref result 'install-metadata-path))
|
||||
(freebsd_base_name . ,(freebsd-base-name base))
|
||||
(freebsd_base_version_label . ,(freebsd-base-version-label base))
|
||||
(freebsd_base_release . ,(freebsd-base-release base))
|
||||
(freebsd_base_branch . ,(freebsd-base-branch base))
|
||||
(freebsd_base_source_root . ,(freebsd-base-source-root base))
|
||||
(freebsd_base_target . ,(freebsd-base-target base))
|
||||
(freebsd_base_target_arch . ,(freebsd-base-target-arch base))
|
||||
(freebsd_base_kernconf . ,(freebsd-base-kernconf base))
|
||||
(freebsd_base_file . ,(assoc-ref result 'freebsd-base-file))
|
||||
(freebsd_source_name . ,(freebsd-source-name source))
|
||||
(freebsd_source_kind . ,(freebsd-source-kind source))
|
||||
(freebsd_source_url . ,(or (freebsd-source-url source) ""))
|
||||
(freebsd_source_path . ,(or (freebsd-source-path source) ""))
|
||||
(freebsd_source_ref . ,(or (freebsd-source-ref source) ""))
|
||||
(freebsd_source_commit . ,(or (freebsd-source-commit source) ""))
|
||||
(freebsd_source_sha256 . ,(or (freebsd-source-sha256 source) ""))
|
||||
(freebsd_source_file . ,(assoc-ref result 'freebsd-source-file))
|
||||
(freebsd_source_materializations_file . ,(assoc-ref result 'freebsd-source-materializations-file))
|
||||
(materialized_source_store_count . ,(length (assoc-ref result 'materialized-source-stores)))
|
||||
(materialized_source_stores . ,(string-join (assoc-ref result 'materialized-source-stores) ","))
|
||||
(disk_capacity . ,(assoc-ref install-spec 'disk-capacity))
|
||||
(root_size . ,(assoc-ref install-spec 'root-size))
|
||||
(efi_size . ,(assoc-ref install-spec 'efi-size))
|
||||
(closure_path . ,(assoc-ref result 'closure-path))
|
||||
(host_base_store_count . ,(length host-base-stores))
|
||||
(host_base_stores . ,(string-join host-base-stores ","))
|
||||
(native_base_store_count . ,(length native-base-stores))
|
||||
(native_base_stores . ,(string-join native-base-stores ","))
|
||||
(fruix_runtime_store_count . ,(length fruix-runtime-stores))
|
||||
(fruix_runtime_stores . ,(string-join fruix-runtime-stores ","))
|
||||
(host_base_provenance_file . ,(assoc-ref result 'host-base-provenance-file))
|
||||
(store_layout_file . ,(assoc-ref result 'store-layout-file))
|
||||
(host_freebsd_version . ,(assoc-ref host-provenance 'freebsd-version-kru))
|
||||
(host_uname . ,(assoc-ref host-provenance 'uname))
|
||||
(usr_src_git_revision . ,(assoc-ref host-provenance 'usr-src-git-revision))
|
||||
(usr_src_git_branch . ,(assoc-ref host-provenance 'usr-src-git-branch))
|
||||
(usr_src_newvers_sha256 . ,(assoc-ref host-provenance 'usr-src-newvers-sha256))
|
||||
(store_item_count . ,(length store-items))))))
|
||||
|
||||
(define (emit-system-image-metadata os-file resolved-symbol store-dir os result)
|
||||
(let* ((image-spec (assoc-ref result 'image-spec))
|
||||
(store-items (assoc-ref result 'store-items))
|
||||
(host-base-stores (assoc-ref result 'host-base-stores))
|
||||
(native-base-stores (assoc-ref result 'native-base-stores))
|
||||
(fruix-runtime-stores (assoc-ref result 'fruix-runtime-stores))
|
||||
(base (operating-system-freebsd-base os))
|
||||
(source (freebsd-base-source base))
|
||||
(host-provenance (call-with-input-file (assoc-ref result 'host-base-provenance-file) read)))
|
||||
(emit-metadata
|
||||
`((action . "image")
|
||||
(os_file . ,os-file)
|
||||
(system_variable . ,resolved-symbol)
|
||||
(store_dir . ,store-dir)
|
||||
(freebsd_base_name . ,(freebsd-base-name base))
|
||||
(freebsd_base_version_label . ,(freebsd-base-version-label base))
|
||||
(freebsd_base_release . ,(freebsd-base-release base))
|
||||
(freebsd_base_branch . ,(freebsd-base-branch base))
|
||||
(freebsd_base_source_root . ,(freebsd-base-source-root base))
|
||||
(freebsd_base_target . ,(freebsd-base-target base))
|
||||
(freebsd_base_target_arch . ,(freebsd-base-target-arch base))
|
||||
(freebsd_base_kernconf . ,(freebsd-base-kernconf base))
|
||||
(freebsd_base_file . ,(assoc-ref result 'freebsd-base-file))
|
||||
(freebsd_source_name . ,(freebsd-source-name source))
|
||||
(freebsd_source_kind . ,(freebsd-source-kind source))
|
||||
(freebsd_source_url . ,(or (freebsd-source-url source) ""))
|
||||
(freebsd_source_path . ,(or (freebsd-source-path source) ""))
|
||||
(freebsd_source_ref . ,(or (freebsd-source-ref source) ""))
|
||||
(freebsd_source_commit . ,(or (freebsd-source-commit source) ""))
|
||||
(freebsd_source_sha256 . ,(or (freebsd-source-sha256 source) ""))
|
||||
(freebsd_source_file . ,(assoc-ref result 'freebsd-source-file))
|
||||
(freebsd_source_materializations_file . ,(assoc-ref result 'freebsd-source-materializations-file))
|
||||
(materialized_source_store_count . ,(length (assoc-ref result 'materialized-source-stores)))
|
||||
(materialized_source_stores . ,(string-join (assoc-ref result 'materialized-source-stores) ","))
|
||||
(disk_capacity . ,(assoc-ref image-spec 'disk-capacity))
|
||||
(root_size . ,(assoc-ref image-spec 'root-size))
|
||||
(image_store_path . ,(assoc-ref result 'image-store-path))
|
||||
(disk_image . ,(assoc-ref result 'disk-image))
|
||||
(esp_image . ,(assoc-ref result 'esp-image))
|
||||
(root_image . ,(assoc-ref result 'root-image))
|
||||
(closure_path . ,(assoc-ref result 'closure-path))
|
||||
(host_base_store_count . ,(length host-base-stores))
|
||||
(host_base_stores . ,(string-join host-base-stores ","))
|
||||
(native_base_store_count . ,(length native-base-stores))
|
||||
(native_base_stores . ,(string-join native-base-stores ","))
|
||||
(fruix_runtime_store_count . ,(length fruix-runtime-stores))
|
||||
(fruix_runtime_stores . ,(string-join fruix-runtime-stores ","))
|
||||
(host_base_provenance_file . ,(assoc-ref result 'host-base-provenance-file))
|
||||
(store_layout_file . ,(assoc-ref result 'store-layout-file))
|
||||
(host_freebsd_version . ,(assoc-ref host-provenance 'freebsd-version-kru))
|
||||
(host_uname . ,(assoc-ref host-provenance 'uname))
|
||||
(usr_src_git_revision . ,(assoc-ref host-provenance 'usr-src-git-revision))
|
||||
(usr_src_git_branch . ,(assoc-ref host-provenance 'usr-src-git-branch))
|
||||
(usr_src_newvers_sha256 . ,(assoc-ref host-provenance 'usr-src-newvers-sha256))
|
||||
(store_item_count . ,(length store-items))))))
|
||||
|
||||
(define (emit-system-installer-metadata os-file resolved-symbol store-dir os result)
|
||||
(let* ((installer-image-spec (assoc-ref result 'installer-image-spec))
|
||||
(image-spec (assoc-ref result 'image-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")
|
||||
(os_file . ,os-file)
|
||||
(system_variable . ,resolved-symbol)
|
||||
(store_dir . ,store-dir)
|
||||
(freebsd_base_name . ,(freebsd-base-name base))
|
||||
(freebsd_base_version_label . ,(freebsd-base-version-label base))
|
||||
(freebsd_base_release . ,(freebsd-base-release base))
|
||||
(freebsd_base_branch . ,(freebsd-base-branch base))
|
||||
(freebsd_base_source_root . ,(freebsd-base-source-root base))
|
||||
(freebsd_base_target . ,(freebsd-base-target base))
|
||||
(freebsd_base_target_arch . ,(freebsd-base-target-arch base))
|
||||
(freebsd_base_kernconf . ,(freebsd-base-kernconf base))
|
||||
(freebsd_base_file . ,(assoc-ref result 'freebsd-base-file))
|
||||
(freebsd_source_name . ,(freebsd-source-name source))
|
||||
(freebsd_source_kind . ,(freebsd-source-kind source))
|
||||
(freebsd_source_url . ,(or (freebsd-source-url source) ""))
|
||||
(freebsd_source_path . ,(or (freebsd-source-path source) ""))
|
||||
(freebsd_source_ref . ,(or (freebsd-source-ref source) ""))
|
||||
(freebsd_source_commit . ,(or (freebsd-source-commit source) ""))
|
||||
(freebsd_source_sha256 . ,(or (freebsd-source-sha256 source) ""))
|
||||
(freebsd_source_file . ,(assoc-ref result 'freebsd-source-file))
|
||||
(freebsd_source_materializations_file . ,(assoc-ref result 'freebsd-source-materializations-file))
|
||||
(materialized_source_store_count . ,(length (assoc-ref result 'materialized-source-stores)))
|
||||
(materialized_source_stores . ,(string-join (assoc-ref result 'materialized-source-stores) ","))
|
||||
(disk_capacity . ,(assoc-ref image-spec 'disk-capacity))
|
||||
(root_size . ,(assoc-ref image-spec 'root-size))
|
||||
(installer_host_name . ,(assoc-ref installer-image-spec 'installer-host-name))
|
||||
(install_target_device . ,(assoc-ref result 'install-target-device))
|
||||
(installer_state_path . ,(assoc-ref result 'installer-state-path))
|
||||
(installer_log_path . ,(assoc-ref result 'installer-log-path))
|
||||
(image_store_path . ,(assoc-ref result 'image-store-path))
|
||||
(disk_image . ,(assoc-ref result 'disk-image))
|
||||
(esp_image . ,(assoc-ref result 'esp-image))
|
||||
(root_image . ,(assoc-ref result 'root-image))
|
||||
(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-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)
|
||||
(let* ((parsed (parse-arguments argv))
|
||||
(command (assoc-ref parsed 'command))
|
||||
(action (assoc-ref parsed 'action))
|
||||
(store-dir (assoc-ref parsed 'store-dir)))
|
||||
(cond
|
||||
((string=? command "system")
|
||||
(let* ((positional (assoc-ref parsed 'positional))
|
||||
(disk-capacity (assoc-ref parsed 'disk-capacity))
|
||||
(root-size (assoc-ref parsed 'root-size))
|
||||
(target-opt (assoc-ref parsed 'target))
|
||||
(install-target-device (assoc-ref parsed 'install-target-device))
|
||||
(rootfs-opt (assoc-ref parsed 'rootfs))
|
||||
(system-name (assoc-ref parsed 'system-name))
|
||||
(requested-symbol (and system-name (string->symbol system-name))))
|
||||
(unless (member action '("build" "image" "installer" "installer-iso" "install" "rootfs"))
|
||||
(error "unknown system action" action))
|
||||
(let* ((os-file (match positional
|
||||
((file . _) file)
|
||||
(() (error "missing operating-system file argument"))))
|
||||
(target (or target-opt
|
||||
(and (string=? action "install")
|
||||
(match positional
|
||||
((_ target-path) target-path)
|
||||
(_ #f)))))
|
||||
(rootfs (or rootfs-opt
|
||||
(and (string=? action "rootfs")
|
||||
(match positional
|
||||
((_ dir) dir)
|
||||
((_ _ dir . _) dir)
|
||||
(_ #f))))))
|
||||
(call-with-values
|
||||
(lambda ()
|
||||
(load-operating-system-from-file os-file requested-symbol))
|
||||
(lambda (os resolved-symbol)
|
||||
(let* ((guile-prefix (or (getenv "GUILE_PREFIX") "/tmp/guile-freebsd-validate-install"))
|
||||
(guile-extra-prefix (or (getenv "GUILE_EXTRA_PREFIX") "/tmp/guile-gnutls-freebsd-validate-install"))
|
||||
(shepherd-prefix (or (getenv "SHEPHERD_PREFIX") "/tmp/shepherd-freebsd-validate-install"))
|
||||
(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
|
||||
((string=? action "build")
|
||||
(emit-system-build-metadata
|
||||
os-file resolved-symbol store-dir os
|
||||
(materialize-operating-system os
|
||||
#:store-dir store-dir
|
||||
#:guile-prefix guile-prefix
|
||||
#:guile-extra-prefix guile-extra-prefix
|
||||
#:shepherd-prefix shepherd-prefix
|
||||
#: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")
|
||||
(unless rootfs
|
||||
(error "rootfs action requires ROOTFS-DIR or --rootfs DIR"))
|
||||
(let ((result (materialize-rootfs os rootfs
|
||||
#:store-dir store-dir
|
||||
#:guile-prefix guile-prefix
|
||||
#:guile-extra-prefix guile-extra-prefix
|
||||
#:shepherd-prefix shepherd-prefix
|
||||
#:declaration-source declaration-source
|
||||
#:declaration-origin os-file
|
||||
#:declaration-system-symbol resolved-symbol)))
|
||||
(emit-metadata
|
||||
`((action . "rootfs")
|
||||
(os_file . ,os-file)
|
||||
(system_variable . ,resolved-symbol)
|
||||
(store_dir . ,store-dir)
|
||||
(rootfs . ,(assoc-ref result 'rootfs))
|
||||
(closure_path . ,(assoc-ref result 'closure-path))
|
||||
(ready_marker . ,(assoc-ref result 'ready-marker))
|
||||
(rc_script . ,(assoc-ref result 'rc-script))))))
|
||||
((string=? action "image")
|
||||
(emit-system-image-metadata
|
||||
os-file resolved-symbol store-dir os
|
||||
(materialize-bhyve-image os
|
||||
#:store-dir store-dir
|
||||
#:guile-prefix guile-prefix
|
||||
#:guile-extra-prefix guile-extra-prefix
|
||||
#:shepherd-prefix shepherd-prefix
|
||||
#:declaration-source declaration-source
|
||||
#:declaration-origin os-file
|
||||
#:declaration-system-symbol resolved-symbol
|
||||
#:root-size (or root-size "256m")
|
||||
#:disk-capacity disk-capacity)))
|
||||
((string=? action "installer")
|
||||
(emit-system-installer-metadata
|
||||
os-file resolved-symbol store-dir os
|
||||
(materialize-installer-image 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/vtbd1")
|
||||
#:root-size (or root-size "10g")
|
||||
#:disk-capacity disk-capacity)))
|
||||
((string=? action "installer-iso")
|
||||
(emit-system-installer-iso-metadata
|
||||
os-file resolved-symbol store-dir os
|
||||
(materialize-installer-iso os
|
||||
#:store-dir store-dir
|
||||
#:guile-prefix guile-prefix
|
||||
#:guile-extra-prefix guile-extra-prefix
|
||||
#:shepherd-prefix shepherd-prefix
|
||||
#: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")
|
||||
(unless target
|
||||
(error "install action requires TARGET or --target PATH"))
|
||||
(emit-system-install-metadata
|
||||
os-file resolved-symbol store-dir os
|
||||
(install-operating-system os
|
||||
#:target target
|
||||
#:store-dir store-dir
|
||||
#:guile-prefix guile-prefix
|
||||
#:guile-extra-prefix guile-extra-prefix
|
||||
#:shepherd-prefix shepherd-prefix
|
||||
#:declaration-source declaration-source
|
||||
#:declaration-origin os-file
|
||||
#:declaration-system-symbol resolved-symbol
|
||||
#:root-size root-size
|
||||
#:disk-capacity disk-capacity))))))))))
|
||||
((string=? command "source")
|
||||
(let* ((positional (assoc-ref parsed 'positional))
|
||||
(cache-dir (assoc-ref parsed 'cache-dir))
|
||||
(source-name (assoc-ref parsed 'source-name))
|
||||
(requested-symbol (and source-name (string->symbol source-name))))
|
||||
(unless (string=? action "materialize")
|
||||
(error "unknown source action" action))
|
||||
(let ((source-file (match positional
|
||||
((file . _) file)
|
||||
(() (error "missing freebsd-source file argument")))))
|
||||
(call-with-values
|
||||
(lambda ()
|
||||
(load-freebsd-source-from-file source-file requested-symbol))
|
||||
(lambda (source resolved-symbol)
|
||||
(let* ((result (materialize-freebsd-source source
|
||||
#:store-dir store-dir
|
||||
#:cache-dir cache-dir))
|
||||
(effective (assoc-ref result 'effective-source)))
|
||||
(emit-metadata
|
||||
`((action . "materialize")
|
||||
(source_file . ,source-file)
|
||||
(source_variable . ,resolved-symbol)
|
||||
(store_dir . ,store-dir)
|
||||
(cache_dir . ,cache-dir)
|
||||
(freebsd_source_name . ,(freebsd-source-name source))
|
||||
(freebsd_source_kind . ,(freebsd-source-kind source))
|
||||
(freebsd_source_url . ,(or (freebsd-source-url source) ""))
|
||||
(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) ""))
|
||||
(materialized_source_store . ,(assoc-ref result 'source-store-path))
|
||||
(materialized_source_root . ,(assoc-ref result 'source-root))
|
||||
(materialized_source_info_file . ,(assoc-ref result 'source-info-file))
|
||||
(materialized_source_tree_sha256 . ,(assoc-ref result 'source-tree-sha256))
|
||||
(materialized_source_cache_path . ,(or (assoc-ref result 'cache-path) ""))
|
||||
(materialized_source_kind . ,(assoc-ref effective 'kind))
|
||||
(materialized_source_url . ,(or (assoc-ref effective 'url) ""))
|
||||
(materialized_source_path . ,(or (assoc-ref effective 'path) ""))
|
||||
(materialized_source_ref . ,(or (assoc-ref effective 'ref) ""))
|
||||
(materialized_source_commit . ,(or (assoc-ref result 'effective-commit) ""))
|
||||
(materialized_source_sha256 . ,(or (assoc-ref result 'effective-sha256) ""))))))))))
|
||||
((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
|
||||
(usage 1)))))
|
||||
|
||||
(main (command-line))
|
||||
139
tests/system/materialize-phase7-rootfs.scm
Normal file
139
tests/system/materialize-phase7-rootfs.scm
Normal file
@@ -0,0 +1,139 @@
|
||||
(use-modules (fruix system freebsd)
|
||||
(ice-9 format)
|
||||
(srfi srfi-13)
|
||||
(rnrs io ports))
|
||||
|
||||
(define workdir
|
||||
(or (getenv "WORKDIR")
|
||||
(error "WORKDIR environment variable is required")))
|
||||
(define os-file
|
||||
(or (getenv "OS_FILE")
|
||||
(error "OS_FILE environment variable is required")))
|
||||
(define store-dir
|
||||
(or (getenv "STORE_DIR")
|
||||
"/frx/store"))
|
||||
(define guile-prefix
|
||||
(or (getenv "GUILE_PREFIX")
|
||||
"/tmp/guile-freebsd-validate-install"))
|
||||
(define guile-extra-prefix
|
||||
(or (getenv "GUILE_EXTRA_PREFIX")
|
||||
"/tmp/guile-gnutls-freebsd-validate-install"))
|
||||
(define shepherd-prefix
|
||||
(or (getenv "SHEPHERD_PREFIX")
|
||||
"/tmp/shepherd-freebsd-validate-install"))
|
||||
(define metadata-file
|
||||
(string-append workdir "/phase7-rootfs-metadata.txt"))
|
||||
(define rootfs
|
||||
(string-append workdir "/rootfs"))
|
||||
|
||||
(primitive-load os-file)
|
||||
(validate-operating-system phase7-operating-system)
|
||||
|
||||
(define (assert-exists path)
|
||||
(unless (or (file-exists? path)
|
||||
(false-if-exception (readlink path)))
|
||||
(error "required path missing" path)))
|
||||
|
||||
(define (assert-symlink-target path expected)
|
||||
(let ((actual (readlink path)))
|
||||
(unless (string=? actual expected)
|
||||
(error "unexpected symlink target" path actual expected))
|
||||
actual))
|
||||
|
||||
(let* ((result (materialize-rootfs phase7-operating-system rootfs
|
||||
#:store-dir store-dir
|
||||
#:guile-prefix guile-prefix
|
||||
#:guile-extra-prefix guile-extra-prefix
|
||||
#:shepherd-prefix shepherd-prefix))
|
||||
(closure-path (assoc-ref result 'closure-path))
|
||||
(ready-marker (assoc-ref result 'ready-marker))
|
||||
(rc-script (assoc-ref result 'rc-script))
|
||||
(run-current-system-target (assert-symlink-target (string-append rootfs "/run/current-system")
|
||||
closure-path))
|
||||
(activate-target (assert-symlink-target (string-append rootfs "/activate")
|
||||
"/run/current-system/activate"))
|
||||
(bin-target (assert-symlink-target (string-append rootfs "/bin")
|
||||
"/run/current-system/profile/bin"))
|
||||
(sbin-target (assert-symlink-target (string-append rootfs "/sbin")
|
||||
"/run/current-system/profile/sbin"))
|
||||
(lib-target (assert-symlink-target (string-append rootfs "/lib")
|
||||
"/run/current-system/profile/lib"))
|
||||
(boot-kernel-target (assert-symlink-target (string-append rootfs "/boot/kernel")
|
||||
"/run/current-system/boot/kernel"))
|
||||
(boot-loader-target (assert-symlink-target (string-append rootfs "/boot/loader")
|
||||
"/run/current-system/boot/loader"))
|
||||
(boot-loader-efi-target (assert-symlink-target (string-append rootfs "/boot/loader.efi")
|
||||
"/run/current-system/boot/loader.efi"))
|
||||
(rc-conf-target (assert-symlink-target (string-append rootfs "/etc/rc.conf")
|
||||
"/run/current-system/etc/rc.conf"))
|
||||
(fstab-target (assert-symlink-target (string-append rootfs "/etc/fstab")
|
||||
"/run/current-system/etc/fstab"))
|
||||
(passwd-target (assert-symlink-target (string-append rootfs "/etc/passwd")
|
||||
"/run/current-system/etc/passwd"))
|
||||
(group-target (assert-symlink-target (string-append rootfs "/etc/group")
|
||||
"/run/current-system/etc/group"))
|
||||
(rc-script-target (assert-symlink-target (string-append rootfs "/usr/local/etc/rc.d/fruix-shepherd")
|
||||
"/run/current-system/usr/local/etc/rc.d/fruix-shepherd"))
|
||||
(rc-conf-content (call-with-input-file (string-append closure-path "/etc/rc.conf") get-string-all))
|
||||
(fstab-content (call-with-input-file (string-append closure-path "/etc/fstab") get-string-all))
|
||||
(activation-content (call-with-input-file (string-append closure-path "/activate") get-string-all))
|
||||
(shepherd-content (call-with-input-file (string-append closure-path "/shepherd/init.scm") get-string-all))
|
||||
(loader-conf-content (call-with-input-file (string-append closure-path "/boot/loader.conf") get-string-all)))
|
||||
(for-each assert-exists
|
||||
(list rootfs closure-path rc-script
|
||||
(string-append rootfs "/etc/rc")
|
||||
(string-append rootfs "/etc/rc.subr")
|
||||
(string-append rootfs "/etc/rc.d")
|
||||
(string-append rootfs "/etc/defaults")
|
||||
(string-append rootfs "/etc/motd")
|
||||
(string-append rootfs "/usr/sbin")
|
||||
(string-append rootfs "/usr/bin")
|
||||
(string-append rootfs "/var/lib/fruix")
|
||||
(string-append rootfs "/var/log")
|
||||
(string-append rootfs "/var/run")
|
||||
(string-append rootfs "/tmp")))
|
||||
(unless (string-contains rc-conf-content "hostname=\"fruix-freebsd\"")
|
||||
(error "rc.conf does not contain the expected hostname"))
|
||||
(unless (string-contains rc-conf-content "fruix_shepherd_enable=\"YES\"")
|
||||
(error "rc.conf does not enable fruix_shepherd"))
|
||||
(unless (and (string-contains fstab-content "/dev/ufs/fruix-root")
|
||||
(string-contains fstab-content "devfs")
|
||||
(string-contains fstab-content "tmpfs"))
|
||||
(error "fstab content was incomplete"))
|
||||
(unless (string-contains activation-content "pw useradd operator")
|
||||
(error "activation script does not provision the operator account"))
|
||||
(unless (string-contains shepherd-content ready-marker)
|
||||
(error "shepherd configuration does not mention the ready marker"))
|
||||
(unless (string-contains loader-conf-content "console=\"comconsole\"")
|
||||
(error "loader.conf does not contain the expected serial console setting"))
|
||||
(call-with-output-file metadata-file
|
||||
(lambda (port)
|
||||
(format port "rootfs=~a~%" rootfs)
|
||||
(format port "closure_path=~a~%" closure-path)
|
||||
(format port "run_current_system_target=~a~%" run-current-system-target)
|
||||
(format port "activate_target=~a~%" activate-target)
|
||||
(format port "bin_target=~a~%" bin-target)
|
||||
(format port "sbin_target=~a~%" sbin-target)
|
||||
(format port "lib_target=~a~%" lib-target)
|
||||
(format port "boot_kernel_target=~a~%" boot-kernel-target)
|
||||
(format port "boot_loader_target=~a~%" boot-loader-target)
|
||||
(format port "boot_loader_efi_target=~a~%" boot-loader-efi-target)
|
||||
(format port "rc_conf_target=~a~%" rc-conf-target)
|
||||
(format port "fstab_target=~a~%" fstab-target)
|
||||
(format port "passwd_target=~a~%" passwd-target)
|
||||
(format port "group_target=~a~%" group-target)
|
||||
(format port "rc_script=~a~%" rc-script)
|
||||
(format port "rc_script_target=~a~%" rc-script-target)
|
||||
(format port "ready_marker=~a~%" ready-marker)
|
||||
(format port "validation_mode=static-rootfs-check~%")
|
||||
(format port "ready_state_mode=freebsd-init+rc.d-shepherd~%")))
|
||||
|
||||
(when (getenv "METADATA_OUT")
|
||||
(copy-file metadata-file (getenv "METADATA_OUT")))
|
||||
|
||||
(format #t "PASS phase7-rootfs\n")
|
||||
(format #t "Metadata file: ~a\n" metadata-file)
|
||||
(when (getenv "METADATA_OUT")
|
||||
(format #t "Copied metadata to: ~a\n" (getenv "METADATA_OUT")))
|
||||
(display "--- metadata ---\n")
|
||||
(display (call-with-input-file metadata-file get-string-all)))
|
||||
102
tests/system/materialize-phase7-system-closure.scm
Normal file
102
tests/system/materialize-phase7-system-closure.scm
Normal file
@@ -0,0 +1,102 @@
|
||||
(use-modules (fruix system freebsd)
|
||||
(ice-9 format)
|
||||
(srfi srfi-13)
|
||||
(rnrs io ports))
|
||||
|
||||
(define workdir
|
||||
(or (getenv "WORKDIR")
|
||||
(error "WORKDIR environment variable is required")))
|
||||
(define os-file
|
||||
(or (getenv "OS_FILE")
|
||||
(error "OS_FILE environment variable is required")))
|
||||
(define store-dir
|
||||
(or (getenv "STORE_DIR")
|
||||
"/frx/store"))
|
||||
(define guile-prefix
|
||||
(or (getenv "GUILE_PREFIX")
|
||||
"/tmp/guile-freebsd-validate-install"))
|
||||
(define guile-extra-prefix
|
||||
(or (getenv "GUILE_EXTRA_PREFIX")
|
||||
"/tmp/guile-gnutls-freebsd-validate-install"))
|
||||
(define shepherd-prefix
|
||||
(or (getenv "SHEPHERD_PREFIX")
|
||||
"/tmp/shepherd-freebsd-validate-install"))
|
||||
(define metadata-file
|
||||
(string-append workdir "/phase7-system-closure-metadata.txt"))
|
||||
|
||||
(primitive-load os-file)
|
||||
(validate-operating-system phase7-operating-system)
|
||||
|
||||
(define (assert-exists path)
|
||||
(unless (file-exists? path)
|
||||
(error "required path missing" path)))
|
||||
|
||||
(let* ((closure-a (materialize-operating-system phase7-operating-system
|
||||
#:store-dir store-dir
|
||||
#:guile-prefix guile-prefix
|
||||
#:guile-extra-prefix guile-extra-prefix
|
||||
#:shepherd-prefix shepherd-prefix))
|
||||
(closure-b (materialize-operating-system phase7-operating-system
|
||||
#:store-dir store-dir
|
||||
#:guile-prefix guile-prefix
|
||||
#:guile-extra-prefix guile-extra-prefix
|
||||
#:shepherd-prefix shepherd-prefix))
|
||||
(closure-path (assoc-ref closure-a 'closure-path))
|
||||
(closure-rebuild-path (assoc-ref closure-b 'closure-path))
|
||||
(kernel-store (assoc-ref closure-a 'kernel-store))
|
||||
(bootloader-store (assoc-ref closure-a 'bootloader-store))
|
||||
(guile-store (assoc-ref closure-a 'guile-store))
|
||||
(guile-extra-store (assoc-ref closure-a 'guile-extra-store))
|
||||
(shepherd-store (assoc-ref closure-a 'shepherd-store))
|
||||
(base-package-stores (assoc-ref closure-a 'base-package-stores))
|
||||
(rc-script (string-append closure-path "/usr/local/etc/rc.d/fruix-shepherd"))
|
||||
(shepherd-config (string-append closure-path "/shepherd/init.scm"))
|
||||
(activation-script (string-append closure-path "/activate"))
|
||||
(loader-conf (string-append closure-path "/boot/loader.conf"))
|
||||
(profile-bin-sh (string-append closure-path "/profile/bin/sh"))
|
||||
(profile-sbin-init (string-append closure-path "/profile/sbin/init"))
|
||||
(profile-rc (string-append closure-path "/profile/etc/rc"))
|
||||
(ready-marker (assoc-ref (operating-system-closure-spec phase7-operating-system)
|
||||
'ready-marker))
|
||||
(rc-script-target (readlink (string-append closure-path "/boot/loader")))
|
||||
(kernel-link (readlink (string-append closure-path "/boot/kernel/kernel"))))
|
||||
(for-each assert-exists
|
||||
(list closure-path kernel-store bootloader-store guile-store
|
||||
guile-extra-store shepherd-store rc-script shepherd-config
|
||||
activation-script loader-conf profile-bin-sh profile-sbin-init
|
||||
profile-rc (string-append closure-path "/parameters.scm")))
|
||||
(unless (string=? closure-path closure-rebuild-path)
|
||||
(error "closure path was not reproducible" closure-path closure-rebuild-path))
|
||||
(call-with-output-file metadata-file
|
||||
(lambda (port)
|
||||
(format port "store_dir=~a~%" store-dir)
|
||||
(format port "closure_path=~a~%" closure-path)
|
||||
(format port "closure_rebuild_path=~a~%" closure-rebuild-path)
|
||||
(format port "kernel_store=~a~%" kernel-store)
|
||||
(format port "bootloader_store=~a~%" bootloader-store)
|
||||
(format port "guile_store=~a~%" guile-store)
|
||||
(format port "guile_extra_store=~a~%" guile-extra-store)
|
||||
(format port "shepherd_store=~a~%" shepherd-store)
|
||||
(format port "base_package_store_count=~a~%" (length base-package-stores))
|
||||
(format port "base_package_stores=~a~%" (string-join base-package-stores ","))
|
||||
(format port "rc_script=~a~%" rc-script)
|
||||
(format port "shepherd_config=~a~%" shepherd-config)
|
||||
(format port "activation_script=~a~%" activation-script)
|
||||
(format port "loader_conf=~a~%" loader-conf)
|
||||
(format port "boot_loader_target=~a~%" rc-script-target)
|
||||
(format port "kernel_link_target=~a~%" kernel-link)
|
||||
(format port "profile_bin_sh=~a~%" profile-bin-sh)
|
||||
(format port "profile_sbin_init=~a~%" profile-sbin-init)
|
||||
(format port "profile_rc=~a~%" profile-rc)
|
||||
(format port "ready_marker=~a~%" ready-marker)
|
||||
(format port "init_integration=freebsd-init+rc.d-shepherd~%")))
|
||||
|
||||
(when (getenv "METADATA_OUT")
|
||||
(copy-file metadata-file (getenv "METADATA_OUT")))
|
||||
|
||||
(format #t "PASS phase7-system-closure~%")
|
||||
(format #t "Metadata file: ~a~%" metadata-file)
|
||||
(when (getenv "METADATA_OUT")
|
||||
(format #t "Copied metadata to: ~a~%" (getenv "METADATA_OUT")))
|
||||
(display "--- metadata ---\n")
|
||||
(display (call-with-input-file metadata-file get-string-all)))
|
||||
121
tests/system/materialize-phase8-system-image.scm
Normal file
121
tests/system/materialize-phase8-system-image.scm
Normal file
@@ -0,0 +1,121 @@
|
||||
(use-modules (fruix system freebsd)
|
||||
(ice-9 format)
|
||||
(ice-9 pretty-print)
|
||||
(ice-9 popen)
|
||||
(srfi srfi-13)
|
||||
(rnrs io ports))
|
||||
|
||||
(define workdir
|
||||
(or (getenv "WORKDIR")
|
||||
(error "WORKDIR environment variable is required")))
|
||||
(define os-file
|
||||
(or (getenv "OS_FILE")
|
||||
(error "OS_FILE environment variable is required")))
|
||||
(define store-dir
|
||||
(or (getenv "STORE_DIR")
|
||||
"/frx/store"))
|
||||
(define guile-prefix
|
||||
(or (getenv "GUILE_PREFIX")
|
||||
"/tmp/guile-freebsd-validate-install"))
|
||||
(define guile-extra-prefix
|
||||
(or (getenv "GUILE_EXTRA_PREFIX")
|
||||
"/tmp/guile-gnutls-freebsd-validate-install"))
|
||||
(define shepherd-prefix
|
||||
(or (getenv "SHEPHERD_PREFIX")
|
||||
"/tmp/shepherd-freebsd-validate-install"))
|
||||
(define metadata-file
|
||||
(string-append workdir "/phase8-system-image-metadata.txt"))
|
||||
(define disk-capacity
|
||||
(let ((value (getenv "DISK_CAPACITY")))
|
||||
(and value (not (string-null? value)) value)))
|
||||
|
||||
(define (trim-trailing-newlines str)
|
||||
(let loop ((len (string-length str)))
|
||||
(if (and (> len 0)
|
||||
(char=? (string-ref str (- len 1)) #\newline))
|
||||
(loop (- len 1))
|
||||
(substring str 0 len))))
|
||||
|
||||
(define (command-output program . args)
|
||||
(let* ((port (apply open-pipe* OPEN_READ program args))
|
||||
(output (get-string-all port))
|
||||
(status (close-pipe port)))
|
||||
(unless (zero? status)
|
||||
(error "command failed" program args status))
|
||||
(trim-trailing-newlines output)))
|
||||
|
||||
(define (assert-exists path)
|
||||
(unless (file-exists? path)
|
||||
(error "required path missing" path)))
|
||||
|
||||
(primitive-load os-file)
|
||||
(validate-operating-system phase7-operating-system)
|
||||
|
||||
(let* ((image-a (if disk-capacity
|
||||
(materialize-bhyve-image phase7-operating-system
|
||||
#:store-dir store-dir
|
||||
#:guile-prefix guile-prefix
|
||||
#:guile-extra-prefix guile-extra-prefix
|
||||
#:shepherd-prefix shepherd-prefix
|
||||
#:disk-capacity disk-capacity)
|
||||
(materialize-bhyve-image phase7-operating-system
|
||||
#:store-dir store-dir
|
||||
#:guile-prefix guile-prefix
|
||||
#:guile-extra-prefix guile-extra-prefix
|
||||
#:shepherd-prefix shepherd-prefix)))
|
||||
(image-b (if disk-capacity
|
||||
(materialize-bhyve-image phase7-operating-system
|
||||
#:store-dir store-dir
|
||||
#:guile-prefix guile-prefix
|
||||
#:guile-extra-prefix guile-extra-prefix
|
||||
#:shepherd-prefix shepherd-prefix
|
||||
#:disk-capacity disk-capacity)
|
||||
(materialize-bhyve-image phase7-operating-system
|
||||
#:store-dir store-dir
|
||||
#:guile-prefix guile-prefix
|
||||
#:guile-extra-prefix guile-extra-prefix
|
||||
#:shepherd-prefix shepherd-prefix)))
|
||||
(image-store-path (assoc-ref image-a 'image-store-path))
|
||||
(image-store-path-rebuild (assoc-ref image-b 'image-store-path))
|
||||
(disk-image (assoc-ref image-a 'disk-image))
|
||||
(esp-image (assoc-ref image-a 'esp-image))
|
||||
(root-image (assoc-ref image-a 'root-image))
|
||||
(closure-path (assoc-ref image-a 'closure-path))
|
||||
(image-spec (assoc-ref image-a 'image-spec))
|
||||
(store-items (assoc-ref image-a 'store-items))
|
||||
(raw-sha256 (command-output "sha256" "-q" disk-image))
|
||||
(image-size-bytes (command-output "stat" "-f" "%z" disk-image)))
|
||||
(for-each assert-exists
|
||||
(list image-store-path disk-image esp-image root-image
|
||||
(string-append image-store-path "/image-spec.scm")
|
||||
(string-append image-store-path "/closure-path")
|
||||
(string-append image-store-path "/.references")
|
||||
(string-append image-store-path "/.fruix-package")))
|
||||
(unless (string=? image-store-path image-store-path-rebuild)
|
||||
(error "image store path was not reproducible" image-store-path image-store-path-rebuild))
|
||||
(call-with-output-file metadata-file
|
||||
(lambda (port)
|
||||
(format port "store_dir=~a~%" store-dir)
|
||||
(format port "image_store_path=~a~%" image-store-path)
|
||||
(format port "image_store_path_rebuild=~a~%" image-store-path-rebuild)
|
||||
(format port "disk_image=~a~%" disk-image)
|
||||
(format port "esp_image=~a~%" esp-image)
|
||||
(format port "root_image=~a~%" root-image)
|
||||
(format port "closure_path=~a~%" closure-path)
|
||||
(format port "disk_capacity=~a~%" (or disk-capacity "<default>"))
|
||||
(format port "store_item_count=~a~%" (length store-items))
|
||||
(format port "raw_sha256=~a~%" raw-sha256)
|
||||
(format port "image_size_bytes=~a~%" image-size-bytes)
|
||||
(format port "image_spec=~a~%"
|
||||
(string-map (lambda (ch) (if (char=? ch #\newline) #\space ch))
|
||||
(with-output-to-string
|
||||
(lambda ()
|
||||
(pretty-print image-spec)))))))
|
||||
(when (getenv "METADATA_OUT")
|
||||
(copy-file metadata-file (getenv "METADATA_OUT")))
|
||||
(format #t "PASS phase8-system-image-materialization~%")
|
||||
(format #t "Metadata file: ~a~%" metadata-file)
|
||||
(when (getenv "METADATA_OUT")
|
||||
(format #t "Copied metadata to: ~a~%" (getenv "METADATA_OUT")))
|
||||
(display "--- metadata ---\n")
|
||||
(display (call-with-input-file metadata-file get-string-all)))
|
||||
78
tests/system/phase11-shepherd-pid1-operating-system.scm.in
Normal file
78
tests/system/phase11-shepherd-pid1-operating-system.scm.in
Normal file
@@ -0,0 +1,78 @@
|
||||
(use-modules (fruix system freebsd)
|
||||
(fruix packages freebsd))
|
||||
|
||||
(define phase11-operating-system
|
||||
(operating-system
|
||||
#:host-name "fruix-freebsd"
|
||||
#:kernel freebsd-kernel
|
||||
#:bootloader freebsd-bootloader
|
||||
#:base-packages (list freebsd-runtime
|
||||
freebsd-networking
|
||||
freebsd-openssh
|
||||
freebsd-userland
|
||||
freebsd-libc
|
||||
freebsd-rc-scripts
|
||||
freebsd-sh
|
||||
freebsd-bash)
|
||||
#: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,71 @@
|
||||
(use-modules (fruix system freebsd)
|
||||
(fruix packages freebsd))
|
||||
|
||||
(define phase13-operating-system
|
||||
(operating-system
|
||||
#:host-name "fruix-freebsd"
|
||||
#:kernel freebsd-native-kernel
|
||||
#:bootloader freebsd-bootloader
|
||||
#:base-packages (list freebsd-native-world)
|
||||
#: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,71 @@
|
||||
(use-modules (fruix system freebsd)
|
||||
(fruix packages freebsd))
|
||||
|
||||
(define phase14-operating-system
|
||||
(operating-system
|
||||
#:host-name "fruix-freebsd"
|
||||
#:kernel freebsd-native-kernel
|
||||
#:bootloader freebsd-native-world
|
||||
#:base-packages (list freebsd-native-world)
|
||||
#: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,71 @@
|
||||
(use-modules (fruix system freebsd)
|
||||
(fruix packages freebsd))
|
||||
|
||||
(define phase14-operating-system
|
||||
(operating-system
|
||||
#:host-name "fruix-freebsd"
|
||||
#:kernel freebsd-native-kernel
|
||||
#:bootloader freebsd-native-world
|
||||
#:base-packages (list freebsd-native-runtime)
|
||||
#: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,71 @@
|
||||
(use-modules (fruix system freebsd)
|
||||
(fruix packages freebsd))
|
||||
|
||||
(define phase14-operating-system
|
||||
(operating-system
|
||||
#:host-name "fruix-freebsd"
|
||||
#:kernel freebsd-native-kernel
|
||||
#:bootloader freebsd-native-bootloader
|
||||
#:base-packages %freebsd-native-system-packages
|
||||
#: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,83 @@
|
||||
(use-modules (fruix system freebsd)
|
||||
(fruix packages freebsd))
|
||||
|
||||
(define phase15-base
|
||||
(freebsd-base
|
||||
#:name "__BASE_NAME__"
|
||||
#:version-label "__BASE_VERSION_LABEL__"
|
||||
#:release "__BASE_RELEASE__"
|
||||
#:branch "__BASE_BRANCH__"
|
||||
#:source-root "/usr/src"
|
||||
#:target "amd64"
|
||||
#:target-arch "amd64"
|
||||
#:kernconf "GENERIC"))
|
||||
|
||||
(define phase15-operating-system
|
||||
(operating-system
|
||||
#:host-name "fruix-freebsd"
|
||||
#:freebsd-base phase15-base
|
||||
#:kernel (freebsd-native-kernel-for phase15-base)
|
||||
#:bootloader (freebsd-native-bootloader-for phase15-base)
|
||||
#:base-packages (freebsd-native-system-packages-for phase15-base)
|
||||
#:groups (list (user-group #:name "wheel" #:gid 0 #:system? #t)
|
||||
(user-group #:name "sshd" #:gid 22 #:system? #t)
|
||||
(user-group #:name "_dhcp" #:gid 65 #:system? #t)
|
||||
(user-group #:name "operator" #:gid 1000 #:system? #f))
|
||||
#:users (list (user-account #:name "root"
|
||||
#:uid 0
|
||||
#:group "wheel"
|
||||
#:comment "Charlie &"
|
||||
#:home "/root"
|
||||
#:shell "/bin/sh"
|
||||
#:system? #t)
|
||||
(user-account #:name "sshd"
|
||||
#:uid 22
|
||||
#:group "sshd"
|
||||
#:comment "Secure Shell Daemon"
|
||||
#:home "/var/empty"
|
||||
#:shell "/usr/sbin/nologin"
|
||||
#:system? #t)
|
||||
(user-account #:name "_dhcp"
|
||||
#:uid 65
|
||||
#:group "_dhcp"
|
||||
#:comment "dhcp programs"
|
||||
#:home "/var/empty"
|
||||
#:shell "/usr/sbin/nologin"
|
||||
#:system? #t)
|
||||
(user-account #:name "operator"
|
||||
#:uid 1000
|
||||
#:group "operator"
|
||||
#:supplementary-groups '("wheel")
|
||||
#:comment "Fruix Operator"
|
||||
#:home "/home/operator"
|
||||
#:shell "/bin/sh"
|
||||
#:system? #f))
|
||||
#:file-systems (list (file-system #:device "/dev/gpt/fruix-root"
|
||||
#:mount-point "/"
|
||||
#:type "ufs"
|
||||
#:options "rw"
|
||||
#:needed-for-boot? #t)
|
||||
(file-system #:device "devfs"
|
||||
#:mount-point "/dev"
|
||||
#:type "devfs"
|
||||
#:options "rw"
|
||||
#:needed-for-boot? #t)
|
||||
(file-system #:device "tmpfs"
|
||||
#:mount-point "/tmp"
|
||||
#:type "tmpfs"
|
||||
#:options "rw,size=64m"))
|
||||
#:services '(shepherd ready-marker sshd)
|
||||
#:loader-entries '(("autoboot_delay" . "1")
|
||||
("boot_multicons" . "YES")
|
||||
("boot_serial" . "YES")
|
||||
("console" . "comconsole,vidconsole"))
|
||||
#:rc-conf-entries '(("clear_tmp_enable" . "NO")
|
||||
("hostid_enable" . "NO")
|
||||
("sendmail_enable" . "NONE")
|
||||
("sshd_enable" . "YES")
|
||||
("ifconfig_xn0" . "SYNCDHCP")
|
||||
("ifconfig_em0" . "SYNCDHCP")
|
||||
("ifconfig_vtnet0" . "SYNCDHCP"))
|
||||
#:init-mode 'shepherd-pid1
|
||||
#:ready-marker "/var/lib/fruix/ready"
|
||||
#:root-authorized-keys '("__ROOT_AUTHORIZED_KEY__")))
|
||||
@@ -0,0 +1,90 @@
|
||||
(use-modules (fruix system freebsd)
|
||||
(fruix packages freebsd))
|
||||
|
||||
(define phase16-source
|
||||
(freebsd-source
|
||||
#:name "__SOURCE_NAME__"
|
||||
#:kind 'local-tree
|
||||
#:path "/usr/src"))
|
||||
|
||||
(define phase16-base
|
||||
(freebsd-base
|
||||
#:name "__BASE_NAME__"
|
||||
#:version-label "__BASE_VERSION_LABEL__"
|
||||
#:release "__BASE_RELEASE__"
|
||||
#:branch "__BASE_BRANCH__"
|
||||
#:source phase16-source
|
||||
#:source-root "/usr/src"
|
||||
#:target "amd64"
|
||||
#:target-arch "amd64"
|
||||
#:kernconf "GENERIC"))
|
||||
|
||||
(define phase16-operating-system
|
||||
(operating-system
|
||||
#:host-name "fruix-freebsd"
|
||||
#:freebsd-base phase16-base
|
||||
#:kernel (freebsd-native-kernel-for phase16-base)
|
||||
#:bootloader (freebsd-native-bootloader-for phase16-base)
|
||||
#:base-packages (freebsd-native-system-packages-for phase16-base)
|
||||
#:groups (list (user-group #:name "wheel" #:gid 0 #:system? #t)
|
||||
(user-group #:name "sshd" #:gid 22 #:system? #t)
|
||||
(user-group #:name "_dhcp" #:gid 65 #:system? #t)
|
||||
(user-group #:name "operator" #:gid 1000 #:system? #f))
|
||||
#:users (list (user-account #:name "root"
|
||||
#:uid 0
|
||||
#:group "wheel"
|
||||
#:comment "Charlie &"
|
||||
#:home "/root"
|
||||
#:shell "/bin/sh"
|
||||
#:system? #t)
|
||||
(user-account #:name "sshd"
|
||||
#:uid 22
|
||||
#:group "sshd"
|
||||
#:comment "Secure Shell Daemon"
|
||||
#:home "/var/empty"
|
||||
#:shell "/usr/sbin/nologin"
|
||||
#:system? #t)
|
||||
(user-account #:name "_dhcp"
|
||||
#:uid 65
|
||||
#:group "_dhcp"
|
||||
#:comment "dhcp programs"
|
||||
#:home "/var/empty"
|
||||
#:shell "/usr/sbin/nologin"
|
||||
#:system? #t)
|
||||
(user-account #:name "operator"
|
||||
#:uid 1000
|
||||
#:group "operator"
|
||||
#:supplementary-groups '("wheel")
|
||||
#:comment "Fruix Operator"
|
||||
#:home "/home/operator"
|
||||
#:shell "/bin/sh"
|
||||
#:system? #f))
|
||||
#:file-systems (list (file-system #:device "/dev/gpt/fruix-root"
|
||||
#:mount-point "/"
|
||||
#:type "ufs"
|
||||
#:options "rw"
|
||||
#:needed-for-boot? #t)
|
||||
(file-system #:device "devfs"
|
||||
#:mount-point "/dev"
|
||||
#:type "devfs"
|
||||
#:options "rw"
|
||||
#:needed-for-boot? #t)
|
||||
(file-system #:device "tmpfs"
|
||||
#:mount-point "/tmp"
|
||||
#:type "tmpfs"
|
||||
#:options "rw,size=64m"))
|
||||
#:services '(shepherd ready-marker sshd)
|
||||
#:loader-entries '(("autoboot_delay" . "1")
|
||||
("boot_multicons" . "YES")
|
||||
("boot_serial" . "YES")
|
||||
("console" . "comconsole,vidconsole"))
|
||||
#:rc-conf-entries '(("clear_tmp_enable" . "NO")
|
||||
("hostid_enable" . "NO")
|
||||
("sendmail_enable" . "NONE")
|
||||
("sshd_enable" . "YES")
|
||||
("ifconfig_xn0" . "SYNCDHCP")
|
||||
("ifconfig_em0" . "SYNCDHCP")
|
||||
("ifconfig_vtnet0" . "SYNCDHCP"))
|
||||
#:init-mode 'shepherd-pid1
|
||||
#:ready-marker "/var/lib/fruix/ready"
|
||||
#:root-authorized-keys '("__ROOT_AUTHORIZED_KEY__")))
|
||||
7
tests/system/phase16-git-freebsd-source.scm.in
Normal file
7
tests/system/phase16-git-freebsd-source.scm.in
Normal file
@@ -0,0 +1,7 @@
|
||||
(use-modules (fruix packages freebsd))
|
||||
|
||||
(define phase16-source
|
||||
(freebsd-source
|
||||
#:name "__SOURCE_NAME__"
|
||||
#:kind 'git
|
||||
#:ref "__SOURCE_REF__"))
|
||||
@@ -0,0 +1,90 @@
|
||||
(use-modules (fruix system freebsd)
|
||||
(fruix packages freebsd))
|
||||
|
||||
(define phase16-source
|
||||
(freebsd-source
|
||||
#:name "__SOURCE_NAME__"
|
||||
#:kind 'git
|
||||
#:ref "__SOURCE_REF__"))
|
||||
|
||||
(define phase16-base
|
||||
(freebsd-base
|
||||
#:name "__BASE_NAME__"
|
||||
#:version-label "__BASE_VERSION_LABEL__"
|
||||
#:release "__BASE_RELEASE__"
|
||||
#:branch "__BASE_BRANCH__"
|
||||
#:source phase16-source
|
||||
#:source-root "__DECLARED_SOURCE_ROOT__"
|
||||
#:target "amd64"
|
||||
#:target-arch "amd64"
|
||||
#:kernconf "GENERIC"))
|
||||
|
||||
(define phase16-operating-system
|
||||
(operating-system
|
||||
#:host-name "fruix-freebsd"
|
||||
#:freebsd-base phase16-base
|
||||
#:kernel (freebsd-native-kernel-for phase16-base)
|
||||
#:bootloader (freebsd-native-bootloader-for phase16-base)
|
||||
#:base-packages (freebsd-native-system-packages-for phase16-base)
|
||||
#:groups (list (user-group #:name "wheel" #:gid 0 #:system? #t)
|
||||
(user-group #:name "sshd" #:gid 22 #:system? #t)
|
||||
(user-group #:name "_dhcp" #:gid 65 #:system? #t)
|
||||
(user-group #:name "operator" #:gid 1000 #:system? #f))
|
||||
#:users (list (user-account #:name "root"
|
||||
#:uid 0
|
||||
#:group "wheel"
|
||||
#:comment "Charlie &"
|
||||
#:home "/root"
|
||||
#:shell "/bin/sh"
|
||||
#:system? #t)
|
||||
(user-account #:name "sshd"
|
||||
#:uid 22
|
||||
#:group "sshd"
|
||||
#:comment "Secure Shell Daemon"
|
||||
#:home "/var/empty"
|
||||
#:shell "/usr/sbin/nologin"
|
||||
#:system? #t)
|
||||
(user-account #:name "_dhcp"
|
||||
#:uid 65
|
||||
#:group "_dhcp"
|
||||
#:comment "dhcp programs"
|
||||
#:home "/var/empty"
|
||||
#:shell "/usr/sbin/nologin"
|
||||
#:system? #t)
|
||||
(user-account #:name "operator"
|
||||
#:uid 1000
|
||||
#:group "operator"
|
||||
#:supplementary-groups '("wheel")
|
||||
#:comment "Fruix Operator"
|
||||
#:home "/home/operator"
|
||||
#:shell "/bin/sh"
|
||||
#:system? #f))
|
||||
#:file-systems (list (file-system #:device "/dev/gpt/fruix-root"
|
||||
#:mount-point "/"
|
||||
#:type "ufs"
|
||||
#:options "rw"
|
||||
#:needed-for-boot? #t)
|
||||
(file-system #:device "devfs"
|
||||
#:mount-point "/dev"
|
||||
#:type "devfs"
|
||||
#:options "rw"
|
||||
#:needed-for-boot? #t)
|
||||
(file-system #:device "tmpfs"
|
||||
#:mount-point "/tmp"
|
||||
#:type "tmpfs"
|
||||
#:options "rw,size=64m"))
|
||||
#:services '(shepherd ready-marker sshd)
|
||||
#:loader-entries '(("autoboot_delay" . "1")
|
||||
("boot_multicons" . "YES")
|
||||
("boot_serial" . "YES")
|
||||
("console" . "comconsole,vidconsole"))
|
||||
#:rc-conf-entries '(("clear_tmp_enable" . "NO")
|
||||
("hostid_enable" . "NO")
|
||||
("sendmail_enable" . "NONE")
|
||||
("sshd_enable" . "YES")
|
||||
("ifconfig_xn0" . "SYNCDHCP")
|
||||
("ifconfig_em0" . "SYNCDHCP")
|
||||
("ifconfig_vtnet0" . "SYNCDHCP"))
|
||||
#:init-mode 'shepherd-pid1
|
||||
#:ready-marker "/var/lib/fruix/ready"
|
||||
#:root-authorized-keys '("__ROOT_AUTHORIZED_KEY__")))
|
||||
8
tests/system/phase16-txz-freebsd-source.scm.in
Normal file
8
tests/system/phase16-txz-freebsd-source.scm.in
Normal file
@@ -0,0 +1,8 @@
|
||||
(use-modules (fruix packages freebsd))
|
||||
|
||||
(define phase16-source
|
||||
(freebsd-source
|
||||
#:name "__SOURCE_NAME__"
|
||||
#:kind 'src-txz
|
||||
#:url "__SOURCE_URL__"
|
||||
#:sha256 "__SOURCE_SHA256__"))
|
||||
91
tests/system/phase17-git-source-operating-system.scm.in
Normal file
91
tests/system/phase17-git-source-operating-system.scm.in
Normal file
@@ -0,0 +1,91 @@
|
||||
(use-modules (fruix system freebsd)
|
||||
(fruix packages freebsd))
|
||||
|
||||
(define phase17-source
|
||||
(freebsd-source
|
||||
#:name "__SOURCE_NAME__"
|
||||
#:kind 'git
|
||||
#:ref "__SOURCE_REF__"
|
||||
#:commit "__SOURCE_COMMIT__"))
|
||||
|
||||
(define phase17-base
|
||||
(freebsd-base
|
||||
#:name "__BASE_NAME__"
|
||||
#:version-label "__BASE_VERSION_LABEL__"
|
||||
#:release "__BASE_RELEASE__"
|
||||
#:branch "__BASE_BRANCH__"
|
||||
#:source phase17-source
|
||||
#:source-root "__DECLARED_SOURCE_ROOT__"
|
||||
#:target "amd64"
|
||||
#:target-arch "amd64"
|
||||
#:kernconf "GENERIC"))
|
||||
|
||||
(define phase17-operating-system
|
||||
(operating-system
|
||||
#:host-name "fruix-freebsd"
|
||||
#:freebsd-base phase17-base
|
||||
#:kernel (freebsd-native-kernel-for phase17-base)
|
||||
#:bootloader (freebsd-native-bootloader-for phase17-base)
|
||||
#:base-packages (freebsd-native-system-packages-for phase17-base)
|
||||
#:groups (list (user-group #:name "wheel" #:gid 0 #:system? #t)
|
||||
(user-group #:name "sshd" #:gid 22 #:system? #t)
|
||||
(user-group #:name "_dhcp" #:gid 65 #:system? #t)
|
||||
(user-group #:name "operator" #:gid 1000 #:system? #f))
|
||||
#:users (list (user-account #:name "root"
|
||||
#:uid 0
|
||||
#:group "wheel"
|
||||
#:comment "Charlie &"
|
||||
#:home "/root"
|
||||
#:shell "/bin/sh"
|
||||
#:system? #t)
|
||||
(user-account #:name "sshd"
|
||||
#:uid 22
|
||||
#:group "sshd"
|
||||
#:comment "Secure Shell Daemon"
|
||||
#:home "/var/empty"
|
||||
#:shell "/usr/sbin/nologin"
|
||||
#:system? #t)
|
||||
(user-account #:name "_dhcp"
|
||||
#:uid 65
|
||||
#:group "_dhcp"
|
||||
#:comment "dhcp programs"
|
||||
#:home "/var/empty"
|
||||
#:shell "/usr/sbin/nologin"
|
||||
#:system? #t)
|
||||
(user-account #:name "operator"
|
||||
#:uid 1000
|
||||
#:group "operator"
|
||||
#:supplementary-groups '("wheel")
|
||||
#:comment "Fruix Operator"
|
||||
#:home "/home/operator"
|
||||
#:shell "/bin/sh"
|
||||
#:system? #f))
|
||||
#:file-systems (list (file-system #:device "/dev/gpt/fruix-root"
|
||||
#:mount-point "/"
|
||||
#:type "ufs"
|
||||
#:options "rw"
|
||||
#:needed-for-boot? #t)
|
||||
(file-system #:device "devfs"
|
||||
#:mount-point "/dev"
|
||||
#:type "devfs"
|
||||
#:options "rw"
|
||||
#:needed-for-boot? #t)
|
||||
(file-system #:device "tmpfs"
|
||||
#:mount-point "/tmp"
|
||||
#:type "tmpfs"
|
||||
#:options "rw,size=64m"))
|
||||
#:services '(shepherd ready-marker sshd)
|
||||
#:loader-entries '(("autoboot_delay" . "1")
|
||||
("boot_multicons" . "YES")
|
||||
("boot_serial" . "YES")
|
||||
("console" . "comconsole,vidconsole"))
|
||||
#:rc-conf-entries '(("clear_tmp_enable" . "NO")
|
||||
("hostid_enable" . "NO")
|
||||
("sendmail_enable" . "NONE")
|
||||
("sshd_enable" . "YES")
|
||||
("ifconfig_xn0" . "SYNCDHCP")
|
||||
("ifconfig_em0" . "SYNCDHCP")
|
||||
("ifconfig_vtnet0" . "SYNCDHCP"))
|
||||
#:init-mode 'shepherd-pid1
|
||||
#:ready-marker "/var/lib/fruix/ready"
|
||||
#:root-authorized-keys '("__ROOT_AUTHORIZED_KEY__")))
|
||||
91
tests/system/phase17-txz-source-operating-system.scm.in
Normal file
91
tests/system/phase17-txz-source-operating-system.scm.in
Normal file
@@ -0,0 +1,91 @@
|
||||
(use-modules (fruix system freebsd)
|
||||
(fruix packages freebsd))
|
||||
|
||||
(define phase17-source
|
||||
(freebsd-source
|
||||
#:name "__SOURCE_NAME__"
|
||||
#:kind 'src-txz
|
||||
#:url "__SOURCE_URL__"
|
||||
#:sha256 "__SOURCE_SHA256__"))
|
||||
|
||||
(define phase17-base
|
||||
(freebsd-base
|
||||
#:name "__BASE_NAME__"
|
||||
#:version-label "__BASE_VERSION_LABEL__"
|
||||
#:release "__BASE_RELEASE__"
|
||||
#:branch "__BASE_BRANCH__"
|
||||
#:source phase17-source
|
||||
#:source-root "__DECLARED_SOURCE_ROOT__"
|
||||
#:target "amd64"
|
||||
#:target-arch "amd64"
|
||||
#:kernconf "GENERIC"))
|
||||
|
||||
(define phase17-operating-system
|
||||
(operating-system
|
||||
#:host-name "fruix-freebsd"
|
||||
#:freebsd-base phase17-base
|
||||
#:kernel (freebsd-native-kernel-for phase17-base)
|
||||
#:bootloader (freebsd-native-bootloader-for phase17-base)
|
||||
#:base-packages (freebsd-native-system-packages-for phase17-base)
|
||||
#:groups (list (user-group #:name "wheel" #:gid 0 #:system? #t)
|
||||
(user-group #:name "sshd" #:gid 22 #:system? #t)
|
||||
(user-group #:name "_dhcp" #:gid 65 #:system? #t)
|
||||
(user-group #:name "operator" #:gid 1000 #:system? #f))
|
||||
#:users (list (user-account #:name "root"
|
||||
#:uid 0
|
||||
#:group "wheel"
|
||||
#:comment "Charlie &"
|
||||
#:home "/root"
|
||||
#:shell "/bin/sh"
|
||||
#:system? #t)
|
||||
(user-account #:name "sshd"
|
||||
#:uid 22
|
||||
#:group "sshd"
|
||||
#:comment "Secure Shell Daemon"
|
||||
#:home "/var/empty"
|
||||
#:shell "/usr/sbin/nologin"
|
||||
#:system? #t)
|
||||
(user-account #:name "_dhcp"
|
||||
#:uid 65
|
||||
#:group "_dhcp"
|
||||
#:comment "dhcp programs"
|
||||
#:home "/var/empty"
|
||||
#:shell "/usr/sbin/nologin"
|
||||
#:system? #t)
|
||||
(user-account #:name "operator"
|
||||
#:uid 1000
|
||||
#:group "operator"
|
||||
#:supplementary-groups '("wheel")
|
||||
#:comment "Fruix Operator"
|
||||
#:home "/home/operator"
|
||||
#:shell "/bin/sh"
|
||||
#:system? #f))
|
||||
#:file-systems (list (file-system #:device "/dev/gpt/fruix-root"
|
||||
#:mount-point "/"
|
||||
#:type "ufs"
|
||||
#:options "rw"
|
||||
#:needed-for-boot? #t)
|
||||
(file-system #:device "devfs"
|
||||
#:mount-point "/dev"
|
||||
#:type "devfs"
|
||||
#:options "rw"
|
||||
#:needed-for-boot? #t)
|
||||
(file-system #:device "tmpfs"
|
||||
#:mount-point "/tmp"
|
||||
#:type "tmpfs"
|
||||
#:options "rw,size=64m"))
|
||||
#:services '(shepherd ready-marker sshd)
|
||||
#:loader-entries '(("autoboot_delay" . "1")
|
||||
("boot_multicons" . "YES")
|
||||
("boot_serial" . "YES")
|
||||
("console" . "comconsole,vidconsole"))
|
||||
#:rc-conf-entries '(("clear_tmp_enable" . "NO")
|
||||
("hostid_enable" . "NO")
|
||||
("sendmail_enable" . "NONE")
|
||||
("sshd_enable" . "YES")
|
||||
("ifconfig_xn0" . "SYNCDHCP")
|
||||
("ifconfig_em0" . "SYNCDHCP")
|
||||
("ifconfig_vtnet0" . "SYNCDHCP"))
|
||||
#:init-mode 'shepherd-pid1
|
||||
#:ready-marker "/var/lib/fruix/ready"
|
||||
#:root-authorized-keys '("__ROOT_AUTHORIZED_KEY__")))
|
||||
91
tests/system/phase18-install-operating-system.scm.in
Normal file
91
tests/system/phase18-install-operating-system.scm.in
Normal file
@@ -0,0 +1,91 @@
|
||||
(use-modules (fruix system freebsd)
|
||||
(fruix packages freebsd))
|
||||
|
||||
(define phase18-source
|
||||
(freebsd-source
|
||||
#:name "__SOURCE_NAME__"
|
||||
#:kind 'git
|
||||
#:ref "__SOURCE_REF__"
|
||||
#:commit "__SOURCE_COMMIT__"))
|
||||
|
||||
(define phase18-base
|
||||
(freebsd-base
|
||||
#:name "__BASE_NAME__"
|
||||
#:version-label "__BASE_VERSION_LABEL__"
|
||||
#:release "__BASE_RELEASE__"
|
||||
#:branch "__BASE_BRANCH__"
|
||||
#:source phase18-source
|
||||
#:source-root "__DECLARED_SOURCE_ROOT__"
|
||||
#:target "amd64"
|
||||
#:target-arch "amd64"
|
||||
#:kernconf "GENERIC"))
|
||||
|
||||
(define phase18-operating-system
|
||||
(operating-system
|
||||
#:host-name "fruix-freebsd"
|
||||
#:freebsd-base phase18-base
|
||||
#:kernel (freebsd-native-kernel-for phase18-base)
|
||||
#:bootloader (freebsd-native-bootloader-for phase18-base)
|
||||
#:base-packages (freebsd-native-system-packages-for phase18-base)
|
||||
#:groups (list (user-group #:name "wheel" #:gid 0 #:system? #t)
|
||||
(user-group #:name "sshd" #:gid 22 #:system? #t)
|
||||
(user-group #:name "_dhcp" #:gid 65 #:system? #t)
|
||||
(user-group #:name "operator" #:gid 1000 #:system? #f))
|
||||
#:users (list (user-account #:name "root"
|
||||
#:uid 0
|
||||
#:group "wheel"
|
||||
#:comment "Charlie &"
|
||||
#:home "/root"
|
||||
#:shell "/bin/sh"
|
||||
#:system? #t)
|
||||
(user-account #:name "sshd"
|
||||
#:uid 22
|
||||
#:group "sshd"
|
||||
#:comment "Secure Shell Daemon"
|
||||
#:home "/var/empty"
|
||||
#:shell "/usr/sbin/nologin"
|
||||
#:system? #t)
|
||||
(user-account #:name "_dhcp"
|
||||
#:uid 65
|
||||
#:group "_dhcp"
|
||||
#:comment "dhcp programs"
|
||||
#:home "/var/empty"
|
||||
#:shell "/usr/sbin/nologin"
|
||||
#:system? #t)
|
||||
(user-account #:name "operator"
|
||||
#:uid 1000
|
||||
#:group "operator"
|
||||
#:supplementary-groups '("wheel")
|
||||
#:comment "Fruix Operator"
|
||||
#:home "/home/operator"
|
||||
#:shell "/bin/sh"
|
||||
#:system? #f))
|
||||
#:file-systems (list (file-system #:device "/dev/gpt/fruix-root"
|
||||
#:mount-point "/"
|
||||
#:type "ufs"
|
||||
#:options "rw"
|
||||
#:needed-for-boot? #t)
|
||||
(file-system #:device "devfs"
|
||||
#:mount-point "/dev"
|
||||
#:type "devfs"
|
||||
#:options "rw"
|
||||
#:needed-for-boot? #t)
|
||||
(file-system #:device "tmpfs"
|
||||
#:mount-point "/tmp"
|
||||
#:type "tmpfs"
|
||||
#:options "rw,size=64m"))
|
||||
#:services '(shepherd ready-marker sshd)
|
||||
#:loader-entries '(("autoboot_delay" . "1")
|
||||
("boot_multicons" . "YES")
|
||||
("boot_serial" . "YES")
|
||||
("console" . "comconsole,vidconsole"))
|
||||
#:rc-conf-entries '(("clear_tmp_enable" . "NO")
|
||||
("hostid_enable" . "NO")
|
||||
("sendmail_enable" . "NONE")
|
||||
("sshd_enable" . "YES")
|
||||
("ifconfig_xn0" . "SYNCDHCP")
|
||||
("ifconfig_em0" . "SYNCDHCP")
|
||||
("ifconfig_vtnet0" . "SYNCDHCP"))
|
||||
#:init-mode 'freebsd-init+rc.d-shepherd
|
||||
#:ready-marker "/var/lib/fruix/ready"
|
||||
#:root-authorized-keys '("__ROOT_AUTHORIZED_KEY__")))
|
||||
@@ -0,0 +1,91 @@
|
||||
(use-modules (fruix system freebsd)
|
||||
(fruix packages freebsd))
|
||||
|
||||
(define phase18-source
|
||||
(freebsd-source
|
||||
#:name "__SOURCE_NAME__"
|
||||
#:kind 'git
|
||||
#:ref "__SOURCE_REF__"
|
||||
#:commit "__SOURCE_COMMIT__"))
|
||||
|
||||
(define phase18-base
|
||||
(freebsd-base
|
||||
#:name "__BASE_NAME__"
|
||||
#:version-label "__BASE_VERSION_LABEL__"
|
||||
#:release "__BASE_RELEASE__"
|
||||
#:branch "__BASE_BRANCH__"
|
||||
#:source phase18-source
|
||||
#:source-root "__DECLARED_SOURCE_ROOT__"
|
||||
#:target "amd64"
|
||||
#:target-arch "amd64"
|
||||
#:kernconf "GENERIC"))
|
||||
|
||||
(define phase18-target-operating-system
|
||||
(operating-system
|
||||
#:host-name "fruix-freebsd"
|
||||
#:freebsd-base phase18-base
|
||||
#:kernel (freebsd-native-kernel-for phase18-base)
|
||||
#:bootloader (freebsd-native-bootloader-for phase18-base)
|
||||
#:base-packages (freebsd-native-system-packages-for phase18-base)
|
||||
#:groups (list (user-group #:name "wheel" #:gid 0 #:system? #t)
|
||||
(user-group #:name "sshd" #:gid 22 #:system? #t)
|
||||
(user-group #:name "_dhcp" #:gid 65 #:system? #t)
|
||||
(user-group #:name "operator" #:gid 1000 #:system? #f))
|
||||
#:users (list (user-account #:name "root"
|
||||
#:uid 0
|
||||
#:group "wheel"
|
||||
#:comment "Charlie &"
|
||||
#:home "/root"
|
||||
#:shell "/bin/sh"
|
||||
#:system? #t)
|
||||
(user-account #:name "sshd"
|
||||
#:uid 22
|
||||
#:group "sshd"
|
||||
#:comment "Secure Shell Daemon"
|
||||
#:home "/var/empty"
|
||||
#:shell "/usr/sbin/nologin"
|
||||
#:system? #t)
|
||||
(user-account #:name "_dhcp"
|
||||
#:uid 65
|
||||
#:group "_dhcp"
|
||||
#:comment "dhcp programs"
|
||||
#:home "/var/empty"
|
||||
#:shell "/usr/sbin/nologin"
|
||||
#:system? #t)
|
||||
(user-account #:name "operator"
|
||||
#:uid 1000
|
||||
#:group "operator"
|
||||
#:supplementary-groups '("wheel")
|
||||
#:comment "Fruix Operator"
|
||||
#:home "/home/operator"
|
||||
#:shell "/bin/sh"
|
||||
#:system? #f))
|
||||
#:file-systems (list (file-system #:device "/dev/gpt/fruix-root"
|
||||
#:mount-point "/"
|
||||
#:type "ufs"
|
||||
#:options "rw"
|
||||
#:needed-for-boot? #t)
|
||||
(file-system #:device "devfs"
|
||||
#:mount-point "/dev"
|
||||
#:type "devfs"
|
||||
#:options "rw"
|
||||
#:needed-for-boot? #t)
|
||||
(file-system #:device "tmpfs"
|
||||
#:mount-point "/tmp"
|
||||
#:type "tmpfs"
|
||||
#:options "rw,size=64m"))
|
||||
#:services '(shepherd ready-marker sshd)
|
||||
#:loader-entries '(("autoboot_delay" . "1")
|
||||
("boot_multicons" . "YES")
|
||||
("boot_serial" . "YES")
|
||||
("console" . "comconsole,vidconsole"))
|
||||
#:rc-conf-entries '(("clear_tmp_enable" . "NO")
|
||||
("hostid_enable" . "NO")
|
||||
("sendmail_enable" . "NONE")
|
||||
("sshd_enable" . "YES")
|
||||
("ifconfig_xn0" . "SYNCDHCP")
|
||||
("ifconfig_em0" . "SYNCDHCP")
|
||||
("ifconfig_vtnet0" . "SYNCDHCP"))
|
||||
#:init-mode 'freebsd-init+rc.d-shepherd
|
||||
#:ready-marker "/var/lib/fruix/ready"
|
||||
#:root-authorized-keys '("__ROOT_AUTHORIZED_KEY__")))
|
||||
@@ -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__")))
|
||||
53
tests/system/phase7-minimal-operating-system.scm
Normal file
53
tests/system/phase7-minimal-operating-system.scm
Normal file
@@ -0,0 +1,53 @@
|
||||
(use-modules (fruix system freebsd)
|
||||
(fruix packages freebsd))
|
||||
|
||||
(define phase7-operating-system
|
||||
(operating-system
|
||||
#:host-name "fruix-freebsd"
|
||||
#:kernel freebsd-kernel
|
||||
#:bootloader freebsd-bootloader
|
||||
#:base-packages (list freebsd-runtime
|
||||
freebsd-userland
|
||||
freebsd-libc
|
||||
freebsd-rc-scripts
|
||||
freebsd-sh
|
||||
freebsd-bash)
|
||||
#:groups (list (user-group #:name "wheel" #:gid 0 #: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 "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/ufs/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)
|
||||
#:loader-entries '(("autoboot_delay" . "1")
|
||||
("console" . "comconsole"))
|
||||
#:rc-conf-entries '(("clear_tmp_enable" . "NO")
|
||||
("hostid_enable" . "NO")
|
||||
("sendmail_enable" . "NONE")
|
||||
("sshd_enable" . "NO"))
|
||||
#:ready-marker "/var/lib/fruix/ready"))
|
||||
77
tests/system/phase9-minimal-operating-system.scm.in
Normal file
77
tests/system/phase9-minimal-operating-system.scm.in
Normal file
@@ -0,0 +1,77 @@
|
||||
(use-modules (fruix system freebsd)
|
||||
(fruix packages freebsd))
|
||||
|
||||
(define phase7-operating-system
|
||||
(operating-system
|
||||
#:host-name "fruix-freebsd"
|
||||
#:kernel freebsd-kernel
|
||||
#:bootloader freebsd-bootloader
|
||||
#:base-packages (list freebsd-runtime
|
||||
freebsd-networking
|
||||
freebsd-openssh
|
||||
freebsd-userland
|
||||
freebsd-libc
|
||||
freebsd-rc-scripts
|
||||
freebsd-sh
|
||||
freebsd-bash)
|
||||
#: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"))
|
||||
#: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__")))
|
||||
102
tests/system/run-phase10-fruix-system-command.sh
Executable file
102
tests/system/run-phase10-fruix-system-command.sh
Executable file
@@ -0,0 +1,102 @@
|
||||
#!/bin/sh
|
||||
set -eu
|
||||
|
||||
project_root=${PROJECT_ROOT:-$(pwd)}
|
||||
os_file=${OS_FILE:-$project_root/tests/system/phase7-minimal-operating-system.scm}
|
||||
system_name=${SYSTEM_NAME:-phase7-operating-system}
|
||||
store_dir=${STORE_DIR:-/frx/store}
|
||||
disk_capacity=${DISK_CAPACITY:-5g}
|
||||
metadata_target=${METADATA_OUT:-}
|
||||
cleanup=0
|
||||
|
||||
if [ -n "${WORKDIR:-}" ]; then
|
||||
workdir=$WORKDIR
|
||||
mkdir -p "$workdir"
|
||||
else
|
||||
workdir=$(mktemp -d /tmp/fruix-phase10-system-command.XXXXXX)
|
||||
cleanup=1
|
||||
fi
|
||||
if [ "${KEEP_WORKDIR:-0}" -eq 1 ]; then
|
||||
cleanup=0
|
||||
fi
|
||||
|
||||
cleanup_workdir() {
|
||||
if [ "$cleanup" -eq 1 ]; then
|
||||
rm -rf "$workdir" 2>/dev/null || sudo rm -rf "$workdir"
|
||||
fi
|
||||
}
|
||||
trap cleanup_workdir EXIT INT TERM
|
||||
|
||||
build_out=$workdir/build.txt
|
||||
image_out=$workdir/image.txt
|
||||
metadata_file=$workdir/phase10-fruix-system-command-metadata.txt
|
||||
|
||||
sudo env \
|
||||
HOME="$HOME" \
|
||||
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}" \
|
||||
"$project_root/bin/fruix" system build "$os_file" --system "$system_name" --store "$store_dir" >"$build_out"
|
||||
|
||||
closure_path=$(sed -n 's/^closure_path=//p' "$build_out")
|
||||
generated_file_count=$(sed -n 's/^generated_file_count=//p' "$build_out")
|
||||
reference_count=$(sed -n 's/^reference_count=//p' "$build_out")
|
||||
|
||||
case "$closure_path" in
|
||||
/frx/store/*-fruix-system-fruix-freebsd) : ;;
|
||||
*) echo "unexpected closure path: $closure_path" >&2; exit 1 ;;
|
||||
esac
|
||||
[ -x "$closure_path/activate" ] || { echo "missing activate script in closure" >&2; exit 1; }
|
||||
[ -n "$generated_file_count" ] || { echo "missing generated_file_count" >&2; exit 1; }
|
||||
[ -n "$reference_count" ] || { echo "missing reference_count" >&2; exit 1; }
|
||||
|
||||
sudo env \
|
||||
HOME="$HOME" \
|
||||
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}" \
|
||||
"$project_root/bin/fruix" system image "$os_file" --system "$system_name" --store "$store_dir" --disk-capacity "$disk_capacity" >"$image_out"
|
||||
|
||||
image_store_path=$(sed -n 's/^image_store_path=//p' "$image_out")
|
||||
disk_image=$(sed -n 's/^disk_image=//p' "$image_out")
|
||||
disk_capacity_reported=$(sed -n 's/^disk_capacity=//p' "$image_out")
|
||||
store_item_count=$(sed -n 's/^store_item_count=//p' "$image_out")
|
||||
|
||||
case "$image_store_path" in
|
||||
/frx/store/*-fruix-bhyve-image-fruix-freebsd) : ;;
|
||||
*) echo "unexpected image store path: $image_store_path" >&2; exit 1 ;;
|
||||
esac
|
||||
case "$disk_image" in
|
||||
/frx/store/*-fruix-bhyve-image-fruix-freebsd/disk.img) : ;;
|
||||
*) echo "unexpected disk image path: $disk_image" >&2; exit 1 ;;
|
||||
esac
|
||||
[ -f "$disk_image" ] || { echo "missing disk image" >&2; exit 1; }
|
||||
[ -n "$disk_capacity_reported" ] || { echo "missing disk capacity report" >&2; exit 1; }
|
||||
[ -n "$store_item_count" ] || { echo "missing store item count" >&2; exit 1; }
|
||||
|
||||
cat >"$metadata_file" <<EOF
|
||||
workdir=$workdir
|
||||
os_file=$os_file
|
||||
system_name=$system_name
|
||||
store_dir=$store_dir
|
||||
closure_path=$closure_path
|
||||
generated_file_count=$generated_file_count
|
||||
reference_count=$reference_count
|
||||
image_store_path=$image_store_path
|
||||
disk_image=$disk_image
|
||||
disk_capacity=$disk_capacity_reported
|
||||
store_item_count=$store_item_count
|
||||
EOF
|
||||
|
||||
if [ -n "$metadata_target" ]; then
|
||||
cp "$metadata_file" "$metadata_target"
|
||||
fi
|
||||
|
||||
printf 'PASS phase10-fruix-system-command\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
|
||||
196
tests/system/run-phase11-shepherd-pid1-qemu.sh
Executable file
196
tests/system/run-phase11-shepherd-pid1-qemu.sh
Executable file
@@ -0,0 +1,196 @@
|
||||
#!/bin/sh
|
||||
set -eu
|
||||
|
||||
repo_root=${PROJECT_ROOT:-$(pwd)}
|
||||
os_template=${OS_TEMPLATE:-$repo_root/tests/system/phase11-shepherd-pid1-operating-system.scm.in}
|
||||
system_name=${SYSTEM_NAME:-phase11-operating-system}
|
||||
metadata_target=${METADATA_OUT:-}
|
||||
root_authorized_key_file=${ROOT_AUTHORIZED_KEY_FILE:-$HOME/.ssh/id_ed25519.pub}
|
||||
root_ssh_private_key_file=${ROOT_SSH_PRIVATE_KEY_FILE:-$HOME/.ssh/id_ed25519}
|
||||
ssh_port=${QEMU_SSH_PORT:-10022}
|
||||
qemu_smp=${QEMU_SMP:-2}
|
||||
disk_capacity=${DISK_CAPACITY:-5g}
|
||||
cleanup=0
|
||||
|
||||
if [ -n "${WORKDIR:-}" ]; then
|
||||
workdir=$WORKDIR
|
||||
mkdir -p "$workdir"
|
||||
else
|
||||
workdir=$(mktemp -d /tmp/fruix-phase11-pid1-qemu.XXXXXX)
|
||||
cleanup=1
|
||||
fi
|
||||
if [ "${KEEP_WORKDIR:-0}" -eq 1 ]; then
|
||||
cleanup=0
|
||||
fi
|
||||
|
||||
phase11_os_file=$workdir/phase11-shepherd-pid1-operating-system.scm
|
||||
phase8_log=$workdir/phase8-system-image.log
|
||||
phase8_metadata=$workdir/phase8-system-image-metadata.txt
|
||||
serial_log=$workdir/serial.log
|
||||
qemu_pidfile=$workdir/qemu.pid
|
||||
metadata_file=$workdir/phase11-shepherd-pid1-qemu-metadata.txt
|
||||
uefi_vars=$workdir/QEMU_UEFI_VARS.fd
|
||||
|
||||
cleanup_workdir() {
|
||||
if [ -f "$qemu_pidfile" ]; then
|
||||
sudo kill "$(sudo cat "$qemu_pidfile")" >/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
|
||||
|
||||
[ -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
|
||||
}
|
||||
cp /usr/local/share/edk2-qemu/QEMU_UEFI_VARS-x86_64.fd "$uefi_vars"
|
||||
root_authorized_key=$(tr -d '\n' < "$root_authorized_key_file")
|
||||
sed "s|__ROOT_AUTHORIZED_KEY__|$root_authorized_key|g" "$os_template" > "$phase11_os_file"
|
||||
|
||||
KEEP_WORKDIR=1 WORKDIR="$workdir/phase8-build" OS_FILE="$phase11_os_file" SYSTEM_NAME="$system_name" DISK_CAPACITY="$disk_capacity" \
|
||||
METADATA_OUT="$phase8_metadata" "$repo_root/tests/system/run-phase8-system-image.sh" >"$phase8_log" 2>&1
|
||||
|
||||
disk_image=$(sed -n 's/^disk_image=//p' "$phase8_metadata")
|
||||
closure_path=$(sed -n 's/^closure_path=//p' "$phase8_metadata")
|
||||
closure_base=$(basename "$closure_path")
|
||||
raw_sha256=$(sed -n 's/^raw_sha256=//p' "$phase8_metadata")
|
||||
image_store_path=$(sed -n 's/^image_store_path=//p' "$phase8_metadata")
|
||||
boot_disk_image=$workdir/boot-disk.img
|
||||
cp "$disk_image" "$boot_disk_image"
|
||||
|
||||
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="$boot_disk_image" \
|
||||
-netdev user,id=net0,hostfwd=tcp::${ssh_port}-:22 \
|
||||
-device virtio-net-pci,netdev=net0
|
||||
|
||||
ssh_guest() {
|
||||
ssh -p "$ssh_port" -i "$root_ssh_private_key_file" \
|
||||
-o BatchMode=yes \
|
||||
-o StrictHostKeyChecking=no \
|
||||
-o UserKnownHostsFile=/dev/null \
|
||||
-o ConnectTimeout=5 \
|
||||
root@127.0.0.1 "$@"
|
||||
}
|
||||
|
||||
for attempt in $(jot 120 1 120); do
|
||||
if ssh_guest 'test -f /var/lib/fruix/ready' >/dev/null 2>&1; then
|
||||
break
|
||||
fi
|
||||
sleep 2
|
||||
done
|
||||
|
||||
ready_marker=$(ssh_guest 'cat /var/lib/fruix/ready')
|
||||
run_current_system_target=$(ssh_guest 'readlink /run/current-system')
|
||||
pid1_command=$(ssh_guest 'ps -p 1 -o command= | sed "s/^ *//"')
|
||||
pid1_binary=$(ssh_guest 'procstat -b 1 2>/dev/null | awk "NR==2 {print \$2}"')
|
||||
shepherd_pid=$(ssh_guest 'cat /var/run/shepherd.pid')
|
||||
shepherd_socket=$(ssh_guest 'test -S /var/run/shepherd.sock && echo present || echo missing')
|
||||
shepherd_status=$(ssh_guest 'test -f /var/run/shepherd.pid && kill -0 "$(cat /var/run/shepherd.pid)" >/dev/null 2>&1 && echo running || echo stopped')
|
||||
sshd_status=$(ssh_guest 'service sshd onestatus >/dev/null 2>&1 && echo running || echo stopped')
|
||||
logger_log=$(ssh_guest 'cat /var/log/fruix-shepherd.log' | tr '\n' ' ')
|
||||
shepherd_bootstrap_tail=$(ssh_guest "awk 'BEGIN { c = 20 } { lines[NR % c] = \$0 } END { start = (NR > c ? NR - c + 1 : 1); for (i = start; i <= NR; i++) print lines[i % c] }' /var/log/shepherd-bootstrap.out 2>/dev/null || true" | tr '\n' ' ')
|
||||
shepherd_log_tail=$(ssh_guest "awk 'BEGIN { c = 20 } { lines[NR % c] = \$0 } END { start = (NR > c ? NR - c + 1 : 1); for (i = start; i <= NR; i++) print lines[i % c] }' /var/log/shepherd.log 2>/dev/null || true" | tr '\n' ' ')
|
||||
guest_dmesg_tail=$(ssh_guest "dmesg | awk 'BEGIN { c = 20 } { lines[NR % c] = \$0 } END { start = (NR > c ? NR - c + 1 : 1); for (i = start; i <= NR; i++) print lines[i % c] }'" | tr '\n' ' ')
|
||||
activate_log=$(ssh_guest 'cat /var/log/fruix-activate.log 2>/dev/null || true' | tr '\n' ' ')
|
||||
login_conf_kind=$(ssh_guest 'if [ -L /etc/login.conf ]; then echo symlink; elif [ -f /etc/login.conf ]; then echo regular; else echo missing; fi')
|
||||
login_conf_db=$(ssh_guest 'test -f /etc/login.conf.db && echo present || echo missing')
|
||||
pwd_dbs=$(ssh_guest 'if [ -f /etc/pwd.db ] && [ -f /etc/spwd.db ]; then echo present; else echo missing; fi')
|
||||
uname_output=$(ssh_guest 'uname -sr')
|
||||
operator_home_listing=$(ssh_guest 'ls -d /home/operator')
|
||||
|
||||
[ "$ready_marker" = ready ] || { echo "unexpected ready marker contents: $ready_marker" >&2; exit 1; }
|
||||
[ "$run_current_system_target" = "/frx/store/$closure_base" ] || {
|
||||
echo "unexpected /run/current-system target in guest: $run_current_system_target" >&2
|
||||
exit 1
|
||||
}
|
||||
[ "$shepherd_pid" = 1 ] || {
|
||||
echo "shepherd is not PID 1: shepherd.pid=$shepherd_pid pid1_command=$pid1_command pid1_binary=$pid1_binary" >&2
|
||||
exit 1
|
||||
}
|
||||
[ "$shepherd_socket" = present ] || { echo "shepherd socket is missing" >&2; exit 1; }
|
||||
[ "$shepherd_status" = running ] || { echo "shepherd is not running" >&2; exit 1; }
|
||||
[ "$sshd_status" = running ] || { echo "sshd is not running" >&2; exit 1; }
|
||||
[ "$login_conf_kind" = regular ] || { echo "/etc/login.conf is not a regular file in guest: $login_conf_kind" >&2; exit 1; }
|
||||
[ "$login_conf_db" = present ] || { echo "/etc/login.conf.db is missing in guest" >&2; exit 1; }
|
||||
[ "$pwd_dbs" = present ] || { echo "pwd.db/spwd.db are missing in guest" >&2; exit 1; }
|
||||
case "$activate_log" in
|
||||
*fruix-activate:done*) : ;;
|
||||
*) echo "activation log does not show successful completion: $activate_log" >&2; exit 1 ;;
|
||||
esac
|
||||
[ "$operator_home_listing" = /home/operator ] || { echo "operator home missing" >&2; exit 1; }
|
||||
|
||||
cat >"$metadata_file" <<EOF
|
||||
workdir=$workdir
|
||||
phase11_os_file=$phase11_os_file
|
||||
phase8_log=$phase8_log
|
||||
phase8_metadata=$phase8_metadata
|
||||
image_store_path=$image_store_path
|
||||
disk_image=$disk_image
|
||||
boot_disk_image=$boot_disk_image
|
||||
closure_path=$closure_path
|
||||
closure_base=$closure_base
|
||||
raw_sha256=$raw_sha256
|
||||
serial_log=$serial_log
|
||||
qemu_pidfile=$qemu_pidfile
|
||||
ssh_port=$ssh_port
|
||||
qemu_smp=$qemu_smp
|
||||
ready_marker=$ready_marker
|
||||
run_current_system_target=$run_current_system_target
|
||||
pid1_command=$pid1_command
|
||||
pid1_binary=$pid1_binary
|
||||
shepherd_pid=$shepherd_pid
|
||||
shepherd_socket=$shepherd_socket
|
||||
shepherd_status=$shepherd_status
|
||||
sshd_status=$sshd_status
|
||||
logger_log=$logger_log
|
||||
shepherd_bootstrap_tail=$shepherd_bootstrap_tail
|
||||
shepherd_log_tail=$shepherd_log_tail
|
||||
guest_dmesg_tail=$guest_dmesg_tail
|
||||
activate_log=$activate_log
|
||||
login_conf_kind=$login_conf_kind
|
||||
login_conf_db=$login_conf_db
|
||||
pwd_dbs=$pwd_dbs
|
||||
uname_output=$uname_output
|
||||
operator_home_listing=$operator_home_listing
|
||||
boot_backend=qemu-uefi-tcg
|
||||
init_mode=shepherd-pid1
|
||||
EOF
|
||||
|
||||
if [ -n "$metadata_target" ]; then
|
||||
mkdir -p "$(dirname "$metadata_target")"
|
||||
cp "$metadata_file" "$metadata_target"
|
||||
fi
|
||||
|
||||
printf 'PASS phase11-shepherd-pid1-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"
|
||||
247
tests/system/run-phase11-shepherd-pid1-xcpng.sh
Executable file
247
tests/system/run-phase11-shepherd-pid1-xcpng.sh
Executable file
@@ -0,0 +1,247 @@
|
||||
#!/bin/sh
|
||||
set -eu
|
||||
|
||||
repo_root=$(CDPATH= cd -- "$(dirname "$0")/../.." && pwd)
|
||||
vm_id=90490f2e-e8fc-4b7a-388e-5c26f0157289
|
||||
os_template=${OS_TEMPLATE:-$repo_root/tests/system/phase11-shepherd-pid1-operating-system.scm.in}
|
||||
system_name=${SYSTEM_NAME:-phase11-operating-system}
|
||||
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}
|
||||
requested_disk_capacity=${DISK_CAPACITY:-}
|
||||
|
||||
cleanup=0
|
||||
if [ -n "${WORKDIR:-}" ]; then
|
||||
workdir=$WORKDIR
|
||||
mkdir -p "$workdir"
|
||||
else
|
||||
workdir=$(mktemp -d /tmp/fruix-phase11-xcpng.XXXXXX)
|
||||
cleanup=1
|
||||
fi
|
||||
if [ "${KEEP_WORKDIR:-0}" -eq 1 ]; then
|
||||
cleanup=0
|
||||
fi
|
||||
|
||||
phase11_os_file=$workdir/phase11-shepherd-pid1-operating-system.scm
|
||||
phase8_log=$workdir/phase8-system-image.log
|
||||
phase8_metadata=$workdir/phase8-system-image-metadata.txt
|
||||
arp_scan_log=$workdir/arp-scan.log
|
||||
ssh_stdout=$workdir/ssh.out
|
||||
ssh_stderr=$workdir/ssh.err
|
||||
metadata_file=$workdir/phase11-shepherd-pid1-xcpng-metadata.txt
|
||||
vdi_info_json=$workdir/vdi-info.json
|
||||
vm_info_json=$workdir/vm-info.json
|
||||
upload_image=$workdir/disk.vhd
|
||||
|
||||
cleanup_workdir() {
|
||||
if [ "$cleanup" -eq 1 ]; then
|
||||
rm -rf "$workdir"
|
||||
fi
|
||||
}
|
||||
trap cleanup_workdir EXIT INT TERM
|
||||
|
||||
[ -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
|
||||
}
|
||||
root_authorized_key=$(tr -d '\n' < "$root_authorized_key_file")
|
||||
|
||||
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)
|
||||
[ -n "$vdi_id" ] || { echo "failed to discover 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; }
|
||||
|
||||
if [ -n "$requested_disk_capacity" ] && [ "$requested_disk_capacity" != "$vdi_size" ]; then
|
||||
echo "existing XCP-ng import path requires an image that matches the target VDI size; use DISK_CAPACITY=$vdi_size or leave it unset" >&2
|
||||
exit 1
|
||||
fi
|
||||
disk_capacity=$vdi_size
|
||||
requested_disk_bytes=$vdi_size
|
||||
|
||||
sed "s|__ROOT_AUTHORIZED_KEY__|$root_authorized_key|g" "$os_template" > "$phase11_os_file"
|
||||
|
||||
KEEP_WORKDIR=1 WORKDIR=$workdir/phase8-build OS_FILE=$phase11_os_file SYSTEM_NAME=$system_name DISK_CAPACITY=$disk_capacity \
|
||||
METADATA_OUT=$phase8_metadata "$repo_root/tests/system/run-phase8-system-image.sh" \
|
||||
>"$phase8_log" 2>&1
|
||||
|
||||
disk_image=$(sed -n 's/^disk_image=//p' "$phase8_metadata")
|
||||
closure_path=$(sed -n 's/^closure_path=//p' "$phase8_metadata")
|
||||
closure_base=$(basename "$closure_path")
|
||||
raw_sha256=$(sed -n 's/^raw_sha256=//p' "$phase8_metadata")
|
||||
image_store_path=$(sed -n 's/^image_store_path=//p' "$phase8_metadata")
|
||||
guile_store=$(grep 'fruix-guile-runtime-3.0$' "$closure_path/.references" | head -n 1)
|
||||
guile_extra_store=$(grep 'fruix-guile-extra-3.0$' "$closure_path/.references" | head -n 1)
|
||||
shepherd_store=$(grep 'fruix-shepherd-runtime-1.0.9$' "$closure_path/.references" | head -n 1)
|
||||
|
||||
command -v qemu-img >/dev/null 2>&1 || {
|
||||
echo "qemu-img is required to convert the raw Fruix image to XCP-ng-compatible VHD" >&2
|
||||
exit 1
|
||||
}
|
||||
qemu-img convert -f raw -O vpc -o subformat=dynamic,force_size=on "$disk_image" "$upload_image"
|
||||
upload_sha256=$(sha256 -q "$upload_image")
|
||||
upload_size_bytes=$(stat -f '%z' "$upload_image")
|
||||
|
||||
xo-cli vm.stop id=$vm_id force=true >/dev/null 2>&1 || true
|
||||
xo-cli disk.importContent id=$vdi_id @=$upload_image >"$workdir/disk-import.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.out"
|
||||
|
||||
vm_mac=$(jq -r '.[0].VIFs[0]' "$vm_info_json")
|
||||
if [ -n "$vm_mac" ] && [ "$vm_mac" != null ]; then
|
||||
vm_mac=$(xo-cli list-objects type=VIF | jq -r '.[] | select(.id=="'$vm_mac'") | .MAC' | tr 'A-Z' 'a-z')
|
||||
else
|
||||
vm_mac=
|
||||
fi
|
||||
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%.*}
|
||||
|
||||
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_ip=
|
||||
for attempt in $(jot 90 1 90); do
|
||||
: >"$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
|
||||
if [ -n "$vm_mac" ]; then
|
||||
guest_ip=$(arp -an | awk -v mac="$vm_mac" 'tolower($4)==mac {gsub(/[()]/,"",$2); print $2; exit}')
|
||||
fi
|
||||
if [ -n "$guest_ip" ]; then
|
||||
if ssh -i "$root_ssh_private_key_file" \
|
||||
-o BatchMode=yes \
|
||||
-o StrictHostKeyChecking=no \
|
||||
-o UserKnownHostsFile=/dev/null \
|
||||
-o ConnectTimeout=3 \
|
||||
root@"$guest_ip" 'test -f /var/lib/fruix/ready' >"$ssh_stdout" 2>"$ssh_stderr"; then
|
||||
break
|
||||
fi
|
||||
fi
|
||||
sleep 5
|
||||
done
|
||||
|
||||
[ -n "$guest_ip" ] || {
|
||||
echo "guest IP was not discovered; manual console inspection is likely required" >&2
|
||||
exit 1
|
||||
}
|
||||
|
||||
ready_marker=$(ssh_guest 'cat /var/lib/fruix/ready')
|
||||
run_current_system_target=$(ssh_guest 'readlink /run/current-system')
|
||||
pid1_command=$(ssh_guest 'ps -p 1 -o command= | sed "s/^ *//"')
|
||||
shepherd_pid=$(ssh_guest 'cat /var/run/shepherd.pid')
|
||||
shepherd_socket=$(ssh_guest 'test -S /var/run/shepherd.sock && echo present || echo missing')
|
||||
shepherd_status=$(ssh_guest 'test -f /var/run/shepherd.pid && kill -0 "$(cat /var/run/shepherd.pid)" >/dev/null 2>&1 && echo running || echo stopped')
|
||||
logger_log=$(ssh_guest 'cat /var/log/fruix-shepherd.log' | tr '\n' ' ')
|
||||
shepherd_bootstrap_tail=$(ssh_guest "awk 'BEGIN { c = 20 } { lines[NR % c] = \$0 } END { start = (NR > c ? NR - c + 1 : 1); for (i = start; i <= NR; i++) print lines[i % c] }' /var/log/shepherd-bootstrap.out 2>/dev/null || true" | tr '\n' ' ')
|
||||
shepherd_log_tail=$(ssh_guest "awk 'BEGIN { c = 20 } { lines[NR % c] = \$0 } END { start = (NR > c ? NR - c + 1 : 1); for (i = start; i <= NR; i++) print lines[i % c] }' /var/log/shepherd.log 2>/dev/null || true" | tr '\n' ' ')
|
||||
guest_dmesg_tail=$(ssh_guest "dmesg | awk 'BEGIN { c = 20 } { lines[NR % c] = \$0 } END { start = (NR > c ? NR - c + 1 : 1); for (i = start; i <= NR; i++) print lines[i % c] }'" | tr '\n' ' ')
|
||||
sshd_status=$(ssh_guest 'service sshd onestatus >/dev/null 2>&1 && echo running || echo stopped')
|
||||
uname_output=$(ssh_guest 'uname -sr')
|
||||
operator_home_listing=$(ssh_guest 'ls -d /home/operator')
|
||||
compat_prefix_shims=$(ssh_guest 'for p in /tmp/guile-freebsd-validate-install /tmp/guile-gnutls-freebsd-validate-install /tmp/shepherd-freebsd-validate-install; do if [ -e "$p" ] || [ -L "$p" ]; then echo present; exit 0; fi; done; echo absent')
|
||||
guile_module_smoke=$(ssh_guest "env LANG='C.UTF-8' LC_ALL='C.UTF-8' LD_LIBRARY_PATH='$guile_extra_store/lib:$guile_store/lib:/usr/local/lib' 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' GUILE_LOAD_PATH='$shepherd_store/share/guile/site/3.0:$guile_extra_store/share/guile/site/3.0' GUILE_SYSTEM_COMPILED_PATH='$guile_store/lib/guile/3.0/ccache:$guile_store/lib/guile/3.0/site-ccache' GUILE_LOAD_COMPILED_PATH='$shepherd_store/lib/guile/3.0/site-ccache:$guile_extra_store/lib/guile/3.0/site-ccache' GUILE_SYSTEM_EXTENSIONS_PATH='$guile_store/lib/guile/3.0/extensions' GUILE_EXTENSIONS_PATH='$guile_extra_store/lib/guile/3.0/extensions' '$guile_store/bin/guile' --no-auto-compile -c '(use-modules (fibers config) (gnutls) (shepherd config)) (display \"ok\") (newline)'")
|
||||
activate_preview=$(ssh_guest 'head -n 5 /run/current-system/activate' | tr '\n' ' ')
|
||||
activate_log=$(ssh_guest 'cat /var/log/fruix-activate.log 2>/dev/null || true' | tr '\n' ' ')
|
||||
login_conf_kind=$(ssh_guest 'if [ -L /etc/login.conf ]; then echo symlink; elif [ -f /etc/login.conf ]; then echo regular; else echo missing; fi')
|
||||
login_conf_db=$(ssh_guest 'test -f /etc/login.conf.db && echo present || echo missing')
|
||||
pwd_dbs=$(ssh_guest 'if [ -f /etc/pwd.db ] && [ -f /etc/spwd.db ]; then echo present; else echo missing; fi')
|
||||
|
||||
[ "$ready_marker" = ready ] || { echo "unexpected ready marker contents: $ready_marker" >&2; exit 1; }
|
||||
[ "$shepherd_pid" = 1 ] || { echo "shepherd is not PID 1: pid=$shepherd_pid command=$pid1_command" >&2; exit 1; }
|
||||
[ "$shepherd_socket" = present ] || { echo "shepherd socket is missing" >&2; exit 1; }
|
||||
[ "$shepherd_status" = running ] || { echo "shepherd is not running" >&2; exit 1; }
|
||||
[ "$sshd_status" = running ] || { echo "sshd is not running" >&2; exit 1; }
|
||||
[ "$compat_prefix_shims" = absent ] || { echo "compatibility prefix shims are still present in /tmp" >&2; exit 1; }
|
||||
[ "$guile_module_smoke" = ok ] || { echo "guest Guile module smoke failed: $guile_module_smoke" >&2; exit 1; }
|
||||
[ "$login_conf_kind" = regular ] || { echo "/etc/login.conf is not a regular file in guest: $login_conf_kind" >&2; exit 1; }
|
||||
[ "$login_conf_db" = present ] || { echo "/etc/login.conf.db is missing in guest" >&2; exit 1; }
|
||||
[ "$pwd_dbs" = present ] || { echo "pwd.db/spwd.db are missing in guest" >&2; exit 1; }
|
||||
case "$activate_log" in
|
||||
*fruix-activate:done*) : ;;
|
||||
*) echo "activation log does not show successful completion: $activate_log" >&2; exit 1 ;;
|
||||
esac
|
||||
[ "$run_current_system_target" = "/frx/store/$closure_base" ] || {
|
||||
echo "unexpected /run/current-system target in guest: $run_current_system_target" >&2
|
||||
exit 1
|
||||
}
|
||||
[ "$operator_home_listing" = /home/operator ] || { echo "operator home missing" >&2; exit 1; }
|
||||
|
||||
cat >"$metadata_file" <<EOF
|
||||
workdir=$workdir
|
||||
vm_id=$vm_id
|
||||
vdi_id=$vdi_id
|
||||
vdi_size=$vdi_size
|
||||
disk_capacity=$disk_capacity
|
||||
requested_disk_capacity=${requested_disk_capacity:-<auto>}
|
||||
requested_disk_bytes=$requested_disk_bytes
|
||||
phase11_os_file=$phase11_os_file
|
||||
phase8_log=$phase8_log
|
||||
phase8_metadata=$phase8_metadata
|
||||
image_store_path=$image_store_path
|
||||
disk_image=$disk_image
|
||||
upload_image=$upload_image
|
||||
upload_format=vhd-dynamic
|
||||
upload_sha256=$upload_sha256
|
||||
upload_size_bytes=$upload_size_bytes
|
||||
closure_path=$closure_path
|
||||
closure_base=$closure_base
|
||||
raw_sha256=$raw_sha256
|
||||
guest_ip=$guest_ip
|
||||
vm_mac=$vm_mac
|
||||
ready_marker=$ready_marker
|
||||
run_current_system_target=$run_current_system_target
|
||||
pid1_command=$pid1_command
|
||||
shepherd_pid=$shepherd_pid
|
||||
shepherd_socket=$shepherd_socket
|
||||
shepherd_status=$shepherd_status
|
||||
sshd_status=$sshd_status
|
||||
logger_log=$logger_log
|
||||
shepherd_bootstrap_tail=$shepherd_bootstrap_tail
|
||||
shepherd_log_tail=$shepherd_log_tail
|
||||
guest_dmesg_tail=$guest_dmesg_tail
|
||||
uname_output=$uname_output
|
||||
operator_home_listing=$operator_home_listing
|
||||
compat_prefix_shims=$compat_prefix_shims
|
||||
guile_module_smoke=$guile_module_smoke
|
||||
activate_preview=$activate_preview
|
||||
activate_log=$activate_log
|
||||
login_conf_kind=$login_conf_kind
|
||||
login_conf_db=$login_conf_db
|
||||
pwd_dbs=$pwd_dbs
|
||||
boot_backend=xcp-ng-xo-cli
|
||||
init_mode=shepherd-pid1
|
||||
operator_access=ssh-root-key
|
||||
root_authorized_key_file=$root_authorized_key_file
|
||||
root_ssh_private_key_file=$root_ssh_private_key_file
|
||||
EOF
|
||||
|
||||
if [ -n "$metadata_target" ]; then
|
||||
mkdir -p "$(dirname "$metadata_target")"
|
||||
cp "$metadata_file" "$metadata_target"
|
||||
fi
|
||||
|
||||
printf 'PASS phase11-shepherd-pid1-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"
|
||||
240
tests/system/run-phase13-native-base-build.sh
Executable file
240
tests/system/run-phase13-native-base-build.sh
Executable file
@@ -0,0 +1,240 @@
|
||||
#!/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/phase13-native-base-pid1-operating-system.scm.in}
|
||||
system_name=${SYSTEM_NAME:-phase13-operating-system}
|
||||
store_dir=${STORE_DIR:-/frx/store}
|
||||
metadata_target=${METADATA_OUT:-}
|
||||
root_authorized_key_file=${ROOT_AUTHORIZED_KEY_FILE:-$HOME/.ssh/id_ed25519.pub}
|
||||
|
||||
[ -x "$fruix_cmd" ] || {
|
||||
echo "fruix command is not executable: $fruix_cmd" >&2
|
||||
exit 1
|
||||
}
|
||||
[ -f "$os_template" ] || {
|
||||
echo "missing operating-system template: $os_template" >&2
|
||||
exit 1
|
||||
}
|
||||
[ -f "$root_authorized_key_file" ] || {
|
||||
echo "missing root authorized key file: $root_authorized_key_file" >&2
|
||||
exit 1
|
||||
}
|
||||
|
||||
cleanup=0
|
||||
if [ -n "${WORKDIR:-}" ]; then
|
||||
workdir=$WORKDIR
|
||||
mkdir -p "$workdir"
|
||||
else
|
||||
workdir=$(mktemp -d /tmp/fruix-phase13-native-build.XXXXXX)
|
||||
cleanup=1
|
||||
fi
|
||||
if [ "${KEEP_WORKDIR:-0}" -eq 1 ]; then
|
||||
cleanup=0
|
||||
fi
|
||||
|
||||
cleanup_workdir() {
|
||||
if [ "$cleanup" -eq 1 ]; then
|
||||
rm -rf "$workdir" 2>/dev/null || sudo rm -rf "$workdir"
|
||||
fi
|
||||
}
|
||||
trap cleanup_workdir EXIT INT TERM
|
||||
|
||||
phase13_os_file=$workdir/phase13-native-base-operating-system.scm
|
||||
build_out_a=$workdir/build-a.txt
|
||||
build_out_b=$workdir/build-b.txt
|
||||
metadata_file=$workdir/phase13-native-base-build-metadata.txt
|
||||
root_authorized_key=$(tr -d '\n' < "$root_authorized_key_file")
|
||||
sed "s|__ROOT_AUTHORIZED_KEY__|$root_authorized_key|g" "$os_template" > "$phase13_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}" \
|
||||
"$@"
|
||||
}
|
||||
|
||||
printf 'Using fruix command: %s\n' "$fruix_cmd"
|
||||
printf 'Working directory: %s\n' "$workdir"
|
||||
printf 'Store directory: %s\n' "$store_dir"
|
||||
printf 'OS template: %s\n' "$os_template"
|
||||
|
||||
action_env "$fruix_cmd" system build "$phase13_os_file" --system "$system_name" --store "$store_dir" >"$build_out_a"
|
||||
action_env "$fruix_cmd" system build "$phase13_os_file" --system "$system_name" --store "$store_dir" >"$build_out_b"
|
||||
|
||||
closure_path=$(sed -n 's/^closure_path=//p' "$build_out_a")
|
||||
closure_rebuild_path=$(sed -n 's/^closure_path=//p' "$build_out_b")
|
||||
kernel_store=$(sed -n 's/^kernel_store=//p' "$build_out_a")
|
||||
bootloader_store=$(sed -n 's/^bootloader_store=//p' "$build_out_a")
|
||||
host_base_store_count=$(sed -n 's/^host_base_store_count=//p' "$build_out_a")
|
||||
host_base_stores=$(sed -n 's/^host_base_stores=//p' "$build_out_a")
|
||||
native_base_store_count=$(sed -n 's/^native_base_store_count=//p' "$build_out_a")
|
||||
native_base_stores=$(sed -n 's/^native_base_stores=//p' "$build_out_a")
|
||||
fruix_runtime_store_count=$(sed -n 's/^fruix_runtime_store_count=//p' "$build_out_a")
|
||||
fruix_runtime_stores=$(sed -n 's/^fruix_runtime_stores=//p' "$build_out_a")
|
||||
host_base_provenance_file=$(sed -n 's/^host_base_provenance_file=//p' "$build_out_a")
|
||||
store_layout_file=$(sed -n 's/^store_layout_file=//p' "$build_out_a")
|
||||
reference_count=$(sed -n 's/^reference_count=//p' "$build_out_a")
|
||||
generated_file_count=$(sed -n 's/^generated_file_count=//p' "$build_out_a")
|
||||
|
||||
[ -n "$closure_path" ] || { echo "missing closure path" >&2; exit 1; }
|
||||
[ "$closure_path" = "$closure_rebuild_path" ] || {
|
||||
echo "native-base closure path was not reproducible: $closure_path != $closure_rebuild_path" >&2
|
||||
exit 1
|
||||
}
|
||||
case "$kernel_store" in
|
||||
/frx/store/*-freebsd-native-kernel-15.0-STABLE) : ;;
|
||||
*) echo "unexpected native kernel store path: $kernel_store" >&2; exit 1 ;;
|
||||
esac
|
||||
case "$bootloader_store" in
|
||||
/frx/store/*-freebsd-bootloader-15.0-STABLE) : ;;
|
||||
*) echo "unexpected bootloader store path: $bootloader_store" >&2; exit 1 ;;
|
||||
esac
|
||||
[ "$host_base_store_count" = 1 ] || { echo "expected exactly one host-staged base store, got: $host_base_store_count" >&2; exit 1; }
|
||||
[ "$native_base_store_count" = 2 ] || { echo "expected exactly two native base stores, got: $native_base_store_count" >&2; exit 1; }
|
||||
[ -n "$fruix_runtime_store_count" ] || { echo "missing Fruix runtime store count" >&2; exit 1; }
|
||||
[ -n "$reference_count" ] || { echo "missing reference count" >&2; exit 1; }
|
||||
[ -n "$generated_file_count" ] || { echo "missing generated file count" >&2; exit 1; }
|
||||
|
||||
world_store=$(printf '%s\n' "$native_base_stores" | tr ',' '\n' | grep 'freebsd-native-world-15.0-STABLE$' | head -n 1)
|
||||
[ -n "$world_store" ] || { echo "failed to recover native world store from metadata" >&2; exit 1; }
|
||||
|
||||
world_build_info=$world_store/.freebsd-native-build-info.scm
|
||||
kernel_build_info=$kernel_store/.freebsd-native-build-info.scm
|
||||
[ -f "$world_build_info" ] || { echo "missing world build info: $world_build_info" >&2; exit 1; }
|
||||
[ -f "$kernel_build_info" ] || { echo "missing kernel build info: $kernel_build_info" >&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; }
|
||||
|
||||
for path in \
|
||||
"$kernel_store/boot/kernel/kernel" \
|
||||
"$kernel_store/boot/kernel/linker.hints" \
|
||||
"$world_store/bin/sh" \
|
||||
"$world_store/sbin/init" \
|
||||
"$world_store/etc/rc" \
|
||||
"$world_store/usr/sbin/sshd" \
|
||||
"$world_store/sbin/dhclient" \
|
||||
"$world_store/usr/bin/cap_mkdb" \
|
||||
"$world_store/usr/sbin/pwd_mkdb" \
|
||||
"$world_store/usr/share/locale/C.UTF-8/LC_CTYPE"
|
||||
do
|
||||
[ -e "$path" ] || {
|
||||
echo "required native world/kernel path missing: $path" >&2
|
||||
exit 1
|
||||
}
|
||||
done
|
||||
|
||||
[ ! -e "$world_store/usr/share/man" ] || { echo "native world still contains pruned man pages" >&2; exit 1; }
|
||||
[ ! -e "$world_store/usr/tests" ] || { echo "native world still contains pruned tests" >&2; exit 1; }
|
||||
|
||||
grep -F 'native-base-stores' "$store_layout_file" >/dev/null || {
|
||||
echo "store layout metadata is missing native-base-stores" >&2
|
||||
exit 1
|
||||
}
|
||||
|
||||
grep -F '(materialized-source' "$world_build_info" >/dev/null || {
|
||||
echo "world build info is missing materialized source metadata" >&2
|
||||
exit 1
|
||||
}
|
||||
grep -F 'path . "/usr/src"' "$world_build_info" >/dev/null || {
|
||||
echo "world build info is missing declared local-tree source provenance" >&2
|
||||
exit 1
|
||||
}
|
||||
grep -F 'source-root . "/frx/store/' "$world_build_info" >/dev/null || {
|
||||
echo "world build info is not using a materialized source root" >&2
|
||||
exit 1
|
||||
}
|
||||
|
||||
grep -F '(materialized-source' "$kernel_build_info" >/dev/null || {
|
||||
echo "kernel build info is missing materialized source metadata" >&2
|
||||
exit 1
|
||||
}
|
||||
grep -F 'path . "/usr/src"' "$kernel_build_info" >/dev/null || {
|
||||
echo "kernel build info is missing declared local-tree source provenance" >&2
|
||||
exit 1
|
||||
}
|
||||
grep -F 'source-root . "/frx/store/' "$kernel_build_info" >/dev/null || {
|
||||
echo "kernel build info is not using a materialized source root" >&2
|
||||
exit 1
|
||||
}
|
||||
|
||||
world_source_tree_sha256=$(grep -o 'source-tree-sha256 . "[^"]*"' "$world_build_info" | head -n 1 | cut -d'"' -f2)
|
||||
kernel_source_tree_sha256=$(grep -o 'source-tree-sha256 . "[^"]*"' "$kernel_build_info" | head -n 1 | cut -d'"' -f2)
|
||||
world_build_root=$(grep -o 'build-root . "[^"]*"' "$world_build_info" | head -n 1 | cut -d'"' -f2)
|
||||
kernel_build_root=$(grep -o 'build-root . "[^"]*"' "$kernel_build_info" | head -n 1 | cut -d'"' -f2)
|
||||
world_build_log=$(grep -o 'buildworld-log . "[^"]*"' "$world_build_info" | head -n 1 | cut -d'"' -f2)
|
||||
kernel_build_log=$(grep -o 'buildkernel-log . "[^"]*"' "$kernel_build_info" | head -n 1 | cut -d'"' -f2)
|
||||
world_install_log=$(grep -o 'install-log . "[^"]*"' "$world_build_info" | head -n 1 | cut -d'"' -f2)
|
||||
kernel_install_log=$(grep -o 'install-log . "[^"]*"' "$kernel_build_info" | head -n 1 | cut -d'"' -f2)
|
||||
|
||||
[ -n "$world_source_tree_sha256" ] || { echo "missing world source-tree hash" >&2; exit 1; }
|
||||
[ -n "$kernel_source_tree_sha256" ] || { echo "missing kernel source-tree hash" >&2; exit 1; }
|
||||
[ "$world_source_tree_sha256" = "$kernel_source_tree_sha256" ] || {
|
||||
echo "native world/kernel source-tree hashes differ: $world_source_tree_sha256 != $kernel_source_tree_sha256" >&2
|
||||
exit 1
|
||||
}
|
||||
[ "$world_build_root" = "$kernel_build_root" ] || {
|
||||
echo "native world/kernel build roots differ: $world_build_root != $kernel_build_root" >&2
|
||||
exit 1
|
||||
}
|
||||
|
||||
for path in "$world_build_log" "$kernel_build_log" "$world_install_log" "$kernel_install_log"; do
|
||||
[ -f "$path" ] || {
|
||||
echo "expected native build log missing: $path" >&2
|
||||
exit 1
|
||||
}
|
||||
done
|
||||
|
||||
closure_base=$(basename "$closure_path")
|
||||
cat >"$metadata_file" <<EOF
|
||||
workdir=$workdir
|
||||
phase13_os_file=$phase13_os_file
|
||||
closure_path=$closure_path
|
||||
closure_rebuild_path=$closure_rebuild_path
|
||||
closure_base=$closure_base
|
||||
kernel_store=$kernel_store
|
||||
world_store=$world_store
|
||||
bootloader_store=$bootloader_store
|
||||
host_base_store_count=$host_base_store_count
|
||||
host_base_stores=$host_base_stores
|
||||
native_base_store_count=$native_base_store_count
|
||||
native_base_stores=$native_base_stores
|
||||
fruix_runtime_store_count=$fruix_runtime_store_count
|
||||
fruix_runtime_stores=$fruix_runtime_stores
|
||||
host_base_provenance_file=$host_base_provenance_file
|
||||
store_layout_file=$store_layout_file
|
||||
reference_count=$reference_count
|
||||
generated_file_count=$generated_file_count
|
||||
world_source_tree_sha256=$world_source_tree_sha256
|
||||
kernel_source_tree_sha256=$kernel_source_tree_sha256
|
||||
world_build_root=$world_build_root
|
||||
kernel_build_root=$kernel_build_root
|
||||
world_build_log=$world_build_log
|
||||
kernel_build_log=$kernel_build_log
|
||||
world_install_log=$world_install_log
|
||||
kernel_install_log=$kernel_install_log
|
||||
frontend_invocation=$fruix_cmd system build
|
||||
native_base_model=freebsd-world+freebsd-kernel-from-usr-src
|
||||
init_mode=shepherd-pid1
|
||||
EOF
|
||||
|
||||
if [ -n "$metadata_target" ]; then
|
||||
mkdir -p "$(dirname "$metadata_target")"
|
||||
cp "$metadata_file" "$metadata_target"
|
||||
fi
|
||||
|
||||
printf 'PASS phase13-native-base-build\n'
|
||||
printf 'Work directory: %s\n' "$workdir"
|
||||
printf 'Metadata file: %s\n' "$metadata_file"
|
||||
if [ -n "$metadata_target" ]; then
|
||||
printf 'Copied metadata to: %s\n' "$metadata_target"
|
||||
fi
|
||||
printf '%s\n' '--- metadata ---'
|
||||
cat "$metadata_file"
|
||||
105
tests/system/run-phase13-native-base-qemu.sh
Executable file
105
tests/system/run-phase13-native-base-qemu.sh
Executable file
@@ -0,0 +1,105 @@
|
||||
#!/bin/sh
|
||||
set -eu
|
||||
|
||||
repo_root=${PROJECT_ROOT:-$(pwd)}
|
||||
os_template=${OS_TEMPLATE:-$repo_root/tests/system/phase13-native-base-pid1-operating-system.scm.in}
|
||||
system_name=${SYSTEM_NAME:-phase13-operating-system}
|
||||
disk_capacity=${DISK_CAPACITY:-8g}
|
||||
root_size=${ROOT_SIZE:-6g}
|
||||
metadata_target=${METADATA_OUT:-}
|
||||
cleanup=0
|
||||
|
||||
if [ -n "${WORKDIR:-}" ]; then
|
||||
workdir=$WORKDIR
|
||||
mkdir -p "$workdir"
|
||||
else
|
||||
workdir=$(mktemp -d /tmp/fruix-phase13-native-qemu.XXXXXX)
|
||||
cleanup=1
|
||||
fi
|
||||
if [ "${KEEP_WORKDIR:-0}" -eq 1 ]; then
|
||||
cleanup=0
|
||||
fi
|
||||
|
||||
inner_metadata=$workdir/phase11-native-qemu-inner-metadata.txt
|
||||
metadata_file=$workdir/phase13-native-base-qemu-metadata.txt
|
||||
|
||||
cleanup_workdir() {
|
||||
if [ "$cleanup" -eq 1 ]; then
|
||||
rm -rf "$workdir" 2>/dev/null || sudo rm -rf "$workdir"
|
||||
fi
|
||||
}
|
||||
trap cleanup_workdir EXIT INT TERM
|
||||
|
||||
KEEP_WORKDIR=1 WORKDIR="$workdir/inner" METADATA_OUT="$inner_metadata" \
|
||||
OS_TEMPLATE="$os_template" SYSTEM_NAME="$system_name" DISK_CAPACITY="$disk_capacity" ROOT_SIZE="$root_size" \
|
||||
"$repo_root/tests/system/run-phase11-shepherd-pid1-qemu.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")
|
||||
serial_log=$(sed -n 's/^serial_log=//p' "$inner_metadata")
|
||||
ssh_port=$(sed -n 's/^ssh_port=//p' "$inner_metadata")
|
||||
shepherd_pid=$(sed -n 's/^shepherd_pid=//p' "$inner_metadata")
|
||||
sshd_status=$(sed -n 's/^sshd_status=//p' "$inner_metadata")
|
||||
activate_log=$(sed -n 's/^activate_log=//p' "$inner_metadata")
|
||||
|
||||
native_base_store_count=$(sed -n 's/^native_base_store_count=//p' "$phase8_metadata")
|
||||
native_base_stores=$(sed -n 's/^native_base_stores=//p' "$phase8_metadata")
|
||||
host_base_store_count=$(sed -n 's/^host_base_store_count=//p' "$phase8_metadata")
|
||||
host_base_stores=$(sed -n 's/^host_base_stores=//p' "$phase8_metadata")
|
||||
|
||||
[ "$native_base_store_count" = 2 ] || { echo "expected 2 native base stores, got: $native_base_store_count" >&2; exit 1; }
|
||||
[ "$host_base_store_count" = 1 ] || { echo "expected 1 host base store, got: $host_base_store_count" >&2; exit 1; }
|
||||
printf '%s\n' "$native_base_stores" | tr ',' '\n' | grep 'freebsd-native-kernel-15.0-STABLE$' >/dev/null || {
|
||||
echo "native base stores do not include the native kernel" >&2
|
||||
exit 1
|
||||
}
|
||||
printf '%s\n' "$native_base_stores" | tr ',' '\n' | grep 'freebsd-native-world-15.0-STABLE$' >/dev/null || {
|
||||
echo "native base stores do not include the native world" >&2
|
||||
exit 1
|
||||
}
|
||||
printf '%s\n' "$host_base_stores" | tr ',' '\n' | grep 'freebsd-bootloader-15.0-STABLE$' >/dev/null || {
|
||||
echo "host base stores do not reduce to the bootloader" >&2
|
||||
exit 1
|
||||
}
|
||||
[ "$shepherd_pid" = 1 ] || { echo "shepherd was not PID 1" >&2; exit 1; }
|
||||
[ "$sshd_status" = running ] || { echo "sshd is not running" >&2; exit 1; }
|
||||
case "$activate_log" in
|
||||
*fruix-activate:done*) : ;;
|
||||
*) echo "activation log does not show success" >&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
|
||||
serial_log=$serial_log
|
||||
ssh_port=$ssh_port
|
||||
disk_capacity=$disk_capacity
|
||||
root_size=$root_size
|
||||
native_base_store_count=$native_base_store_count
|
||||
native_base_stores=$native_base_stores
|
||||
host_base_store_count=$host_base_store_count
|
||||
host_base_stores=$host_base_stores
|
||||
shepherd_pid=$shepherd_pid
|
||||
sshd_status=$sshd_status
|
||||
boot_backend=qemu-uefi-tcg
|
||||
init_mode=shepherd-pid1
|
||||
native_base_boot=ok
|
||||
EOF
|
||||
|
||||
if [ -n "$metadata_target" ]; then
|
||||
mkdir -p "$(dirname "$metadata_target")"
|
||||
cp "$metadata_file" "$metadata_target"
|
||||
fi
|
||||
|
||||
printf 'PASS phase13-native-base-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"
|
||||
111
tests/system/run-phase13-native-base-xcpng.sh
Executable file
111
tests/system/run-phase13-native-base-xcpng.sh
Executable file
@@ -0,0 +1,111 @@
|
||||
#!/bin/sh
|
||||
set -eu
|
||||
|
||||
repo_root=${PROJECT_ROOT:-$(pwd)}
|
||||
os_template=${OS_TEMPLATE:-$repo_root/tests/system/phase13-native-base-pid1-operating-system.scm.in}
|
||||
system_name=${SYSTEM_NAME:-phase13-operating-system}
|
||||
root_size=${ROOT_SIZE:-6g}
|
||||
metadata_target=${METADATA_OUT:-}
|
||||
cleanup=0
|
||||
|
||||
if [ -n "${WORKDIR:-}" ]; then
|
||||
workdir=$WORKDIR
|
||||
mkdir -p "$workdir"
|
||||
else
|
||||
workdir=$(mktemp -d /tmp/fruix-phase13-native-xcpng.XXXXXX)
|
||||
cleanup=1
|
||||
fi
|
||||
if [ "${KEEP_WORKDIR:-0}" -eq 1 ]; then
|
||||
cleanup=0
|
||||
fi
|
||||
|
||||
inner_metadata=$workdir/phase11-native-xcpng-inner-metadata.txt
|
||||
metadata_file=$workdir/phase13-native-base-xcpng-metadata.txt
|
||||
|
||||
cleanup_workdir() {
|
||||
if [ "$cleanup" -eq 1 ]; then
|
||||
rm -rf "$workdir"
|
||||
fi
|
||||
}
|
||||
trap cleanup_workdir EXIT INT TERM
|
||||
|
||||
KEEP_WORKDIR=1 WORKDIR="$workdir/inner" METADATA_OUT="$inner_metadata" \
|
||||
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")
|
||||
|
||||
native_base_store_count=$(sed -n 's/^native_base_store_count=//p' "$phase8_metadata")
|
||||
native_base_stores=$(sed -n 's/^native_base_stores=//p' "$phase8_metadata")
|
||||
host_base_store_count=$(sed -n 's/^host_base_store_count=//p' "$phase8_metadata")
|
||||
host_base_stores=$(sed -n 's/^host_base_stores=//p' "$phase8_metadata")
|
||||
|
||||
[ "$native_base_store_count" = 2 ] || { echo "expected 2 native base stores, got: $native_base_store_count" >&2; exit 1; }
|
||||
[ "$host_base_store_count" = 1 ] || { echo "expected 1 host base store, got: $host_base_store_count" >&2; exit 1; }
|
||||
printf '%s\n' "$native_base_stores" | tr ',' '\n' | grep 'freebsd-native-kernel-15.0-STABLE$' >/dev/null || {
|
||||
echo "native base stores do not include the native kernel" >&2
|
||||
exit 1
|
||||
}
|
||||
printf '%s\n' "$native_base_stores" | tr ',' '\n' | grep 'freebsd-native-world-15.0-STABLE$' >/dev/null || {
|
||||
echo "native base stores do not include the native world" >&2
|
||||
exit 1
|
||||
}
|
||||
printf '%s\n' "$host_base_stores" | tr ',' '\n' | grep 'freebsd-bootloader-15.0-STABLE$' >/dev/null || {
|
||||
echo "host base stores do not reduce to the bootloader" >&2
|
||||
exit 1
|
||||
}
|
||||
[ "$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
|
||||
|
||||
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
|
||||
native_base_store_count=$native_base_store_count
|
||||
native_base_stores=$native_base_stores
|
||||
host_base_store_count=$host_base_store_count
|
||||
host_base_stores=$host_base_stores
|
||||
shepherd_pid=$shepherd_pid
|
||||
sshd_status=$sshd_status
|
||||
compat_prefix_shims=$compat_prefix_shims
|
||||
guile_module_smoke=$guile_module_smoke
|
||||
boot_backend=xcp-ng-xo-cli
|
||||
init_mode=shepherd-pid1
|
||||
native_base_boot=ok
|
||||
EOF
|
||||
|
||||
if [ -n "$metadata_target" ]; then
|
||||
mkdir -p "$(dirname "$metadata_target")"
|
||||
cp "$metadata_file" "$metadata_target"
|
||||
fi
|
||||
|
||||
printf 'PASS phase13-native-base-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"
|
||||
103
tests/system/run-phase14-native-boot-qemu.sh
Executable file
103
tests/system/run-phase14-native-boot-qemu.sh
Executable file
@@ -0,0 +1,103 @@
|
||||
#!/bin/sh
|
||||
set -eu
|
||||
|
||||
repo_root=${PROJECT_ROOT:-$(pwd)}
|
||||
os_template=${OS_TEMPLATE:-$repo_root/tests/system/phase14-native-boot-pid1-operating-system.scm.in}
|
||||
system_name=${SYSTEM_NAME:-phase14-operating-system}
|
||||
disk_capacity=${DISK_CAPACITY:-8g}
|
||||
root_size=${ROOT_SIZE:-6g}
|
||||
metadata_target=${METADATA_OUT:-}
|
||||
cleanup=0
|
||||
|
||||
if [ -n "${WORKDIR:-}" ]; then
|
||||
workdir=$WORKDIR
|
||||
mkdir -p "$workdir"
|
||||
else
|
||||
workdir=$(mktemp -d /tmp/fruix-phase14-native-boot-qemu.XXXXXX)
|
||||
cleanup=1
|
||||
fi
|
||||
if [ "${KEEP_WORKDIR:-0}" -eq 1 ]; then
|
||||
cleanup=0
|
||||
fi
|
||||
|
||||
inner_metadata=$workdir/phase14-native-boot-qemu-inner-metadata.txt
|
||||
metadata_file=$workdir/phase14-native-boot-qemu-metadata.txt
|
||||
|
||||
cleanup_workdir() {
|
||||
if [ "$cleanup" -eq 1 ]; then
|
||||
rm -rf "$workdir" 2>/dev/null || sudo rm -rf "$workdir"
|
||||
fi
|
||||
}
|
||||
trap cleanup_workdir EXIT INT TERM
|
||||
|
||||
KEEP_WORKDIR=1 WORKDIR="$workdir/inner" METADATA_OUT="$inner_metadata" \
|
||||
OS_TEMPLATE="$os_template" SYSTEM_NAME="$system_name" DISK_CAPACITY="$disk_capacity" ROOT_SIZE="$root_size" \
|
||||
"$repo_root/tests/system/run-phase11-shepherd-pid1-qemu.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")
|
||||
serial_log=$(sed -n 's/^serial_log=//p' "$inner_metadata")
|
||||
ssh_port=$(sed -n 's/^ssh_port=//p' "$inner_metadata")
|
||||
shepherd_pid=$(sed -n 's/^shepherd_pid=//p' "$inner_metadata")
|
||||
sshd_status=$(sed -n 's/^sshd_status=//p' "$inner_metadata")
|
||||
activate_log=$(sed -n 's/^activate_log=//p' "$inner_metadata")
|
||||
|
||||
native_base_store_count=$(sed -n 's/^native_base_store_count=//p' "$phase8_metadata")
|
||||
native_base_stores=$(sed -n 's/^native_base_stores=//p' "$phase8_metadata")
|
||||
host_base_store_count=$(sed -n 's/^host_base_store_count=//p' "$phase8_metadata")
|
||||
host_base_stores=$(sed -n 's/^host_base_stores=//p' "$phase8_metadata")
|
||||
|
||||
[ "$native_base_store_count" = 2 ] || { echo "expected 2 native base stores, got: $native_base_store_count" >&2; exit 1; }
|
||||
[ "$host_base_store_count" = 0 ] || { echo "expected 0 host base stores, got: $host_base_store_count" >&2; exit 1; }
|
||||
[ -z "$host_base_stores" ] || { echo "host base stores are not empty: $host_base_stores" >&2; exit 1; }
|
||||
printf '%s\n' "$native_base_stores" | tr ',' '\n' | grep 'freebsd-native-kernel-15.0-STABLE$' >/dev/null || {
|
||||
echo "native base stores do not include the native kernel" >&2
|
||||
exit 1
|
||||
}
|
||||
printf '%s\n' "$native_base_stores" | tr ',' '\n' | grep 'freebsd-native-world-15.0-STABLE$' >/dev/null || {
|
||||
echo "native base stores do not include the native world" >&2
|
||||
exit 1
|
||||
}
|
||||
[ "$shepherd_pid" = 1 ] || { echo "shepherd was not PID 1" >&2; exit 1; }
|
||||
[ "$sshd_status" = running ] || { echo "sshd is not running" >&2; exit 1; }
|
||||
case "$activate_log" in
|
||||
*fruix-activate:done*) : ;;
|
||||
*) echo "activation log does not show success" >&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
|
||||
serial_log=$serial_log
|
||||
ssh_port=$ssh_port
|
||||
disk_capacity=$disk_capacity
|
||||
root_size=$root_size
|
||||
native_base_store_count=$native_base_store_count
|
||||
native_base_stores=$native_base_stores
|
||||
host_base_store_count=$host_base_store_count
|
||||
host_base_stores=$host_base_stores
|
||||
shepherd_pid=$shepherd_pid
|
||||
sshd_status=$sshd_status
|
||||
boot_backend=qemu-uefi-tcg
|
||||
init_mode=shepherd-pid1
|
||||
native_boot_assets=freebsd-native-world
|
||||
native_base_boot=ok
|
||||
EOF
|
||||
|
||||
if [ -n "$metadata_target" ]; then
|
||||
mkdir -p "$(dirname "$metadata_target")"
|
||||
cp "$metadata_file" "$metadata_target"
|
||||
fi
|
||||
|
||||
printf 'PASS phase14-native-boot-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"
|
||||
109
tests/system/run-phase14-native-boot-xcpng.sh
Executable file
109
tests/system/run-phase14-native-boot-xcpng.sh
Executable file
@@ -0,0 +1,109 @@
|
||||
#!/bin/sh
|
||||
set -eu
|
||||
|
||||
repo_root=${PROJECT_ROOT:-$(pwd)}
|
||||
os_template=${OS_TEMPLATE:-$repo_root/tests/system/phase14-native-boot-pid1-operating-system.scm.in}
|
||||
system_name=${SYSTEM_NAME:-phase14-operating-system}
|
||||
root_size=${ROOT_SIZE:-6g}
|
||||
metadata_target=${METADATA_OUT:-}
|
||||
cleanup=0
|
||||
|
||||
if [ -n "${WORKDIR:-}" ]; then
|
||||
workdir=$WORKDIR
|
||||
mkdir -p "$workdir"
|
||||
else
|
||||
workdir=$(mktemp -d /tmp/fruix-phase14-native-boot-xcpng.XXXXXX)
|
||||
cleanup=1
|
||||
fi
|
||||
if [ "${KEEP_WORKDIR:-0}" -eq 1 ]; then
|
||||
cleanup=0
|
||||
fi
|
||||
|
||||
inner_metadata=$workdir/phase14-native-boot-xcpng-inner-metadata.txt
|
||||
metadata_file=$workdir/phase14-native-boot-xcpng-metadata.txt
|
||||
|
||||
cleanup_workdir() {
|
||||
if [ "$cleanup" -eq 1 ]; then
|
||||
rm -rf "$workdir"
|
||||
fi
|
||||
}
|
||||
trap cleanup_workdir EXIT INT TERM
|
||||
|
||||
KEEP_WORKDIR=1 WORKDIR="$workdir/inner" METADATA_OUT="$inner_metadata" \
|
||||
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")
|
||||
|
||||
native_base_store_count=$(sed -n 's/^native_base_store_count=//p' "$phase8_metadata")
|
||||
native_base_stores=$(sed -n 's/^native_base_stores=//p' "$phase8_metadata")
|
||||
host_base_store_count=$(sed -n 's/^host_base_store_count=//p' "$phase8_metadata")
|
||||
host_base_stores=$(sed -n 's/^host_base_stores=//p' "$phase8_metadata")
|
||||
|
||||
[ "$native_base_store_count" = 2 ] || { echo "expected 2 native base stores, got: $native_base_store_count" >&2; exit 1; }
|
||||
[ "$host_base_store_count" = 0 ] || { echo "expected 0 host base stores, got: $host_base_store_count" >&2; exit 1; }
|
||||
[ -z "$host_base_stores" ] || { echo "host base stores are not empty: $host_base_stores" >&2; exit 1; }
|
||||
printf '%s\n' "$native_base_stores" | tr ',' '\n' | grep 'freebsd-native-kernel-15.0-STABLE$' >/dev/null || {
|
||||
echo "native base stores do not include the native kernel" >&2
|
||||
exit 1
|
||||
}
|
||||
printf '%s\n' "$native_base_stores" | tr ',' '\n' | grep 'freebsd-native-world-15.0-STABLE$' >/dev/null || {
|
||||
echo "native base stores do not include the native world" >&2
|
||||
exit 1
|
||||
}
|
||||
[ "$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
|
||||
|
||||
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
|
||||
native_base_store_count=$native_base_store_count
|
||||
native_base_stores=$native_base_stores
|
||||
host_base_store_count=$host_base_store_count
|
||||
host_base_stores=$host_base_stores
|
||||
shepherd_pid=$shepherd_pid
|
||||
sshd_status=$sshd_status
|
||||
compat_prefix_shims=$compat_prefix_shims
|
||||
guile_module_smoke=$guile_module_smoke
|
||||
boot_backend=xcp-ng-xo-cli
|
||||
init_mode=shepherd-pid1
|
||||
native_boot_assets=freebsd-native-world
|
||||
native_base_boot=ok
|
||||
EOF
|
||||
|
||||
if [ -n "$metadata_target" ]; then
|
||||
mkdir -p "$(dirname "$metadata_target")"
|
||||
cp "$metadata_file" "$metadata_target"
|
||||
fi
|
||||
|
||||
printf 'PASS phase14-native-boot-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"
|
||||
142
tests/system/run-phase14-native-development-split.sh
Executable file
142
tests/system/run-phase14-native-development-split.sh
Executable file
@@ -0,0 +1,142 @@
|
||||
#!/bin/sh
|
||||
set -eu
|
||||
|
||||
repo_root=${PROJECT_ROOT:-$(pwd)}
|
||||
store_dir=${STORE_DIR:-/frx/store}
|
||||
metadata_target=${METADATA_OUT:-}
|
||||
cleanup=0
|
||||
|
||||
if [ -n "${WORKDIR:-}" ]; then
|
||||
workdir=$WORKDIR
|
||||
mkdir -p "$workdir"
|
||||
else
|
||||
workdir=$(mktemp -d /tmp/fruix-phase14-native-development.XXXXXX)
|
||||
cleanup=1
|
||||
fi
|
||||
if [ "${KEEP_WORKDIR:-0}" -eq 1 ]; then
|
||||
cleanup=0
|
||||
fi
|
||||
|
||||
metadata_file=$workdir/phase14-native-development-split-metadata.txt
|
||||
|
||||
cleanup_workdir() {
|
||||
if [ "$cleanup" -eq 1 ]; then
|
||||
rm -rf "$workdir" 2>/dev/null || sudo rm -rf "$workdir"
|
||||
fi
|
||||
}
|
||||
trap cleanup_workdir EXIT INT TERM
|
||||
|
||||
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}" \
|
||||
"$@"
|
||||
}
|
||||
|
||||
native_system_packages=$(GUILE_AUTO_COMPILE=0 /tmp/guile-freebsd-validate-install/bin/guile -L modules -L "$HOME/repos/guix" -c '
|
||||
(use-modules (fruix packages freebsd) (srfi srfi-13))
|
||||
(display (string-join (map freebsd-package-name %freebsd-native-system-packages) ","))
|
||||
(newline)')
|
||||
|
||||
native_development_packages=$(GUILE_AUTO_COMPILE=0 /tmp/guile-freebsd-validate-install/bin/guile -L modules -L "$HOME/repos/guix" -c '
|
||||
(use-modules (fruix packages freebsd) (srfi srfi-13))
|
||||
(display (string-join (map freebsd-package-name %freebsd-native-development-profile-packages) ","))
|
||||
(newline)')
|
||||
|
||||
materialize_native_package() {
|
||||
package_name=$1
|
||||
action_env env LD_LIBRARY_PATH="/tmp/guile-freebsd-validate-install/lib:/usr/local/lib" /tmp/guile-freebsd-validate-install/bin/guile -L modules -L "$HOME/repos/guix" -c '
|
||||
(use-modules (fruix packages freebsd) (ice-9 hash-table))
|
||||
(let* ((args (command-line))
|
||||
(store-dir (list-ref args (- (length args) 2)))
|
||||
(package-name (list-ref args (- (length args) 1)))
|
||||
(pkg (case (string->symbol package-name)
|
||||
((freebsd-native-bootloader) freebsd-native-bootloader)
|
||||
((freebsd-native-headers) freebsd-native-headers)
|
||||
(else (error "unsupported package" package-name))))
|
||||
(system-module (resolve-module (quote (fruix system freebsd))))
|
||||
(materialize (module-ref system-module (quote materialize-freebsd-package)))
|
||||
(cache (make-hash-table))
|
||||
(path (materialize pkg store-dir cache)))
|
||||
(display "STORE_PATH=")
|
||||
(display path)
|
||||
(newline))' dummy "$store_dir" "$package_name"
|
||||
}
|
||||
|
||||
bootloader_store=$(materialize_native_package freebsd-native-bootloader | sed -n 's/^STORE_PATH=//p' | tail -n 1)
|
||||
headers_store=$(materialize_native_package freebsd-native-headers | sed -n 's/^STORE_PATH=//p' | tail -n 1)
|
||||
|
||||
case "$bootloader_store" in
|
||||
/frx/store/*-freebsd-native-bootloader-15.0-STABLE) : ;;
|
||||
*) echo "unexpected native bootloader store path: $bootloader_store" >&2; exit 1 ;;
|
||||
esac
|
||||
case "$headers_store" in
|
||||
/frx/store/*-freebsd-native-headers-15.0-STABLE) : ;;
|
||||
*) echo "unexpected native headers store path: $headers_store" >&2; exit 1 ;;
|
||||
esac
|
||||
|
||||
for path in \
|
||||
"$bootloader_store/boot/loader" \
|
||||
"$bootloader_store/boot/loader.efi" \
|
||||
"$bootloader_store/boot/device.hints" \
|
||||
"$bootloader_store/boot/defaults/loader.conf" \
|
||||
"$bootloader_store/boot/lua/loader.lua" \
|
||||
"$headers_store/usr/include/stdio.h" \
|
||||
"$headers_store/usr/include/sys/param.h" \
|
||||
"$headers_store/usr/share/mk/bsd.prog.mk"
|
||||
do
|
||||
[ -e "$path" ] || {
|
||||
echo "expected native split artifact path missing: $path" >&2
|
||||
exit 1
|
||||
}
|
||||
done
|
||||
|
||||
[ ! -e "$bootloader_store/usr/include" ] || { echo "native bootloader slice unexpectedly contains headers" >&2; exit 1; }
|
||||
[ ! -e "$headers_store/boot" ] || { echo "native headers slice unexpectedly contains /boot" >&2; exit 1; }
|
||||
[ ! -e "$headers_store/bin/sh" ] || { echo "native headers slice unexpectedly contains runtime binaries" >&2; exit 1; }
|
||||
|
||||
case ",$native_system_packages," in
|
||||
*,freebsd-native-runtime,*) : ;;
|
||||
*) echo "native system package set does not include freebsd-native-runtime" >&2; exit 1 ;;
|
||||
esac
|
||||
case ",$native_development_packages," in
|
||||
*,freebsd-native-runtime,*) : ;;
|
||||
*) echo "native development package set does not include freebsd-native-runtime" >&2; exit 1 ;;
|
||||
esac
|
||||
case ",$native_development_packages," in
|
||||
*,freebsd-native-headers,*) : ;;
|
||||
*) echo "native development package set does not include freebsd-native-headers" >&2; exit 1 ;;
|
||||
esac
|
||||
case ",$native_development_packages," in
|
||||
*,freebsd-clang-toolchain,*) : ;;
|
||||
*) echo "native development package set does not retain the explicit toolchain artifact" >&2; exit 1 ;;
|
||||
esac
|
||||
|
||||
cat >"$metadata_file" <<EOF
|
||||
workdir=$workdir
|
||||
store_dir=$store_dir
|
||||
bootloader_store=$bootloader_store
|
||||
headers_store=$headers_store
|
||||
native_system_packages=$native_system_packages
|
||||
native_development_packages=$native_development_packages
|
||||
runtime_vs_development_split=ok
|
||||
EOF
|
||||
|
||||
if [ -n "$metadata_target" ]; then
|
||||
mkdir -p "$(dirname "$metadata_target")"
|
||||
cp "$metadata_file" "$metadata_target"
|
||||
fi
|
||||
|
||||
printf 'PASS phase14-native-development-split\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"
|
||||
124
tests/system/run-phase14-native-runtime-qemu.sh
Executable file
124
tests/system/run-phase14-native-runtime-qemu.sh
Executable file
@@ -0,0 +1,124 @@
|
||||
#!/bin/sh
|
||||
set -eu
|
||||
|
||||
repo_root=${PROJECT_ROOT:-$(pwd)}
|
||||
os_template=${OS_TEMPLATE:-$repo_root/tests/system/phase14-native-runtime-pid1-operating-system.scm.in}
|
||||
system_name=${SYSTEM_NAME:-phase14-operating-system}
|
||||
disk_capacity=${DISK_CAPACITY:-12g}
|
||||
root_size=${ROOT_SIZE:-10g}
|
||||
metadata_target=${METADATA_OUT:-}
|
||||
cleanup=0
|
||||
|
||||
if [ -n "${WORKDIR:-}" ]; then
|
||||
workdir=$WORKDIR
|
||||
mkdir -p "$workdir"
|
||||
else
|
||||
workdir=$(mktemp -d /tmp/fruix-phase14-native-runtime-qemu.XXXXXX)
|
||||
cleanup=1
|
||||
fi
|
||||
if [ "${KEEP_WORKDIR:-0}" -eq 1 ]; then
|
||||
cleanup=0
|
||||
fi
|
||||
|
||||
inner_metadata=$workdir/phase14-native-runtime-qemu-inner-metadata.txt
|
||||
metadata_file=$workdir/phase14-native-runtime-qemu-metadata.txt
|
||||
|
||||
cleanup_workdir() {
|
||||
if [ "$cleanup" -eq 1 ]; then
|
||||
rm -rf "$workdir" 2>/dev/null || sudo rm -rf "$workdir"
|
||||
fi
|
||||
}
|
||||
trap cleanup_workdir EXIT INT TERM
|
||||
|
||||
KEEP_WORKDIR=1 WORKDIR="$workdir/inner" METADATA_OUT="$inner_metadata" \
|
||||
OS_TEMPLATE="$os_template" SYSTEM_NAME="$system_name" DISK_CAPACITY="$disk_capacity" ROOT_SIZE="$root_size" \
|
||||
"$repo_root/tests/system/run-phase11-shepherd-pid1-qemu.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")
|
||||
serial_log=$(sed -n 's/^serial_log=//p' "$inner_metadata")
|
||||
ssh_port=$(sed -n 's/^ssh_port=//p' "$inner_metadata")
|
||||
shepherd_pid=$(sed -n 's/^shepherd_pid=//p' "$inner_metadata")
|
||||
sshd_status=$(sed -n 's/^sshd_status=//p' "$inner_metadata")
|
||||
activate_log=$(sed -n 's/^activate_log=//p' "$inner_metadata")
|
||||
|
||||
native_base_store_count=$(sed -n 's/^native_base_store_count=//p' "$phase8_metadata")
|
||||
native_base_stores=$(sed -n 's/^native_base_stores=//p' "$phase8_metadata")
|
||||
host_base_store_count=$(sed -n 's/^host_base_store_count=//p' "$phase8_metadata")
|
||||
host_base_stores=$(sed -n 's/^host_base_stores=//p' "$phase8_metadata")
|
||||
|
||||
[ "$native_base_store_count" = 3 ] || { echo "expected 3 native base stores, got: $native_base_store_count" >&2; exit 1; }
|
||||
[ "$host_base_store_count" = 0 ] || { echo "expected 0 host base stores, got: $host_base_store_count" >&2; exit 1; }
|
||||
[ -z "$host_base_stores" ] || { echo "host base stores are not empty: $host_base_stores" >&2; exit 1; }
|
||||
printf '%s\n' "$native_base_stores" | tr ',' '\n' | grep 'freebsd-native-kernel-15.0-STABLE$' >/dev/null || {
|
||||
echo "native base stores do not include the native kernel" >&2
|
||||
exit 1
|
||||
}
|
||||
printf '%s\n' "$native_base_stores" | tr ',' '\n' | grep 'freebsd-native-world-15.0-STABLE$' >/dev/null || {
|
||||
echo "native base stores do not include the native boot/world source artifact" >&2
|
||||
exit 1
|
||||
}
|
||||
runtime_store=$(printf '%s\n' "$native_base_stores" | tr ',' '\n' | grep 'freebsd-native-runtime-15.0-STABLE$' | head -n 1)
|
||||
[ -n "$runtime_store" ] || {
|
||||
echo "native base stores do not include the explicit native runtime slice" >&2
|
||||
exit 1
|
||||
}
|
||||
for path in \
|
||||
"$runtime_store/bin/sh" \
|
||||
"$runtime_store/sbin/init" \
|
||||
"$runtime_store/etc/rc" \
|
||||
"$runtime_store/usr/sbin/sshd" \
|
||||
"$runtime_store/sbin/dhclient" \
|
||||
"$runtime_store/usr/bin/ssh-keygen" \
|
||||
"$runtime_store/usr/share/locale/C.UTF-8/LC_CTYPE"
|
||||
do
|
||||
[ -e "$path" ] || {
|
||||
echo "required native runtime path missing: $path" >&2
|
||||
exit 1
|
||||
}
|
||||
done
|
||||
[ ! -e "$runtime_store/boot" ] || { echo "native runtime still contains /boot" >&2; exit 1; }
|
||||
[ ! -e "$runtime_store/usr/include" ] || { echo "native runtime still contains /usr/include" >&2; exit 1; }
|
||||
[ "$shepherd_pid" = 1 ] || { echo "shepherd was not PID 1" >&2; exit 1; }
|
||||
[ "$sshd_status" = running ] || { echo "sshd is not running" >&2; exit 1; }
|
||||
case "$activate_log" in
|
||||
*fruix-activate:done*) : ;;
|
||||
*) echo "activation log does not show success" >&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
|
||||
serial_log=$serial_log
|
||||
ssh_port=$ssh_port
|
||||
disk_capacity=$disk_capacity
|
||||
root_size=$root_size
|
||||
runtime_store=$runtime_store
|
||||
native_base_store_count=$native_base_store_count
|
||||
native_base_stores=$native_base_stores
|
||||
host_base_store_count=$host_base_store_count
|
||||
host_base_stores=$host_base_stores
|
||||
shepherd_pid=$shepherd_pid
|
||||
sshd_status=$sshd_status
|
||||
boot_backend=qemu-uefi-tcg
|
||||
init_mode=shepherd-pid1
|
||||
native_runtime_ready=ok
|
||||
EOF
|
||||
|
||||
if [ -n "$metadata_target" ]; then
|
||||
mkdir -p "$(dirname "$metadata_target")"
|
||||
cp "$metadata_file" "$metadata_target"
|
||||
fi
|
||||
|
||||
printf 'PASS phase14-native-runtime-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"
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user