# 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/