From 0e8b30434f2fab6e8cad3ddab7c54757cac13377 Mon Sep 17 00:00:00 2001 From: Steffen Beyer Date: Mon, 6 Apr 2026 06:38:23 +0200 Subject: [PATCH] system: consume promoted native base results --- docs/GUIX_DIFFERENCES.md | 12 +- docs/PROGRESS.md | 56 ++- ...omoted-native-base-declarations-freebsd.md | 165 +++++++++ docs/system-deployment-workflow.md | 47 ++- modules/fruix/system/freebsd.scm | 14 + modules/fruix/system/freebsd/build.scm | 335 +++++++++++++++--- modules/fruix/system/freebsd/media.scm | 79 +++-- modules/fruix/system/freebsd/model.scm | 85 ++++- ...omoted-native-base-operating-system.scm.in | 73 ++++ ...-promoted-native-base-declaration-xcpng.sh | 227 ++++++++++++ 10 files changed, 984 insertions(+), 109 deletions(-) create mode 100644 docs/reports/postphase20-promoted-native-base-declarations-freebsd.md create mode 100644 tests/system/phase20-promoted-native-base-operating-system.scm.in create mode 100755 tests/system/run-phase20-promoted-native-base-declaration-xcpng.sh diff --git a/docs/GUIX_DIFFERENCES.md b/docs/GUIX_DIFFERENCES.md index c94945e..020e93d 100644 --- a/docs/GUIX_DIFFERENCES.md +++ b/docs/GUIX_DIFFERENCES.md @@ -1,6 +1,6 @@ # Fruix differences for Guix sysadmins -Date: 2026-04-05 +Date: 2026-04-06 This document is aimed at operators who already know Guix well and want a quick map of where Fruix behaves similarly and where it intentionally differs. @@ -310,6 +310,16 @@ So instead of treating: as three unrelated architectural forks, Fruix is moving toward one native-build result model with different executor policies attached to it. +Fruix also now lets system declarations consume a promoted native-build result bundle directly. + +That means Fruix can now distinguish more explicitly between: + +- a mutable build/result root used during execution +- an immutable promoted native-build identity in `/frx/store` +- and a later system declaration that points at that promoted identity as an input + +Compared with Guix, this is still Guix-like in the important sense that the deployed system is identified by immutable store objects, but Fruix is making the FreeBSD native-base result set itself a more explicit first-class declaration input than it was before. + ## Where Fruix is intentionally trying to improve on Guix's representation Fruix is not trying to improve on Guix's core semantics. Guix already got those right. diff --git a/docs/PROGRESS.md b/docs/PROGRESS.md index 678b649..0a81a6e 100644 --- a/docs/PROGRESS.md +++ b/docs/PROGRESS.md @@ -60,6 +60,10 @@ Fruix currently has: - end-to-end validated staged-result-plus-promotion paths for executor policies: - `ssh-guest` - `self-hosted` +- system declarations that can now consume a promoted native-build result bundle directly via: + - `promoted-native-build-result` + - `operating-system-from-promoted-native-build-result` +- a real XCP-ng boot validation of a system materialized from a promoted native-build result identity Validated boot modes still are: @@ -72,42 +76,36 @@ The validated Phase 18 installation work currently uses: ## Latest completed achievement -### 2026-04-06 — Native build executor model introduced and validated across two executor policies +### 2026-04-06 — Promoted native-base result sets are now first-class declaration inputs -Fruix now has an explicit executor model for native base builds, and the same staged-result-plus-promotion flow is validated across both host-initiated guest execution and guest self-hosted execution. +Fruix can now materialize and boot a normal system declaration directly from a promoted native-build result bundle in `/frx/store`. Highlights: -- native base-build placement is now modeled as executor policy rather than as separate unrelated paths -- current executor kinds are: - - `host` - - `ssh-guest` - - `self-hosted` -- guest result roots now carry explicit executor metadata instead of only an ad hoc executor string -- both validated executor paths now converge on the same Fruix-native flow: - - stage mutable results under `/var/lib/fruix/native-builds/` - - emit promotion metadata with shared provenance/build-policy/artifact shape - - promote immutable identities into `/frx/store/...` -- the `ssh-guest` path now also stages promotable native-build result roots rather than only temporary build directories under `/var/tmp` -- the host can now promote both executor paths through the same command: - - `fruix native-build promote RESULT_ROOT` +- promoted native-build result bundles are no longer only post-build artifacts +- system declarations can now refer directly to those promoted identities via: + - `promoted-native-build-result` + - `operating-system-from-promoted-native-build-result` +- promoted result bundles now drive: + - kernel selection + - bootloader selection + - base world selection + - optional promoted headers selection +- the resulting system closure now records promoted-result provenance explicitly in: + - `metadata/promoted-native-build-result.scm` + - `metadata/store-layout.scm` + - closure `.references` +- the validated real-VM boot used the promoted `ssh-guest` result bundle as the declaration input, proving that the result/promotion model is now consumable by ordinary Fruix system materialization Validation: -- `PASS phase20-host-initiated-native-build-xcpng` -- `PASS phase20-host-initiated-native-build-store-promotion-xcpng` -- `PASS phase20-self-hosted-native-build-xcpng` -- `PASS phase20-native-build-store-promotion-xcpng` +- `PASS phase20-promoted-native-base-declaration-xcpng` -Reports: +Report: +- `docs/reports/postphase20-promoted-native-base-declarations-freebsd.md` - `docs/system-deployment-workflow.md` - `docs/GUIX_DIFFERENCES.md` -- `docs/reports/phase20-development-environment-freebsd.md` -- `docs/reports/phase20-host-initiated-native-builds-freebsd.md` -- `docs/reports/phase20-self-hosted-native-builds-freebsd.md` -- `docs/reports/phase20-native-build-store-promotion-freebsd.md` -- `docs/reports/phase20-native-build-executor-model-freebsd.md` ## Recent major milestones @@ -135,12 +133,12 @@ Reports: The next practical follow-up is now clearer: -- extend the shared executor/result model from validated `ssh-guest` and `self-hosted` paths to the `host` path as well -- decide how much of result import/promotion should remain host-side versus become a more integrated Fruix action -- determine whether executor selection should become part of a higher-level Fruix native-build/deployment command surface +- decide how much of result creation, validation, promotion, and declaration consumption should be wrapped into one higher-level Fruix workflow +- determine whether native-build result selection should become a more explicit operator-facing deployment/declaration surface rather than a lower-level helper step +- continue tightening how executor policy and promoted-result identity are presented to operators without losing the new first-class declaration-input model The immediate architectural direction is no longer just “can guest self-hosting work?” It is now: -- how should Fruix expose executor selection, execution policy, and result promotion as one coherent operator-facing workflow? +- how should Fruix expose build executor choice, promoted native-base identities, and deployment materialization as one coherent product workflow? diff --git a/docs/reports/postphase20-promoted-native-base-declarations-freebsd.md b/docs/reports/postphase20-promoted-native-base-declarations-freebsd.md new file mode 100644 index 0000000..aa5db1e --- /dev/null +++ b/docs/reports/postphase20-promoted-native-base-declarations-freebsd.md @@ -0,0 +1,165 @@ +# Post-Phase 20: promoted native base result sets as declaration inputs + +Date: 2026-04-06 + +## Goal + +Move Fruix from: + +- “the guest built a kernel” + +to: + +- “Fruix materialized a native-base result set with identity X” + +and then prove that a normal Fruix system declaration can consume that promoted result set directly. + +Concretely, the desired product behavior was: + +1. build runs somewhere +2. result is recorded in a staging/result root +3. Fruix validates the result shape and metadata +4. Fruix promotes it into store objects +5. system declarations can refer to those promoted artifacts + +Before this step, Fruix already had validated steps 1 through 4. + +This step implemented and validated step 5. + +## What changed + +### First-class promoted result reference in system declarations + +Fruix now has a first-class promoted native-build result reference object. + +Current declaration-level entry points are: + +- `promoted-native-build-result` +- `operating-system-from-promoted-native-build-result` + +A declaration can now point at a promoted native-build result bundle in `/frx/store`, for example: + +```scheme +(define promoted + (promoted-native-build-result + #:store-path "/frx/store/...-fruix-native-build-result-...")) + +(define os + (operating-system-from-promoted-native-build-result + promoted + ...)) +``` + +### Promoted result bundles now drive system composition + +From a promoted result bundle, Fruix now derives: + +- the effective FreeBSD base metadata used by the system declaration +- kernel package identity from the promoted kernel artifact store +- bootloader package identity from the promoted bootloader artifact store +- base-package identity from the promoted world artifact store +- optional development-package identity from the promoted headers artifact store + +This means a system declaration can now be based on a promoted native-build result set rather than only on source-driven base-package reconstruction. + +### Closure metadata now records promoted-result provenance explicitly + +When a system declaration uses a promoted native-build result set, Fruix now records that explicitly in the resulting closure metadata. + +New closure metadata includes: + +- `metadata/promoted-native-build-result.scm` +- `metadata/store-layout.scm` entries for: + - promoted result summary + - promoted artifact stores + +The system closure also retains the promoted result bundle itself as an explicit reference in: + +- `/frx/store/...-fruix-system-.../.references` + +So the resulting deployed closure does not merely happen to contain files copied from a promoted artifact; it explicitly records which promoted result set it came from. + +## Implementation notes + +The key plumbing additions were: + +- a promoted native-build result record in: + - `modules/fruix/system/freebsd/model.scm` +- promoted-result readers/helpers in: + - `modules/fruix/system/freebsd/build.scm` +- direct system-construction helper: + - `operating-system-from-promoted-native-build-result` +- support for consuming promoted artifact stores directly when materializing packages +- closure/store-layout metadata wiring in: + - `modules/fruix/system/freebsd/media.scm` + +The important product-level effect is that promoted native-build results are no longer only post-build artifacts. They are now declaration inputs. + +## Validation harness + +Added: + +- `tests/system/phase20-promoted-native-base-operating-system.scm.in` +- `tests/system/run-phase20-promoted-native-base-declaration-xcpng.sh` + +The harness supports two modes: + +- use an explicit `RESULT_STORE=/frx/store/...-fruix-native-build-result-...` +- or, if no result store is supplied, first run the validated host-initiated promotion path and consume that result + +## Validation performed + +Validated on the approved real XCP-ng path using the promoted `ssh-guest` result bundle: + +- result bundle: + - `/frx/store/ffe44f5d1ba576e1f811ad3fe3a526a242b5c4a5-fruix-native-build-result-15.0-STABLE-ssh-guest` +- VM: + - `90490f2e-e8fc-4b7a-388e-5c26f0157289` +- VDI: + - `0f1f90d3-48ca-4fa2-91d8-fc6339b95743` + +Passing run: + +- `PASS phase20-promoted-native-base-declaration-xcpng` + +Representative metadata: + +```text +closure_path=/frx/store/ac1e6dcfe67d3cde49d4fd5da97740f6244276b4-fruix-system-fruix-freebsd +result_store=/frx/store/ffe44f5d1ba576e1f811ad3fe3a526a242b5c4a5-fruix-native-build-result-15.0-STABLE-ssh-guest +executor_kind=ssh-guest +executor_name=ssh-guest +executor_version=1 +world_store=/frx/store/89c7a71c3df148a1f99b13d57fd6be88243eb2cb-fruix-native-world-15.0-STABLE-ssh-guest +kernel_store=/frx/store/93bac81122022b40438d356146a6854b4ee48513-fruix-native-kernel-15.0-STABLE-ssh-guest +headers_store=/frx/store/dd7f39f526bca4849caf1eaf96ae25d29b43493c-fruix-native-headers-15.0-STABLE-ssh-guest +bootloader_store=/frx/store/78b1c6b0b5c0c2c1549f5f42f3d64b6d9293669b-fruix-native-bootloader-15.0-STABLE-ssh-guest +kernel_link=/frx/store/93bac81122022b40438d356146a6854b4ee48513-fruix-native-kernel-15.0-STABLE-ssh-guest/boot/kernel/kernel +bootloader_link=/frx/store/78b1c6b0b5c0c2c1549f5f42f3d64b6d9293669b-fruix-native-bootloader-15.0-STABLE-ssh-guest/boot/loader.efi +guest_kernel_link=/frx/store/93bac81122022b40438d356146a6854b4ee48513-fruix-native-kernel-15.0-STABLE-ssh-guest/boot/kernel/kernel +guest_bootloader_link=/frx/store/78b1c6b0b5c0c2c1549f5f42f3d64b6d9293669b-fruix-native-bootloader-15.0-STABLE-ssh-guest/boot/loader.efi +guest_uname=FreeBSD 15.0-STABLE +promoted_native_base_declaration=ok +``` + +## Validated facts + +The real-VM validation proved that Fruix can now: + +- consume a promoted native-build result bundle directly from a declaration +- materialize a normal system closure from that promoted identity +- retain explicit promoted-result provenance in closure metadata +- boot the resulting system successfully on the approved XCP-ng path +- keep the closure's kernel and bootloader linked directly to the promoted artifact stores +- preserve the promoted result-bundle store itself as an explicit closure reference + +So the operator-facing story is now materially better: + +- not only “a build happened somewhere” +- but “this system declaration is based on promoted native-base result X” + +## Result + +Promoted native-build result sets are now first-class Fruix declaration inputs. + +That is the point where the native FreeBSD base path stops being only a build/promotion harness and starts acting more like real product behavior. \ No newline at end of file diff --git a/docs/system-deployment-workflow.md b/docs/system-deployment-workflow.md index 7480341..40b7b0d 100644 --- a/docs/system-deployment-workflow.md +++ b/docs/system-deployment-workflow.md @@ -1,6 +1,6 @@ # Fruix system deployment workflow -Date: 2026-04-05 +Date: 2026-04-06 ## Purpose @@ -353,6 +353,51 @@ This is the current Fruix-native answer to the question: - where should immutable native-build identity live? - `/frx/store/...` +### Using promoted native-build results in system declarations + +Fruix system declarations can now refer directly to a promoted native-build result bundle. + +Current declaration-level helpers are: + +- `promoted-native-build-result` +- `operating-system-from-promoted-native-build-result` + +Representative pattern: + +```scheme +(define promoted + (promoted-native-build-result + #:store-path "/frx/store/...-fruix-native-build-result-...")) + +(define os + (operating-system-from-promoted-native-build-result + promoted + #:host-name "fruix-freebsd" + ...)) +``` + +That now gives Fruix a more product-like story: + +1. a build runs under some executor policy +2. Fruix records the staged mutable result +3. Fruix promotes it into immutable store identities +4. a later system declaration can point at that promoted result identity +5. Fruix materializes and boots a normal system from that promoted identity + +The resulting closure now records that provenance explicitly through: + +- `metadata/promoted-native-build-result.scm` +- `metadata/store-layout.scm` +- closure references that retain the selected result-bundle store path + +So the operator-facing statement is now: + +- “this Fruix system is based on promoted native-base result X” + +not only: + +- “some earlier build happened and its files were copied somewhere.” + ### Native-build executor model Fruix now has an explicit executor model for native base builds. diff --git a/modules/fruix/system/freebsd.scm b/modules/fruix/system/freebsd.scm index e48d702..18ce904 100644 --- a/modules/fruix/system/freebsd.scm +++ b/modules/fruix/system/freebsd.scm @@ -26,10 +26,16 @@ file-system-type file-system-options file-system-needed-for-boot? + promoted-native-build-result? + promoted-native-build-result-store-path + promoted-native-build-result-metadata-file + promoted-native-build-result-metadata + promoted-native-build-result-spec operating-system operating-system? operating-system-host-name operating-system-freebsd-base + operating-system-native-build-result operating-system-kernel operating-system-bootloader operating-system-base-packages @@ -56,6 +62,14 @@ host-native-build-executor ssh-guest-native-build-executor self-hosted-native-build-executor + promoted-native-build-result + promoted-native-build-result->freebsd-base + promoted-native-build-result-artifact-store + promoted-native-build-result-kernel-package + promoted-native-build-result-bootloader-package + promoted-native-build-result-base-packages + promoted-native-build-result-development-packages + operating-system-from-promoted-native-build-result promote-native-build-result operating-system-closure-spec operating-system-install-spec diff --git a/modules/fruix/system/freebsd/build.scm b/modules/fruix/system/freebsd/build.scm index b7dc4b9..6d87162 100644 --- a/modules/fruix/system/freebsd/build.scm +++ b/modules/fruix/system/freebsd/build.scm @@ -11,6 +11,14 @@ #:use-module (srfi srfi-1) #:use-module (srfi srfi-13) #:export (host-freebsd-provenance + promoted-native-build-result + promoted-native-build-result->freebsd-base + promoted-native-build-result-artifact-store + promoted-native-build-result-kernel-package + promoted-native-build-result-bootloader-package + promoted-native-build-result-base-packages + promoted-native-build-result-development-packages + operating-system-from-promoted-native-build-result materialize-freebsd-package promote-native-build-result materialize-prefix)) @@ -341,53 +349,55 @@ (append overrides plan))) (define* (materialize-freebsd-package package store-dir cache #:optional source-cache) - (let* ((source-cache (or source-cache (make-hash-table))) - (input-paths (map (lambda (input) - (materialize-freebsd-package input store-dir cache source-cache)) - (freebsd-package-inputs package))) - (prepared-package - (if (freebsd-native-build-package? package) - (let* ((source (plan-freebsd-source (freebsd-package-install-plan package))) - (source-result (materialize-freebsd-source/cached source store-dir source-cache)) - (plan (plan-with-materialized-source (freebsd-package-install-plan package) - source-result))) - (package-with-install-plan package plan)) - package)) - (effective-input-paths - (if (freebsd-native-build-package? package) - (cons (build-plan-ref (freebsd-package-install-plan prepared-package) - 'materialized-source-store - #f) - input-paths) - input-paths)) - (effective-input-paths (filter identity effective-input-paths)) - (manifest (package-manifest-string prepared-package effective-input-paths)) - (cache-key (sha256-string manifest)) - (cached (hash-ref cache cache-key #f))) - (if cached - cached - (let* ((display-name (string-append (freebsd-package-name prepared-package) - "-" - (freebsd-package-version prepared-package))) - (output-path (make-store-path store-dir display-name manifest - #:kind 'freebsd-package))) - (unless (file-exists? output-path) - (case (freebsd-package-build-system prepared-package) - ((copy-build-system) - (mkdir-p output-path) - (for-each (lambda (entry) - (materialize-plan-entry output-path entry)) - (freebsd-package-install-plan prepared-package)) - (write-file (string-append output-path "/.references") - (string-join effective-input-paths "\n")) - (write-file (string-append output-path "/.fruix-package") manifest)) - ((freebsd-world-build-system freebsd-kernel-build-system) - (materialize-native-freebsd-package prepared-package effective-input-paths manifest output-path)) - (else - (error (format #f "unsupported package build system: ~a" - (freebsd-package-build-system prepared-package)))))) - (hash-set! cache cache-key output-path) - output-path)))) + (if (existing-store-package? package) + (validate-existing-store-package package) + (let* ((source-cache (or source-cache (make-hash-table))) + (input-paths (map (lambda (input) + (materialize-freebsd-package input store-dir cache source-cache)) + (freebsd-package-inputs package))) + (prepared-package + (if (freebsd-native-build-package? package) + (let* ((source (plan-freebsd-source (freebsd-package-install-plan package))) + (source-result (materialize-freebsd-source/cached source store-dir source-cache)) + (plan (plan-with-materialized-source (freebsd-package-install-plan package) + source-result))) + (package-with-install-plan package plan)) + package)) + (effective-input-paths + (if (freebsd-native-build-package? package) + (cons (build-plan-ref (freebsd-package-install-plan prepared-package) + 'materialized-source-store + #f) + input-paths) + input-paths)) + (effective-input-paths (filter identity effective-input-paths)) + (manifest (package-manifest-string prepared-package effective-input-paths)) + (cache-key (sha256-string manifest)) + (cached (hash-ref cache cache-key #f))) + (if cached + cached + (let* ((display-name (string-append (freebsd-package-name prepared-package) + "-" + (freebsd-package-version prepared-package))) + (output-path (make-store-path store-dir display-name manifest + #:kind 'freebsd-package))) + (unless (file-exists? output-path) + (case (freebsd-package-build-system prepared-package) + ((copy-build-system) + (mkdir-p output-path) + (for-each (lambda (entry) + (materialize-plan-entry output-path entry)) + (freebsd-package-install-plan prepared-package)) + (write-file (string-append output-path "/.references") + (string-join effective-input-paths "\n")) + (write-file (string-append output-path "/.fruix-package") manifest)) + ((freebsd-world-build-system freebsd-kernel-build-system) + (materialize-native-freebsd-package prepared-package effective-input-paths manifest output-path)) + (else + (error (format #f "unsupported package build system: ~a" + (freebsd-package-build-system prepared-package)))))) + (hash-set! cache cache-key output-path) + output-path))))) (define native-build-result-promotion-version "1") @@ -433,6 +443,237 @@ (error "unsupported native build result promotion version" promotion-file)) result))) +(define existing-store-package-build-system 'existing-store-item-build-system) + +(define (existing-store-package? package) + (eq? (freebsd-package-build-system package) + existing-store-package-build-system)) + +(define (existing-store-package-ref package key default) + (build-plan-ref (freebsd-package-install-plan package) key default)) + +(define (validate-existing-store-package package) + (let* ((store-path (existing-store-package-ref package 'store-path #f)) + (required-file (existing-store-package-ref package 'required-file #f)) + (metadata-file (existing-store-package-ref package 'metadata-file #f))) + (unless (and (string? store-path) (file-exists? store-path)) + (error "existing-store package is missing a valid store path" package store-path)) + (when metadata-file + (unless (file-exists? metadata-file) + (error "existing-store package metadata file is missing" package metadata-file))) + (when required-file + (unless (file-exists? (string-append store-path "/" required-file)) + (error "existing-store package is missing required file" + package + (string-append store-path "/" required-file)))) + store-path)) + +(define (normalize-promoted-native-build-result value) + (cond + ((promoted-native-build-result? value) + value) + ((string? value) + (promoted-native-build-result #:store-path value)) + (else + (error "expected a promoted native build result or store path" value)))) + +(define (read-promoted-native-build-artifact-metadata metadata-file) + (unless (file-exists? metadata-file) + (error "promoted native build artifact metadata file is missing" metadata-file)) + (let ((metadata (call-with-input-file metadata-file read))) + (unless (equal? (native-build-result-ref metadata 'native-build-object-version #f) + native-build-result-promotion-version) + (error "unsupported promoted native build object version" metadata-file)) + (unless (eq? (native-build-result-ref metadata 'object-kind #f) 'artifact) + (error "promoted native build object is not an artifact" metadata-file)) + metadata)) + +(define (promoted-native-build-result-artifact-entry result artifact-kind) + (let* ((metadata (promoted-native-build-result-metadata result)) + (artifacts (native-build-result-ref metadata 'artifacts '())) + (entry (find (lambda (item) + (eq? (native-build-result-ref item 'artifact-kind #f) + artifact-kind)) + artifacts))) + (unless entry + (error "promoted native build result is missing artifact entry" artifact-kind)) + entry)) + +(define (promoted-native-build-result-artifact-store result artifact-kind) + (let* ((result (normalize-promoted-native-build-result result)) + (entry (promoted-native-build-result-artifact-entry result artifact-kind)) + (store-path (native-build-result-ref entry 'store-path #f)) + (metadata-file (native-build-result-ref entry 'metadata-file #f)) + (artifact-metadata (and metadata-file + (read-promoted-native-build-artifact-metadata metadata-file))) + (required-file (and artifact-metadata + (native-build-result-ref artifact-metadata 'required-file #f)))) + (unless (and (string? store-path) (file-exists? store-path)) + (error "promoted native build result is missing artifact store" artifact-kind store-path)) + (when artifact-metadata + (unless (eq? (native-build-result-ref artifact-metadata 'artifact-kind #f) + artifact-kind) + (error "promoted native build artifact metadata kind mismatch" + artifact-kind + metadata-file))) + (when required-file + (unless (file-exists? (string-append store-path "/" required-file)) + (error "promoted native build artifact store is missing required file" + artifact-kind + (string-append store-path "/" required-file)))) + store-path)) + +(define* (promoted-native-build-result #:key store-path) + (unless (and (string? store-path) (file-exists? store-path)) + (error "promoted native build result store path does not exist" store-path)) + (let* ((metadata-file (string-append store-path "/.fruix-native-build-result.scm"))) + (unless (file-exists? metadata-file) + (error "promoted native build result store is missing metadata" metadata-file)) + (let* ((metadata (call-with-input-file metadata-file read)) + (result (make-promoted-native-build-result store-path metadata-file metadata))) + (unless (equal? (native-build-result-ref metadata 'native-build-result-version #f) + native-build-result-promotion-version) + (error "unsupported promoted native build result version" metadata-file)) + (unless (eq? (native-build-result-ref metadata 'object-kind #f) 'result-bundle) + (error "promoted native build result store does not contain a result bundle" metadata-file)) + (for-each (lambda (artifact-kind) + (promoted-native-build-result-artifact-store result artifact-kind)) + '(world kernel headers bootloader)) + result))) + +(define (promoted-native-build-result->freebsd-base result) + (let* ((result (normalize-promoted-native-build-result result)) + (metadata (promoted-native-build-result-metadata result)) + (base (native-build-result-ref metadata 'freebsd-base '())) + (source (native-build-result-ref metadata 'source '())) + (source-root (or (native-build-result-ref source 'source-root #f) + (native-build-result-ref base 'source-root #f) + "/usr/src")) + (source-name (string-append "promoted-native-build-result-source-" + (path-basename + (promoted-native-build-result-store-path result)))) + (synthetic-source (freebsd-source #:name source-name + #:kind 'local-tree + #:path source-root))) + (freebsd-base #:name (native-build-result-ref base 'name "promoted-native-build-result") + #:version-label (native-build-result-ref base 'version-label "unknown") + #:release (native-build-result-ref base 'release "unknown") + #:branch (native-build-result-ref base 'branch "unknown") + #:source synthetic-source + #:source-root (native-build-result-ref base 'source-root source-root) + #:target (native-build-result-ref base 'target "amd64") + #:target-arch (native-build-result-ref base 'target-arch "amd64") + #:kernconf (native-build-result-ref base 'kernconf "GENERIC")))) + +(define (promoted-native-build-result-artifact-package result artifact-kind + package-name synopsis description) + (let* ((result (normalize-promoted-native-build-result result)) + (metadata (promoted-native-build-result-metadata result)) + (base (native-build-result-ref metadata 'freebsd-base '())) + (entry (promoted-native-build-result-artifact-entry result artifact-kind)) + (store-path (promoted-native-build-result-artifact-store result artifact-kind)) + (metadata-file (native-build-result-ref entry 'metadata-file #f)) + (artifact-metadata (and metadata-file + (read-promoted-native-build-artifact-metadata metadata-file))) + (required-file (and artifact-metadata + (native-build-result-ref artifact-metadata 'required-file #f)))) + (freebsd-package + #:name package-name + #:version (native-build-result-ref base 'version-label "unknown") + #:build-system existing-store-package-build-system + #:home-page "https://www.freebsd.org/" + #:synopsis synopsis + #:description description + #:license 'bsd-2 + #:install-plan `((store-path . ,store-path) + (metadata-file . ,metadata-file) + (required-file . ,required-file) + (artifact-kind . ,artifact-kind) + (result-store . ,(promoted-native-build-result-store-path result)))))) + +(define (promoted-native-build-result-world-package result) + (promoted-native-build-result-artifact-package + result + 'world + "freebsd-promoted-world" + "Promoted Fruix-native FreeBSD world artifact" + "FreeBSD world artifact imported from a promoted Fruix native-build result bundle.")) + +(define (promoted-native-build-result-kernel-package result) + (promoted-native-build-result-artifact-package + result + 'kernel + "freebsd-promoted-kernel" + "Promoted Fruix-native FreeBSD kernel artifact" + "FreeBSD kernel artifact imported from a promoted Fruix native-build result bundle.")) + +(define (promoted-native-build-result-bootloader-package result) + (promoted-native-build-result-artifact-package + result + 'bootloader + "freebsd-promoted-bootloader" + "Promoted Fruix-native FreeBSD bootloader artifact" + "FreeBSD bootloader artifact imported from a promoted Fruix native-build result bundle.")) + +(define (promoted-native-build-result-headers-package result) + (promoted-native-build-result-artifact-package + result + 'headers + "freebsd-promoted-headers" + "Promoted Fruix-native FreeBSD headers artifact" + "FreeBSD headers artifact imported from a promoted Fruix native-build result bundle.")) + +(define (promoted-native-build-result-base-packages result) + (list (promoted-native-build-result-world-package result))) + +(define (promoted-native-build-result-development-packages result) + (list (promoted-native-build-result-headers-package result))) + +(define* (operating-system-from-promoted-native-build-result result + #:key + (host-name #f) + (freebsd-base #f) + (kernel #f) + (bootloader #f) + (base-packages #f) + (development-packages #f) + (users #f) + (groups #f) + (file-systems #f) + (services #f) + (loader-entries #f) + (rc-conf-entries #f) + (init-mode #f) + (ready-marker #f) + (root-authorized-keys #f)) + (let* ((result (normalize-promoted-native-build-result result)) + (defaults default-minimal-operating-system) + (fallback (lambda (value thunk) + (if (eq? value #f) (thunk) value)))) + (operating-system + #:host-name (fallback host-name (lambda () (operating-system-host-name defaults))) + #:freebsd-base (fallback freebsd-base (lambda () + (promoted-native-build-result->freebsd-base result))) + #:native-build-result result + #:kernel (fallback kernel (lambda () + (promoted-native-build-result-kernel-package result))) + #:bootloader (fallback bootloader (lambda () + (promoted-native-build-result-bootloader-package result))) + #:base-packages (fallback base-packages (lambda () + (promoted-native-build-result-base-packages result))) + #:development-packages (fallback development-packages (lambda () + (operating-system-development-packages defaults))) + #:users (fallback users (lambda () (operating-system-users defaults))) + #:groups (fallback groups (lambda () (operating-system-groups defaults))) + #:file-systems (fallback file-systems (lambda () (operating-system-file-systems defaults))) + #:services (fallback services (lambda () (operating-system-services defaults))) + #:loader-entries (fallback loader-entries (lambda () (operating-system-loader-entries defaults))) + #:rc-conf-entries (fallback rc-conf-entries (lambda () (operating-system-rc-conf-entries defaults))) + #:init-mode (fallback init-mode (lambda () (operating-system-init-mode defaults))) + #:ready-marker (fallback ready-marker (lambda () (operating-system-ready-marker defaults))) + #:root-authorized-keys (fallback root-authorized-keys (lambda () + (operating-system-root-authorized-keys defaults)))))) + (define (native-build-artifact-entry result artifact-kind) (let* ((artifacts (native-build-result-ref result 'artifacts '())) (entry (assoc artifact-kind artifacts))) diff --git a/modules/fruix/system/freebsd/media.scm b/modules/fruix/system/freebsd/media.scm index 1342306..0ff0e19 100644 --- a/modules/fruix/system/freebsd/media.scm +++ b/modules/fruix/system/freebsd/media.scm @@ -76,6 +76,7 @@ (validate-operating-system os) (let* ((cache (make-hash-table)) (source-cache (make-hash-table)) + (native-build-result (operating-system-native-build-result os)) (kernel-package (operating-system-kernel os)) (bootloader-package (operating-system-bootloader os)) (base-packages (operating-system-base-packages os)) @@ -134,31 +135,53 @@ (delete-duplicates (map (lambda (result) (assoc-ref result 'source-store-path)) source-materializations))) + (promoted-native-build-result-summary + (and native-build-result + (promoted-native-build-result-spec native-build-result))) + (promoted-native-build-result-store + (and native-build-result + (promoted-native-build-result-store-path native-build-result))) + (promoted-native-build-artifact-stores + (delete-duplicates + (filter identity + (if native-build-result + (map (lambda (artifact-kind) + (promoted-native-build-result-artifact-store native-build-result artifact-kind)) + '(world kernel headers bootloader)) + '())))) (metadata-files - `(("metadata/freebsd-base.scm" - . ,(object->string (freebsd-base-spec (operating-system-freebsd-base os)))) - ("metadata/freebsd-source.scm" - . ,(object->string (freebsd-source-spec (freebsd-base-source (operating-system-freebsd-base os))))) - ("metadata/freebsd-source-materializations.scm" - . ,(object->string (map freebsd-source-materialization-spec source-materializations))) - ("metadata/host-base-provenance.scm" - . ,(object->string (host-freebsd-provenance))) - ("metadata/store-layout.scm" - . ,(object->string - `((freebsd-base . ,(freebsd-base-spec (operating-system-freebsd-base os))) - (freebsd-source . ,(freebsd-source-spec (freebsd-base-source (operating-system-freebsd-base os)))) - (materialized-source-store-count . ,(length materialized-source-stores)) - (materialized-source-stores . ,materialized-source-stores) - (host-base-store-count . ,(length host-base-stores)) - (host-base-stores . ,host-base-stores) - (native-base-store-count . ,(length native-base-stores)) - (native-base-stores . ,native-base-stores) - (development-package-store-count . ,(length development-package-stores)) - (development-package-stores . ,development-package-stores) - (fruix-runtime-store-count . ,(length fruix-runtime-stores)) - (fruix-runtime-stores . ,fruix-runtime-stores) - (host-base-replacement-order . ,%freebsd-host-staged-replacement-order) - (init-mode . ,(operating-system-init-mode os))))))) + (append + (list (cons "metadata/freebsd-base.scm" + (object->string (freebsd-base-spec (operating-system-freebsd-base os)))) + (cons "metadata/freebsd-source.scm" + (object->string (freebsd-source-spec (freebsd-base-source (operating-system-freebsd-base os))))) + (cons "metadata/freebsd-source-materializations.scm" + (object->string (map freebsd-source-materialization-spec source-materializations))) + (cons "metadata/host-base-provenance.scm" + (object->string (host-freebsd-provenance))) + (cons "metadata/store-layout.scm" + (object->string + `((freebsd-base . ,(freebsd-base-spec (operating-system-freebsd-base os))) + (freebsd-source . ,(freebsd-source-spec (freebsd-base-source (operating-system-freebsd-base os)))) + (promoted-native-build-result . ,promoted-native-build-result-summary) + (promoted-native-build-artifact-store-count . ,(length promoted-native-build-artifact-stores)) + (promoted-native-build-artifact-stores . ,promoted-native-build-artifact-stores) + (materialized-source-store-count . ,(length materialized-source-stores)) + (materialized-source-stores . ,materialized-source-stores) + (host-base-store-count . ,(length host-base-stores)) + (host-base-stores . ,host-base-stores) + (native-base-store-count . ,(length native-base-stores)) + (native-base-stores . ,native-base-stores) + (development-package-store-count . ,(length development-package-stores)) + (development-package-stores . ,development-package-stores) + (fruix-runtime-store-count . ,(length fruix-runtime-stores)) + (fruix-runtime-stores . ,fruix-runtime-stores) + (host-base-replacement-order . ,%freebsd-host-staged-replacement-order) + (init-mode . ,(operating-system-init-mode os)))))) + (if promoted-native-build-result-summary + (list (cons "metadata/promoted-native-build-result.scm" + (object->string promoted-native-build-result-summary))) + '()))) (generated-files (append (operating-system-generated-files os #:guile-store guile-store #:guile-extra-store guile-extra-store @@ -169,7 +192,10 @@ ("usr/local/etc/rc.d/fruix-shepherd" . ,(render-rc-script shepherd-store guile-store guile-extra-store))))) (references (delete-duplicates - (append materialized-source-stores + (append (if promoted-native-build-result-store + (list promoted-native-build-result-store) + '()) + materialized-source-stores host-base-stores native-base-stores development-package-stores @@ -252,6 +278,9 @@ (materialized-source-stores . ,materialized-source-stores) (host-base-provenance-file . ,(string-append closure-path "/metadata/host-base-provenance.scm")) (store-layout-file . ,(string-append closure-path "/metadata/store-layout.scm")) + (promoted-native-build-result-file + . ,(and promoted-native-build-result-summary + (string-append closure-path "/metadata/promoted-native-build-result.scm"))) (generated-files . ,(map car generated-files)) (references . ,references)))) diff --git a/modules/fruix/system/freebsd/model.scm b/modules/fruix/system/freebsd/model.scm index b5b1248..151fed3 100644 --- a/modules/fruix/system/freebsd/model.scm +++ b/modules/fruix/system/freebsd/model.scm @@ -26,10 +26,17 @@ file-system-type file-system-options file-system-needed-for-boot? + make-promoted-native-build-result + promoted-native-build-result? + promoted-native-build-result-store-path + promoted-native-build-result-metadata-file + promoted-native-build-result-metadata + promoted-native-build-result-spec operating-system operating-system? operating-system-host-name operating-system-freebsd-base + operating-system-native-build-result operating-system-kernel operating-system-bootloader operating-system-base-packages @@ -95,13 +102,66 @@ (needed-for-boot? #f)) (make-file-system device mount-point type options needed-for-boot?)) +(define-record-type + (make-promoted-native-build-result store-path metadata-file metadata) + promoted-native-build-result? + (store-path promoted-native-build-result-store-path) + (metadata-file promoted-native-build-result-metadata-file) + (metadata promoted-native-build-result-metadata)) + +(define (promoted-native-build-result-metadata-ref metadata key default) + (match (assoc key metadata) + ((_ . value) value) + (#f default))) + +(define (promoted-native-build-result-artifact-spec metadata artifact-kind) + (find (lambda (entry) + (eq? (promoted-native-build-result-metadata-ref entry 'artifact-kind #f) + artifact-kind)) + (promoted-native-build-result-metadata-ref metadata 'artifacts '()))) + +(define (promoted-native-build-result-spec result) + (let* ((metadata (promoted-native-build-result-metadata result)) + (base (promoted-native-build-result-metadata-ref metadata 'freebsd-base '())) + (source (promoted-native-build-result-metadata-ref metadata 'source '()))) + `((store-path . ,(promoted-native-build-result-store-path result)) + (metadata-file . ,(promoted-native-build-result-metadata-file result)) + (executor-kind . ,(promoted-native-build-result-metadata-ref metadata 'executor-kind #f)) + (executor-name . ,(promoted-native-build-result-metadata-ref metadata 'executor-name #f)) + (executor-version . ,(promoted-native-build-result-metadata-ref metadata 'executor-version #f)) + (run-id . ,(promoted-native-build-result-metadata-ref metadata 'run-id #f)) + (version-label . ,(promoted-native-build-result-metadata-ref base 'version-label #f)) + (release . ,(promoted-native-build-result-metadata-ref base 'release #f)) + (branch . ,(promoted-native-build-result-metadata-ref base 'branch #f)) + (source-store . ,(promoted-native-build-result-metadata-ref source 'store-path #f)) + (source-root . ,(promoted-native-build-result-metadata-ref source 'source-root #f)) + (artifact-count . ,(promoted-native-build-result-metadata-ref metadata 'artifact-count 0)) + (world-store . ,(promoted-native-build-result-metadata-ref + (promoted-native-build-result-artifact-spec metadata 'world) + 'store-path + #f)) + (kernel-store . ,(promoted-native-build-result-metadata-ref + (promoted-native-build-result-artifact-spec metadata 'kernel) + 'store-path + #f)) + (headers-store . ,(promoted-native-build-result-metadata-ref + (promoted-native-build-result-artifact-spec metadata 'headers) + 'store-path + #f)) + (bootloader-store . ,(promoted-native-build-result-metadata-ref + (promoted-native-build-result-artifact-spec metadata 'bootloader) + 'store-path + #f))))) + (define-record-type - (make-operating-system host-name freebsd-base kernel bootloader base-packages development-packages - users groups file-systems services loader-entries rc-conf-entries - init-mode ready-marker root-authorized-keys) + (make-operating-system host-name freebsd-base native-build-result kernel bootloader + base-packages development-packages users groups file-systems + services loader-entries rc-conf-entries init-mode ready-marker + root-authorized-keys) operating-system? (host-name operating-system-host-name) (freebsd-base operating-system-freebsd-base) + (native-build-result operating-system-native-build-result) (kernel operating-system-kernel) (bootloader operating-system-bootloader) (base-packages operating-system-base-packages) @@ -119,6 +179,7 @@ (define* (operating-system #:key (host-name "fruix-freebsd") (freebsd-base %default-freebsd-base) + (native-build-result #f) (kernel freebsd-kernel) (bootloader freebsd-bootloader) (base-packages %freebsd-system-packages) @@ -164,9 +225,10 @@ (init-mode 'freebsd-init+rc.d-shepherd) (ready-marker "/var/lib/fruix/ready") (root-authorized-keys '())) - (make-operating-system host-name freebsd-base kernel bootloader base-packages development-packages - users groups file-systems services loader-entries rc-conf-entries - init-mode ready-marker root-authorized-keys)) + (make-operating-system host-name freebsd-base native-build-result kernel bootloader + base-packages development-packages users groups file-systems + services loader-entries rc-conf-entries init-mode ready-marker + root-authorized-keys)) (define default-minimal-operating-system (operating-system)) @@ -234,6 +296,7 @@ (define (validate-operating-system os) (let* ((host-name (operating-system-host-name os)) (base (operating-system-freebsd-base os)) + (native-build-result (operating-system-native-build-result os)) (base-packages (operating-system-base-packages os)) (development-packages (operating-system-development-packages os)) (users (operating-system-users os)) @@ -247,6 +310,9 @@ (error "operating-system host-name must not be empty")) (unless (freebsd-base? base) (error "operating-system freebsd-base must be a record")) + (when native-build-result + (unless (promoted-native-build-result? native-build-result) + (error "operating-system native-build-result must be a record"))) (unless (every freebsd-package? base-packages) (error "operating-system base-packages must be a list of records")) (unless (every freebsd-package? development-packages) @@ -306,6 +372,9 @@ "activate" "shepherd/init.scm" "usr/local/bin/fruix") + (if (operating-system-native-build-result os) + '("metadata/promoted-native-build-result.scm") + '()) (if (null? (operating-system-development-packages os)) '() '("usr/local/bin/fruix-development-environment" @@ -325,6 +394,10 @@ (validate-operating-system os) `((host-name . ,(operating-system-host-name os)) (freebsd-base . ,(freebsd-base-spec (operating-system-freebsd-base os))) + (promoted-native-build-result + . ,(and (operating-system-native-build-result os) + (promoted-native-build-result-spec + (operating-system-native-build-result os)))) (kernel-package . ,(freebsd-package-name (operating-system-kernel os))) (bootloader-package . ,(freebsd-package-name (operating-system-bootloader os))) (base-package-count . ,(length (operating-system-base-packages os))) diff --git a/tests/system/phase20-promoted-native-base-operating-system.scm.in b/tests/system/phase20-promoted-native-base-operating-system.scm.in new file mode 100644 index 0000000..91d15f6 --- /dev/null +++ b/tests/system/phase20-promoted-native-base-operating-system.scm.in @@ -0,0 +1,73 @@ +(use-modules (fruix system freebsd) + (fruix packages freebsd)) + +(define phase20-promoted-native-build-result + (promoted-native-build-result + #:store-path "__PROMOTED_RESULT_STORE__")) + +(define phase20-promoted-native-base-operating-system + (operating-system-from-promoted-native-build-result + phase20-promoted-native-build-result + #:host-name "fruix-freebsd" + #:groups (list (user-group #:name "wheel" #:gid 0 #:system? #t) + (user-group #:name "sshd" #:gid 22 #:system? #t) + (user-group #:name "_dhcp" #:gid 65 #:system? #t) + (user-group #:name "operator" #:gid 1000 #:system? #f)) + #:users (list (user-account #:name "root" + #:uid 0 + #:group "wheel" + #:comment "Charlie &" + #:home "/root" + #:shell "/bin/sh" + #:system? #t) + (user-account #:name "sshd" + #:uid 22 + #:group "sshd" + #:comment "Secure Shell Daemon" + #:home "/var/empty" + #:shell "/usr/sbin/nologin" + #:system? #t) + (user-account #:name "_dhcp" + #:uid 65 + #:group "_dhcp" + #:comment "dhcp programs" + #:home "/var/empty" + #:shell "/usr/sbin/nologin" + #:system? #t) + (user-account #:name "operator" + #:uid 1000 + #:group "operator" + #:supplementary-groups '("wheel") + #:comment "Fruix Operator" + #:home "/home/operator" + #:shell "/bin/sh" + #:system? #f)) + #:file-systems (list (file-system #:device "/dev/gpt/fruix-root" + #:mount-point "/" + #:type "ufs" + #:options "rw" + #:needed-for-boot? #t) + (file-system #:device "devfs" + #:mount-point "/dev" + #:type "devfs" + #:options "rw" + #:needed-for-boot? #t) + (file-system #:device "tmpfs" + #:mount-point "/tmp" + #:type "tmpfs" + #:options "rw,size=64m")) + #:services '(shepherd ready-marker sshd) + #:loader-entries '(("autoboot_delay" . "1") + ("boot_multicons" . "YES") + ("boot_serial" . "YES") + ("console" . "comconsole,vidconsole")) + #:rc-conf-entries '(("clear_tmp_enable" . "NO") + ("hostid_enable" . "NO") + ("sendmail_enable" . "NONE") + ("sshd_enable" . "YES") + ("ifconfig_xn0" . "SYNCDHCP") + ("ifconfig_em0" . "SYNCDHCP") + ("ifconfig_vtnet0" . "SYNCDHCP")) + #:init-mode 'shepherd-pid1 + #:ready-marker "/var/lib/fruix/ready" + #:root-authorized-keys '("__ROOT_AUTHORIZED_KEY__"))) diff --git a/tests/system/run-phase20-promoted-native-base-declaration-xcpng.sh b/tests/system/run-phase20-promoted-native-base-declaration-xcpng.sh new file mode 100755 index 0000000..123efac --- /dev/null +++ b/tests/system/run-phase20-promoted-native-base-declaration-xcpng.sh @@ -0,0 +1,227 @@ +#!/bin/sh +set -eu + +repo_root=${PROJECT_ROOT:-$(pwd)} +os_template=${OS_TEMPLATE:-$repo_root/tests/system/phase20-promoted-native-base-operating-system.scm.in} +system_name=${SYSTEM_NAME:-phase20-promoted-native-base-operating-system} +root_size=${ROOT_SIZE:-12g} +result_store=${RESULT_STORE:-} +metadata_target=${METADATA_OUT:-} +root_authorized_key_file=${ROOT_AUTHORIZED_KEY_FILE:-$HOME/.ssh/id_ed25519.pub} +root_ssh_private_key_file=${ROOT_SSH_PRIVATE_KEY_FILE:-$HOME/.ssh/id_ed25519} +cleanup=0 + +if [ -n "${WORKDIR:-}" ]; then + workdir=$WORKDIR + mkdir -p "$workdir" +else + workdir=$(mktemp -d /tmp/fruix-phase20-promoted-native-base-xcpng.XXXXXX) + cleanup=1 +fi +if [ "${KEEP_WORKDIR:-0}" -eq 1 ]; then + cleanup=0 +fi + +promotion_metadata=$workdir/phase20-promoted-native-base-promotion-metadata.txt +prepared_template=$workdir/phase20-promoted-native-base-operating-system.scm.in +inner_metadata=$workdir/phase20-promoted-native-base-inner-metadata.txt +metadata_file=$workdir/phase20-promoted-native-base-declaration-xcpng-metadata.txt + +cleanup_workdir() { + if [ "$cleanup" -eq 1 ]; then + rm -rf "$workdir" + fi +} +trap cleanup_workdir EXIT INT TERM + +if [ -z "$result_store" ]; then + KEEP_WORKDIR=1 WORKDIR="$workdir/promotion" METADATA_OUT="$promotion_metadata" \ + ROOT_AUTHORIZED_KEY_FILE="$root_authorized_key_file" \ + ROOT_SSH_PRIVATE_KEY_FILE="$root_ssh_private_key_file" \ + ROOT_SIZE=20g \ + "$repo_root/tests/system/run-phase20-host-initiated-native-build-store-promotion-xcpng.sh" + result_store=$(sed -n 's/^result_store=//p' "$promotion_metadata") +fi + +[ -n "$result_store" ] || { echo "missing promoted result store" >&2; exit 1; } +[ -d "$result_store" ] || { echo "promoted result store does not exist: $result_store" >&2; exit 1; } +[ -f "$result_store/.fruix-native-build-result.scm" ] || { + echo "promoted result store is missing .fruix-native-build-result.scm" >&2 + exit 1 +} +[ -L "$result_store/artifacts/world" ] || { echo "promoted result store is missing world artifact link" >&2; exit 1; } +[ -L "$result_store/artifacts/kernel" ] || { echo "promoted result store is missing kernel artifact link" >&2; exit 1; } +[ -L "$result_store/artifacts/headers" ] || { echo "promoted result store is missing headers artifact link" >&2; exit 1; } +[ -L "$result_store/artifacts/bootloader" ] || { echo "promoted result store is missing bootloader artifact link" >&2; exit 1; } + +world_store=$(readlink "$result_store/artifacts/world") +kernel_store=$(readlink "$result_store/artifacts/kernel") +headers_store=$(readlink "$result_store/artifacts/headers") +bootloader_store=$(readlink "$result_store/artifacts/bootloader") +result_metadata_file=$result_store/.fruix-native-build-result.scm +executor_kind=$(grep -o '(executor-kind \. [^)]*)' "$result_metadata_file" | head -n 1 | sed 's/(executor-kind \. //; s/)$//') +executor_name=$(grep -o '(executor-name \. "[^"]*")' "$result_metadata_file" | head -n 1 | sed 's/(executor-name \. "//; s/")$//') +executor_version=$(grep -o '(executor-version \. "[^"]*")' "$result_metadata_file" | head -n 1 | sed 's/(executor-version \. "//; s/")$//') + +[ -f "$world_store/bin/sh" ] || { echo "promoted world store is missing /bin/sh" >&2; exit 1; } +[ -f "$kernel_store/boot/kernel/kernel" ] || { echo "promoted kernel store is missing kernel" >&2; exit 1; } +[ -f "$headers_store/usr/include/sys/param.h" ] || { echo "promoted headers store is missing param.h" >&2; exit 1; } +[ -f "$bootloader_store/boot/loader.efi" ] || { echo "promoted bootloader store is missing loader.efi" >&2; exit 1; } + +sed "s|__PROMOTED_RESULT_STORE__|$result_store|g" "$os_template" > "$prepared_template" + +action_metadata=${promotion_metadata:-} +ROOT_SIZE="$root_size" KEEP_WORKDIR=1 WORKDIR="$workdir/boot" METADATA_OUT="$inner_metadata" \ + ROOT_AUTHORIZED_KEY_FILE="$root_authorized_key_file" \ + ROOT_SSH_PRIVATE_KEY_FILE="$root_ssh_private_key_file" \ + OS_TEMPLATE="$prepared_template" SYSTEM_NAME="$system_name" \ + "$repo_root/tests/system/run-phase11-shepherd-pid1-xcpng.sh" + +phase8_metadata=$(sed -n 's/^phase8_metadata=//p' "$inner_metadata") +closure_path=$(sed -n 's/^closure_path=//p' "$inner_metadata") +closure_base=$(sed -n 's/^closure_base=//p' "$inner_metadata") +guest_ip=$(sed -n 's/^guest_ip=//p' "$inner_metadata") +vm_id=$(sed -n 's/^vm_id=//p' "$inner_metadata") +vdi_id=$(sed -n 's/^vdi_id=//p' "$inner_metadata") +shepherd_pid=$(sed -n 's/^shepherd_pid=//p' "$inner_metadata") +sshd_status=$(sed -n 's/^sshd_status=//p' "$inner_metadata") +compat_prefix_shims=$(sed -n 's/^compat_prefix_shims=//p' "$inner_metadata") +guile_module_smoke=$(sed -n 's/^guile_module_smoke=//p' "$inner_metadata") +activate_log=$(sed -n 's/^activate_log=//p' "$inner_metadata") + +promoted_result_file=$closure_path/metadata/promoted-native-build-result.scm +store_layout_file=$closure_path/metadata/store-layout.scm + +[ "$shepherd_pid" = 1 ] || { echo "shepherd was not PID 1" >&2; exit 1; } +[ "$sshd_status" = running ] || { echo "sshd is not running" >&2; exit 1; } +[ "$compat_prefix_shims" = absent ] || { echo "compatibility prefix shims reappeared" >&2; exit 1; } +[ "$guile_module_smoke" = ok ] || { echo "guest Guile module smoke failed" >&2; exit 1; } +case "$activate_log" in + *fruix-activate:done*) : ;; + *) echo "activation log does not show success" >&2; exit 1 ;; +esac + +[ -f "$promoted_result_file" ] || { echo "closure is missing promoted native build result metadata" >&2; exit 1; } +[ -f "$store_layout_file" ] || { echo "closure is missing store layout metadata" >&2; exit 1; } +grep -F "$result_store" "$promoted_result_file" >/dev/null || { + echo "closure promoted result metadata does not reference the selected result store" >&2 + exit 1 +} +grep -F "$result_store" "$closure_path/.references" >/dev/null || { + echo "closure references do not retain the promoted result store" >&2 + exit 1 +} +grep -F "$kernel_store" "$promoted_result_file" >/dev/null || { + echo "closure promoted result metadata does not reference the promoted kernel store" >&2 + exit 1 +} +grep -F "$bootloader_store" "$promoted_result_file" >/dev/null || { + echo "closure promoted result metadata does not reference the promoted bootloader store" >&2 + exit 1 +} +grep -F "$result_store" "$store_layout_file" >/dev/null || { + echo "store layout metadata does not record the promoted result store" >&2 + exit 1 +} + +kernel_link=$(readlink "$closure_path/boot/kernel/kernel") +bootloader_link=$(readlink "$closure_path/boot/loader.efi") +[ "$kernel_link" = "$kernel_store/boot/kernel/kernel" ] || { + echo "closure kernel link does not target the promoted kernel store" >&2 + exit 1 +} +[ "$bootloader_link" = "$bootloader_store/boot/loader.efi" ] || { + echo "closure bootloader link does not target the promoted bootloader store" >&2 + exit 1 +} + +ssh_guest() { + ssh -i "$root_ssh_private_key_file" \ + -o BatchMode=yes \ + -o StrictHostKeyChecking=no \ + -o UserKnownHostsFile=/dev/null \ + -o ConnectTimeout=5 \ + root@"$guest_ip" "$@" +} + +guest_promoted_metadata=$(ssh -i "$root_ssh_private_key_file" \ + -o BatchMode=yes \ + -o StrictHostKeyChecking=no \ + -o UserKnownHostsFile=/dev/null \ + -o ConnectTimeout=5 \ + root@"$guest_ip" 'sh -s' </dev/null +grep -F '$result_store' /run/current-system/.references >/dev/null +kernel_link=$(readlink /run/current-system/boot/kernel/kernel) +bootloader_link=$(readlink /run/current-system/boot/loader.efi) +[ "$kernel_link" = "$kernel_store/boot/kernel/kernel" ] +[ "$bootloader_link" = "$bootloader_store/boot/loader.efi" ] +[ -x /bin/sh ] +[ -x /usr/sbin/sshd ] +printf 'kernel_link=%s\n' "$kernel_link" +printf 'bootloader_link=%s\n' "$bootloader_link" +printf 'uname=%s\n' "$(uname -sr)" +printf 'promoted_result_store=%s\n' '$result_store' +EOF +) + +guest_kernel_link=$(printf '%s\n' "$guest_promoted_metadata" | sed -n 's/^kernel_link=//p') +guest_bootloader_link=$(printf '%s\n' "$guest_promoted_metadata" | sed -n 's/^bootloader_link=//p') +guest_uname=$(printf '%s\n' "$guest_promoted_metadata" | sed -n 's/^uname=//p') + +[ "$guest_kernel_link" = "$kernel_store/boot/kernel/kernel" ] || { + echo "guest kernel link does not target the promoted kernel store" >&2 + exit 1 +} +[ "$guest_bootloader_link" = "$bootloader_store/boot/loader.efi" ] || { + echo "guest bootloader link does not target the promoted bootloader store" >&2 + exit 1 +} + +cat >"$metadata_file" <