365 lines
10 KiB
Rust
365 lines
10 KiB
Rust
|
|
use anyhow::{Context, Result, bail};
|
||
|
|
use clap::{Args, Parser, Subcommand};
|
||
|
|
use geth_config::GethPaths;
|
||
|
|
use geth_control::{ControlRequest, ControlResponse};
|
||
|
|
use std::path::PathBuf;
|
||
|
|
|
||
|
|
#[derive(Debug, Parser)]
|
||
|
|
#[command(name = "geth", about = "Personal local-first Iroh mesh runtime")]
|
||
|
|
pub struct Cli {
|
||
|
|
#[arg(long, global = true)]
|
||
|
|
pub json: bool,
|
||
|
|
#[arg(long, global = true)]
|
||
|
|
pub jsonl: bool,
|
||
|
|
#[command(subcommand)]
|
||
|
|
pub command: Command,
|
||
|
|
}
|
||
|
|
|
||
|
|
#[derive(Debug, Subcommand)]
|
||
|
|
pub enum Command {
|
||
|
|
Init,
|
||
|
|
Daemon {
|
||
|
|
#[command(subcommand)]
|
||
|
|
command: DaemonCommand,
|
||
|
|
},
|
||
|
|
Status,
|
||
|
|
Node {
|
||
|
|
#[command(subcommand)]
|
||
|
|
command: NodeCommand,
|
||
|
|
},
|
||
|
|
Resource {
|
||
|
|
#[command(subcommand)]
|
||
|
|
command: ResourceCommand,
|
||
|
|
},
|
||
|
|
Keychain {
|
||
|
|
#[command(subcommand)]
|
||
|
|
command: KeychainCommand,
|
||
|
|
},
|
||
|
|
Auth {
|
||
|
|
#[command(subcommand)]
|
||
|
|
command: AuthCommand,
|
||
|
|
},
|
||
|
|
Secret {
|
||
|
|
#[command(subcommand)]
|
||
|
|
command: SecretCommand,
|
||
|
|
},
|
||
|
|
Cas {
|
||
|
|
#[command(subcommand)]
|
||
|
|
command: CasCommand,
|
||
|
|
},
|
||
|
|
Kv {
|
||
|
|
#[command(subcommand)]
|
||
|
|
command: KvCommand,
|
||
|
|
},
|
||
|
|
Pubsub {
|
||
|
|
#[command(subcommand)]
|
||
|
|
command: PubsubCommand,
|
||
|
|
},
|
||
|
|
Pipe {
|
||
|
|
#[command(subcommand)]
|
||
|
|
command: PipeCommand,
|
||
|
|
},
|
||
|
|
Db {
|
||
|
|
#[command(subcommand)]
|
||
|
|
command: DbCommand,
|
||
|
|
},
|
||
|
|
Document {
|
||
|
|
#[command(subcommand)]
|
||
|
|
command: DocumentCommand,
|
||
|
|
},
|
||
|
|
Ssh {
|
||
|
|
#[command(subcommand)]
|
||
|
|
command: SshCommand,
|
||
|
|
},
|
||
|
|
}
|
||
|
|
|
||
|
|
#[derive(Debug, Subcommand)]
|
||
|
|
pub enum DaemonCommand {
|
||
|
|
Run,
|
||
|
|
}
|
||
|
|
|
||
|
|
#[derive(Debug, Subcommand)]
|
||
|
|
pub enum NodeCommand {
|
||
|
|
Id,
|
||
|
|
Status,
|
||
|
|
}
|
||
|
|
|
||
|
|
#[derive(Debug, Subcommand)]
|
||
|
|
pub enum ResourceCommand {
|
||
|
|
List,
|
||
|
|
Create { kind: String, name: String },
|
||
|
|
}
|
||
|
|
|
||
|
|
#[derive(Debug, Subcommand)]
|
||
|
|
pub enum KeychainCommand {
|
||
|
|
Init,
|
||
|
|
Status,
|
||
|
|
}
|
||
|
|
|
||
|
|
#[derive(Debug, Subcommand)]
|
||
|
|
pub enum AuthCommand {
|
||
|
|
Explain {
|
||
|
|
subject: String,
|
||
|
|
resource: String,
|
||
|
|
capability: String,
|
||
|
|
},
|
||
|
|
}
|
||
|
|
|
||
|
|
#[derive(Debug, Subcommand)]
|
||
|
|
pub enum SecretCommand {
|
||
|
|
Status,
|
||
|
|
}
|
||
|
|
|
||
|
|
#[derive(Debug, Subcommand)]
|
||
|
|
pub enum CasCommand {
|
||
|
|
Add {
|
||
|
|
path: PathBuf,
|
||
|
|
},
|
||
|
|
Get {
|
||
|
|
hash: String,
|
||
|
|
#[arg(long)]
|
||
|
|
out: PathBuf,
|
||
|
|
},
|
||
|
|
Hash {
|
||
|
|
path: PathBuf,
|
||
|
|
},
|
||
|
|
Has {
|
||
|
|
hash: String,
|
||
|
|
},
|
||
|
|
List,
|
||
|
|
}
|
||
|
|
|
||
|
|
#[derive(Debug, Subcommand)]
|
||
|
|
pub enum KvCommand {
|
||
|
|
Create {
|
||
|
|
name: String,
|
||
|
|
},
|
||
|
|
Set {
|
||
|
|
name: String,
|
||
|
|
key: String,
|
||
|
|
value: String,
|
||
|
|
},
|
||
|
|
Get {
|
||
|
|
name: String,
|
||
|
|
key: String,
|
||
|
|
},
|
||
|
|
}
|
||
|
|
|
||
|
|
#[derive(Debug, Subcommand)]
|
||
|
|
pub enum PubsubCommand {
|
||
|
|
Pub { topic: String, message: String },
|
||
|
|
Sub { topic: String },
|
||
|
|
}
|
||
|
|
|
||
|
|
#[derive(Debug, Subcommand)]
|
||
|
|
pub enum PipeCommand {
|
||
|
|
Listen { name: String },
|
||
|
|
Connect { target: String },
|
||
|
|
}
|
||
|
|
|
||
|
|
#[derive(Debug, Subcommand)]
|
||
|
|
pub enum DbCommand {
|
||
|
|
Add { name: String, path: PathBuf },
|
||
|
|
Status { name: String },
|
||
|
|
}
|
||
|
|
|
||
|
|
#[derive(Debug, Subcommand)]
|
||
|
|
pub enum DocumentCommand {
|
||
|
|
Create { name: String },
|
||
|
|
Status { name: String },
|
||
|
|
}
|
||
|
|
|
||
|
|
#[derive(Debug, Subcommand)]
|
||
|
|
pub enum SshCommand {
|
||
|
|
Proxy { node: String },
|
||
|
|
}
|
||
|
|
|
||
|
|
#[derive(Debug, Args)]
|
||
|
|
pub struct EmptyArgs {}
|
||
|
|
|
||
|
|
pub async fn run() -> Result<()> {
|
||
|
|
let cli = Cli::parse();
|
||
|
|
let paths = GethPaths::resolve().context("resolve geth paths")?;
|
||
|
|
match cli.command {
|
||
|
|
Command::Init => {
|
||
|
|
let node = geth_node::init_node(&paths).context("initialize geth node")?;
|
||
|
|
println!("initialized geth home: {}", node.paths.home().display());
|
||
|
|
println!("agent: {}", node.agent_id);
|
||
|
|
println!("node: {}", node.node_id);
|
||
|
|
}
|
||
|
|
Command::Daemon {
|
||
|
|
command: DaemonCommand::Run,
|
||
|
|
} => {
|
||
|
|
geth_node::run_daemon(paths)
|
||
|
|
.await
|
||
|
|
.context("run geth daemon")?;
|
||
|
|
}
|
||
|
|
command => {
|
||
|
|
let request = request_for_command(command)?;
|
||
|
|
let response = geth_node::send_control(&paths, request)
|
||
|
|
.await
|
||
|
|
.with_context(|| {
|
||
|
|
format!("connect to daemon at {}", paths.socket_path().display())
|
||
|
|
})?;
|
||
|
|
print_response(response, cli.json || cli.jsonl)?;
|
||
|
|
}
|
||
|
|
}
|
||
|
|
Ok(())
|
||
|
|
}
|
||
|
|
|
||
|
|
fn request_for_command(command: Command) -> Result<ControlRequest> {
|
||
|
|
Ok(match command {
|
||
|
|
Command::Status => ControlRequest::Status,
|
||
|
|
Command::Node {
|
||
|
|
command: NodeCommand::Id,
|
||
|
|
} => ControlRequest::NodeId,
|
||
|
|
Command::Node {
|
||
|
|
command: NodeCommand::Status,
|
||
|
|
} => ControlRequest::Status,
|
||
|
|
Command::Resource {
|
||
|
|
command: ResourceCommand::List,
|
||
|
|
} => ControlRequest::ResourceList,
|
||
|
|
Command::Resource {
|
||
|
|
command: ResourceCommand::Create { kind, name },
|
||
|
|
} => ControlRequest::ResourceCreate { kind, name },
|
||
|
|
Command::Keychain {
|
||
|
|
command: KeychainCommand::Init,
|
||
|
|
} => ControlRequest::ModuleStub {
|
||
|
|
module: "keychain".to_owned(),
|
||
|
|
command: "init".to_owned(),
|
||
|
|
},
|
||
|
|
Command::Keychain {
|
||
|
|
command: KeychainCommand::Status,
|
||
|
|
} => ControlRequest::KeychainStatus,
|
||
|
|
Command::Auth {
|
||
|
|
command:
|
||
|
|
AuthCommand::Explain {
|
||
|
|
subject,
|
||
|
|
resource,
|
||
|
|
capability,
|
||
|
|
},
|
||
|
|
} => ControlRequest::AuthExplain {
|
||
|
|
subject,
|
||
|
|
resource,
|
||
|
|
capability,
|
||
|
|
},
|
||
|
|
Command::Secret { command } => ControlRequest::ModuleStub {
|
||
|
|
module: "secret".to_owned(),
|
||
|
|
command: format!("{command:?}"),
|
||
|
|
},
|
||
|
|
Command::Cas { command } => match command {
|
||
|
|
CasCommand::Add { path } => ControlRequest::CasAdd { path },
|
||
|
|
CasCommand::Get { hash, out } => ControlRequest::CasGet {
|
||
|
|
hash: hash.into(),
|
||
|
|
out,
|
||
|
|
},
|
||
|
|
CasCommand::Hash { path } => ControlRequest::CasHash { path },
|
||
|
|
CasCommand::Has { hash } => ControlRequest::CasHas { hash: hash.into() },
|
||
|
|
CasCommand::List => ControlRequest::CasList,
|
||
|
|
},
|
||
|
|
Command::Kv { command } => ControlRequest::ModuleStub {
|
||
|
|
module: "kv".to_owned(),
|
||
|
|
command: format!("{command:?}"),
|
||
|
|
},
|
||
|
|
Command::Pubsub { command } => ControlRequest::ModuleStub {
|
||
|
|
module: "pubsub".to_owned(),
|
||
|
|
command: format!("{command:?}"),
|
||
|
|
},
|
||
|
|
Command::Pipe { command } => ControlRequest::ModuleStub {
|
||
|
|
module: "pipe".to_owned(),
|
||
|
|
command: format!("{command:?}"),
|
||
|
|
},
|
||
|
|
Command::Db { command } => ControlRequest::ModuleStub {
|
||
|
|
module: "db".to_owned(),
|
||
|
|
command: format!("{command:?}"),
|
||
|
|
},
|
||
|
|
Command::Document { command } => ControlRequest::ModuleStub {
|
||
|
|
module: "document".to_owned(),
|
||
|
|
command: format!("{command:?}"),
|
||
|
|
},
|
||
|
|
Command::Ssh { command } => ControlRequest::ModuleStub {
|
||
|
|
module: "ssh-proxy".to_owned(),
|
||
|
|
command: format!("{command:?}"),
|
||
|
|
},
|
||
|
|
Command::Init | Command::Daemon { .. } => bail!("command is handled directly"),
|
||
|
|
})
|
||
|
|
}
|
||
|
|
|
||
|
|
fn print_response(response: ControlResponse, json: bool) -> Result<()> {
|
||
|
|
if json {
|
||
|
|
println!("{}", serde_json::to_string_pretty(&response)?);
|
||
|
|
return Ok(());
|
||
|
|
}
|
||
|
|
match response {
|
||
|
|
ControlResponse::Status(status) => {
|
||
|
|
println!("geth daemon: running");
|
||
|
|
println!("home: {}", status.home.display());
|
||
|
|
println!("socket: {}", status.socket.display());
|
||
|
|
println!("agent: {}", status.agent_id);
|
||
|
|
println!("node: {}", status.node_id);
|
||
|
|
println!("iroh: {}", status.iroh);
|
||
|
|
}
|
||
|
|
ControlResponse::NodeId(node) => {
|
||
|
|
println!("agent: {}", node.agent_id);
|
||
|
|
println!("node: {}", node.node_id);
|
||
|
|
println!(
|
||
|
|
"endpoint: {}",
|
||
|
|
node.endpoint_id
|
||
|
|
.as_deref()
|
||
|
|
.unwrap_or("not started in bootstrap")
|
||
|
|
);
|
||
|
|
}
|
||
|
|
ControlResponse::ResourceList { resources } => {
|
||
|
|
if resources.is_empty() {
|
||
|
|
println!("no resources");
|
||
|
|
} else {
|
||
|
|
for resource in resources {
|
||
|
|
println!("{}\t{}\t{}", resource.kind, resource.name, resource.id);
|
||
|
|
}
|
||
|
|
}
|
||
|
|
}
|
||
|
|
ControlResponse::ResourceCreated { resource } => {
|
||
|
|
println!(
|
||
|
|
"created resource: {} {} ({})",
|
||
|
|
resource.kind, resource.name, resource.id
|
||
|
|
);
|
||
|
|
}
|
||
|
|
ControlResponse::CasAdded { hash, size_bytes } => {
|
||
|
|
println!("{hash} {size_bytes} bytes");
|
||
|
|
}
|
||
|
|
ControlResponse::CasGot {
|
||
|
|
hash,
|
||
|
|
out,
|
||
|
|
size_bytes,
|
||
|
|
} => {
|
||
|
|
println!("wrote {hash} to {} ({size_bytes} bytes)", out.display());
|
||
|
|
}
|
||
|
|
ControlResponse::CasHash { hash } => println!("{hash}"),
|
||
|
|
ControlResponse::CasHas { hash, present } => println!("{hash}: {present}"),
|
||
|
|
ControlResponse::CasList { blobs } => {
|
||
|
|
for blob in blobs {
|
||
|
|
println!("{}\t{} bytes", blob.hash, blob.size_bytes);
|
||
|
|
}
|
||
|
|
}
|
||
|
|
ControlResponse::KeychainStatus(status) => {
|
||
|
|
println!("initialized: {}", status.initialized);
|
||
|
|
println!("admin_keys: {}", status.admin_keys);
|
||
|
|
println!("users: {}", status.users);
|
||
|
|
println!("devices: {}", status.devices);
|
||
|
|
println!("nodes: {}", status.nodes);
|
||
|
|
}
|
||
|
|
ControlResponse::AuthExplain(explain) => {
|
||
|
|
println!("allowed: {}", explain.allowed);
|
||
|
|
println!("subject: {}", explain.subject);
|
||
|
|
println!("resource: {}", explain.resource);
|
||
|
|
println!("capability: {}", explain.capability);
|
||
|
|
println!("reason: {}", explain.reason);
|
||
|
|
println!("evaluated_ops: {}", explain.evaluated_ops);
|
||
|
|
}
|
||
|
|
ControlResponse::NotImplemented { module, command } => {
|
||
|
|
println!("{module} {command}: not implemented yet");
|
||
|
|
}
|
||
|
|
ControlResponse::Error { message } => bail!(message),
|
||
|
|
}
|
||
|
|
Ok(())
|
||
|
|
}
|