diff --git a/Cargo.lock b/Cargo.lock index 340073c32..d10fa736e 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -3044,6 +3044,29 @@ dependencies = [ "sha3-asm", ] +[[package]] +name = "kfetch" +version = "0.1.0" +dependencies = [ + "anyhow", + "kinode_process_lib 0.8.0 (git+https://github.com/kinode-dao/process_lib?rev=010e175)", + "rmp-serde", + "serde", + "serde_json", + "wit-bindgen", +] + +[[package]] +name = "kill" +version = "0.1.0" +dependencies = [ + "anyhow", + "kinode_process_lib 0.8.0 (git+https://github.com/kinode-dao/process_lib?rev=010e175)", + "serde", + "serde_json", + "wit-bindgen", +] + [[package]] name = "kinode" version = "0.8.0" @@ -3129,6 +3152,28 @@ dependencies = [ "lib", ] +[[package]] +name = "kinode_process_lib" +version = "0.8.0" +source = "git+https://github.com/kinode-dao/process_lib?rev=010e175#010e175b4e66242c2ef9422088e355698728600d" +dependencies = [ + "alloy-json-rpc 0.1.0 (git+https://github.com/alloy-rs/alloy.git?rev=cad7935)", + "alloy-primitives 0.7.0", + "alloy-rpc-types 0.1.0 (git+https://github.com/alloy-rs/alloy.git?rev=cad7935)", + "alloy-transport 0.1.0 (git+https://github.com/alloy-rs/alloy.git?rev=cad7935)", + "anyhow", + "bincode", + "http 1.1.0", + "mime_guess", + "rand 0.8.5", + "rmp-serde", + "serde", + "serde_json", + "thiserror", + "url", + "wit-bindgen", +] + [[package]] name = "kinode_process_lib" version = "0.8.0" @@ -5542,10 +5587,10 @@ dependencies = [ [[package]] name = "top" -version = "0.1.0" +version = "0.2.0" dependencies = [ "anyhow", - "kinode_process_lib 0.8.0 (git+https://github.com/kinode-dao/process_lib?rev=09dc9f9)", + "kinode_process_lib 0.8.0 (git+https://github.com/kinode-dao/process_lib?rev=010e175)", "serde", "serde_json", "wit-bindgen", diff --git a/Cargo.toml b/Cargo.toml index 6a15dd15c..a244dbab2 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -22,7 +22,7 @@ members = [ "kinode/packages/kns_indexer/kns_indexer", "kinode/packages/kns_indexer/get_block", "kinode/packages/kns_indexer/state", "kinode/packages/settings/settings", "kinode/packages/terminal/terminal", - "kinode/packages/terminal/alias", "kinode/packages/terminal/cat", "kinode/packages/terminal/echo", "kinode/packages/terminal/hi", "kinode/packages/terminal/m", "kinode/packages/terminal/top", + "kinode/packages/terminal/alias", "kinode/packages/terminal/cat", "kinode/packages/terminal/echo", "kinode/packages/terminal/hi", "kinode/packages/terminal/kfetch", "kinode/packages/terminal/kill", "kinode/packages/terminal/m", "kinode/packages/terminal/top", "kinode/packages/terminal/namehash_to_name", "kinode/packages/terminal/net_diagnostics", "kinode/packages/terminal/peer", "kinode/packages/terminal/peers", "kinode/packages/tester/tester", "kinode/packages/tester/test_runner", ] diff --git a/kinode/packages/terminal/kfetch/Cargo.toml b/kinode/packages/terminal/kfetch/Cargo.toml new file mode 100644 index 000000000..752754815 --- /dev/null +++ b/kinode/packages/terminal/kfetch/Cargo.toml @@ -0,0 +1,21 @@ +[package] +name = "kfetch" +version = "0.1.0" +edition = "2021" + +[features] +simulation-mode = [] + +[dependencies] +anyhow = "1.0" +kinode_process_lib = { git = "https://github.com/kinode-dao/process_lib", rev = "010e175" } +rmp-serde = "1.1.2" +serde = { version = "1.0", features = ["derive"] } +serde_json = "1.0" +wit-bindgen = "0.24.0" + +[lib] +crate-type = ["cdylib"] + +[package.metadata.component] +package = "kinode:process" diff --git a/kinode/packages/terminal/kfetch/src/lib.rs b/kinode/packages/terminal/kfetch/src/lib.rs new file mode 100644 index 000000000..ab6ef197a --- /dev/null +++ b/kinode/packages/terminal/kfetch/src/lib.rs @@ -0,0 +1,166 @@ +use kinode_process_lib::kernel_types::{ + KernelCommand, KernelPrint, KernelPrintResponse, KernelResponse, +}; +use kinode_process_lib::{call_init, eth, net, println, Address, Message, Request}; +use std::collections::HashSet; + +/// Fetching OS version from main package.. LMK if there's a better way... +const CARGO_TOML: &str = include_str!("../../../../Cargo.toml"); + +wit_bindgen::generate!({ + path: "target/wit", + world: "process-v0", +}); + +call_init!(init); +fn init(our: Address) { + // get identity + let Ok(Ok(Message::Response { body, .. })) = Request::to(("our", "net", "distro", "sys")) + .body(rmp_serde::to_vec(&net::NetAction::GetPeer(our.node.clone())).unwrap()) + .send_and_await_response(60) + else { + println!("failed to get response from net"); + return; + }; + let Ok(net::NetResponse::Peer(Some(our_id))) = rmp_serde::from_slice(&body) else { + println!("got malformed response from net"); + return; + }; + + // get eth providers + let Ok(Message::Response { body, .. }) = Request::new() + .target(("our", "eth", "distro", "sys")) + .body(serde_json::to_vec(ð::EthConfigAction::GetProviders).unwrap()) + .send_and_await_response(60) + .unwrap() + else { + println!("failed to get response from eth"); + return; + }; + let Ok(eth::EthConfigResponse::Providers(providers)) = serde_json::from_slice(&body) else { + println!("failed to parse eth response"); + return; + }; + + // get eth subs + let Ok(Message::Response { body, .. }) = Request::new() + .target(("our", "eth", "distro", "sys")) + .body(serde_json::to_vec(ð::EthConfigAction::GetState).unwrap()) + .send_and_await_response(60) + .unwrap() + else { + println!("failed to get response from eth"); + return; + }; + let Ok(eth::EthConfigResponse::State { + active_subscriptions, + outstanding_requests, + }) = serde_json::from_slice(&body) + else { + println!("failed to parse eth response"); + return; + }; + + // get number of processes + let Ok(Message::Response { body, .. }) = Request::new() + .target(("our", "kernel", "distro", "sys")) + .body(serde_json::to_vec(&KernelCommand::Debug(KernelPrint::ProcessMap)).unwrap()) + .send_and_await_response(60) + .unwrap() + else { + println!("failed to get response from kernel"); + return; + }; + let Ok(KernelResponse::Debug(KernelPrintResponse::ProcessMap(map))) = + serde_json::from_slice::(&body) + else { + println!("failed to parse kernel response"); + return; + }; + let num_processes = map.len(); + print_bird( + &our, + our_id, + providers, + // sum up all the subscriptions + active_subscriptions + .values() + .map(|v| v.len()) + .sum::(), + outstanding_requests.len() as usize, + num_processes, + ); +} + +fn print_bird( + our: &Address, + our_id: net::Identity, + providers: HashSet, + active_subscriptions: usize, + outstanding_requests: usize, + num_processes: usize, +) { + println!( + r#" + .` +`@@,, ,* {} + `@%@@@, ,~-##` + ~@@#@%#@@, ##### Kinode {} + ~-%######@@@, ##### + -%%#######@#####, pubkey: {} + ~^^%##########@ routing: {} + >^#########@ + `>#######` {} eth providers for chain IDs {} + .>######% {} active eth subscriptions + /###%^#% {} outstanding eth requests + /##%@# ` + ./######` + /.^`.#^#^` + ` ,#`#`#, {} running processes + ,/ /` ` + .*` + "#, + our.node(), + version_from_cargo_toml(), + our_id.networking_key, + routing_to_string(our_id.routing), + providers.len(), + providers + .into_iter() + .map(|p| p.chain_id.to_string()) + // remove duplicates + .collect::>() + .into_iter() + .collect::>() + .join(", "), + active_subscriptions, + outstanding_requests, + num_processes + ) +} + +fn routing_to_string(routing: net::NodeRouting) -> String { + match routing { + net::NodeRouting::Direct { ip, ports } => format!( + "direct at {} with {}", + ip, + ports.into_keys().into_iter().collect::>().join(", ") + ), + net::NodeRouting::Routers(routers) => format!("{} routers", routers.len()), + } +} + +fn version_from_cargo_toml() -> String { + let version = CARGO_TOML + .lines() + .find(|line| line.starts_with("version = ")) + .expect("Failed to find version in Cargo.toml"); + + version + .split('=') + .last() + .expect("Failed to parse version from Cargo.toml") + .trim() + .trim_matches('"') + .to_string() +} diff --git a/kinode/packages/terminal/kill/Cargo.toml b/kinode/packages/terminal/kill/Cargo.toml new file mode 100644 index 000000000..a121fa59b --- /dev/null +++ b/kinode/packages/terminal/kill/Cargo.toml @@ -0,0 +1,20 @@ +[package] +name = "kill" +version = "0.1.0" +edition = "2021" + +[features] +simulation-mode = [] + +[dependencies] +anyhow = "1.0" +kinode_process_lib = { git = "https://github.com/kinode-dao/process_lib", rev = "010e175" } +serde = { version = "1.0", features = ["derive"] } +serde_json = "1.0" +wit-bindgen = "0.24.0" + +[lib] +crate-type = ["cdylib"] + +[package.metadata.component] +package = "kinode:process" diff --git a/kinode/packages/terminal/kill/src/lib.rs b/kinode/packages/terminal/kill/src/lib.rs new file mode 100644 index 000000000..990d19cda --- /dev/null +++ b/kinode/packages/terminal/kill/src/lib.rs @@ -0,0 +1,48 @@ +use kinode_process_lib::kernel_types::{KernelCommand, KernelPrint, KernelResponse}; +use kinode_process_lib::{ + await_next_message_body, call_init, println, Address, Message, ProcessId, Request, +}; + +wit_bindgen::generate!({ + path: "target/wit", + world: "process-v0", +}); + +call_init!(init); +fn init(_our: Address) { + let Ok(args) = await_next_message_body() else { + println!("failed to get args"); + return; + }; + + let Ok(proc_id) = String::from_utf8(args) else { + println!("failed to stringify arguments"); + return; + }; + + let body = match proc_id.parse::() { + Ok(proc_id) => serde_json::to_vec(&KernelCommand::KillProcess(proc_id)).unwrap(), + Err(_) => { + println!("invalid process id"); + return; + } + }; + + let Ok(Message::Response { body, .. }) = Request::new() + .target(("our", "kernel", "distro", "sys")) + .body(body) + .send_and_await_response(60) + .unwrap() + else { + println!("failed to get response from kernel"); + return; + }; + let Ok(KernelResponse::KilledProcess(proc_id)) = + serde_json::from_slice::(&body) + else { + println!("failed to parse kernel response"); + return; + }; + + println!("killed process {}", proc_id); +} diff --git a/kinode/packages/terminal/net_diagnostics/src/lib.rs b/kinode/packages/terminal/net_diagnostics/src/lib.rs index 212c0ae47..718f1cf38 100644 --- a/kinode/packages/terminal/net_diagnostics/src/lib.rs +++ b/kinode/packages/terminal/net_diagnostics/src/lib.rs @@ -9,7 +9,7 @@ call_init!(init); fn init(_our: Address) { let Ok(Ok(Message::Response { body, .. })) = Request::to(("our", "net", "distro", "sys")) .body(rmp_serde::to_vec(&net::NetAction::GetDiagnostics).unwrap()) - .send_and_await_response(5) + .send_and_await_response(60) else { println!("failed to get diagnostics from networking module"); return; @@ -18,5 +18,5 @@ fn init(_our: Address) { println!("got malformed response from networking module"); return; }; - println!("{printout}"); + println!("\r\n{printout}"); } diff --git a/kinode/packages/terminal/pkg/scripts.json b/kinode/packages/terminal/pkg/scripts.json index f8f4dfaf1..bf67c916a 100644 --- a/kinode/packages/terminal/pkg/scripts.json +++ b/kinode/packages/terminal/pkg/scripts.json @@ -9,14 +9,6 @@ "grant_capabilities": [], "wit_version": 0 }, - "echo.wasm": { - "root": false, - "public": false, - "request_networking": false, - "request_capabilities": [], - "grant_capabilities": [], - "wit_version": 0 - }, "cat.wasm": { "root": false, "public": false, @@ -33,6 +25,14 @@ "grant_capabilities": [], "wit_version": 0 }, + "echo.wasm": { + "root": false, + "public": false, + "request_networking": false, + "request_capabilities": [], + "grant_capabilities": [], + "wit_version": 0 + }, "hi.wasm": { "root": false, "public": false, @@ -45,6 +45,23 @@ ], "wit_version": 0 }, + "kfetch.wasm": { + "root": true, + "public": false, + "request_networking": false, + "grant_capabilities": [ + "eth:distro:sys", + "kernel:distro:sys", + "net:distro:sys" + ], + "wit_version": 0 + }, + "kill.wasm": { + "root": true, + "public": false, + "request_networking": false, + "wit_version": 0 + }, "m.wasm": { "root": true, "public": true, @@ -100,13 +117,9 @@ "wit_version": 0 }, "top.wasm": { - "root": false, + "root": true, "public": false, "request_networking": false, - "request_capabilities": [ - "kernel:distro:sys" - ], - "grant_capabilities": [], "wit_version": 0 } } \ No newline at end of file diff --git a/kinode/packages/terminal/terminal/src/lib.rs b/kinode/packages/terminal/terminal/src/lib.rs index c0252176f..fb876f046 100644 --- a/kinode/packages/terminal/terminal/src/lib.rs +++ b/kinode/packages/terminal/terminal/src/lib.rs @@ -71,6 +71,14 @@ fn init(our: Address) { "hi".to_string(), ProcessId::new(Some("hi"), "terminal", "sys"), ), + ( + "kill".to_string(), + ProcessId::new(Some("kill"), "terminal", "sys"), + ), + ( + "kfetch".to_string(), + ProcessId::new(Some("kfetch"), "terminal", "sys"), + ), ( "m".to_string(), ProcessId::new(Some("m"), "terminal", "sys"), diff --git a/kinode/packages/terminal/top/Cargo.toml b/kinode/packages/terminal/top/Cargo.toml index a7de56742..8a3818a6b 100644 --- a/kinode/packages/terminal/top/Cargo.toml +++ b/kinode/packages/terminal/top/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "top" -version = "0.1.0" +version = "0.2.0" edition = "2021" [features] @@ -8,7 +8,7 @@ simulation-mode = [] [dependencies] anyhow = "1.0" -kinode_process_lib = { git = "https://github.com/kinode-dao/process_lib", rev = "09dc9f9" } +kinode_process_lib = { git = "https://github.com/kinode-dao/process_lib", rev = "010e175" } serde = { version = "1.0", features = ["derive"] } serde_json = "1.0" wit-bindgen = "0.24.0" diff --git a/kinode/packages/terminal/top/src/lib.rs b/kinode/packages/terminal/top/src/lib.rs index 22932f8cb..7d5c66db4 100644 --- a/kinode/packages/terminal/top/src/lib.rs +++ b/kinode/packages/terminal/top/src/lib.rs @@ -1,6 +1,8 @@ -use kinode_process_lib::kernel_types::{KernelCommand, KernelPrint}; +use kinode_process_lib::kernel_types::{ + KernelCommand, KernelPrint, KernelPrintResponse, KernelResponse, PersistedProcess, +}; use kinode_process_lib::{ - await_next_message_body, call_init, println, Address, ProcessId, Request, + await_next_message_body, call_init, println, Address, Message, ProcessId, Request, }; wit_bindgen::generate!({ @@ -20,25 +22,76 @@ fn init(_our: Address) { return; }; - if proc_id.is_empty() { - let _ = Request::new() - .target(("our", "kernel", "distro", "sys")) - .body(serde_json::to_vec(&KernelCommand::Debug(KernelPrint::ProcessMap)).unwrap()) - .send(); - } else { - match proc_id.parse::() { - Ok(proc_id) => { - let _ = Request::new() - .target(("our", "kernel", "distro", "sys")) - .body( - serde_json::to_vec(&KernelCommand::Debug(KernelPrint::Process(proc_id))) - .unwrap(), - ) - .send(); + let Ok(Message::Response { body, .. }) = Request::new() + .target(("our", "kernel", "distro", "sys")) + .body(if proc_id.is_empty() { + serde_json::to_vec(&KernelCommand::Debug(KernelPrint::ProcessMap)).unwrap() + } else { + match proc_id.parse::() { + Ok(proc_id) => { + serde_json::to_vec(&KernelCommand::Debug(KernelPrint::Process(proc_id))) + .unwrap() + } + Err(_) => { + println!("invalid process id"); + return; + } + } + }) + .send_and_await_response(60) + .unwrap() + else { + println!("failed to get response from kernel"); + return; + }; + let Ok(KernelResponse::Debug(kernel_print_response)) = + serde_json::from_slice::(&body) + else { + println!("failed to parse kernel response"); + return; + }; + + match kernel_print_response { + KernelPrintResponse::ProcessMap(process_map) => { + let len = process_map.len(); + let printout = process_map + .iter() + .map(|(proc_id, process)| print_process(proc_id, process)) + .collect::>() + .join("\r\n"); + println!("\r\n{printout}\r\n\r\ntop: {len} running processes"); + } + KernelPrintResponse::Process(process) => match process { + None => { + println!("process {} not running", proc_id); + return; } - Err(_) => { - println!("invalid process id"); + Some(process) => { + println!("{}", print_process(&proc_id.parse().unwrap(), &process)); } + }, + KernelPrintResponse::HasCap(_) => { + println!("kernel gave wrong kind of response"); } } } + +fn print_process(id: &ProcessId, process: &PersistedProcess) -> String { + format!( + "{}:\r\n {}\r\n wit: {}\r\n on-exit: {:?}\r\n public: {}\r\n capabilities: {:?}", + id, + if process.wasm_bytes_handle.is_empty() { + "(runtime)" + } else { + &process.wasm_bytes_handle + }, + process.wit_version.unwrap_or_default(), + process.on_exit, + process.public, + process + .capabilities + .iter() + .map(|c| c.to_string()) + .collect::>() + ) +} diff --git a/kinode/src/kernel/mod.rs b/kinode/src/kernel/mod.rs index ad76c2f16..c7bc759ba 100644 --- a/kinode/src/kernel/mod.rs +++ b/kinode/src/kernel/mod.rs @@ -520,52 +520,47 @@ async fn handle_kernel_request( .await .expect("event loop: fatal: sender died"); } - t::KernelCommand::Debug(kind) => match kind { - t::KernelPrint::ProcessMap => { - let mut process_map_string = "".to_string(); - for (id, process) in &mut *process_map { - process_map_string.push_str(&format!("{}: {}\r\n", id, process)); - } - let _ = send_to_terminal - .send(t::Printout { - verbosity: 0, - content: format!("kernel process map:\r\n{process_map_string}\r\nfound {} running processes", process_map.len()), - }) - .await; - } - t::KernelPrint::Process(process_id) => { - let Some(proc) = process_map.get(&process_id) else { - let _ = send_to_terminal - .send(t::Printout { - verbosity: 0, - content: format!("kernel: no such running process {}", process_id), - }) - .await; - return None; - }; - let _ = send_to_terminal - .send(t::Printout { - verbosity: 0, - content: format!("process info for {process_id}:\r\n{proc}",), - }) - .await; - } - t::KernelPrint::HasCap { on, cap } => { - let _ = send_to_terminal - .send(t::Printout { - verbosity: 0, - content: format!( - "process {} has cap:\r\n{}", - on, - process_map - .get(&on) - .map(|p| p.capabilities.contains_key(&cap)) - .unwrap_or(false) - ), - }) - .await; - } - }, + t::KernelCommand::Debug(kind) => { + let response = match kind { + t::KernelPrint::ProcessMap => t::KernelPrintResponse::ProcessMap( + process_map + .clone() + .into_iter() + .map(|(k, v)| (k, v.into())) + .collect(), + ), + t::KernelPrint::Process(process_id) => t::KernelPrintResponse::Process( + process_map.get(&process_id).cloned().map(|p| p.into()), + ), + t::KernelPrint::HasCap { on, cap } => t::KernelPrintResponse::HasCap( + process_map + .get(&on) + .map(|p| p.capabilities.contains_key(&cap)), + ), + }; + send_to_loop + .send(t::KernelMessage { + id: km.id, + source: t::Address { + node: our_name.clone(), + process: KERNEL_PROCESS_ID.clone(), + }, + target: km.rsvp.unwrap_or(km.source), + rsvp: None, + message: t::Message::Response(( + t::Response { + inherit: false, + body: serde_json::to_vec(&t::KernelResponse::Debug(response)).unwrap(), + metadata: None, + capabilities: vec![], + }, + None, + )), + lazy_load_blob: None, + }) + .await + .expect("event loop: fatal: sender died"); + } } None } diff --git a/kinode/src/main.rs b/kinode/src/main.rs index 982313d25..0b4fd4774 100644 --- a/kinode/src/main.rs +++ b/kinode/src/main.rs @@ -395,7 +395,7 @@ async fn main() { match res { Ok(_) => "graceful exit".into(), Err(e) => format!( - "uh oh, a kernel process crashed -- this should never happen: {e:?}" + "runtime crash: {e:?}" ), } diff --git a/lib/src/core.rs b/lib/src/core.rs index f8f3d1fb7..f65607740 100644 --- a/lib/src/core.rs +++ b/lib/src/core.rs @@ -43,7 +43,7 @@ pub struct ProcessId { impl Serialize for ProcessId { fn serialize(&self, serializer: S) -> Result where - S: serde::ser::Serializer, + S: serde::Serializer, { format!("{}", self).serialize(serializer) } @@ -52,7 +52,7 @@ impl Serialize for ProcessId { impl<'a> Deserialize<'a> for ProcessId { fn deserialize(deserializer: D) -> Result where - D: serde::de::Deserializer<'a>, + D: serde::Deserializer<'a>, { let s = String::deserialize(deserializer)?; s.parse().map_err(serde::de::Error::custom) @@ -1237,6 +1237,14 @@ pub enum KernelResponse { StartedProcess, RunProcessError, KilledProcess(ProcessId), + Debug(KernelPrintResponse), +} + +#[derive(Debug, Serialize, Deserialize)] +pub enum KernelPrintResponse { + ProcessMap(UserspaceProcessMap), + Process(Option), + HasCap(Option), } #[derive(Debug)] @@ -1282,6 +1290,7 @@ pub enum CapMessage { pub type ReverseCapIndex = HashMap>>; pub type ProcessMap = HashMap; +pub type UserspaceProcessMap = HashMap; #[derive(Clone, Debug, Serialize, Deserialize)] pub struct PersistedProcess { @@ -1292,29 +1301,24 @@ pub struct PersistedProcess { pub public: bool, // marks if a process allows messages from any process } -impl std::fmt::Display for PersistedProcess { - fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result { - write!( - f, - "Process {{\n wasm_bytes_handle: {},\n wit_version: {:?},\n on_exit: {:?},\n public: {}\n capabilities: {}\n}}", - { - if &self.wasm_bytes_handle == "" { - "(none, this is a runtime process)" - } else { - &self.wasm_bytes_handle - } - }, - self.wit_version, - self.on_exit, - self.public, - { - let mut caps_string = "[".to_string(); - for cap in self.capabilities.keys() { - caps_string += &format!("\n {}", cap.to_string()); - } - caps_string + "\n ]" - }, - ) +#[derive(Clone, Debug, Serialize, Deserialize)] +pub struct UserspacePersistedProcess { + pub wasm_bytes_handle: String, + pub wit_version: Option, + pub on_exit: OnExit, + pub capabilities: HashSet, + pub public: bool, +} + +impl From for UserspacePersistedProcess { + fn from(p: PersistedProcess) -> Self { + UserspacePersistedProcess { + wasm_bytes_handle: p.wasm_bytes_handle, + wit_version: p.wit_version, + on_exit: p.on_exit, + capabilities: p.capabilities.into_keys().collect(), + public: p.public, + } } }