use crate::utils::command_exists;
use colored::Colorize;
use std::path::PathBuf;
use std::process::Output;
use tokio::process::Command;
use tracing::{debug, info};
pub async fn install_package(package_name: &str, debug: bool) -> Result<(), String> {
if package_name.is_empty() || package_name == "--help" || package_name == "help" {
list_available_packages();
return Ok(());
}
match package_name.to_lowercase().replace('_', "-").as_str() {
"azure-cli" | "azure_cli" => install_azure_cli(debug).await,
"grafana" => execute_install_script("grafana", debug).await,
"scylladb" => execute_install_script("scylladb", debug).await,
"nginx_full" => execute_install_script("nginx_full", debug).await,
"opencv-rust" => execute_install_script("opencv-rust", debug).await,
"elixir_erlang" => execute_install_script("elixir_erlang", debug).await,
"docker" => execute_install_script("docker", debug).await,
"triggerdotdev" => execute_install_script("triggerdotdev", debug).await,
"iotop" => execute_install_script("iotop", debug).await,
_ => {
info!("{} '{}'", "Unknown package:".red(), package_name);
info!("");
list_available_packages();
Err(format!("Package '{}' is not supported", package_name))
}
}
}
fn list_available_packages() {
info!("{}", "Available packages:".bright_blue().bold());
info!("");
info!(
" {} - Azure CLI (cross-platform: winget/Linux script)",
"azure-cli".cyan()
);
info!(
" {} - Monitoring and observability platform",
"grafana".cyan()
);
info!(" {} - High-performance NoSQL database", "scylladb".cyan());
info!(
" {} - Full Nginx installation with modules",
"nginx_full".cyan()
);
info!(" {} - OpenCV bindings for Rust", "opencv-rust".cyan());
info!(" {} - Container platform", "docker".cyan());
info!(" {} - Elixir and Erlang runtime", "elixir_erlang".cyan());
info!(" {} - Background job framework", "triggerdotdev".cyan());
info!(" {} - I/O monitoring tool", "iotop".cyan());
info!("");
info!(
"{} {}",
"Usage:".bright_blue(),
"xbp install <package>".yellow()
);
}
async fn install_azure_cli(_debug: bool) -> Result<(), String> {
if command_exists("az") {
info!("{}", "Azure CLI is already installed.".green());
return Ok(());
}
#[cfg(target_os = "windows")]
{
if !command_exists("winget") {
return Err(
"winget is required. Install it from the Microsoft Store or use Windows 10/11."
.to_string(),
);
}
info!("Installing Azure CLI via winget...");
let output = Command::new("winget")
.args([
"install",
"--exact",
"--id",
"Microsoft.AzureCLI",
"--accept-package-agreements",
"--accept-source-agreements",
])
.output()
.await
.map_err(|e| format!("winget failed: {}", e))?;
if !output.status.success() {
let stderr = String::from_utf8_lossy(&output.stderr);
let stdout = String::from_utf8_lossy(&output.stdout);
return Err(format!("Azure CLI install failed: {}{}", stdout, stderr));
}
info!(
"{}",
"Azure CLI installed. Restart your terminal to use 'az'.".green()
);
Ok(())
}
#[cfg(target_os = "macos")]
{
if !command_exists("brew") {
return Err("Homebrew is required. Install from https://brew.sh/".to_string());
}
info!("Installing Azure CLI via Homebrew...");
let output = Command::new("brew")
.args(["install", "azure-cli"])
.output()
.await
.map_err(|e| format!("brew install failed: {}", e))?;
if !output.status.success() {
let stderr = String::from_utf8_lossy(&output.stderr);
return Err(format!("Azure CLI install failed: {}", stderr));
}
info!("{}", "Azure CLI installed successfully.".green());
Ok(())
}
#[cfg(target_os = "linux")]
{
info!("Installing Azure CLI via official script...");
let script = "curl -sL https://aka.ms/InstallAzureCLIDeb | sudo bash";
let output = Command::new("sh")
.args(["-c", script])
.output()
.await
.map_err(|e| format!("Azure CLI install failed: {}", e))?;
if !output.status.success() {
let stderr = String::from_utf8_lossy(&output.stderr);
return Err(format!("Azure CLI install failed: {}", stderr));
}
info!("{}", "Azure CLI installed successfully.".green());
Ok(())
}
#[cfg(not(any(target_os = "windows", target_os = "linux", target_os = "macos")))]
{
Err("Azure CLI install is supported on Windows, Linux, and macOS only.".to_string())
}
}
async fn execute_install_script(package_name: &str, debug: bool) -> Result<(), String> {
let script_path: String = format!("./package_install_scripts/{}.sh", package_name);
let script_pathbuf: PathBuf = PathBuf::from(&script_path);
if debug {
debug!("Looking for install script at: {}", script_path);
}
if !script_pathbuf.exists() {
return Err(format!("Install script not found: {}", script_path));
}
if debug {
debug!("Making script executable: {}", script_path);
}
let chmod_output: Output = Command::new("chmod")
.arg("+x")
.arg(&script_path)
.output()
.await
.map_err(|e| format!("Failed to execute chmod command: {}", e))?;
if !chmod_output.status.success() {
return Err(format!(
"Failed to make script executable: {}",
String::from_utf8_lossy(&chmod_output.stderr)
));
}
if debug {
debug!("Executing install script: {}", script_path);
}
let script_output: Output = Command::new("bash")
.arg(&script_path)
.output()
.await
.map_err(|e| format!("Failed to execute install script: {}", e))?;
if debug {
debug!("Script output: {:?}", script_output);
}
if !script_output.status.success() {
return Err(format!(
"Install script failed: {}",
String::from_utf8_lossy(&script_output.stderr)
));
}
let stdout = String::from_utf8_lossy(&script_output.stdout);
if !stdout.trim().is_empty() {
info!("{}", stdout);
}
info!("{} installation completed successfully!", package_name);
Ok(())
}