use anyhow::{bail, Context, Result};
use crate::display::{print_box_header, print_info, print_separator, print_success, print_warning};
pub async fn get_dependencies() -> Result<()> {
print_box_header("π¦ KwaaiNet β Get Dependencies");
let exe = std::env::current_exe().context("cannot determine current executable path")?;
let install_dir = exe.parent().context("cannot determine install directory")?;
#[cfg(windows)]
let p2pd_name = "p2pd.exe";
#[cfg(not(windows))]
let p2pd_name = "p2pd";
let p2pd_dst = install_dir.join(p2pd_name);
if p2pd_dst.exists() {
print_success(&format!("p2pd already present at {}", p2pd_dst.display()));
print_separator();
return Ok(());
}
if let Some(path) = find_in_path(p2pd_name) {
print_success(&format!("p2pd found on PATH at {}", path.display()));
print_separator();
return Ok(());
}
print_info("p2pd not found β downloading from latest releaseβ¦");
let target = build_target_triple();
if target.is_none() {
print_warning("Unsupported platform β please download p2pd manually from:");
println!(" https://github.com/Kwaai-AI-Lab/KwaaiNet/releases/latest");
print_separator();
return Ok(());
}
let target = target.unwrap();
#[cfg(windows)]
let (archive_ext, is_zip) = ("zip", true);
#[cfg(not(windows))]
let (archive_ext, is_zip) = ("tar.xz", false);
let url = format!(
"https://github.com/Kwaai-AI-Lab/KwaaiNet/releases/latest/download/kwaainet-{}.{}",
target, archive_ext
);
println!(" Target: {}", target);
println!(" Archive: kwaainet-{}.{}", target, archive_ext);
println!();
let client = reqwest::Client::builder()
.user_agent("kwaainet-setup")
.build()?;
let response = client
.get(&url)
.send()
.await
.context("failed to download archive")?;
if !response.status().is_success() {
bail!("download failed: HTTP {}", response.status());
}
let bytes = response
.bytes()
.await
.context("failed to read archive bytes")?;
let archive_size_mb = bytes.len() as f64 / 1_048_576.0;
println!(" Downloaded {:.1} MB", archive_size_mb);
let tmp_dir = std::env::temp_dir().join("kwaainet-setup");
std::fs::create_dir_all(&tmp_dir).context("failed to create temp dir")?;
let archive_path = tmp_dir.join(format!("kwaainet-{}.{}", target, archive_ext));
std::fs::write(&archive_path, &bytes).context("failed to write archive to temp dir")?;
let p2pd_src = if is_zip {
extract_from_zip(&archive_path, &tmp_dir, p2pd_name)?
} else {
extract_from_tarxz(&archive_path, &tmp_dir, target, p2pd_name)?
};
std::fs::copy(&p2pd_src, &p2pd_dst)
.with_context(|| format!("failed to install p2pd to {}", p2pd_dst.display()))?;
#[cfg(unix)]
{
use std::os::unix::fs::PermissionsExt;
std::fs::set_permissions(&p2pd_dst, std::fs::Permissions::from_mode(0o755))
.context("failed to set p2pd executable bit")?;
}
let _ = std::fs::remove_file(&archive_path);
let _ = std::fs::remove_file(&p2pd_src);
print_success(&format!("p2pd installed to {}", p2pd_dst.display()));
print_info("You can now run: kwaainet start --daemon");
print_separator();
Ok(())
}
fn extract_from_tarxz(
archive: &std::path::Path,
out_dir: &std::path::Path,
target: &str,
binary_name: &str,
) -> Result<std::path::PathBuf> {
let member = format!("kwaainet-{}/{}", target, binary_name);
let status = std::process::Command::new("tar")
.args([
"-xJf",
archive.to_str().unwrap(),
"--strip-components=1",
"-C",
out_dir.to_str().unwrap(),
&member,
])
.status()
.context("failed to run tar β is it installed?")?;
if !status.success() {
bail!("tar exited with status {}", status);
}
let out = out_dir.join(binary_name);
if !out.exists() {
bail!("tar ran but {} was not found in output", binary_name);
}
Ok(out)
}
#[allow(unused_variables)]
fn extract_from_zip(
archive: &std::path::Path,
out_dir: &std::path::Path,
binary_name: &str,
) -> Result<std::path::PathBuf> {
#[cfg(windows)]
{
let status = std::process::Command::new("powershell")
.args([
"-NoProfile",
"-Command",
&format!(
"Expand-Archive -Path '{}' -DestinationPath '{}' -Force",
archive.display(),
out_dir.display()
),
])
.status()
.context("failed to run PowerShell Expand-Archive")?;
if !status.success() {
bail!("PowerShell Expand-Archive failed with status {}", status);
}
let out = out_dir.join(binary_name);
if !out.exists() {
bail!(
"Expand-Archive ran but {} was not found in output",
binary_name
);
}
return Ok(out);
}
#[cfg(not(windows))]
bail!("zip extraction is only supported on Windows");
}
fn build_target_triple() -> Option<&'static str> {
match (std::env::consts::OS, std::env::consts::ARCH) {
("macos", "aarch64") => Some("aarch64-apple-darwin"),
("macos", "x86_64") => Some("x86_64-apple-darwin"),
("linux", "x86_64") => Some("x86_64-unknown-linux-gnu"),
("linux", "aarch64") => Some("aarch64-unknown-linux-gnu"),
("windows", "x86_64") => Some("x86_64-pc-windows-msvc"),
_ => None,
}
}
fn find_in_path(name: &str) -> Option<std::path::PathBuf> {
let paths = std::env::var_os("PATH")?;
for dir in std::env::split_paths(&paths) {
let candidate = dir.join(name);
if candidate.exists() {
return Some(candidate);
}
}
None
}