use netstat2::{get_sockets_info, AddressFamilyFlags, ProtocolFlags, ProtocolSocketInfo};
use std::collections::BTreeMap;
use std::env;
use std::fs;
use std::path::Path;
use std::path::PathBuf;
use std::process::Output;
use std::time::Duration;
use std::time::Instant;
use tokio::process::Command;
use xbp::commands::{install_package, view_logs, parse_logs_args};
use xbp::logging::{init_logger, log_info, log_error, log_success, log_timed, log_process_output, LogLevel};
use xbp::commands::redeploy_v2::run_redeploy_v2;
#[tokio::main]
async fn main() {
let args: Vec<String> = env::args().collect();
let debug: bool = args.contains(&"--debug".to_string());
if let Err(e) = init_logger(debug).await {
eprintln!("Failed to initialize logger: {}", e);
}
if args.len() > 1 {
match args[1].as_str() {
"-v" | "--version" => {
print_version().await;
return;
}
"ports" => {
let port_filter: Option<String> = if args.len() > 3 && args[2] == "-p" {
Some(args[3].clone())
} else {
None
};
let kill: bool = args.contains(&"--kill".to_string());
let nginx_search: bool = args.contains(&"-n".to_string());
if debug {
println!("\x1b[94mDebug mode enabled\x1b[0m");
println!("[DEBUG] Args: {:?}", args);
}
let _ = log_info("ports", "Executing ports command",
port_filter.as_deref()).await;
let start: Instant = Instant::now();
let command_output: String =
execute_ports_command_nesttstat2(port_filter.clone(), debug, kill).await;
let elapsed: Duration = start.elapsed();
let _ = log_timed(
LogLevel::Success,
"ports",
"Ports command completed",
elapsed.as_millis() as u64
).await;
if debug {
println!("[DEBUG] execute_ports_command took: {:.2?}", elapsed);
}
if command_output.trim().is_empty() {
if let Some(port) = port_filter.clone() {
println!("\x1b[94mNo active processes found on port: {}\x1b[0m", port);
}
} else {
display_output(command_output);
}
if nginx_search {
if let Some(port) = port_filter {
println!("\nSearching NGINX configurations for port: {}\n", port);
search_nginx_configs(&port).await;
} else {
eprintln!("\x1b[91mError: -n flag requires a port to be specified with -p.\x1b[0m");
}
}
}
"setup" => {
let _ = log_info("setup", "Starting system setup", None).await;
let start: Instant = Instant::now();
run_setup_with_debug(debug).await;
let elapsed = start.elapsed();
let _ = log_timed(
LogLevel::Success,
"setup",
"Setup completed",
elapsed.as_millis() as u64
).await;
if debug {
println!("[DEBUG] run_setup took: {:.2?}", elapsed);
}
}
"redeploy" | "-r" => {
let _ = log_info("redeploy", "Starting redeploy process", None).await;
let start: Instant = Instant::now();
let result = redeploy_with_debug(debug).await;
let elapsed = start.elapsed();
match result {
Ok(()) => {
let _ = log_timed(
LogLevel::Success,
"redeploy",
"Redeploy completed successfully",
elapsed.as_millis() as u64
).await;
}
Err(e) => {
eprintln!("\x1b[91mRedeploy failed: {}\x1b[0m", e);
let _ = log_error("redeploy", "Redeploy failed", Some(&e)).await;
}
}
if debug {
println!("[DEBUG] redeploy took: {:.2?}", elapsed);
}
}
"redeploy_v2" => {
let mut password: Option<String> = None;
let mut username: Option<String> = None;
let mut host: Option<String> = None;
let mut project_dir: Option<String> = None;
let mut i = 2;
while i < args.len() {
match args[i].as_str() {
"-p" => {
if let Some(p) = args.get(i + 1) {
password = Some(p.clone());
i += 1;
} else {
eprintln!("\x1b[91mError: -p requires a password.\x1b[0m");
return;
}
}
"-u" => {
if let Some(u) = args.get(i + 1) {
username = Some(u.clone());
i += 1;
} else {
eprintln!("\x1b[91mError: -u requires a username.\x1b[0m");
return;
}
}
"-h" => {
if let Some(h) = args.get(i + 1) {
host = Some(h.clone());
i += 1;
} else {
eprintln!("\x1b[91mError: -h requires a host.\x1b[0m");
return;
}
}
"-d" => {
if let Some(d) = args.get(i + 1) {
project_dir = Some(d.clone());
i += 1;
} else {
eprintln!("\x1b[91mError: -d requires a project directory.\x1b[0m");
return;
}
}
_ => {}
}
i += 1;
}
let _ = log_info("redeploy_v2", "Starting remote redeploy process", None).await;
let start: Instant = Instant::now();
let result = run_redeploy_v2(password, username, host, project_dir, debug).await;
let elapsed = start.elapsed();
match result {
Ok(()) => {
let _ = log_timed(
LogLevel::Success,
"redeploy_v2",
"Remote redeploy completed successfully",
elapsed.as_millis() as u64,
)
.await;
}
Err(e) => {
eprintln!("\x1b[91mRemote redeploy failed: {}\x1b[0m", e);
let _ = log_error("redeploy_v2", "Remote redeploy failed", Some(&e)).await;
}
}
}
"config" => {
let start: Instant = Instant::now();
read_config_file_with_debug(debug).await;
if debug {
println!("[DEBUG] read_config_file took: {:.2?}", start.elapsed());
}
}
"help" | "--help" | "-h" => {
print_help().await;
}
"install" => {
if args.len() < 3 {
println!("Available packages:");
println!(" \x1b[92m📦 docker\x1b[0m - Install Docker engine and docker-compose");
println!(" \x1b[92m📦 nginx_full\x1b[0m - Install NGINX web server and certbot");
println!(" \x1b[92m📦 opencv-rust\x1b[0m - Install OpenCV-Rust");
println!(" \x1b[92m📦 elixir_erlang\x1b[0m - Install Elixir and Erlang");
println!(" \x1b[92m📦 grafana\x1b[0m - Install Grafana");
println!(" \x1b[92m📦 scylladb\x1b[0m - Install ScyllaDB");
println!(" \x1b[92m📦 triggerdotdev\x1b[0m - Install trigger.dev cli");
println!(" \x1b[92m📦 iotop\x1b[0m - Install iotop");
println!();
println!("Usage: xbp install <package_name>");
return;
}
let package_name: String = args[2].clone();
let debug: bool = args.contains(&"--debug".to_string());
let install_msg = format!("Installing package: {}", package_name);
let _ = log_info("install", &install_msg, None).await;
let result: Result<(), String> = install_package(&package_name, debug).await;
match result {
Ok(()) => {
println!("Installation successful");
let success_msg = format!("Successfully installed: {}", package_name);
let _ = log_success("install", &success_msg, None).await;
},
Err(e) => {
eprintln!("Installation failed: {}", e);
let error_msg = format!("Failed to install: {}", package_name);
let _ = log_error("install", &error_msg, Some(&e)).await;
},
}
}
"logs" => {
let (command_filter, lines, follow, list_files) = parse_logs_args(&args[2..]);
if args.contains(&"--help".to_string()) {
println!("\x1b[96m📋 XBP Logs Viewer\x1b[0m");
println!();
println!("View and manage XBP log files stored in /var/log/xbp/");
println!();
println!("Usage: xbp logs [options]");
println!();
println!("Options:");
println!(" -c, --command <cmd> Filter logs by command");
println!(" -n, --lines <num> Number of lines to show (default: 50)");
println!(" -f, --follow Follow logs in real-time");
println!(" -l, --list List all available log files");
println!(" --debug Enable debug output");
println!();
println!("Examples:");
println!(" xbp logs # Show recent logs");
println!(" xbp logs -c install # Show install command logs");
println!(" xbp logs -n 100 # Show last 100 lines");
println!(" xbp logs --list # List all log files");
return;
}
let result = view_logs(command_filter, lines, follow, list_files, debug).await;
match result {
Ok(()) => {},
Err(e) => eprintln!("\x1b[91m❌ Failed to view logs: {}\x1b[0m", e),
}
}
_ => {
println!("Usage: xbp <command> [options]");
println!("Commands: ports, setup, redeploy, config, install, logs, help");
println!("Run 'xbp help' for detailed information.");
}
}
} else {
println!("Usage: xbp ports [-p <port>] [--debug] [--kill] | xbp setup");
}
}
async fn print_help() {
println!(":::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::");
println!(":.:.:.:.:.:.:.:.:.:.:.:.:.:.:.:.:.:.:.:.:.:.:.:.:.::.:.:.:.:.:.:.:.:.:.:.:.:.:.:.:.:.:.:.:.:.:.:.:.:.::.:.:.:.:.:.:.:.:.:.:.:.:.:.:.:.:.:.:.:.:.:.:.:.:.:");
println!("::::.:::.:::.:::.:::.:::.:::.:::.:::.:::.:::.:::.:.::::.:::.:::.:::.:::.:::.:::.:::.:::.:::.:::.:::.:.::::.:::.:::.:::.:::.:::.:::.:::.:::.:::.:::.:::.:.");
println!(":.:::.:::.:::.:::.:::.:::.:::.:::.:::.:::.:::.::::::.:::.:::.:::.:::.:::.:::.:::.:::.:::.:::.:::.::::::.:::.:::.:::.:::.:::.:::.:::.:::.:::.:::.:::.:::::");
println!("::.:::.:::::.:::.:::.:::.:::.:::.::::::.:::.:::.:::.:::.:.:.::.:::.:::.:::.:::.:::.:::.:::.:::.:::.::.:::.:::.:::.:::.:::.:::.:::.:::.:.::::.:::.:::.:::.::::");
println!("::.:::.:::.:::.:::.:::.:::.:::.:::.:::.:.:.:::::.:::::.:::.:::.:::.::####::.::.:::.:::.:*###=:.:::.::::::::::.:::.:::.:::.:::.:::.:::.:.::::.:::.:::.:::.::::");
println!("::.:::.:::.:::.:::.:::.:::.:::.:::.:::.:::.:::.:.:.::::.:::.:::::=%%##-:.::.:::.:::#%%%+:::.:::.:.:.:.:::.:::.:::.:::.:::.:::.:::.:.::::.:::.:::.:::.::::");
println!("::.:::.:.:::.:::.:::.:::.:::.:::::.:::.:::.:::.:.:.:.:.:.:::.:.:::=%%%#::.::.:::.:*%#%+:.:::.::::::::::::.:::.:::.:::.:::.:::.:::.:.::::.:::.:::.:::.::::");
println!("::.:::.:::.:::.:::.:::.:::.:::.:::.:::.:::.:::.:.:.::::::.:::::.:.:=#%#%-:.::.:::#%#%+:::.:::.:.:.:.:.:::.:::.:::.:::.:::.:::.:::.:.::::.:::.:::.:::.::::");
println!("::.:::.:::.:::.:::.:::.:::.:::.:::.:::.:::.:::.:.:.::.:.::.:.:.:::::=%%#%::.::.:#%%%+:.:.:.:::::::.::::::.:::.:::.:::.:::.:::.:::.:.::::.:::.:::.:::.::::");
println!("::.:::.:::.:::.:::.:::.:::.:::.:::.:::.:::.:::.:.:.:::::.::::::.:.:.:=%%#%=:.:-#%#%+:::::::.:.:.:.:.:.:::.:::.:::.:::.:::.:::.:::.:.::::.:::.:::.:::.::::");
println!("::.:::.:::.:::.:::.:::.:::.:::.:::.:::.:::.:::.:.:.:.:.::.:.:.:::::::=#%%#=::.-#%%#+:.:.:.:::::::::::::::.:::.:::.:::.:::.:::.:::.:.::::.:::.:::.:::.::::");
println!("::.:::.:::.:.::.::.:.:.:.::::::.::.:::.:.:.:.:.::..::::.::::::.:.:.:=%%##-:.::::#%%%+:::.:.:.:.:.:.:.::::.:::.:::.:::.:::.:::.:::.:.::::.:::.:::.:::.::::");
println!("::.:::.:::.:::.:::.:::.:::.:::.:::.:::.:::.:::.:.:.:::.:.:.::::::=##%%-:::.:.::##%%+:.:::::::.:::.:.:::.:::.:::.:::.:::.:::.:::.:.::::.:::.:::.:::.::::");
println!("::.:::.:::.:::.:::.:::.:::.:::.:::.:::.:::.:::.:.:.::.:::::::.:.::=%%%#::.::::::::##%#+::.:.:.::.::::::::.:::.:::.:::.:::.:::.:::.:.::::.:::.:::.:::.::::");
println!("::.:::.:::.:::.:::.:::.:::.:::.:::.:::.:::.:::.:.:.:::.:.:.:.:::.=#%##-:::.:.:.:.::#%%%*::::.:.::.:.:.:::.:::.:::.:::.:::.:::.:::.:.::::.:::.:::.:::.::::");
println!("::.:::.:::.:::.:::.:::.:::.:::.:::.:::.:::.:::.:.:.:.::::::::.:::%%##::.:.::::::::::*#%#-.:.:::.:::::::::.:::.:::.:::.:::.:::.:::.:.::::.:::.:::.:::.::::");
println!("::.:::.:::.:::.:::.:::.:::.:::.:::.:::.:::.:::.:.:.::.:.:.:.::.::.::::::::.:.:.:.:.:::.:::::.:::.:.:.::::.:::.:::.:::.:::.:::.:::.:.::::.:::.:::.:::.::::");
println!("::.:::.:::.:::.:::.:::.:::.:::.:::.:::.:::.:::.:.:.:::::::.:.::.::.:.:.:.::::::.:::.:::.:.:.:.:::::::.:::.:::.:::.:::.:::.:::.:::.:.::::.:::.:::.:::.::::");
println!("::.:::.:::.:::.:::.:::.:::.:::.:::.:::.:::.:::.:.:.:::::::.:.::.::.:.:.:.::::::.::.:::.:.:.:.:::::::.:::.:::.:::.:::.:::.:::.:::.:.::::.:::.:::.:::.::::");
println!();
println!("\x1b[93m📋 Usage:\x1b[0m \x1b[96mxbp <command> [options]\x1b[0m");
println!();
println!("\x1b[93m🚀 Commands:\x1b[0m");
println!(" \x1b[92m🔌 ports\x1b[0m [-p <port>] [--debug] [--kill] [-n] List active ports and processes, optionally search NGINX configs");
println!(" \x1b[92m⚙️ setup\x1b[0m Run initial setup commands");
println!(
" \x1b[92m🔄 redeploy\x1b[0m Redeploy using redeploy.sh"
);
println!(
" \x1b[92m📡 redeploy_v2\x1b[0m [-p <password>] [-u <username>] [-h <host>] [-d <project_dir>] Remotely redeploy using redeploy.sh"
);
println!(
" \x1b[92m📝 config\x1b[0m Display xbp.json contents"
);
println!(" \x1b[92m📦 install\x1b[0m <package> Install a package");
println!(" \x1b[92m📋 logs\x1b[0m View XBP logs");
println!();
println!("\x1b[93m💡 Examples:\x1b[0m");
println!(" \x1b[90mxbp ports -p 3000 --kill # Kill processes on port 3000\x1b[0m");
println!(" \x1b[90mxbp setup --debug # Run setup with debug output\x1b[0m");
println!(" \x1b[90mxbp install docker # Install Docker package\x1b[0m");
println!(" \x1b[90mxbp logs -c install # View install command logs\x1b[0m");
println!(" \x1b[90mxbp -v # Display XBP version\x1b[0m");
println!();
}
async fn read_config_file_with_debug(debug: bool) {
let current_dir: PathBuf = env::current_dir().expect("Failed to get current directory");
let xbp_json_path_dotfolder: PathBuf = current_dir.join(".xbp/xbp.json");
let xbp_json_path_root: PathBuf = current_dir.join("xbp.json");
if debug {
println!("[DEBUG] Current dir: {}", current_dir.display());
println!(
"[DEBUG] Checking for: {}",
xbp_json_path_dotfolder.display()
);
println!("[DEBUG] Checking for: {}", xbp_json_path_root.display());
}
let (found_path, found_location) = if xbp_json_path_dotfolder.exists() {
(Some(xbp_json_path_dotfolder), Some(".xbp/xbp.json"))
} else if xbp_json_path_root.exists() {
(Some(xbp_json_path_root), Some("xbp.json"))
} else {
(None, None)
};
if let (Some(path), Some(location)) = (found_path, found_location) {
println!("Found xbp.json at: {}", path.display());
match fs::read_to_string(&path) {
Ok(contents) => {
if debug {
println!("[DEBUG] xbp.json contents: {}", 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: String = value.to_string().replace("\"", "");
println!("{:<15} | {}", key, value_str);
}
} else {
eprintln!(
"\x1b[91mFailed to parse {} contents as JSON.\x1b[0m",
location
);
}
}
Err(e) => {
eprintln!("\x1b[91mFailed to read {}: {}\x1b[0m", location, e);
}
}
} else {
println!("\x1b[91mNo .xbp/xbp.json or xbp.json found in the current directory.\x1b[0m");
}
}
async fn redeploy_with_debug(debug: bool) -> Result<(), String> {
println!("\x1b[93mRedeploying application...\x1b[0m");
let current_dir: PathBuf = env::current_dir().expect("Failed to get current directory");
let mut dir: &Path = current_dir.as_path();
let mut xbp_path: Option<PathBuf> = None;
for _ in 0..2 {
let potential_path: PathBuf = dir.join(".xbp").join("redeploy.sh");
if debug {
println!(
"[DEBUG] Checking for redeploy.sh at: {}",
potential_path.display()
);
}
if potential_path.exists() {
xbp_path = Some(potential_path);
if debug {
println!(
"[DEBUG] Found redeploy.sh at: {}",
xbp_path.as_ref().unwrap().display()
);
}
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_start: Instant = Instant::now();
let chmod_output: Output = match Command::new("sudo")
.arg("chmod")
.arg("+x")
.arg(&xbp_path)
.output()
.await
{
Ok(output) => output,
Err(e) => {
let error_msg = format!("Failed to execute chmod command: {}", e);
eprintln!("\x1b[91m{}\x1b[0m", error_msg);
return Err(error_msg);
}
};
let chmod_elapsed: Duration = chmod_start.elapsed();
println!("\x1b[94mMaking redeploy.sh executable...\x1b[0m");
if debug {
println!("[DEBUG] chmod output: {:?}", chmod_output);
println!("[DEBUG] chmod took: {:.2?}", chmod_elapsed);
}
if !chmod_output.status.success() {
let error_msg = format!(
"Failed to make redeploy.sh executable: {}",
String::from_utf8_lossy(&chmod_output.stderr)
);
eprintln!("\x1b[91m{}\x1b[0m", error_msg);
return Err(error_msg);
}
let redeploy_start: Instant = Instant::now();
let redeploy_output: Output = Command::new(&xbp_path)
.output()
.await
.map_err(|e| format!("Failed to execute redeploy.sh: {}", e))?;
let redeploy_elapsed = redeploy_start.elapsed();
if debug {
println!("[DEBUG] redeploy.sh output: {:?}", redeploy_output);
println!("[DEBUG] redeploy.sh took: {:.2?}", redeploy_elapsed);
}
if !redeploy_output.status.success() {
let stderr_output = String::from_utf8_lossy(&redeploy_output.stderr);
let stdout_output = String::from_utf8_lossy(&redeploy_output.stdout);
let error_msg = format!(
"Failed to run redeploy.sh:\nstderr: {}\nstdout: {}",
stderr_output, stdout_output
);
eprintln!("\x1b[91m{}\x1b[0m", error_msg);
let _ = log_process_output("redeploy", "redeploy.sh", &stdout_output, &stderr_output).await;
return Err(error_msg);
}
let stdout_output = String::from_utf8_lossy(&redeploy_output.stdout);
let stderr_output = String::from_utf8_lossy(&redeploy_output.stderr);
println!(
"\x1b[92mRedeploy output:\x1b[0m {}",
stdout_output
);
let _ = log_process_output("redeploy", "redeploy.sh", &stdout_output, &stderr_output).await;
Ok(())
} else {
let error_msg = "No .xbp/redeploy.sh found in the directory hierarchy.";
println!("\x1b[91m{}\x1b[0m", error_msg);
Err(error_msg.to_string())
}
}
async fn run_setup_with_debug(debug: bool) {
let setup_cmd: String = "sudo apt install -y net-tools nginx pkg-config libssl-dev build-essential plocate sshpass neofetch certbot python3-certbot-nginx".to_string();
if debug {
println!("[DEBUG] Running setup command: {}", setup_cmd);
}
let start: Instant = Instant::now();
let output: Output = Command::new("sh")
.arg("-c")
.arg(setup_cmd)
.output()
.await
.expect("Failed to execute setup command");
let elapsed = start.elapsed();
if debug {
println!("[DEBUG] Setup command output: {:?}", output);
println!("[DEBUG] Setup command took: {:.2?}", elapsed);
}
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_nesttstat2(
port_filter: Option<String>,
debug: bool,
kill: bool,
) -> String {
let start: Instant = Instant::now();
let af_flags: AddressFamilyFlags = AddressFamilyFlags::IPV4 | AddressFamilyFlags::IPV6;
let proto_flags: ProtocolFlags = ProtocolFlags::TCP;
let sockets: Vec<netstat2::SocketInfo> = match get_sockets_info(af_flags, proto_flags) {
Ok(s) => s,
Err(e) => {
return format!("Failed to get sockets info: {}", e);
}
};
if debug {
println!("[DEBUG] Fetched {} TCP sockets", sockets.len());
}
let mut table_output: String = String::new();
let mut port_map: BTreeMap<u16, Vec<(&netstat2::SocketInfo, &netstat2::TcpSocketInfo)>> =
BTreeMap::new();
for socket in &sockets {
if let ProtocolSocketInfo::Tcp(ref tcp_info) = socket.protocol_socket_info {
if let Some(ref filter) = port_filter {
if filter != &tcp_info.local_port.to_string() {
continue;
}
}
port_map
.entry(tcp_info.local_port)
.or_default()
.push((socket, tcp_info));
}
}
for (port, entries) in &port_map {
table_output.push_str(&format!("Port: {}\n", port));
table_output.push_str(&format!(
"{:<10} {:<20} {:<20} {:<10} {:<10}\n",
"PID", "LocalAddr", "RemoteAddr", "State", "Process"
));
table_output.push_str(&format!("{:-<80}\n", ""));
for (socket, tcp_info) in entries {
let pids: String = if !socket.associated_pids.is_empty() {
socket
.associated_pids
.iter()
.map(|pid| pid.to_string())
.collect::<Vec<_>>()
.join(",")
} else {
"-".to_string()
};
let process_names: String = "-".to_string();
table_output.push_str(&format!(
"{:<10} {:<20} {:<20} {:<10} {:<10}\n",
pids,
tcp_info.local_addr,
tcp_info.remote_addr,
format!("{:?}", tcp_info.state),
process_names
));
if kill {
for pid in &socket.associated_pids {
let killed: bool = kill_process_with_debug(&pid.to_string(), debug).await;
if killed {
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", ""));
}
if debug {
println!(
"[DEBUG] execute_ports_command_nesttstat2 took: {:.2?}",
start.elapsed()
);
}
table_output
}
async fn kill_process_with_debug(pid: &str, debug: bool) -> bool {
if debug {
println!("[DEBUG] Attempting to kill PID: {}", pid);
}
let start: Instant = Instant::now();
let kill_output: Output = Command::new("sh")
.arg("-c")
.arg(format!("sudo kill -9 {}", pid))
.output()
.await
.expect("Failed to execute kill command");
let elapsed = start.elapsed();
if debug {
println!(
"[DEBUG] kill_process output: status={:?}, stdout='{}', stderr='{}', took: {:.2?}",
kill_output.status,
String::from_utf8_lossy(&kill_output.stdout),
String::from_utf8_lossy(&kill_output.stderr),
elapsed
);
}
kill_output.status.success()
}
fn display_output(output: String) {
println!("{}", output);
}
async fn print_version() {
let version = env!("CARGO_PKG_VERSION");
println!("XBP Version: {}", version);
}
async fn search_nginx_configs(port: &str) {
let nginx_sites_available_path = PathBuf::from("/etc/nginx/sites-available");
if !nginx_sites_available_path.exists() {
println!("\x1b[93mWarning: /etc/nginx/sites-available/ not found. Skipping NGINX config search.\x1b[0m");
return;
}
let mut found_configs = false;
match fs::read_dir(&nginx_sites_available_path) {
Ok(entries) => {
for entry in entries {
if let Ok(entry) = entry {
let path = entry.path();
if path.is_file() {
let config_content = match fs::read_to_string(&path) {
Ok(content) => content,
Err(_) => continue, };
if config_content.contains(&format!("proxy_pass http://127.0.0.1:{}", port)) ||
config_content.contains(&format!("listen {}", port)) {
println!("\x1b[92mFound port {} in NGINX config: {}\x1b[0m", port, path.display());
found_configs = true;
}
}
}
}
}
Err(e) => {
eprintln!("\x1b[91mError reading NGINX sites-available directory: {}\x1b[0m", e);
return;
}
}
if !found_configs {
println!("\x1b[94mNo NGINX configurations found for port {}.\x1b[0m", port);
}
}