xbp 0.4.1

XBP is a build pack and deployment management tool to deploy, rust, nextjs etc and manage the NGINX configs below it
Documentation
use crate::config::SshConfig;
use crate::logging::{log_error, log_info, log_success};
use anyhow::Result;
use std::io::{self, Write};
use async_ssh2_tokio::client::{Client, AuthMethod, ServerCheckMethod};

pub async fn run_redeploy_v2(password: Option<String>, username: Option<String>, host: Option<String>, project_dir: Option<String>, debug: bool) -> Result<(), String> {
    let mut config = SshConfig::load().map_err(|e| format!("Failed to load SSH config: {}", e))?;

    let final_username = username.or_else(|| config.username.clone());
    let final_host = host.or_else(|| config.host.clone());
    let final_project_dir = project_dir.or_else(|| config.project_dir.clone());

    let mut input_password = password;

    let resolved_username = if let Some(u) = final_username {
        u
    } else {
        let u = prompt_for_input("Enter SSH username: ")?;
        config.username = Some(u.clone());
        u
    };

    let resolved_host = if let Some(h) = final_host {
        h
    } else {
        let h = prompt_for_input("Enter SSH host: ")?;
        config.host = Some(h.clone());
        h
    };

    let resolved_project_dir = if let Some(d) = final_project_dir {
        d
    } else {
        let d = prompt_for_input("Enter remote project directory: ")?;
        config.project_dir = Some(d.clone());
        d
    };

    if input_password.is_none() {
        input_password = config.password.clone();
    }

    let resolved_password = if let Some(p) = input_password {
        p
    } else {
        let p = prompt_for_password("Enter SSH password: ")?;
        config.password = Some(p.clone());
        p
    };

    config.save().map_err(|e| format!("Failed to save SSH config: {}", e))?;

    log_info("redeploy_v2", "Attempting SSH connection", Some(&format!("Host: {}, User: {}", resolved_host, resolved_username))).await?;

    // Establish SSH connection
    let client = Client::connect(
        (resolved_host.as_str(), 22),
        resolved_username.as_str(),
        AuthMethod::with_password(resolved_password.as_str()),
        ServerCheckMethod::NoCheck,
    )
    .await
    .map_err(|e| format!("SSH connection failed: {}", e))?;

    log_success("redeploy_v2", "SSH connection established", None).await?;

    // Execute redeploy.sh
    let command_to_execute = format!("cd {} && sudo sh redeploy.sh", resolved_project_dir);
    if debug {
        log_info("redeploy_v2", "Executing remote command", Some(&command_to_execute)).await?;
    }

    let result = client.execute(command_to_execute.as_str()).await.map_err(|e| format!("Failed to execute remote command: {}", e))?;

    // Read output
    let stdout = result.stdout;
    let stderr = result.stderr;

    if !stdout.is_empty() {
        log_info("redeploy_v2", "Remote stdout", Some(&stdout)).await?;
        println!("\x1b[92mRemote Output:\x1b[0m\n{}", stdout);
    }
    if !stderr.is_empty() {
        log_error("redeploy_v2", "Remote stderr", Some(&stderr)).await?;
        eprintln!("\x1b[91mRemote Error:\x1b[0m\n{}", stderr);
    }

    let exit_status = result.exit_status;

    if exit_status == 0 {
        log_success("redeploy_v2", "Remote redeploy.sh executed successfully", None).await?;
        Ok(())
    } else {
        log_error("redeploy_v2", &format!("Remote redeploy.sh failed with exit status: {}", exit_status), None).await?;
        Err(format!("Remote redeploy.sh failed with exit status: {}", exit_status))
    }
}

fn prompt_for_input(prompt: &str) -> Result<String, String> {
    print!("\x1b[96m{}\x1b[0m", prompt);
    io::stdout().flush().map_err(|e| format!("Failed to flush stdout: {}", e))?;
    let mut input = String::new();
    io::stdin().read_line(&mut input).map_err(|e| format!("Failed to read input: {}", e))?;
    Ok(input.trim().to_string())
}

fn prompt_for_password(prompt: &str) -> Result<String, String> {
    rpassword::prompt_password(prompt)
        .map_err(|e| format!("Failed to read password: {}", e))
}