Files
frxd/src/main.rs
T

288 lines
8.2 KiB
Rust

use std::path::PathBuf;
use anyhow::{Result, bail};
use clap::{Parser, Subcommand, ValueEnum};
use frxd::config::Config;
use frxd::crypto::Keypair;
use frxd::message::{EXPOSURE_FULL, EXPOSURE_METADATA};
use frxd::{commands, node, relay};
#[derive(Parser)]
#[command(
name = "frxd",
version,
about = "FRX member node — querier, responder, and local index (Draft 0.4)"
)]
struct Cli {
#[arg(long, global = true, default_value = "frxd.toml")]
config: PathBuf,
#[command(subcommand)]
command: Command,
}
#[derive(Subcommand)]
enum Command {
Init {
#[arg(long, default_value = "member")]
name: String,
#[arg(long, default_value = "127.0.0.1:7701")]
listen: String,
#[arg(long, default_value = "http://127.0.0.1:7700")]
relay: String,
#[arg(long, default_value = "./frx-data")]
data_dir: String,
#[arg(long)]
force: bool,
#[arg(long)]
id: Option<String>,
#[arg(long)]
registry: Option<String>,
#[arg(long)]
ma_key: Option<String>,
},
Add {
path: PathBuf,
#[arg(long)]
name: Option<String>,
#[arg(long)]
shared: bool,
#[arg(long, value_enum, default_value_t = Exposure::Full)]
exposure: Exposure,
},
Reindex,
Search {
text: String,
#[arg(long, default_value_t = 10)]
limit: usize,
},
Query {
text: String,
#[arg(long)]
max_results: Option<usize>,
#[arg(long)]
timeout_ms: Option<u64>,
#[arg(long)]
local_only: bool,
},
Serve,
Relay {
#[arg(long, default_value = "127.0.0.1:7700")]
listen: String,
#[arg(long, default_value_t = relay::DEFAULT_CAPACITY)]
capacity: usize,
},
Status,
Member {
#[command(subcommand)]
command: MemberCommand,
},
Key {
#[command(subcommand)]
command: KeyCommand,
},
Registry {
#[arg(long, default_value = "./frx-registry")]
dir: PathBuf,
#[command(subcommand)]
command: RegistryCommand,
},
Aggregates {
#[arg(long)]
from: Option<String>,
#[arg(long)]
period: Option<String>,
#[arg(long)]
timeout_ms: Option<u64>,
},
}
#[derive(Subcommand)]
enum MemberCommand {
Add {
name: String,
pubkey: String,
#[arg(long, default_value = "source")]
class: String,
#[arg(long = "previous")]
previous: Vec<String>,
},
Remove {
name: String,
},
List,
}
#[derive(Subcommand)]
enum KeyCommand {
Show,
Rotate,
}
#[derive(Subcommand)]
enum RegistryCommand {
Init,
Add {
id: String,
pubkey: String,
#[arg(long, default_value = "source")]
class: String,
#[arg(long)]
not_before: Option<u64>,
#[arg(long)]
not_after: Option<u64>,
},
AddKey {
id: String,
pubkey: String,
#[arg(long)]
not_before: Option<u64>,
#[arg(long)]
not_after: Option<u64>,
},
RevokeKey {
id: String,
pubkey: String,
},
Remove {
id: String,
},
List,
Show,
Serve {
#[arg(long, default_value = "127.0.0.1:7800")]
listen: String,
},
}
#[derive(Clone, Copy, ValueEnum)]
enum Exposure {
Metadata,
Full,
}
#[tokio::main]
async fn main() -> Result<()> {
let cli = Cli::parse();
match cli.command {
Command::Init {
name,
listen,
relay,
data_dir,
force,
id,
registry,
ma_key,
} => {
if cli.config.exists() && !force {
bail!(
"config {} already exists (use --force to overwrite)",
cli.config.display()
);
}
if registry.is_some() && ma_key.is_none() {
bail!("--ma-key is required with --registry");
}
let mut config = Config::new(&name, &listen, &relay, &data_dir);
config.node.id = id;
config.node.registry = registry;
config.node.ma_key = ma_key;
if config.node.registry.is_none() {
config.node.dev_bootstrap = true;
println!(
"warning: no registry configured; development open bootstrap (any valid key is accepted; do not deploy)"
);
}
let key = Keypair::generate();
config.save_key(&key)?;
config.save(&cli.config)?;
println!("wrote config {}", cli.config.display());
println!("wrote key {}", config.key_path().display());
println!("pubkey {}", key.public_hex());
println!("data dir {}", config.data_dir().display());
}
Command::Add {
path,
name,
shared,
exposure,
} => {
let exposure = match exposure {
Exposure::Metadata => EXPOSURE_METADATA,
Exposure::Full => EXPOSURE_FULL,
};
commands::add(&cli.config, &path, name, shared, exposure)?;
}
Command::Reindex => commands::reindex(&cli.config)?,
Command::Search { text, limit } => commands::search(&cli.config, &text, limit).await?,
Command::Query {
text,
max_results,
timeout_ms,
local_only,
} => {
commands::query(&cli.config, &text, max_results, timeout_ms, !local_only).await?;
}
Command::Serve => {
let config = Config::load(&cli.config)?;
let handle = node::Node::start(config).await?;
println!("frxd listening on http://{}", handle.addr);
println!("pubkey {}", handle.pubkey);
println!("control API POST http://{}/v1/local/query", handle.addr);
tokio::signal::ctrl_c().await?;
}
Command::Relay { listen, capacity } => {
let (listener, addr) = relay::bind(&listen).await?;
println!("relay listening on http://{addr} (capacity {capacity})");
relay::run(listener, capacity).await?;
}
Command::Status => commands::status(&cli.config).await?,
Command::Member { command } => match command {
MemberCommand::Add {
name,
pubkey,
class,
previous,
} => commands::member_add(&cli.config, &name, &pubkey, &class, &previous)?,
MemberCommand::Remove { name } => commands::member_remove(&cli.config, &name)?,
MemberCommand::List => commands::member_list(&cli.config)?,
},
Command::Key { command } => match command {
KeyCommand::Show => commands::key_show(&cli.config)?,
KeyCommand::Rotate => commands::key_rotate(&cli.config)?,
},
Command::Registry { dir, command } => match command {
RegistryCommand::Init => commands::registry_init(&dir)?,
RegistryCommand::Add {
id,
pubkey,
class,
not_before,
not_after,
} => commands::registry_add(&dir, &id, &pubkey, &class, not_before, not_after)?,
RegistryCommand::AddKey {
id,
pubkey,
not_before,
not_after,
} => commands::registry_add_key(&dir, &id, &pubkey, not_before, not_after)?,
RegistryCommand::RevokeKey { id, pubkey } => {
commands::registry_revoke_key(&dir, &id, &pubkey)?
}
RegistryCommand::Remove { id } => commands::registry_remove(&dir, &id)?,
RegistryCommand::List => commands::registry_list(&dir)?,
RegistryCommand::Show => commands::registry_show(&dir)?,
RegistryCommand::Serve { listen } => commands::registry_serve(&dir, &listen).await?,
},
Command::Aggregates {
from,
period,
timeout_ms,
} => {
commands::aggregates(&cli.config, from.as_deref(), period.as_deref(), timeout_ms)
.await?
}
}
Ok(())
}