From 7cef73368b809cf163c3a98de9452b505887b527 Mon Sep 17 00:00:00 2001 From: Jan De Landtsheer Date: Mon, 29 Sep 2025 22:56:23 +0200 Subject: [PATCH] topology: unify CLI and types::Topology (ValueEnum + aliases); bcachefs-2copy uses --replicas=2; update orchestrator call to make_filesystems(cfg); minor overlay fix; docs previously synced --- docs/API.md | 2 +- docs/SCHEMA.md | 8 +++++--- src/cli/args.rs | 29 +++-------------------------- src/config/loader.rs | 4 ++-- src/fs/plan.rs | 11 +++++++---- src/orchestrator/run.rs | 4 ++-- src/types.rs | 40 +++++++++++++++++++++++++++++++++++++--- 7 files changed, 57 insertions(+), 41 deletions(-) diff --git a/docs/API.md b/docs/API.md index 346dec9..5ef1665 100644 --- a/docs/API.md +++ b/docs/API.md @@ -72,7 +72,7 @@ Configuration types - [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: single, dual_independent, ssd_hdd_bcachefs, btrfs_raid1 (opt-in). + - Values: btrfs_single, bcachefs_single, dual_independent, bcachefs_2copy, 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) diff --git a/docs/SCHEMA.md b/docs/SCHEMA.md index 785702f..0a7fc49 100644 --- a/docs/SCHEMA.md +++ b/docs/SCHEMA.md @@ -81,9 +81,11 @@ report: ``` Topology modes -- single: One eligible disk. Create BIOS boot (if enabled), ESP 512 MiB, remainder as data. Make a btrfs filesystem labeled ZOSDATA on the data partition. -- dual_independent: Two eligible disks. On each disk, create BIOS boot (if enabled) + ESP + data. Create a separate btrfs filesystem labeled ZOSDATA on each data partition. No RAID by default. -- 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. Make a bcachefs filesystem across both with label ZOSDATA, using SSD as cache/promote and HDD as backing. +- 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). +- 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. Validation rules diff --git a/src/cli/args.rs b/src/cli/args.rs index 195077f..a50e514 100644 --- a/src/cli/args.rs +++ b/src/cli/args.rs @@ -51,31 +51,8 @@ impl std::fmt::Display for LogLevelArg { } } -/// Topology argument (maps to config Topology with snake_case semantics). -#[derive(Debug, Clone, Copy, ValueEnum)] -#[value(rename_all = "kebab_case")] -pub enum TopologyArg { - BtrfsSingle, - BcachefsSingle, - DualIndependent, - SsdHddBcachefs, - Bcachefs2Copy, - BtrfsRaid1, -} - -impl std::fmt::Display for TopologyArg { - fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { - let s = match self { - TopologyArg::BtrfsSingle => "btrfs_single", - TopologyArg::BcachefsSingle => "bcachefs_single", - TopologyArg::DualIndependent => "dual_independent", - TopologyArg::SsdHddBcachefs => "ssd_hdd_bcachefs", - TopologyArg::Bcachefs2Copy => "bcachefs_2copy", - TopologyArg::BtrfsRaid1 => "btrfs_raid1", - }; - f.write_str(s) - } -} +//// Using crate::types::Topology (ValueEnum) directly for CLI parsing to avoid duplication. +// TopologyArg enum removed; CLI field uses crate::types::Topology /// zosstorage - one-shot disk initializer for initramfs. #[derive(Debug, Parser)] @@ -99,7 +76,7 @@ pub struct Cli { /// Select topology (overrides config topology) #[arg(short = 't', long = "topology", value_enum)] - pub topology: Option, + pub topology: Option, /// Present but non-functional; returns unimplemented error #[arg(short = 'f', long = "force")] diff --git a/src/config/loader.rs b/src/config/loader.rs index 043eaf9..a533645 100644 --- a/src/config/loader.rs +++ b/src/config/loader.rs @@ -271,8 +271,8 @@ fn cli_overlay_value(cli: &Cli) -> Value { root.insert("device_selection".into(), Value::Object(device_selection)); } - // topology override via --topology - if let Some(t) = cli.topology { + // topology override via --topology (avoid moving out of borrowed field) + if let Some(t) = cli.topology.as_ref() { root.insert("topology".into(), Value::String(t.to_string())); } diff --git a/src/fs/plan.rs b/src/fs/plan.rs index 72b7aa9..42c42b9 100644 --- a/src/fs/plan.rs +++ b/src/fs/plan.rs @@ -4,7 +4,7 @@ // api: fs::FsPlan { specs: Vec } // api: fs::FsResult { kind: FsKind, devices: Vec, uuid: String, label: String } // api: fs::plan_filesystems(parts: &[crate::partition::PartitionResult], cfg: &crate::config::types::Config) -> crate::Result -// api: fs::make_filesystems(plan: &FsPlan) -> crate::Result> +// api: fs::make_filesystems(plan: &FsPlan, cfg: &crate::types::Config) -> crate::Result> // REGION: API-END // // REGION: RESPONSIBILITIES @@ -206,7 +206,7 @@ pub fn plan_filesystems( /// - This initial implementation applies labels and creates filesystems with minimal flags. /// - Btrfs RAID profile (e.g., raid1) will be applied in a follow-up by mapping config to mkfs flags. /// - UUID is captured via blkid -o export on the first device of each spec. -pub fn make_filesystems(plan: &FsPlan) -> Result> { +pub fn make_filesystems(plan: &FsPlan, cfg: &Config) -> Result> { // Discover required tools up-front let vfat_tool = which_tool("mkfs.vfat")?; let btrfs_tool = which_tool("mkfs.btrfs")?; @@ -289,9 +289,12 @@ pub fn make_filesystems(plan: &FsPlan) -> Result> { if spec.devices.is_empty() { return Err(Error::Filesystem("bcachefs requires at least one device".into())); } - // bcachefs format --label LABEL dev_cache dev_backing ... (single-device also supported) - // TODO(fs): map compression/checksum/cache-mode and data/metadata replica flags in a follow-up. + // bcachefs format --label LABEL [--replicas=2] dev1 [dev2 ...] + // Apply replicas policy for Bcachefs2Copy topology (data+metadata replicas = 2) let mut args: Vec = vec![mkfs.clone(), "format".into(), "--label".into(), spec.label.clone()]; + if matches!(cfg.topology, Topology::Bcachefs2Copy) { + args.push("--replicas=2".into()); + } args.extend(spec.devices.iter().cloned()); let args_ref: Vec<&str> = args.iter().map(|s| s.as_str()).collect(); run_cmd(&args_ref)?; diff --git a/src/orchestrator/run.rs b/src/orchestrator/run.rs index 9859920..b34f0e5 100644 --- a/src/orchestrator/run.rs +++ b/src/orchestrator/run.rs @@ -198,7 +198,7 @@ pub fn run(ctx: &Context) -> Result<()> { // Filesystem planning and creation let fs_plan = zfs::plan_filesystems(&part_results, &ctx.cfg)?; info!("orchestrator: filesystem plan contains {} spec(s)", fs_plan.specs.len()); - let fs_results = zfs::make_filesystems(&fs_plan)?; + let fs_results = zfs::make_filesystems(&fs_plan, &ctx.cfg)?; info!("orchestrator: created {} filesystem(s)", fs_results.len()); // Mount planning and application @@ -345,7 +345,7 @@ fn build_summary_json(disks: &[Disk], plan: &partition::PartitionPlan, cfg: &Con crate::types::Topology::BcachefsSingle => "bcachefs_single", crate::types::Topology::DualIndependent => "dual_independent", crate::types::Topology::SsdHddBcachefs => "ssd_hdd_bcachefs", - crate::types::Topology::Bcachefs2Copy => "bcachefs_2copy", + crate::types::Topology::Bcachefs2Copy => "bcachefs2_copy", crate::types::Topology::BtrfsRaid1 => "btrfs_raid1", }; diff --git a/src/types.rs b/src/types.rs index 9b89b78..b1270b1 100644 --- a/src/types.rs +++ b/src/types.rs @@ -2,8 +2,21 @@ //! //! Mirrors docs in [docs/SCHEMA.md](docs/SCHEMA.md) and is loaded/validated by //! [fn load_and_merge()](src/config/loader.rs:1) and [fn validate()](src/config/loader.rs:1). +// +// REGION: API +// api: types::Topology { BtrfsSingle, BcachefsSingle, DualIndependent, Bcachefs2Copy, SsdHddBcachefs, BtrfsRaid1 } +// api: types::Config { logging, device_selection, topology, partitioning, filesystem, mount, report } +// api: types::Partitioning { alignment_mib, require_empty_disks, bios_boot, esp, data, cache } +// api: types::FsOptions { btrfs, bcachefs, vfat } +// REGION: API-END +// +// REGION: RESPONSIBILITIES +// - Define serde-serializable configuration types and enums used across modules. +// - Keep field names and enums stable; update docs/SCHEMA.md when public surface changes. +// REGION: RESPONSIBILITIES-END use serde::{Deserialize, Serialize}; +use clap::ValueEnum; #[derive(Debug, Clone, Serialize, Deserialize)] pub struct LoggingConfig { @@ -25,23 +38,44 @@ pub struct DeviceSelection { pub min_size_gib: u64, } -#[derive(Debug, Clone, Serialize, Deserialize)] +#[derive(Debug, Clone, Copy, Serialize, Deserialize, ValueEnum)] #[serde(rename_all = "snake_case")] +#[value(rename_all = "snake_case")] pub enum Topology { /// Single eligible disk; btrfs on remainder. + #[value(alias = "btrfs-single")] BtrfsSingle, /// Single eligible disk; bcachefs on remainder. + #[value(alias = "bcachefs-single")] BcachefsSingle, - /// Two eligible disks; independent btrfs on each data partition. + /// Independent btrfs filesystems on each data partition (any number of disks). + #[value(alias = "dual-independent")] DualIndependent, /// SSD + HDD; bcachefs with SSD cache/promote and HDD backing. + #[value(alias = "ssd-hdd-bcachefs")] SsdHddBcachefs, - /// Two-disk bcachefs layout using both data partitions (2 copies semantics). + /// Multi-device bcachefs with two replicas (data+metadata). + #[value(alias = "bcachefs2-copy", alias = "bcachefs-2copy", alias = "bcachefs-2-copy")] Bcachefs2Copy, /// Optional mirrored btrfs across two disks when explicitly requested. + #[value(alias = "btrfs-raid1")] BtrfsRaid1, } +impl std::fmt::Display for Topology { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + let s = match self { + Topology::BtrfsSingle => "btrfs_single", + Topology::BcachefsSingle => "bcachefs_single", + Topology::DualIndependent => "dual_independent", + Topology::SsdHddBcachefs => "ssd_hdd_bcachefs", + Topology::Bcachefs2Copy => "bcachefs2_copy", + Topology::BtrfsRaid1 => "btrfs_raid1", + }; + f.write_str(s) + } +} + #[derive(Debug, Clone, Serialize, Deserialize)] pub struct BiosBootSpec { /// Whether to create a tiny BIOS boot partition.