system: add in-node build and reconfigure

This commit is contained in:
2026-04-06 08:55:35 +02:00
parent 0e8b30434f
commit db4d5bdf4c
11 changed files with 916 additions and 52 deletions
+9 -3
View File
@@ -171,8 +171,10 @@ So compared with Guix-on-Linux intuition, Fruix operators should be more explici
This remains the biggest operational gap, but it is no longer a complete gap.
Installed Fruix systems now provide a small in-guest helper:
Installed Fruix systems now provide a larger in-guest helper surface:
- `fruix system build`
- `fruix system reconfigure`
- `fruix system status`
- `fruix system switch /frx/store/...-fruix-system-...`
- `fruix system rollback`
@@ -183,11 +185,14 @@ What this gives you today:
- explicit rollback-generation tracking
- in-place switching between already-staged closures on the installed target
- rollback without reinstalling the whole system image again
- a validated in-node build path that can read the embedded current declaration inputs
- a validated in-node `reconfigure` path that can build a candidate closure locally and stage it as the next generation
What it still does **not** give you yet compared with Guix:
- a mature `reconfigure`-style workflow that builds and stages the new closure from inside the target system
- a mature end-to-end `upgrade` story for advancing declared inputs automatically
- automatic closure transfer/fetch as part of `switch`
- a higher-level `deploy` workflow across multiple machines or targets
- the broader generation-management UX Guix operators expect
So if you come from Guix, assume that Fruix now has:
@@ -195,7 +200,8 @@ So if you come from Guix, assume that Fruix now has:
- strong closure/store semantics
- explicit install artifacts
- explicit generation metadata roots
- a real but still modest installed-system switch/rollback UX
- a real installed-system build/reconfigure/switch/rollback surface
- but not yet the fuller long-term node/deployment UX that Guix users may expect
## 7. Fruix keeps Guix-like store semantics, but not Guix/Nix hash-prefix machinery exactly
+41 -22
View File
@@ -64,6 +64,11 @@ Fruix currently has:
- `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
- installed systems that now carry their own canonical declaration inputs and bundled Fruix node CLI sources
- a real XCP-ng validation of in-node:
- `fruix system build`
- `fruix system reconfigure`
- `fruix system rollback`
Validated boot modes still are:
@@ -76,34 +81,45 @@ The validated Phase 18 installation work currently uses:
## Latest completed achievement
### 2026-04-06 — Promoted native-base result sets are now first-class declaration inputs
### 2026-04-06 — Installed systems can now build and reconfigure themselves from local declaration state
Fruix can now materialize and boot a normal system declaration directly from a promoted native-build result bundle in `/frx/store`.
Fruix-installed systems are now meaningfully closer to real Fruix nodes.
Highlights:
- 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
- system closures now carry canonical declaration metadata in:
- `metadata/system-declaration.scm`
- `metadata/system-declaration-info.scm`
- `metadata/system-declaration-system`
- system closures now also carry bundled Fruix node CLI sources in:
- `share/fruix/node/scripts/fruix.scm`
- `share/fruix/node/modules/...`
- `share/fruix/node/guix/guix/build/utils.scm`
- the installed helper at `/usr/local/bin/fruix` now supports:
- `fruix system build`
- `fruix system reconfigure`
- `fruix system status`
- `fruix system switch`
- `fruix system rollback`
- no-argument in-node `build` and `reconfigure` now use the node's own embedded declaration inputs
- in-node Fruix builds now reuse the installed Guile/Shepherd runtime stores already referenced by the system instead of assuming host-only `/tmp/...` build prefixes
- the real XCP-ng validation proved the full installed-node flow:
- boot current system
- build from local declaration state
- build a candidate declaration on-node
- `reconfigure` into that candidate generation
- reboot into the candidate generation
- `rollback`
- reboot back into the original generation
Validation:
- `PASS phase20-promoted-native-base-declaration-xcpng`
- `PASS postphase20-installed-node-build-reconfigure-xcpng`
Report:
Reports:
- `docs/reports/postphase20-promoted-native-base-declarations-freebsd.md`
- `docs/reports/postphase20-installed-node-management-freebsd.md`
- `docs/system-deployment-workflow.md`
- `docs/GUIX_DIFFERENCES.md`
@@ -133,12 +149,15 @@ Report:
The next practical follow-up is now clearer:
- 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
- grow the installed-node command surface from validated `build`/`reconfigure`/`rollback` toward:
- `upgrade`
- `build-base`
- `deploy`
- decide how executor policy, native-base promotion, and installed-node reconfiguration should compose in one operator-facing workflow
- determine how much of native-build request/promotion should remain explicit versus being absorbed into higher-level Fruix node actions
The immediate architectural direction is no longer just “can guest self-hosting work?”
It is now:
- how should Fruix expose build executor choice, promoted native-base identities, and deployment materialization as one coherent product workflow?
- how should Fruix expose real managed-node behavior across declaration inputs, native-base results, generation switching, and deployment actions?
@@ -0,0 +1,178 @@
# Post-Phase 20: installed systems as real Fruix nodes
Date: 2026-04-06
## Goal
Make a Fruix-installed machine feel like a managed Fruix node instead of only a deployed image with switch/rollback helpers.
The immediate target was not yet the full long-term command surface of:
- `fruix system upgrade`
- `fruix system build-base`
- `fruix system deploy`
but it was enough to cross an important product threshold:
- installed nodes can now remember their own declaration inputs
- installed nodes can build from those inputs locally
- installed nodes can reconfigure themselves into a newly built generation
- installed nodes can still roll back cleanly
## What changed
### Canonical declaration state now ships inside the system closure
Fruix system closures now carry explicit declaration metadata:
- `metadata/system-declaration.scm`
- `metadata/system-declaration-info.scm`
- `metadata/system-declaration-system`
That gives an installed system a canonical local answer to:
- what declaration source produced me?
- what top-level system variable should be used?
This declaration metadata is also recorded through the installed generation layout metadata.
### Bundled Fruix node CLI sources now ship inside the closure
Installed system closures now also carry a self-contained Fruix node CLI source bundle under:
- `share/fruix/node/scripts/fruix.scm`
- `share/fruix/node/modules/...`
- `share/fruix/node/guix/guix/build/utils.scm`
This gives the installed node enough local Fruix/Guix Scheme source to run Fruix system actions from the node itself.
### Installed `fruix` helper gained local build/reconfigure support
The installed helper at:
- `/usr/local/bin/fruix`
now supports:
- `fruix system build`
- `fruix system reconfigure`
- `fruix system status`
- `fruix system switch`
- `fruix system rollback`
Current behavior:
- `fruix system build` with no extra arguments uses:
- `/run/current-system/metadata/system-declaration.scm`
- `/run/current-system/metadata/system-declaration-system`
- `fruix system reconfigure` with no extra arguments builds from that same embedded declaration and then stages a switch to the resulting closure
- both commands can also take an explicit declaration file plus `--system NAME`, using the same general CLI shape as the host-side Fruix frontend
### In-node builds now reuse the installed Fruix runtime stores
A crucial implementation fix was needed here.
An installed node should not try to reconstruct the Guile/Shepherd runtime prefixes from host-side `/tmp/...` build roots or host `/usr/local/lib/...` assumptions.
Instead, in-node Fruix builds now explicitly reuse the installed runtime stores already referenced by the current system closure.
That allows the in-node build path to work on the real installed system rather than depending on build-host-only paths.
## Validation harness
Added:
- `tests/system/postphase20-installed-node-operating-system.scm.in`
- `tests/system/run-postphase20-installed-node-build-reconfigure-xcpng.sh`
This harness validates the new installed-node workflow on the approved real XCP-ng path.
## Validation performed
Approved real XCP-ng path:
- VM `90490f2e-e8fc-4b7a-388e-5c26f0157289`
- VDI `0f1f90d3-48ca-4fa2-91d8-fc6339b95743`
Promoted native-base result consumed by the installed node declaration:
- `/frx/store/ffe44f5d1ba576e1f811ad3fe3a526a242b5c4a5-fruix-native-build-result-15.0-STABLE-ssh-guest`
Validated flow:
1. boot a Fruix-installed node built from the promoted native-base result
2. confirm the installed node exposes:
- embedded declaration metadata
- bundled node CLI sources
3. run in-guest:
- `fruix system build`
using the node's own embedded declaration inputs
4. copy a candidate declaration to the guest
5. run in-guest:
- `fruix system build /root/candidate.scm --system postphase20-installed-node-operating-system`
6. run in-guest:
- `fruix system reconfigure /root/candidate.scm --system postphase20-installed-node-operating-system`
7. reboot and verify the candidate generation boots
8. run in-guest:
- `fruix system rollback`
9. reboot and verify the original generation boots again
Passing run:
- `PASS postphase20-installed-node-build-reconfigure-xcpng`
Representative metadata:
```text
closure_path=/frx/store/cd4e52d8bff348953939401c8623d4189d7c9432-fruix-system-fruix-node-current
current_built_closure=/frx/store/18ee10925a15b48c676463a3359c45ff766e16a0-fruix-system-fruix-node-current
candidate_closure=/frx/store/46fc9631faf556c30a1a5f39f718d5d38a3f6ba8-fruix-system-fruix-node-canary
reconfigure_closure=/frx/store/46fc9631faf556c30a1a5f39f718d5d38a3f6ba8-fruix-system-fruix-node-canary
reconfigure_current_generation=2
reconfigure_current_closure=/frx/store/46fc9631faf556c30a1a5f39f718d5d38a3f6ba8-fruix-system-fruix-node-canary
reconfigure_rollback_generation=1
reconfigure_rollback_closure=/frx/store/cd4e52d8bff348953939401c8623d4189d7c9432-fruix-system-fruix-node-current
candidate_hostname=fruix-node-canary
rollback_current_generation=1
rollback_current_closure=/frx/store/cd4e52d8bff348953939401c8623d4189d7c9432-fruix-system-fruix-node-current
rollback_rollback_generation=2
rollback_rollback_closure=/frx/store/46fc9631faf556c30a1a5f39f718d5d38a3f6ba8-fruix-system-fruix-node-canary
rollback_hostname=fruix-node-current
installed_node_build_reconfigure=ok
```
## Important observation
The no-argument in-node build of the current declaration did not necessarily reproduce the exact original current closure path.
That is expected with the current model because closure metadata still records builder-local provenance such as the current build host context.
So the significant validated fact is not strict bit-for-bit closure reuse.
It is that the installed node can now:
- read its own declaration inputs locally
- build a valid local candidate closure from them
- build a different candidate declaration locally
- reconfigure itself into that candidate generation
- boot the candidate generation
- roll back and boot the prior generation again
## Result
Fruix-installed machines are now meaningfully closer to real Fruix nodes.
They no longer only support:
- `status`
- `switch`
- `rollback`
They now also support a validated local node workflow for:
- reading embedded declaration inputs
- building a local candidate closure
- reconfiguring into that locally built candidate
- rolling back through the same installed generation model
This is the first concrete step toward a fuller operator-facing Fruix node command surface.
+55 -2
View File
@@ -178,12 +178,32 @@ Installed Fruix systems now also ship a small in-guest deployment helper at:
Current validated in-guest commands are:
```sh
fruix system build
fruix system reconfigure
fruix system status
fruix system switch /frx/store/...-fruix-system-...
fruix system rollback
```
Current intended usage:
Installed systems now carry canonical declaration state in:
- `/run/current-system/metadata/system-declaration.scm`
- `/run/current-system/metadata/system-declaration-info.scm`
- `/run/current-system/metadata/system-declaration-system`
So the in-guest helper can now build from the node's own embedded declaration inputs.
Current validated build/reconfigure behavior is:
- `fruix system build`
- with no extra arguments, builds from the embedded current declaration
- `fruix system reconfigure`
- with no extra arguments, builds from the embedded current declaration and stages a switch to the resulting closure
- both commands can also take an explicit declaration file plus `--system NAME`
Current intended usage now has two validated patterns.
### Pattern A: build elsewhere, then switch/rollback locally
1. build a candidate closure on the operator side with `./bin/fruix system build`
2. ensure that candidate closure is present on the installed target's `/frx/store`
@@ -192,11 +212,44 @@ Current intended usage:
5. if needed, run `fruix system rollback`
6. reboot back into the recorded rollback generation
Important current limitation:
Important current limitation of this lower-level pattern:
- `fruix system switch` does **not** yet fetch or copy the candidate closure onto the target for you
- it assumes the selected closure is already present in the installed system's `/frx/store`
### Pattern B: build and reconfigure from the node itself
1. inspect or edit the node declaration inputs
- embedded current declaration, or
- an explicit replacement declaration file
2. run:
```sh
fruix system build
```
or:
```sh
fruix system build /path/to/candidate.scm --system my-operating-system
```
3. stage a local generation update with:
```sh
fruix system reconfigure
```
or:
```sh
fruix system reconfigure /path/to/candidate.scm --system my-operating-system
```
4. reboot into the staged generation
5. if needed, run `fruix system rollback`
6. reboot back into the recorded prior generation
### In-guest development environment
Opt-in systems can also expose a separate development overlay under:
+94 -16
View File
@@ -72,7 +72,13 @@
(store-dir "/frx/store")
(guile-prefix "/tmp/guile-freebsd-validate-install")
(guile-extra-prefix "/tmp/guile-gnutls-freebsd-validate-install")
(shepherd-prefix "/tmp/shepherd-freebsd-validate-install"))
(shepherd-prefix "/tmp/shepherd-freebsd-validate-install")
(guile-store-path #f)
(guile-extra-store-path #f)
(shepherd-store-path #f)
(declaration-source #f)
(declaration-origin #f)
(declaration-system-symbol #f))
(validate-operating-system os)
(let* ((cache (make-hash-table))
(source-cache (make-hash-table))
@@ -110,12 +116,15 @@
("/usr/local/lib/libtasn1.so.6" . "lib/libtasn1.so.6")
("/usr/local/lib/libhogweed.so.6" . "lib/libhogweed.so.6")
("/usr/local/lib/libnettle.so.8" . "lib/libnettle.so.8")))
(guile-store (materialize-prefix guile-prefix "fruix-guile-runtime" "3.0" store-dir
#:extra-files guile-runtime-extra-files))
(guile-extra-store (materialize-prefix guile-extra-prefix "fruix-guile-extra" "3.0" store-dir
#:extra-files (append guile-runtime-extra-files
guile-extra-runtime-files)))
(shepherd-store (materialize-prefix shepherd-prefix "fruix-shepherd-runtime" "1.0.9" store-dir))
(guile-store (or guile-store-path
(materialize-prefix guile-prefix "fruix-guile-runtime" "3.0" store-dir
#:extra-files guile-runtime-extra-files)))
(guile-extra-store (or guile-extra-store-path
(materialize-prefix guile-extra-prefix "fruix-guile-extra" "3.0" store-dir
#:extra-files (append guile-runtime-extra-files
guile-extra-runtime-files))))
(shepherd-store (or shepherd-store-path
(materialize-prefix shepherd-prefix "fruix-shepherd-runtime" "1.0.9" store-dir)))
(host-base-stores
(delete-duplicates
(map cdr
@@ -149,6 +158,19 @@
(promoted-native-build-result-artifact-store native-build-result artifact-kind))
'(world kernel headers bootloader))
'()))))
(declaration-source-text
(or declaration-source
";; Fruix declaration source is unavailable for this closure.\n"))
(declaration-origin-text (or declaration-origin ""))
(declaration-system-text
(cond ((symbol? declaration-system-symbol)
(symbol->string declaration-system-symbol))
((string? declaration-system-symbol)
declaration-system-symbol)
(else "")))
(declaration-info-object
`((available? . ,(not (not declaration-source)))
(system-variable . ,declaration-system-text)))
(metadata-files
(append
(list (cons "metadata/freebsd-base.scm"
@@ -159,10 +181,18 @@
(object->string (map freebsd-source-materialization-spec source-materializations)))
(cons "metadata/host-base-provenance.scm"
(object->string (host-freebsd-provenance)))
(cons "metadata/system-declaration.scm"
declaration-source-text)
(cons "metadata/system-declaration-info.scm"
(object->string declaration-info-object))
(cons "metadata/system-declaration-system"
(string-append declaration-system-text "\n"))
(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))))
(system-declaration-available? . ,(not (not declaration-source)))
(system-declaration-system-variable . ,declaration-system-text)
(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)
@@ -277,6 +307,9 @@
(freebsd-source-materializations-file . ,(string-append closure-path "/metadata/freebsd-source-materializations.scm"))
(materialized-source-stores . ,materialized-source-stores)
(host-base-provenance-file . ,(string-append closure-path "/metadata/host-base-provenance.scm"))
(system-declaration-file . ,(string-append closure-path "/metadata/system-declaration.scm"))
(system-declaration-info-file . ,(string-append closure-path "/metadata/system-declaration-info.scm"))
(system-declaration-system-file . ,(string-append closure-path "/metadata/system-declaration-system"))
(store-layout-file . ,(string-append closure-path "/metadata/store-layout.scm"))
(promoted-native-build-result-file
. ,(and promoted-native-build-result-summary
@@ -309,6 +342,9 @@
(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"))
(system-declaration-file . ,(string-append closure-path "/metadata/system-declaration.scm"))
(system-declaration-info-file . ,(string-append closure-path "/metadata/system-declaration-info.scm"))
(system-declaration-system-file . ,(string-append closure-path "/metadata/system-declaration-system"))
(store-layout-file . ,(string-append closure-path "/metadata/store-layout.scm"))
(install-metadata-path . ,install-metadata-path)
(install-spec . ,install-spec)))
@@ -321,6 +357,9 @@
(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"))
(system-declaration-file . ,(string-append closure-path "/metadata/system-declaration.scm"))
(system-declaration-info-file . ,(string-append closure-path "/metadata/system-declaration-info.scm"))
(system-declaration-system-file . ,(string-append closure-path "/metadata/system-declaration-system"))
(store-layout-file . ,(string-append closure-path "/metadata/store-layout.scm"))))
(define* (populate-system-generation-layout os rootfs closure-path
@@ -437,12 +476,18 @@
(store-dir "/frx/store")
(guile-prefix "/tmp/guile-freebsd-validate-install")
(guile-extra-prefix "/tmp/guile-gnutls-freebsd-validate-install")
(shepherd-prefix "/tmp/shepherd-freebsd-validate-install"))
(shepherd-prefix "/tmp/shepherd-freebsd-validate-install")
(declaration-source #f)
(declaration-origin #f)
(declaration-system-symbol #f))
(let* ((closure (materialize-operating-system os
#:store-dir store-dir
#:guile-prefix guile-prefix
#:guile-extra-prefix guile-extra-prefix
#:shepherd-prefix shepherd-prefix))
#:shepherd-prefix shepherd-prefix
#:declaration-source declaration-source
#:declaration-origin declaration-origin
#:declaration-system-symbol declaration-system-symbol))
(closure-path (assoc-ref closure 'closure-path)))
(populate-rootfs-from-closure os rootfs closure-path)))
@@ -783,6 +828,9 @@
(guile-prefix "/tmp/guile-freebsd-validate-install")
(guile-extra-prefix "/tmp/guile-gnutls-freebsd-validate-install")
(shepherd-prefix "/tmp/shepherd-freebsd-validate-install")
(declaration-source #f)
(declaration-origin #f)
(declaration-system-symbol #f)
(efi-size "64m")
(root-size #f)
(disk-capacity #f)
@@ -795,7 +843,10 @@
#:store-dir store-dir
#:guile-prefix guile-prefix
#:guile-extra-prefix guile-extra-prefix
#:shepherd-prefix shepherd-prefix))
#:shepherd-prefix shepherd-prefix
#:declaration-source declaration-source
#:declaration-origin declaration-origin
#:declaration-system-symbol declaration-system-symbol))
(closure-path (assoc-ref closure 'closure-path))
(store-items (store-reference-closure (list closure-path)))
(target-kind (if (string-prefix? "/dev/" target)
@@ -910,6 +961,9 @@
(guile-prefix "/tmp/guile-freebsd-validate-install")
(guile-extra-prefix "/tmp/guile-gnutls-freebsd-validate-install")
(shepherd-prefix "/tmp/shepherd-freebsd-validate-install")
(declaration-source #f)
(declaration-origin #f)
(declaration-system-symbol #f)
(efi-size "64m")
(root-size "256m")
(disk-capacity #f)
@@ -920,7 +974,10 @@
#:store-dir store-dir
#:guile-prefix guile-prefix
#:guile-extra-prefix guile-extra-prefix
#:shepherd-prefix shepherd-prefix))
#:shepherd-prefix shepherd-prefix
#:declaration-source declaration-source
#:declaration-origin declaration-origin
#:declaration-system-symbol declaration-system-symbol))
(closure-path (assoc-ref closure 'closure-path))
(image-spec (operating-system-image-spec os
#:efi-size efi-size
@@ -963,7 +1020,10 @@
#:store-dir store-dir
#:guile-prefix guile-prefix
#:guile-extra-prefix guile-extra-prefix
#:shepherd-prefix shepherd-prefix)
#:shepherd-prefix shepherd-prefix
#:declaration-source declaration-source
#:declaration-origin declaration-origin
#:declaration-system-symbol declaration-system-symbol)
(copy-rootfs-for-image rootfs image-rootfs)
(copy-store-items-into-rootfs image-rootfs store-dir store-items)
(mkdir-p (string-append esp-stage "/EFI/BOOT"))
@@ -1031,6 +1091,9 @@
(guile-prefix "/tmp/guile-freebsd-validate-install")
(guile-extra-prefix "/tmp/guile-gnutls-freebsd-validate-install")
(shepherd-prefix "/tmp/shepherd-freebsd-validate-install")
(declaration-source #f)
(declaration-origin #f)
(declaration-system-symbol #f)
(install-target-device "/dev/vtbd1")
(efi-size "64m")
(root-size "10g")
@@ -1049,12 +1112,18 @@
#:store-dir store-dir
#:guile-prefix guile-prefix
#:guile-extra-prefix guile-extra-prefix
#:shepherd-prefix shepherd-prefix))
#:shepherd-prefix shepherd-prefix
#:declaration-source declaration-source
#:declaration-origin declaration-origin
#:declaration-system-symbol declaration-system-symbol))
(installer-closure (materialize-operating-system installer-os
#:store-dir store-dir
#:guile-prefix guile-prefix
#:guile-extra-prefix guile-extra-prefix
#:shepherd-prefix shepherd-prefix))
#:shepherd-prefix shepherd-prefix
#:declaration-source declaration-source
#:declaration-origin declaration-origin
#:declaration-system-symbol declaration-system-symbol))
(target-closure-path (assoc-ref target-closure 'closure-path))
(installer-closure-path (assoc-ref installer-closure 'closure-path))
(target-store-items (store-reference-closure (list target-closure-path)))
@@ -1339,6 +1408,9 @@
(guile-prefix "/tmp/guile-freebsd-validate-install")
(guile-extra-prefix "/tmp/guile-gnutls-freebsd-validate-install")
(shepherd-prefix "/tmp/shepherd-freebsd-validate-install")
(declaration-source #f)
(declaration-origin #f)
(declaration-system-symbol #f)
(install-target-device "/dev/vtbd0")
(root-size #f)
(installer-host-name (string-append (operating-system-host-name os)
@@ -1355,12 +1427,18 @@
#:store-dir store-dir
#:guile-prefix guile-prefix
#:guile-extra-prefix guile-extra-prefix
#:shepherd-prefix shepherd-prefix))
#:shepherd-prefix shepherd-prefix
#:declaration-source declaration-source
#:declaration-origin declaration-origin
#:declaration-system-symbol declaration-system-symbol))
(installer-closure (materialize-operating-system installer-os
#:store-dir store-dir
#:guile-prefix guile-prefix
#:guile-extra-prefix guile-extra-prefix
#:shepherd-prefix shepherd-prefix))
#:shepherd-prefix shepherd-prefix
#:declaration-source declaration-source
#:declaration-origin declaration-origin
#:declaration-system-symbol declaration-system-symbol))
(target-closure-path (assoc-ref target-closure 'closure-path))
(installer-closure-path (assoc-ref installer-closure 'closure-path))
(target-closure-store-items (store-reference-closure (list target-closure-path)))
+6
View File
@@ -369,8 +369,12 @@
"metadata/freebsd-base.scm"
"metadata/host-base-provenance.scm"
"metadata/store-layout.scm"
"metadata/system-declaration.scm"
"metadata/system-declaration-info.scm"
"metadata/system-declaration-system"
"activate"
"shepherd/init.scm"
"share/fruix/node/scripts/fruix.scm"
"usr/local/bin/fruix")
(if (operating-system-native-build-result os)
'("metadata/promoted-native-build-result.scm")
@@ -404,6 +408,8 @@
(base-packages . ,(package-names (operating-system-base-packages os)))
(development-package-count . ,(length (operating-system-development-packages os)))
(development-packages . ,(package-names (operating-system-development-packages os)))
(installed-system-command-surface-version . "2")
(bundled-fruix-node-cli-version . "1")
(development-environment-helper-version
. ,(if (null? (operating-system-development-packages os)) #f "1"))
(self-hosted-native-build-helper-version
+153 -2
View File
@@ -4,6 +4,7 @@
#:use-module (ice-9 match)
#:use-module (srfi srfi-1)
#:use-module (srfi srfi-13)
#:use-module (rnrs io ports)
#:export (operating-system-generated-files
render-activation-rc-script
render-rc-script))
@@ -467,7 +468,57 @@
"}\n\n"
"load_rc_config $name\n"
"run_rc_command \"$1\"\n")))
(define (render-installed-system-fruix os)
(define (path-parent path)
(let ((index (string-rindex path #\/)))
(cond
((not index) ".")
((zero? index) "/")
(else (substring path 0 index)))))
(define (read-source-file-string path)
(call-with-input-file path get-string-all))
(define (bundled-fruix-node-files)
(let* ((repo-root (or (getenv "FRUIX_PROJECT_ROOT")
(let ((render-file (current-filename)))
(and render-file
(path-parent
(path-parent
(path-parent
(path-parent
(path-parent render-file)))))))
(getcwd)))
(guix-root (or (getenv "GUIX_SOURCE_DIR")
(string-append (getenv "HOME") "/repos/guix")))
(specs `((,(string-append repo-root "/scripts/fruix.scm")
. "share/fruix/node/scripts/fruix.scm")
(,(string-append repo-root "/modules/fruix/packages/freebsd.scm")
. "share/fruix/node/modules/fruix/packages/freebsd.scm")
(,(string-append repo-root "/modules/fruix/system/freebsd.scm")
. "share/fruix/node/modules/fruix/system/freebsd.scm")
(,(string-append repo-root "/modules/fruix/system/freebsd/build.scm")
. "share/fruix/node/modules/fruix/system/freebsd/build.scm")
(,(string-append repo-root "/modules/fruix/system/freebsd/executor.scm")
. "share/fruix/node/modules/fruix/system/freebsd/executor.scm")
(,(string-append repo-root "/modules/fruix/system/freebsd/media.scm")
. "share/fruix/node/modules/fruix/system/freebsd/media.scm")
(,(string-append repo-root "/modules/fruix/system/freebsd/model.scm")
. "share/fruix/node/modules/fruix/system/freebsd/model.scm")
(,(string-append repo-root "/modules/fruix/system/freebsd/render.scm")
. "share/fruix/node/modules/fruix/system/freebsd/render.scm")
(,(string-append repo-root "/modules/fruix/system/freebsd/source.scm")
. "share/fruix/node/modules/fruix/system/freebsd/source.scm")
(,(string-append repo-root "/modules/fruix/system/freebsd/utils.scm")
. "share/fruix/node/modules/fruix/system/freebsd/utils.scm")
(,(string-append guix-root "/guix/build/utils.scm")
. "share/fruix/node/guix/guix/build/utils.scm"))))
(map (lambda (entry)
(cons (cdr entry)
(read-source-file-string (car entry))))
specs)))
(define (render-installed-system-fruix os guile-store guile-extra-store shepherd-store)
(string-append
"#!/bin/sh\n"
"set -eu\n"
@@ -485,6 +536,17 @@
"rollback_generation_file=\"$system_root/rollback-generation\"\n"
"gcroots_root=/frx/var/fruix/gcroots\n"
"run_current_link=/run/current-system\n"
"node_root=/run/current-system/share/fruix/node\n"
"node_script=\"$node_root/scripts/fruix.scm\"\n"
"node_module_root=\"$node_root/modules\"\n"
"node_guix_root=\"$node_root/guix\"\n"
"declaration_file=/run/current-system/metadata/system-declaration.scm\n"
"declaration_info_file=/run/current-system/metadata/system-declaration-info.scm\n"
"declaration_system_file=/run/current-system/metadata/system-declaration-system\n"
"default_store_dir=/frx/store\n"
"guile_store='" guile-store "'\n"
"guile_extra_store='" guile-extra-store "'\n"
"shepherd_store='" shepherd-store "'\n"
"layout_version=2\n"
"host_name='" (operating-system-host-name os) "'\n"
"ready_marker='" (operating-system-ready-marker os) "'\n"
@@ -493,6 +555,8 @@
"{\n"
" cat <<'EOF'\n"
"Usage: fruix system status\n"
" fruix system build [DECLARATION [--system NAME] ...]\n"
" fruix system reconfigure [DECLARATION [--system NAME] ...]\n"
" fruix system switch /frx/store/...-fruix-system-...\n"
" fruix system rollback\n"
"EOF\n"
@@ -514,6 +578,10 @@
" tr -d '\\n' < \"$1\"\n"
" fi\n"
"}\n\n"
"default_system_name()\n"
"{\n"
" read_file_maybe \"$declaration_system_file\"\n"
"}\n\n"
"symlink_force()\n"
"{\n"
" target=$1\n"
@@ -537,6 +605,79 @@
" [ -f \"$closure/shepherd/init.scm\" ] || die \"closure is missing shepherd config: $closure\"\n"
" [ -f \"$closure/boot/loader.efi\" ] || die \"closure is missing loader.efi: $closure\"\n"
"}\n\n"
"ensure_default_declaration()\n"
"{\n"
" [ -f \"$declaration_file\" ] || die \"current declaration file is missing: $declaration_file\"\n"
" [ -f \"$declaration_info_file\" ] || die \"current declaration info file is missing: $declaration_info_file\"\n"
" current_system_name=$(default_system_name)\n"
" [ -n \"$current_system_name\" ] || die \"current declaration is missing a system variable name\"\n"
"}\n\n"
"run_node_cli()\n"
"{\n"
" [ -x \"$guile_store/bin/guile\" ] || die \"missing Guile runtime: $guile_store/bin/guile\"\n"
" [ -f \"$node_script\" ] || die \"missing bundled Fruix node CLI: $node_script\"\n"
" [ -d \"$node_module_root\" ] || die \"missing bundled Fruix modules: $node_module_root\"\n"
" [ -d \"$node_guix_root\" ] || die \"missing bundled Guix modules: $node_guix_root\"\n"
" guile_load_path=\"$node_module_root:$node_guix_root:$shepherd_store/share/guile/site/3.0:$guile_extra_store/share/guile/site/3.0\"\n"
" guile_system_path=\"$guile_store/share/guile/3.0:$guile_store/share/guile/site/3.0:$guile_store/share/guile/site:$guile_store/share/guile\"\n"
" guile_system_compiled_path=\"$guile_store/lib/guile/3.0/ccache:$guile_store/lib/guile/3.0/site-ccache\"\n"
" guile_load_compiled_path=\"$shepherd_store/lib/guile/3.0/site-ccache:$guile_extra_store/lib/guile/3.0/site-ccache\"\n"
" guile_system_extensions_path=\"$guile_store/lib/guile/3.0/extensions\"\n"
" guile_extensions_path=\"$guile_extra_store/lib/guile/3.0/extensions\"\n"
" ld_library_path=\"$guile_extra_store/lib:$guile_store/lib:/usr/local/lib\"\n"
" env \\\n"
" GUILE_AUTO_COMPILE=0 \\\n"
" GUILE_SYSTEM_PATH=\"$guile_system_path\" \\\n"
" GUILE_LOAD_PATH=\"$guile_load_path\" \\\n"
" GUILE_SYSTEM_COMPILED_PATH=\"$guile_system_compiled_path\" \\\n"
" GUILE_LOAD_COMPILED_PATH=\"$guile_load_compiled_path\" \\\n"
" GUILE_SYSTEM_EXTENSIONS_PATH=\"$guile_system_extensions_path\" \\\n"
" GUILE_EXTENSIONS_PATH=\"$guile_extensions_path\" \\\n"
" LD_LIBRARY_PATH=\"$ld_library_path\" \\\n"
" GUILE_PREFIX=\"$guile_store\" \\\n"
" GUILE_EXTRA_PREFIX=\"$guile_extra_store\" \\\n"
" SHEPHERD_PREFIX=\"$shepherd_store\" \\\n"
" FRUIX_GUILE_STORE=\"$guile_store\" \\\n"
" FRUIX_GUILE_EXTRA_STORE=\"$guile_extra_store\" \\\n"
" FRUIX_SHEPHERD_STORE=\"$shepherd_store\" \\\n"
" GUIX_SOURCE_DIR=\"$node_guix_root\" \\\n"
" FRUIX_PROJECT_ROOT=\"$node_root\" \\\n"
" \"$guile_store/bin/guile\" --no-auto-compile -s \"$node_script\" \"$@\"\n"
"}\n\n"
"system_build()\n"
"{\n"
" if [ $# -eq 0 ]; then\n"
" ensure_default_declaration\n"
" run_node_cli system build \"$declaration_file\" --system \"$current_system_name\" --store \"$default_store_dir\"\n"
" else\n"
" run_node_cli system build \"$@\"\n"
" fi\n"
"}\n\n"
"reconfigure_system()\n"
"{\n"
" build_output=$(mktemp /tmp/fruix-system-reconfigure.XXXXXX)\n"
" if [ $# -eq 0 ]; then\n"
" ensure_default_declaration\n"
" if ! run_node_cli system build \"$declaration_file\" --system \"$current_system_name\" --store \"$default_store_dir\" > \"$build_output\"; then\n"
" cat \"$build_output\" >&2 || true\n"
" rm -f \"$build_output\"\n"
" exit 1\n"
" fi\n"
" else\n"
" if ! run_node_cli system build \"$@\" > \"$build_output\"; then\n"
" cat \"$build_output\" >&2 || true\n"
" rm -f \"$build_output\"\n"
" exit 1\n"
" fi\n"
" fi\n"
" closure=$(sed -n 's/^closure_path=//p' \"$build_output\" | tail -n 1)\n"
" [ -n \"$closure\" ] || die \"failed to recover closure_path from in-system build output\"\n"
" cat \"$build_output\"\n"
" rm -f \"$build_output\"\n"
" switch_to_closure \"$closure\"\n"
" printf 'reconfigure_closure=%s\\n' \"$closure\"\n"
" printf 'reboot_required=true\\n'\n"
"}\n\n"
"max_generation_number()\n"
"{\n"
" max=0\n"
@@ -726,6 +867,14 @@
" [ $# -eq 2 ] || { usage >&2; exit 1; }\n"
" status\n"
" ;;\n"
" build)\n"
" shift 2\n"
" system_build \"$@\"\n"
" ;;\n"
" reconfigure)\n"
" shift 2\n"
" reconfigure_system \"$@\"\n"
" ;;\n"
" switch)\n"
" [ $# -eq 3 ] || { usage >&2; exit 1; }\n"
" switch_to_closure \"$3\"\n"
@@ -1030,7 +1179,9 @@
#:guile-extra-store guile-extra-store
#:shepherd-store shepherd-store))
("shepherd/init.scm" . ,(render-shepherd-config os))
("usr/local/bin/fruix" . ,(render-installed-system-fruix os)))
("usr/local/bin/fruix"
. ,(render-installed-system-fruix os guile-store guile-extra-store shepherd-store)))
(bundled-fruix-node-files)
(if (null? (operating-system-development-packages os))
'()
`(("usr/local/bin/fruix-development-environment"
+33 -4
View File
@@ -6,7 +6,8 @@
(ice-9 format)
(ice-9 match)
(srfi srfi-1)
(srfi srfi-13))
(srfi srfi-13)
(rnrs io ports))
(define (usage code)
(format (if (= code 0) #t (current-error-port))
@@ -69,6 +70,9 @@ Common options:\n\
(format #t "~a=~a~%" (car field) (stringify (cdr field))))
fields))
(define (read-file-string file)
(call-with-input-file file get-string-all))
(define (lookup-bound-value module symbol)
(let ((var (module-variable module symbol)))
(and var (variable-ref var))))
@@ -629,7 +633,11 @@ Common options:\n\
(lambda (os resolved-symbol)
(let* ((guile-prefix (or (getenv "GUILE_PREFIX") "/tmp/guile-freebsd-validate-install"))
(guile-extra-prefix (or (getenv "GUILE_EXTRA_PREFIX") "/tmp/guile-gnutls-freebsd-validate-install"))
(shepherd-prefix (or (getenv "SHEPHERD_PREFIX") "/tmp/shepherd-freebsd-validate-install")))
(shepherd-prefix (or (getenv "SHEPHERD_PREFIX") "/tmp/shepherd-freebsd-validate-install"))
(guile-store-path (getenv "FRUIX_GUILE_STORE"))
(guile-extra-store-path (getenv "FRUIX_GUILE_EXTRA_STORE"))
(shepherd-store-path (getenv "FRUIX_SHEPHERD_STORE"))
(declaration-source (read-file-string os-file)))
(cond
((string=? action "build")
(emit-system-build-metadata
@@ -638,7 +646,13 @@ Common options:\n\
#:store-dir store-dir
#:guile-prefix guile-prefix
#:guile-extra-prefix guile-extra-prefix
#:shepherd-prefix shepherd-prefix)))
#:shepherd-prefix shepherd-prefix
#:guile-store-path guile-store-path
#:guile-extra-store-path guile-extra-store-path
#:shepherd-store-path shepherd-store-path
#:declaration-source declaration-source
#:declaration-origin os-file
#:declaration-system-symbol resolved-symbol)))
((string=? action "rootfs")
(unless rootfs
(error "rootfs action requires ROOTFS-DIR or --rootfs DIR"))
@@ -646,7 +660,10 @@ Common options:\n\
#:store-dir store-dir
#:guile-prefix guile-prefix
#:guile-extra-prefix guile-extra-prefix
#:shepherd-prefix shepherd-prefix)))
#:shepherd-prefix shepherd-prefix
#:declaration-source declaration-source
#:declaration-origin os-file
#:declaration-system-symbol resolved-symbol)))
(emit-metadata
`((action . "rootfs")
(os_file . ,os-file)
@@ -664,6 +681,9 @@ Common options:\n\
#:guile-prefix guile-prefix
#:guile-extra-prefix guile-extra-prefix
#:shepherd-prefix shepherd-prefix
#:declaration-source declaration-source
#:declaration-origin os-file
#:declaration-system-symbol resolved-symbol
#:root-size (or root-size "256m")
#:disk-capacity disk-capacity)))
((string=? action "installer")
@@ -674,6 +694,9 @@ Common options:\n\
#:guile-prefix guile-prefix
#:guile-extra-prefix guile-extra-prefix
#:shepherd-prefix shepherd-prefix
#:declaration-source declaration-source
#:declaration-origin os-file
#:declaration-system-symbol resolved-symbol
#:install-target-device (or install-target-device "/dev/vtbd1")
#:root-size (or root-size "10g")
#:disk-capacity disk-capacity)))
@@ -685,6 +708,9 @@ Common options:\n\
#:guile-prefix guile-prefix
#:guile-extra-prefix guile-extra-prefix
#:shepherd-prefix shepherd-prefix
#:declaration-source declaration-source
#:declaration-origin os-file
#:declaration-system-symbol resolved-symbol
#:install-target-device (or install-target-device "/dev/vtbd0")
#:root-size root-size)))
((string=? action "install")
@@ -698,6 +724,9 @@ Common options:\n\
#:guile-prefix guile-prefix
#:guile-extra-prefix guile-extra-prefix
#:shepherd-prefix shepherd-prefix
#:declaration-source declaration-source
#:declaration-origin os-file
#:declaration-system-symbol resolved-symbol
#:root-size root-size
#:disk-capacity disk-capacity))))))))))
((string=? command "source")
@@ -0,0 +1,73 @@
(use-modules (fruix system freebsd)
(fruix packages freebsd))
(define postphase20-promoted-native-build-result
(promoted-native-build-result
#:store-path "__PROMOTED_RESULT_STORE__"))
(define postphase20-installed-node-operating-system
(operating-system-from-promoted-native-build-result
postphase20-promoted-native-build-result
#:host-name "__HOST_NAME__"
#:groups (list (user-group #:name "wheel" #:gid 0 #:system? #t)
(user-group #:name "sshd" #:gid 22 #:system? #t)
(user-group #:name "_dhcp" #:gid 65 #:system? #t)
(user-group #:name "operator" #:gid 1000 #:system? #f))
#:users (list (user-account #:name "root"
#:uid 0
#:group "wheel"
#:comment "Charlie &"
#:home "/root"
#:shell "/bin/sh"
#:system? #t)
(user-account #:name "sshd"
#:uid 22
#:group "sshd"
#:comment "Secure Shell Daemon"
#:home "/var/empty"
#:shell "/usr/sbin/nologin"
#:system? #t)
(user-account #:name "_dhcp"
#:uid 65
#:group "_dhcp"
#:comment "dhcp programs"
#:home "/var/empty"
#:shell "/usr/sbin/nologin"
#:system? #t)
(user-account #:name "operator"
#:uid 1000
#:group "operator"
#:supplementary-groups '("wheel")
#:comment "Fruix Operator"
#:home "/home/operator"
#:shell "/bin/sh"
#:system? #f))
#:file-systems (list (file-system #:device "/dev/gpt/fruix-root"
#:mount-point "/"
#:type "ufs"
#:options "rw"
#:needed-for-boot? #t)
(file-system #:device "devfs"
#:mount-point "/dev"
#:type "devfs"
#:options "rw"
#:needed-for-boot? #t)
(file-system #:device "tmpfs"
#:mount-point "/tmp"
#:type "tmpfs"
#:options "rw,size=64m"))
#:services '(shepherd ready-marker sshd)
#:loader-entries '(("autoboot_delay" . "1")
("boot_multicons" . "YES")
("boot_serial" . "YES")
("console" . "comconsole,vidconsole"))
#:rc-conf-entries '(("clear_tmp_enable" . "NO")
("hostid_enable" . "NO")
("sendmail_enable" . "NONE")
("sshd_enable" . "YES")
("ifconfig_xn0" . "SYNCDHCP")
("ifconfig_em0" . "SYNCDHCP")
("ifconfig_vtnet0" . "SYNCDHCP"))
#:init-mode 'shepherd-pid1
#:ready-marker "/var/lib/fruix/ready"
#:root-authorized-keys '("__ROOT_AUTHORIZED_KEY__")))
+3 -3
View File
@@ -104,11 +104,11 @@ image_size_bytes=$(stat -f '%z' "$disk_image")
closure_base=$(basename "$closure_path")
case "$image_store_path" in
/frx/store/*-fruix-bhyve-image-fruix-freebsd) : ;;
/frx/store/*-fruix-bhyve-image-*) : ;;
*) echo "unexpected image store path: $image_store_path" >&2; exit 1 ;;
esac
case "$disk_image" in
/frx/store/*-fruix-bhyve-image-fruix-freebsd/disk.img) : ;;
/frx/store/*-fruix-bhyve-image-*/disk.img) : ;;
*) echo "unexpected disk image path: $disk_image" >&2; exit 1 ;;
esac
@@ -142,7 +142,7 @@ if [ -L "$mnt_root/etc/master.passwd" ]; then master_passwd_kind=symlink; elif [
loader_conf_image=$mnt_root/frx/store/$closure_base/boot/loader.conf
rc_conf_image=$mnt_root/frx/store/$closure_base/etc/rc.conf
grep -F 'comconsole' "$loader_conf_image" >/dev/null || { echo "loader.conf is missing serial console config" >&2; exit 1; }
grep -F 'hostname="fruix-freebsd"' "$rc_conf_image" >/dev/null || { echo "rc.conf is missing hostname" >&2; exit 1; }
grep -E '^hostname=".+"$' "$rc_conf_image" >/dev/null || { echo "rc.conf is missing hostname" >&2; exit 1; }
[ -f "$host_base_provenance_file" ] || { echo "missing host base provenance file: $host_base_provenance_file" >&2; exit 1; }
[ -f "$store_layout_file" ] || { echo "missing store layout file: $store_layout_file" >&2; exit 1; }
[ -n "$host_freebsd_version" ] || { echo "missing host freebsd version provenance" >&2; exit 1; }
@@ -0,0 +1,271 @@
#!/bin/sh
set -eu
repo_root=${PROJECT_ROOT:-$(pwd)}
os_template=${OS_TEMPLATE:-$repo_root/tests/system/postphase20-installed-node-operating-system.scm.in}
system_name=${SYSTEM_NAME:-postphase20-installed-node-operating-system}
result_store=${RESULT_STORE:-/frx/store/ffe44f5d1ba576e1f811ad3fe3a526a242b5c4a5-fruix-native-build-result-15.0-STABLE-ssh-guest}
root_size=${ROOT_SIZE:-12g}
current_host_name=${CURRENT_HOST_NAME:-fruix-node-current}
candidate_host_name=${CANDIDATE_HOST_NAME:-fruix-node-canary}
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-postphase20-installed-node-xcpng.XXXXXX)
cleanup=1
fi
if [ "${KEEP_WORKDIR:-0}" -eq 1 ]; then
cleanup=0
fi
current_os_file=$workdir/current-operating-system.scm
candidate_os_file=$workdir/candidate-operating-system.scm
inner_metadata=$workdir/postphase20-installed-node-inner-metadata.txt
current_build_out=$workdir/current-build.txt
candidate_build_out=$workdir/candidate-build.txt
reconfigure_out=$workdir/reconfigure.txt
rollback_out=$workdir/rollback.txt
post_reconfigure_status=$workdir/post-reconfigure-status.txt
post_boot_candidate_status=$workdir/post-boot-candidate-status.txt
post_boot_rollback_status=$workdir/post-boot-rollback-status.txt
metadata_file=$workdir/postphase20-installed-node-build-reconfigure-xcpng-metadata.txt
cleanup_workdir() {
if [ "$cleanup" -eq 1 ]; then
rm -rf "$workdir"
fi
}
trap cleanup_workdir EXIT INT TERM
[ -f "$os_template" ] || { echo "missing operating-system template: $os_template" >&2; exit 1; }
[ -f "$root_authorized_key_file" ] || { echo "missing root authorized key file: $root_authorized_key_file" >&2; exit 1; }
[ -f "$root_ssh_private_key_file" ] || { echo "missing root SSH private key file: $root_ssh_private_key_file" >&2; exit 1; }
[ -d "$result_store" ] || { echo "promoted result store does not exist: $result_store" >&2; exit 1; }
root_authorized_key=$(tr -d '\n' < "$root_authorized_key_file")
render_os() {
output=$1
host_name=$2
sed \
-e "s|__PROMOTED_RESULT_STORE__|$result_store|g" \
-e "s|__HOST_NAME__|$host_name|g" \
-e "s|__ROOT_AUTHORIZED_KEY__|$root_authorized_key|g" \
"$os_template" > "$output"
}
render_os "$current_os_file" "$current_host_name"
render_os "$candidate_os_file" "$candidate_host_name"
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="$current_os_file" SYSTEM_NAME="$system_name" ROOT_SIZE="$root_size" \
"$repo_root/tests/system/run-phase11-shepherd-pid1-xcpng.sh"
phase8_metadata=$(sed -n 's/^phase8_metadata=//p' "$inner_metadata")
closure_path=$(sed -n 's/^closure_path=//p' "$inner_metadata")
closure_base=$(sed -n 's/^closure_base=//p' "$inner_metadata")
guest_ip=$(sed -n 's/^guest_ip=//p' "$inner_metadata")
vm_id=$(sed -n 's/^vm_id=//p' "$inner_metadata")
vdi_id=$(sed -n 's/^vdi_id=//p' "$inner_metadata")
shepherd_pid=$(sed -n 's/^shepherd_pid=//p' "$inner_metadata")
sshd_status=$(sed -n 's/^sshd_status=//p' "$inner_metadata")
compat_prefix_shims=$(sed -n 's/^compat_prefix_shims=//p' "$inner_metadata")
guile_module_smoke=$(sed -n 's/^guile_module_smoke=//p' "$inner_metadata")
activate_log=$(sed -n 's/^activate_log=//p' "$inner_metadata")
[ "$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
for path in \
"$closure_path/metadata/system-declaration.scm" \
"$closure_path/metadata/system-declaration-info.scm" \
"$closure_path/metadata/system-declaration-system" \
"$closure_path/share/fruix/node/scripts/fruix.scm" \
"$closure_path/share/fruix/node/modules/fruix/system/freebsd/render.scm" \
"$closure_path/share/fruix/node/guix/guix/build/utils.scm"
do
[ -f "$path" ] || {
echo "required installed-node path missing: $path" >&2
exit 1
}
done
grep -F "$current_host_name" "$closure_path/metadata/system-declaration.scm" >/dev/null || {
echo "embedded declaration does not mention current host name" >&2
exit 1
}
grep -F "$system_name" "$closure_path/metadata/system-declaration-system" >/dev/null || {
echo "embedded declaration system name is missing" >&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" "$@"
}
scp_guest() {
scp -O -i "$root_ssh_private_key_file" \
-o BatchMode=yes \
-o StrictHostKeyChecking=no \
-o UserKnownHostsFile=/dev/null \
-o ConnectTimeout=5 \
"$@"
}
wait_for_ssh() {
for attempt in $(jot 120 1 120); do
if ssh_guest 'service sshd onestatus >/dev/null 2>&1' >/dev/null 2>&1; then
return 0
fi
sleep 2
done
return 1
}
reboot_guest() {
ssh_guest 'shutdown -r now >/dev/null 2>&1 || reboot >/dev/null 2>&1 || true' >/dev/null 2>&1 || true
sleep 5
wait_for_ssh || {
echo "guest did not return over SSH after reboot" >&2
exit 1
}
}
ssh_guest 'sh -s' <<EOF
set -eu
[ -x /usr/local/bin/fruix ]
[ -f /run/current-system/metadata/system-declaration.scm ]
[ -f /run/current-system/metadata/system-declaration-info.scm ]
[ -f /run/current-system/metadata/system-declaration-system ]
[ -f /run/current-system/share/fruix/node/scripts/fruix.scm ]
[ -f /run/current-system/share/fruix/node/modules/fruix/system/freebsd/media.scm ]
[ -f /run/current-system/share/fruix/node/guix/guix/build/utils.scm ]
grep -F '$system_name' /run/current-system/metadata/system-declaration-system >/dev/null
EOF
ssh_guest '/usr/local/bin/fruix system build' > "$current_build_out"
current_built_closure=$(sed -n 's/^closure_path=//p' "$current_build_out" | tail -n 1)
[ -n "$current_built_closure" ] || { echo "missing closure_path from in-system build output" >&2; exit 1; }
case "$current_built_closure" in
/frx/store/*-fruix-system-$current_host_name) : ;;
*)
echo "in-system build of current declaration produced an unexpected closure path: $current_built_closure" >&2
exit 1
;;
esac
scp_guest "$candidate_os_file" root@"$guest_ip":/root/candidate.scm >/dev/null
ssh_guest "/usr/local/bin/fruix system build /root/candidate.scm --system $system_name" > "$candidate_build_out"
candidate_closure=$(sed -n 's/^closure_path=//p' "$candidate_build_out" | tail -n 1)
[ -n "$candidate_closure" ] || { echo "missing candidate closure_path from in-system build output" >&2; exit 1; }
[ "$candidate_closure" != "$closure_path" ] || {
echo "candidate closure unexpectedly matches current closure" >&2
exit 1
}
ssh_guest "/usr/local/bin/fruix system reconfigure /root/candidate.scm --system $system_name" > "$reconfigure_out"
reconfigure_closure=$(sed -n 's/^reconfigure_closure=//p' "$reconfigure_out" | tail -n 1)
reconfigure_current_generation=$(sed -n 's/^current_generation=//p' "$reconfigure_out" | tail -n 1)
reconfigure_current_closure=$(sed -n 's/^current_closure=//p' "$reconfigure_out" | tail -n 1)
reconfigure_rollback_generation=$(sed -n 's/^rollback_generation=//p' "$reconfigure_out" | tail -n 1)
reconfigure_rollback_closure=$(sed -n 's/^rollback_closure=//p' "$reconfigure_out" | tail -n 1)
[ "$reconfigure_closure" = "$candidate_closure" ] || { echo "reconfigure closure mismatch" >&2; exit 1; }
[ "$reconfigure_current_generation" = 2 ] || { echo "unexpected current generation after reconfigure: $reconfigure_current_generation" >&2; exit 1; }
[ "$reconfigure_current_closure" = "$candidate_closure" ] || { echo "unexpected current closure after reconfigure" >&2; exit 1; }
[ "$reconfigure_rollback_generation" = 1 ] || { echo "unexpected rollback generation after reconfigure: $reconfigure_rollback_generation" >&2; exit 1; }
[ "$reconfigure_rollback_closure" = "$closure_path" ] || { echo "unexpected rollback closure after reconfigure" >&2; exit 1; }
ssh_guest '/usr/local/bin/fruix system status' > "$post_reconfigure_status"
reboot_guest
candidate_hostname=$(ssh_guest 'hostname')
candidate_run_current=$(ssh_guest 'readlink /run/current-system')
ssh_guest '/usr/local/bin/fruix system status' > "$post_boot_candidate_status"
[ "$candidate_hostname" = "$candidate_host_name" ] || { echo "unexpected host name after candidate boot: $candidate_hostname" >&2; exit 1; }
[ "$candidate_run_current" = "$candidate_closure" ] || { echo "unexpected current closure after candidate boot: $candidate_run_current" >&2; exit 1; }
ssh_guest '/usr/local/bin/fruix system rollback' > "$rollback_out"
rollback_current_generation=$(sed -n 's/^current_generation=//p' "$rollback_out" | tail -n 1)
rollback_current_closure=$(sed -n 's/^current_closure=//p' "$rollback_out" | tail -n 1)
rollback_rollback_generation=$(sed -n 's/^rollback_generation=//p' "$rollback_out" | tail -n 1)
rollback_rollback_closure=$(sed -n 's/^rollback_closure=//p' "$rollback_out" | tail -n 1)
[ "$rollback_current_generation" = 1 ] || { echo "unexpected current generation after rollback: $rollback_current_generation" >&2; exit 1; }
[ "$rollback_current_closure" = "$closure_path" ] || { echo "unexpected current closure after rollback" >&2; exit 1; }
[ "$rollback_rollback_generation" = 2 ] || { echo "unexpected rollback generation after rollback: $rollback_rollback_generation" >&2; exit 1; }
[ "$rollback_rollback_closure" = "$candidate_closure" ] || { echo "unexpected rollback closure after rollback" >&2; exit 1; }
reboot_guest
rollback_hostname=$(ssh_guest 'hostname')
rollback_run_current=$(ssh_guest 'readlink /run/current-system')
ssh_guest '/usr/local/bin/fruix system status' > "$post_boot_rollback_status"
[ "$rollback_hostname" = "$current_host_name" ] || { echo "unexpected host name after rollback boot: $rollback_hostname" >&2; exit 1; }
[ "$rollback_run_current" = "$closure_path" ] || { echo "unexpected current closure after rollback boot: $rollback_run_current" >&2; exit 1; }
cat > "$metadata_file" <<EOF
workdir=$workdir
inner_metadata=$inner_metadata
phase8_metadata=$phase8_metadata
closure_path=$closure_path
closure_base=$closure_base
vm_id=$vm_id
vdi_id=$vdi_id
guest_ip=$guest_ip
root_size=$root_size
result_store=$result_store
current_host_name=$current_host_name
candidate_host_name=$candidate_host_name
current_build_out=$current_build_out
current_built_closure=$current_built_closure
candidate_build_out=$candidate_build_out
candidate_closure=$candidate_closure
reconfigure_out=$reconfigure_out
reconfigure_closure=$reconfigure_closure
reconfigure_current_generation=$reconfigure_current_generation
reconfigure_current_closure=$reconfigure_current_closure
reconfigure_rollback_generation=$reconfigure_rollback_generation
reconfigure_rollback_closure=$reconfigure_rollback_closure
candidate_hostname=$candidate_hostname
candidate_run_current=$candidate_run_current
rollback_out=$rollback_out
rollback_current_generation=$rollback_current_generation
rollback_current_closure=$rollback_current_closure
rollback_rollback_generation=$rollback_rollback_generation
rollback_rollback_closure=$rollback_rollback_closure
rollback_hostname=$rollback_hostname
rollback_run_current=$rollback_run_current
boot_backend=xcp-ng-xo-cli
init_mode=shepherd-pid1
installed_node_build_reconfigure=ok
EOF
if [ -n "$metadata_target" ]; then
mkdir -p "$(dirname "$metadata_target")"
cp "$metadata_file" "$metadata_target"
fi
printf 'PASS postphase20-installed-node-build-reconfigure-xcpng\n'
printf 'Work directory: %s\n' "$workdir"
printf 'Metadata file: %s\n' "$metadata_file"
if [ -n "$metadata_target" ]; then
printf 'Copied metadata to: %s\n' "$metadata_target"
fi
printf '%s\n' '--- metadata ---'
cat "$metadata_file"