From add4d929451f443f46b8699890ff612ee1e4157d Mon Sep 17 00:00:00 2001 From: Marco Allegretti Date: Wed, 11 Mar 2026 15:39:18 +0100 Subject: [PATCH] feat(pack): build-image and build-verity subcommands build-image [--out ]: creates an EROFS image by invoking mkfs.erofs. Output filename defaults to .app.img. Fails if the output file already exists or mkfs.erofs is unavailable. build-verity [--out ]: creates a dm-verity hash tree by invoking veritysetup format. Writes the hash device to .hash (or --out path). Extracts the root hash from veritysetup output and writes it to .roothash. Fails if veritysetup is unavailable. Both subcommands shell out to external tools (erofs-utils and cryptsetup-bin respectively) and return an error with an installation hint if the binary is not found. --- crates/weft-pack/src/main.rs | 73 ++++++++++++++++++++++++++++++++++++ 1 file changed, 73 insertions(+) diff --git a/crates/weft-pack/src/main.rs b/crates/weft-pack/src/main.rs index 160be86..c6ad57f 100644 --- a/crates/weft-pack/src/main.rs +++ b/crates/weft-pack/src/main.rs @@ -55,6 +55,22 @@ fn main() -> anyhow::Result<()> { Some("list") => { list_installed(); } + Some("build-image") => { + let dir = args.get(2).context("usage: weft-pack build-image ")?; + let out = args + .windows(2) + .find(|w| w[0] == "--out") + .map(|w| w[1].as_str()); + build_image(Path::new(dir), out.map(Path::new))?; + } + Some("build-verity") => { + let img = args.get(2).context("usage: weft-pack build-verity ")?; + let out = args + .windows(2) + .find(|w| w[0] == "--out") + .map(|w| w[1].as_str()); + build_verity(Path::new(img), out.map(Path::new))?; + } Some("bundle") => { let dir = args.get(2).context("usage: weft-pack bundle ")?; let out = args @@ -111,6 +127,8 @@ fn main() -> anyhow::Result<()> { eprintln!(" weft-pack install install package to app store"); eprintln!(" weft-pack uninstall remove installed package"); eprintln!(" weft-pack list list installed packages"); + eprintln!(" weft-pack build-image [--out ] create EROFS image with mkfs.erofs"); + eprintln!(" weft-pack build-verity [--out ] add dm-verity hash tree"); eprintln!(" weft-pack bundle [--out ] create .app.tar.zst archive"); eprintln!(" weft-pack unbundle [--out ] extract .app.tar.zst"); eprintln!(" weft-pack generate-key [] generate Ed25519 keypair"); @@ -357,6 +375,61 @@ fn copy_dir(src: &Path, dst: &Path) -> anyhow::Result<()> { Ok(()) } +fn build_image(dir: &Path, out_path: Option<&Path>) -> anyhow::Result<()> { + let manifest = load_manifest(dir)?; + let app_id = &manifest.package.id; + let default_name = format!("{app_id}.app.img"); + let output = match out_path { + Some(p) => p.to_path_buf(), + None => PathBuf::from(&default_name), + }; + if output.exists() { + anyhow::bail!("{} already exists", output.display()); + } + let status = std::process::Command::new("mkfs.erofs") + .arg(&output) + .arg(dir) + .status() + .context("spawn mkfs.erofs; ensure erofs-utils is installed")?; + if !status.success() { + anyhow::bail!("mkfs.erofs failed with status {status}"); + } + println!("image: {}", output.display()); + Ok(()) +} + +fn build_verity(img: &Path, hash_out: Option<&Path>) -> anyhow::Result<()> { + let stem = img.file_stem().context("no file stem")?.to_string_lossy(); + let default_hash = img.with_file_name(format!("{stem}.hash")); + let hash_path = hash_out.map(|p| p.to_path_buf()).unwrap_or(default_hash); + if hash_path.exists() { + anyhow::bail!("{} already exists", hash_path.display()); + } + let output = std::process::Command::new("veritysetup") + .args(["format", &img.to_string_lossy(), &hash_path.to_string_lossy()]) + .output() + .context("spawn veritysetup; ensure cryptsetup-bin is installed")?; + if !output.status.success() { + anyhow::bail!( + "veritysetup failed: {}", + String::from_utf8_lossy(&output.stderr) + ); + } + let stdout = String::from_utf8_lossy(&output.stdout); + let root_hash = stdout + .lines() + .find(|l| l.starts_with("Root hash:")) + .context("root hash not found in veritysetup output")?; + let hash_value = root_hash.trim_start_matches("Root hash:").trim(); + let roothash_path = img.with_extension("roothash"); + std::fs::write(&roothash_path, hash_value) + .with_context(|| format!("write {}", roothash_path.display()))?; + println!("hash image: {}", hash_path.display()); + println!("root hash: {hash_value}"); + println!("roothash: {}", roothash_path.display()); + Ok(()) +} + fn bundle_package(dir: &Path, out_dir: Option<&Path>) -> anyhow::Result<()> { let manifest = load_manifest(dir)?; let app_id = &manifest.package.id;