veilid-server with Clap v4
This commit is contained in:
+299
-45
@@ -4,7 +4,6 @@
|
||||
#![recursion_limit = "256"]
|
||||
|
||||
mod client_api;
|
||||
mod cmdline;
|
||||
mod server;
|
||||
mod settings;
|
||||
mod tools;
|
||||
@@ -14,82 +13,337 @@ mod veilid_logs;
|
||||
#[cfg(windows)]
|
||||
mod windows;
|
||||
|
||||
use crate::settings::*;
|
||||
|
||||
use clap::{Args, Parser};
|
||||
use server::*;
|
||||
use settings::LogLevel;
|
||||
use std::collections::HashMap;
|
||||
use std::ffi::{OsString, OsStr};
|
||||
use std::path::Path;
|
||||
use std::str::FromStr;
|
||||
use tools::*;
|
||||
use veilid_core::{TypedKeyGroup, TypedSecretGroup};
|
||||
use veilid_logs::*;
|
||||
|
||||
#[derive(Args, Debug, Clone)]
|
||||
#[group(multiple = false)]
|
||||
pub struct Logging {
|
||||
/// Turn on debug logging on the terminal
|
||||
#[arg(long)]
|
||||
debug: bool,
|
||||
/// Turn on trace logging on the terminal
|
||||
#[arg(long)]
|
||||
trace: bool,
|
||||
}
|
||||
|
||||
#[derive(Parser, Debug, Clone)]
|
||||
#[command(author, version, about)]
|
||||
pub struct CmdlineArgs {
|
||||
|
||||
/// Run in daemon mode in the background
|
||||
#[arg(short, long)]
|
||||
daemon: bool,
|
||||
|
||||
/// Run in the foreground
|
||||
#[arg(short, long)]
|
||||
foreground: bool,
|
||||
|
||||
/// Specify a configuration file to use
|
||||
#[arg(short, long, value_name = "FILE", default_value = OsString::from(Settings::get_default_config_path()))]
|
||||
config_file: Option<OsString>,
|
||||
|
||||
/// Specify configuration value to set (key in dot format, value in json format), eg: logging.api.enabled=true
|
||||
#[arg(short, long, value_name = "CONFIG")]
|
||||
set_config: Vec<String>,
|
||||
|
||||
/// Specify password to use to protect the device encryption key
|
||||
#[arg(short, long, value_name = "PASSWORD")]
|
||||
password: Option<String>,
|
||||
|
||||
/// Change password used to protect the device encryption key. Device storage will be migrated.
|
||||
#[arg(long, value_name = "PASSWORD")]
|
||||
new_password: Option<String>,
|
||||
|
||||
/// Do not automatically attach the server to the Veilid network
|
||||
///
|
||||
/// Default behaviour is to automatically attach the server to the Veilid network, this option disables this behaviour.
|
||||
#[arg(long, value_name = "BOOL")]
|
||||
no_attach: bool,
|
||||
|
||||
#[command(flatten)]
|
||||
logging: Logging,
|
||||
|
||||
/// Turn on OpenTelemetry tracing
|
||||
///
|
||||
/// This option uses the GRPC OpenTelemetry protocol, not HTTP. The format for the endpoint is host:port, like 'localhost:4317'
|
||||
#[arg(long, value_name = "endpoint")]
|
||||
otlp: Option<String>,
|
||||
|
||||
/// Run as an extra daemon on the same machine for testing purposes, specify a number greater than zero to offset the listening ports
|
||||
#[arg(long)]
|
||||
subnode_index: Option<u16>,
|
||||
|
||||
/// Only generate a new keypair and print it
|
||||
///
|
||||
/// Generate a new keypair for a specific crypto kind and print both the key and its secret to the terminal, then exit immediately.
|
||||
#[arg(long, value_name = "crypto_kind")]
|
||||
generate_key_pair: Option<String>,
|
||||
|
||||
/// Set the node ids and secret keys
|
||||
///
|
||||
/// Specify node ids in typed key set format ('[VLD0:xxxx,VLD1:xxxx]') on the command line, a prompt appears to enter the secret key set interactively.
|
||||
#[arg(long, value_name = "key_set")]
|
||||
set_node_id: Option<String>,
|
||||
|
||||
/// Delete the entire contents of the protected store (DANGER, NO UNDO!)
|
||||
#[arg(long)]
|
||||
delete_protected_store: bool,
|
||||
|
||||
/// Delete the entire contents of the table store (DANGER, NO UNDO!)
|
||||
#[arg(long)]
|
||||
delete_table_store: bool,
|
||||
|
||||
/// Delete the entire contents of the block store (DANGER, NO UNDO!)
|
||||
#[arg(long)]
|
||||
delete_block_store: bool,
|
||||
|
||||
/// Instead of running the server, print the configuration it would use to the console
|
||||
#[arg(long)]
|
||||
dump_config: bool,
|
||||
|
||||
/// Prints the bootstrap TXT record for this node and then quits
|
||||
#[arg(long)]
|
||||
dump_txt_record: bool,
|
||||
|
||||
/// Emits a JSON-Schema for a named type
|
||||
#[arg(long, value_name = "schema_name")]
|
||||
emit_schema: Option<String>,
|
||||
|
||||
/// Specify a list of bootstrap hostnames to use
|
||||
#[arg(long, value_name = "BOOTSTRAP_LIST")]
|
||||
bootstrap: Option<String>,
|
||||
|
||||
/// panic on ctrl-c instead of graceful shutdown
|
||||
#[arg(long)]
|
||||
panic: bool,
|
||||
|
||||
/// password override to use for network isolation
|
||||
#[arg(long, value_name = "KEY")]
|
||||
network_key: Option<String>,
|
||||
|
||||
/// Wait for debugger to attach
|
||||
#[cfg(debug_assertions)]
|
||||
#[arg(long)]
|
||||
wait_for_debug: bool,
|
||||
|
||||
/// enable tokio console
|
||||
#[cfg(feature = "rt-tokio")]
|
||||
#[arg(long)]
|
||||
console: bool,
|
||||
}
|
||||
|
||||
#[instrument(err)]
|
||||
fn main() -> EyreResult<()> {
|
||||
#[cfg(windows)]
|
||||
let _ = ansi_term::enable_ansi_support();
|
||||
color_eyre::install()?;
|
||||
|
||||
let (settings, matches) = cmdline::process_command_line()?;
|
||||
// Get command line options
|
||||
let args = CmdlineArgs::parse();
|
||||
|
||||
let svc_args = args.clone();
|
||||
|
||||
// Check for one-off commands
|
||||
#[cfg(debug_assertions)]
|
||||
if args.wait_for_debug{
|
||||
use bugsalot::debugger;
|
||||
debugger::wait_until_attached(None).expect("state() not implemented on this platform");
|
||||
}
|
||||
|
||||
// Attempt to load configuration
|
||||
let settings_path: Option<&OsStr> = if let Some(config_file) = &args.config_file {
|
||||
if Path::new(&config_file).exists() {
|
||||
Some(config_file)
|
||||
} else {
|
||||
None
|
||||
}
|
||||
} else {
|
||||
None
|
||||
};
|
||||
|
||||
let settings = Settings::new(settings_path).wrap_err("configuration is invalid")?;
|
||||
|
||||
// write lock the settings
|
||||
let mut settingsrw = settings.write();
|
||||
|
||||
// Set config from command line
|
||||
if args.daemon {
|
||||
settingsrw.daemon.enabled = true;
|
||||
settingsrw.logging.terminal.enabled = false;
|
||||
}
|
||||
if args.foreground {
|
||||
settingsrw.daemon.enabled = false;
|
||||
}
|
||||
if let Some(subnode_index) = args.subnode_index {
|
||||
if subnode_index == 0 {
|
||||
bail!("value of subnode_index should be between 1 and 65535");
|
||||
}
|
||||
settingsrw.testing.subnode_index = subnode_index;
|
||||
};
|
||||
|
||||
if args.logging.debug {
|
||||
settingsrw.logging.terminal.enabled = true;
|
||||
settingsrw.logging.terminal.level = LogLevel::Debug;
|
||||
}
|
||||
if args.logging.trace {
|
||||
settingsrw.logging.terminal.enabled = true;
|
||||
settingsrw.logging.terminal.level = LogLevel::Trace;
|
||||
}
|
||||
if args.otlp.is_some() {
|
||||
println!("Enabling OTLP tracing");
|
||||
settingsrw.logging.otlp.enabled = true;
|
||||
settingsrw.logging.otlp.grpc_endpoint = NamedSocketAddrs::from_str(
|
||||
args.otlp.expect("should not be null because of default missing value").as_str(),
|
||||
)
|
||||
.wrap_err("failed to parse OTLP address")?;
|
||||
settingsrw.logging.otlp.level = LogLevel::Trace;
|
||||
}
|
||||
if args.no_attach {
|
||||
settingsrw.auto_attach = false;
|
||||
}
|
||||
if args.delete_protected_store {
|
||||
settingsrw.core.protected_store.delete = true;
|
||||
}
|
||||
if args.delete_block_store {
|
||||
settingsrw.core.block_store.delete = true;
|
||||
}
|
||||
if args.delete_table_store {
|
||||
settingsrw.core.table_store.delete = true;
|
||||
}
|
||||
if let Some(password) = args.password {
|
||||
settingsrw.core.protected_store.device_encryption_key_password = password;
|
||||
}
|
||||
if let Some(new_password) = args.new_password {
|
||||
settingsrw.core.protected_store.new_device_encryption_key_password = Some(new_password);
|
||||
}
|
||||
if let Some(network_key) = args.network_key {
|
||||
settingsrw.core.network.network_key_password = Some(network_key);
|
||||
}
|
||||
if args.dump_txt_record {
|
||||
// Turn off terminal logging so we can be interactive
|
||||
settingsrw.logging.terminal.enabled = false;
|
||||
}
|
||||
let mut node_id_set = false;
|
||||
if let Some(key_set) = args.set_node_id {
|
||||
node_id_set = true;
|
||||
// Turn off terminal logging so we can be interactive
|
||||
settingsrw.logging.terminal.enabled = false;
|
||||
|
||||
// Split or get secret
|
||||
let tks =
|
||||
TypedKeyGroup::from_str(&key_set).wrap_err("failed to decode node id set from command line")?;
|
||||
|
||||
let buffer = rpassword::prompt_password("Enter secret key set (will not echo): ")
|
||||
.wrap_err("invalid secret key")?;
|
||||
let buffer = buffer.trim().to_string();
|
||||
let tss = TypedSecretGroup::from_str(&buffer).wrap_err("failed to decode secret set")?;
|
||||
|
||||
settingsrw.core.network.routing_table.node_id = Some(tks);
|
||||
settingsrw.core.network.routing_table.node_id_secret = Some(tss);
|
||||
}
|
||||
|
||||
if let Some(bootstrap) = args.bootstrap {
|
||||
println!("Overriding bootstrap list with: ");
|
||||
let mut bootstrap_list: Vec<String> = Vec::new();
|
||||
for x in bootstrap.split(',') {
|
||||
let x = x.trim().to_string();
|
||||
if !x.is_empty() {
|
||||
println!(" {}", x);
|
||||
bootstrap_list.push(x);
|
||||
}
|
||||
}
|
||||
settingsrw.core.network.routing_table.bootstrap = bootstrap_list;
|
||||
};
|
||||
|
||||
#[cfg(feature = "rt-tokio")]
|
||||
if args.console {
|
||||
settingsrw.logging.console.enabled = true;
|
||||
}
|
||||
|
||||
drop(settingsrw);
|
||||
|
||||
// Set specific config settings
|
||||
for set_config in args.set_config {
|
||||
if let Some((k, v)) = set_config.split_once('=') {
|
||||
let k = k.trim();
|
||||
let v = v.trim();
|
||||
settings.set(k, v)?;
|
||||
}
|
||||
}
|
||||
|
||||
// Apply subnode index if we're testing
|
||||
settings
|
||||
.apply_subnode_index()
|
||||
.wrap_err("failed to apply subnode index")?;
|
||||
|
||||
// --- Dump Config ---
|
||||
if matches.occurrences_of("dump-config") != 0 {
|
||||
if args.dump_config {
|
||||
return serde_yaml::to_writer(std::io::stdout(), &*settings.read())
|
||||
.wrap_err("failed to write yaml");
|
||||
}
|
||||
|
||||
// --- Generate DHT Key ---
|
||||
if matches.occurrences_of("generate-key-pair") != 0 {
|
||||
if let Some(ckstr) = matches.get_one::<String>("generate-key-pair") {
|
||||
if ckstr == "" {
|
||||
let mut tks = veilid_core::TypedKeyGroup::new();
|
||||
let mut tss = veilid_core::TypedSecretGroup::new();
|
||||
for ck in veilid_core::VALID_CRYPTO_KINDS {
|
||||
let tkp = veilid_core::Crypto::generate_keypair(ck)
|
||||
.wrap_err("invalid crypto kind")?;
|
||||
tks.add(veilid_core::TypedKey::new(tkp.kind, tkp.value.key));
|
||||
tss.add(veilid_core::TypedSecret::new(tkp.kind, tkp.value.secret));
|
||||
}
|
||||
println!(
|
||||
"Public Keys:\n{}\nSecret Keys:\n{}\n",
|
||||
tks.to_string(),
|
||||
tss.to_string()
|
||||
);
|
||||
} else {
|
||||
let ck: veilid_core::CryptoKind =
|
||||
veilid_core::FourCC::from_str(ckstr).wrap_err("couldn't parse crypto kind")?;
|
||||
let tkp =
|
||||
veilid_core::Crypto::generate_keypair(ck).wrap_err("invalid crypto kind")?;
|
||||
println!("{}", tkp.to_string());
|
||||
if let Some(ckstr) = args.generate_key_pair {
|
||||
if ckstr == "" {
|
||||
let mut tks = veilid_core::TypedKeyGroup::new();
|
||||
let mut tss = veilid_core::TypedSecretGroup::new();
|
||||
for ck in veilid_core::VALID_CRYPTO_KINDS {
|
||||
let tkp = veilid_core::Crypto::generate_keypair(ck)
|
||||
.wrap_err("invalid crypto kind")?;
|
||||
tks.add(veilid_core::TypedKey::new(tkp.kind, tkp.value.key));
|
||||
tss.add(veilid_core::TypedSecret::new(tkp.kind, tkp.value.secret));
|
||||
}
|
||||
return Ok(());
|
||||
println!(
|
||||
"Public Keys:\n{}\nSecret Keys:\n{}\n",
|
||||
tks.to_string(),
|
||||
tss.to_string()
|
||||
);
|
||||
} else {
|
||||
bail!("missing crypto kind");
|
||||
let ck: veilid_core::CryptoKind =
|
||||
veilid_core::FourCC::from_str(&ckstr).wrap_err("couldn't parse crypto kind")?;
|
||||
let tkp =
|
||||
veilid_core::Crypto::generate_keypair(ck).wrap_err("invalid crypto kind")?;
|
||||
println!("{}", tkp.to_string());
|
||||
}
|
||||
return Ok(());
|
||||
}
|
||||
|
||||
// -- Emit JSON-Schema --
|
||||
if matches.occurrences_of("emit-schema") != 0 {
|
||||
if let Some(esstr) = matches.value_of("emit-schema") {
|
||||
let mut schemas = HashMap::<String, String>::new();
|
||||
veilid_core::json_api::emit_schemas(&mut schemas);
|
||||
if let Some(esstr) = args.emit_schema {
|
||||
let mut schemas = HashMap::<String, String>::new();
|
||||
veilid_core::json_api::emit_schemas(&mut schemas);
|
||||
|
||||
if let Some(schema) = schemas.get(esstr) {
|
||||
println!("{}", schema);
|
||||
} else {
|
||||
println!("Valid schemas:");
|
||||
for s in schemas.keys() {
|
||||
println!(" {}", s);
|
||||
}
|
||||
if let Some(schema) = schemas.get(&esstr) {
|
||||
println!("{}", schema);
|
||||
} else {
|
||||
println!("Valid schemas:");
|
||||
for s in schemas.keys() {
|
||||
println!(" {}", s);
|
||||
}
|
||||
|
||||
return Ok(());
|
||||
}
|
||||
|
||||
return Ok(());
|
||||
}
|
||||
|
||||
// See if we're just running a quick command
|
||||
let (server_mode, success, failure) = if matches.occurrences_of("set-node-id") != 0 {
|
||||
let (server_mode, success, failure) = if node_id_set {
|
||||
(
|
||||
ServerMode::ShutdownImmediate,
|
||||
"Node Id and Secret set successfully",
|
||||
"Failed to set Node Id and Secret",
|
||||
)
|
||||
} else if matches.occurrences_of("dump-txt-record") != 0 {
|
||||
} else if args.dump_txt_record {
|
||||
(ServerMode::DumpTXTRecord, "", "Failed to dump txt record")
|
||||
} else {
|
||||
(ServerMode::Normal, "", "")
|
||||
@@ -118,9 +372,9 @@ fn main() -> EyreResult<()> {
|
||||
if settings.read().daemon.enabled {
|
||||
cfg_if! {
|
||||
if #[cfg(windows)] {
|
||||
return windows::run_service(settings, matches);
|
||||
return windows::run_service(settings, svc_args);
|
||||
} else if #[cfg(unix)] {
|
||||
return unix::run_daemon(settings, matches);
|
||||
return unix::run_daemon(settings, svc_args);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -138,7 +392,7 @@ fn main() -> EyreResult<()> {
|
||||
std::process::exit(1);
|
||||
}));
|
||||
|
||||
let panic_on_shutdown = matches.occurrences_of("panic") != 0;
|
||||
let panic_on_shutdown = args.panic;
|
||||
ctrlc::set_handler(move || {
|
||||
if panic_on_shutdown {
|
||||
panic!("panic requested");
|
||||
|
||||
Reference in New Issue
Block a user