Files
fruix-bootstrap/docs/reports/postphase20-installed-node-management-freebsd.md

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 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:

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.