From b3b1ba248906d1b4a7a7544930235f2d340f0d3f Mon Sep 17 00:00:00 2001 From: Steffen Beyer Date: Sat, 4 Apr 2026 19:41:49 +0200 Subject: [PATCH] system: record explicit generation layout --- .gitignore | 1 + docs/GUIX_DIFFERENCES.md | 225 ++++++++++++++++++ docs/PROGRESS.md | 54 ++--- .../phase19-generation-layout-freebsd.md | 192 +++++++++++++++ docs/system-deployment-workflow.md | 56 ++++- modules/fruix/system/freebsd/media.scm | 94 +++++++- .../run-phase19-generation-layout-qemu.sh | 160 +++++++++++++ 7 files changed, 740 insertions(+), 42 deletions(-) create mode 100644 .gitignore create mode 100644 docs/GUIX_DIFFERENCES.md create mode 100644 docs/reports/phase19-generation-layout-freebsd.md create mode 100755 tests/system/run-phase19-generation-layout-qemu.sh diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..5f1d7c3 --- /dev/null +++ b/.gitignore @@ -0,0 +1 @@ +/TODO.md diff --git a/docs/GUIX_DIFFERENCES.md b/docs/GUIX_DIFFERENCES.md new file mode 100644 index 0000000..f0ab78d --- /dev/null +++ b/docs/GUIX_DIFFERENCES.md @@ -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 diff --git a/docs/PROGRESS.md b/docs/PROGRESS.md index 9e59a56..5f0c29c 100644 --- a/docs/PROGRESS.md +++ b/docs/PROGRESS.md @@ -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. diff --git a/docs/reports/phase19-generation-layout-freebsd.md b/docs/reports/phase19-generation-layout-freebsd.md new file mode 100644 index 0000000..6d9c7ed --- /dev/null +++ b/docs/reports/phase19-generation-layout-freebsd.md @@ -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 diff --git a/docs/system-deployment-workflow.md b/docs/system-deployment-workflow.md index fac2d94..de64ab8 100644 --- a/docs/system-deployment-workflow.md +++ b/docs/system-deployment-workflow.md @@ -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. diff --git a/modules/fruix/system/freebsd/media.scm b/modules/fruix/system/freebsd/media.scm index a6704a4..d5fdbc4 100644 --- a/modules/fruix/system/freebsd/media.scm +++ b/modules/fruix/system/freebsd/media.scm @@ -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")) diff --git a/tests/system/run-phase19-generation-layout-qemu.sh b/tests/system/run-phase19-generation-layout-qemu.sh new file mode 100755 index 0000000..3a61b63 --- /dev/null +++ b/tests/system/run-phase19-generation-layout-qemu.sh @@ -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" <