pub mod config; pub mod get_peer_id; pub mod keys; pub mod participate; use std::{ net::{Ipv4Addr, SocketAddr}, path::{Path, PathBuf}, }; use clap::{Parser, Subcommand}; use color_eyre::eyre::Result; use lb_tui_zone::cli::NodeKeyArgs; use lb_utils::yaml::{OnUnknownKeys, deserialize_value_at_path}; use libp2p::Multiaddr; use crate::{ cli::{ config::migrate_0_1_2, keys::{AddKeyArgs, GenerateKeyArgs, RemoveKeyArgs}, }, config::{ ApiArgs, BlendArgs, CryptarchiaArgs, DeploymentArgs, DeploymentSettings, LogArgs, NetworkArgs, RunConfig, SdpArgs, StateArgs, UserConfig, api::serde::AxumBackendSettings, blend::serde::core::BackendConfig as BlendCoreConfig, network::serde::SwarmConfig, update_api, update_blend, update_cryptarchia, update_network, update_sdp, update_state, update_tracing, }, }; fn long_version() -> String { let head_commit_hash = env!("HEAD_COMMIT_HASH"); let head_tag_name = env!("HEAD_TAG_NAME"); let pkg_version = env!("PKG_VERSION"); let target = env!("TARGET"); let profile = env!("PROFILE"); let rustc_version = env!("RUSTC_VERSION"); let commit_line = match (head_commit_hash, head_tag_name) { (commit_hash, tag_name) if !commit_hash.is_empty() && !tag_name.is_empty() => { format!("commit: {commit_hash} (tag {tag_name})") } (commit_hash, _) if !commit_hash.is_empty() => { format!("commit: {commit_hash}") } _ => "commit: unknown".to_owned(), }; format!( "\ {pkg_version} {commit_line} target: {target} profile: {profile} rustc: {rustc_version}" ) } #[derive(Parser, Debug)] #[command(author, version, long_version = long_version(), about, long_about = None, args_conflicts_with_subcommands = true, subcommand_negates_reqs = true)] pub struct CliArgs { #[command(subcommand)] pub command: Option, /// Path for a yaml-encoded network config file config: Option, /// Dry-run flag. If active, the binary will try to deserialize the config /// file and then exit. #[clap(long = "check-config", action)] check_config_only: bool, /// Overrides log config. #[clap(flatten)] log: LogArgs, /// Overrides network config. #[clap(flatten)] network: NetworkArgs, /// Overrides blend config. #[clap(flatten)] blend: BlendArgs, /// Overrides cryptarchia config. #[clap(flatten)] cryptarchia: CryptarchiaArgs, /// Overrides sdp config. #[clap(flatten)] sdp: SdpArgs, /// Overrides http config. #[clap(flatten)] api: ApiArgs, #[clap(flatten)] deployment: DeploymentArgs, #[clap(flatten)] state: StateArgs, } impl CliArgs { #[must_use] pub fn user_config_path(&self) -> &Path { self.config .as_deref() .expect("config path is required when not using a subcommand") } #[must_use] pub const fn deployment_config_path(&self) -> &Option { &self.deployment.custom_deployment_path } #[must_use] pub const fn dry_run(&self) -> bool { self.check_config_only } } #[derive(Subcommand, Debug)] pub enum Command { /// Initialize a new user config with generated keys InitConfig(Box), /// Update existing user config with keys from keystore UpdateConfig(Box), /// Migrates a new user config with generated keys MigrateConfig(Box), /// Migrates 0.1.2 config to a new user config #[command(name = "migrate-from-0.1.2")] Migrate0_1_2(Box), /// Generate a new key of type. GenerateKey(Box), /// Add a key of type to a keystore. AddKey(Box), /// Remove a key with title from a keystore. RemoveKey(Box), /// Publish text inscriptions as zone blocks Inscribe(NodeKeyArgs), /// Generate stakeholder.yaml and provider.yaml from a user config Participate(ParticipateArgs), /// Print the libp2p `PeerId` derived from the node key in a user config GetPeerId(GetPeerIdArgs), } #[derive(Parser, Debug)] pub struct InitArgs { /// Output file path for the generated config. #[clap(long = "output", short = 'o', default_value = "user_config.yaml")] pub output: PathBuf, /// Path for the generated keystore file. /// Defaults to 'keystore.yaml' in the same directory as --output. #[clap(long = "keystore", short = 'k')] pub keystore: Option, /// Overwrite existing user config and keystore. #[arg(long, default_value_t = false)] pub overwrite: bool, #[clap(flatten)] pub log: LogArgs, #[clap(flatten)] pub network: NetworkArgs, #[clap(flatten)] pub blend: BlendArgs, #[clap(flatten)] pub cryptarchia: CryptarchiaArgs, #[clap(flatten)] pub sdp: SdpArgs, #[clap(flatten)] pub api: ApiArgs, #[clap(flatten)] pub state: StateArgs, /// Name (or path, relative to the state folder) of the storage DB folder. #[clap(long = "storage-path")] pub storage_path: Option, } /// Set of arguments for use in c-bindings crate. #[derive(Debug)] pub struct EmbeddedInitArgs { /// Trusted peers to bootstrap from (multiaddr format). /// If `--skip-ibd` is not set, peers whose multiaddrs include a `PeerId` /// are also used as IBD peers. pub initial_peers: Vec, /// Output file path for the generated config pub output: PathBuf, /// Network listen port pub net_port: u16, /// Blend listen port pub blend_port: u16, /// HTTP API listen address pub http_addr: SocketAddr, /// External address for nodes with a known public IP (disables NAT /// traversal). Format: /ip4//udp//quic-v1 pub external_address: Option, pub state_path: Option, pub storage_path: Option, pub logs_path: Option, /// Disable Initial Block Download (IBD) by leaving the IBD peer list /// empty, regardless of any peers passed via `--initial-peers`/`-p`. pub skip_ibd: bool, /// Log filter directives to write into the generated config, e.g. /// `warn,logos_blockchain=debug,libp2p_gossipsub::behaviour=error`. pub log_filter: Option, /// Path for the generated KMS keys YAML file. /// Defaults to 'kms.yaml' in the same directory as --output. pub kms_file: Option, } impl From for InitArgs { fn from(args: EmbeddedInitArgs) -> Self { let mut init_args = Self { output: args.output.clone(), keystore: args.kms_file.clone(), ..Default::default() }; init_args.log.filter.clone_from(&args.log_filter); init_args.network.port = Some(args.net_port); init_args .network .external_address .clone_from(&args.external_address); init_args.network.initial_peers = Some(args.initial_peers.clone()); init_args.blend.blend_addr = Some(BlendCoreConfig::default_listening_address(args.blend_port)); init_args.cryptarchia.skip_ibd = args.skip_ibd; init_args.api.addr = Some(args.http_addr); init_args.state.path.clone_from(&args.state_path); init_args.storage_path.clone_from(&args.storage_path); init_args.log.directory.clone_from(&args.logs_path); init_args } } impl Default for EmbeddedInitArgs { fn default() -> Self { Self { initial_peers: Vec::new(), output: PathBuf::from("user_config.yaml"), net_port: SwarmConfig::default_port(), blend_port: BlendCoreConfig::default_port(), http_addr: AxumBackendSettings::default_listening_address( AxumBackendSettings::default_port(), ), external_address: None, state_path: None, storage_path: None, logs_path: None, skip_ibd: false, log_filter: None, kms_file: None, } } } #[derive(Parser, Debug)] pub struct UpdateArgs { /// Output file path for the generated config. #[clap(long = "user-config", short = 'u', default_value = "user_config.yaml")] pub user_config: PathBuf, /// Path for the keystore file. #[clap(long = "keystore")] pub keystore: PathBuf, /// Auto approve interactive promps. #[arg(short, long, default_value_t = false)] pub yes: bool, #[clap(flatten)] log: LogArgs, #[clap(flatten)] network: NetworkArgs, #[clap(flatten)] blend: BlendArgs, #[clap(flatten)] cryptarchia: CryptarchiaArgs, #[clap(flatten)] sdp: SdpArgs, #[clap(flatten)] api: ApiArgs, #[clap(flatten)] state: StateArgs, } impl UpdateArgs { /// Creates arguments programmatically (e.g. from the c-bindings crate), /// leaving all config overrides at their defaults. `auto_approve` skips /// interactive prompts. #[must_use] pub fn new(user_config: PathBuf, keystore: PathBuf, auto_approve: bool) -> Self { Self { user_config, keystore, yes: auto_approve, ..Default::default() } } } // Custom default implementation to require keystore path initialized from clap. impl Default for UpdateArgs { fn default() -> Self { Self { user_config: "user_config.yaml".into(), keystore: "keystore.yaml".into(), yes: false, log: LogArgs::default(), network: NetworkArgs::default(), blend: BlendArgs::default(), cryptarchia: CryptarchiaArgs::default(), sdp: SdpArgs::default(), api: ApiArgs::default(), state: StateArgs::default(), } } } #[derive(Parser, Debug)] pub struct MigrateArgs { /// Output file path for the generated config. #[clap(long = "output", short = 'o', default_value = "user_config.yaml")] pub output: PathBuf, /// Path for the keystore file. #[clap(long = "keystore")] pub keystore: PathBuf, #[clap(flatten)] log: LogArgs, #[clap(flatten)] network: NetworkArgs, #[clap(flatten)] blend: BlendArgs, #[clap(flatten)] cryptarchia: CryptarchiaArgs, #[clap(flatten)] sdp: SdpArgs, #[clap(flatten)] api: ApiArgs, #[clap(flatten)] state: StateArgs, /// Name (or path, relative to the state folder) of the storage DB folder. #[clap(long = "storage-path")] storage_path: Option, } impl MigrateArgs { /// Creates arguments programmatically (e.g. from the c-bindings crate), /// leaving all config overrides at their defaults. #[must_use] pub fn new(output: PathBuf, keystore: PathBuf) -> Self { Self { output, keystore, log: LogArgs::default(), network: NetworkArgs::default(), blend: BlendArgs::default(), cryptarchia: CryptarchiaArgs::default(), sdp: SdpArgs::default(), api: ApiArgs::default(), state: StateArgs::default(), storage_path: None, } } } impl From for InitArgs { fn from(migrate: MigrateArgs) -> Self { Self { output: migrate.output, keystore: Some(migrate.keystore), log: migrate.log, network: migrate.network, blend: migrate.blend, cryptarchia: migrate.cryptarchia, sdp: migrate.sdp, api: migrate.api, state: migrate.state, storage_path: migrate.storage_path, overwrite: false, } } } impl Default for InitArgs { fn default() -> Self { Self::parse_from::, String>(vec![]) } } #[derive(Parser, Debug)] pub struct ParticipateArgs { /// Path to the user config YAML file #[arg(long, default_value = "user_config.yaml")] pub config: PathBuf, /// Path to the keystore YAML file #[arg(long, default_value = "keystore.yaml")] pub keystore: PathBuf, /// Output directory for `participation_data.yaml` #[arg(long, default_value = ".")] pub output: PathBuf, /// Node's public IPv4 address, required when the blend listening address /// is 0.0.0.0 #[arg(long)] pub external_address: Option, } #[derive(Parser, Debug)] pub struct GetPeerIdArgs { /// Path to the user config YAML file #[arg(long, default_value = "user_config.yaml")] pub config: PathBuf, } /// Applies CLI overrides from `args` to `user_config` and returns a /// `RunConfig` ready to start the node. pub fn build_run_config(mut user_config: UserConfig, args: CliArgs) -> Result { let CliArgs { log: log_args, api: api_args, network: network_args, blend: blend_args, cryptarchia: cryptarchia_args, sdp: sdp_args, deployment: deployment_args, state: state_args, .. } = args; update_tracing(&mut user_config.tracing, log_args)?; update_network(&mut user_config.network, network_args)?; update_blend(&mut user_config.blend, blend_args); update_cryptarchia(&mut user_config.cryptarchia, cryptarchia_args); update_sdp(&mut user_config.sdp, sdp_args); update_api(&mut user_config.api, api_args); update_state(&mut user_config.state, state_args); let deployment_settings = match deployment_args.custom_deployment_path { None => DeploymentSettings::default(), Some(path) => deserialize_value_at_path::(&path, OnUnknownKeys::Fail)?, }; Ok(RunConfig { deployment: deployment_settings, user: user_config, }) }