566 lines
17 KiB
Markdown
566 lines
17 KiB
Markdown
# PLAN_INSTALLER
|
|
|
|
## Summary
|
|
|
|
Yes: this paints a consistent picture.
|
|
|
|
The proposed Fruix installer direction is:
|
|
|
|
- **FreeBSD-native underneath** for disk, filesystem, boot, and target-system materialization.
|
|
- **Fruix-native above** for declaration generation, install metadata, identity/pinning, and long-lived product behavior.
|
|
- **Guix-like installer structure** for the interactive installer itself: a Scheme program, organized as explicit steps, with a small **Newt** TUI.
|
|
- **Disko-like storage modeling** as part of the base Fruix system model, not as an installer-only ad hoc script layer.
|
|
|
|
That yields a coherent split:
|
|
|
|
1. **storage declarations are first-class Fruix objects**
|
|
2. **materialization uses native FreeBSD tools**
|
|
3. **the installer is a Fruix application**
|
|
4. **the installed result records both declaration identity and realized storage metadata**
|
|
5. **ordinary `reconfigure` / `switch` / `rollback` do not repartition disks**
|
|
|
|
## Current starting point
|
|
|
|
Already in place:
|
|
|
|
- canonical `fruix` repo/channel exists
|
|
- bootable installer image and installer ISO paths exist
|
|
- installer ISO boot path has been validated
|
|
- minimal scripted install flow exists
|
|
- installed nodes are already real Fruix nodes for build/reconfigure/build-base/deploy/switch/rollback
|
|
|
|
So this plan is **not** starting from zero. It starts from a validated non-interactive installer artifact pipeline and layers a first-class storage model plus an interactive installer UX on top.
|
|
|
|
## Design principles
|
|
|
|
### 1. Storage layout belongs in base Fruix
|
|
|
|
Fruix should gain a first-class storage declaration layer that can be used by:
|
|
|
|
- installer TUI
|
|
- non-interactive `fruix system install`
|
|
- disk image generation
|
|
- installer image generation
|
|
- future remote install / install-on-deploy flows
|
|
|
|
This should not live only inside the installer.
|
|
|
|
### 2. Runtime config and install-time layout are related but distinct
|
|
|
|
Fruix should persist both:
|
|
|
|
- the **declared storage intent**
|
|
- the **realized runtime mount/boot config**
|
|
- the **realized install metadata**
|
|
|
|
But routine system generation operations should not automatically mutate partition tables.
|
|
|
|
### 3. Keep the execution substrate FreeBSD-native
|
|
|
|
Actual realization should shell out to native FreeBSD tools such as:
|
|
|
|
- `gpart`
|
|
- `newfs`
|
|
- `mount`
|
|
- `swapon`
|
|
- `geli` when encryption is added
|
|
- `zpool` / `zfs` when ZFS support is added
|
|
- `mkimg` / image tooling where applicable
|
|
- existing Fruix install/materialization helpers
|
|
|
|
### 4. Keep installer logic in canonical `fruix`
|
|
|
|
The installer TUI, step logic, declaration generation, and install semantics belong in canonical `fruix`, not `fruix-bootstrap`.
|
|
|
|
### 5. Prefer a simple network story first
|
|
|
|
Unlike Guix-on-Linux, Fruix-on-FreeBSD should not assume ConnMan.
|
|
|
|
First version should keep networking deliberately simple:
|
|
|
|
- optional networking
|
|
- wired-first
|
|
- DHCP-first
|
|
- manual static configuration only if needed
|
|
- no Wi-Fi manager requirement in v1
|
|
- no long-lived network-management daemon dependency for the installer UX
|
|
|
|
## Non-goals for v1
|
|
|
|
The first interactive Fruix installer should **not** try to solve all storage and deployment problems.
|
|
|
|
Out of scope for the first version:
|
|
|
|
- automatic storage mutation during ordinary `fruix system reconfigure`
|
|
- post-install partition resizing/migration engine
|
|
- full multi-disk orchestration
|
|
- Wi-Fi-first installer UX
|
|
- exhaustive ZFS layout support on day one
|
|
- polished remote install orchestration from the installer itself
|
|
- replacing the existing validated scripted install path immediately
|
|
|
|
## Target architecture
|
|
|
|
## Layer 1: Fruix storage declaration model
|
|
|
|
Add a base Fruix storage model that represents:
|
|
|
|
- target disk(s)
|
|
- partition table type
|
|
- partitions
|
|
- labels
|
|
- filesystem type
|
|
- mount points
|
|
- swap
|
|
- optional boot partition details
|
|
- optional encryption layer
|
|
- later: ZFS/UFS specialization and multi-disk layouts
|
|
|
|
This should be declarative and serializable into metadata.
|
|
|
|
Suggested module direction:
|
|
|
|
- `modules/fruix/system/storage/model.scm`
|
|
- `modules/fruix/system/storage/validate.scm`
|
|
- `modules/fruix/system/storage/render.scm`
|
|
- `modules/fruix/system/freebsd/storage.scm` or equivalent backend module
|
|
|
|
Generic storage intent should stay separate from FreeBSD-specific realization.
|
|
|
|
## Layer 2: FreeBSD materialization backend
|
|
|
|
Add a backend that turns storage declarations into explicit, ordered actions:
|
|
|
|
- probe disks
|
|
- validate target device selection
|
|
- create partition table
|
|
- create partitions
|
|
- make filesystems
|
|
- enable swap
|
|
- mount target tree
|
|
- render boot/runtime config files
|
|
- populate/install Fruix system closure
|
|
- install boot support
|
|
|
|
This should look more like Disko's “generate apply scripts from declarations” than like ad hoc shell embedded throughout the installer.
|
|
|
|
Important: the backend should support both:
|
|
|
|
- **dry-run / plan rendering**
|
|
- **real application**
|
|
|
|
## Layer 3: Fruix installer application
|
|
|
|
Add an installer application in Scheme, structured more like Guix's installer:
|
|
|
|
- explicit installer state object
|
|
- ordered steps
|
|
- step-local validation
|
|
- generated final declaration
|
|
- final install/apply step
|
|
|
|
Suggested module direction:
|
|
|
|
- `modules/fruix/installer.scm`
|
|
- `modules/fruix/installer/state.scm`
|
|
- `modules/fruix/installer/steps.scm`
|
|
- `modules/fruix/installer/final.scm`
|
|
- `modules/fruix/installer/newt.scm`
|
|
- `modules/fruix/installer/newt/*.scm`
|
|
|
|
## Layer 4: Metadata and persistence
|
|
|
|
Persist:
|
|
|
|
- the selected/generated Fruix system declaration
|
|
- the selected/generated storage declaration
|
|
- realized disk/filesystem identifiers
|
|
- install target details
|
|
- Fruix channel identity / origin / revision where available
|
|
- installer version / plan version
|
|
|
|
Record this both:
|
|
|
|
- in closure metadata where appropriate
|
|
- in installed-system state under Fruix-managed metadata paths
|
|
|
|
## Proposed commands and behavior
|
|
|
|
Near-term expected command surface:
|
|
|
|
- existing `fruix system installer`
|
|
- existing `fruix system installer-iso`
|
|
- existing `fruix system install`
|
|
- new internal or public storage application entrypoint
|
|
- installer TUI launched inside Fruix installer media
|
|
|
|
Possible later public storage commands:
|
|
|
|
- `fruix system storage-plan ...`
|
|
- `fruix system storage-apply ...`
|
|
- `fruix system install --layout FILE ...`
|
|
|
|
The exact CLI naming can be deferred. The important part is the architecture: a first-class storage layer reused by the installer and non-interactive install paths.
|
|
|
|
## Step-by-step implementation plan
|
|
|
|
## Step 1: Write down the storage semantics before coding
|
|
|
|
Define the product semantics for Fruix storage declarations.
|
|
|
|
Deliverables:
|
|
|
|
- document the distinction between:
|
|
- declared storage intent
|
|
- realized storage metadata
|
|
- runtime mount/boot configuration
|
|
- document the safety boundary:
|
|
- installer/install actions may partition/format
|
|
- `reconfigure` / `switch` / `rollback` may not partition/format by default
|
|
- define v1 supported layouts
|
|
|
|
Recommended v1 supported layouts:
|
|
|
|
- single-disk GPT
|
|
- EFI system partition
|
|
- UFS root filesystem
|
|
- optional swap partition
|
|
- optional separate `/var`
|
|
|
|
Exit criteria:
|
|
|
|
- Fruix has a stable written storage model contract before implementation starts
|
|
|
|
## Step 2: Add the base storage declaration model to canonical Fruix
|
|
|
|
Implement first-class storage records and constructors.
|
|
|
|
Deliverables:
|
|
|
|
- generic storage record types
|
|
- normalization and validation helpers
|
|
- deterministic metadata rendering
|
|
- support for embedding storage layout in or alongside operating-system declarations
|
|
|
|
Requirements:
|
|
|
|
- must be reusable by installer, image builder, and non-interactive install
|
|
- must not hard-code installer UI concerns into the model
|
|
- must preserve future room for ZFS/encryption/multi-disk extensions
|
|
|
|
Exit criteria:
|
|
|
|
- a declaration can represent a concrete install target layout without performing side effects
|
|
|
|
## Step 3: Add a FreeBSD storage realization backend
|
|
|
|
Implement the backend that turns storage declarations into ordered materialization operations.
|
|
|
|
Deliverables:
|
|
|
|
- disk probing helpers
|
|
- validation of target devices
|
|
- plan generation
|
|
- dry-run text rendering
|
|
- actual apply path that shells out to FreeBSD tools
|
|
|
|
The backend should initially support:
|
|
|
|
- `gpart create`
|
|
- `gpart add`
|
|
- `newfs_msdos` or equivalent for EFI where needed
|
|
- `newfs` for UFS root
|
|
- `swapon` configuration
|
|
- mount tree creation
|
|
- target population handoff into existing Fruix install/materialization code
|
|
|
|
Prefer explicit, testable helper functions over one giant shell script.
|
|
|
|
Exit criteria:
|
|
|
|
- Fruix can take a storage declaration and non-interactively realize it onto a raw disk/image using native FreeBSD tooling
|
|
|
|
## Step 4: Rework non-interactive install to use the storage layer
|
|
|
|
Refactor `fruix system install` so it consumes the new storage declaration layer rather than ad hoc installer-only disk logic.
|
|
|
|
Deliverables:
|
|
|
|
- `fruix system install` path routed through the shared storage backend
|
|
- existing installer image / installer ISO install path updated to use the same backend
|
|
- no duplication of partitioning logic between installer and CLI install path
|
|
|
|
Exit criteria:
|
|
|
|
- one shared storage/apply path is used by both scripted install and future TUI install
|
|
|
|
## Step 5: Persist storage declaration and realized layout metadata
|
|
|
|
Add explicit metadata capture.
|
|
|
|
Deliverables:
|
|
|
|
- declared storage layout snapshot in system metadata
|
|
- realized partition/filesystem metadata snapshot after install
|
|
- linkage from installed system generation to install provenance
|
|
- clear versioning of metadata schema
|
|
|
|
Examples of useful recorded data:
|
|
|
|
- target device used at install time
|
|
- partition labels and filesystem types
|
|
- UUIDs / provider names discovered after creation
|
|
- boot partition details
|
|
- install timestamp
|
|
- Fruix declaration identity and source origin
|
|
|
|
Exit criteria:
|
|
|
|
- Fruix can answer both “what layout was intended?” and “what was actually installed?”
|
|
|
|
## Step 6: Package and validate the Newt stack for Fruix on FreeBSD
|
|
|
|
Before building the real TUI, make the UI dependency story explicit.
|
|
|
|
Deliverables:
|
|
|
|
- package definition for `newt` if not already available through Fruix inputs
|
|
- package definition or integration path for Guile bindings to Newt
|
|
- builder/runtime validation on FreeBSD
|
|
- minimal proof-of-life TUI program in Fruix
|
|
|
|
Notes:
|
|
|
|
- target end state is a Guix-like Scheme + Newt installer structure
|
|
- if Guile bindings need bring-up work on FreeBSD, do that here
|
|
- avoid coupling installer design to bootstrap repo specifics
|
|
|
|
Exit criteria:
|
|
|
|
- Fruix can run a tiny Scheme/Newt UI inside a FreeBSD Fruix environment
|
|
|
|
## Step 7: Build the installer state machine and step framework
|
|
|
|
Implement the Guix-like installer structure.
|
|
|
|
Deliverables:
|
|
|
|
- installer state object
|
|
- step descriptors
|
|
- step ordering and back/next flow
|
|
- step-local validation
|
|
- final rendering of generated declaration(s)
|
|
|
|
Suggested initial steps:
|
|
|
|
1. welcome
|
|
2. keyboard/console assumptions if needed
|
|
3. target disk selection
|
|
4. storage layout selection/edit
|
|
5. hostname
|
|
6. root/user setup
|
|
7. network choice (optional/simple)
|
|
8. summary/review
|
|
9. install/apply
|
|
10. completion/reboot/shell
|
|
|
|
Exit criteria:
|
|
|
|
- installer logic exists independently of the final widgets used for each step
|
|
|
|
## Step 8: Implement the first small Newt TUI
|
|
|
|
Build the actual interactive UI on top of the step framework.
|
|
|
|
Deliverables:
|
|
|
|
- main installer entrypoint launched by Fruix installer media
|
|
- Newt forms/widgets for each initial step
|
|
- summary screen showing:
|
|
- chosen Fruix system settings
|
|
- chosen storage layout
|
|
- clear destructive-action warnings
|
|
- failure screen with logs/shell escape path
|
|
|
|
Requirements:
|
|
|
|
- keep it small and reliable
|
|
- prefer clear text prompts over elaborate UI behaviors
|
|
- always provide a shell/log escape hatch during install
|
|
|
|
Exit criteria:
|
|
|
|
- a user can interactively choose a v1 layout and reach a final review screen
|
|
|
|
## Step 9: Keep networking simple and explicit
|
|
|
|
Implement only a minimal network story for the installer.
|
|
|
|
Deliverables:
|
|
|
|
- detect interfaces
|
|
- optional “configure wired networking” step
|
|
- DHCP-first support via native FreeBSD tools
|
|
- optional manual static configuration if easy to support cleanly
|
|
- no ConnMan dependency
|
|
- network may be entirely skipped when installing from fully prebuilt local artifacts
|
|
|
|
Likely implementation approach:
|
|
|
|
- use `ifconfig`, `dhclient`, `route`, and resolver file updates as needed
|
|
- treat networking as a helper step, not as a central installer subsystem
|
|
|
|
Exit criteria:
|
|
|
|
- installer can fetch or validate network reachability when needed without adopting a heavy network-management stack
|
|
|
|
## Step 10: Wire installer apply step to the shared backend
|
|
|
|
Connect the TUI's final “install” action to the already-implemented shared storage and install backend.
|
|
|
|
Deliverables:
|
|
|
|
- summary confirmation before destructive actions
|
|
- apply progress view
|
|
- disk layout realization
|
|
- target system population
|
|
- boot setup
|
|
- metadata persistence
|
|
- success/failure handling
|
|
|
|
Requirements:
|
|
|
|
- no TUI-specific partitioning logic
|
|
- all real disk mutation goes through the shared backend
|
|
- logs remain available for debugging and validation
|
|
|
|
Exit criteria:
|
|
|
|
- interactive and non-interactive installs are two frontends to the same Fruix install engine
|
|
|
|
## Step 11: Update installer media to launch the new Fruix installer app
|
|
|
|
Promote the TUI from an internal component to the default installer UX.
|
|
|
|
Deliverables:
|
|
|
|
- installer image launches Fruix installer application by default
|
|
- shell remains available as an escape hatch
|
|
- installer ISO/test paths updated accordingly
|
|
- existing scripted fallback remains available during transition if needed
|
|
|
|
Exit criteria:
|
|
|
|
- booting the Fruix installer media starts the Fruix Newt installer by default
|
|
|
|
## Step 12: Add end-to-end validation harnesses
|
|
|
|
Extend the existing validation style with real installer coverage.
|
|
|
|
Deliverables:
|
|
|
|
- syntax/unit validation for storage declarations and backend rendering
|
|
- non-interactive storage apply tests on raw disk images
|
|
- installer media boot test that reaches the TUI
|
|
- automated or semi-automated install test for the v1 layout
|
|
- real XCP-ng validation for the full install path
|
|
- regression coverage for installed-node boot/switch/rollback after installation
|
|
|
|
Suggested validation milestones:
|
|
|
|
- build storage declaration -> render plan -> apply to raw image
|
|
- installer image boots -> Newt UI launches
|
|
- interactive or scripted test chooses default v1 layout -> install completes
|
|
- installed system boots and behaves as a real Fruix node
|
|
|
|
Exit criteria:
|
|
|
|
- the installer is validated as a product workflow, not only as a boot artifact
|
|
|
|
## Step 13: Expand storage capabilities after the v1 path is solid
|
|
|
|
Once the basic path is validated, add richer layouts incrementally.
|
|
|
|
Next candidates:
|
|
|
|
- ZFS root layouts
|
|
- encryption layer
|
|
- separate `/home`
|
|
- mirrored boot/root layouts
|
|
- multi-disk declarations
|
|
- image-builder reuse of the same storage declaration object
|
|
|
|
Rule:
|
|
|
|
- every new layout feature must extend the shared storage model/backend rather than adding installer-only special cases
|
|
|
|
## Recommended implementation order by milestone
|
|
|
|
### Milestone A: base storage model
|
|
|
|
- Step 1
|
|
- Step 2
|
|
- Step 3
|
|
|
|
### Milestone B: shared install engine
|
|
|
|
- Step 4
|
|
- Step 5
|
|
|
|
### Milestone C: UI substrate
|
|
|
|
- Step 6
|
|
- Step 7
|
|
|
|
### Milestone D: first interactive installer
|
|
|
|
- Step 8
|
|
- Step 9
|
|
- Step 10
|
|
- Step 11
|
|
|
|
### Milestone E: validation and expansion
|
|
|
|
- Step 12
|
|
- Step 13
|
|
|
|
## Concrete v1 scope recommendation
|
|
|
|
To keep the first Fruix installer manageable, v1 should target exactly this workflow:
|
|
|
|
- boot Fruix installer ISO
|
|
- choose target disk
|
|
- choose default GPT layout
|
|
- create:
|
|
- EFI partition
|
|
- root UFS partition
|
|
- optional swap
|
|
- enter hostname and initial credentials
|
|
- optionally bring up wired DHCP
|
|
- install prebuilt Fruix system artifact
|
|
- record Fruix declaration + storage metadata
|
|
- reboot into installed Fruix node
|
|
|
|
That is enough to prove the full architecture without getting stuck in storage-combinator explosion.
|
|
|
|
## Consistency check
|
|
|
|
This plan is internally consistent with current Fruix direction because it keeps the existing product boundaries intact:
|
|
|
|
- canonical `fruix` remains the source of truth
|
|
- `fruix-bootstrap` remains only a builder-preparation layer
|
|
- installer logic becomes a Fruix feature, not a bootstrap feature
|
|
- storage layout becomes a reusable Fruix declaration, not a one-off install script
|
|
- FreeBSD-specific disk actions remain native and practical
|
|
- installed systems remain real Fruix nodes after boot
|
|
|
|
## Immediate next action
|
|
|
|
The recommended next implementation task is:
|
|
|
|
1. write the v1 storage semantics document
|
|
2. add the first base storage declaration model to canonical Fruix
|
|
3. route existing non-interactive install paths through that shared model/backend before building the Newt UI
|
|
|
|
That sequencing prevents the TUI from hard-coding one-off install logic and keeps the installer aligned with long-lived Fruix system behavior.
|