138 lines
4.3 KiB
Rust
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());
|
|
}
|
|
}
|