From b88bf114be8a449adbca7c987a1ee9c0241c682a Mon Sep 17 00:00:00 2001 From: Clint Date: Wed, 1 Jan 2020 02:35:23 +0100 Subject: [PATCH 01/12] Refactor YAML --- src/cli.yml | 197 ++++++++++++++++++++++------------------------------ 1 file changed, 84 insertions(+), 113 deletions(-) diff --git a/src/cli.yml b/src/cli.yml index dc6d2a4..d87987f 100644 --- a/src/cli.yml +++ b/src/cli.yml @@ -1,117 +1,88 @@ -name: Lucid -author: 'Written in Rust by Clint.Network (twitter.com/clint_network)' subcommands: - - - cli: - about: 'Spawn to the command line interface' - author: 'Written in Rust by Clint.Network (twitter.com/clint_network)' - args: - - - help: - help: 'Prints CLI help information' - short: h - long: help - - - init: - about: 'Initialize Lucid and generate configuration file' - author: 'Written in Rust by Clint.Network (twitter.com/clint_network)' - args: - - - secret: - help: 'Set the JWT secret' - short: s - long: secret + - cli: + about: "Spawn to the command line interface" + args: + - help: + help: "Prints CLI help information" + short: h + long: help + - init: + about: "Initialize Lucid and generate configuration file" + args: + - secret: + help: "Set the JWT secret" + short: s + long: secret + takes_value: true + - force: + help: "Initialize Lucid and overwrite existing configuration file" + short: f + long: force + takes_value: false + - config: + help: "Specify the Lucid configuration file location" + short: c + long: config + takes_value: true + - server: + about: "Run a new Lucid server instance" + args: + - config: + help: "Specify the Lucid configuration file" + short: c + long: config + takes_value: true + - settings: + about: "Manage Lucid configuration file" + subcommands: + - set: + about: "Update a setting parameter" + - get: + about: "Get a setting parameter" + args: + - config: + help: "Specify the Lucid configuration file" + short: c + long: config + takes_value: true + - store: + about: "Play with the KV store (get/set)" + - tokens: + about: "Manage JWT Tokens (issue, revoke etc.)" + subcommands: + - revoke: + about: "Revoke a JWT Token" + args: + - token: + help: "Token to revoke" + index: 1 + - issue: + about: "Issue a new JWT Token" + args: + - expiration: + help: "Specify the expiration date of the JWT Token (timestamp)" + short: e + long: expiration takes_value: true - - - force: - help: 'Initialize Lucid and overwrite existing configuration file' - short: f - long: force - takes_value: false - - - config: - help: 'Specify the Lucid configuration file location' + - count: + help: "Number of tokens to issue " short: c - long: config - takes_value: true - - - server: - about: 'Run a new Lucid server instance' - author: 'Written in Rust by Clint.Network (twitter.com/clint_network)' - args: - - - config: - help: 'Specify the Lucid configuration file' - short: c - long: config - takes_value: true - - - settings: - about: 'Manage Lucid configuration file' - author: 'Written in Rust by Clint.Network (twitter.com/clint_network)' - subcommands: - - set: - about: 'Update a setting parameter' - author: 'Written in Rust by Clint.Network (twitter.com/clint_network)' - - get: - about: 'Get a setting parameter' - author: 'Written in Rust by Clint.Network (twitter.com/clint_network)' - args: - - config: - help: 'Specify the Lucid configuration file' - short: c - long: config - takes_value: true - - - store: - about: 'Play with the KV store (get/set)' - author: 'Written in Rust by Clint.Network (twitter.com/clint_network)' - - - tokens: - about: 'Manage JWT Tokens (issue, revoke etc.)' - author: 'Written in Rust by Clint.Network (twitter.com/clint_network)' - subcommands: - - - revoke: - about: 'Revoke a JWT Token' - author: 'Written in Rust by Clint.Network (twitter.com/clint_network)' - args: - - - token: - help: 'Token to revoke' - index: 1 - - - issue: - about: 'Issue a new JWT Token' - author: 'Written in Rust by Clint.Network (twitter.com/clint_network)' - args: - - - expiration: - help: 'Specify the expiration date of the JWT Token (timestamp)' - short: e - long: expiration - takes_value: true - - count: - help: 'Number of tokens to issue ' - short: c - long: count - index: 1 - args: - - config: - help: 'Specify the Lucid configuration file' - short: c - long: config - takes_value: true + long: count + index: 1 + args: + - config: + help: "Specify the Lucid configuration file" + short: c + long: config + takes_value: true args: - - - help: - help: 'Prints help information' - short: h - long: help - takes_value: false - - - version: - help: 'Prints version information' - short: v - long: version - takes_value: false + - help: + help: "Prints help information" + short: h + long: help + takes_value: false + - version: + help: "Prints version information" + short: v + long: version + takes_value: false From 567bc7aa74e350df9e601594e515d80b3ee14b20 Mon Sep 17 00:00:00 2001 From: Clint Date: Wed, 1 Jan 2020 02:44:31 +0100 Subject: [PATCH 02/12] Add the about message in the banner --- src/lucid.rs | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/src/lucid.rs b/src/lucid.rs index 8df4ca0..53c2a47 100644 --- a/src/lucid.rs +++ b/src/lucid.rs @@ -36,7 +36,9 @@ impl Lucid { ██║ ██║ ██║██║ ██║██║ ██║ ██╔═██╗ ╚██╗ ██╔╝ ██████╗╚██████╔╝╚██████╗██║██████╔╝ ██║ ██╗ ╚████╔╝ ╚═════╝ ╚═════╝ ╚═════╝╚═╝╚═════╝ ╚═╝ ╚═╝ ╚═══╝ - "###); + +A Fast, Secure and Distributed KV store with an HTTP API. +Written in Rust by Clint.Network (twitter.com/clint_network)"###); } fn show_version(&self) { @@ -59,8 +61,7 @@ impl Lucid { pub fn initialize(&mut self) -> Result<(), std::io::Error> { let cli_yaml = load_yaml!("cli.yml"); - let mut commands = App::from_yaml(cli_yaml) - .name(crate_description!()); + let mut commands = App::from_yaml(cli_yaml); self.show_banner(); match self.handle_cli(&mut commands) { Some(usage) => println!("{}{}", usage, match usage.to_owned().contains("USAGE") { From e41f344bb02e18120672bb45bcc48ca45a797e83 Mon Sep 17 00:00:00 2001 From: CephalonRho Date: Sat, 18 Jan 2020 21:16:38 +0100 Subject: [PATCH 03/12] Port CLI changes from split-cli --- Cargo.lock | 1 + Cargo.toml | 5 +- src/cli.yml | 111 ++++----------- src/configuration.rs | 44 +++--- src/lucid.rs | 322 ++----------------------------------------- src/main.rs | 177 ++++++++++++++++++++++-- src/server.rs | 8 +- 7 files changed, 237 insertions(+), 431 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index ad95e93..f69fdee 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -627,6 +627,7 @@ version = "0.4.8" source = "registry+https://github.com/rust-lang/crates.io-index" dependencies = [ "cfg-if 0.1.10 (registry+https://github.com/rust-lang/crates.io-index)", + "serde 1.0.101 (registry+https://github.com/rust-lang/crates.io-index)", ] [[package]] diff --git a/Cargo.toml b/Cargo.toml index 5e2c926..fd02129 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -42,7 +42,6 @@ tree_magic = "*" byte-unit = "*" snafu = "*" bytes = "*" -log = "*" fern = "*" tokio = "0.1.22" @@ -53,3 +52,7 @@ features = ["yaml"] [dependencies.warp] version = "*" features = ["tls"] + +[dependencies.log] +version = "*" +features = ["serde"] diff --git a/src/cli.yml b/src/cli.yml index d87987f..739a97d 100644 --- a/src/cli.yml +++ b/src/cli.yml @@ -1,88 +1,27 @@ +name: "Lucid" +author: "Written in Rust by Clint.Network (twitter.com/clint_network)" +settings: + - GlobalVersion + - SubcommandRequiredElseHelp +args: + - config: + help: "Specify the Lucid configuration file" + short: c + long: config + takes_value: true subcommands: - - cli: - about: "Spawn to the command line interface" - args: - - help: - help: "Prints CLI help information" - short: h - long: help - - init: - about: "Initialize Lucid and generate configuration file" - args: - - secret: - help: "Set the JWT secret" - short: s - long: secret - takes_value: true - - force: - help: "Initialize Lucid and overwrite existing configuration file" - short: f - long: force - takes_value: false - - config: - help: "Specify the Lucid configuration file location" - short: c - long: config - takes_value: true - - server: - about: "Run a new Lucid server instance" - args: - - config: - help: "Specify the Lucid configuration file" - short: c - long: config - takes_value: true - - settings: - about: "Manage Lucid configuration file" - subcommands: - - set: - about: "Update a setting parameter" - - get: - about: "Get a setting parameter" - args: - - config: - help: "Specify the Lucid configuration file" - short: c - long: config - takes_value: true - - store: - about: "Play with the KV store (get/set)" - - tokens: - about: "Manage JWT Tokens (issue, revoke etc.)" - subcommands: - - revoke: - about: "Revoke a JWT Token" - args: - - token: - help: "Token to revoke" - index: 1 - - issue: - about: "Issue a new JWT Token" - args: - - expiration: - help: "Specify the expiration date of the JWT Token (timestamp)" - short: e - long: expiration + - init: + about: "Initialize Lucid and generate configuration file" + args: + - secret: + help: "Set the JWT secret" + short: s + long: secret takes_value: true - - count: - help: "Number of tokens to issue " - short: c - long: count - index: 1 - args: - - config: - help: "Specify the Lucid configuration file" - short: c - long: config - takes_value: true -args: - - help: - help: "Prints help information" - short: h - long: help - takes_value: false - - version: - help: "Prints version information" - short: v - long: version - takes_value: false + - force: + help: "Initialize Lucid and overwrite existing configuration file" + short: f + long: force + takes_value: false + - server: + about: "Run a new Lucid server instance" \ No newline at end of file diff --git a/src/configuration.rs b/src/configuration.rs index b1b4549..7aa7ccf 100644 --- a/src/configuration.rs +++ b/src/configuration.rs @@ -1,4 +1,10 @@ -use std::net::{Ipv4Addr, IpAddr}; +use std::{ + net::{IpAddr, Ipv4Addr}, + path::PathBuf, +}; + +use app_dirs::{AppDataType, AppDirsError}; +use log::LevelFilter; #[derive(Debug, Clone, Serialize, Deserialize)] pub struct Configuration { @@ -9,11 +15,19 @@ pub struct Configuration { pub webui: WebUI, pub store: Store, pub http: Http, - pub logging: Logging + pub logging: Logging, } impl Configuration { - pub fn default() -> Configuration { + pub fn get_path() -> Result { + let mut path = app_dirs::get_app_root(AppDataType::UserConfig, &crate::APP_INFO)?; + path.push("lucid.yml"); + Ok(path) + } +} + +impl Default for Configuration { + fn default() -> Configuration { Configuration { default: Base { bind_address: IpAddr::from(Ipv4Addr::LOCALHOST), @@ -21,7 +35,7 @@ impl Configuration { port_ssl: 7021, use_ssl: true, ssl_certificate: String::new(), - ssl_certificate_key: String::new() + ssl_certificate_key: String::new(), }, authentication: Authentication { enabled: true, @@ -36,17 +50,13 @@ impl Configuration { enabled: false, private_key: String::new(), }, - webui: WebUI { - enabled: false - }, - store: Store { - max_limit: 7340032 - }, + webui: WebUI { enabled: false }, + store: Store { max_limit: 7340032 }, http: Http { - request_size_limit: 8388608 + request_size_limit: 8388608, }, logging: Logging { - level: "Info".to_string() + level: LevelFilter::Info, }, } } @@ -83,22 +93,22 @@ pub struct Encryption { #[derive(Debug, Clone, Serialize, Deserialize)] pub struct WebUI { - pub enabled: bool + pub enabled: bool, } #[derive(Debug, Clone, Serialize, Deserialize)] pub struct Store { - pub max_limit: u64 + pub max_limit: u64, } #[derive(Debug, Clone, Serialize, Deserialize)] pub struct Http { - pub request_size_limit: u64 + pub request_size_limit: u64, } #[derive(Debug, Clone, Serialize, Deserialize)] pub struct Logging { - pub level: String + pub level: LevelFilter, } #[derive(Debug, Clone, Serialize, Deserialize)] @@ -107,4 +117,4 @@ pub struct Claims { pub iss: String, pub iat: i64, pub exp: i64, -} \ No newline at end of file +} diff --git a/src/lucid.rs b/src/lucid.rs index 53c2a47..b0028c7 100644 --- a/src/lucid.rs +++ b/src/lucid.rs @@ -1,324 +1,22 @@ -use std::{ - fs::{self, File}, - io::{self, Error, ErrorKind, Write}, - path::Path, - str::FromStr, -}; +use std::sync::{Arc, RwLock}; -use app_dirs::*; -use chrono::{DateTime, Duration, Utc}; -use clap::App; -use jsonwebtoken::*; -use rand::Rng; -use ring::digest::SHA256; -use log::LevelFilter; - -use crate::configuration::{Claims, Configuration}; +use crate::configuration::Configuration; use crate::server::Server; pub struct Lucid { - configuration_location: String, - configuration: Option + configuration: Arc>, } impl Lucid { - pub fn default() -> Lucid { + pub fn new(configuration: Configuration) -> Self { Lucid { - configuration_location: String::new(), - configuration: None - } - } - - pub fn show_banner(&self) { - println!(r###" - ██╗ ██╗ ██╗ ██████╗██╗██████╗ ██╗ ██╗██╗ ██╗ - ██║ ██║ ██║██╔════╝██║██╔══██╗ ██║ ██╔╝██║ ██║ - ██║ ██║ ██║██║ ██║██║ ██║ ██╔═██╗ ╚██╗ ██╔╝ - ██████╗╚██████╔╝╚██████╗██║██████╔╝ ██║ ██╗ ╚████╔╝ - ╚═════╝ ╚═════╝ ╚═════╝╚═╝╚═════╝ ╚═╝ ╚═╝ ╚═══╝ - -A Fast, Secure and Distributed KV store with an HTTP API. -Written in Rust by Clint.Network (twitter.com/clint_network)"###); - } - - fn show_version(&self) { - info!("Lucid Version {}\n", crate_version!()); - println!("\ -+-----------------+-----------------------+--------------------+\n\ -| Lucid KV Development Credits |\n\ -+-----------------+-----------------------+--------------------+\n\ -| Clint Mourlevat | me@clint.network | Lucid Founder |\n\ -| Jonathan Serra | jonathan@blocs.fr | Core Development |\n\ -| CephalonRho | CephalonRho@gmail.com | Core Development |\n\ -| Rigwild | me@rigwild.dev | Web UI Development |\n\ -+-----------------+-----------------------+--------------------+") - } - - fn show_help(&self, commands: &mut App) { - commands.print_help().unwrap(); - println!("\n"); - } - - pub fn initialize(&mut self) -> Result<(), std::io::Error> { - let cli_yaml = load_yaml!("cli.yml"); - let mut commands = App::from_yaml(cli_yaml); - self.show_banner(); - match self.handle_cli(&mut commands) { - Some(usage) => println!("{}{}", usage, match usage.to_owned().contains("USAGE") { - true => "\n", - false => "" - }), - None => self.show_help(&mut commands) - } - return Ok(()); - } - - fn handle_cli(&mut self, commands: &mut App) -> Option<&str> { - match commands.get_matches_from_safe_borrow(::std::env::args_os()) { - Ok(cli) => { - if cli.is_present("help") { - return None; - } - if cli.is_present("version") { - self.show_version(); - return Some("") - } - - if let Some(matches) = cli.subcommand_matches("cli") { - fn display_cli_help() { - info!( - "This is all the available commands:\n\ - - set [key] - Set an object\n\ - - get [key] - Get an object\n\ - - lock [key] - Lock an object\n\ - - unlock [key] - Unlock an object\n\ - - expire [key] - Set an object expiration date\n\ - - increment [key] - Increment the value of an object\n\ - - decrement [key] - Decrement the value of an object\n\ - - drop [key] - Drop an object" - ); - } - - if matches.is_present("help") { - info!("Welcome to the Lucid Command Line Interface (CLI)\n"); - display_cli_help(); - return Some(""); - } else { - info!("Welcome to the Lucid Command Line Interface (CLI)\nType 'help' to display all commands.\n"); - - // TODO: Try to connect to the remote endpoint - // TODO: Use env var to set remote endpoint - - let mut input = String::new(); - loop { - // TODO: Display the good endpoint - print!("{}> ", "127.0.0.1:7021"); - io::stdout().flush().unwrap(); - match io::stdin().read_line(&mut input) { - Ok(_) => { - match input.trim().as_ref() { - "exit" | "quit" => { - info!("Exiting Lucid CLI"); - break; - } - "help" | "?" | "-h" | "/?" => { - display_cli_help(); - } - _ => { - warn!("Unknown command '{}'", input.trim()); - } - } - println!(); - input.clear(); - } - _ => () - } - } - std::process::exit(0); - } - } - - if let Some(matches) = cli.subcommand_matches("init") { - let secret_key = match matches.value_of("secret") { - Some(secret) => { - secret.to_owned() - } - None => { - let secret_key_bytes = ring::digest::digest(&SHA256, &rand::thread_rng().gen::<[u8; 32]>()); - secret_key_bytes.as_ref().iter().fold( - String::with_capacity(secret_key_bytes.as_ref().len() * 2), - |mut acc, x| { - acc.push_str(&format!("{:0>2x}", x)); - acc - }, - ) - } - }; - - let mut has_configuration_file: Option<&str> = None; - - if let Some(configuration_file) = matches.value_of("config") { - has_configuration_file = Some(configuration_file); - } - - match self.initialize_node(has_configuration_file, secret_key, matches.is_present("force")) { - Ok(lucid_yml_location) => { - info!("Lucid successfully initialized in {}", lucid_yml_location); - }, - Err(e) => { - error!("Unable to initialize Lucid node: {}", e.get_ref().unwrap().description()); - } - } - return Some(""); - } - - if let Some(matches) = cli.subcommand_matches("server") { - &mut self.configure(matches.value_of("config")); - match &self.configuration { - Some(config) => { - let mut lucid_server = Server::new(); - lucid_server.configure(config.clone()); - lucid_server.run(); - }, - None => { - warn!("The Lucid node is not successfully configured."); - } - }; - return Some(""); - } - - if let Some(matches) = cli.subcommand_matches("settings") { - &mut self.configure(matches.value_of("config")); - if let Some(_) = &self.configuration { - match fs::read_to_string(&self.configuration_location) { - Ok(content) => info!("Configuration location: {}\n\n{}", &self.configuration_location, content), - Err(err) => error!("{}", err) - } - } - return Some(""); - } - - if let Some(matches) = cli.subcommand_matches("store") { - &mut self.configure(matches.value_of("config")); - return None; - } - - if let Some(matches) = cli.subcommand_matches("tokens") { - &mut self.configure(matches.value_of("config")); - if matches.is_present("issue") { - if let Some(config) = &self.configuration { - match &self.issue_jwt(&config.clone().authentication.secret_key, None) { - Some(token) => { - info!("JWT token successfully generated: {}", token); - return Some(""); - }, - None => { - info!("Unable to generate JWT token."); - return Some(""); - } - } - } - } - return None; - } - } - Err(e) => { - return Some(Box::leak(e.message.into_boxed_str())); - } - } - None - } - - fn issue_jwt(&self, secret_key: &String, expiration: Option>) -> Option { - let lucid_root_claims = Claims { - sub: String::from("Lucid Root Token"), - iss: String::from("http://127.0.0.1:7021/"), // TODO: check issuer, maybe set the proper uri - iat: Utc::now().timestamp(), - exp: match expiration { - Some(exp) => exp.timestamp(), - None => (Utc::now() + Duration::weeks(52 * 3)).timestamp() - }, - }; - match encode(&Header::default(), &lucid_root_claims, secret_key.as_ref()) { - Ok(token) => Some(token), - _ => None, + configuration: Arc::new(RwLock::new(configuration)), } } - // Initialize the node by creating a lucid.yml configuration file - fn initialize_node(&mut self, configuration_file: Option<&str>, secret_key: String, force: bool) -> Result<&str, std::io::Error> { - let lucid_yml = match configuration_file { - Some(custom_configuration_file) => String::from(custom_configuration_file), - None => match get_data_root(AppDataType::UserConfig) { - Ok(mut appdata_root) => { - &appdata_root.push("lucid"); - fs::create_dir_all(&appdata_root.clone().into_os_string().into_string().unwrap())?; - &appdata_root.push("lucid.yml"); - appdata_root.clone().into_os_string().into_string().unwrap() - }, - Err(_) => { - return Err(Error::new(ErrorKind::Interrupted, "Unable to get the Lucid configuration folder.")); - } - } - }; - - if Path::new(&lucid_yml).exists() && !force { - return Err(Error::new(ErrorKind::Interrupted, "The Lucid node is already initialized.")); - } else { - match File::create(lucid_yml.clone()) { - Ok(mut file_handle) => { - match &self.issue_jwt(&secret_key, None) { - Some(root_token) => { - let mut default_configuration = Configuration::default(); - default_configuration.authentication.root_token = root_token.clone(); - default_configuration.authentication.secret_key = secret_key; - if file_handle.write_all(serde_yaml::to_string(&default_configuration).unwrap().as_bytes()).is_ok() { - return Ok(Box::leak(lucid_yml.into_boxed_str())); - } - return Err(Error::new(ErrorKind::Interrupted, "Unable to create default configuration.")); - }, - None => { - return Err(Error::new(ErrorKind::Interrupted, "Unable to create the JWT root token.")); - } - } -// let r = &self.issue_jwt(&secret_key, None); -// return Err(Error::new(ErrorKind::Interrupted, "Unable to create the JWT root token.")); - }, - Err(_) => { - return Err(Error::new(ErrorKind::Interrupted, "Unable to create the Lucid configuration file.")); - } - } - } + pub fn run(&self) -> Result<(), std::io::Error> { + let server = Server::new(self.configuration.clone()); + server.run(); + Ok(()) } - - // Configure the current instance with the default or a specific configuration file - fn configure(&mut self, configuration_file: Option<&str>) { - let mut lucid_yml = String::new(); - match configuration_file { - Some(conf) => lucid_yml = String::from(conf), - None => match get_data_root(AppDataType::UserConfig) { - Ok(mut appdata_root) => { - &appdata_root.push("lucid"); - fs::create_dir_all(&appdata_root.clone().into_os_string().into_string().unwrap()).unwrap(); - &appdata_root.push("lucid.yml"); - lucid_yml = appdata_root.into_os_string().into_string().unwrap(); - }, - Err(_) => warn!("Unable to get the Lucid configuration folder.") - } - }; - - if Path::new(&lucid_yml).exists() { - match fs::read_to_string(&lucid_yml) { - Ok(content) => { - let configuration: Configuration = serde_yaml::from_str(&content).unwrap(); - log::set_max_level(LevelFilter::from_str(&configuration.logging.level).expect("Invalid logging level in configuration")); - self.configuration_location = lucid_yml; - self.configuration = Some(configuration); - }, - Err(_) => warn!("Unable to read the Lucid configuration file.") - } - } else { - warn!("This configuration file does not exists."); - } - } -} \ No newline at end of file +} diff --git a/src/main.rs b/src/main.rs index 158d97b..b094440 100644 --- a/src/main.rs +++ b/src/main.rs @@ -5,18 +5,57 @@ extern crate serde_derive; #[macro_use] extern crate log; -use chrono::Utc; -use fern::Dispatch; -use log::LevelFilter; - -use lucid::Lucid; - mod configuration; mod kvstore; mod lucid; mod server; -fn main() -> Result<(), std::io::Error> { +use configuration::{Claims, Configuration}; +use lucid::Lucid; + +use std::{ + fmt, + fs::{self, File}, + path::Path, +}; + +use app_dirs::{AppDirsError, AppInfo}; +use chrono::{DateTime, Duration, Utc}; +use clap::App; +use fern::Dispatch; +use jsonwebtoken::Header; +use log::LevelFilter; +use rand::Rng; +use ring::digest; +use snafu::{ResultExt, Snafu}; + +const APP_INFO: AppInfo = AppInfo { + name: "lucid", + author: "Clint.Network", +}; + +const BANNER: &'static str = r###" +██╗ ██╗ ██╗ ██████╗██╗██████╗ ██╗ ██╗██╗ ██╗ +██║ ██║ ██║██╔════╝██║██╔══██╗ ██║ ██╔╝██║ ██║ +██║ ██║ ██║██║ ██║██║ ██║ ██╔═██╗ ╚██╗ ██╔╝ +██████╗╚██████╔╝╚██████╗██║██████╔╝ ██║ ██╗ ╚████╔╝ +╚═════╝ ╚═════╝ ╚═════╝╚═╝╚═════╝ ╚═╝ ╚═╝ ╚═══╝ + +A Fast, Secure and Distributed KV store with an HTTP API. +Written in Rust by Clint.Network (twitter.com/clint_network) +"###; + +const CREDITS: &'static str = "\ + +-----------------+-----------------------+--------------------+\n\ + | Lucid KV Development Credits |\n\ + +-----------------+-----------------------+--------------------+\n\ + | Clint Mourlevat | me@clint.network | Lucid Founder |\n\ + | Jonathan Serra | jonathan@blocs.fr | Core Development |\n\ + | CephalonRho | CephalonRho@gmail.com | Core Development |\n\ + | Rigwild | me@rigwild.dev | Web UI Development |\n\ + +-----------------+-----------------------+--------------------+"; + +fn main() -> Result<(), Error> { Dispatch::new() .format(|out, message, record| { out.finish(format_args!( @@ -31,6 +70,126 @@ fn main() -> Result<(), std::io::Error> { .apply() .expect("Couldn't start logger"); log::set_max_level(LevelFilter::Debug); - let mut lucid = Lucid::default(); - lucid.initialize() + + println!("{}", BANNER); + let long_version = format!("{}\n{}", crate_version!(), CREDITS); + + let cli_yaml = load_yaml!("cli.yml"); + let app = App::from_yaml(&cli_yaml) + .version(crate_version!()) + .long_version(long_version.as_str()); + let matches = match app.get_matches_safe() { + Ok(x) => x, + Err(clap::Error { + kind: clap::ErrorKind::HelpDisplayed, + message, + .. + }) + | Err(clap::Error { + kind: clap::ErrorKind::VersionDisplayed, + message, + .. + }) => { + println!("{}", message); + return Ok(()); + } + Err(e) => return Err(Error::ParseCli { source: e }), + }; + + let config_path = { + if let Some(config) = matches.value_of("config") { + Path::new(config).to_path_buf() + } else { + Configuration::get_path().context(GetConfigDir)? + } + }; + if let Some(init_matches) = matches.subcommand_matches("init") { + if config_path.exists() && !init_matches.is_present("force") { + return Err(Error::AlreadyInitialized); + } else { + let mut config = Configuration::default(); + let secret_key = generate_secret_key(); + config.authentication.root_token = issue_jwt(&secret_key, None)?; + config.authentication.secret_key = secret_key; + fs::create_dir_all(config_path.parent().unwrap()).context(CreateConfigDir)?; + serde_yaml::to_writer( + File::create(&config_path).context(CreateConfigFile)?, + &config, + ) + .context(WriteConfigFile)?; + info!( + "Lucid successfully initialized in {}", + config_path.to_string_lossy() + ); + } + } + if let Some(_) = matches.subcommand_matches("server") { + if config_path.exists() { + let config: Configuration = + serde_yaml::from_reader(File::open(&config_path).context(OpenConfigFile)?) + .context(ReadConfigFile)?; + log::set_max_level(config.logging.level); // this has to be executed every time the logging configuration changes + Lucid::new(config).run().context(RunServer)?; + } else { + return Err(Error::ConfigurationNotFound); + } + } + Ok(()) +} + +fn generate_secret_key() -> String { + let secret_key_bytes = digest::digest(&digest::SHA256, &rand::thread_rng().gen::<[u8; 32]>()); + secret_key_bytes.as_ref().iter().fold( + String::with_capacity(secret_key_bytes.as_ref().len() * 2), + |mut acc, x| { + acc.push_str(&format!("{:0>2x}", x)); + acc + }, + ) +} + +fn issue_jwt(secret_key: &str, expiration: Option>) -> Result { + let lucid_root_claims = Claims { + sub: String::from("Lucid Root Token"), + iss: String::from("http://127.0.0.1:7021/"), // TODO: check issuer, maybe set the proper uri + iat: Utc::now().timestamp(), + exp: match expiration { + Some(exp) => exp.timestamp(), + None => (Utc::now() + Duration::weeks(52 * 3)).timestamp(), + }, + }; + jsonwebtoken::encode(&Header::default(), &lucid_root_claims, secret_key.as_ref()) + .context(EncodeJwt) +} + +#[derive(Snafu)] +pub enum Error { + #[snafu(display("{}", source))] + ParseCli { source: clap::Error }, + #[snafu(display("{}", source))] + RunServer { source: std::io::Error }, + #[snafu(display("Configuration file not found"))] + ConfigurationNotFound, + #[snafu(display("The Lucid node has already been initialized"))] + AlreadyInitialized, + #[snafu(display("Unable to get the Lucid configuration directory: {}", source))] + GetConfigDir { source: AppDirsError }, + #[snafu(display("Unable to create the Lucid configuration directory: {}", source))] + CreateConfigDir { source: std::io::Error }, + #[snafu(display("Unable to create the Lucid configuration file: {}", source))] + CreateConfigFile { source: std::io::Error }, + #[snafu(display("Unable to write the Lucid configuration file: {}", source))] + WriteConfigFile { source: serde_yaml::Error }, + #[snafu(display("Unable to open the Lucid configuration file: {}", source))] + OpenConfigFile { source: std::io::Error }, + #[snafu(display("Unable to read the Lucid configuration file: {}", source))] + ReadConfigFile { source: serde_yaml::Error }, + #[snafu(display("Error while encoding the JWT root token: {}", source))] + EncodeJwt { source: jsonwebtoken::errors::Error }, +} + +impl fmt::Debug for Error { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + fmt::Display::fmt(self, f) + } } diff --git a/src/server.rs b/src/server.rs index b5f5745..c002282 100644 --- a/src/server.rs +++ b/src/server.rs @@ -21,16 +21,12 @@ pub struct Server { } impl Server { - pub fn new() -> Server { + pub fn new(configuration: Arc>) -> Server { Server { - configuration: Arc::new(RwLock::new(Configuration::default())), + configuration, } } - pub fn configure(&mut self, configuration: Configuration) { - *self.configuration.write().unwrap() = configuration; - } - pub fn run(&self) { let store = Arc::new(KvStore::new()); let store = warp::any().map(move || store.clone()); From f1743b0a22197e3c5fdfabc5d458f5858f69d4b3 Mon Sep 17 00:00:00 2001 From: CephalonRho Date: Sun, 19 Jan 2020 11:51:03 +0100 Subject: [PATCH 04/12] Add "-no-banner" flag --- src/cli.yml | 3 +++ src/main.rs | 5 ++++- 2 files changed, 7 insertions(+), 1 deletion(-) diff --git a/src/cli.yml b/src/cli.yml index 739a97d..d29b8d7 100644 --- a/src/cli.yml +++ b/src/cli.yml @@ -9,6 +9,9 @@ args: short: c long: config takes_value: true + - no-banner: + help: "Disable showing the banner on start" + long: no-banner subcommands: - init: about: "Initialize Lucid and generate configuration file" diff --git a/src/main.rs b/src/main.rs index b094440..6f45479 100644 --- a/src/main.rs +++ b/src/main.rs @@ -71,7 +71,6 @@ fn main() -> Result<(), Error> { .expect("Couldn't start logger"); log::set_max_level(LevelFilter::Debug); - println!("{}", BANNER); let long_version = format!("{}\n{}", crate_version!(), CREDITS); let cli_yaml = load_yaml!("cli.yml"); @@ -96,6 +95,10 @@ fn main() -> Result<(), Error> { Err(e) => return Err(Error::ParseCli { source: e }), }; + if !matches.is_present("no-banner") { + println!("{}", BANNER); + } + let config_path = { if let Some(config) = matches.value_of("config") { Path::new(config).to_path_buf() From 4ccc11fed652b63f01b8d38a487e241449b350f7 Mon Sep 17 00:00:00 2001 From: CephalonRho Date: Sun, 19 Jan 2020 12:05:25 +0100 Subject: [PATCH 05/12] Add author to subcommands --- src/cli.yml | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/src/cli.yml b/src/cli.yml index d29b8d7..cbca19d 100644 --- a/src/cli.yml +++ b/src/cli.yml @@ -1,5 +1,5 @@ name: "Lucid" -author: "Written in Rust by Clint.Network (twitter.com/clint_network)" +author: &author "Written in Rust by Clint.Network (twitter.com/clint_network)" settings: - GlobalVersion - SubcommandRequiredElseHelp @@ -15,6 +15,7 @@ args: subcommands: - init: about: "Initialize Lucid and generate configuration file" + author: *author args: - secret: help: "Set the JWT secret" @@ -27,4 +28,5 @@ subcommands: long: force takes_value: false - server: - about: "Run a new Lucid server instance" \ No newline at end of file + about: "Run a new Lucid server instance" + author: *author \ No newline at end of file From 6e2eb12f0cba8fcf7948093a085fd1905232bcce Mon Sep 17 00:00:00 2001 From: CephalonRho Date: Sun, 19 Jan 2020 12:10:41 +0100 Subject: [PATCH 06/12] Fix inconsistent quoting in cli.yml --- src/cli.yml | 18 +++++++++--------- 1 file changed, 9 insertions(+), 9 deletions(-) diff --git a/src/cli.yml b/src/cli.yml index cbca19d..51e2aa9 100644 --- a/src/cli.yml +++ b/src/cli.yml @@ -1,17 +1,17 @@ name: "Lucid" author: &author "Written in Rust by Clint.Network (twitter.com/clint_network)" settings: - - GlobalVersion - - SubcommandRequiredElseHelp + - "GlobalVersion" + - "SubcommandRequiredElseHelp" args: - config: help: "Specify the Lucid configuration file" - short: c - long: config + short: "c" + long: "config" takes_value: true - no-banner: help: "Disable showing the banner on start" - long: no-banner + long: "no-banner" subcommands: - init: about: "Initialize Lucid and generate configuration file" @@ -19,13 +19,13 @@ subcommands: args: - secret: help: "Set the JWT secret" - short: s - long: secret + short: "s" + long: "secret" takes_value: true - force: help: "Initialize Lucid and overwrite existing configuration file" - short: f - long: force + short: "f" + long: "force" takes_value: false - server: about: "Run a new Lucid server instance" From d0c43483f3d2b68bb136bd6a87c02034d7365c47 Mon Sep 17 00:00:00 2001 From: CephalonRho Date: Sun, 19 Jan 2020 12:42:02 +0100 Subject: [PATCH 07/12] Fix showing "Error: " when Lucid is run with no arguments --- src/main.rs | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/src/main.rs b/src/main.rs index 6f45479..192498f 100644 --- a/src/main.rs +++ b/src/main.rs @@ -88,6 +88,11 @@ fn main() -> Result<(), Error> { kind: clap::ErrorKind::VersionDisplayed, message, .. + }) + | Err(clap::Error { + kind: clap::ErrorKind::MissingArgumentOrSubcommand, + message, + .. }) => { println!("{}", message); return Ok(()); From d39d6008ea8b9c84c44495245e9e9eba48f612d4 Mon Sep 17 00:00:00 2001 From: CephalonRho Date: Sun, 19 Jan 2020 13:00:10 +0100 Subject: [PATCH 08/12] Add "settings" subcommand --- src/cli.yml | 4 +++- src/main.rs | 7 +++++++ 2 files changed, 10 insertions(+), 1 deletion(-) diff --git a/src/cli.yml b/src/cli.yml index 51e2aa9..fc2a14b 100644 --- a/src/cli.yml +++ b/src/cli.yml @@ -29,4 +29,6 @@ subcommands: takes_value: false - server: about: "Run a new Lucid server instance" - author: *author \ No newline at end of file + author: *author + - settings: + about: "Manage the Lucid configuration file" \ No newline at end of file diff --git a/src/main.rs b/src/main.rs index 192498f..0c49141 100644 --- a/src/main.rs +++ b/src/main.rs @@ -142,6 +142,13 @@ fn main() -> Result<(), Error> { return Err(Error::ConfigurationNotFound); } } + if let Some(_) = matches.subcommand_matches("settings") { + if config_path.exists() { + println!("Configuration location: {:?}\n\n{}", &config_path, fs::read_to_string(&config_path).context(OpenConfigFile)?); + } else { + return Err(Error::ConfigurationNotFound); + } + } Ok(()) } From e84597acabfe068141f1e99654b1b06475fb8bd7 Mon Sep 17 00:00:00 2001 From: CephalonRho Date: Sun, 19 Jan 2020 13:20:38 +0100 Subject: [PATCH 09/12] Fix missing "author" in settings subcommand --- src/cli.yml | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/cli.yml b/src/cli.yml index fc2a14b..50d0ec0 100644 --- a/src/cli.yml +++ b/src/cli.yml @@ -31,4 +31,5 @@ subcommands: about: "Run a new Lucid server instance" author: *author - settings: - about: "Manage the Lucid configuration file" \ No newline at end of file + about: "Manage the Lucid configuration file" + author: *author \ No newline at end of file From 18f2d19ff8dc7d9cb676ea9d5493e5993cfd7cdd Mon Sep 17 00:00:00 2001 From: "Clint.Network" Date: Sun, 19 Jan 2020 17:31:07 +0100 Subject: [PATCH 10/12] Minor changes --- src/cli.yml | 2 +- src/main.rs | 6 +++--- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/src/cli.yml b/src/cli.yml index 50d0ec0..d118479 100644 --- a/src/cli.yml +++ b/src/cli.yml @@ -1,5 +1,5 @@ name: "Lucid" -author: &author "Written in Rust by Clint.Network (twitter.com/clint_network)" +author: &author "Written in Rust by Clint.Network (https://github.com/lucid-kv)" settings: - "GlobalVersion" - "SubcommandRequiredElseHelp" diff --git a/src/main.rs b/src/main.rs index 0c49141..178f89b 100644 --- a/src/main.rs +++ b/src/main.rs @@ -31,7 +31,7 @@ use snafu::{ResultExt, Snafu}; const APP_INFO: AppInfo = AppInfo { name: "lucid", - author: "Clint.Network", + author: "LucidKV", }; const BANNER: &'static str = r###" @@ -42,7 +42,7 @@ const BANNER: &'static str = r###" ╚═════╝ ╚═════╝ ╚═════╝╚═╝╚═════╝ ╚═╝ ╚═╝ ╚═══╝ A Fast, Secure and Distributed KV store with an HTTP API. -Written in Rust by Clint.Network (twitter.com/clint_network) +Written in Rust by Clint.Network (https://github.com/lucid-kv) "###; const CREDITS: &'static str = "\ @@ -71,7 +71,7 @@ fn main() -> Result<(), Error> { .expect("Couldn't start logger"); log::set_max_level(LevelFilter::Debug); - let long_version = format!("{}\n{}", crate_version!(), CREDITS); + let long_version = format!("{}\n{}\n\nYou can send a tips here: 3BxEYn4RZ3iYETcFpN7nA6VqCY4Hz1tSUK", crate_version!(), CREDITS); let cli_yaml = load_yaml!("cli.yml"); let app = App::from_yaml(&cli_yaml) From 38736388d4120aba39081341cd316d13e5517abd Mon Sep 17 00:00:00 2001 From: CephalonRho Date: Sun, 19 Jan 2020 20:29:55 +0100 Subject: [PATCH 11/12] Add space to banner in main.rs --- src/main.rs | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/src/main.rs b/src/main.rs index 178f89b..8a7895e 100644 --- a/src/main.rs +++ b/src/main.rs @@ -35,11 +35,11 @@ const APP_INFO: AppInfo = AppInfo { }; const BANNER: &'static str = r###" -██╗ ██╗ ██╗ ██████╗██╗██████╗ ██╗ ██╗██╗ ██╗ -██║ ██║ ██║██╔════╝██║██╔══██╗ ██║ ██╔╝██║ ██║ -██║ ██║ ██║██║ ██║██║ ██║ ██╔═██╗ ╚██╗ ██╔╝ -██████╗╚██████╔╝╚██████╗██║██████╔╝ ██║ ██╗ ╚████╔╝ -╚═════╝ ╚═════╝ ╚═════╝╚═╝╚═════╝ ╚═╝ ╚═╝ ╚═══╝ + ██╗ ██╗ ██╗ ██████╗██╗██████╗ ██╗ ██╗██╗ ██╗ + ██║ ██║ ██║██╔════╝██║██╔══██╗ ██║ ██╔╝██║ ██║ + ██║ ██║ ██║██║ ██║██║ ██║ ██╔═██╗ ╚██╗ ██╔╝ + ██████╗╚██████╔╝╚██████╗██║██████╔╝ ██║ ██╗ ╚████╔╝ + ╚═════╝ ╚═════╝ ╚═════╝╚═╝╚═════╝ ╚═╝ ╚═╝ ╚═══╝ A Fast, Secure and Distributed KV store with an HTTP API. Written in Rust by Clint.Network (https://github.com/lucid-kv) From 98353ce29f20c2748c4ffb17ae4119f1da7b139a Mon Sep 17 00:00:00 2001 From: CephalonRho Date: Sun, 19 Jan 2020 20:40:42 +0100 Subject: [PATCH 12/12] Add banner to help message --- src/cli.yml | 16 +++++++++++++++- 1 file changed, 15 insertions(+), 1 deletion(-) diff --git a/src/cli.yml b/src/cli.yml index d118479..158367f 100644 --- a/src/cli.yml +++ b/src/cli.yml @@ -1,5 +1,16 @@ name: "Lucid" author: &author "Written in Rust by Clint.Network (https://github.com/lucid-kv)" +template: &template |-4 + ██╗ ██╗ ██╗ ██████╗██╗██████╗ ██╗ ██╗██╗ ██╗ + ██║ ██║ ██║██╔════╝██║██╔══██╗ ██║ ██╔╝██║ ██║ + ██║ ██║ ██║██║ ██║██║ ██║ ██╔═██╗ ╚██╗ ██╔╝ + ██████╗╚██████╔╝╚██████╗██║██████╔╝ ██║ ██╗ ╚████╔╝ + ╚═════╝ ╚═════╝ ╚═════╝╚═╝╚═════╝ ╚═╝ ╚═╝ ╚═══╝ + + A Fast, Secure and Distributed KV store with an HTTP API. + Written in Rust by Clint.Network (twitter.com/clint_network) + + {all-args} settings: - "GlobalVersion" - "SubcommandRequiredElseHelp" @@ -16,6 +27,7 @@ subcommands: - init: about: "Initialize Lucid and generate configuration file" author: *author + template: *template args: - secret: help: "Set the JWT secret" @@ -30,6 +42,8 @@ subcommands: - server: about: "Run a new Lucid server instance" author: *author + template: *template - settings: about: "Manage the Lucid configuration file" - author: *author \ No newline at end of file + author: *author + template: *template \ No newline at end of file