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());
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");
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());
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;
}
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() {
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)
);
}
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
);
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);
}