use std::path::{Path, PathBuf};
use ed25519_dalek::{Signature, Verifier, VerifyingKey};
use fidius_core::package::{PackageError, PackageManifest};
use serde::de::DeserializeOwned;
pub fn load_package_manifest<M: DeserializeOwned>(
dir: &Path,
) -> Result<PackageManifest<M>, PackageError> {
fidius_core::package::load_manifest(dir)
}
pub fn discover_packages(dir: &Path) -> Result<Vec<PathBuf>, PackageError> {
let mut packages = Vec::new();
if !dir.is_dir() {
return Ok(packages);
}
let entries = std::fs::read_dir(dir).map_err(PackageError::Io)?;
for entry in entries {
let entry = entry.map_err(PackageError::Io)?;
let path = entry.path();
if path.is_dir() && path.join("package.toml").exists() {
packages.push(path);
}
}
packages.sort();
Ok(packages)
}
pub fn verify_package(dir: &Path, trusted_keys: &[VerifyingKey]) -> Result<(), PackageError> {
let sig_path = dir.join("package.sig");
if !sig_path.exists() {
return Err(PackageError::SignatureNotFound {
path: dir.display().to_string(),
});
}
let sig_bytes: [u8; 64] =
std::fs::read(&sig_path)?
.try_into()
.map_err(|_| PackageError::SignatureInvalid {
path: dir.display().to_string(),
})?;
let signature = Signature::from_bytes(&sig_bytes);
let digest = fidius_core::package::package_digest(dir)?;
for key in trusted_keys {
if key.verify(&digest, &signature).is_ok() {
return Ok(());
}
}
Err(PackageError::SignatureInvalid {
path: dir.display().to_string(),
})
}
pub fn unpack_fid(archive: &Path, dest: &Path) -> Result<PathBuf, PackageError> {
let pkg_dir = fidius_core::package::unpack_package(archive, dest)?;
if !pkg_dir.join("package.sig").exists() {
#[cfg(feature = "tracing")]
tracing::warn!(
package = %pkg_dir.display(),
"unpacked package is unsigned (no package.sig found)"
);
}
Ok(pkg_dir)
}
pub fn build_package(dir: &Path, release: bool) -> Result<PathBuf, PackageError> {
let cargo_toml = dir.join("Cargo.toml");
if !cargo_toml.exists() {
return Err(PackageError::BuildFailed(format!(
"Cargo.toml not found in {}",
dir.display()
)));
}
let mut cmd = std::process::Command::new("cargo");
cmd.arg("build").arg("--manifest-path").arg(&cargo_toml);
if release {
cmd.arg("--release");
}
let output = cmd.output().map_err(PackageError::Io)?;
if !output.status.success() {
let stderr = String::from_utf8_lossy(&output.stderr);
return Err(PackageError::BuildFailed(stderr.to_string()));
}
let profile = if release { "release" } else { "debug" };
let target_dir = dir.join("target").join(profile);
let dylib_ext = if cfg!(target_os = "macos") {
"dylib"
} else if cfg!(target_os = "windows") {
"dll"
} else {
"so"
};
if let Ok(entries) = std::fs::read_dir(&target_dir) {
for entry in entries.flatten() {
let path = entry.path();
if path.extension().and_then(|e| e.to_str()) == Some(dylib_ext) {
return Ok(path);
}
}
}
Err(PackageError::BuildFailed(format!(
"build succeeded but no .{} file found in {}",
dylib_ext,
target_dir.display()
)))
}