Files
fds-os/rust/fds-workstation/src/main.rs
T
2026-09-21 22:29:23 +08:00

138 lines
4.3 KiB
Rust

use clap::{Parser, Subcommand};
use fds_common::Result;
use std::{path::PathBuf, process::ExitCode};
#[derive(Parser)]
#[command(
version,
about = "Build software bundles and metadata-first cartridge images on Linux"
)]
struct Cli {
/// Optional wrapper that accepts TOOL followed by its arguments.
#[arg(long, global = true)]
image_tool_runner: Option<PathBuf>,
#[command(subcommand)]
command: Action,
}
#[derive(Subcommand)]
enum Action {
/// Check xz, erofs-utils and the unprivileged filesystem inspection sandbox.
Doctor,
/// Build or package software on the workstation, never on the Pi.
Software {
#[command(subcommand)]
command: Software,
},
/// Construct and verify a complete 1+m GPT cartridge image before burning.
Create { recipe: PathBuf, output: PathBuf },
/// Verify GPT, every filesystem, catalogue and xz tarball in a prepared image.
Inspect { image: PathBuf },
/// Verify a prepared image and save an image/target-bound write preview.
Preview {
image: PathBuf,
target: PathBuf,
output: PathBuf,
/// Use an existing disposable regular file instead of a physical USB drive.
#[arg(long)]
file_target: bool,
},
/// Write the entire prepared image only after exact preview confirmation.
Write {
preview: PathBuf,
#[arg(long)]
confirm: String,
},
}
#[derive(Subcommand)]
enum Software {
/// Execute an explicit trusted workstation build recipe, then package its output.
Build { recipe: PathBuf, output: PathBuf },
/// Package an existing software tree without running its build command.
Pack { recipe: PathBuf, output: PathBuf },
/// Check a built software descriptor, archive digest and extracted contents.
Inspect { directory: PathBuf },
}
fn run() -> Result<()> {
let cli = Cli::parse();
let result = match cli.command {
Action::Doctor => Ok(fds_workstation::doctor::cartridge(
cli.image_tool_runner.as_deref(),
)?),
Action::Preview {
image,
target,
output,
file_target,
} => serde_json::to_value(fds_workstation::writing::preview(
&image,
&target,
&output,
file_target,
cli.image_tool_runner.as_deref(),
)?),
Action::Write { preview, confirm } => {
fds_workstation::writing::write(&preview, &confirm)?;
return Ok(());
}
Action::Software { command } => serde_json::to_value(match command {
Software::Build { recipe, output } => {
fds_workstation::software::build(&recipe, &output, true)?
}
Software::Pack { recipe, output } => {
fds_workstation::software::build(&recipe, &output, false)?
}
Software::Inspect { directory } => fds_workstation::software::load(&directory)?,
}),
Action::Create { recipe, output } => serde_json::to_value(
fds_workstation::cartridge::create(&recipe, &output, cli.image_tool_runner.as_deref())?,
),
Action::Inspect { image } => serde_json::to_value(fds_workstation::cartridge::inspect(
&image,
cli.image_tool_runner.as_deref(),
)?),
}
.map_err(|e| fds_common::Error(e.to_string()))?;
println!("{}", serde_json::to_string_pretty(&result).unwrap());
Ok(())
}
fn main() -> ExitCode {
match run() {
Ok(()) => ExitCode::SUCCESS,
Err(e) => {
eprintln!("fds-cartridge: {e}");
ExitCode::from(2)
}
}
}
#[cfg(test)]
mod tests {
use super::*;
use clap::CommandFactory;
#[test]
fn typed_grammar() {
Cli::command().debug_assert();
assert!(
Cli::try_parse_from([
"fds-cartridge",
"software",
"build",
"recipe.toml",
"bundle"
])
.is_ok()
);
assert!(
Cli::try_parse_from([
"fds-cartridge",
"create",
"recipe.toml",
"card.img",
"--force"
])
.is_err()
);
assert!(Cli::try_parse_from(["fds-cartridge", "create", "recipe.toml"]).is_err());
}
}