784 lines
22 KiB
Markdown
784 lines
22 KiB
Markdown
# zosstorage API Skeletons (Proposed)
|
|
|
|
Purpose
|
|
- This document proposes concrete Rust module skeletons with public API surface and doc comments.
|
|
- Bodies intentionally use todo!() or are omitted for approval-first workflow.
|
|
- 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)
|
|
|
|
Conventions
|
|
- Shared [type Result<T>](../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.
|
|
|
|
---
|
|
|
|
## Crate root
|
|
|
|
References
|
|
- [src/lib.rs](../src/lib.rs)
|
|
- [type Result<T> = std::result::Result<T, Error>](../src/errors.rs:1)
|
|
|
|
Skeleton (for later implementation in code mode)
|
|
```rust
|
|
//! Crate root for zosstorage: one-shot disk provisioning utility for initramfs.
|
|
|
|
pub mod cli;
|
|
pub mod logging;
|
|
pub mod config;
|
|
pub mod device;
|
|
pub mod partition;
|
|
pub mod fs;
|
|
pub mod mount;
|
|
pub mod report;
|
|
pub mod orchestrator;
|
|
pub mod idempotency;
|
|
pub mod util;
|
|
pub mod errors;
|
|
|
|
pub use errors::{Error, Result};
|
|
|
|
/// Crate version constants.
|
|
pub const VERSION: &str = env!("CARGO_PKG_VERSION");
|
|
```
|
|
|
|
---
|
|
|
|
## Errors
|
|
|
|
References
|
|
- [enum Error](../src/errors.rs:1)
|
|
- [type Result<T>](../src/errors.rs:1)
|
|
|
|
Skeleton
|
|
```rust
|
|
use thiserror::Error as ThisError;
|
|
|
|
/// Top-level error for zosstorage.
|
|
#[derive(Debug, ThisError)]
|
|
pub enum Error {
|
|
#[error("configuration error: {0}")]
|
|
Config(String),
|
|
#[error("validation error: {0}")]
|
|
Validation(String),
|
|
#[error("device discovery error: {0}")]
|
|
Device(String),
|
|
#[error("partitioning error: {0}")]
|
|
Partition(String),
|
|
#[error("filesystem error: {0}")]
|
|
Filesystem(String),
|
|
#[error("mount error: {0}")]
|
|
Mount(String),
|
|
#[error("report error: {0}")]
|
|
Report(String),
|
|
#[error("external tool '{tool}' failed with status {status}: {stderr}")]
|
|
Tool {
|
|
tool: String,
|
|
status: i32,
|
|
stderr: String,
|
|
},
|
|
#[error("unimplemented: {0}")]
|
|
Unimplemented(&'static str),
|
|
#[error(transparent)]
|
|
Other(#[from] anyhow::Error),
|
|
}
|
|
|
|
pub type Result<T> = std::result::Result<T, Error>;
|
|
```
|
|
|
|
---
|
|
|
|
## Entrypoint
|
|
|
|
References
|
|
- [fn main()](../src/main.rs:1)
|
|
- [fn run(ctx: &Context) -> Result<()>](../src/orchestrator/run.rs:1)
|
|
|
|
Skeleton
|
|
```rust
|
|
use zosstorage::{cli, config, logging, orchestrator, Result};
|
|
|
|
fn main() {
|
|
// Initialize minimal logging early (fallback).
|
|
// Proper logging config will be applied after CLI parsing.
|
|
// No stdout printing.
|
|
if let Err(e) = real_main() {
|
|
// In initramfs, emit minimal error to stderr and exit non-zero.
|
|
eprintln!("error: {e}");
|
|
std::process::exit(1);
|
|
}
|
|
}
|
|
|
|
fn real_main() -> Result<()> {
|
|
let cli = cli::from_args();
|
|
let log_opts = logging::LogOptions::from_cli(&cli);
|
|
logging::init_logging(&log_opts)?;
|
|
|
|
let cfg = config::load_and_merge(&cli)?;
|
|
config::validate(&cfg)?;
|
|
|
|
let ctx = orchestrator::Context::new(cfg, log_opts);
|
|
orchestrator::run(&ctx)
|
|
}
|
|
```
|
|
|
|
---
|
|
|
|
## CLI
|
|
|
|
References
|
|
- [struct Cli](../src/cli/args.rs:1)
|
|
- [fn from_args() -> Cli](../src/cli/args.rs:1)
|
|
|
|
Skeleton
|
|
```rust
|
|
use clap::{Parser, ValueEnum};
|
|
|
|
/// zosstorage - one-shot disk initializer for initramfs.
|
|
#[derive(Debug, Parser)]
|
|
#[command(name = "zosstorage", disable_help_subcommand = true)]
|
|
pub struct Cli {
|
|
/// Path to YAML configuration (mirrors kernel cmdline key 'zosstorage.config=')
|
|
#[arg(long = "config")]
|
|
pub config: Option<String>,
|
|
|
|
/// Log level: error, warn, info, debug
|
|
#[arg(long = "log-level", default_value = "info")]
|
|
pub log_level: String,
|
|
|
|
/// Also log to /run/zosstorage/zosstorage.log
|
|
#[arg(long = "log-to-file", default_value_t = false)]
|
|
pub log_to_file: bool,
|
|
|
|
/// Enable writing /etc/fstab entries
|
|
#[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<String>,
|
|
|
|
/// 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,
|
|
}
|
|
|
|
/// Parse CLI arguments (non-interactive; suitable for initramfs).
|
|
pub fn from_args() -> Cli {
|
|
Cli::parse()
|
|
}
|
|
```
|
|
|
|
---
|
|
|
|
## Logging
|
|
|
|
References
|
|
- [struct LogOptions](../src/logging/mod.rs:1)
|
|
- [fn init_logging(opts: &LogOptions) -> Result<()>](../src/logging/mod.rs:1)
|
|
|
|
Skeleton
|
|
```rust
|
|
use crate::Result;
|
|
|
|
/// Logging options resolved from CLI and/or config.
|
|
#[derive(Debug, Clone)]
|
|
pub struct LogOptions {
|
|
pub level: String, // "error" | "warn" | "info" | "debug"
|
|
pub to_file: bool, // when true, logs to /run/zosstorage/zosstorage.log
|
|
}
|
|
|
|
impl LogOptions {
|
|
pub fn from_cli(cli: &crate::cli::Cli) -> Self {
|
|
Self { level: cli.log_level.clone(), to_file: cli.log_to_file }
|
|
}
|
|
}
|
|
|
|
/// Initialize tracing subscriber according to options.
|
|
/// Must be idempotent when called once in process lifetime.
|
|
pub fn init_logging(opts: &LogOptions) -> Result<()> {
|
|
todo!("set up tracing for stderr and optional file layer")
|
|
}
|
|
```
|
|
|
|
---
|
|
|
|
## 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)
|
|
|
|
Skeleton
|
|
```rust
|
|
use serde::{Deserialize, Serialize};
|
|
|
|
#[derive(Debug, Clone, Serialize, Deserialize)]
|
|
pub struct LoggingConfig {
|
|
pub level: String, // default "info"
|
|
pub to_file: bool, // default false
|
|
}
|
|
|
|
#[derive(Debug, Clone, Serialize, Deserialize)]
|
|
pub struct DeviceSelection {
|
|
pub include_patterns: Vec<String>,
|
|
pub exclude_patterns: Vec<String>,
|
|
pub allow_removable: bool,
|
|
pub min_size_gib: u64,
|
|
}
|
|
|
|
#[derive(Debug, Clone, Serialize, Deserialize)]
|
|
#[serde(rename_all = "snake_case")]
|
|
pub enum Topology {
|
|
BtrfsSingle,
|
|
BcachefsSingle,
|
|
DualIndependent,
|
|
Bcachefs2Copy,
|
|
SsdHddBcachefs,
|
|
BtrfsRaid1,
|
|
}
|
|
|
|
#[derive(Debug, Clone, Serialize, Deserialize)]
|
|
pub struct BiosBootSpec {
|
|
pub enabled: bool,
|
|
pub size_mib: u64,
|
|
pub gpt_name: String, // "zosboot"
|
|
}
|
|
|
|
#[derive(Debug, Clone, Serialize, Deserialize)]
|
|
pub struct EspSpec {
|
|
pub size_mib: u64,
|
|
pub label: String, // "ZOSBOOT"
|
|
pub gpt_name: String, // "zosboot"
|
|
}
|
|
|
|
#[derive(Debug, Clone, Serialize, Deserialize)]
|
|
pub struct DataSpec {
|
|
pub gpt_name: String, // "zosdata"
|
|
}
|
|
|
|
#[derive(Debug, Clone, Serialize, Deserialize)]
|
|
pub struct CacheSpec {
|
|
pub gpt_name: String, // "zoscache"
|
|
}
|
|
|
|
#[derive(Debug, Clone, Serialize, Deserialize)]
|
|
pub struct Partitioning {
|
|
pub alignment_mib: u64,
|
|
pub require_empty_disks: bool,
|
|
pub bios_boot: BiosBootSpec,
|
|
pub esp: EspSpec,
|
|
pub data: DataSpec,
|
|
pub cache: CacheSpec,
|
|
}
|
|
|
|
#[derive(Debug, Clone, Serialize, Deserialize)]
|
|
pub struct BtrfsOptions {
|
|
pub label: String, // "ZOSDATA"
|
|
pub compression: String, // "zstd:3"
|
|
pub raid_profile: String // "none" | "raid1"
|
|
}
|
|
|
|
#[derive(Debug, Clone, Serialize, Deserialize)]
|
|
pub struct BcachefsOptions {
|
|
pub label: String, // "ZOSDATA"
|
|
pub cache_mode: String, // "promote" | "writeback?"
|
|
pub compression: String, // "zstd"
|
|
pub checksum: String, // "crc32c"
|
|
}
|
|
|
|
#[derive(Debug, Clone, Serialize, Deserialize)]
|
|
pub struct VfatOptions {
|
|
pub label: String, // "ZOSBOOT"
|
|
}
|
|
|
|
#[derive(Debug, Clone, Serialize, Deserialize)]
|
|
pub struct FsOptions {
|
|
pub btrfs: BtrfsOptions,
|
|
pub bcachefs: BcachefsOptions,
|
|
pub vfat: VfatOptions,
|
|
}
|
|
|
|
#[derive(Debug, Clone, Serialize, Deserialize)]
|
|
#[serde(rename_all = "snake_case")]
|
|
pub enum MountSchemeKind {
|
|
PerUuid,
|
|
Custom, // reserved
|
|
}
|
|
|
|
#[derive(Debug, Clone, Serialize, Deserialize)]
|
|
pub struct MountScheme {
|
|
pub base_dir: String, // "/var/cache"
|
|
pub scheme: MountSchemeKind,
|
|
pub fstab_enabled: bool,
|
|
}
|
|
|
|
#[derive(Debug, Clone, Serialize, Deserialize)]
|
|
pub struct ReportOptions {
|
|
pub path: String, // "/run/zosstorage/state.json"
|
|
}
|
|
|
|
#[derive(Debug, Clone, Serialize, Deserialize)]
|
|
pub struct Config {
|
|
pub version: u32,
|
|
pub logging: LoggingConfig,
|
|
pub device_selection: DeviceSelection,
|
|
pub topology: Topology,
|
|
pub partitioning: Partitioning,
|
|
pub filesystem: FsOptions,
|
|
pub mount: MountScheme,
|
|
pub report: ReportOptions,
|
|
}
|
|
```
|
|
|
|
---
|
|
|
|
## Configuration I/O
|
|
|
|
References
|
|
- [fn load_and_merge(cli: &Cli) -> Result<Config>](../src/config/loader.rs:1)
|
|
- [fn validate(cfg: &Config) -> Result<()>](../src/config/loader.rs:1)
|
|
|
|
Skeleton
|
|
```rust
|
|
use crate::{cli::Cli, Result};
|
|
|
|
/// Load defaults, merge file config, overlay CLI, and finally kernel cmdline.
|
|
pub fn load_and_merge(cli: &Cli) -> Result<crate::config::types::Config> {
|
|
todo!("implement precedence: file < CLI < kernel cmdline key 'zosstorage.config='")
|
|
}
|
|
|
|
/// Validate semantic correctness of the configuration.
|
|
pub fn validate(cfg: &crate::config::types::Config) -> Result<()> {
|
|
todo!("ensure device filters, sizes, topology combinations are valid")
|
|
}
|
|
```
|
|
|
|
---
|
|
|
|
## 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<Vec<Disk>>](../src/device/discovery.rs:1)
|
|
|
|
Skeleton
|
|
```rust
|
|
use crate::Result;
|
|
|
|
/// Eligible block device.
|
|
#[derive(Debug, Clone)]
|
|
pub struct Disk {
|
|
pub path: String,
|
|
pub size_bytes: u64,
|
|
pub rotational: bool,
|
|
pub model: Option<String>,
|
|
pub serial: Option<String>,
|
|
}
|
|
|
|
/// Compiled device filters from config patterns.
|
|
#[derive(Debug, Clone)]
|
|
pub struct DeviceFilter {
|
|
pub include: Vec<regex::Regex>,
|
|
pub exclude: Vec<regex::Regex>,
|
|
pub min_size_gib: u64,
|
|
}
|
|
|
|
/// Abstract provider for devices to enable testing with doubles.
|
|
pub trait DeviceProvider {
|
|
fn list_block_devices(&self) -> Result<Vec<Disk>>;
|
|
fn probe_properties(&self, disk: &mut Disk) -> Result<()>;
|
|
}
|
|
|
|
/// Discover eligible disks according to the filter policy.
|
|
pub fn discover(filter: &DeviceFilter) -> Result<Vec<Disk>> {
|
|
todo!("enumerate /dev, apply include/exclude, probe properties")
|
|
}
|
|
```
|
|
|
|
---
|
|
|
|
## 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<PartitionPlan>](../src/partition/plan.rs:1)
|
|
- [fn apply_partitions(plan: &PartitionPlan) -> Result<Vec<PartitionResult>>](../src/partition/plan.rs:1)
|
|
|
|
Skeleton
|
|
```rust
|
|
use crate::{config::types::Config, device::Disk, Result};
|
|
|
|
#[derive(Debug, Clone, Copy)]
|
|
pub enum PartRole {
|
|
BiosBoot,
|
|
Esp,
|
|
Data,
|
|
Cache,
|
|
}
|
|
|
|
#[derive(Debug, Clone)]
|
|
pub struct PartitionSpec {
|
|
pub role: PartRole,
|
|
pub size_mib: Option<u64>, // None means "remainder"
|
|
pub gpt_name: String, // zosboot | zosdata | zoscache
|
|
}
|
|
|
|
#[derive(Debug, Clone)]
|
|
pub struct DiskPlan {
|
|
pub disk: Disk,
|
|
pub parts: Vec<PartitionSpec>,
|
|
}
|
|
|
|
#[derive(Debug, Clone)]
|
|
pub struct PartitionPlan {
|
|
pub alignment_mib: u64,
|
|
pub disks: Vec<DiskPlan>,
|
|
pub require_empty_disks: bool,
|
|
}
|
|
|
|
#[derive(Debug, Clone)]
|
|
pub struct PartitionResult {
|
|
pub disk: String,
|
|
pub part_number: u32,
|
|
pub role: PartRole,
|
|
pub gpt_name: String,
|
|
pub uuid: String,
|
|
pub start_mib: u64,
|
|
pub size_mib: u64,
|
|
pub device_path: String, // e.g., /dev/nvme0n1p2
|
|
}
|
|
|
|
/// Compute GPT-only plan per topology and constraints.
|
|
pub fn plan_partitions(disks: &[Disk], cfg: &Config) -> Result<PartitionPlan> {
|
|
todo!("layout bios boot, ESP, data (and cache for SSD/HDD); align to 1 MiB")
|
|
}
|
|
|
|
/// Apply plan using sgdisk and verify via blkid; require empty disks if configured.
|
|
pub fn apply_partitions(plan: &PartitionPlan) -> Result<Vec<PartitionResult>> {
|
|
todo!("shell out to sgdisk, trigger udev settle, collect partition GUIDs")
|
|
}
|
|
```
|
|
|
|
---
|
|
|
|
## 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)
|
|
|
|
Skeleton
|
|
```rust
|
|
use crate::{partition::PartitionResult, config::types::Config, Result};
|
|
|
|
#[derive(Debug, Clone, Copy)]
|
|
pub enum FsKind {
|
|
Vfat,
|
|
Btrfs,
|
|
Bcachefs,
|
|
}
|
|
|
|
#[derive(Debug, Clone)]
|
|
pub struct FsSpec {
|
|
pub kind: FsKind,
|
|
pub devices: Vec<String>, // 1 device for vfat/btrfs; 2 for bcachefs (cache + backing)
|
|
pub label: String, // "ZOSBOOT" (vfat) or "ZOSDATA" (data)
|
|
}
|
|
|
|
#[derive(Debug, Clone)]
|
|
pub struct FsPlan {
|
|
pub specs: Vec<FsSpec>,
|
|
}
|
|
|
|
#[derive(Debug, Clone)]
|
|
pub struct FsResult {
|
|
pub kind: FsKind,
|
|
pub devices: Vec<String>,
|
|
pub uuid: String,
|
|
pub label: String,
|
|
}
|
|
|
|
/// Decide which partitions get which filesystem based on topology.
|
|
pub fn plan_filesystems(
|
|
parts: &[PartitionResult],
|
|
cfg: &Config,
|
|
) -> Result<FsPlan> {
|
|
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, cfg: &Config) -> Result<Vec<FsResult>> {
|
|
todo!("invoke mkfs tools with configured options via util::run_cmd")
|
|
}
|
|
```
|
|
|
|
---
|
|
|
|
## 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)
|
|
|
|
Skeleton
|
|
```rust
|
|
use crate::{fs::FsResult, config::types::Config, Result};
|
|
|
|
#[derive(Debug, Clone)]
|
|
pub struct MountPlan {
|
|
pub entries: Vec<(String /* source */, String /* target */, String /* fstype */, String /* options */)>,
|
|
}
|
|
|
|
#[derive(Debug, Clone)]
|
|
pub struct MountResult {
|
|
pub source: String,
|
|
pub target: String,
|
|
pub fstype: String,
|
|
pub options: String,
|
|
}
|
|
|
|
//// 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<MountPlan> {
|
|
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.
|
|
pub fn apply_mounts(plan: &MountPlan) -> Result<Vec<MountResult>> {
|
|
todo!("perform mount syscalls and return results")
|
|
}
|
|
|
|
//// 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 only the four final subvolume/subdir entries with UUID= sources")
|
|
}
|
|
```
|
|
|
|
---
|
|
|
|
## 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)
|
|
|
|
Skeleton
|
|
```rust
|
|
use serde::{Serialize, Deserialize};
|
|
use crate::{device::Disk, partition::PartitionResult, fs::FsResult, mount::MountResult, Result};
|
|
|
|
pub const REPORT_VERSION: &str = "v1";
|
|
|
|
#[derive(Debug, Clone, Serialize, Deserialize)]
|
|
pub struct StateReport {
|
|
pub version: String,
|
|
pub timestamp: String, // RFC3339
|
|
pub status: String, // "success" | "already_provisioned" | "error"
|
|
pub disks: Vec<serde_json::Value>,
|
|
pub partitions: Vec<serde_json::Value>,
|
|
pub filesystems: Vec<serde_json::Value>,
|
|
pub mounts: Vec<serde_json::Value>,
|
|
pub error: Option<String>,
|
|
}
|
|
|
|
/// Build the machine-readable state report.
|
|
pub fn build_report(
|
|
disks: &[Disk],
|
|
parts: &[PartitionResult],
|
|
fs: &[FsResult],
|
|
mounts: &[MountResult],
|
|
status: &str,
|
|
) -> StateReport {
|
|
todo!("assemble structured report in v1 format")
|
|
}
|
|
|
|
/// Write the state report to the configured path (default /run/zosstorage/state.json).
|
|
pub fn write_report(report: &StateReport, path: &str) -> Result<()> {
|
|
todo!("serialize to JSON and persist atomically")
|
|
}
|
|
```
|
|
|
|
---
|
|
|
|
## Orchestrator
|
|
|
|
References
|
|
- [struct Context](../src/orchestrator/run.rs:1)
|
|
- [fn run(ctx: &Context) -> Result<()>](../src/orchestrator/run.rs:1)
|
|
|
|
Skeleton
|
|
```rust
|
|
use crate::{config::types::Config, logging::LogOptions, Result};
|
|
|
|
/// Execution context holding resolved configuration and environment flags.
|
|
#[derive(Debug, Clone)]
|
|
pub struct Context {
|
|
pub cfg: Config,
|
|
pub log: LogOptions,
|
|
}
|
|
|
|
impl Context {
|
|
pub fn new(cfg: Config, log: LogOptions) -> Self { Self { cfg, log } }
|
|
}
|
|
|
|
/// High-level one-shot flow; aborts on any validation failure.
|
|
/// Returns Ok(()) on success and also on no-op when already provisioned.
|
|
pub fn run(ctx: &Context) -> Result<()> {
|
|
todo!("idempotency check, discovery, planning, application, reporting")
|
|
}
|
|
```
|
|
|
|
---
|
|
|
|
## Idempotency
|
|
|
|
References
|
|
- [fn detect_existing_state() -> Result<Option<StateReport>>](../src/idempotency/mod.rs:1)
|
|
- [fn is_empty_disk(disk: &Disk) -> Result<bool>](../src/idempotency/mod.rs:1)
|
|
|
|
Skeleton
|
|
```rust
|
|
use crate::{device::Disk, report::StateReport, Result};
|
|
|
|
/// Return existing state if system is already provisioned; otherwise None.
|
|
pub fn detect_existing_state() -> Result<Option<StateReport>> {
|
|
todo!("probe GPT names (zosboot, zosdata, zoscache) and FS labels (ZOSBOOT, ZOSDATA)")
|
|
}
|
|
|
|
/// Determine if a disk is empty (no partitions and no known FS signatures).
|
|
pub fn is_empty_disk(disk: &Disk) -> Result<bool> {
|
|
todo!("use blkid and partition table inspection to declare emptiness")
|
|
}
|
|
```
|
|
|
|
---
|
|
|
|
## Utilities
|
|
|
|
References
|
|
- [struct CmdOutput](../src/util/mod.rs:1)
|
|
- [fn which_tool(name: &str) -> Result<Option<String>>](../src/util/mod.rs:1)
|
|
- [fn run_cmd(args: &[&str]) -> Result<()>](../src/util/mod.rs:1)
|
|
- [fn run_cmd_capture(args: &[&str]) -> Result<CmdOutput>](../src/util/mod.rs:1)
|
|
- [fn udev_settle(timeout_ms: u64) -> Result<()>](../src/util/mod.rs:1)
|
|
|
|
Skeleton
|
|
```rust
|
|
use crate::Result;
|
|
|
|
/// Captured output from an external tool invocation.
|
|
#[derive(Debug, Clone)]
|
|
pub struct CmdOutput {
|
|
pub status: i32,
|
|
pub stdout: String,
|
|
pub stderr: String,
|
|
}
|
|
|
|
/// Locate the absolute path to required tool if available.
|
|
pub fn which_tool(name: &str) -> Result<Option<String>> {
|
|
todo!("use 'which' crate or manual PATH scanning")
|
|
}
|
|
|
|
/// Run a command and return Ok if the exit status is zero.
|
|
pub fn run_cmd(args: &[&str]) -> Result<()> {
|
|
todo!("spawn process, log stderr on failure, map to Error::Tool")
|
|
}
|
|
|
|
/// Run a command and capture stdout/stderr for parsing (e.g., blkid).
|
|
pub fn run_cmd_capture(args: &[&str]) -> Result<CmdOutput> {
|
|
todo!("spawn process and collect output")
|
|
}
|
|
|
|
/// Call udevadm settle with a timeout; warn if unavailable, then no-op.
|
|
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()")
|
|
}
|
|
```
|
|
|
|
---
|
|
|
|
Approval gate
|
|
- This API skeleton is ready for implementation as source files with todo!() bodies.
|
|
- Upon approval, we will:
|
|
- Create the src/ files as outlined.
|
|
- Add dependencies via cargo add.
|
|
- Ensure all modules compile with placeholders.
|
|
- 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) |