diff --git a/eclipta-cli/src/commands/config/config.rs b/eclipta-cli/src/commands/config/config.rs deleted file mode 100644 index afc8096..0000000 --- a/eclipta-cli/src/commands/config/config.rs +++ /dev/null @@ -1,125 +0,0 @@ -use clap::Args; -use std::{collections::HashMap, fs, path::PathBuf}; -use anyhow::{anyhow, Result}; -use serde::{Deserialize, Serialize}; - -#[derive(Args)] -pub struct ConfigOptions { - #[arg(long)] - pub agent: String, - - #[arg(long)] - pub get: Option, - - #[arg(long)] - pub set: Option, - - #[arg(long)] - pub list: bool, -} - -#[derive(Debug, Serialize, Deserialize, Default)] -pub struct AgentSettings { - pub settings: HashMap, -} - -#[derive(Debug, Deserialize)] -struct AgentStatus { - id: String, - hostname: String, - kernel: String, - version: String, - last_seen: String, - uptime_secs: u64, - cpu_load: Option<[f32; 3]>, - mem_used_mb: Option, - mem_total_mb: Option, - process_count: Option, - disk_used_mb: Option, - disk_total_mb: Option, - net_rx_kb: Option, - net_tx_kb: Option, - tcp_connections: Option, - alert: Option, -} - -pub async fn handle_config(opts: ConfigOptions) -> Result<()> { - let runtime_path = PathBuf::from(format!("/run/eclipta/{}.json", opts.agent)); - let config_path = PathBuf::from(format!("/etc/eclipta/agent-{}.conf.json", opts.agent)); - - // Load runtime status from /run/eclipta/.json - let status_data = fs::read_to_string(&runtime_path)?; - let agent: AgentStatus = serde_json::from_str(&status_data)?; - - println!("Agent Configuration for '{}':\n", agent.id); - println!("Hostname : {}", agent.hostname); - println!("Kernel : {}", agent.kernel); - println!("Version : {}", agent.version); - println!("Uptime (secs) : {}", agent.uptime_secs); - println!("Last Seen : {}", agent.last_seen); - - if let Some(cpu) = agent.cpu_load { - println!("CPU Load : {:.1} / {:.1} / {:.1}", cpu[0], cpu[1], cpu[2]); - } - - if let (Some(used), Some(total)) = (agent.mem_used_mb, agent.mem_total_mb) { - println!("Memory : {} MB / {} MB", used, total); - } - - if let Some(proc) = agent.process_count { - println!("Processes : {}", proc); - } - - if let (Some(du), Some(dt)) = (agent.disk_used_mb, agent.disk_total_mb) { - println!("Disk Usage : {} MB / {} MB", du, dt); - } - - if let (Some(rx), Some(tx)) = (agent.net_rx_kb, agent.net_tx_kb) { - println!("Network RX/TX : {} KB / {} KB", rx, tx); - } - - if let Some(tc) = agent.tcp_connections { - println!("TCP Conns : {}", tc); - } - - if let Some(alert) = agent.alert { - println!("Alert : {}", if alert { "YES" } else { "No" }); - } - - // Load config from /etc/eclipta/agent-xxx.conf.json - let mut config: AgentSettings = if config_path.exists() { - let data = fs::read_to_string(&config_path)?; - serde_json::from_str(&data).unwrap_or_default() - } else { - AgentSettings::default() - }; - - if opts.list { - println!("\nAgent Saved Settings:"); - for (key, value) in &config.settings { - println!("{} = {}", key, value); - } - } - - if let Some(key) = opts.get { - if let Some(val) = config.settings.get(&key) { - println!("\n{} = {}", key, val); - } else { - println!("\nKey '{}' not found", key); - } - } - - if let Some(kv) = opts.set { - let parts: Vec<&str> = kv.splitn(2, '=').collect(); - if parts.len() != 2 { - return Err(anyhow!("Invalid format. Use --set key=value")); - } - - config.settings.insert(parts[0].to_string(), parts[1].to_string()); - fs::create_dir_all("/etc/eclipta")?; - fs::write(&config_path, serde_json::to_string_pretty(&config)?)?; - println!("\nUpdated config: {} = {}", parts[0], parts[1]); - } - - Ok(()) -} diff --git a/eclipta-cli/src/commands/config/daemon.rs b/eclipta-cli/src/commands/config/daemon.rs deleted file mode 100644 index 663c121..0000000 --- a/eclipta-cli/src/commands/config/daemon.rs +++ /dev/null @@ -1,73 +0,0 @@ -use chrono::Utc; -use serde::Serialize; -use std::{fs::File, io::Write, path::PathBuf, thread, time::Duration}; -use sysinfo::{System, RefreshKind, CpuRefreshKind, MemoryRefreshKind, LoadAvg}; -use hostname::get; -use crate::utils::logger::success; - -#[derive(Debug, Serialize)] -struct AgentStatus { - id: String, - hostname: String, - kernel: String, - version: String, - uptime_secs: u64, - cpu_load: [f32; 3], - mem_used_mb: u64, - mem_total_mb: u64, - last_seen: String, -} - -pub async fn handle_daemon() { - let agent_id = "agent-001"; - let run_path = PathBuf::from(format!("/run/eclipta/{}.json", agent_id)); - - success(" Starting Eclipta Agent Daemon..."); - - let mut sys = System::new_with_specifics( - RefreshKind::new() - .with_memory(MemoryRefreshKind::everything()) - .with_cpu(CpuRefreshKind::everything()), - ); - - loop { - sys.refresh_memory(); - sys.refresh_cpu(); - - let hostname = get() - .map(|s| s.to_string_lossy().into_owned()) - .unwrap_or_else(|_| "unknown-host".into()); - - let kernel = System::kernel_version().unwrap_or_else(|| "unknown-kernel".into()); - let version = "0.1.0".to_string(); - let uptime_secs = System::uptime(); - let load: LoadAvg = System::load_average(); - let mem_total_mb = sys.total_memory() / 1024; - let mem_used_mb = (sys.total_memory() - sys.available_memory()) / 1024; - let now = Utc::now().to_rfc3339(); - - let agent = AgentStatus { - id: agent_id.to_string(), - hostname, - kernel, - version, - uptime_secs, - cpu_load: [ - load.one as f32, - load.five as f32, - load.fifteen as f32, -], - mem_used_mb, - mem_total_mb, - last_seen: now, - }; - - if let Ok(json) = serde_json::to_string_pretty(&agent) { - if let Ok(mut file) = File::create(&run_path) { - let _ = file.write_all(json.as_bytes()); - } - } - - thread::sleep(Duration::from_secs(5)); - } -} diff --git a/eclipta-cli/src/commands/config/mod.rs b/eclipta-cli/src/commands/config/mod.rs deleted file mode 100644 index 1220fbb..0000000 --- a/eclipta-cli/src/commands/config/mod.rs +++ /dev/null @@ -1,2 +0,0 @@ -pub mod config; -pub mod daemon; diff --git a/eclipta-cli/src/commands/ebpf/inspect.rs b/eclipta-cli/src/commands/ebpf/inspect.rs index 03c8018..9e55331 100644 --- a/eclipta-cli/src/commands/ebpf/inspect.rs +++ b/eclipta-cli/src/commands/ebpf/inspect.rs @@ -1,53 +1,171 @@ use aya::Ebpf; use clap::Args; use std::path::PathBuf; -use crate::utils::logger::{info, error}; +use crate::utils::logger::success; +use crate::utils::db::ensure_db_ready; +use crate::db::programs::{get_program_by_id, get_program_by_title}; use serde_json; -use crate::utils::paths::default_bin_object; +use anyhow::{Result, anyhow}; #[derive(Args, Debug)] pub struct InspectOptions { - /// Path to eBPF ELF (defaults to $ECLIPTA_BIN or ./bin/ebpf.so) - #[arg(short, long)] + /// Path to eBPF ELF file (alternative to --id/--title) + #[arg(short, long, conflicts_with_all = ["id", "title"])] pub program: Option, + /// Program ID from database (alternative to --program/--title) + #[arg(long, conflicts_with_all = ["program", "title"])] + pub id: Option, + + /// Program title from database (alternative to --program/--id) + #[arg(long, conflicts_with_all = ["program", "id"])] + pub title: Option, + + /// Output in JSON format #[arg(long)] pub json: bool, + /// Show verbose technical details #[arg(long)] pub verbose: bool, } -pub fn handle_inspect(opts: InspectOptions) { - let program_path = opts.program.unwrap_or_else(default_bin_object); - if !program_path.exists() { - error("Missing compiled eBPF program."); - return; +pub async fn handle_inspect(opts: InspectOptions) -> Result<()> { + if opts.program.is_none() && opts.id.is_none() && opts.title.is_none() { + return Err(anyhow!( + "Please specify a program to inspect using one of:\n\ + --program (for file path)\n\ + --id (for database ID)\n\ + --title (for database title)\n\ + \n\ + Use --help for more information." + )); } + let (program_path, program_metadata) = if let Some(id) = opts.id { + println!("Looking up program with ID {} in database...", id); + let pool = ensure_db_ready().await + .map_err(|e| anyhow!("Failed to connect to database: {}", e))?; + + let program = get_program_by_id(&pool, id).await + .map_err(|e| anyhow!("Database query failed: {}", e))? + .ok_or_else(|| anyhow!("No program found with ID {}", id))?; + + println!("Found program: '{}' (v{})", program.title, program.version); + + let path = PathBuf::from(&program.path); + if !path.exists() { + return Err(anyhow!("Program file not found at: {}\nThe program may have been moved or deleted.", program.path)); + } + + (path, Some(program)) + } else if let Some(ref title) = opts.title { + println!("Looking up program with title '{}' in database...", title); + let pool = ensure_db_ready().await + .map_err(|e| anyhow!("Failed to connect to database: {}", e))?; + + let programs = get_program_by_title(&pool, title).await + .map_err(|e| anyhow!("Database query failed: {}", e))?; + + match programs.len() { + 1 => { + let program = programs[0].clone(); + println!("Found program: '{}' (v{})", program.title, program.version); + + let path = PathBuf::from(&program.path); + if !path.exists() { + return Err(anyhow!("Program file not found at: {}\nThe program may have been moved or deleted.", program.path)); + } + (path, Some(program)) + } + n if n > 1 => { + println!("Multiple programs found with title '{}':", title); + for (i, prog) in programs.iter().enumerate() { + println!(" {}. ID: {}, Version: {}, Status: {}", i + 1, prog.id, prog.version, prog.status); + } + return Err(anyhow!("Multiple programs found with title '{}'. Please use --id to specify which one to inspect.", title)); + } + _ => { + return Err(anyhow!("No program found with title '{}'", title)); + } + } + } else if let Some(program_path) = opts.program { + println!("Inspecting program file: {}", program_path.display()); + if !program_path.exists() { + return Err(anyhow!("Program file not found at: {}", program_path.display())); + } + (program_path, None) + } else { + return Err(anyhow!("No program specified. Use --help for available options.")); + }; + + println!("Parsing eBPF ELF file..."); let bpf = match Ebpf::load_file(&program_path) { - Ok(b) => b, + Ok(b) => { + println!("Successfully parsed ELF file"); + b + } Err(e) => { - error(&format!("Failed to parse ELF: {}", e)); - return; + return Err(anyhow!("Failed to parse ELF file: {}\nMake sure the file is a valid compiled eBPF program.", e)); } }; let programs: Vec<_> = bpf.programs().map(|(name, _)| name.to_string()).collect(); let maps: Vec<_> = bpf.maps().map(|(name, _)| name.to_string()).collect(); + let mut output_data = serde_json::json!({ + "elf_path": program_path.display().to_string(), + "programs": programs, + "maps": maps, + }); + if let Some(ref metadata) = program_metadata { + output_data["metadata"] = serde_json::json!({ + "id": metadata.id, + "title": metadata.title, + "version": metadata.version, + "status": metadata.status, + "path": metadata.path + }); + } if opts.json { - let output = serde_json::json!({ - "elf": program_path.display().to_string(), - "programs": programs, - "maps": maps, - }); - println!("{}", serde_json::to_string_pretty(&output).unwrap()); + println!("{}", serde_json::to_string_pretty(&output_data).unwrap()); } else { - info(&format!("✅ ELF: {}", program_path.display())); - println!("Programs:"); - for name in &programs { println!(" - {}", name); } - println!("Maps:"); - for name in &maps { println!(" - {}", name); } + success(&format!("Inspecting eBPF Program: {}", program_path.display())); + + if let Some(ref metadata) = program_metadata { + println!("\nProgram Metadata:"); + println!(" ID: {}", metadata.id); + println!(" Title: {}", metadata.title); + println!(" Version: {}", metadata.version); + println!(" Status: {}", metadata.status); + println!(" Path: {}", metadata.path); + } + + println!("\neBPF Programs:"); + if programs.is_empty() { + println!(" (no programs found)"); + } else { + for name in &programs { + println!(" {}", name); + } + } + + println!("\neBPF Maps:"); + if maps.is_empty() { + println!(" (no maps found)"); + } else { + for name in &maps { + println!(" {}", name); + } + } + + if opts.verbose { + println!("\nTechnical Details:"); + println!(" ELF Path: {}", program_path.display()); + println!(" Programs Count: {}", programs.len()); + println!(" Maps Count: {}", maps.len()); + } } + + Ok(()) } diff --git a/eclipta-cli/src/commands/ebpf/load.rs b/eclipta-cli/src/commands/ebpf/load.rs index f05f2c1..f1255aa 100644 --- a/eclipta-cli/src/commands/ebpf/load.rs +++ b/eclipta-cli/src/commands/ebpf/load.rs @@ -37,11 +37,11 @@ pub const XDP_DROP_SECTION: &str = "xdp_drop"; pub const TC_INGRESS_SECTION: &str = "tc_ingress"; pub const TC_EGRESS_SECTION: &str = "tc_egress"; pub const SOCKET_FILTER_SECTION: &str = "socket_filter"; -pub const TRACEPOINT_NET_SECTION: &str = "tracepoint/net"; +// pub const TRACEPOINT_NET_SECTION: &str = "tracepoint/net"; pub const KPROBE_NET_SECTION: &str = "kprobe/net"; pub const UPROBE_NET_SECTION: &str = "uprobe/net"; pub const LSM_NET_SECTION: &str = "lsm/net"; -pub const TRACEPOINT_SECTION: &str = "tracepoint"; +// pub const TRACEPOINT_SECTION: &str = "tracepoint"; #[derive(Debug)] pub struct ProgramRequirements { @@ -228,34 +228,7 @@ fn validate_runtime_args(opts: &LoadOptions, requirements: &ProgramRequirements) Ok(()) } -fn load_ebpf_with_aya(path: &PathBuf) -> Result<()> { - let mut ebpf = Ebpf::load_file(path) - .context("Failed to load eBPF object with Aya")?; - - let map_count = ebpf.maps().count(); - if map_count == 0 { - println!("No maps found in eBPF object"); - } else { - println!("Found {} maps in eBPF object", map_count); - } - - for (name, program) in ebpf.programs_mut() { - match load_program_by_type(program) { - Ok(()) => println!("Program '{}' loaded successfully", name), - Err(e) => { - let error_msg = e.to_string(); - if error_msg.contains("busy") || error_msg.contains("already") { - println!("Program '{}' already loaded (EBUSY)", name); - continue; - } - return Err(anyhow!("Failed to load program '{}': {}", name, e)); - } - } - } - println!("Aya eBPF loading completed successfully"); - Ok(()) -} async fn load_and_attach_ebpf( path: &PathBuf, @@ -617,55 +590,3 @@ pub(crate) fn load_program_by_type(program: &mut Program) -> Result<(), ProgramE } } } - -pub fn get_program_requirements(sections: &HashSet<String>) -> ProgramRequirements { - let mut requires_interface = false; - let mut requires_socket_fd = false; - let mut program_type = String::new(); - - for section in sections { - match section.as_str() { - "XDP" => { - requires_interface = true; - program_type = "XDP".to_string(); - } - "TC Ingress" | "TC Egress" => { - requires_interface = true; - program_type = "TC".to_string(); - } - "Socket Filter" => { - requires_socket_fd = true; - program_type = "SocketFilter".to_string(); - } - "Tracepoint" => { - program_type = "Tracepoint".to_string(); - // Note: For this function, we can't extract category/name from section names - // as we only have the processed section names, not the raw ELF section names - } - _ => {} - } - } - - ProgramRequirements { - sections: sections.clone(), - requires_interface, - requires_socket_fd, - program_type, - tracepoint_category: None, - tracepoint_name: None, - } -} - -pub fn validate_ebpf_file_legacy(path: PathBuf) -> Result<(), String> { - validate_ebpf_file(&path) - .map_err(|e| e.to_string()) - .map(|_| ()) -} - -pub fn handle_file_process(path: PathBuf) { - if let Err(e) = validate_ebpf_file(&path) { - eprintln!("Validation failed: {}", e); - return; - } - println!("eBPF object file is valid: {}", path.display()); -} \ No newline at end of file diff --git a/eclipta-cli/src/commands/mod.rs b/eclipta-cli/src/commands/mod.rs index 483c21a..7ade73a 100644 --- a/eclipta-cli/src/commands/mod.rs +++ b/eclipta-cli/src/commands/mod.rs @@ -1,7 +1,6 @@ pub mod system; pub mod ebpf; pub mod network; -pub mod config; pub mod run; pub mod version; diff --git a/eclipta-cli/src/commands/system/monitor.rs b/eclipta-cli/src/commands/system/monitor.rs index b4dcd0c..10631ec 100644 --- a/eclipta-cli/src/commands/system/monitor.rs +++ b/eclipta-cli/src/commands/system/monitor.rs @@ -109,7 +109,7 @@ fn live_hook_for( pid: u32, ) -> Option<String> { let prog_id = prog_index.get(program_name).copied()?; - let mut candidates: Vec<&LinkInfo> = link_index.iter().filter(|l| l.prog_id == prog_id).collect(); + let candidates: Vec<&LinkInfo> = link_index.iter().filter(|l| l.prog_id == prog_id).collect(); if candidates.is_empty() { return None; } if let Some(first_pid_match) = candidates.iter().find(|l| l.pid == Some(pid)) { return Some(render_hook(first_pid_match)); diff --git a/eclipta-cli/src/commands/system/status.rs b/eclipta-cli/src/commands/system/status.rs index 8025176..a47c825 100644 --- a/eclipta-cli/src/commands/system/status.rs +++ b/eclipta-cli/src/commands/system/status.rs @@ -188,7 +188,7 @@ async fn build_program_status(program: &crate::db::programs::Program) -> Result< }) } -async fn get_kernel_status(program_name: &str) -> Result<KernelStatus> { +async fn get_kernel_status(_program_name: &str) -> Result<KernelStatus> { // Check if program is loaded in kernel using bpftool let output = TokioCommand::new("bpftool") .args(["prog", "list"]) @@ -254,7 +254,7 @@ async fn get_kernel_status(program_name: &str) -> Result<KernelStatus> { }) } -async fn get_attachment_status(program_name: &str) -> Result<AttachmentStatus> { +async fn get_attachment_status(_program_name: &str) -> Result<AttachmentStatus> { // Check if program is attached to any hooks let output = TokioCommand::new("bpftool") .args(["link", "list"]) diff --git a/eclipta-cli/src/db/programs.rs b/eclipta-cli/src/db/programs.rs index 1cff0ec..e24437c 100644 --- a/eclipta-cli/src/db/programs.rs +++ b/eclipta-cli/src/db/programs.rs @@ -1,6 +1,6 @@ use sqlx::{Pool, Postgres, Row}; -#[derive(Debug)] +#[derive(Debug, Clone)] pub struct Program { pub id: i32, pub title: String, diff --git a/eclipta-cli/src/main.rs b/eclipta-cli/src/main.rs index 83b33ea..c7af664 100644 --- a/eclipta-cli/src/main.rs +++ b/eclipta-cli/src/main.rs @@ -29,11 +29,11 @@ use crate::commands::network::{ ping_all::handle_ping_all, }; -// CONFIG COMMANDS -use crate::commands::config::{ - config::{handle_config, ConfigOptions}, - daemon::handle_daemon, -}; +// CONFIG COMMANDS - Temporarily disabled due to missing config module +// use crate::commands::config::{ +// config::{handle_config, ConfigOptions}, +// daemon::handle_daemon, +// }; // STORE / DB COMMANDS use crate::commands::store::check_db::{handle_check_db, CheckDbOptions}; @@ -62,11 +62,11 @@ enum Commands { Logs(LogOptions), Unload(UnloadOptions), Inspect(InspectOptions), - Daemon, + // Daemon, // Temporarily disabled Monitor, PingAll, WatchCpu(WatchCpuOptions), - Config(ConfigOptions), + // Config(ConfigOptions), // Temporarily disabled Alerts, Version(VersionOptions), Run(RunOptions), @@ -92,13 +92,17 @@ async fn handle_command(cmd: Commands) -> Result<(), Box<dyn std::error::Error>> Commands::Status(opts) => run_status(opts).await?, Commands::Load(opts) => handle_load(opts).await?, Commands::Unload(opts) => handle_unload(opts), - Commands::Inspect(opts) => handle_inspect(opts), + Commands::Inspect(opts) => { + if let Err(e) = handle_inspect(opts).await { + eprintln!("[INSPECT ERROR] {}", e); + } + } Commands::Logs(opts) => handle_logs(opts).await, - Commands::Daemon => handle_daemon().await, + // Commands::Daemon => handle_daemon().await, // Temporarily disabled Commands::Monitor => handle_monitor().await?, Commands::PingAll => handle_ping_all().await, Commands::WatchCpu(opts) => handle_watch_cpu(opts).await?, - Commands::Config(opts) => handle_config(opts).await?, + // Commands::Config(opts) => handle_config(opts).await?, // Temporarily disabled Commands::Alerts => handle_alerts().await?, Commands::Version(opts) => handle_version(opts).await?, Commands::Run(opts) => handle_run(opts).await,