xbp 0.2.2

XBP is a build pack and deployment management tool to deploy, rust, nextjs etc and manage the NGINX configs below it
use std::env;
use std::process::Output;
use std::str::Lines;
use tokio::process::Command;

use std::fs;
use std::path::Path;
use std::path::PathBuf;

#[tokio::main]
async fn main() {
    let args: Vec<String> = env::args().collect();
    if args.len() > 1 {
        match args[1].as_str() {
            "ports" => {
                let port_filter: Option<String> = if args.len() > 3 && args[2] == "-p" {
                    Some(args[3].clone())
                } else {
                    None
                };
                let debug: bool = args.contains(&"--debug".to_string());
                let kill: bool = args.contains(&"--kill".to_string());
                if debug {
                    println!("\x1b[94mDebug mode enabled\x1b[0m");
                }
                let command_output: String =
                    execute_ports_command(port_filter.clone(), debug, kill).await;
                if command_output.trim().is_empty() {
                    if let Some(port) = port_filter {
                        println!("\x1b[94mNo active processes found on port: {}\x1b[0m", port);
                    }
                } else {
                    display_output(command_output);
                }
            }
            "setup" => {
                run_setup().await;
            }
            "redeploy" | "-r" => {
                redeploy().await;
            }
            "config" => {
                read_config_file().await;
            }
            "help" | "--help" | "-h" => {
                print_help().await;
            }
            _ => {
                println!("Usage: xbp ports [-p <port>] [--debug] [--kill] | xbp setup");
            }
        }
    } else {
        println!("Usage: xbp ports [-p <port>] [--debug] [--kill] | xbp setup");
    }
}

async fn print_help() {
    println!("Usage: xbp <command> [options]");
    println!("\nCommands:");
    println!("  ports [-p <port>] [--debug] [--kill]  : List active ports and processes, optionally filter by port, enable debug mode, or kill processes.");
    println!("  setup                                 : Run initial setup commands.");
    println!("  redeploy                              : Redeploy the application by running redeploy.sh in the closest .xbp folder.");
    println!("  config                                : Read and display the contents of xbp.json in the .xbp folder.");
}

async fn read_config_file() {
    let current_dir: PathBuf = env::current_dir().expect("Failed to get current directory");
    let xbp_json_path: PathBuf = current_dir.join(".xbp/xbp.json");

    if xbp_json_path.exists() {
        println!("Found xbp.json at: {}", xbp_json_path.display());

        // Read the xbp.json file
        match fs::read_to_string(&xbp_json_path) {
            Ok(contents) => {
                if let Ok(json_data) = serde_json::from_str::<serde_json::Value>(&contents) {
                    for (key, value) in json_data.as_object().unwrap() {
                        let value_str = value.to_string().replace("\"", "");
                        println!("{:<15} |   {}", key, value_str);
                    }
                } else {
                    eprintln!("\x1b[91mFailed to parse xbp.json contents as JSON.\x1b[0m");
                }
            }
            Err(e) => {
                eprintln!("\x1b[91mFailed to read xbp.json: {}\x1b[0m", e);
            }
        }
    } else {
        println!("\x1b[91mNo .xbp folder with xbp.json found in the current directory.\x1b[0m");
    }
}

async fn redeploy() {
    println!("\x1b[93mRedeploying application...\x1b[0m");

    // Find the closest .xbp folder
    let current_dir = env::current_dir().expect("Failed to get current directory");
    let mut dir = current_dir.as_path();
    let mut xbp_path = None;

    for _ in 0..2 {
        let potential_path = dir.join(".xbp").join("redeploy.sh");
        if potential_path.exists() {
            xbp_path = Some(potential_path);
            break;
        }
        if let Some(parent) = dir.parent() {
            dir = parent;
        } else {
            break;
        }
    }

    if let Some(xbp_path) = xbp_path {
        println!("Found redeploy.sh at: {}", xbp_path.display());

        // Make the script executable
        let chmod_output = Command::new("sudo")
            .arg("chmod")
            .arg("+x")
            .arg(&xbp_path)
            .output()
            .await
            .expect("Failed to execute chmod command");
        println!("\x1b[94mMaking redeploy.sh executable...\x1b[0m");

        if !chmod_output.status.success() {
            eprintln!(
                "\x1b[91mFailed to make redeploy.sh executable: {}\x1b[0m",
                String::from_utf8_lossy(&chmod_output.stderr)
            );
            return;
        }

        // Run the redeploy.sh script
        let redeploy_output: Output = Command::new(&xbp_path)
            .output()
            .await
            .expect("Failed to execute redeploy.sh");

        if !redeploy_output.status.success() {
            eprintln!(
                "\x1b[91mFailed to run redeploy.sh: {}\x1b[0m",
                String::from_utf8_lossy(&redeploy_output.stderr)
            );
            return;
        }

        println!(
            "\x1b[92mRedeploy output:\x1b[0m {}",
            String::from_utf8_lossy(&redeploy_output.stdout)
        );
    } else {
        println!("\x1b[91mNo .xbp/redeploy.sh found in the directory hierarchy.\x1b[0m");
    }
}

async fn run_setup() {
    // Placeholder for setup logic
    // run command sudo apt install net-tools
    let output: Output = Command::new("sh")
        .arg("-c")
        .arg("sudo apt install -y net-tools nginx pkg-config libssl-dev build-essential plocate sshpass neofetch certbot python3-certbot-nginx")
        .output()
        .await
        .expect("Failed to execute setup command");

    if !output.status.success() {
        eprintln!(
            "\x1b[91mSetup failed: {}\x1b[0m",
            String::from_utf8_lossy(&output.stderr)
        );
        return;
    }

    if !output.stdout.is_empty() {
        println!(
            "\x1b[92mSetup output:\x1b[0m {}",
            String::from_utf8_lossy(&output.stdout)
        );
    }

    // Additional setup steps can be added here

    println!("\x1b[92mSetup completed successfully!\x1b[0m");
}

async fn execute_ports_command(port_filter: Option<String>, debug: bool, kill: bool) -> String {
    let output: Output = Command::new("sh")
        .arg("-c")
        .arg("sudo netstat -tuln | awk 'NR>2 {print $4}' | sed 's/.*://g' | sort -n | uniq | while read port; do echo \"Port: $port\"; sudo fuser -n tcp $port 2>/dev/null | xargs -r ps -fp; echo \"------\"; done")
        .output()
        .await
        .expect("Failed to execute ports command");

    if debug {
        println!("\x1b[94mExecuting ports command...\x1b[0m");
        println!("{}", String::from_utf8_lossy(&output.stdout));
    }

    let output_str: String = String::from_utf8_lossy(&output.stdout).to_string();
    let mut table_output: String = String::new();

    for block in output_str.split("------\n") {
        if block.trim().is_empty() {
            continue;
        }
        let mut lines: Lines<'_> = block.lines();
        if let Some(port_line) = lines.next() {
            if let Some(port) = port_line.strip_prefix("Port: ") {
                if port_filter.is_some() && port_filter.as_ref().unwrap() != port {
                    continue;
                }
                table_output.push_str(&format!("Port: {}\n", port));
                table_output.push_str(&format!(
                    "{:<10} {:<10} {:<10} {:<5} {:<10} {:<10} {:<10} {}\n",
                    "UID", "PID", "PPID", "C", "STIME", "TTY", "TIME", "CMD"
                ));
                table_output.push_str(&format!("{:-<80}\n", ""));
                for line in lines {
                    if !line.trim().is_empty() && !line.starts_with("UID") {
                        let parts: Vec<&str> = line.split_whitespace().collect();
                        if parts.len() >= 8 {
                            table_output.push_str(&format!(
                                "{:<10} {:<10} {:<10} {:<5} {:<10} {:<10} {:<10} {}\n",
                                parts[0],
                                parts[1],
                                parts[2],
                                parts[3],
                                parts[4],
                                parts[5],
                                parts[6],
                                parts[7..].join(" ")
                            ));
                            if kill {
                                let pid = parts[1];
                                let kill_output: Output = Command::new("sh")
                                    .arg("-c")
                                    .arg(format!("sudo kill -9 {}", pid))
                                    .output()
                                    .await
                                    .expect("Failed to execute kill command");
                                if kill_output.status.success() {
                                    println!(
                                        "\x1b[92mSuccessfully killed process with PID: {}\x1b[0m",
                                        pid
                                    );
                                    // clear the line from the output

                                    table_output.push_str(&format!(
                                        "\x1b[91mKilled process with PID: {}\x1b[0m\n",
                                        pid
                                    ));
                                } else {
                                    eprintln!(
                                        "\x1b[91mFailed to kill process with PID: {}\x1b[0m",
                                        pid
                                    );
                                }
                            }
                        }
                    }
                }
                table_output.push_str(&format!("{:-<80}\n\n", ""));
            }
        }
    }

    table_output
}

fn display_output(output: String) {
    println!("{}", output);
}