Bootstrap geth Rust workspace

This commit is contained in:
Eric Wendland 2026-05-15 15:08:20 +02:00
commit 26f81ff1ef
73 changed files with 4835 additions and 0 deletions

365
crates/geth-cli/src/lib.rs Normal file
View file

@ -0,0 +1,365 @@
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(())
}