system: record explicit generation layout

This commit is contained in:
2026-04-04 19:41:49 +02:00
parent e86f74af97
commit b3b1ba2489
7 changed files with 740 additions and 42 deletions

1
.gitignore vendored Normal file
View File

@@ -0,0 +1 @@
/TODO.md

225
docs/GUIX_DIFFERENCES.md Normal file
View File

@@ -0,0 +1,225 @@
# Fruix differences for Guix sysadmins
Date: 2026-04-04
This document is aimed at operators who already know Guix well and want a quick map of where Fruix behaves similarly and where it intentionally differs.
The short version is:
- Fruix is strongly **Guix-inspired**
- it tries to preserve the important semantic properties
- but it does **not** copy Guix mechanically where FreeBSD or Fruix-specific concerns make a different representation clearer
## Big picture
Fruix keeps the core Guix ideas you would expect:
- declarative inputs
- content-addressed store paths
- immutable build outputs
- rollback-friendly deployment identity
- provenance tied to the deployed closure rather than mutable in-place state
But Fruix differs in at least four major ways today:
1. it targets **FreeBSD**, not GNU/Linux
2. its system frontend is currently smaller:
- `fruix system build|rootfs|image|install|installer|installer-iso`
3. it treats **FreeBSD source provenance** as an explicit deployment concern
4. its installed-system generation model is still earlier-stage than Guix's mature system-generation workflow
## Similarities to Guix
If you know Guix System, these Fruix properties should feel familiar.
### Immutable deployment identity
A deployed Fruix system is identified primarily by its closure path in `/frx/store`, not by mutable files under `/etc` or `/usr/local`.
### `/run/current-system`
Fruix keeps the Guix-like runtime convention:
- `/run/current-system`
That path remains the active runtime boundary used by activation and service wiring.
### Rollback-friendly semantics
Fruix avoids in-place mutation of an older deployed closure.
The validated rollback story today is:
- keep the earlier declaration
- rebuild or rematerialize it
- boot or redeploy that earlier closure again
That is Guix-like in spirit even though Fruix does not yet expose the same installed-system rollback command surface.
### Generation-style metadata and roots
Fruix now records explicit installed-system generation state under:
- `/var/lib/fruix/system`
and explicit retention roots under:
- `/frx/var/fruix/gcroots`
This preserves the basic Guix idea that deployment state and reachability should be represented explicitly rather than inferred from whatever happens to be on disk.
## Important differences from Guix
## 1. Fruix does not mirror Guix's on-disk generation layout 1:1
This is intentional.
Guix heavily reuses its profile-generation model and represents a lot of meaning through symlink structure such as profile links and system generation links.
Fruix keeps the **semantics** but uses a more explicit metadata-oriented layout for installed systems.
Current Fruix layout:
```text
/var/lib/fruix/system/
current -> generations/1
current-generation
generations/
1/
closure -> /frx/store/...-fruix-system-...
metadata.scm
provenance.scm
install.scm
```
Why Fruix does this:
- it makes deployment state easier to inspect directly
- it gives FreeBSD-specific install and provenance details a clearer home
- it keeps room for richer operator tooling later without losing the Guix properties
So the rule of thumb is:
- **same semantics as Guix where practical**
- **not necessarily the same directory names or symlink vocabulary**
## 2. Fruix currently keeps `/run/current-system` simple
Even though Fruix now records explicit generation metadata under `/var/lib/fruix/system`, it still keeps:
- `/run/current-system -> /frx/store/...`
rather than making `/run/current-system` point through a generation directory first.
This was chosen to preserve compatibility with the already-validated activation and runtime model while adding explicit generation metadata separately.
## 3. Fruix treats source provenance more explicitly
Guix sysadmins are used to derivation/store provenance. Fruix adds an extra emphasis because its current FreeBSD story depends on explicit source selection and materialization.
Fruix routinely records:
- declared FreeBSD source object metadata
- materialized source store paths
- source materialization metadata files
- closure-level store layout metadata
- install metadata on the target system
This is more prominent in Fruix than most Guix system docs because FreeBSD base/source identity has been an active design concern for this project.
## 4. Fruix has installer-media workflows as first-class system actions
Guix has installation media and image workflows, but Fruix's current system frontend makes these especially explicit because they are still part of the active architectural bring-up story.
Current Fruix actions include:
- `fruix system install`
- `fruix system installer`
- `fruix system installer-iso`
That is a little more deployment-medium-oriented than the mental model many Guix users start with.
## 5. Device naming is more environment-sensitive than Guix users may expect
Because Fruix is on FreeBSD, the install target device naming is not the same as on Linux.
Validated examples:
- installer disk-image path under QEMU:
- `/dev/vtbd1`
- installer ISO path under QEMU:
- `/dev/vtbd0`
- installer ISO path under XCP-ng:
- `/dev/ada0`
So compared with Guix-on-Linux intuition, Fruix operators should be more explicit about target-device selection during installation and installer-media validation.
## 6. Fruix does not yet have Guix-equivalent installed-system generation commands
This is the biggest current operational gap.
Fruix does **not** yet provide a mature equivalent of the familiar Guix System operator flow around in-place generation switching and rollback commands.
Today, Fruix rollback is mostly:
- declaration-driven
- rebuild/redeploy based
rather than:
- switch current system generation in place through a dedicated command
So if you come from Guix, assume that Fruix currently has:
- strong closure/store semantics
- explicit install artifacts
- explicit generation metadata roots
- but a less mature installed-system generation UX
## Where Fruix is intentionally trying to improve on Guix's representation
Fruix is not trying to improve on Guix's core semantics. Guix already got those right.
Where Fruix is intentionally experimenting is mostly the **representation layer**:
- make generation state more legible to operators
- make provenance more visible without needing to reconstruct it mentally from symlink layout alone
- separate:
- runtime entry point (`/run/current-system`)
- installed deployment state (`/var/lib/fruix/system`)
- retention roots (`/frx/var/fruix/gcroots`)
That is why Fruix currently prefers a small per-generation metadata directory instead of only a bare generation link.
## Practical operator advice for Guix users
If you are already comfortable with Guix, the safest Fruix mental model today is:
1. think in terms of immutable closures and declarations first
2. use `fruix system build` as the canonical starting point
3. treat image/install/installer/installer-iso as deployment materializers built from the same declaration
4. identify a deployment by:
- closure path
- source provenance metadata
- install metadata
5. think of rollback today as:
- “redeploy the earlier declaration again”
rather than:
- “switch to an already-managed previous generation in place”
## Status summary
Today Fruix is closest to Guix in:
- store and closure semantics
- declarative deployment identity
- rollback-friendly immutability
- `/run/current-system` runtime model
It differs most from Guix in:
- FreeBSD platform details
- source-provenance emphasis
- installer-medium-oriented workflows
- generation-layout representation
- and the still-maturing installed-system generation command surface

View File

@@ -26,6 +26,10 @@ Fruix currently has:
- `fruix system installer` - `fruix system installer`
- a bootable Fruix-managed installer ISO: - a bootable Fruix-managed installer ISO:
- `fruix system installer-iso` - `fruix system installer-iso`
- an explicit installed-system generation layout under:
- `/var/lib/fruix/system`
- explicit installed-system retention roots under:
- `/frx/var/fruix/gcroots`
Validated boot modes still are: Validated boot modes still are:
@@ -38,42 +42,38 @@ The validated Phase 18 installation work currently uses:
## Latest completed achievement ## Latest completed achievement
### 2026-04-04 — Phase 19.1 completed ### 2026-04-04 — Phase 19.2 completed
Fruix now has a documented canonical deployment workflow for build, image generation, installation, roll-forward, and rollback. Fruix now records an explicit installed-system generation layout and retention-root model instead of relying mainly on harness knowledge.
Highlights: Highlights:
- added canonical operator workflow documentation: - added explicit installed-system generation layout under:
- `docs/system-deployment-workflow.md` - `/var/lib/fruix/system`
- added Phase 19.1 report: - added explicit installed-system retention roots under:
- `docs/reports/phase19-deployment-workflow-freebsd.md` - `/frx/var/fruix/gcroots`
- the documented command surface now explicitly centers on: - installed targets now record a first-generation deployment directory containing:
- `fruix system build` - `closure`
- `fruix system rootfs` - `metadata.scm`
- `fruix system image` - `provenance.scm`
- `fruix system install` - `install.scm`
- `fruix system installer` - `/run/current-system` remains the runtime boundary and still points directly at the active closure path
- `fruix system installer-iso` - added Guix-oriented operator notes in:
- the deployment story now explicitly records: - `docs/GUIX_DIFFERENCES.md`
- declaration-driven roll-forward - updated deployment workflow documentation to reflect the new explicit generation model
- rollback by rebuilding/redeploying an earlier declaration
- current deployment identity via closure path and emitted provenance metadata
- current environment-specific installer target-device conventions
Validation basis referenced by the workflow documentation: Validation:
- `PASS phase15-base-rollback-qemu` - `PASS phase19-generation-layout-qemu`
- `PASS phase15-base-rollback-xcpng` - regression re-check:
- `PASS phase18-system-install` - `PASS phase18-installer-iso`
- `PASS phase18-installer-environment`
- `PASS phase18-installer-iso`
- `PASS phase18-installer-iso-xcpng`
Reports: Reports:
- `docs/system-deployment-workflow.md` - `docs/system-deployment-workflow.md`
- `docs/GUIX_DIFFERENCES.md`
- `docs/reports/phase19-deployment-workflow-freebsd.md` - `docs/reports/phase19-deployment-workflow-freebsd.md`
- `docs/reports/phase19-generation-layout-freebsd.md`
## Recent major milestones ## Recent major milestones
@@ -99,6 +99,6 @@ Reports:
Per `docs/PLAN_4.md`, the next planned step is: Per `docs/PLAN_4.md`, the next planned step is:
- **Phase 19.2** — make the installed-system generation and rollback-root model more explicit - **Phase 19.3** — validate installed-system rollback through the intended operator-facing workflow
Phase 19.1 is now complete: Fruix documents a coherent operator-facing deployment workflow for build, installation, roll-forward, and rollback. Phase 19.2 is now complete: Fruix has an explicit installed-system generation layout and retention-root model on FreeBSD.

View 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

View File

@@ -255,6 +255,43 @@ Therefore the canonical workflow is:
Do not assume that a device name validated in one harness is portable to another. Do not assume that a device name validated in one harness is portable to another.
## Installed-system generation layout
Installed Fruix systems now record an explicit first-generation deployment layout under:
- `/var/lib/fruix/system`
Current validated shape:
```text
/var/lib/fruix/system/
current -> generations/1
current-generation
generations/
1/
closure -> /frx/store/...-fruix-system-...
metadata.scm
provenance.scm
install.scm # present on installed targets
```
Installed systems also now create explicit GC-root-style deployment links under:
- `/frx/var/fruix/gcroots`
Current validated shape:
```text
/frx/var/fruix/gcroots/
current-system -> /frx/store/...-fruix-system-...
system-1 -> /frx/store/...-fruix-system-...
```
Important detail:
- `/run/current-system` still points directly at the active closure path in `/frx/store`
- the explicit generation layout therefore adds deployment metadata and retention roots without changing the already-validated runtime contract used by activation, rc.d wiring, and tests
## Roll-forward workflow ## Roll-forward workflow
The current Fruix roll-forward model is declaration-driven. The current Fruix roll-forward model is declaration-driven.
@@ -310,13 +347,14 @@ Current rollback is:
- **redeploy the earlier declaration again** - **redeploy the earlier declaration again**
Future Phase 19 work is expected to make these more explicit: What still remains for later Phase 19 work is making rollback itself operator-driven at the installed-system layer, rather than only declaration/redeploy driven.
- current generation Still pending:
- previous generation
- rollback target - previous-generation tracking beyond the initial explicit generation-1 layout
- installed-system roots and generation links - an explicit rollback target link distinct from `current`
- an operator-facing installed-system rollback workflow - an operator-facing installed-system rollback workflow
- generation switching without full redeploy
## Provenance and deployment identity ## Provenance and deployment identity
@@ -341,12 +379,12 @@ The deployment workflow is now coherent, but it is not yet the final generation-
Not yet first-class: Not yet first-class:
- installed-system generation directories and symlink model
- a dedicated `switch` or `reconfigure` command - a dedicated `switch` or `reconfigure` command
- an installed-system rollback command that moves between generations in place - an installed-system rollback command that moves between generations in place
- explicit GC-root management for installed systems - multi-generation retention and previous-generation tracking beyond generation 1
- generation switching policy independent of full redeploy
Those are the next logical steps after Phase 19.1. Those are the next logical steps after the current explicit-generation layout.
## Summary ## Summary
@@ -359,4 +397,4 @@ The current canonical Fruix deployment model is:
- **identify deployments by closure path and provenance metadata** - **identify deployments by closure path and provenance metadata**
- **roll back by rebuilding/redeploying the earlier declaration**, not by mutating the current closure in place - **roll back by rebuilding/redeploying the earlier declaration**, not by mutating the current closure in place
That is the operator-facing workflow Fruix should document and use until explicit installed-system generation management is added. That is the operator-facing workflow Fruix should document and use while installed-system generation switching remains more limited than Guix's mature in-place system-generation workflow.

View File

@@ -233,7 +233,76 @@
(mkdir-p (dirname link-name)) (mkdir-p (dirname link-name))
(symlink target link-name)) (symlink target link-name))
(define (populate-rootfs-from-closure os rootfs closure-path) (define system-generation-layout-version "1")
(define* (system-generation-metadata-object os closure-path
#:key
(generation-number 1)
install-spec
install-metadata-path)
`((system-generation-version . ,system-generation-layout-version)
(generation-number . ,generation-number)
(host-name . ,(operating-system-host-name os))
(ready-marker . ,(operating-system-ready-marker os))
(init-mode . ,(operating-system-init-mode os))
(closure-path . ,closure-path)
(parameters-file . ,(string-append closure-path "/parameters.scm"))
(freebsd-base-file . ,(string-append closure-path "/metadata/freebsd-base.scm"))
(freebsd-source-file . ,(string-append closure-path "/metadata/freebsd-source.scm"))
(freebsd-source-materializations-file
. ,(string-append closure-path "/metadata/freebsd-source-materializations.scm"))
(host-base-provenance-file . ,(string-append closure-path "/metadata/host-base-provenance.scm"))
(store-layout-file . ,(string-append closure-path "/metadata/store-layout.scm"))
(install-metadata-path . ,install-metadata-path)
(install-spec . ,install-spec)))
(define (system-generation-provenance-object closure-path)
`((closure-path . ,closure-path)
(parameters-file . ,(string-append closure-path "/parameters.scm"))
(freebsd-base-file . ,(string-append closure-path "/metadata/freebsd-base.scm"))
(freebsd-source-file . ,(string-append closure-path "/metadata/freebsd-source.scm"))
(freebsd-source-materializations-file
. ,(string-append closure-path "/metadata/freebsd-source-materializations.scm"))
(host-base-provenance-file . ,(string-append closure-path "/metadata/host-base-provenance.scm"))
(store-layout-file . ,(string-append closure-path "/metadata/store-layout.scm"))))
(define* (populate-system-generation-layout os rootfs closure-path
#:key
(generation-number 1)
install-spec
install-metadata-path)
(let* ((system-root (string-append rootfs "/var/lib/fruix/system"))
(generation-name (number->string generation-number))
(generation-link-target (string-append "generations/" generation-name))
(generation-dir (string-append system-root "/generations/" generation-name))
(gcroots-dir (string-append rootfs "/frx/var/fruix/gcroots"))
(generation-install-file (string-append generation-dir "/install.scm"))
(root-install-file (and install-metadata-path
(string-append rootfs install-metadata-path))))
(mkdir-p generation-dir)
(symlink-force closure-path (string-append generation-dir "/closure"))
(write-file (string-append generation-dir "/metadata.scm")
(object->string
(system-generation-metadata-object os closure-path
#:generation-number generation-number
#:install-spec install-spec
#:install-metadata-path install-metadata-path)))
(write-file (string-append generation-dir "/provenance.scm")
(object->string (system-generation-provenance-object closure-path)))
(when (and root-install-file (file-exists? root-install-file))
(copy-regular-file root-install-file generation-install-file)
(chmod generation-install-file #o644))
(symlink-force generation-link-target (string-append system-root "/current"))
(write-file (string-append system-root "/current-generation")
(string-append generation-name "\n"))
(mkdir-p gcroots-dir)
(symlink-force closure-path (string-append gcroots-dir "/system-" generation-name))
(symlink-force closure-path (string-append gcroots-dir "/current-system"))))
(define* (populate-rootfs-from-closure os rootfs closure-path
#:key
install-spec
install-metadata-path)
(when (file-exists? rootfs) (when (file-exists? rootfs)
(delete-file-recursively rootfs)) (delete-file-recursively rootfs))
(mkdir-p rootfs) (mkdir-p rootfs)
@@ -280,6 +349,9 @@
(string-append rootfs "/usr/local/etc/rc.d/fruix-activate")) (string-append rootfs "/usr/local/etc/rc.d/fruix-activate"))
(symlink-force "/run/current-system/usr/local/etc/rc.d/fruix-shepherd" (symlink-force "/run/current-system/usr/local/etc/rc.d/fruix-shepherd"
(string-append rootfs "/usr/local/etc/rc.d/fruix-shepherd")) (string-append rootfs "/usr/local/etc/rc.d/fruix-shepherd"))
(populate-system-generation-layout os rootfs closure-path
#:install-spec install-spec
#:install-metadata-path install-metadata-path)
`((rootfs . ,rootfs) `((rootfs . ,rootfs)
(closure-path . ,closure-path) (closure-path . ,closure-path)
(ready-marker . ,(operating-system-ready-marker os)) (ready-marker . ,(operating-system-ready-marker os))
@@ -576,9 +648,10 @@
" [ -n \"$item_base\" ] || continue\n" " [ -n \"$item_base\" ] || continue\n"
" (cd '" store-dir "' && pax -rw -pe \"$item_base\" \"$mnt_root" store-dir "\")\n" " (cd '" store-dir "' && pax -rw -pe \"$item_base\" \"$mnt_root" store-dir "\")\n"
"done <\"$store_items_file\"\n" "done <\"$store_items_file\"\n"
"mkdir -p \"$mnt_root/var/lib/fruix\" \"$mnt_esp/EFI/BOOT\"\n" "mkdir -p \"$mnt_root/var/lib/fruix\" \"$mnt_root/var/lib/fruix/system/generations/1\" \"$mnt_esp/EFI/BOOT\"\n"
"cp \"$target_loader_efi\" \"$mnt_esp/EFI/BOOT/BOOTX64.EFI\"\n" "cp \"$target_loader_efi\" \"$mnt_esp/EFI/BOOT/BOOTX64.EFI\"\n"
"cp \"$install_metadata_source\" \"$mnt_root/var/lib/fruix/install.scm\"\n" "cp \"$install_metadata_source\" \"$mnt_root/var/lib/fruix/install.scm\"\n"
"cp \"$install_metadata_source\" \"$mnt_root/var/lib/fruix/system/generations/1/install.scm\"\n"
"sync\n" "sync\n"
"echo 'fruix-installer:done'\n" "echo 'fruix-installer:done'\n"
"write_state done\n"))) "write_state done\n")))
@@ -676,7 +749,9 @@
(dynamic-wind (dynamic-wind
(lambda () #t) (lambda () #t)
(lambda () (lambda ()
(populate-rootfs-from-closure os rootfs closure-path) (populate-rootfs-from-closure os rootfs closure-path
#:install-spec install-spec
#:install-metadata-path install-metadata-relative-path)
(mkdir-p mnt-root) (mkdir-p mnt-root)
(mkdir-p mnt-esp) (mkdir-p mnt-esp)
(case target-kind (case target-kind
@@ -718,7 +793,10 @@
(write-file install-metadata-file (write-file install-metadata-file
(object->string (object->string
(operating-system-install-metadata-object install-spec closure-path store-items))) (operating-system-install-metadata-object install-spec closure-path store-items)))
(chmod install-metadata-file #o644)) (chmod install-metadata-file #o644)
(populate-system-generation-layout os mnt-root closure-path
#:install-spec install-spec
#:install-metadata-path install-metadata-relative-path))
(run-command "sync") (run-command "sync")
`((target . ,target) `((target . ,target)
(target-kind . ,target-kind) (target-kind . ,target-kind)
@@ -962,7 +1040,9 @@
(lambda () #t) (lambda () #t)
(lambda () (lambda ()
(populate-rootfs-from-closure installer-os installer-rootfs installer-closure-path) (populate-rootfs-from-closure installer-os installer-rootfs installer-closure-path)
(populate-rootfs-from-closure os target-rootfs target-closure-path) (populate-rootfs-from-closure os target-rootfs target-closure-path
#:install-spec target-install-spec
#:install-metadata-path "/var/lib/fruix/install.scm")
(copy-rootfs-for-image installer-rootfs image-rootfs) (copy-rootfs-for-image installer-rootfs image-rootfs)
(mkdir-p plan-root) (mkdir-p plan-root)
(mkdir-p (string-append image-rootfs "/usr/local/libexec")) (mkdir-p (string-append image-rootfs "/usr/local/libexec"))
@@ -1265,7 +1345,9 @@
(lambda () #t) (lambda () #t)
(lambda () (lambda ()
(populate-rootfs-from-closure installer-os installer-rootfs installer-closure-path) (populate-rootfs-from-closure installer-os installer-rootfs installer-closure-path)
(populate-rootfs-from-closure os target-rootfs target-closure-path) (populate-rootfs-from-closure os target-rootfs target-closure-path
#:install-spec target-install-spec
#:install-metadata-path "/var/lib/fruix/install.scm")
(copy-rootfs-for-image installer-rootfs image-rootfs) (copy-rootfs-for-image installer-rootfs image-rootfs)
(mkdir-p plan-root) (mkdir-p plan-root)
(mkdir-p (string-append image-rootfs "/usr/local/libexec")) (mkdir-p (string-append image-rootfs "/usr/local/libexec"))

View File

@@ -0,0 +1,160 @@
#!/bin/sh
set -eu
repo_root=${PROJECT_ROOT:-$(pwd)}
metadata_target=${METADATA_OUT:-}
cleanup=0
if [ -n "${WORKDIR:-}" ]; then
workdir=$WORKDIR
mkdir -p "$workdir"
else
workdir=$(mktemp -d /tmp/fruix-phase19-generation-layout-qemu.XXXXXX)
cleanup=1
fi
if [ "${KEEP_WORKDIR:-0}" -eq 1 ]; then
cleanup=0
fi
inner_workdir=$workdir/phase18-install
inner_metadata=$workdir/phase18-system-install-metadata.txt
metadata_file=$workdir/phase19-generation-layout-qemu-metadata.txt
mnt_esp=$workdir/mnt-esp
mnt_root=$workdir/mnt-root
md_unit=
cleanup_workdir() {
if [ -n "$md_unit" ]; then
sudo umount "$mnt_esp" >/dev/null 2>&1 || true
sudo umount "$mnt_root" >/dev/null 2>&1 || true
sudo mdconfig -d -u "$md_unit" >/dev/null 2>&1 || true
fi
if [ "$cleanup" -eq 1 ]; then
rm -rf "$workdir" 2>/dev/null || sudo rm -rf "$workdir"
fi
}
trap cleanup_workdir EXIT INT TERM
mkdir -p "$mnt_esp" "$mnt_root"
KEEP_WORKDIR=1 WORKDIR="$inner_workdir" METADATA_OUT="$inner_metadata" \
"$repo_root/tests/system/run-phase18-system-install.sh" >/dev/null
field() {
sed -n "s/^$1=//p" "$inner_metadata" | tail -n 1
}
target_image=$(field target_image)
closure_path=$(field closure_path)
materialized_source_store=$(field materialized_source_store)
install_metadata_path=$(field install_metadata_path)
run_current_system_target_reported=$(field run_current_system_target)
serial_log=$(field serial_log)
closure_base=$(basename "$closure_path")
guest_md=$(sudo mdconfig -a -t vnode -f "$target_image")
md_unit=${guest_md#md}
sudo mount -t msdosfs "/dev/${guest_md}p1" "$mnt_esp"
sudo mount -t ufs -o ro "/dev/${guest_md}p2" "$mnt_root"
system_root=$mnt_root/var/lib/fruix/system
generation_root=$system_root/generations/1
gcroots_root=$mnt_root/frx/var/fruix/gcroots
[ -d "$system_root" ] || { echo "missing explicit system generation root" >&2; exit 1; }
[ -d "$generation_root" ] || { echo "missing generation 1 directory" >&2; exit 1; }
[ -f "$system_root/current-generation" ] || { echo "missing current-generation file" >&2; exit 1; }
[ -L "$system_root/current" ] || { echo "missing current generation link" >&2; exit 1; }
[ -L "$generation_root/closure" ] || { echo "missing generation closure link" >&2; exit 1; }
[ -f "$generation_root/metadata.scm" ] || { echo "missing generation metadata file" >&2; exit 1; }
[ -f "$generation_root/provenance.scm" ] || { echo "missing generation provenance file" >&2; exit 1; }
[ -f "$generation_root/install.scm" ] || { echo "missing generation install metadata file" >&2; exit 1; }
[ -L "$gcroots_root/current-system" ] || { echo "missing current-system gc root" >&2; exit 1; }
[ -L "$gcroots_root/system-1" ] || { echo "missing system-1 gc root" >&2; exit 1; }
current_generation=$(tr -d '\n' < "$system_root/current-generation")
current_link=$(readlink "$system_root/current")
generation_closure=$(readlink "$generation_root/closure")
gcroot_current=$(readlink "$gcroots_root/current-system")
gcroot_generation=$(readlink "$gcroots_root/system-1")
run_current_system_target=$(readlink "$mnt_root/run/current-system")
generation_metadata=$(cat "$generation_root/metadata.scm")
generation_provenance=$(cat "$generation_root/provenance.scm")
generation_install=$(cat "$generation_root/install.scm")
install_metadata_host=$(cat "$mnt_root$install_metadata_path")
[ "$current_generation" = 1 ] || { echo "unexpected current generation: $current_generation" >&2; exit 1; }
[ "$current_link" = generations/1 ] || { echo "unexpected current link target: $current_link" >&2; exit 1; }
[ "$generation_closure" = "/frx/store/$closure_base" ] || { echo "unexpected generation closure target: $generation_closure" >&2; exit 1; }
[ "$gcroot_current" = "/frx/store/$closure_base" ] || { echo "unexpected current-system gc root: $gcroot_current" >&2; exit 1; }
[ "$gcroot_generation" = "/frx/store/$closure_base" ] || { echo "unexpected system-1 gc root: $gcroot_generation" >&2; exit 1; }
[ "$run_current_system_target" = "/frx/store/$closure_base" ] || { echo "unexpected /run/current-system target: $run_current_system_target" >&2; exit 1; }
[ "$run_current_system_target_reported" = "/frx/store/$closure_base" ] || { echo "unexpected reported guest current-system target: $run_current_system_target_reported" >&2; exit 1; }
case "$generation_metadata" in
*"$closure_path"*) : ;;
*) echo "generation metadata does not record closure path" >&2; exit 1 ;;
esac
case "$generation_metadata" in
*"$install_metadata_path"*) : ;;
*) echo "generation metadata does not record install metadata path" >&2; exit 1 ;;
esac
case "$generation_provenance" in
*"$closure_path/metadata/store-layout.scm"*) : ;;
*) echo "generation provenance does not reference store layout metadata" >&2; exit 1 ;;
esac
case "$generation_install" in
*"$closure_path"*) : ;;
*) echo "generation install metadata does not record closure path" >&2; exit 1 ;;
esac
case "$generation_install" in
*"$materialized_source_store"*) : ;;
*) echo "generation install metadata does not record materialized source store" >&2; exit 1 ;;
esac
case "$install_metadata_host" in
*"$closure_path"*) : ;;
*) echo "installed target metadata does not record closure path" >&2; exit 1 ;;
esac
sudo umount "$mnt_esp"
sudo umount "$mnt_root"
sudo mdconfig -d -u "$md_unit"
md_unit=
cat >"$metadata_file" <<EOF
workdir=$workdir
inner_workdir=$inner_workdir
inner_metadata=$inner_metadata
target_image=$target_image
closure_path=$closure_path
closure_base=$closure_base
materialized_source_store=$materialized_source_store
install_metadata_path=$install_metadata_path
system_root=$system_root
generation_root=$generation_root
gcroots_root=$gcroots_root
current_generation=$current_generation
current_link=$current_link
generation_closure=$generation_closure
gcroot_current=$gcroot_current
gcroot_generation=$gcroot_generation
run_current_system_target=$run_current_system_target
serial_log=$serial_log
generation_layout=explicit
boot_backend=qemu-uefi-tcg
generation_layout_validation=ok
EOF
if [ -n "$metadata_target" ]; then
mkdir -p "$(dirname "$metadata_target")"
cp "$metadata_file" "$metadata_target"
fi
printf 'PASS phase19-generation-layout-qemu\n'
printf 'Work directory: %s\n' "$workdir"
printf 'Metadata file: %s\n' "$metadata_file"
if [ -n "$metadata_target" ]; then
printf 'Copied metadata to: %s\n' "$metadata_target"
fi
printf '%s\n' '--- metadata ---'
cat "$metadata_file"