xbp 0.9.4

XBP is a zero-config build pack that can also interact with proxies, kafka, sockets, synthetic monitors.
Documentation
//! Package installation module for xbp
//!
//! This module provides functionality to install various packages using
//! predefined shell scripts. It supports executing installation scripts
//! for supported packages and provides debug output for troubleshooting.

use std::path::PathBuf;
use std::process::Output;
use tokio::process::Command;
use tracing::{debug, info};
use colored::Colorize;

/// Installs a package by name using the corresponding installation script
///
/// # Arguments
///
/// * `package_name` - The name of the package to install (e.g., "grafana")
/// * `debug` - Whether to enable debug output during installation
///
/// # Returns
///
/// * `Ok(())` if the installation was successful
/// * `Err(String)` if the package is not supported or installation failed
///
/// # Examples
///
/// ```
/// let result = install_package("grafana", true).await;
/// match result {
///     Ok(()) => info!("Installation successful"),
///     Err(e) => error!("Installation failed: {}", e),
/// }
/// ```
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().as_str() {
        "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!("  {} - 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());
}

/// Executes the installation script for a specific package
///
/// This function locates the installation script in the package_install_scripts
/// directory, makes it executable, and runs it. It provides detailed debug
/// output when debug mode is enabled.
///
/// # Arguments
///
/// * `package_name` - The name of the package (used to find the script file)
/// * `debug` - Whether to enable debug output during script execution
///
/// # Returns
///
/// * `Ok(())` if the script executed successfully
/// * `Err(String)` if the script was not found, couldn't be made executable, or failed to run
///
/// # Script Location
///
/// Scripts are expected to be located at `./package_install_scripts/{package_name}.sh`
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);
    }

    // Make the script executable
    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(())
}