docs: define Fruix deployment workflow

This commit is contained in:
2026-04-04 19:11:14 +02:00
parent 43c155bb9f
commit e86f74af97
3 changed files with 532 additions and 29 deletions

View File

@@ -38,44 +38,42 @@ The validated Phase 18 installation work currently uses:
## Latest completed achievement ## Latest completed achievement
### 2026-04-04 — Phase 18.3 completed ### 2026-04-04 — Phase 19.1 completed
Fruix now builds a bootable installer ISO, boots it, installs from it, and boots the installed target successfully. Fruix now has a documented canonical deployment workflow for build, image generation, installation, roll-forward, and rollback.
Highlights: Highlights:
- added in `modules/fruix/system/freebsd.scm`: - added canonical operator workflow documentation:
- `operating-system-installer-iso-spec` - `docs/system-deployment-workflow.md`
- `materialize-installer-iso` - added Phase 19.1 report:
- added CLI support in `scripts/fruix.scm`: - `docs/reports/phase19-deployment-workflow-freebsd.md`
- the documented command surface now explicitly centers on:
- `fruix system build`
- `fruix system rootfs`
- `fruix system image`
- `fruix system install`
- `fruix system installer`
- `fruix system installer-iso` - `fruix system installer-iso`
- the installer ISO now carries: - the deployment story now explicitly records:
- a UEFI El Torito boot image - declaration-driven roll-forward
- `/boot/root.img` as the installer mdroot payload - rollback by rebuilding/redeploying an earlier declaration
- the installer closure - current deployment identity via closure path and emitted provenance metadata
- the selected target closure - current environment-specific installer target-device conventions
- the target runtime store closure needed for installation
- in-guest installer state/log/scripts
- validated workflows:
- local QEMU/UEFI/TCG boot, install, and installed-target reboot
- real XCP-ng VM boot, install, and installed-target reboot
- a platform-specific installer detail is now recorded in-tree:
- QEMU ISO path installs onto `/dev/vtbd0`
- XCP-ng ISO path installs onto `/dev/ada0`
Validation: Validation basis referenced by the workflow documentation:
- `PASS phase15-base-rollback-qemu`
- `PASS phase15-base-rollback-xcpng`
- `PASS phase18-system-install`
- `PASS phase18-installer-environment`
- `PASS phase18-installer-iso` - `PASS phase18-installer-iso`
- `PASS phase18-installer-iso-xcpng` - `PASS phase18-installer-iso-xcpng`
Report: Reports:
- `docs/reports/phase18-installer-iso-freebsd.md` - `docs/system-deployment-workflow.md`
- `docs/reports/phase19-deployment-workflow-freebsd.md`
Commits:
- `1970c5c``system: add UEFI installer ISO builder`
- `604ad82``system: validate UEFI installer ISO boot path`
## Recent major milestones ## Recent major milestones
@@ -101,6 +99,6 @@ Commits:
Per `docs/PLAN_4.md`, the next planned step is: Per `docs/PLAN_4.md`, the next planned step is:
- **Phase 19.1** — define and document the canonical Fruix deployment workflow for rebuild, image generation, installation, and rollback - **Phase 19.2** — make the installed-system generation and rollback-root model more explicit
Phase 18.3 is now complete: Fruix can build, boot, install from, and validate a bootable UEFI installer ISO on FreeBSD. Phase 19.1 is now complete: Fruix documents a coherent operator-facing deployment workflow for build, installation, roll-forward, and rollback.

View File

@@ -0,0 +1,143 @@
# Phase 19.1: canonical Fruix deployment workflow on FreeBSD
Date: 2026-04-04
## Goal
Phase 19.1 is about turning Fruix's already-validated closure/image/install behavior into a clear operator-facing deployment story.
The verification target here is documentation clarity rather than a new low-level boot primitive.
The repo needed a single coherent explanation of how Fruix expects operators to:
- build a system closure
- materialize a rootfs or image
- install directly to an image or block device
- use the installer image and installer ISO paths
- roll forward to a candidate declaration
- roll back to an earlier declaration
## Result
Phase 19.1 is complete.
The repository now documents a first-class Fruix deployment workflow in:
- `docs/system-deployment-workflow.md`
That document defines the current canonical command surface and explains how the already-existing validated paths fit together operationally.
## What was documented
### Canonical frontend
The documented user-facing frontend is now explicitly:
- `./bin/fruix system ...`
This includes the currently supported deployment-oriented actions:
- `build`
- `rootfs`
- `image`
- `install`
- `installer`
- `installer-iso`
### Canonical deployment model
The workflow document now defines Fruix's current deployment model as:
1. declare a system in Scheme
2. build the system closure in `/frx/store`
3. materialize the artifact appropriate to the target environment
4. boot or install that artifact
5. treat the resulting closure path and emitted provenance metadata as the deployment identity
### Roll-forward and rollback semantics
The document makes explicit an important current design point:
- Fruix rollback is already real at the declaration/closure/deployment layer
- but it is not yet a first-class installed-system generation switch operation
So the documented rollback workflow today is:
- retain the earlier declaration
- rebuild or rematerialize it
- redeploy or reboot that earlier closure again
That matches what Fruix has already validated in earlier phases.
### Platform-specific installer target-device detail
The workflow document also records the now-important target-device distinctions between validated environments:
- installer disk-image path under QEMU:
- `/dev/vtbd1`
- installer ISO path under QEMU:
- `/dev/vtbd0`
- installer ISO path under XCP-ng:
- `/dev/ada0`
That makes the deployment story less harness-specific and more operator-explicit.
## Why this satisfies Phase 19.1
Before this phase, Fruix already had the machinery for:
- building declarative system closures
- generating bootable images
- performing direct non-interactive installation
- booting a Fruix installer environment
- booting and installing from a Fruix installer ISO
- rollback-friendly redeploy of earlier declarations
What was missing was a repo-level explanation that unified those into a single operator workflow.
The new document closes that gap by connecting:
- Phase 10 command-surface work
- Phase 15 redeploy/rollback validation
- Phase 18 install and installer-media validation
- and the recent QEMU + XCP-ng installer ISO validation
## Current boundaries now made explicit
The documentation intentionally records what Fruix has **not** solved yet:
- installed-system generation links
- explicit rollback targets and generation metadata
- a first-class `switch` or `reconfigure` command
- installed-system rollback as an in-place operator workflow
- GC-root management for installed systems
Those are left for later Phase 19 steps rather than being blurred into the current deployment story.
## References to existing validation
The documented workflow rests on already-passing validation paths, including:
- `PASS phase18-system-install`
- `PASS phase18-installer-environment`
- `PASS phase18-installer-iso`
- `PASS phase18-installer-iso-xcpng`
- `PASS phase15-base-rollback-qemu`
- `PASS phase15-base-rollback-xcpng`
## Conclusion
Phase 19.1 is now complete.
Fruix has a documented canonical deployment workflow for FreeBSD covering:
- build
- image generation
- direct install
- installer-media install
- roll-forward
- rollback by redeploying an earlier declaration
The next step is Phase 19.2:
- model installed-system generations, rollback targets, and deployment roots more explicitly.

View File

@@ -0,0 +1,362 @@
# Fruix system deployment workflow
Date: 2026-04-04
## Purpose
This document defines the current canonical Fruix workflow for:
- building a declarative system closure
- materializing deployable artifacts
- installing a declarative system onto an image or disk
- booting through installer media
- rolling forward to a candidate system
- rolling back to an earlier declared system
This is the Phase 19.1 operator-facing view of the system model already implemented in earlier phases.
## Core model
A Fruix system workflow starts from a Scheme file that binds an `operating-system` object.
Today, the canonical frontend is:
- `./bin/fruix system ...`
The important output objects are:
- **system closure**
- a content-addressed store item under `/frx/store/*-fruix-system-<host-name>`
- includes boot assets, activation logic, profile tree, metadata, and references
- **rootfs tree**
- a materialized runtime tree for inspection or image staging
- **disk image**
- a bootable GPT/UEFI raw disk image
- **installer image**
- a bootable Fruix installer disk image that installs a selected target system from inside the guest
- **installer ISO**
- a bootable UEFI ISO with an embedded installer mdroot payload
- **install metadata**
- `/var/lib/fruix/install.scm` on installed targets
- records the selected closure path, install spec, and referenced store items including source provenance
The current deployment story is therefore already declaration-driven and content-addressed, even before first-class installed-system generations are modeled more explicitly.
## Canonical command surface
### Build a system closure
```sh
sudo env HOME="$HOME" \
GUILE_AUTO_COMPILE=0 \
GUIX_SOURCE_DIR="$HOME/repos/guix" \
GUILE_BIN="/tmp/guile-freebsd-validate-install/bin/guile" \
GUILE_EXTRA_PREFIX="/tmp/guile-gnutls-freebsd-validate-install" \
SHEPHERD_PREFIX="/tmp/shepherd-freebsd-validate-install" \
./bin/fruix system build path/to/system.scm --system my-operating-system
```
Primary result:
- `closure_path=/frx/store/...-fruix-system-...`
Use this when you want to:
- validate the declarative system composition itself
- inspect provenance/layout metadata
- compare candidate and current closure paths
- drive later rootfs/image/install steps from the same declaration
### Materialize a rootfs tree
```sh
sudo env HOME="$HOME" ... \
./bin/fruix system rootfs path/to/system.scm ./rootfs --system my-operating-system
```
Primary result:
- `rootfs=...`
- `closure_path=/frx/store/...`
Use this when you want to:
- inspect the runtime filesystem layout directly
- stage a tree for debugging
- validate `/run/current-system`-style symlink layout without booting a full image
### Materialize a bootable disk image
```sh
sudo env HOME="$HOME" ... \
./bin/fruix system image path/to/system.scm \
--system my-operating-system \
--root-size 6g
```
Primary result:
- `disk_image=/frx/store/.../disk.img`
Use this when you want to:
- boot the system directly as a VM image
- test a candidate deployment under QEMU or XCP-ng
- validate a roll-forward or rollback candidate by image boot
### Install directly to an image file or block device
```sh
sudo env HOME="$HOME" ... \
./bin/fruix system install path/to/system.scm \
--system my-operating-system \
--target ./installed.img \
--disk-capacity 12g \
--root-size 10g
```
Primary result:
- `target=...`
- `target_kind=raw-file` or `block-device`
- `install_metadata_path=/var/lib/fruix/install.scm`
Use this when you want to:
- produce an installed target image without booting an installer guest
- validate installation mechanics directly
- populate a raw image or a real `/dev/...` target
### Materialize a bootable installer disk image
```sh
sudo env HOME="$HOME" ... \
./bin/fruix system installer path/to/system.scm \
--system my-operating-system \
--install-target-device /dev/vtbd1 \
--root-size 10g
```
Primary result:
- `installer_disk_image=/frx/store/.../disk.img`
Use this when you want to:
- boot a Fruix installer environment as a disk image
- let the in-guest installer partition and install onto a second disk
- validate non-interactive installation from inside a booted Fruix guest
### Materialize a bootable installer ISO
```sh
sudo env HOME="$HOME" ... \
./bin/fruix system installer-iso path/to/system.scm \
--system my-operating-system \
--install-target-device /dev/vtbd0
```
Primary result:
- `iso_image=/frx/store/.../installer.iso`
- `boot_efi_image=/frx/store/.../efiboot.img`
- `root_image=/frx/store/.../root.img`
Use this when you want to:
- boot through UEFI ISO media instead of a writable installer disk image
- install from an ISO-attached Fruix environment
- test the same install model on more realistic VM paths
## Deployment patterns
### 1. Build-first workflow
The default Fruix operator workflow starts by building the closure first:
1. edit the system declaration
2. run `fruix system build`
3. inspect emitted metadata
4. if needed, produce one of:
- `rootfs`
- `image`
- `install`
- `installer`
- `installer-iso`
This keeps the declaration-to-closure boundary explicit.
### 2. VM image deployment workflow
Use this when you want to boot a system directly rather than through an installer.
1. run `fruix system image`
2. boot the image in QEMU or convert/import it for XCP-ng
3. validate:
- `/run/current-system`
- shepherd/sshd state
- activation log
4. keep the closure path from the build metadata as the deployment identity
This is the current canonical direct deployment path for already-built images.
### 3. Direct installation workflow
Use this when you want an installed target image or disk without a booted installer guest.
1. run `fruix system install --target ...`
2. let Fruix partition, format, populate, and install the target
3. boot the installed result
4. validate `/var/lib/fruix/install.scm` and target services
This is the most direct install path.
### 4. Installer-environment workflow
Use this when the install itself should happen from inside a booted Fruix environment.
1. run `fruix system installer`
2. boot the installer disk image
3. let the in-guest installer run onto the selected target device
4. boot the installed target
This is useful when the installer environment itself is part of what needs validation.
### 5. Installer-ISO workflow
Use this when the desired operator artifact is a bootable UEFI ISO.
1. run `fruix system installer-iso`
2. boot the ISO under the target virtualization path
3. let the in-guest installer run onto the selected target device
4. eject the ISO and reboot the installed target
This is now validated on both:
- local `QEMU/UEFI/TCG`
- the approved real `XCP-ng` VM path
## Install-target device conventions
The install target device is not identical across all boot styles.
Current validated defaults are:
- direct installer disk-image path under QEMU:
- `/dev/vtbd1`
- installer ISO path under QEMU:
- `/dev/vtbd0`
- installer ISO path under XCP-ng:
- `/dev/ada0`
Therefore the canonical workflow is:
- always treat `--install-target-device` as an explicit deployment parameter when moving between virtualization environments
Do not assume that a device name validated in one harness is portable to another.
## Roll-forward workflow
The current Fruix roll-forward model is declaration-driven.
Canonical process:
1. keep the current known-good system declaration
2. prepare a candidate declaration
- this may differ by FreeBSD base identity
- source revision
- services
- users/groups
- or other operating-system fields
3. run `fruix system build` for the candidate
4. materialize either:
- `fruix system image`
- `fruix system install`
- `fruix system installer`
- `fruix system installer-iso`
5. boot or install the candidate
6. validate the candidate closure in the booted system
The important property is that the candidate closure appears beside the earlier one in `/frx/store` rather than mutating it in place.
## Rollback workflow
The current canonical rollback workflow is also declaration-driven.
Today, rollback means:
1. retain the earlier declaration that produced the known-good closure
2. rebuild or rematerialize that earlier declaration
3. redeploy or reboot that earlier artifact again
Concretely, the usual rollback choices are:
- rebuild the earlier declaration with `fruix system build` and confirm the old closure path reappears
- boot the earlier declaration again through `fruix system image`
- reinstall the earlier declaration through `fruix system install`, `installer`, or `installer-iso` if the deployment medium itself must change
This rollback story has already been validated at the closure/image/deployment level:
- side-by-side base-version coexistence in `/frx/store`
- roll-forward to a candidate closure
- rollback by rebuilding and booting the earlier declaration again
- validation on both local QEMU and the approved XCP-ng VM path
### Important scope note
This is not yet the same thing as a first-class installed-system generation switch command.
Current rollback is:
- **redeploy the earlier declaration again**
Future Phase 19 work is expected to make these more explicit:
- current generation
- previous generation
- rollback target
- installed-system roots and generation links
- an operator-facing installed-system rollback workflow
## Provenance and deployment identity
For any serious deployment or rollback decision, the canonical identity is not merely the host name. It is the emitted metadata:
- `closure_path`
- declared FreeBSD base/source metadata
- materialized source store paths
- install metadata at `/var/lib/fruix/install.scm`
- store item counts and reference lists
Operators should retain metadata from successful candidate and current deployments because Fruix already emits enough data to answer:
- which declaration was built
- which closure booted
- which source snapshot was materialized
- which target device or image was installed
## Current limitations
The deployment workflow is now coherent, but it is not yet the final generation-management story.
Not yet first-class:
- installed-system generation directories and symlink model
- a dedicated `switch` or `reconfigure` command
- an installed-system rollback command that moves between generations in place
- explicit GC-root management for installed systems
Those are the next logical steps after Phase 19.1.
## Summary
The current canonical Fruix deployment model is:
- **declare** a system in Scheme
- **build** the closure with `fruix system build`
- **materialize** the artifact appropriate to the deployment target
- **boot or install** that artifact
- **identify deployments by closure path and provenance metadata**
- **roll back by rebuilding/redeploying the earlier declaration**, not by mutating the current closure in place
That is the operator-facing workflow Fruix should document and use until explicit installed-system generation management is added.