diff --git a/PROMPT.md b/PROMPT.md index b1bd27e..6a01f10 100644 --- a/PROMPT.md +++ b/PROMPT.md @@ -24,7 +24,7 @@ Device Discovery Partitioning Requirements - Use GPT exclusively. Honor 1 MiB alignment boundaries. -- For BIOS compatibility, create a small `bios_boot` partition (exact size TBD—assume 1 MiB for now, placed first). +- For BIOS compatibility on BIOS systems, create a small `bios_boot` partition (size 1 MiB, placed first). When running under UEFI (`/sys/firmware/efi` present), the BIOS boot partition is suppressed. - Create a 512 MiB FAT32 ESP on each disk, label `ZOSBOOT`. Each ESP is independent; synchronization will be handled by another tool (out of scope). Ensure unique partition UUIDs while keeping identical labels. - Remaining disk capacity is provisioned per configuration (see below). - Before making changes, verify the device has no existing partitions or filesystem signatures; abort otherwise. @@ -51,11 +51,11 @@ Filesystem Provisioning Configuration Input - Accept configuration via: - * Kernel command line parameter (name TBD, e.g., `zosstorage.config=`) pointing to a YAML configuration descriptor. - * Optional CLI flags when run in user space (must mirror kernel cmdline semantics). - * On-disk YAML config file (default path TBD, e.g., `/etc/zosstorage/config.yaml`). -- Establish clear precedence: kernel cmdline overrides CLI arguments, which override config file defaults. No interactive prompts inside initramfs. -- YAML schema must at least describe disk selection rules, desired filesystem layout, boot partition preferences, filesystem options, mount targets, and logging verbosity. Document the schema and provide validation. + * Kernel command line parameter `zosstorage.config=` pointing to a YAML configuration descriptor. + * Optional CLI flags when run in user space (mirror kernel cmdline semantics). + * On-disk YAML config file (default path `/etc/zosstorage/config.yaml`). +- Precedence: kernel cmdline overrides CLI arguments, which override config file, which override built-in defaults. No interactive prompts inside initramfs. +- YAML schema must describe disk selection rules, desired filesystem layout, boot partition preferences, filesystem options, mount targets, and logging verbosity. See [docs/SCHEMA.md](docs/SCHEMA.md) and [src/types.rs](src/types.rs:1). State Reporting - After successful provisioning, emit a JSON state report (path TBD, e.g., `/run/zosstorage/state.json`) capturing: @@ -70,7 +70,7 @@ Logging - By default, logs go to stderr; design for optional redirection to a file (path TBD). Avoid using `println!`. System Integration -- Decide whether to generate `/etc/fstab` entries; if enabled, produce deterministic ordering and documentation. Otherwise, document alternative mount management. +- `/etc/fstab` generation: optional via CLI/config. When enabled, write only the four final subvolume/subdir mount entries (system, etc, modules, vm-meta) with `UUID=` sources in deterministic order. Root mounts under `/var/mounts/{UUID}` are runtime-only and excluded from fstab. - After provisioning, ensure the initramfs can mount the new filesystems (e.g., call `udevadm settle` if necessary). No external services are invoked. - No responsibility for updating `vmlinuz.efi`; another subsystem handles kernel updates. @@ -95,11 +95,17 @@ Documentation & Deliverables - Include architectural notes describing module boundaries (device discovery, partitioning, filesystem provisioning, config parsing, logging, reporting). Open Items (call out explicitly) -- Exact sizes and ordering for `bios_boot` partition awaiting confirmation; note assumptions in code and documentation. -- Mount point naming scheme under `/var/cache` (per-UUID vs. config-defined) still to be finalized. -- Filesystem-specific tuning parameters (compression, RAID values, `bcachefs` options) require explicit defaults from stakeholders. -- Path/location for YAML config, kernel cmdline key, JSON report path, and optional log file path need final confirmation. -- Decision whether `/etc/fstab` is generated remains pending. +- BIOS vs UEFI: `bios_boot` partition size fixed at 1 MiB and created only on BIOS systems; suppressed under UEFI (`/sys/firmware/efi` present). +- Mount scheme finalized: + - Root mounts for each data filesystem at `/var/mounts/{UUID}` (runtime only). + - Final subvolume/subdir mounts from the primary data filesystem to `/var/cache/{system,etc,modules,vm-meta}`. +- Filesystem-specific tuning parameters (compression, RAID values, `bcachefs` options) remain open for refinement; sensible defaults applied. +- Config paths and keys stabilized: + - Kernel cmdline key: `zosstorage.config=` + - Default config file: `/etc/zosstorage/config.yaml` + - Default report path: `/run/zosstorage/state.json` + - Optional log file: `/run/zosstorage/zosstorage.log` +- `/etc/fstab` generation policy decided: optional flag; writes only the four final subvolume/subdir entries. Implementation Constraints - Stick to clear module boundaries. Provide unit tests where possible (e.g., config parsing, device filtering). diff --git a/README.md b/README.md index a159668..1f84a3e 100644 --- a/README.md +++ b/README.md @@ -2,7 +2,7 @@ One-shot disk provisioning utility intended for initramfs. It discovers eligible disks, plans a GPT layout based on a chosen topology, creates filesystems, mounts them under a predictable scheme, and emits a machine-readable report. Safe-by-default with a non-destructive preview mode. -Status: first-draft preview capable. Partition apply, mkfs, and mounts are gated until the planning is validated in your environment. +Status: apply mode implemented. Partition application (sgdisk), filesystem creation (vfat/btrfs/bcachefs), mount scheme with subvolumes, and optional fstab writing are available. Preview mode remains supported. Key modules - CLI and entrypoint: @@ -33,8 +33,9 @@ Requirements - Linux with /proc and /sys mounted (initramfs friendly) - External tools discovered at runtime: - blkid (for probing UUIDs and signatures) - - sgdisk (for GPT application) — planned - - mkfs.vfat, mkfs.btrfs, bcachefs (for formatting) — invoked by fs/plan when enabled in execution phase + - sgdisk (for GPT application) + - mkfs.vfat, mkfs.btrfs, bcachefs (for formatting) + - udevadm (optional; for settle after partitioning) - Tracing/logging to stderr by default; optional file at /run/zosstorage/zosstorage.log Install and build @@ -57,6 +58,7 @@ CLI usage - Other: -c, --config PATH Merge a YAML config file (overrides defaults) -s, --fstab Enable writing /etc/fstab entries (when mounts are applied) + -a, --apply Perform partitioning, filesystem creation, and mounts (destructive) -f, --force Present but not implemented (returns an error) Examples @@ -68,6 +70,8 @@ Examples sudo ./zosstorage --show -t ssd-hdd-bcachefs --allow-removable -l debug - Quiet plan to file: sudo ./zosstorage --report /run/zosstorage/plan.json -t dual-independent +- Apply single-disk btrfs (DESTRUCTIVE; wipes target disk): + sudo ./zosstorage --apply -t btrfs-single Preview JSON shape (examples) 1) Already provisioned (idempotency success): @@ -107,14 +111,15 @@ Preview JSON shape (examples) } ], "filesystems_planned": [ - { "kind": "vfat", "from_roles": ["esp"], "label": "ZOSBOOT", "planned_mountpoint": null }, - { "kind": "btrfs", "from_roles": ["data"], "devices_planned": 2, "label": "ZOSDATA", "planned_mountpoint_template": "/var/cache/{UUID}" } + { "kind": "vfat", "from_roles": ["esp"], "label": "ZOSBOOT" }, + { "kind": "btrfs", "from_roles": ["data"], "devices_planned": 2, "label": "ZOSDATA" } ], "mount": { "scheme": "per_uuid", "base_dir": "/var/cache", "fstab_enabled": false, - "target_template": "/var/cache/{UUID}" + "root_mount_template": "/var/mounts/{UUID}", + "final_targets": ["/var/cache/system", "/var/cache/etc", "/var/cache/modules", "/var/cache/vm-meta"] } } diff --git a/docs/API-SKELETONS.md b/docs/API-SKELETONS.md index e325c86..a3d8b1f 100644 --- a/docs/API-SKELETONS.md +++ b/docs/API-SKELETONS.md @@ -6,34 +6,34 @@ Purpose - After approval, these will be created in the src tree in Code mode. Index -- [src/lib.rs](src/lib.rs) -- [src/errors.rs](src/errors.rs) -- [src/main.rs](src/main.rs) -- [src/cli/args.rs](src/cli/args.rs) -- [src/logging/mod.rs](src/logging/mod.rs) -- [src/types.rs](src/types.rs) -- [src/config/loader.rs](src/config/loader.rs) -- [src/device/discovery.rs](src/device/discovery.rs) -- [src/partition/plan.rs](src/partition/plan.rs) -- [src/fs/plan.rs](src/fs/plan.rs) -- [src/mount/ops.rs](src/mount/ops.rs) -- [src/report/state.rs](src/report/state.rs) -- [src/orchestrator/run.rs](src/orchestrator/run.rs) -- [src/idempotency/mod.rs](src/idempotency/mod.rs) -- [src/util/mod.rs](src/util/mod.rs) +- [src/lib.rs](../src/lib.rs) +- [src/errors.rs](../src/errors.rs) +- [src/main.rs](../src/main.rs) +- [src/cli/args.rs](../src/cli/args.rs) +- [src/logging/mod.rs](../src/logging/mod.rs) +- [src/types.rs](../src/types.rs) +- [src/config/loader.rs](../src/config/loader.rs) +- [src/device/discovery.rs](../src/device/discovery.rs) +- [src/partition/plan.rs](../src/partition/plan.rs) +- [src/fs/plan.rs](../src/fs/plan.rs) +- [src/mount/ops.rs](../src/mount/ops.rs) +- [src/report/state.rs](../src/report/state.rs) +- [src/orchestrator/run.rs](../src/orchestrator/run.rs) +- [src/idempotency/mod.rs](../src/idempotency/mod.rs) +- [src/util/mod.rs](../src/util/mod.rs) Conventions -- Shared [type Result](src/errors.rs:1) and [enum Error](src/errors.rs:1). +- Shared [type Result](../src/errors.rs:1) and [enum Error](../src/errors.rs:1). - No stdout prints; use tracing only. -- External tools invoked via [util](src/util/mod.rs) wrappers. +- External tools invoked via [util](../src/util/mod.rs) wrappers. --- ## Crate root References -- [src/lib.rs](src/lib.rs) -- [type Result = std::result::Result](src/errors.rs:1) +- [src/lib.rs](../src/lib.rs) +- [type Result = std::result::Result](../src/errors.rs:1) Skeleton (for later implementation in code mode) ```rust @@ -63,8 +63,8 @@ pub const VERSION: &str = env!("CARGO_PKG_VERSION"); ## Errors References -- [enum Error](src/errors.rs:1) -- [type Result](src/errors.rs:1) +- [enum Error](../src/errors.rs:1) +- [type Result](../src/errors.rs:1) Skeleton ```rust @@ -107,8 +107,8 @@ pub type Result = std::result::Result; ## Entrypoint References -- [fn main()](src/main.rs:1) -- [fn run(ctx: &Context) -> Result<()>](src/orchestrator/run.rs:1) +- [fn main()](../src/main.rs:1) +- [fn run(ctx: &Context) -> Result<()>](../src/orchestrator/run.rs:1) Skeleton ```rust @@ -143,8 +143,8 @@ fn real_main() -> Result<()> { ## CLI References -- [struct Cli](src/cli/args.rs:1) -- [fn from_args() -> Cli](src/cli/args.rs:1) +- [struct Cli](../src/cli/args.rs:1) +- [fn from_args() -> Cli](../src/cli/args.rs:1) Skeleton ```rust @@ -170,6 +170,18 @@ pub struct Cli { #[arg(long = "fstab", default_value_t = false)] pub fstab: bool, + /// Print preview JSON to stdout (non-destructive) + #[arg(long = "show", default_value_t = false)] + pub show: bool, + + /// Write preview JSON to a file (non-destructive) + #[arg(long = "report")] + pub report: Option, + + /// Perform partitioning, filesystem creation, and mounts (DESTRUCTIVE) + #[arg(long = "apply", default_value_t = false)] + pub apply: bool, + /// Present but non-functional; returns unimplemented error #[arg(long = "force")] pub force: bool, @@ -186,8 +198,8 @@ pub fn from_args() -> Cli { ## Logging References -- [struct LogOptions](src/logging/mod.rs:1) -- [fn init_logging(opts: &LogOptions) -> Result<()>](src/logging/mod.rs:1) +- [struct LogOptions](../src/logging/mod.rs:1) +- [fn init_logging(opts: &LogOptions) -> Result<()>](../src/logging/mod.rs:1) Skeleton ```rust @@ -218,13 +230,13 @@ pub fn init_logging(opts: &LogOptions) -> Result<()> { ## Configuration types References -- [struct Config](src/types.rs:1) -- [enum Topology](src/types.rs:1) -- [struct DeviceSelection](src/types.rs:1) -- [struct Partitioning](src/types.rs:1) -- [struct FsOptions](src/types.rs:1) -- [struct MountScheme](src/types.rs:1) -- [struct ReportOptions](src/types.rs:1) +- [struct Config](../src/types.rs:1) +- [enum Topology](../src/types.rs:1) +- [struct DeviceSelection](../src/types.rs:1) +- [struct Partitioning](../src/types.rs:1) +- [struct FsOptions](../src/types.rs:1) +- [struct MountScheme](../src/types.rs:1) +- [struct ReportOptions](../src/types.rs:1) Skeleton ```rust @@ -247,8 +259,10 @@ pub struct DeviceSelection { #[derive(Debug, Clone, Serialize, Deserialize)] #[serde(rename_all = "snake_case")] pub enum Topology { - Single, + BtrfsSingle, + BcachefsSingle, DualIndependent, + Bcachefs2Copy, SsdHddBcachefs, BtrfsRaid1, } @@ -351,8 +365,8 @@ pub struct Config { ## Configuration I/O References -- [fn load_and_merge(cli: &Cli) -> Result](src/config/loader.rs:1) -- [fn validate(cfg: &Config) -> Result<()>](src/config/loader.rs:1) +- [fn load_and_merge(cli: &Cli) -> Result](../src/config/loader.rs:1) +- [fn validate(cfg: &Config) -> Result<()>](../src/config/loader.rs:1) Skeleton ```rust @@ -374,10 +388,10 @@ pub fn validate(cfg: &crate::config::types::Config) -> Result<()> { ## Device discovery References -- [struct Disk](src/device/discovery.rs:1) -- [struct DeviceFilter](src/device/discovery.rs:1) -- [trait DeviceProvider](src/device/discovery.rs:1) -- [fn discover(filter: &DeviceFilter) -> Result>](src/device/discovery.rs:1) +- [struct Disk](../src/device/discovery.rs:1) +- [struct DeviceFilter](../src/device/discovery.rs:1) +- [trait DeviceProvider](../src/device/discovery.rs:1) +- [fn discover(filter: &DeviceFilter) -> Result>](../src/device/discovery.rs:1) Skeleton ```rust @@ -418,12 +432,12 @@ pub fn discover(filter: &DeviceFilter) -> Result> { ## Partitioning References -- [enum PartRole](src/partition/plan.rs:1) -- [struct PartitionSpec](src/partition/plan.rs:1) -- [struct PartitionPlan](src/partition/plan.rs:1) -- [struct PartitionResult](src/partition/plan.rs:1) -- [fn plan_partitions(disks: &[Disk], cfg: &Config) -> Result](src/partition/plan.rs:1) -- [fn apply_partitions(plan: &PartitionPlan) -> Result>](src/partition/plan.rs:1) +- [enum PartRole](../src/partition/plan.rs:1) +- [struct PartitionSpec](../src/partition/plan.rs:1) +- [struct PartitionPlan](../src/partition/plan.rs:1) +- [struct PartitionResult](../src/partition/plan.rs:1) +- [fn plan_partitions(disks: &[Disk], cfg: &Config) -> Result](../src/partition/plan.rs:1) +- [fn apply_partitions(plan: &PartitionPlan) -> Result>](../src/partition/plan.rs:1) Skeleton ```rust @@ -485,12 +499,12 @@ pub fn apply_partitions(plan: &PartitionPlan) -> Result> { ## Filesystems References -- [enum FsKind](src/fs/plan.rs:1) -- [struct FsSpec](src/fs/plan.rs:1) -- [struct FsPlan](src/fs/plan.rs:1) -- [struct FsResult](src/fs/plan.rs:1) -- [fn plan_filesystems(...)](src/fs/plan.rs:1) -- [fn make_filesystems(...)](src/fs/plan.rs:1) +- [enum FsKind](../src/fs/plan.rs:1) +- [struct FsSpec](../src/fs/plan.rs:1) +- [struct FsPlan](../src/fs/plan.rs:1) +- [struct FsResult](../src/fs/plan.rs:1) +- [fn plan_filesystems(...)](../src/fs/plan.rs:1) +- [fn make_filesystems(...)](../src/fs/plan.rs:1) Skeleton ```rust @@ -531,8 +545,8 @@ pub fn plan_filesystems( todo!("map ESP to vfat, data to btrfs or bcachefs according to topology") } -/// Create the filesystems and return identity info (UUIDs, labels). -pub fn make_filesystems(plan: &FsPlan) -> Result> { +//// Create the filesystems and return identity info (UUIDs, labels). +pub fn make_filesystems(plan: &FsPlan, cfg: &Config) -> Result> { todo!("invoke mkfs tools with configured options via util::run_cmd") } ``` @@ -542,11 +556,11 @@ pub fn make_filesystems(plan: &FsPlan) -> Result> { ## Mounting References -- [struct MountPlan](src/mount/ops.rs:1) -- [struct MountResult](src/mount/ops.rs:1) -- [fn plan_mounts(...)](src/mount/ops.rs:1) -- [fn apply_mounts(...)](src/mount/ops.rs:1) -- [fn maybe_write_fstab(...)](src/mount/ops.rs:1) +- [struct MountPlan](../src/mount/ops.rs:1) +- [struct MountResult](../src/mount/ops.rs:1) +- [fn plan_mounts(...)](../src/mount/ops.rs:1) +- [fn apply_mounts(...)](../src/mount/ops.rs:1) +- [fn maybe_write_fstab(...)](../src/mount/ops.rs:1) Skeleton ```rust @@ -565,9 +579,13 @@ pub struct MountResult { pub options: String, } -/// Build mount plan under /var/cache/ by default. +//// Build mount plan: +//// - Root-mount all data filesystems under `/var/mounts/{UUID}` (runtime only) +//// - Ensure/create subvolumes on the primary data filesystem: system, etc, modules, vm-meta +//// - Plan final mounts to `/var/cache/{system,etc,modules,vm-meta}` using +//// `subvol=` for btrfs and `X-mount.subdir=` for bcachefs. pub fn plan_mounts(fs_results: &[FsResult], cfg: &Config) -> Result { - todo!("create per-UUID directories and mount mapping") + todo!("root mounts under /var/mounts/{UUID}; final subvol/subdir mounts to /var/cache/{system,etc,modules,vm-meta}") } /// Apply mounts using syscalls (nix), ensuring directories exist. @@ -575,9 +593,12 @@ pub fn apply_mounts(plan: &MountPlan) -> Result> { todo!("perform mount syscalls and return results") } -/// Optionally generate /etc/fstab entries in deterministic order. +//// Optionally generate /etc/fstab entries for final subvolume/subdir mounts only. +//// - Write exactly four entries: system, etc, modules, vm-meta +//// - Use UUID= sources; deterministic order by target path +//// - Exclude runtime root mounts under `/var/mounts/{UUID}` pub fn maybe_write_fstab(mounts: &[MountResult], cfg: &Config) -> Result<()> { - todo!("when enabled, write fstab entries") + todo!("when enabled, write only the four final subvolume/subdir entries with UUID= sources") } ``` @@ -586,10 +607,10 @@ pub fn maybe_write_fstab(mounts: &[MountResult], cfg: &Config) -> Result<()> { ## Reporting References -- [const REPORT_VERSION: &str](src/report/state.rs:1) -- [struct StateReport](src/report/state.rs:1) -- [fn build_report(...)](src/report/state.rs:1) -- [fn write_report(...)](src/report/state.rs:1) +- [const REPORT_VERSION: &str](../src/report/state.rs:1) +- [struct StateReport](../src/report/state.rs:1) +- [fn build_report(...)](../src/report/state.rs:1) +- [fn write_report(...)](../src/report/state.rs:1) Skeleton ```rust @@ -632,8 +653,8 @@ pub fn write_report(report: &StateReport, path: &str) -> Result<()> { ## Orchestrator References -- [struct Context](src/orchestrator/run.rs:1) -- [fn run(ctx: &Context) -> Result<()>](src/orchestrator/run.rs:1) +- [struct Context](../src/orchestrator/run.rs:1) +- [fn run(ctx: &Context) -> Result<()>](../src/orchestrator/run.rs:1) Skeleton ```rust @@ -662,8 +683,8 @@ pub fn run(ctx: &Context) -> Result<()> { ## Idempotency References -- [fn detect_existing_state() -> Result>](src/idempotency/mod.rs:1) -- [fn is_empty_disk(disk: &Disk) -> Result](src/idempotency/mod.rs:1) +- [fn detect_existing_state() -> Result>](../src/idempotency/mod.rs:1) +- [fn is_empty_disk(disk: &Disk) -> Result](../src/idempotency/mod.rs:1) Skeleton ```rust @@ -685,11 +706,11 @@ pub fn is_empty_disk(disk: &Disk) -> Result { ## Utilities References -- [struct CmdOutput](src/util/mod.rs:1) -- [fn which_tool(name: &str) -> Result>](src/util/mod.rs:1) -- [fn run_cmd(args: &[&str]) -> Result<()>](src/util/mod.rs:1) -- [fn run_cmd_capture(args: &[&str]) -> Result](src/util/mod.rs:1) -- [fn udev_settle(timeout_ms: u64) -> Result<()>](src/util/mod.rs:1) +- [struct CmdOutput](../src/util/mod.rs:1) +- [fn which_tool(name: &str) -> Result>](../src/util/mod.rs:1) +- [fn run_cmd(args: &[&str]) -> Result<()>](../src/util/mod.rs:1) +- [fn run_cmd_capture(args: &[&str]) -> Result](../src/util/mod.rs:1) +- [fn udev_settle(timeout_ms: u64) -> Result<()>](../src/util/mod.rs:1) Skeleton ```rust @@ -722,6 +743,11 @@ pub fn run_cmd_capture(args: &[&str]) -> Result { pub fn udev_settle(timeout_ms: u64) -> Result<()> { todo!("invoke udevadm settle when present") } + +/// Detect UEFI environment by checking /sys/firmware/efi; used to suppress BIOS boot partition on UEFI. +pub fn is_efi_boot() -> bool { + todo!("return Path::new(\"/sys/firmware/efi\").exists()") +} ``` --- @@ -735,24 +761,24 @@ Approval gate - Add initial tests scaffolding and example configs. References summary -- [fn main()](src/main.rs:1) -- [fn from_args()](src/cli/args.rs:1) -- [fn init_logging(opts: &LogOptions)](src/logging/mod.rs:1) -- [fn load_and_merge(cli: &Cli)](src/config/loader.rs:1) -- [fn validate(cfg: &Config)](src/config/loader.rs:1) -- [fn discover(filter: &DeviceFilter)](src/device/discovery.rs:1) -- [fn plan_partitions(disks: &[Disk], cfg: &Config)](src/partition/plan.rs:1) -- [fn apply_partitions(plan: &PartitionPlan)](src/partition/plan.rs:1) -- [fn plan_filesystems(parts: &[PartitionResult], cfg: &Config)](src/fs/plan.rs:1) -- [fn make_filesystems(plan: &FsPlan)](src/fs/plan.rs:1) -- [fn plan_mounts(fs_results: &[FsResult], cfg: &Config)](src/mount/ops.rs:1) -- [fn apply_mounts(plan: &MountPlan)](src/mount/ops.rs:1) -- [fn maybe_write_fstab(mounts: &[MountResult], cfg: &Config)](src/mount/ops.rs:1) -- [fn build_report(...)](src/report/state.rs:1) -- [fn write_report(report: &StateReport)](src/report/state.rs:1) -- [fn detect_existing_state()](src/idempotency/mod.rs:1) -- [fn is_empty_disk(disk: &Disk)](src/idempotency/mod.rs:1) -- [fn which_tool(name: &str)](src/util/mod.rs:1) -- [fn run_cmd(args: &[&str])](src/util/mod.rs:1) -- [fn run_cmd_capture(args: &[&str])](src/util/mod.rs:1) -- [fn udev_settle(timeout_ms: u64)](src/util/mod.rs:1) \ No newline at end of file +- [fn main()](../src/main.rs:1) +- [fn from_args()](../src/cli/args.rs:1) +- [fn init_logging(opts: &LogOptions)](../src/logging/mod.rs:1) +- [fn load_and_merge(cli: &Cli)](../src/config/loader.rs:1) +- [fn validate(cfg: &Config)](../src/config/loader.rs:1) +- [fn discover(filter: &DeviceFilter)](../src/device/discovery.rs:1) +- [fn plan_partitions(disks: &[Disk], cfg: &Config)](../src/partition/plan.rs:1) +- [fn apply_partitions(plan: &PartitionPlan)](../src/partition/plan.rs:1) +- [fn plan_filesystems(parts: &[PartitionResult], cfg: &Config)](../src/fs/plan.rs:1) +- [fn make_filesystems(plan: &FsPlan)](../src/fs/plan.rs:1) +- [fn plan_mounts(fs_results: &[FsResult], cfg: &Config)](../src/mount/ops.rs:1) +- [fn apply_mounts(plan: &MountPlan)](../src/mount/ops.rs:1) +- [fn maybe_write_fstab(mounts: &[MountResult], cfg: &Config)](../src/mount/ops.rs:1) +- [fn build_report(...)](../src/report/state.rs:1) +- [fn write_report(report: &StateReport)](../src/report/state.rs:1) +- [fn detect_existing_state()](../src/idempotency/mod.rs:1) +- [fn is_empty_disk(disk: &Disk)](../src/idempotency/mod.rs:1) +- [fn which_tool(name: &str)](../src/util/mod.rs:1) +- [fn run_cmd(args: &[&str])](../src/util/mod.rs:1) +- [fn run_cmd_capture(args: &[&str])](../src/util/mod.rs:1) +- [fn udev_settle(timeout_ms: u64)](../src/util/mod.rs:1) \ No newline at end of file diff --git a/docs/API.md b/docs/API.md index 5ef1665..97efc48 100644 --- a/docs/API.md +++ b/docs/API.md @@ -8,40 +8,40 @@ Conventions - External tooling calls are mediated via utility wrappers. Module index -- [src/main.rs](src/main.rs) -- [src/lib.rs](src/lib.rs) -- [src/errors.rs](src/errors.rs) -- [src/cli/args.rs](src/cli/args.rs) -- [src/logging/mod.rs](src/logging/mod.rs) -- [src/types.rs](src/types.rs) -- [src/config/loader.rs](src/config/loader.rs) -- [src/device/discovery.rs](src/device/discovery.rs) -- [src/partition/plan.rs](src/partition/plan.rs) -- [src/fs/plan.rs](src/fs/plan.rs) -- [src/mount/ops.rs](src/mount/ops.rs) -- [src/report/state.rs](src/report/state.rs) -- [src/orchestrator/run.rs](src/orchestrator/run.rs) -- [src/idempotency/mod.rs](src/idempotency/mod.rs) -- [src/util/mod.rs](src/util/mod.rs) +- [src/main.rs](../src/main.rs) +- [src/lib.rs](../src/lib.rs) +- [src/errors.rs](../src/errors.rs) +- [src/cli/args.rs](../src/cli/args.rs) +- [src/logging/mod.rs](../src/logging/mod.rs) +- [src/types.rs](../src/types.rs) +- [src/config/loader.rs](../src/config/loader.rs) +- [src/device/discovery.rs](../src/device/discovery.rs) +- [src/partition/plan.rs](../src/partition/plan.rs) +- [src/fs/plan.rs](../src/fs/plan.rs) +- [src/mount/ops.rs](../src/mount/ops.rs) +- [src/report/state.rs](../src/report/state.rs) +- [src/orchestrator/run.rs](../src/orchestrator/run.rs) +- [src/idempotency/mod.rs](../src/idempotency/mod.rs) +- [src/util/mod.rs](../src/util/mod.rs) Common errors and result -- [enum Error](src/errors.rs:1) +- [enum Error](../src/errors.rs:1) - Top-level error type covering parse/validation errors, device discovery errors, partitioning failures, filesystem mkfs errors, mount errors, report write errors, and external tool invocation failures with stderr capture. -- [type Result = std::result::Result](src/errors.rs:1) +- [type Result = std::result::Result](../src/errors.rs:1) - Shared result alias across modules. Crate root -- [src/lib.rs](src/lib.rs) +- [src/lib.rs](../src/lib.rs) - Exposes crate version constants, the prelude, and re-exports common types for consumers of the library (tests/integration). No heavy logic. Entrypoint -- [fn main()](src/main.rs:1) +- [fn main()](../src/main.rs:1) - Initializes logging based on CLI defaults, parses CLI flags and kernel cmdline, loads and validates configuration, and invokes the orchestrator run sequence. Avoids stdout; logs via tracing only. Orchestrator -- [struct Context](src/orchestrator/run.rs:1) +- [struct Context](../src/orchestrator/run.rs:1) - Aggregates resolved configuration, logging options, and environment flags suited for initramfs execution. -- [fn run(ctx: &Context) -> Result<()>](src/orchestrator/run.rs:1) +- [fn run(ctx: &Context) -> Result<()>](../src/orchestrator/run.rs:1) - High-level one-shot flow: - Idempotency detection - Device discovery @@ -52,131 +52,141 @@ Orchestrator - Aborts the entire run on any validation or execution failure. Returns Ok on successful no-op if already provisioned. CLI -- [struct Cli](src/cli/args.rs:1) +- [struct Cli](../src/cli/args.rs:1) - Mirrors kernel cmdline semantics with flags: - --config PATH - --log-level LEVEL - --log-to-file - --fstab + - --show + - --report PATH + - --apply - --force (present, returns unimplemented error) -- [fn from_args() -> Cli](src/cli/args.rs:1) +- [fn from_args() -> Cli](../src/cli/args.rs:1) - Parses argv without side effects; suitable for initramfs. Logging -- [struct LogOptions](src/logging/mod.rs:1) +- [struct LogOptions](../src/logging/mod.rs:1) - Holds level and optional file target (/run/zosstorage/zosstorage.log). -- [fn init_logging(opts: &LogOptions) -> Result<()>](src/logging/mod.rs:1) +- [fn init_logging(opts: &LogOptions) -> Result<()>](../src/logging/mod.rs:1) - Configures tracing-subscriber for stderr by default and optional file layer when enabled. Must be idempotent. Configuration types -- [struct Config](src/types.rs:1) +- [struct Config](../src/types.rs:1) - The validated configuration used by the orchestrator, containing logging, device selection rules, topology, partitioning, filesystem options, mount scheme, and report path. -- [enum Topology](src/types.rs:1) - - Values: btrfs_single, bcachefs_single, dual_independent, bcachefs_2copy, ssd_hdd_bcachefs, btrfs_raid1 (opt-in). -- [struct DeviceSelection](src/types.rs:1) +- [enum Topology](../src/types.rs:1) + - Values: btrfs_single, bcachefs_single, dual_independent, bcachefs2_copy, ssd_hdd_bcachefs, btrfs_raid1 (opt-in). +- [struct DeviceSelection](../src/types.rs:1) - Include and exclude regex patterns, minimum size, removable policy. -- [struct Partitioning](src/types.rs:1) +- [struct Partitioning](../src/types.rs:1) - Alignment, emptiness requirement, bios_boot, esp, data, cache GPT names and sizes where applicable. -- [struct BtrfsOptions](src/types.rs:1) +- [struct BtrfsOptions](../src/types.rs:1) - Compression string and raid profile (none or raid1). -- [struct BcachefsOptions](src/types.rs:1) +- [struct BcachefsOptions](../src/types.rs:1) - Cache mode (promote or writeback), compression, checksum. -- [struct VfatOptions](src/types.rs:1) +- [struct VfatOptions](../src/types.rs:1) - Reserved for ESP mkfs options; includes label ZOSBOOT. -- [struct FsOptions](src/types.rs:1) +- [struct FsOptions](../src/types.rs:1) - Aggregates BtrfsOptions, BcachefsOptions, VfatOptions and shared defaults such as ZOSDATA label. -- [enum MountSchemeKind](src/types.rs:1) +- [enum MountSchemeKind](../src/types.rs:1) - Values: per_uuid, custom (future). -- [struct MountScheme](src/types.rs:1) +- [struct MountScheme](../src/types.rs:1) - Base directory (/var/cache), scheme kind, fstab enabled flag. -- [struct ReportOptions](src/types.rs:1) +- [struct ReportOptions](../src/types.rs:1) - Output path (/run/zosstorage/state.json). Configuration IO -- [fn load_and_merge(cli: &Cli) -> Result](src/config/loader.rs:1) +- [fn load_and_merge(cli: &Cli) -> Result](../src/config/loader.rs:1) - Loads built-in defaults, optionally merges on-disk config, overlays CLI flags, and finally overlays kernel cmdline via zosstorage.config=. Validates the YAML against types and constraints. -- [fn validate(cfg: &Config) -> Result<()>](src/config/loader.rs:1) +- [fn validate(cfg: &Config) -> Result<()>](../src/config/loader.rs:1) - Ensures structural and semantic validity (e.g., disk selection rules not empty, sizes non-zero, supported topology combinations). Device discovery -- [struct Disk](src/device/discovery.rs:1) +- [struct Disk](../src/device/discovery.rs:1) - Represents an eligible block device with path, size, rotational flag, and identifiers (serial, model if available). -- [struct DeviceFilter](src/device/discovery.rs:1) +- [struct DeviceFilter](../src/device/discovery.rs:1) - Derived from DeviceSelection; compiled regexes and size thresholds for efficient filtering. -- [trait DeviceProvider](src/device/discovery.rs:1) +- [trait DeviceProvider](../src/device/discovery.rs:1) - Abstraction for listing /dev and probing properties, enabling test doubles. -- [fn discover(filter: &DeviceFilter) -> Result>](src/device/discovery.rs:1) +- [fn discover(filter: &DeviceFilter) -> Result>](../src/device/discovery.rs:1) - Returns eligible disks or a well-defined error if none are found. Partitioning -- [enum PartRole](src/partition/plan.rs:1) +- [enum PartRole](../src/partition/plan.rs:1) - Roles: BiosBoot, Esp, Data, Cache. -- [struct PartitionSpec](src/partition/plan.rs:1) +- [struct PartitionSpec](../src/partition/plan.rs:1) - Declarative spec for a single partition: role, optional size_mib, gpt_name (zosboot, zosdata, zoscache), and reserved filesystem label when role is Esp (ZOSBOOT). -- [struct DiskPlan](src/partition/plan.rs:1) +- [struct DiskPlan](../src/partition/plan.rs:1) - The planned set of PartitionSpec instances for a single Disk in the chosen topology. -- [struct PartitionPlan](src/partition/plan.rs:1) +- [struct PartitionPlan](../src/partition/plan.rs:1) - Combined plan across all target disks, including alignment rules and safety checks. -- [struct PartitionResult](src/partition/plan.rs:1) +- [struct PartitionResult](../src/partition/plan.rs:1) - Result of applying a DiskPlan: device path of each created partition, role, partition GUID, and gpt_name. -- [fn plan_partitions(disks: &[Disk], cfg: &Config) -> Result](src/partition/plan.rs:1) +- [fn plan_partitions(disks: &[Disk], cfg: &Config) -> Result](../src/partition/plan.rs:1) - Produces a GPT-only plan with 1 MiB alignment, bios boot first (1 MiB), ESP 512 MiB, data remainder, and zoscache on SSD for ssd_hdd_bcachefs. -- [fn apply_partitions(plan: &PartitionPlan) -> Result>](src/partition/plan.rs:1) +- [fn apply_partitions(plan: &PartitionPlan) -> Result>](../src/partition/plan.rs:1) - Executes the plan via sgdisk and related utilities. Aborts if target disks are not empty or if signatures are detected. Filesystems -- [enum FsKind](src/fs/plan.rs:1) +- [enum FsKind](../src/fs/plan.rs:1) - Values: Vfat, Btrfs, Bcachefs. -- [struct FsSpec](src/fs/plan.rs:1) +- [struct FsSpec](../src/fs/plan.rs:1) - Maps PartitionResult to desired filesystem kind and label (ZOSBOOT for ESP; ZOSDATA for all data filesystems including bcachefs). -- [struct FsPlan](src/fs/plan.rs:1) +- [struct FsPlan](../src/fs/plan.rs:1) - Plan of mkfs operations across all partitions and devices given the topology. -- [struct FsResult](src/fs/plan.rs:1) +- [struct FsResult](../src/fs/plan.rs:1) - Output of mkfs: device path(s), fs uuid, label, and kind. -- [fn plan_filesystems(disks: &[Disk], parts: &[PartitionResult], cfg: &Config) -> Result](src/fs/plan.rs:1) +- [fn plan_filesystems(disks: &[Disk], parts: &[PartitionResult], cfg: &Config) -> Result](../src/fs/plan.rs:1) - Determines which partitions receive vfat, btrfs, or bcachefs, and aggregates tuning options. -- [fn make_filesystems(plan: &FsPlan) -> Result>](src/fs/plan.rs:1) +- [fn make_filesystems(plan: &FsPlan, cfg: &Config) -> Result>](../src/fs/plan.rs:1) - Invokes mkfs.vfat, mkfs.btrfs, mkfs.bcachefs accordingly via utility wrappers and returns filesystem identities. Mounting -- [struct MountPlan](src/mount/ops.rs:1) - - Derived from FsResult entries: creates target directories under /var/cache/ and the mounts required for the current boot. -- [struct MountResult](src/mount/ops.rs:1) +- [struct MountPlan](../src/mount/ops.rs:1) + - Derived from FsResult entries. Plans: + - Root mounts for all data filesystems under `/var/mounts/{UUID}` (runtime only). + - btrfs root options: `rw,noatime,subvolid=5` + - bcachefs root options: `rw,noatime` + - Final subvolume/subdir mounts (from the primary data filesystem) to: + - `/var/cache/system`, `/var/cache/etc`, `/var/cache/modules`, `/var/cache/vm-meta` +- [struct MountResult](../src/mount/ops.rs:1) - Actual mount operations performed (source, target, fstype, options). -- [fn plan_mounts(fs_results: &[FsResult], cfg: &Config) -> Result](src/mount/ops.rs:1) - - Translates filesystem identities to mount targets and options. -- [fn apply_mounts(plan: &MountPlan) -> Result>](src/mount/ops.rs:1) - - Performs mounts using syscalls (nix crate) with minimal dependencies. Ensures directories exist. -- [fn maybe_write_fstab(mounts: &[MountResult], cfg: &Config) -> Result<()>](src/mount/ops.rs:1) - - When enabled, generates /etc/fstab entries in deterministic order. Disabled by default. +- [fn plan_mounts(fs_results: &[FsResult], cfg: &Config) -> Result](../src/mount/ops.rs:1) + - Translates filesystem identities to mount targets and options, including `subvol=` for btrfs and `X-mount.subdir=` for bcachefs. +- [fn apply_mounts(plan: &MountPlan) -> Result>](../src/mount/ops.rs:1) + - Performs mounts using syscalls (nix crate). Ensures directories exist and creates subvolumes/subdirs if missing. +- [fn maybe_write_fstab(mounts: &[MountResult], cfg: &Config) -> Result<()>](../src/mount/ops.rs:1) + - When enabled, writes only the four subvolume/subdir mount entries to `/etc/fstab` in deterministic order using `UUID=` sources. Root mounts under `/var/mounts` are excluded. Reporting -- [const REPORT_VERSION: &str](src/report/state.rs:1) +- [const REPORT_VERSION: &str](../src/report/state.rs:1) - Version string for the JSON payload schema. -- [struct StateReport](src/report/state.rs:1) +- [struct StateReport](../src/report/state.rs:1) - Machine-readable state describing discovered disks, created partitions, filesystems, labels, mountpoints, status, and timestamp. -- [fn build_report(disks: &[Disk], parts: &[PartitionResult], fs: &[FsResult], mounts: &[MountResult], status: &str) -> StateReport](src/report/state.rs:1) +- [fn build_report(disks: &[Disk], parts: &[PartitionResult], fs: &[FsResult], mounts: &[MountResult], status: &str) -> StateReport](../src/report/state.rs:1) - Constructs a StateReport matching REPORT_VERSION. -- [fn write_report(report: &StateReport) -> Result<()>](src/report/state.rs:1) +- [fn write_report(report: &StateReport) -> Result<()>](../src/report/state.rs:1) - Writes JSON to /run/zosstorage/state.json (configurable). Idempotency -- [fn detect_existing_state() -> Result>](src/idempotency/mod.rs:1) +- [fn detect_existing_state() -> Result>](../src/idempotency/mod.rs:1) - Probes for expected GPT names (zosboot, zosdata, zoscache where applicable) and filesystem labels (ZOSBOOT, ZOSDATA). If present and consistent, returns a StateReport; orchestrator exits success without changes. -- [fn is_empty_disk(disk: &Disk) -> Result](src/idempotency/mod.rs:1) +- [fn is_empty_disk(disk: &Disk) -> Result](../src/idempotency/mod.rs:1) - Determines disk emptiness: absence of partitions and known filesystem signatures. Utilities -- [struct CmdOutput](src/util/mod.rs:1) +- [struct CmdOutput](../src/util/mod.rs:1) - Captures status, stdout, stderr from external tool invocations. -- [fn which_tool(name: &str) -> Result>](src/util/mod.rs:1) +- [fn which_tool(name: &str) -> Result>](../src/util/mod.rs:1) - Locates a required system utility in PATH, returning its absolute path if available. -- [fn run_cmd(args: &[&str]) -> Result<()>](src/util/mod.rs:1) +- [fn run_cmd(args: &[&str]) -> Result<()>](../src/util/mod.rs:1) - Executes a command (args[0] is binary) and returns Ok when exit status is zero; logs stderr on failure. -- [fn run_cmd_capture(args: &[&str]) -> Result](src/util/mod.rs:1) +- [fn run_cmd_capture(args: &[&str]) -> Result](../src/util/mod.rs:1) - Executes a command and returns captured output for parsing (e.g., blkid). -- [fn udev_settle(timeout_ms: u64) -> Result<()>](src/util/mod.rs:1) +- [fn udev_settle(timeout_ms: u64) -> Result<()>](../src/util/mod.rs:1) - Calls udevadm settle with a timeout when available; otherwise no-ops with a warning. +- [fn is_efi_boot() -> bool](../src/util/mod.rs:1) + - Detects UEFI environment by checking `/sys/firmware/efi`; used to suppress BIOS boot partition creation on UEFI systems. Behavioral notes and contracts - Safety and idempotency: @@ -188,10 +198,13 @@ Behavioral notes and contracts - Data filesystems use label ZOSDATA regardless of backend kind. - Cache partitions in bcachefs topology use GPT name zoscache. - Topology-specific behavior: - - single: one data filesystem (btrfs) on the sole disk. - - dual_independent: two separate btrfs filesystems, one per disk. + - btrfs_single: one data filesystem (btrfs) on the sole disk. + - bcachefs_single: one data filesystem (bcachefs) on the sole disk. + - dual_independent: independent btrfs filesystems on each eligible disk (one or more). + - bcachefs2_copy: multi-device bcachefs across two or more data partitions with `--replicas=2` (data and metadata). - ssd_hdd_bcachefs: bcachefs spanning SSD (cache/promote) and HDD (backing), labeled ZOSDATA. - btrfs_raid1: only when explicitly requested; otherwise default to independent btrfs. +- UEFI vs BIOS: when running under UEFI (`/sys/firmware/efi` present), the BIOS boot partition is suppressed. Module dependency overview diff --git a/docs/ARCHITECTURE.md b/docs/ARCHITECTURE.md index e14af8e..daf93c1 100644 --- a/docs/ARCHITECTURE.md +++ b/docs/ARCHITECTURE.md @@ -30,116 +30,122 @@ Top level - [tests/integration_ssd_hdd.rs](tests/integration_ssd_hdd.rs) Crate sources -- [src/main.rs](src/main.rs) -- [src/lib.rs](src/lib.rs) -- [src/errors.rs](src/errors.rs) -- [src/cli/args.rs](src/cli/args.rs) -- [src/logging/mod.rs](src/logging/mod.rs) -- [src/config/loader.rs](src/config/loader.rs) -- [src/types.rs](src/types.rs) -- [src/device/discovery.rs](src/device/discovery.rs) -- [src/partition/plan.rs](src/partition/plan.rs) -- [src/fs/plan.rs](src/fs/plan.rs) -- [src/mount/ops.rs](src/mount/ops.rs) -- [src/report/state.rs](src/report/state.rs) -- [src/orchestrator/run.rs](src/orchestrator/run.rs) -- [src/idempotency/mod.rs](src/idempotency/mod.rs) -- [src/util/mod.rs](src/util/mod.rs) +- [src/main.rs](../src/main.rs) +- [src/lib.rs](../src/lib.rs) +- [src/errors.rs](../src/errors.rs) +- [src/cli/args.rs](../src/cli/args.rs) +- [src/logging/mod.rs](../src/logging/mod.rs) +- [src/config/loader.rs](../src/config/loader.rs) +- [src/types.rs](../src/types.rs) +- [src/device/discovery.rs](../src/device/discovery.rs) +- [src/partition/plan.rs](../src/partition/plan.rs) +- [src/fs/plan.rs](../src/fs/plan.rs) +- [src/mount/ops.rs](../src/mount/ops.rs) +- [src/report/state.rs](../src/report/state.rs) +- [src/orchestrator/run.rs](../src/orchestrator/run.rs) +- [src/idempotency/mod.rs](../src/idempotency/mod.rs) +- [src/util/mod.rs](../src/util/mod.rs) Module responsibilities -- [src/main.rs](src/main.rs) +- [src/main.rs](../src/main.rs) - Entrypoint. Parse CLI, initialize logging, load and merge configuration per precedence, call orchestrator. No stdout spam. -- [src/lib.rs](src/lib.rs) +- [src/lib.rs](../src/lib.rs) - Crate exports, prelude, version constants, Result alias. -- [src/errors.rs](src/errors.rs) +- [src/errors.rs](../src/errors.rs) - Common error enum and Result alias via thiserror. -- [src/cli/args.rs](src/cli/args.rs) +- [src/cli/args.rs](../src/cli/args.rs) - CLI definition mirroring kernel cmdline semantics; provide non-interactive interface. Stub --force returns unimplemented. -- [src/logging/mod.rs](src/logging/mod.rs) +- [src/logging/mod.rs](../src/logging/mod.rs) - Initialize tracing; levels error, warn, info, debug; default to stderr; optional file target. -- [src/config/loader.rs](src/config/loader.rs) and [src/types.rs](src/types.rs) +- [src/config/loader.rs](../src/config/loader.rs) and [src/types.rs](../src/types.rs) - YAML schema types, validation, loading, and merging with CLI and kernel cmdline. -- [src/device/discovery.rs](src/device/discovery.rs) +- [src/device/discovery.rs](../src/device/discovery.rs) - Device discovery under /dev with filters and allowlist; probe emptiness safely. -- [src/partition/plan.rs](src/partition/plan.rs) +- [src/partition/plan.rs](../src/partition/plan.rs) - GPT-only planning and application; 1 MiB alignment; create bios boot, ESP, data and cache partitions with strict safety checks. -- [src/fs/plan.rs](src/fs/plan.rs) +- [src/fs/plan.rs](../src/fs/plan.rs) - Filesystem provisioning: vfat for ESP, btrfs for ZOSDATA, bcachefs for SSD+HDD mode; all data filesystems labeled ZOSDATA. -- [src/mount/ops.rs](src/mount/ops.rs) - - Mount per-UUID under /var/cache/. Optional fstab writing, disabled by default. -- [src/report/state.rs](src/report/state.rs) +- [src/mount/ops.rs](../src/mount/ops.rs) + - Mount scheme: + - Root-mount all data filesystems under `/var/mounts/{UUID}` (runtime only) + - btrfs root: `rw,noatime,subvolid=5` + - bcachefs root: `rw,noatime` + - Create or ensure subvolumes on the primary data filesystem: `system`, `etc`, `modules`, `vm-meta` + - Mount final subvolume/subdir targets at `/var/cache/{system,etc,modules,vm-meta}` + - Optional fstab writing: only the four final targets, deterministic order, `UUID=` sources; disabled by default +- [src/report/state.rs](../src/report/state.rs) - Build and write JSON state report with version field. -- [src/orchestrator/run.rs](src/orchestrator/run.rs) +- [src/orchestrator/run.rs](../src/orchestrator/run.rs) - One-shot flow orchestration with abort-on-any-validation-error policy. -- [src/idempotency/mod.rs](src/idempotency/mod.rs) +- [src/idempotency/mod.rs](../src/idempotency/mod.rs) - Detect prior provisioning via GPT names and labels; return success-without-changes. -- [src/util/mod.rs](src/util/mod.rs) +- [src/util/mod.rs](../src/util/mod.rs) - Shell-out, udev settle, and helpers. Public API surface (signatures; implementation to follow after approval) Entrypoint and orchestrator -- [fn main()](src/main.rs:1) -- [struct Context](src/orchestrator/run.rs:1) -- [fn run(ctx: &Context) -> Result<()>](src/orchestrator/run.rs:1) +- [fn main()](../src/main.rs:1) +- [struct Context](../src/orchestrator/run.rs:1) +- [fn run(ctx: &Context) -> Result<()>](../src/orchestrator/run.rs:1) CLI -- [struct Cli](src/cli/args.rs:1) -- [fn from_args() -> Cli](src/cli/args.rs:1) +- [struct Cli](../src/cli/args.rs:1) +- [fn from_args() -> Cli](../src/cli/args.rs:1) Logging -- [struct LogOptions](src/logging/mod.rs:1) -- [fn init_logging(opts: &LogOptions) -> Result<()>](src/logging/mod.rs:1) +- [struct LogOptions](../src/logging/mod.rs:1) +- [fn init_logging(opts: &LogOptions) -> Result<()>](../src/logging/mod.rs:1) Config -- [struct Config](src/types.rs:1) -- [enum Topology](src/types.rs:1) -- [struct DeviceSelection](src/types.rs:1) -- [struct FsOptions](src/types.rs:1) -- [struct MountScheme](src/types.rs:1) -- [fn load_and_merge(cli: &Cli) -> Result](src/config/loader.rs:1) -- [fn validate(cfg: &Config) -> Result<()>](src/config/loader.rs:1) +- [struct Config](../src/types.rs:1) +- [enum Topology](../src/types.rs:1) +- [struct DeviceSelection](../src/types.rs:1) +- [struct FsOptions](../src/types.rs:1) +- [struct MountScheme](../src/types.rs:1) +- [fn load_and_merge(cli: &Cli) -> Result](../src/config/loader.rs:1) +- [fn validate(cfg: &Config) -> Result<()>](../src/config/loader.rs:1) Device discovery -- [struct Disk](src/device/discovery.rs:1) -- [struct DeviceFilter](src/device/discovery.rs:1) -- [trait DeviceProvider](src/device/discovery.rs:1) -- [fn discover(filter: &DeviceFilter) -> Result>](src/device/discovery.rs:1) +- [struct Disk](../src/device/discovery.rs:1) +- [struct DeviceFilter](../src/device/discovery.rs:1) +- [trait DeviceProvider](../src/device/discovery.rs:1) +- [fn discover(filter: &DeviceFilter) -> Result>](../src/device/discovery.rs:1) Partitioning -- [struct PartitionSpec](src/partition/plan.rs:1) -- [struct PartitionPlan](src/partition/plan.rs:1) -- [struct PartitionResult](src/partition/plan.rs:1) -- [fn plan_partitions(disks: &[Disk], cfg: &Config) -> Result](src/partition/plan.rs:1) -- [fn apply_partitions(plan: &PartitionPlan) -> Result>](src/partition/plan.rs:1) +- [struct PartitionSpec](../src/partition/plan.rs:1) +- [struct PartitionPlan](../src/partition/plan.rs:1) +- [struct PartitionResult](../src/partition/plan.rs:1) +- [fn plan_partitions(disks: &[Disk], cfg: &Config) -> Result](../src/partition/plan.rs:1) +- [fn apply_partitions(plan: &PartitionPlan) -> Result>](../src/partition/plan.rs:1) Filesystems -- [enum FsKind](src/fs/plan.rs:1) -- [struct FsSpec](src/fs/plan.rs:1) -- [struct FsPlan](src/fs/plan.rs:1) -- [struct FsResult](src/fs/plan.rs:1) -- [fn plan_filesystems(disks: &[Disk], parts: &[PartitionResult], cfg: &Config) -> Result](src/fs/plan.rs:1) -- [fn make_filesystems(plan: &FsPlan) -> Result>](src/fs/plan.rs:1) +- [enum FsKind](../src/fs/plan.rs:1) +- [struct FsSpec](../src/fs/plan.rs:1) +- [struct FsPlan](../src/fs/plan.rs:1) +- [struct FsResult](../src/fs/plan.rs:1) +- [fn plan_filesystems(disks: &[Disk], parts: &[PartitionResult], cfg: &Config) -> Result](../src/fs/plan.rs:1) +- [fn make_filesystems(plan: &FsPlan) -> Result>](../src/fs/plan.rs:1) Mounting -- [struct MountPlan](src/mount/ops.rs:1) -- [struct MountResult](src/mount/ops.rs:1) -- [fn plan_mounts(fs_results: &[FsResult], cfg: &Config) -> Result](src/mount/ops.rs:1) -- [fn apply_mounts(plan: &MountPlan) -> Result>](src/mount/ops.rs:1) -- [fn maybe_write_fstab(mounts: &[MountResult], cfg: &Config) -> Result<()>](src/mount/ops.rs:1) +- [struct MountPlan](../src/mount/ops.rs:1) +- [struct MountResult](../src/mount/ops.rs:1) +- [fn plan_mounts(fs_results: &[FsResult], cfg: &Config) -> Result](../src/mount/ops.rs:1) +- [fn apply_mounts(plan: &MountPlan) -> Result>](../src/mount/ops.rs:1) +- [fn maybe_write_fstab(mounts: &[MountResult], cfg: &Config) -> Result<()>](../src/mount/ops.rs:1) Reporting -- [const REPORT_VERSION: &str](src/report/state.rs:1) -- [struct StateReport](src/report/state.rs:1) -- [fn build_report(...) -> StateReport](src/report/state.rs:1) -- [fn write_report(report: &StateReport) -> Result<()>](src/report/state.rs:1) +- [const REPORT_VERSION: &str](../src/report/state.rs:1) +- [struct StateReport](../src/report/state.rs:1) +- [fn build_report(...) -> StateReport](../src/report/state.rs:1) +- [fn write_report(report: &StateReport) -> Result<()>](../src/report/state.rs:1) Idempotency -- [fn detect_existing_state() -> Result>](src/idempotency/mod.rs:1) -- [fn is_empty_disk(disk: &Disk) -> Result](src/idempotency/mod.rs:1) +- [fn detect_existing_state() -> Result>](../src/idempotency/mod.rs:1) +- [fn is_empty_disk(disk: &Disk) -> Result](../src/idempotency/mod.rs:1) Errors and Result -- [enum Error](src/errors.rs:1) -- [type Result = std::result::Result](src/errors.rs:1) +- [enum Error](../src/errors.rs:1) +- [type Result = std::result::Result](../src/errors.rs:1) Execution flow @@ -190,8 +196,15 @@ Filesystem provisioning defaults - Filesystem tuning options configurable with sensible defaults and extension points Mount scheme and fstab policy -- Mount under /var/cache/ using filesystem UUID to create stable subdirectories -- Optional /etc/fstab generation disabled by default; when enabled, produce deterministic order with documentation +- Runtime root mounts: + - Each data filesystem is root-mounted at `/var/mounts/{UUID}` (runtime only) + - btrfs: `rw,noatime,subvolid=5`; bcachefs: `rw,noatime` +- Final targets (from primary data filesystem only): + - `/var/cache/system`, `/var/cache/etc`, `/var/cache/modules`, `/var/cache/vm-meta` + - btrfs subvolume option: `-o subvol={name},noatime` + - bcachefs subdir option: `-o X-mount.subdir={name},noatime` +- /etc/fstab generation: + - Disabled by default. When enabled, write only the four final targets with `UUID=` sources in deterministic order. Root mounts under `/var/mounts` are excluded. Idempotency detection - Consider the system provisioned when expected GPT names and filesystem labels are present and consistent diff --git a/docs/DEV_WORKFLOW.md b/docs/DEV_WORKFLOW.md index 00893cb..4fc5459 100644 --- a/docs/DEV_WORKFLOW.md +++ b/docs/DEV_WORKFLOW.md @@ -8,18 +8,18 @@ Goal Core Principles 1) Contract-first per module - API signatures and responsibilities are documented in [docs/API-SKELETONS.md](docs/API-SKELETONS.md) and mirrored by crate modules: - - [src/types.rs](src/types.rs) - - [fn load_and_merge()](src/config/loader.rs:1), [fn validate()](src/config/loader.rs:1) - - [fn from_args()](src/cli/args.rs:1) - - [struct LogOptions](src/logging/mod.rs:1), [fn init_logging()](src/logging/mod.rs:1) - - [fn discover()](src/device/discovery.rs:1) - - [fn plan_partitions()](src/partition/plan.rs:1), [fn apply_partitions()](src/partition/plan.rs:1) - - [fn plan_filesystems()](src/fs/plan.rs:1), [fn make_filesystems()](src/fs/plan.rs:1) - - [fn plan_mounts()](src/mount/ops.rs:1), [fn apply_mounts()](src/mount/ops.rs:1), [fn maybe_write_fstab()](src/mount/ops.rs:1) - - [const REPORT_VERSION](src/report/state.rs:1), [fn build_report()](src/report/state.rs:1), [fn write_report()](src/report/state.rs:1) - - [struct Context](src/orchestrator/run.rs:1), [fn run()](src/orchestrator/run.rs:1) - - [fn detect_existing_state()](src/idempotency/mod.rs:1), [fn is_empty_disk()](src/idempotency/mod.rs:1) - - [struct CmdOutput](src/util/mod.rs:1), [fn which_tool()](src/util/mod.rs:1), [fn run_cmd()](src/util/mod.rs:1), [fn run_cmd_capture()](src/util/mod.rs:1), [fn udev_settle()](src/util/mod.rs:1) + - [src/types.rs](../src/types.rs) + - [fn load_and_merge()](../src/config/loader.rs:1), [fn validate()](../src/config/loader.rs:1) + - [fn from_args()](../src/cli/args.rs:1) + - [struct LogOptions](../src/logging/mod.rs:1), [fn init_logging()](../src/logging/mod.rs:1) + - [fn discover()](../src/device/discovery.rs:1) + - [fn plan_partitions()](../src/partition/plan.rs:1), [fn apply_partitions()](../src/partition/plan.rs:1) + - [fn plan_filesystems()](../src/fs/plan.rs:1), [fn make_filesystems()](../src/fs/plan.rs:1) + - [fn plan_mounts()](../src/mount/ops.rs:1), [fn apply_mounts()](../src/mount/ops.rs:1), [fn maybe_write_fstab()](../src/mount/ops.rs:1) + - [const REPORT_VERSION](../src/report/state.rs:1), [fn build_report()](../src/report/state.rs:1), [fn write_report()](../src/report/state.rs:1) + - [struct Context](../src/orchestrator/run.rs:1), [fn run()](../src/orchestrator/run.rs:1) + - [fn detect_existing_state()](../src/idempotency/mod.rs:1), [fn is_empty_disk()](../src/idempotency/mod.rs:1) + - [struct CmdOutput](../src/util/mod.rs:1), [fn which_tool()](../src/util/mod.rs:1), [fn run_cmd()](../src/util/mod.rs:1), [fn run_cmd_capture()](../src/util/mod.rs:1), [fn udev_settle()](../src/util/mod.rs:1) 2) Grep-able region markers in code - Every module contains the following optional annotated regions: @@ -55,22 +55,22 @@ Core Principles 6) Module ownership and boundaries - Add a “Module Responsibilities” section in each module’s header doc comment summarizing scope and non-goals. - Example references: - - [src/device/discovery.rs](src/device/discovery.rs) - - [src/partition/plan.rs](src/partition/plan.rs) - - [src/fs/plan.rs](src/fs/plan.rs) - - [src/mount/ops.rs](src/mount/ops.rs) - - [src/report/state.rs](src/report/state.rs) + - [src/device/discovery.rs](../src/device/discovery.rs) + - [src/partition/plan.rs](../src/partition/plan.rs) + - [src/fs/plan.rs](../src/fs/plan.rs) + - [src/mount/ops.rs](../src/mount/ops.rs) + - [src/report/state.rs](../src/report/state.rs) 7) Invariants and safety notes - For code that must uphold safety or idempotency invariants, annotate with: // SAFETY: explanation // IDEMPOTENCY: explanation - Example locations: - - [fn apply_partitions()](src/partition/plan.rs:1) must enforce empty-disks rule when configured. - - [fn make_filesystems()](src/fs/plan.rs:1) must not run if partitioning failed. + - [fn apply_partitions()](../src/partition/plan.rs:1) must enforce empty-disks rule when configured. + - [fn make_filesystems()](../src/fs/plan.rs:1) must not run if partitioning failed. 8) Error mapping consistency - - Centralize conversions to [enum Error](src/errors.rs:1). When calling external tools, wrap failures into Error::Tool with stderr captured. + - Centralize conversions to [enum Error](../src/errors.rs:1). When calling external tools, wrap failures into Error::Tool with stderr captured. - Annotate mapping areas with: // ERROR: mapping external failure to Error::Tool @@ -111,7 +111,7 @@ Checklist for adding a new feature - Add examples if config or output formats change - Update [config/zosstorage.example.yaml](config/zosstorage.example.yaml) or add a new example file - Keep error mapping and logging consistent: - - Ensure any external tool calls map errors to [enum Error](src/errors.rs:1) + - Ensure any external tool calls map errors to [enum Error](../src/errors.rs:1) - Run cargo build and update any broken references Optional automation (future) diff --git a/docs/SCHEMA.md b/docs/SCHEMA.md index 0a7fc49..94bcff1 100644 --- a/docs/SCHEMA.md +++ b/docs/SCHEMA.md @@ -43,7 +43,7 @@ device_selection: allow_removable: false # future option; default false min_size_gib: 10 # ignore devices smaller than this (default 10) topology: # desired overall layout; see values below - mode: single # single | dual_independent | ssd_hdd_bcachefs | btrfs_raid1 (optional) + mode: btrfs_single # btrfs_single | bcachefs_single | dual_independent | bcachefs2_copy | ssd_hdd_bcachefs | btrfs_raid1 partitioning: alignment_mib: 1 # GPT alignment in MiB require_empty_disks: true # abort if any partition or FS signatures exist @@ -83,8 +83,8 @@ report: Topology modes - btrfs_single: One eligible disk. Create BIOS boot (if enabled), ESP 512 MiB, remainder as data. Create a btrfs filesystem labeled ZOSDATA on the data partition. - bcachefs_single: One eligible disk. Create BIOS boot (if enabled), ESP 512 MiB, remainder as data. Create a bcachefs filesystem labeled ZOSDATA on the data partition. -- dual_independent: Two eligible disks. On each disk, create BIOS boot (if enabled) + ESP + data. Create an independent btrfs filesystem labeled ZOSDATA on each data partition. No RAID by default. -- bcachefs_2copy: Two eligible disks. Create data partitions on both, then create a single multi-device bcachefs labeled ZOSDATA spanning the data partitions (two-copies semantics to be tuned via mkfs options in a follow-up). +- dual_independent: One or more eligible disks. On each disk, create BIOS boot (if enabled) + ESP + data. Create an independent btrfs filesystem labeled ZOSDATA on each data partition. No RAID by default. +- bcachefs2_copy: Two or more eligible disks (minimum 2). Create data partitions and then a single multi-device bcachefs labeled ZOSDATA spanning those data partitions. The mkfs step uses `--replicas=2` (data and metadata). - ssd_hdd_bcachefs: One SSD/NVMe and one HDD. Create BIOS boot (if enabled) + ESP on both as required. Create cache (on SSD) and data/backing (on HDD) partitions named zoscache and zosdata respectively. Create a bcachefs labeled ZOSDATA across SSD(HDD) per policy (SSD cache/promote; HDD backing). - btrfs_raid1: Optional mode if explicitly requested. Create mirrored btrfs across two disks for the data role with raid1 profile. Not enabled by default. @@ -119,11 +119,15 @@ Filesystem section - vfat: label ZOSBOOT used for ESP. Mount section -- base_dir: default /var/cache. -- scheme: - - per_uuid: mount data filesystems at /var/cache/ - - custom: reserved for future mapping-by-config, not yet implemented. -- fstab.enabled: default false. When true, zosstorage will generate fstab entries in deterministic order. +- Runtime root mounts (all data filesystems): + - Each data filesystem is root-mounted at `/var/mounts/{UUID}` + - btrfs root mount options: `rw,noatime,subvolid=5` + - bcachefs root mount options: `rw,noatime` +- Subvolume mounts (from the primary data filesystem only) to final targets: + - Targets: `/var/cache/system`, `/var/cache/etc`, `/var/cache/modules`, `/var/cache/vm-meta` + - btrfs subvol options: `-o rw,noatime,subvol={name}` + - bcachefs subdir options: `-o rw,noatime,X-mount.subdir={name}` +- fstab.enabled: default false. When true, zosstorage writes only the four subvolume mount entries, in deterministic target order, using `UUID=` sources for the filesystem; root mounts under `/var/mounts` are excluded. Report section - path: default /run/zosstorage/state.json. @@ -135,7 +139,7 @@ Minimal single-disk btrfs ```yaml version: 1 topology: - mode: single + mode: btrfs_single ``` Dual independent btrfs (two disks) @@ -186,15 +190,15 @@ Future extensions - Multiple topology groups on multi-disk systems Reference modules -- [src/types.rs](src/types.rs) -- [src/config/loader.rs](src/config/loader.rs) -- [src/cli/args.rs](src/cli/args.rs) -- [src/orchestrator/run.rs](src/orchestrator/run.rs) -- [src/partition/plan.rs](src/partition/plan.rs) -- [src/fs/plan.rs](src/fs/plan.rs) -- [src/mount/ops.rs](src/mount/ops.rs) -- [src/report/state.rs](src/report/state.rs) -- [src/idempotency/mod.rs](src/idempotency/mod.rs) +- [src/types.rs](../src/types.rs) +- [src/config/loader.rs](../src/config/loader.rs) +- [src/cli/args.rs](../src/cli/args.rs) +- [src/orchestrator/run.rs](../src/orchestrator/run.rs) +- [src/partition/plan.rs](../src/partition/plan.rs) +- [src/fs/plan.rs](../src/fs/plan.rs) +- [src/mount/ops.rs](../src/mount/ops.rs) +- [src/report/state.rs](../src/report/state.rs) +- [src/idempotency/mod.rs](../src/idempotency/mod.rs) Change log - v1: Initial draft of schema and precedence rules diff --git a/docs/SPECS.md b/docs/SPECS.md index 27a1134..38d012e 100644 --- a/docs/SPECS.md +++ b/docs/SPECS.md @@ -3,31 +3,31 @@ This document finalizes core specifications required before code skeleton implementation. It complements [docs/ARCHITECTURE.md](docs/ARCHITECTURE.md) and [docs/SCHEMA.md](docs/SCHEMA.md), and references the API declarations listed in [docs/API.md](docs/API.md). Linked modules and functions -- Logging module: [src/logging/mod.rs](src/logging/mod.rs) - - [fn init_logging(opts: &LogOptions) -> Result<()>](src/logging/mod.rs:1) -- Report module: [src/report/state.rs](src/report/state.rs) - - [const REPORT_VERSION: &str](src/report/state.rs:1) - - [fn build_report(...) -> StateReport](src/report/state.rs:1) - - [fn write_report(report: &StateReport) -> Result<()>](src/report/state.rs:1) -- Device module: [src/device/discovery.rs](src/device/discovery.rs) - - [fn discover(filter: &DeviceFilter) -> Result>](src/device/discovery.rs:1) -- Partitioning module: [src/partition/plan.rs](src/partition/plan.rs) - - [fn plan_partitions(disks: &[Disk], cfg: &Config) -> Result](src/partition/plan.rs:1) - - [fn apply_partitions(plan: &PartitionPlan) -> Result>](src/partition/plan.rs:1) -- Filesystems module: [src/fs/plan.rs](src/fs/plan.rs) - - [fn plan_filesystems(disks: &[Disk], parts: &[PartitionResult], cfg: &Config) -> Result](src/fs/plan.rs:1) - - [fn make_filesystems(plan: &FsPlan) -> Result>](src/fs/plan.rs:1) -- Mount module: [src/mount/ops.rs](src/mount/ops.rs) - - [fn plan_mounts(fs_results: &[FsResult], cfg: &Config) -> Result](src/mount/ops.rs:1) - - [fn apply_mounts(plan: &MountPlan) -> Result>](src/mount/ops.rs:1) - - [fn maybe_write_fstab(mounts: &[MountResult], cfg: &Config) -> Result<()>](src/mount/ops.rs:1) -- Idempotency module: [src/idempotency/mod.rs](src/idempotency/mod.rs) - - [fn detect_existing_state() -> Result>](src/idempotency/mod.rs:1) - - [fn is_empty_disk(disk: &Disk) -> Result](src/idempotency/mod.rs:1) -- CLI module: [src/cli/args.rs](src/cli/args.rs) - - [fn from_args() -> Cli](src/cli/args.rs:1) -- Orchestrator: [src/orchestrator/run.rs](src/orchestrator/run.rs) - - [fn run(ctx: &Context) -> Result<()>](src/orchestrator/run.rs:1) +- Logging module: [src/logging/mod.rs](../src/logging/mod.rs) + - [fn init_logging(opts: &LogOptions) -> Result<()>](../src/logging/mod.rs:1) +- Report module: [src/report/state.rs](../src/report/state.rs) + - [const REPORT_VERSION: &str](../src/report/state.rs:1) + - [fn build_report(...) -> StateReport](../src/report/state.rs:1) + - [fn write_report(report: &StateReport) -> Result<()>](../src/report/state.rs:1) +- Device module: [src/device/discovery.rs](../src/device/discovery.rs) + - [fn discover(filter: &DeviceFilter) -> Result>](../src/device/discovery.rs:1) +- Partitioning module: [src/partition/plan.rs](../src/partition/plan.rs) + - [fn plan_partitions(disks: &[Disk], cfg: &Config) -> Result](../src/partition/plan.rs:1) + - [fn apply_partitions(plan: &PartitionPlan) -> Result>](../src/partition/plan.rs:1) +- Filesystems module: [src/fs/plan.rs](../src/fs/plan.rs) + - [fn plan_filesystems(disks: &[Disk], parts: &[PartitionResult], cfg: &Config) -> Result](../src/fs/plan.rs:1) + - [fn make_filesystems(plan: &FsPlan) -> Result>](../src/fs/plan.rs:1) +- Mount module: [src/mount/ops.rs](../src/mount/ops.rs) + - [fn plan_mounts(fs_results: &[FsResult], cfg: &Config) -> Result](../src/mount/ops.rs:1) + - [fn apply_mounts(plan: &MountPlan) -> Result>](../src/mount/ops.rs:1) + - [fn maybe_write_fstab(mounts: &[MountResult], cfg: &Config) -> Result<()>](../src/mount/ops.rs:1) +- Idempotency module: [src/idempotency/mod.rs](../src/idempotency/mod.rs) + - [fn detect_existing_state() -> Result>](../src/idempotency/mod.rs:1) + - [fn is_empty_disk(disk: &Disk) -> Result](../src/idempotency/mod.rs:1) +- CLI module: [src/cli/args.rs](../src/cli/args.rs) + - [fn from_args() -> Cli](../src/cli/args.rs:1) +- Orchestrator: [src/orchestrator/run.rs](../src/orchestrator/run.rs) + - [fn run(ctx: &Context) -> Result<()>](../src/orchestrator/run.rs:1) --- @@ -39,7 +39,7 @@ Goals Configuration - Levels: error, warn, info, debug (default info). -- Propagation: single global initialization via [fn init_logging](src/logging/mod.rs:1). Subsequent calls must be no-ops. +- Propagation: single global initialization via [fn init_logging](../src/logging/mod.rs:1). Subsequent calls must be no-ops. Implementation notes - Use tracing and tracing-subscriber. @@ -57,7 +57,7 @@ Location - Default output: /run/zosstorage/state.json Versioning -- Include a top-level string field version equal to [REPORT_VERSION](src/report/state.rs:1). Start with v1. +- Include a top-level string field version equal to [REPORT_VERSION](../src/report/state.rs:1). Start with v1. Schema example @@ -154,17 +154,17 @@ Default exclude patterns - ^/dev/fd\\d+$ Selection policy -- Compile include and exclude regex into [DeviceFilter](src/device/discovery.rs). +- Compile include and exclude regex into [DeviceFilter](../src/device/discovery.rs). - Enumerate device candidates and apply: - Must match at least one include. - Must not match any exclude. - Must be larger than min_size_gib (default 10). - Probing - Gather size, rotational flag, model, serial when available. - - Expose via [struct Disk](src/device/discovery.rs:1). + - Expose via [struct Disk](../src/device/discovery.rs:1). No eligible disks -- Return a specific error variant in [enum Error](src/errors.rs:1). +- Return a specific error variant in [enum Error](../src/errors.rs:1). --- @@ -181,17 +181,20 @@ Layout defaults - Cache partitions (only in ssd_hdd_bcachefs): GPT name zoscache on SSD. Per-topology specifics -- single: All roles on the single disk. -- dual_independent: Each disk gets BIOS boot + ESP + data. -- ssd_hdd_bcachefs: SSD gets BIOS boot + ESP + zoscache, HDD gets BIOS boot + ESP + zosdata. +- btrfs_single: All roles on the single disk; data formatted as btrfs. +- bcachefs_single: All roles on the single disk; data formatted as bcachefs. +- dual_independent: On each eligible disk (one or more), create BIOS boot (if applicable), ESP, and data. +- bcachefs_2copy: Create data partitions on two or more disks; later formatted as one multi-device bcachefs spanning all data partitions. +- ssd_hdd_bcachefs: SSD gets BIOS boot + ESP + zoscache; HDD gets BIOS boot + ESP + zosdata; combined later into one bcachefs. +- btrfs_raid1: Two disks minimum; data partitions mirrored via btrfs RAID1. Safety checks - Ensure unique partition UUIDs. -- Verify no pre-existing partitions or signatures. Use blkid or similar via [run_cmd_capture](src/util/mod.rs:1). -- After partition creation, run udev settle via [udev_settle](src/util/mod.rs:1). +- Verify no pre-existing partitions or signatures. Use blkid or similar via [run_cmd_capture](../src/util/mod.rs:1). +- After partition creation, run udev settle via [udev_settle](../src/util/mod.rs:1). Application -- Utilize sgdisk helpers in [apply_partitions](src/partition/plan.rs:1). +- Utilize sgdisk helpers in [apply_partitions](../src/partition/plan.rs:1). --- @@ -199,34 +202,38 @@ Application Kinds - Vfat for ESP, label ZOSBOOT. -- Btrfs for data on single and dual_independent. -- Bcachefs for ssd_hdd_bcachefs (SSD cache, HDD backing). +- Btrfs for data in btrfs_single, dual_independent, and btrfs_raid1 (with RAID1 profile). +- Bcachefs for data in bcachefs_single, ssd_hdd_bcachefs (SSD cache + HDD backing), and bcachefs_2copy (multi-device). - All data filesystems use label ZOSDATA. Defaults -- btrfs: compression zstd:3, raid_profile none unless explicitly set to raid1 in btrfs_raid1 mode. -- bcachefs: cache_mode promote, compression zstd, checksum crc32c. +- btrfs: compression zstd:3, raid_profile none unless explicitly set; for btrfs_raid1 use -m raid1 -d raid1. +- bcachefs: cache_mode promote, compression zstd, checksum crc32c; for bcachefs_2copy use `--replicas=2` (data and metadata). - vfat: ESP label ZOSBOOT. Planning and execution -- Decide mapping of [PartitionResult](src/partition/plan.rs:1) to [FsSpec](src/fs/plan.rs:1) in [plan_filesystems](src/fs/plan.rs:1). -- Create filesystems in [make_filesystems](src/fs/plan.rs:1) through wrapped mkfs tools. -- Capture resulting identifiers (fs uuid, label) in [FsResult](src/fs/plan.rs:1). +- Decide mapping of [PartitionResult](../src/partition/plan.rs:1) to [FsSpec](../src/fs/plan.rs:1) in [plan_filesystems](../src/fs/plan.rs:1). +- Create filesystems in [make_filesystems](../src/fs/plan.rs:1) through wrapped mkfs tools. +- Capture resulting identifiers (fs uuid, label) in [FsResult](../src/fs/plan.rs:1). --- ## 6. Mount scheme and fstab policy -Scheme -- per_uuid under /var/cache: directories named as filesystem UUIDs. +Runtime root mounts (all data filesystems) +- Each data filesystem is root-mounted at `/var/mounts/{UUID}` (runtime only). +- btrfs root mount options: `rw,noatime,subvolid=5` +- bcachefs root mount options: `rw,noatime` -Mount options -- btrfs: ssd when non-rotational underlying device, compress from config, defaults otherwise. -- vfat: defaults, utf8. +Final subvolume/subdir mounts (from the primary data filesystem) +- Create or ensure subvolumes named: `system`, `etc`, `modules`, `vm-meta` +- Mount targets: `/var/cache/system`, `/var/cache/etc`, `/var/cache/modules`, `/var/cache/vm-meta` +- btrfs options: `-o rw,noatime,subvol={name}` +- bcachefs options: `-o rw,noatime,X-mount.subdir={name}` -fstab +fstab policy - Disabled by default. -- When enabled, [maybe_write_fstab](src/mount/ops.rs:1) writes deterministic entries sorted by target path. +- When enabled, [maybe_write_fstab](../src/mount/ops.rs:1) writes only the four final subvolume/subdir entries using `UUID=` sources, in deterministic target order. Root mounts under `/var/mounts` are excluded. --- @@ -235,20 +242,23 @@ fstab Signals for already-provisioned system - Expected GPT names found: zosboot, zosdata, and zoscache when applicable. - Filesystems with labels ZOSBOOT for ESP and ZOSDATA for all data filesystems. -- When consistent with selected topology, [detect_existing_state](src/idempotency/mod.rs:1) returns a StateReport and orchestrator exits success without changes. +- When consistent with selected topology, [detect_existing_state](../src/idempotency/mod.rs:1) returns a StateReport and orchestrator exits success without changes. Disk emptiness -- [is_empty_disk](src/idempotency/mod.rs:1) checks for absence of partitions and FS signatures before any modification. +- [is_empty_disk](../src/idempotency/mod.rs:1) checks for absence of partitions and FS signatures before any modification. --- ## 8. CLI flags and help text outline -Flags mirrored by [struct Cli](src/cli/args.rs:1) parsed via [from_args](src/cli/args.rs:1) +Flags mirrored by [struct Cli](../src/cli/args.rs:1) parsed via [from_args](../src/cli/args.rs:1) - --config PATH - --log-level LEVEL error | warn | info | debug - --log-to-file - --fstab enable fstab generation +- --show print preview JSON to stdout (non-destructive) +- --report PATH write preview JSON to file (non-destructive) +- --apply perform partitioning, filesystem creation, and mounts (DESTRUCTIVE) - --force present but returns unimplemented error Kernel cmdline @@ -257,7 +267,7 @@ Kernel cmdline Help text sections - NAME, SYNOPSIS, DESCRIPTION - CONFIG PRECEDENCE -- TOPOLOGIES: single, dual_independent, ssd_hdd_bcachefs, btrfs_raid1 +- TOPOLOGIES: btrfs_single, bcachefs_single, dual_independent, bcachefs_2copy, ssd_hdd_bcachefs, btrfs_raid1 - SAFETY AND IDEMPOTENCY - REPORTS - EXIT CODES: 0 success or already_provisioned, non-zero on error @@ -267,9 +277,10 @@ Help text sections ## 9. Integration testing plan (QEMU KVM) Scenarios to scaffold in [tests/](tests/) -- Single disk 40 GiB virtio: validates single topology end-to-end smoke. -- Dual NVMe 40 GiB each: validates dual_independent topology. -- SSD NVMe + HDD virtio: validates ssd_hdd_bcachefs topology. +- Single disk 40 GiB virtio: validates btrfs_single topology end-to-end smoke. +- Dual NVMe 40 GiB each: validates dual_independent topology (independent btrfs per disk). +- SSD NVMe + HDD virtio: validates ssd_hdd_bcachefs topology (bcachefs with SSD cache/promote, HDD backing). +- Three disks: validates bcachefs_2copy across data partitions using `--replicas=2`. - Negative: no eligible disks, or non-empty disk should abort. Test strategy @@ -281,7 +292,7 @@ Test strategy Artifacts to validate - Presence of expected partition GPT names. - Filesystems created with correct labels. -- Mountpoints under /var/cache/ when running in a VM. +- Runtime root mounts under `/var/mounts/{UUID}` and final subvolume targets at `/var/cache/{system,etc,modules,vm-meta}`. - JSON report validates against v1 schema. --- diff --git a/docs/adr/0001-modular-workflow.md b/docs/adr/0001-modular-workflow.md index 03f98ad..9f12efc 100644 --- a/docs/adr/0001-modular-workflow.md +++ b/docs/adr/0001-modular-workflow.md @@ -47,14 +47,14 @@ Consequences Implementation Notes - Region markers have been added to key modules: - - [src/config/loader.rs](src/config/loader.rs) - - [src/orchestrator/run.rs](src/orchestrator/run.rs) - - [src/cli/args.rs](src/cli/args.rs) - - [src/device/discovery.rs](src/device/discovery.rs) - - [src/partition/plan.rs](src/partition/plan.rs) - - [src/fs/plan.rs](src/fs/plan.rs) - - [src/mount/ops.rs](src/mount/ops.rs) - - [src/report/state.rs](src/report/state.rs) + - [src/config/loader.rs](../src/config/loader.rs) + - [src/orchestrator/run.rs](../src/orchestrator/run.rs) + - [src/cli/args.rs](../src/cli/args.rs) + - [src/device/discovery.rs](../src/device/discovery.rs) + - [src/partition/plan.rs](../src/partition/plan.rs) + - [src/fs/plan.rs](../src/fs/plan.rs) + - [src/mount/ops.rs](../src/mount/ops.rs) + - [src/report/state.rs](../src/report/state.rs) - Remaining modules will follow the same pattern as needed (e.g., util, idempotency, main/lib if helpful). Related Documents