6.4 KiB
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 upgradefruix system build-basefruix 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.scmmetadata/system-declaration-info.scmmetadata/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.scmshare/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 buildfruix system reconfigurefruix system statusfruix system switchfruix system rollback
Current behavior:
fruix system buildwith no extra arguments uses:/run/current-system/metadata/system-declaration.scm/run/current-system/metadata/system-declaration-system
fruix system reconfigurewith 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.intests/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:
- boot a Fruix-installed node built from the promoted native-base result
- confirm the installed node exposes:
- embedded declaration metadata
- bundled node CLI sources
- run in-guest:
fruix system buildusing the node's own embedded declaration inputs
- copy a candidate declaration to the guest
- run in-guest:
fruix system build /root/candidate.scm --system postphase20-installed-node-operating-system
- run in-guest:
fruix system reconfigure /root/candidate.scm --system postphase20-installed-node-operating-system
- reboot and verify the candidate generation boots
- run in-guest:
fruix system rollback
- reboot and verify the original generation boots again
Passing run:
PASS postphase20-installed-node-build-reconfigure-xcpng
Representative metadata:
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:
statusswitchrollback
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.