system: record explicit generation layout
This commit is contained in:
1
.gitignore
vendored
Normal file
1
.gitignore
vendored
Normal file
@@ -0,0 +1 @@
|
||||
/TODO.md
|
||||
225
docs/GUIX_DIFFERENCES.md
Normal file
225
docs/GUIX_DIFFERENCES.md
Normal file
@@ -0,0 +1,225 @@
|
||||
# Fruix differences for Guix sysadmins
|
||||
|
||||
Date: 2026-04-04
|
||||
|
||||
This document is aimed at operators who already know Guix well and want a quick map of where Fruix behaves similarly and where it intentionally differs.
|
||||
|
||||
The short version is:
|
||||
|
||||
- Fruix is strongly **Guix-inspired**
|
||||
- it tries to preserve the important semantic properties
|
||||
- but it does **not** copy Guix mechanically where FreeBSD or Fruix-specific concerns make a different representation clearer
|
||||
|
||||
## Big picture
|
||||
|
||||
Fruix keeps the core Guix ideas you would expect:
|
||||
|
||||
- declarative inputs
|
||||
- content-addressed store paths
|
||||
- immutable build outputs
|
||||
- rollback-friendly deployment identity
|
||||
- provenance tied to the deployed closure rather than mutable in-place state
|
||||
|
||||
But Fruix differs in at least four major ways today:
|
||||
|
||||
1. it targets **FreeBSD**, not GNU/Linux
|
||||
2. its system frontend is currently smaller:
|
||||
- `fruix system build|rootfs|image|install|installer|installer-iso`
|
||||
3. it treats **FreeBSD source provenance** as an explicit deployment concern
|
||||
4. its installed-system generation model is still earlier-stage than Guix's mature system-generation workflow
|
||||
|
||||
## Similarities to Guix
|
||||
|
||||
If you know Guix System, these Fruix properties should feel familiar.
|
||||
|
||||
### Immutable deployment identity
|
||||
|
||||
A deployed Fruix system is identified primarily by its closure path in `/frx/store`, not by mutable files under `/etc` or `/usr/local`.
|
||||
|
||||
### `/run/current-system`
|
||||
|
||||
Fruix keeps the Guix-like runtime convention:
|
||||
|
||||
- `/run/current-system`
|
||||
|
||||
That path remains the active runtime boundary used by activation and service wiring.
|
||||
|
||||
### Rollback-friendly semantics
|
||||
|
||||
Fruix avoids in-place mutation of an older deployed closure.
|
||||
|
||||
The validated rollback story today is:
|
||||
|
||||
- keep the earlier declaration
|
||||
- rebuild or rematerialize it
|
||||
- boot or redeploy that earlier closure again
|
||||
|
||||
That is Guix-like in spirit even though Fruix does not yet expose the same installed-system rollback command surface.
|
||||
|
||||
### Generation-style metadata and roots
|
||||
|
||||
Fruix now records explicit installed-system generation state under:
|
||||
|
||||
- `/var/lib/fruix/system`
|
||||
|
||||
and explicit retention roots under:
|
||||
|
||||
- `/frx/var/fruix/gcroots`
|
||||
|
||||
This preserves the basic Guix idea that deployment state and reachability should be represented explicitly rather than inferred from whatever happens to be on disk.
|
||||
|
||||
## Important differences from Guix
|
||||
|
||||
## 1. Fruix does not mirror Guix's on-disk generation layout 1:1
|
||||
|
||||
This is intentional.
|
||||
|
||||
Guix heavily reuses its profile-generation model and represents a lot of meaning through symlink structure such as profile links and system generation links.
|
||||
|
||||
Fruix keeps the **semantics** but uses a more explicit metadata-oriented layout for installed systems.
|
||||
|
||||
Current Fruix layout:
|
||||
|
||||
```text
|
||||
/var/lib/fruix/system/
|
||||
current -> generations/1
|
||||
current-generation
|
||||
generations/
|
||||
1/
|
||||
closure -> /frx/store/...-fruix-system-...
|
||||
metadata.scm
|
||||
provenance.scm
|
||||
install.scm
|
||||
```
|
||||
|
||||
Why Fruix does this:
|
||||
|
||||
- it makes deployment state easier to inspect directly
|
||||
- it gives FreeBSD-specific install and provenance details a clearer home
|
||||
- it keeps room for richer operator tooling later without losing the Guix properties
|
||||
|
||||
So the rule of thumb is:
|
||||
|
||||
- **same semantics as Guix where practical**
|
||||
- **not necessarily the same directory names or symlink vocabulary**
|
||||
|
||||
## 2. Fruix currently keeps `/run/current-system` simple
|
||||
|
||||
Even though Fruix now records explicit generation metadata under `/var/lib/fruix/system`, it still keeps:
|
||||
|
||||
- `/run/current-system -> /frx/store/...`
|
||||
|
||||
rather than making `/run/current-system` point through a generation directory first.
|
||||
|
||||
This was chosen to preserve compatibility with the already-validated activation and runtime model while adding explicit generation metadata separately.
|
||||
|
||||
## 3. Fruix treats source provenance more explicitly
|
||||
|
||||
Guix sysadmins are used to derivation/store provenance. Fruix adds an extra emphasis because its current FreeBSD story depends on explicit source selection and materialization.
|
||||
|
||||
Fruix routinely records:
|
||||
|
||||
- declared FreeBSD source object metadata
|
||||
- materialized source store paths
|
||||
- source materialization metadata files
|
||||
- closure-level store layout metadata
|
||||
- install metadata on the target system
|
||||
|
||||
This is more prominent in Fruix than most Guix system docs because FreeBSD base/source identity has been an active design concern for this project.
|
||||
|
||||
## 4. Fruix has installer-media workflows as first-class system actions
|
||||
|
||||
Guix has installation media and image workflows, but Fruix's current system frontend makes these especially explicit because they are still part of the active architectural bring-up story.
|
||||
|
||||
Current Fruix actions include:
|
||||
|
||||
- `fruix system install`
|
||||
- `fruix system installer`
|
||||
- `fruix system installer-iso`
|
||||
|
||||
That is a little more deployment-medium-oriented than the mental model many Guix users start with.
|
||||
|
||||
## 5. Device naming is more environment-sensitive than Guix users may expect
|
||||
|
||||
Because Fruix is on FreeBSD, the install target device naming is not the same as on Linux.
|
||||
|
||||
Validated examples:
|
||||
|
||||
- installer disk-image path under QEMU:
|
||||
- `/dev/vtbd1`
|
||||
- installer ISO path under QEMU:
|
||||
- `/dev/vtbd0`
|
||||
- installer ISO path under XCP-ng:
|
||||
- `/dev/ada0`
|
||||
|
||||
So compared with Guix-on-Linux intuition, Fruix operators should be more explicit about target-device selection during installation and installer-media validation.
|
||||
|
||||
## 6. Fruix does not yet have Guix-equivalent installed-system generation commands
|
||||
|
||||
This is the biggest current operational gap.
|
||||
|
||||
Fruix does **not** yet provide a mature equivalent of the familiar Guix System operator flow around in-place generation switching and rollback commands.
|
||||
|
||||
Today, Fruix rollback is mostly:
|
||||
|
||||
- declaration-driven
|
||||
- rebuild/redeploy based
|
||||
|
||||
rather than:
|
||||
|
||||
- switch current system generation in place through a dedicated command
|
||||
|
||||
So if you come from Guix, assume that Fruix currently has:
|
||||
|
||||
- strong closure/store semantics
|
||||
- explicit install artifacts
|
||||
- explicit generation metadata roots
|
||||
- but a less mature installed-system generation UX
|
||||
|
||||
## Where Fruix is intentionally trying to improve on Guix's representation
|
||||
|
||||
Fruix is not trying to improve on Guix's core semantics. Guix already got those right.
|
||||
|
||||
Where Fruix is intentionally experimenting is mostly the **representation layer**:
|
||||
|
||||
- make generation state more legible to operators
|
||||
- make provenance more visible without needing to reconstruct it mentally from symlink layout alone
|
||||
- separate:
|
||||
- runtime entry point (`/run/current-system`)
|
||||
- installed deployment state (`/var/lib/fruix/system`)
|
||||
- retention roots (`/frx/var/fruix/gcroots`)
|
||||
|
||||
That is why Fruix currently prefers a small per-generation metadata directory instead of only a bare generation link.
|
||||
|
||||
## Practical operator advice for Guix users
|
||||
|
||||
If you are already comfortable with Guix, the safest Fruix mental model today is:
|
||||
|
||||
1. think in terms of immutable closures and declarations first
|
||||
2. use `fruix system build` as the canonical starting point
|
||||
3. treat image/install/installer/installer-iso as deployment materializers built from the same declaration
|
||||
4. identify a deployment by:
|
||||
- closure path
|
||||
- source provenance metadata
|
||||
- install metadata
|
||||
5. think of rollback today as:
|
||||
- “redeploy the earlier declaration again”
|
||||
rather than:
|
||||
- “switch to an already-managed previous generation in place”
|
||||
|
||||
## Status summary
|
||||
|
||||
Today Fruix is closest to Guix in:
|
||||
|
||||
- store and closure semantics
|
||||
- declarative deployment identity
|
||||
- rollback-friendly immutability
|
||||
- `/run/current-system` runtime model
|
||||
|
||||
It differs most from Guix in:
|
||||
|
||||
- FreeBSD platform details
|
||||
- source-provenance emphasis
|
||||
- installer-medium-oriented workflows
|
||||
- generation-layout representation
|
||||
- and the still-maturing installed-system generation command surface
|
||||
@@ -26,6 +26,10 @@ Fruix currently has:
|
||||
- `fruix system installer`
|
||||
- a bootable Fruix-managed installer ISO:
|
||||
- `fruix system installer-iso`
|
||||
- an explicit installed-system generation layout under:
|
||||
- `/var/lib/fruix/system`
|
||||
- explicit installed-system retention roots under:
|
||||
- `/frx/var/fruix/gcroots`
|
||||
|
||||
Validated boot modes still are:
|
||||
|
||||
@@ -38,42 +42,38 @@ The validated Phase 18 installation work currently uses:
|
||||
|
||||
## 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:
|
||||
|
||||
- added canonical operator workflow documentation:
|
||||
- `docs/system-deployment-workflow.md`
|
||||
- added Phase 19.1 report:
|
||||
- `docs/reports/phase19-deployment-workflow-freebsd.md`
|
||||
- the documented command surface now explicitly centers on:
|
||||
- `fruix system build`
|
||||
- `fruix system rootfs`
|
||||
- `fruix system image`
|
||||
- `fruix system install`
|
||||
- `fruix system installer`
|
||||
- `fruix system installer-iso`
|
||||
- the deployment story now explicitly records:
|
||||
- declaration-driven roll-forward
|
||||
- rollback by rebuilding/redeploying an earlier declaration
|
||||
- current deployment identity via closure path and emitted provenance metadata
|
||||
- current environment-specific installer target-device conventions
|
||||
- added explicit installed-system generation layout under:
|
||||
- `/var/lib/fruix/system`
|
||||
- added explicit installed-system retention roots under:
|
||||
- `/frx/var/fruix/gcroots`
|
||||
- installed targets now record a first-generation deployment directory containing:
|
||||
- `closure`
|
||||
- `metadata.scm`
|
||||
- `provenance.scm`
|
||||
- `install.scm`
|
||||
- `/run/current-system` remains the runtime boundary and still points directly at the active closure path
|
||||
- added Guix-oriented operator notes in:
|
||||
- `docs/GUIX_DIFFERENCES.md`
|
||||
- updated deployment workflow documentation to reflect the new explicit generation model
|
||||
|
||||
Validation basis referenced by the workflow documentation:
|
||||
Validation:
|
||||
|
||||
- `PASS phase15-base-rollback-qemu`
|
||||
- `PASS phase15-base-rollback-xcpng`
|
||||
- `PASS phase18-system-install`
|
||||
- `PASS phase18-installer-environment`
|
||||
- `PASS phase18-installer-iso`
|
||||
- `PASS phase18-installer-iso-xcpng`
|
||||
- `PASS phase19-generation-layout-qemu`
|
||||
- regression re-check:
|
||||
- `PASS phase18-installer-iso`
|
||||
|
||||
Reports:
|
||||
|
||||
- `docs/system-deployment-workflow.md`
|
||||
- `docs/GUIX_DIFFERENCES.md`
|
||||
- `docs/reports/phase19-deployment-workflow-freebsd.md`
|
||||
- `docs/reports/phase19-generation-layout-freebsd.md`
|
||||
|
||||
## Recent major milestones
|
||||
|
||||
@@ -99,6 +99,6 @@ Reports:
|
||||
|
||||
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.
|
||||
|
||||
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
|
||||
@@ -255,6 +255,43 @@ Therefore the canonical workflow is:
|
||||
|
||||
Do not assume that a device name validated in one harness is portable to another.
|
||||
|
||||
## Installed-system generation layout
|
||||
|
||||
Installed Fruix systems now record an explicit first-generation deployment layout under:
|
||||
|
||||
- `/var/lib/fruix/system`
|
||||
|
||||
Current validated shape:
|
||||
|
||||
```text
|
||||
/var/lib/fruix/system/
|
||||
current -> generations/1
|
||||
current-generation
|
||||
generations/
|
||||
1/
|
||||
closure -> /frx/store/...-fruix-system-...
|
||||
metadata.scm
|
||||
provenance.scm
|
||||
install.scm # present on installed targets
|
||||
```
|
||||
|
||||
Installed systems also now create explicit GC-root-style deployment links under:
|
||||
|
||||
- `/frx/var/fruix/gcroots`
|
||||
|
||||
Current validated shape:
|
||||
|
||||
```text
|
||||
/frx/var/fruix/gcroots/
|
||||
current-system -> /frx/store/...-fruix-system-...
|
||||
system-1 -> /frx/store/...-fruix-system-...
|
||||
```
|
||||
|
||||
Important detail:
|
||||
|
||||
- `/run/current-system` still points directly at the active closure path in `/frx/store`
|
||||
- the explicit generation layout therefore adds deployment metadata and retention roots without changing the already-validated runtime contract used by activation, rc.d wiring, and tests
|
||||
|
||||
## Roll-forward workflow
|
||||
|
||||
The current Fruix roll-forward model is declaration-driven.
|
||||
@@ -310,13 +347,14 @@ Current rollback is:
|
||||
|
||||
- **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
|
||||
- previous generation
|
||||
- rollback target
|
||||
- installed-system roots and generation links
|
||||
Still pending:
|
||||
|
||||
- previous-generation tracking beyond the initial explicit generation-1 layout
|
||||
- an explicit rollback target link distinct from `current`
|
||||
- an operator-facing installed-system rollback workflow
|
||||
- generation switching without full redeploy
|
||||
|
||||
## Provenance and deployment identity
|
||||
|
||||
@@ -341,12 +379,12 @@ The deployment workflow is now coherent, but it is not yet the final generation-
|
||||
|
||||
Not yet first-class:
|
||||
|
||||
- installed-system generation directories and symlink model
|
||||
- a dedicated `switch` or `reconfigure` command
|
||||
- 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
|
||||
|
||||
@@ -359,4 +397,4 @@ The current canonical Fruix deployment model is:
|
||||
- **identify deployments by closure path and provenance metadata**
|
||||
- **roll back by rebuilding/redeploying the earlier declaration**, not by mutating the current closure in place
|
||||
|
||||
That is the operator-facing workflow Fruix should document and use 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.
|
||||
|
||||
@@ -233,7 +233,76 @@
|
||||
(mkdir-p (dirname link-name))
|
||||
(symlink target link-name))
|
||||
|
||||
(define (populate-rootfs-from-closure os rootfs closure-path)
|
||||
(define system-generation-layout-version "1")
|
||||
|
||||
(define* (system-generation-metadata-object os closure-path
|
||||
#:key
|
||||
(generation-number 1)
|
||||
install-spec
|
||||
install-metadata-path)
|
||||
`((system-generation-version . ,system-generation-layout-version)
|
||||
(generation-number . ,generation-number)
|
||||
(host-name . ,(operating-system-host-name os))
|
||||
(ready-marker . ,(operating-system-ready-marker os))
|
||||
(init-mode . ,(operating-system-init-mode os))
|
||||
(closure-path . ,closure-path)
|
||||
(parameters-file . ,(string-append closure-path "/parameters.scm"))
|
||||
(freebsd-base-file . ,(string-append closure-path "/metadata/freebsd-base.scm"))
|
||||
(freebsd-source-file . ,(string-append closure-path "/metadata/freebsd-source.scm"))
|
||||
(freebsd-source-materializations-file
|
||||
. ,(string-append closure-path "/metadata/freebsd-source-materializations.scm"))
|
||||
(host-base-provenance-file . ,(string-append closure-path "/metadata/host-base-provenance.scm"))
|
||||
(store-layout-file . ,(string-append closure-path "/metadata/store-layout.scm"))
|
||||
(install-metadata-path . ,install-metadata-path)
|
||||
(install-spec . ,install-spec)))
|
||||
|
||||
(define (system-generation-provenance-object closure-path)
|
||||
`((closure-path . ,closure-path)
|
||||
(parameters-file . ,(string-append closure-path "/parameters.scm"))
|
||||
(freebsd-base-file . ,(string-append closure-path "/metadata/freebsd-base.scm"))
|
||||
(freebsd-source-file . ,(string-append closure-path "/metadata/freebsd-source.scm"))
|
||||
(freebsd-source-materializations-file
|
||||
. ,(string-append closure-path "/metadata/freebsd-source-materializations.scm"))
|
||||
(host-base-provenance-file . ,(string-append closure-path "/metadata/host-base-provenance.scm"))
|
||||
(store-layout-file . ,(string-append closure-path "/metadata/store-layout.scm"))))
|
||||
|
||||
(define* (populate-system-generation-layout os rootfs closure-path
|
||||
#:key
|
||||
(generation-number 1)
|
||||
install-spec
|
||||
install-metadata-path)
|
||||
(let* ((system-root (string-append rootfs "/var/lib/fruix/system"))
|
||||
(generation-name (number->string generation-number))
|
||||
(generation-link-target (string-append "generations/" generation-name))
|
||||
(generation-dir (string-append system-root "/generations/" generation-name))
|
||||
(gcroots-dir (string-append rootfs "/frx/var/fruix/gcroots"))
|
||||
(generation-install-file (string-append generation-dir "/install.scm"))
|
||||
(root-install-file (and install-metadata-path
|
||||
(string-append rootfs install-metadata-path))))
|
||||
(mkdir-p generation-dir)
|
||||
(symlink-force closure-path (string-append generation-dir "/closure"))
|
||||
(write-file (string-append generation-dir "/metadata.scm")
|
||||
(object->string
|
||||
(system-generation-metadata-object os closure-path
|
||||
#:generation-number generation-number
|
||||
#:install-spec install-spec
|
||||
#:install-metadata-path install-metadata-path)))
|
||||
(write-file (string-append generation-dir "/provenance.scm")
|
||||
(object->string (system-generation-provenance-object closure-path)))
|
||||
(when (and root-install-file (file-exists? root-install-file))
|
||||
(copy-regular-file root-install-file generation-install-file)
|
||||
(chmod generation-install-file #o644))
|
||||
(symlink-force generation-link-target (string-append system-root "/current"))
|
||||
(write-file (string-append system-root "/current-generation")
|
||||
(string-append generation-name "\n"))
|
||||
(mkdir-p gcroots-dir)
|
||||
(symlink-force closure-path (string-append gcroots-dir "/system-" generation-name))
|
||||
(symlink-force closure-path (string-append gcroots-dir "/current-system"))))
|
||||
|
||||
(define* (populate-rootfs-from-closure os rootfs closure-path
|
||||
#:key
|
||||
install-spec
|
||||
install-metadata-path)
|
||||
(when (file-exists? rootfs)
|
||||
(delete-file-recursively rootfs))
|
||||
(mkdir-p rootfs)
|
||||
@@ -280,6 +349,9 @@
|
||||
(string-append rootfs "/usr/local/etc/rc.d/fruix-activate"))
|
||||
(symlink-force "/run/current-system/usr/local/etc/rc.d/fruix-shepherd"
|
||||
(string-append rootfs "/usr/local/etc/rc.d/fruix-shepherd"))
|
||||
(populate-system-generation-layout os rootfs closure-path
|
||||
#:install-spec install-spec
|
||||
#:install-metadata-path install-metadata-path)
|
||||
`((rootfs . ,rootfs)
|
||||
(closure-path . ,closure-path)
|
||||
(ready-marker . ,(operating-system-ready-marker os))
|
||||
@@ -576,9 +648,10 @@
|
||||
" [ -n \"$item_base\" ] || continue\n"
|
||||
" (cd '" store-dir "' && pax -rw -pe \"$item_base\" \"$mnt_root" store-dir "\")\n"
|
||||
"done <\"$store_items_file\"\n"
|
||||
"mkdir -p \"$mnt_root/var/lib/fruix\" \"$mnt_esp/EFI/BOOT\"\n"
|
||||
"mkdir -p \"$mnt_root/var/lib/fruix\" \"$mnt_root/var/lib/fruix/system/generations/1\" \"$mnt_esp/EFI/BOOT\"\n"
|
||||
"cp \"$target_loader_efi\" \"$mnt_esp/EFI/BOOT/BOOTX64.EFI\"\n"
|
||||
"cp \"$install_metadata_source\" \"$mnt_root/var/lib/fruix/install.scm\"\n"
|
||||
"cp \"$install_metadata_source\" \"$mnt_root/var/lib/fruix/system/generations/1/install.scm\"\n"
|
||||
"sync\n"
|
||||
"echo 'fruix-installer:done'\n"
|
||||
"write_state done\n")))
|
||||
@@ -676,7 +749,9 @@
|
||||
(dynamic-wind
|
||||
(lambda () #t)
|
||||
(lambda ()
|
||||
(populate-rootfs-from-closure os rootfs closure-path)
|
||||
(populate-rootfs-from-closure os rootfs closure-path
|
||||
#:install-spec install-spec
|
||||
#:install-metadata-path install-metadata-relative-path)
|
||||
(mkdir-p mnt-root)
|
||||
(mkdir-p mnt-esp)
|
||||
(case target-kind
|
||||
@@ -718,7 +793,10 @@
|
||||
(write-file install-metadata-file
|
||||
(object->string
|
||||
(operating-system-install-metadata-object install-spec closure-path store-items)))
|
||||
(chmod install-metadata-file #o644))
|
||||
(chmod install-metadata-file #o644)
|
||||
(populate-system-generation-layout os mnt-root closure-path
|
||||
#:install-spec install-spec
|
||||
#:install-metadata-path install-metadata-relative-path))
|
||||
(run-command "sync")
|
||||
`((target . ,target)
|
||||
(target-kind . ,target-kind)
|
||||
@@ -962,7 +1040,9 @@
|
||||
(lambda () #t)
|
||||
(lambda ()
|
||||
(populate-rootfs-from-closure installer-os installer-rootfs installer-closure-path)
|
||||
(populate-rootfs-from-closure os target-rootfs target-closure-path)
|
||||
(populate-rootfs-from-closure os target-rootfs target-closure-path
|
||||
#:install-spec target-install-spec
|
||||
#:install-metadata-path "/var/lib/fruix/install.scm")
|
||||
(copy-rootfs-for-image installer-rootfs image-rootfs)
|
||||
(mkdir-p plan-root)
|
||||
(mkdir-p (string-append image-rootfs "/usr/local/libexec"))
|
||||
@@ -1265,7 +1345,9 @@
|
||||
(lambda () #t)
|
||||
(lambda ()
|
||||
(populate-rootfs-from-closure installer-os installer-rootfs installer-closure-path)
|
||||
(populate-rootfs-from-closure os target-rootfs target-closure-path)
|
||||
(populate-rootfs-from-closure os target-rootfs target-closure-path
|
||||
#:install-spec target-install-spec
|
||||
#:install-metadata-path "/var/lib/fruix/install.scm")
|
||||
(copy-rootfs-for-image installer-rootfs image-rootfs)
|
||||
(mkdir-p plan-root)
|
||||
(mkdir-p (string-append image-rootfs "/usr/local/libexec"))
|
||||
|
||||
160
tests/system/run-phase19-generation-layout-qemu.sh
Executable file
160
tests/system/run-phase19-generation-layout-qemu.sh
Executable file
@@ -0,0 +1,160 @@
|
||||
#!/bin/sh
|
||||
set -eu
|
||||
|
||||
repo_root=${PROJECT_ROOT:-$(pwd)}
|
||||
metadata_target=${METADATA_OUT:-}
|
||||
cleanup=0
|
||||
|
||||
if [ -n "${WORKDIR:-}" ]; then
|
||||
workdir=$WORKDIR
|
||||
mkdir -p "$workdir"
|
||||
else
|
||||
workdir=$(mktemp -d /tmp/fruix-phase19-generation-layout-qemu.XXXXXX)
|
||||
cleanup=1
|
||||
fi
|
||||
if [ "${KEEP_WORKDIR:-0}" -eq 1 ]; then
|
||||
cleanup=0
|
||||
fi
|
||||
|
||||
inner_workdir=$workdir/phase18-install
|
||||
inner_metadata=$workdir/phase18-system-install-metadata.txt
|
||||
metadata_file=$workdir/phase19-generation-layout-qemu-metadata.txt
|
||||
mnt_esp=$workdir/mnt-esp
|
||||
mnt_root=$workdir/mnt-root
|
||||
md_unit=
|
||||
|
||||
cleanup_workdir() {
|
||||
if [ -n "$md_unit" ]; then
|
||||
sudo umount "$mnt_esp" >/dev/null 2>&1 || true
|
||||
sudo umount "$mnt_root" >/dev/null 2>&1 || true
|
||||
sudo mdconfig -d -u "$md_unit" >/dev/null 2>&1 || true
|
||||
fi
|
||||
if [ "$cleanup" -eq 1 ]; then
|
||||
rm -rf "$workdir" 2>/dev/null || sudo rm -rf "$workdir"
|
||||
fi
|
||||
}
|
||||
trap cleanup_workdir EXIT INT TERM
|
||||
|
||||
mkdir -p "$mnt_esp" "$mnt_root"
|
||||
|
||||
KEEP_WORKDIR=1 WORKDIR="$inner_workdir" METADATA_OUT="$inner_metadata" \
|
||||
"$repo_root/tests/system/run-phase18-system-install.sh" >/dev/null
|
||||
|
||||
field() {
|
||||
sed -n "s/^$1=//p" "$inner_metadata" | tail -n 1
|
||||
}
|
||||
|
||||
target_image=$(field target_image)
|
||||
closure_path=$(field closure_path)
|
||||
materialized_source_store=$(field materialized_source_store)
|
||||
install_metadata_path=$(field install_metadata_path)
|
||||
run_current_system_target_reported=$(field run_current_system_target)
|
||||
serial_log=$(field serial_log)
|
||||
closure_base=$(basename "$closure_path")
|
||||
|
||||
guest_md=$(sudo mdconfig -a -t vnode -f "$target_image")
|
||||
md_unit=${guest_md#md}
|
||||
sudo mount -t msdosfs "/dev/${guest_md}p1" "$mnt_esp"
|
||||
sudo mount -t ufs -o ro "/dev/${guest_md}p2" "$mnt_root"
|
||||
|
||||
system_root=$mnt_root/var/lib/fruix/system
|
||||
generation_root=$system_root/generations/1
|
||||
gcroots_root=$mnt_root/frx/var/fruix/gcroots
|
||||
|
||||
[ -d "$system_root" ] || { echo "missing explicit system generation root" >&2; exit 1; }
|
||||
[ -d "$generation_root" ] || { echo "missing generation 1 directory" >&2; exit 1; }
|
||||
[ -f "$system_root/current-generation" ] || { echo "missing current-generation file" >&2; exit 1; }
|
||||
[ -L "$system_root/current" ] || { echo "missing current generation link" >&2; exit 1; }
|
||||
[ -L "$generation_root/closure" ] || { echo "missing generation closure link" >&2; exit 1; }
|
||||
[ -f "$generation_root/metadata.scm" ] || { echo "missing generation metadata file" >&2; exit 1; }
|
||||
[ -f "$generation_root/provenance.scm" ] || { echo "missing generation provenance file" >&2; exit 1; }
|
||||
[ -f "$generation_root/install.scm" ] || { echo "missing generation install metadata file" >&2; exit 1; }
|
||||
[ -L "$gcroots_root/current-system" ] || { echo "missing current-system gc root" >&2; exit 1; }
|
||||
[ -L "$gcroots_root/system-1" ] || { echo "missing system-1 gc root" >&2; exit 1; }
|
||||
|
||||
current_generation=$(tr -d '\n' < "$system_root/current-generation")
|
||||
current_link=$(readlink "$system_root/current")
|
||||
generation_closure=$(readlink "$generation_root/closure")
|
||||
gcroot_current=$(readlink "$gcroots_root/current-system")
|
||||
gcroot_generation=$(readlink "$gcroots_root/system-1")
|
||||
run_current_system_target=$(readlink "$mnt_root/run/current-system")
|
||||
generation_metadata=$(cat "$generation_root/metadata.scm")
|
||||
generation_provenance=$(cat "$generation_root/provenance.scm")
|
||||
generation_install=$(cat "$generation_root/install.scm")
|
||||
install_metadata_host=$(cat "$mnt_root$install_metadata_path")
|
||||
|
||||
[ "$current_generation" = 1 ] || { echo "unexpected current generation: $current_generation" >&2; exit 1; }
|
||||
[ "$current_link" = generations/1 ] || { echo "unexpected current link target: $current_link" >&2; exit 1; }
|
||||
[ "$generation_closure" = "/frx/store/$closure_base" ] || { echo "unexpected generation closure target: $generation_closure" >&2; exit 1; }
|
||||
[ "$gcroot_current" = "/frx/store/$closure_base" ] || { echo "unexpected current-system gc root: $gcroot_current" >&2; exit 1; }
|
||||
[ "$gcroot_generation" = "/frx/store/$closure_base" ] || { echo "unexpected system-1 gc root: $gcroot_generation" >&2; exit 1; }
|
||||
[ "$run_current_system_target" = "/frx/store/$closure_base" ] || { echo "unexpected /run/current-system target: $run_current_system_target" >&2; exit 1; }
|
||||
[ "$run_current_system_target_reported" = "/frx/store/$closure_base" ] || { echo "unexpected reported guest current-system target: $run_current_system_target_reported" >&2; exit 1; }
|
||||
|
||||
case "$generation_metadata" in
|
||||
*"$closure_path"*) : ;;
|
||||
*) echo "generation metadata does not record closure path" >&2; exit 1 ;;
|
||||
esac
|
||||
case "$generation_metadata" in
|
||||
*"$install_metadata_path"*) : ;;
|
||||
*) echo "generation metadata does not record install metadata path" >&2; exit 1 ;;
|
||||
esac
|
||||
case "$generation_provenance" in
|
||||
*"$closure_path/metadata/store-layout.scm"*) : ;;
|
||||
*) echo "generation provenance does not reference store layout metadata" >&2; exit 1 ;;
|
||||
esac
|
||||
case "$generation_install" in
|
||||
*"$closure_path"*) : ;;
|
||||
*) echo "generation install metadata does not record closure path" >&2; exit 1 ;;
|
||||
esac
|
||||
case "$generation_install" in
|
||||
*"$materialized_source_store"*) : ;;
|
||||
*) echo "generation install metadata does not record materialized source store" >&2; exit 1 ;;
|
||||
esac
|
||||
case "$install_metadata_host" in
|
||||
*"$closure_path"*) : ;;
|
||||
*) echo "installed target metadata does not record closure path" >&2; exit 1 ;;
|
||||
esac
|
||||
|
||||
sudo umount "$mnt_esp"
|
||||
sudo umount "$mnt_root"
|
||||
sudo mdconfig -d -u "$md_unit"
|
||||
md_unit=
|
||||
|
||||
cat >"$metadata_file" <<EOF
|
||||
workdir=$workdir
|
||||
inner_workdir=$inner_workdir
|
||||
inner_metadata=$inner_metadata
|
||||
target_image=$target_image
|
||||
closure_path=$closure_path
|
||||
closure_base=$closure_base
|
||||
materialized_source_store=$materialized_source_store
|
||||
install_metadata_path=$install_metadata_path
|
||||
system_root=$system_root
|
||||
generation_root=$generation_root
|
||||
gcroots_root=$gcroots_root
|
||||
current_generation=$current_generation
|
||||
current_link=$current_link
|
||||
generation_closure=$generation_closure
|
||||
gcroot_current=$gcroot_current
|
||||
gcroot_generation=$gcroot_generation
|
||||
run_current_system_target=$run_current_system_target
|
||||
serial_log=$serial_log
|
||||
generation_layout=explicit
|
||||
boot_backend=qemu-uefi-tcg
|
||||
generation_layout_validation=ok
|
||||
EOF
|
||||
|
||||
if [ -n "$metadata_target" ]; then
|
||||
mkdir -p "$(dirname "$metadata_target")"
|
||||
cp "$metadata_file" "$metadata_target"
|
||||
fi
|
||||
|
||||
printf 'PASS phase19-generation-layout-qemu\n'
|
||||
printf 'Work directory: %s\n' "$workdir"
|
||||
printf 'Metadata file: %s\n' "$metadata_file"
|
||||
if [ -n "$metadata_target" ]; then
|
||||
printf 'Copied metadata to: %s\n' "$metadata_target"
|
||||
fi
|
||||
printf '%s\n' '--- metadata ---'
|
||||
cat "$metadata_file"
|
||||
Reference in New Issue
Block a user