622 lines
16 KiB
Markdown
622 lines
16 KiB
Markdown
# STORAGE_MODEL
|
|
|
|
## Summary
|
|
|
|
This document defines the first Fruix storage-model direction for the installer work described in `docs/PLAN_INSTALLER.md`.
|
|
|
|
Recommendation:
|
|
|
|
- use a Fruix-native name such as **`storage-layout`**
|
|
- do **not** adopt `disko` as a Fruix subsystem name
|
|
- do **not** make storage mutation part of routine `reconfigure` / `switch` / `rollback`
|
|
- make storage declarations a base Fruix concept reused by installer, non-interactive install, and image generation
|
|
|
|
In short:
|
|
|
|
- **Disko-like idea**
|
|
- **Fruix-native naming and semantics**
|
|
- **FreeBSD-native realization**
|
|
|
|
## Naming
|
|
|
|
## Chosen public term
|
|
|
|
Use:
|
|
|
|
- **storage-layout**
|
|
|
|
Related terms:
|
|
|
|
- `storage-disk`
|
|
- `storage-partition`
|
|
- `partition-file-system`
|
|
- `partition-swap`
|
|
- `realized-storage-layout`
|
|
|
|
## Terms to avoid as Fruix subsystem names
|
|
|
|
Avoid using these as the Fruix public subsystem name:
|
|
|
|
- `disko`
|
|
- `frisko`
|
|
|
|
Reasoning:
|
|
|
|
- `disko` already refers to a real Nix project with its own semantics and docs
|
|
- `frisko` is fun, but too cute for a core long-lived product surface
|
|
- `storage-layout` is clearer in code, docs, and CLI design
|
|
|
|
We can still say **“Disko-like”** in architecture notes when comparing ideas.
|
|
|
|
## Product goal
|
|
|
|
Fruix needs a first-class way to describe how storage should be laid out for installation.
|
|
|
|
That declaration should be reusable by:
|
|
|
|
- the interactive installer
|
|
- `fruix system install`
|
|
- disk image generation
|
|
- installer media generation
|
|
- later install/deploy workflows
|
|
|
|
It should also preserve the distinction between:
|
|
|
|
1. **declared storage intent**
|
|
2. **realized storage result**
|
|
3. **runtime mount/boot configuration**
|
|
|
|
Those three things are related, but they are not the same object.
|
|
|
|
## Core semantics
|
|
|
|
## 1. Declared storage intent
|
|
|
|
A `storage-layout` describes what Fruix intends to create.
|
|
|
|
Examples:
|
|
|
|
- one GPT disk
|
|
- an EFI partition
|
|
- a UFS root filesystem
|
|
- an optional swap partition
|
|
- an optional separate `/var`
|
|
|
|
This object is:
|
|
|
|
- declarative
|
|
- serializable
|
|
- safe to build/evaluate without mutating disks
|
|
- reusable across installer and non-interactive install flows
|
|
|
|
This is the Fruix equivalent of the good part of the Disko model.
|
|
|
|
## 2. Realized storage result
|
|
|
|
A realized storage result records what was actually created on a specific machine or image.
|
|
|
|
Examples:
|
|
|
|
- target device `/dev/ada0`
|
|
- partition labels actually written
|
|
- UFS labels actually created
|
|
- GPT partition UUIDs if available
|
|
- final runtime device paths used in generated config
|
|
|
|
This object is:
|
|
|
|
- machine-specific
|
|
- produced only after a successful apply/install step
|
|
- stored as install metadata / provenance
|
|
|
|
## 3. Runtime mount and boot configuration
|
|
|
|
The running Fruix system still needs concrete runtime configuration.
|
|
|
|
Examples:
|
|
|
|
- root mounted from `/dev/ufs/fruix-root`
|
|
- EFI mounted from `/dev/gpt/fruix-efi`
|
|
- swap activated from `/dev/gpt/fruix-swap`
|
|
- generated `fstab` / boot metadata / loader-related config
|
|
|
|
This object is:
|
|
|
|
- what the installed system actually uses at boot/runtime
|
|
- derived from the layout and/or realized result
|
|
- not a command to repartition the machine later
|
|
|
|
## Safety boundary
|
|
|
|
This is the most important semantic rule.
|
|
|
|
### Allowed to mutate storage
|
|
|
|
These workflows may partition/format/mount:
|
|
|
|
- installer TUI final apply step
|
|
- `fruix system install`
|
|
- image-building paths that explicitly materialize a target disk/image layout
|
|
- future explicitly named storage-apply/install commands
|
|
|
|
### Not allowed to mutate storage by default
|
|
|
|
These workflows must **not** repartition/reformat disks by default:
|
|
|
|
- `fruix system build`
|
|
- `fruix system reconfigure`
|
|
- `fruix system switch`
|
|
- `fruix system rollback`
|
|
- `fruix system deploy`
|
|
|
|
If Fruix later grows explicit storage migration commands, they should remain separate from ordinary generation switching.
|
|
|
|
## Relationship to the existing `operating-system` model
|
|
|
|
Current Fruix `operating-system` objects already carry runtime file-system information via `file-systems`.
|
|
|
|
That should remain true.
|
|
|
|
For the installer work, Fruix should add an **optional** storage declaration alongside existing runtime configuration.
|
|
|
|
Recommended direction:
|
|
|
|
- `file-systems` remains the runtime/booted-system view
|
|
- `storage-layout` becomes the install-time declarative view
|
|
- Fruix validates that the two are compatible when both are present
|
|
- Fruix may later derive more of `file-systems` automatically from `storage-layout`, but v1 does not need to force that change everywhere
|
|
|
|
This keeps the existing API stable while adding the new storage layer incrementally.
|
|
|
|
## Proposed `operating-system` integration
|
|
|
|
Add an optional field conceptually like:
|
|
|
|
- `storage-layout`
|
|
|
|
So the model becomes:
|
|
|
|
- host/users/services/etc. = running system intent
|
|
- file-systems = runtime mount intent
|
|
- storage-layout = install-time disk realization intent
|
|
|
|
That means a Fruix system declaration can eventually answer both:
|
|
|
|
- “how should this installed system boot and mount?”
|
|
- “how should its disk have been created?”
|
|
|
|
## Proposed v1 scope
|
|
|
|
V1 should intentionally be small.
|
|
|
|
### Supported layout
|
|
|
|
- single target disk
|
|
- GPT partition table
|
|
- UEFI boot
|
|
- EFI system partition
|
|
- UFS root filesystem
|
|
- optional swap partition
|
|
- optional separate `/var`
|
|
|
|
### Deferred
|
|
|
|
- ZFS root
|
|
- encryption
|
|
- multi-disk layouts
|
|
- mirrored boot
|
|
- RAID
|
|
- LVM-like layering
|
|
- post-install storage migration
|
|
- Wi-Fi-oriented installer behaviors
|
|
|
|
## Proposed model shape
|
|
|
|
This section proposes the shape of the first Fruix storage records. It is a design target, not yet a frozen implementation.
|
|
|
|
## Top-level object: `storage-layout`
|
|
|
|
Suggested responsibilities:
|
|
|
|
- identify the layout version
|
|
- record platform/backend intent
|
|
- hold one or more disks
|
|
- define boot mode
|
|
- define install-time defaults / policy
|
|
|
|
Suggested fields:
|
|
|
|
- `version`
|
|
- `platform` (initially `'freebsd`)
|
|
- `boot-mode` (initially `'uefi`)
|
|
- `disks`
|
|
- `name` or `profile-name`
|
|
- optional description/metadata
|
|
|
|
Example conceptual form:
|
|
|
|
```scheme
|
|
(storage-layout
|
|
#:name "default-efi-ufs"
|
|
#:version "1"
|
|
#:platform 'freebsd
|
|
#:boot-mode 'uefi
|
|
#:disks (list ...))
|
|
```
|
|
|
|
## Disk object: `storage-disk`
|
|
|
|
Suggested responsibilities:
|
|
|
|
- identify one install target disk
|
|
- define partition table type
|
|
- carry ordered partitions
|
|
- optionally carry selection/binding information
|
|
|
|
Suggested fields:
|
|
|
|
- `name` or `role` (example: `'system`)
|
|
- `device` or binding selector
|
|
- `partition-table` (v1: `'gpt`)
|
|
- `partitions`
|
|
|
|
Example conceptual form:
|
|
|
|
```scheme
|
|
(storage-disk
|
|
#:name 'system
|
|
#:device "/dev/ada0"
|
|
#:partition-table 'gpt
|
|
#:partitions (list ...))
|
|
```
|
|
|
|
## Partition object: `storage-partition`
|
|
|
|
Suggested responsibilities:
|
|
|
|
- define a partition's role and size
|
|
- define labels
|
|
- attach a content object
|
|
|
|
Suggested fields:
|
|
|
|
- `name`
|
|
- `role` (example: `'efi`, `'root`, `'swap`, `'var`)
|
|
- `size`
|
|
- `gpt-type` or type shortcut
|
|
- `label`
|
|
- `content`
|
|
|
|
Example conceptual form:
|
|
|
|
```scheme
|
|
(storage-partition
|
|
#:name 'root
|
|
#:role 'root
|
|
#:size "100%"
|
|
#:label "fruix-root"
|
|
#:content (partition-file-system ...))
|
|
```
|
|
|
|
## Content variants for v1
|
|
|
|
V1 needs only a small set of partition content kinds.
|
|
|
|
### `partition-file-system`
|
|
|
|
Suggested fields:
|
|
|
|
- `format` (initially `'ufs` or `'msdosfs`/`'vfat` equivalent for EFI)
|
|
- `mount-point`
|
|
- `mount-options`
|
|
- `label`
|
|
- `needed-for-boot?`
|
|
|
|
### `partition-swap`
|
|
|
|
Suggested fields:
|
|
|
|
- `label`
|
|
- optional priority/options
|
|
|
|
### `partition-empty`
|
|
|
|
For reserved/unformatted cases if needed later.
|
|
|
|
## Concrete v1 example
|
|
|
|
A concrete v1 declaration might conceptually look like this:
|
|
|
|
```scheme
|
|
(operating-system
|
|
#:host-name "fruix-node"
|
|
#:file-systems
|
|
(list (file-system #:device "/dev/ufs/fruix-root"
|
|
#:mount-point "/"
|
|
#:type "ufs"
|
|
#:options "rw"
|
|
#:needed-for-boot? #t)
|
|
(file-system #:device "/dev/gpt/fruix-efi"
|
|
#:mount-point "/boot/efi"
|
|
#:type "msdosfs"
|
|
#:options "rw"
|
|
#:needed-for-boot? #t)
|
|
(file-system #:device "devfs"
|
|
#:mount-point "/dev"
|
|
#:type "devfs"
|
|
#:options "rw"
|
|
#:needed-for-boot? #t))
|
|
#:storage-layout
|
|
(storage-layout
|
|
#:name "default-efi-ufs"
|
|
#:version "1"
|
|
#:platform 'freebsd
|
|
#:boot-mode 'uefi
|
|
#:disks
|
|
(list
|
|
(storage-disk
|
|
#:name 'system
|
|
#:device "/dev/ada0"
|
|
#:partition-table 'gpt
|
|
#:partitions
|
|
(list
|
|
(storage-partition
|
|
#:name 'efi
|
|
#:role 'efi
|
|
#:size "512m"
|
|
#:label "fruix-efi"
|
|
#:content (partition-file-system
|
|
#:format 'msdosfs
|
|
#:mount-point "/boot/efi"
|
|
#:mount-options '("rw")
|
|
#:needed-for-boot? #t))
|
|
(storage-partition
|
|
#:name 'swap
|
|
#:role 'swap
|
|
#:size "2g"
|
|
#:label "fruix-swap"
|
|
#:content (partition-swap #:label "fruix-swap"))
|
|
(storage-partition
|
|
#:name 'root
|
|
#:role 'root
|
|
#:size "100%"
|
|
#:label "fruix-root"
|
|
#:content (partition-file-system
|
|
#:format 'ufs
|
|
#:label "fruix-root"
|
|
#:mount-point "/"
|
|
#:mount-options '("rw")
|
|
#:needed-for-boot? #t)))))))
|
|
```
|
|
|
|
The exact constructor names may change, but the semantic split should remain.
|
|
|
|
## Device binding semantics
|
|
|
|
This is the main question where Fruix should stay simple at first.
|
|
|
|
## V1 rule
|
|
|
|
In v1, it is acceptable for the applied layout to use a concrete device path during install, such as:
|
|
|
|
- `/dev/ada0`
|
|
- `/dev/vtbd0`
|
|
- image file target translated by the install backend
|
|
|
|
That keeps the first implementation tractable.
|
|
|
|
## Later extension
|
|
|
|
Later Fruix may add more abstract binding forms, for example:
|
|
|
|
- symbolic disk roles
|
|
- serial-based selection
|
|
- by-path selection
|
|
- interactive installer selection bound at apply time
|
|
|
|
But that should be an extension to the same model, not a separate subsystem.
|
|
|
|
## FreeBSD-specific runtime naming policy
|
|
|
|
For installed runtime configuration, Fruix should prefer stable FreeBSD-native names where possible.
|
|
|
|
Recommended initial policy:
|
|
|
|
- UFS root mounts via `/dev/ufs/<label>`
|
|
- EFI partition mounts via `/dev/gpt/<label>`
|
|
- swap uses `/dev/gpt/<label>`
|
|
|
|
Why this helps:
|
|
|
|
- avoids fragile raw device-number coupling
|
|
- makes realized runtime config clearer
|
|
- aligns with native FreeBSD device naming conventions
|
|
|
|
The realized metadata should still record the raw underlying install target device and any discovered GPT UUID/provider details for provenance.
|
|
|
|
## Realized metadata shape
|
|
|
|
After a successful apply/install, Fruix should record a `realized-storage-layout` object or equivalent metadata file.
|
|
|
|
Suggested recorded data:
|
|
|
|
- layout schema version
|
|
- original declared storage layout snapshot
|
|
- install target disk path
|
|
- created partition labels
|
|
- created filesystem labels
|
|
- discovered runtime device paths
|
|
- mount points
|
|
- swap devices
|
|
- boot mode
|
|
- install timestamp
|
|
- Fruix declaration identity
|
|
- Fruix channel/origin/revision when available
|
|
|
|
Suggested install metadata paths on the installed node:
|
|
|
|
- `/var/lib/fruix/system/install/storage-layout.scm`
|
|
- `/var/lib/fruix/system/install/realized-storage-layout.scm`
|
|
- `/var/lib/fruix/system/install/install-metadata.scm`
|
|
|
|
Exact paths can still change, but the distinction between declared and realized metadata should remain explicit.
|
|
|
|
## Apply/backend semantics
|
|
|
|
The storage backend should be generated from the declaration model, not hand-coded independently in each installer path.
|
|
|
|
For v1, the backend should be able to produce:
|
|
|
|
- a **plan** view
|
|
- a **dry-run script or ordered action list**
|
|
- a **real apply** execution path
|
|
|
|
The apply path should shell out to native FreeBSD tools such as:
|
|
|
|
- `gpart`
|
|
- `newfs`
|
|
- `newfs_msdos` or equivalent EFI filesystem formatter
|
|
- `mount`
|
|
- `umount`
|
|
- `swapon`
|
|
- boot/install helpers already used by Fruix install flows
|
|
|
|
That backend should be shared by:
|
|
|
|
- installer TUI final apply step
|
|
- `fruix system install`
|
|
- image-building paths where possible
|
|
|
|
## Validation rules for the declaration model
|
|
|
|
The first validation layer should reject obviously unsafe or inconsistent layouts.
|
|
|
|
Examples:
|
|
|
|
- no disks defined
|
|
- more than one disk in a v1 single-disk layout
|
|
- unsupported partition table type
|
|
- duplicate partition names or labels
|
|
- missing root filesystem
|
|
- multiple root filesystems in a single-disk v1 layout
|
|
- EFI required for UEFI boot mode but not present
|
|
- mount-point conflicts
|
|
- unsupported filesystem types for the current backend
|
|
- missing concrete device binding when performing a real apply
|
|
|
|
## Relationship to installer UX
|
|
|
|
The installer UI should not invent its own storage logic.
|
|
|
|
Instead, it should:
|
|
|
|
1. gather user choices
|
|
2. produce or modify a `storage-layout`
|
|
3. validate it
|
|
4. show a final review/summary
|
|
5. hand it to the shared backend
|
|
|
|
That keeps the installer TUI as a frontend, not as the source of truth.
|
|
|
|
## Relationship to non-interactive install
|
|
|
|
`fruix system install` should use the same model and backend.
|
|
|
|
In practice that means:
|
|
|
|
- the CLI may consume a declaration already containing a `storage-layout`
|
|
- or accept install-time device overrides/bindings
|
|
- then validate and apply through the same backend used by the installer UI
|
|
|
|
This is the main reason to make `storage-layout` part of base Fruix rather than a UI-only concept.
|
|
|
|
## Relationship to system rebuild/reconfigure
|
|
|
|
A booted Fruix node may continue to carry the original `storage-layout` as metadata or declaration state.
|
|
|
|
But:
|
|
|
|
- `reconfigure` should rebuild/switch generations
|
|
- `switch` should change the active generation
|
|
- `rollback` should change the active generation backward
|
|
- none of those should repartition the disk simply because a `storage-layout` exists in the declaration
|
|
|
|
That rule should be baked into the semantics from the start.
|
|
|
|
## Recommended module direction
|
|
|
|
Suggested new modules:
|
|
|
|
- `modules/fruix/system/storage/model.scm`
|
|
- `modules/fruix/system/storage/validate.scm`
|
|
- `modules/fruix/system/storage/render.scm`
|
|
- `modules/fruix/system/freebsd/storage.scm`
|
|
|
|
Suggested responsibilities:
|
|
|
|
### `model.scm`
|
|
|
|
- record types
|
|
- constructors
|
|
- normalization helpers
|
|
- metadata/spec rendering
|
|
|
|
### `validate.scm`
|
|
|
|
- structural validation
|
|
- v1 policy checks
|
|
- compatibility checks with `operating-system`
|
|
|
|
### `render.scm`
|
|
|
|
- plan rendering
|
|
- dry-run text output
|
|
- metadata serialization helpers
|
|
|
|
### `freebsd/storage.scm`
|
|
|
|
- FreeBSD disk probing
|
|
- apply ordering
|
|
- execution of native tools
|
|
- realized metadata collection
|
|
|
|
## Immediate v1 implementation recommendation
|
|
|
|
Implement in this order:
|
|
|
|
1. add the base `storage-layout` record types
|
|
2. add structural validation
|
|
3. add spec/metadata serialization
|
|
4. extend `operating-system` with optional `storage-layout`
|
|
5. add a minimal FreeBSD backend for:
|
|
- GPT
|
|
- EFI
|
|
- UFS root
|
|
- optional swap
|
|
6. route `fruix system install` through that backend
|
|
7. only then build the Newt installer UI on top
|
|
|
|
That order prevents the UI from hard-coding installer-only storage behavior.
|
|
|
|
## Open questions intentionally deferred
|
|
|
|
These are real design questions, but they do not need to block v1.
|
|
|
|
- whether `file-systems` can later be partially or fully derived from `storage-layout`
|
|
- how best to model ZFS datasets in a Fruix-native way
|
|
- how to model encryption layers cleanly on FreeBSD
|
|
- whether device binding should later support serial/path matching in the declaration itself
|
|
- what the final public CLI names for standalone storage planning/apply commands should be
|
|
|
|
## Final recommendation
|
|
|
|
Fruix should adopt the **Disko-like architecture** but not the Disko name.
|
|
|
|
The right Fruix model is:
|
|
|
|
- **`storage-layout`** as a first-class declaration
|
|
- **FreeBSD-native** realization backend
|
|
- **explicit realized metadata** after installation
|
|
- **shared install engine** for both TUI and CLI install
|
|
- **no implicit repartitioning** during routine system lifecycle operations
|