From b370feac42f539021d2bf3a968a2bac9f52cd1c9 Mon Sep 17 00:00:00 2001 From: Martin Habovstiak Date: Fri, 24 Dec 2021 11:32:23 +0100 Subject: [PATCH] Configuration cleanup This change makes `configure_me` do the job of reading environment variables, significantly reducing the required code. It also changes use of `String` to `PathBuf` where appropriate and removes some string manipulations. --- config_spec.toml | 18 ++++-- dbif/src/lib.rs | 31 ++++----- src/main.rs | 165 +++++++---------------------------------------- 3 files changed, 55 insertions(+), 159 deletions(-) diff --git a/config_spec.toml b/config_spec.toml index 6374003..b9af796 100644 --- a/config_spec.toml +++ b/config_spec.toml @@ -1,24 +1,34 @@ +[general] +env_prefix = "HELIPAD" +conf_file_param = "conf" +conf_dir_param = "conf_dir" + [[param]] name = "database_dir" -type = "String" +type = "std::path::PathBuf" doc = "The location of the database file." +default = "std::path::PathBuf::from(\"database.db\")" [[param]] name = "macaroon" -type = "String" +type = "std::path::PathBuf" doc = "The location of the macaroon file." +default = "std::path::PathBuf::from(\"/lnd/data/chain/bitcoin/mainnet/admin.macaroon\")" [[param]] name = "cert" -type = "String" +type = "std::path::PathBuf" doc = "The location of the tls certificate file." +default = "std::path::PathBuf::from(\"/lnd/tls.cert\")" [[param]] name = "listen_port" type = "u16" doc = "The port to listen on." +default = "2112" [[param]] name = "lnd_url" type = "String" -doc = "The url and port of the LND grpc api." \ No newline at end of file +doc = "The url and port of the LND grpc api." +default = "\"https://127.0.0.1:10009\".to_owned()" diff --git a/dbif/src/lib.rs b/dbif/src/lib.rs index 556da59..40d2089 100644 --- a/dbif/src/lib.rs +++ b/dbif/src/lib.rs @@ -3,6 +3,7 @@ use std::error::Error; use std::fmt; use serde::{Deserialize, Serialize}; use std::os::unix::fs::PermissionsExt; +use std::path::Path; #[derive(Serialize, Deserialize, Debug)] @@ -31,26 +32,26 @@ impl fmt::Display for HydraError { impl Error for HydraError {} -fn connect_to_database(init: bool, filepath: &String) -> Result> { - if let Ok(conn) = Connection::open(filepath.as_str()) { +fn connect_to_database(init: bool, filepath: &Path) -> Result> { + if let Ok(conn) = Connection::open(filepath) { if init { - match set_database_file_permissions(filepath.as_str()) { + match set_database_file_permissions(filepath) { Ok(_) => {}, Err(e) => { eprintln!("{:#?}", e); } } - println!("Using database file: [{}]", filepath.as_str()); + println!("Using database file: [{}]", filepath.display()); } Ok(conn) } else { - return Err(Box::new(HydraError(format!("Could not open a database file at: [{}].", filepath).into()))) + return Err(Box::new(HydraError(format!("Could not open a database file at: [{}].", filepath.display()).into()))) } } //Set permissions on the database file -fn set_database_file_permissions(filepath: &str) -> Result> { +fn set_database_file_permissions(filepath: &Path) -> Result> { match std::fs::File::open(filepath) { Ok(fh) => { @@ -58,23 +59,23 @@ fn set_database_file_permissions(filepath: &str) -> Result> Ok(metadata) => { let mut perms = metadata.permissions(); perms.set_mode(0o666); - println!("Set file permission to: [666] on database file: [{}]", filepath); + println!("Set file permission to: [666] on database file: [{}]", filepath.display()); Ok(true) }, Err(e) => { - return Err(Box::new(HydraError(format!("Error getting metadata from database file handle: [{}]. Error: {:#?}.", filepath, e).into()))) + return Err(Box::new(HydraError(format!("Error getting metadata from database file handle: [{}]. Error: {:#?}.", filepath.display(), e).into()))) } } }, Err(e) => { - return Err(Box::new(HydraError(format!("Error opening database file handle: [{}] for permissions setting. Error: {:#?}.", filepath, e).into()))) + return Err(Box::new(HydraError(format!("Error opening database file handle: [{}] for permissions setting. Error: {:#?}.", filepath.display(), e).into()))) } } } //Create a new database file if needed -pub fn create_database(filepath: &String) -> Result> { +pub fn create_database(filepath: &Path) -> Result> { let conn = connect_to_database(true, filepath)?; match conn.execute( @@ -98,14 +99,14 @@ pub fn create_database(filepath: &String) -> Result> { } Err(e) => { eprintln!("{}", e); - return Err(Box::new(HydraError(format!("Failed to create database: [{}].", filepath).into()))) + return Err(Box::new(HydraError(format!("Failed to create database: [{}].", filepath.display()).into()))) } } } //Add an invoice to the database -pub fn add_invoice_to_db(filepath: &String, boost: BoostRecord) -> Result> { +pub fn add_invoice_to_db(filepath: &Path, boost: BoostRecord) -> Result> { let conn = connect_to_database(false, filepath)?; match conn.execute("INSERT INTO boosts (idx, time, value_msat, value_msat_total, action, sender, app, message, podcast, episode, tlv) \ @@ -134,7 +135,7 @@ pub fn add_invoice_to_db(filepath: &String, boost: BoostRecord) -> Result Result, Box> { +pub fn get_boosts_from_db(filepath: &Path, index: u64, max: u64, direction: bool) -> Result, Box> { let conn = connect_to_database(false, filepath)?; let mut boosts: Vec = Vec::new(); @@ -189,7 +190,7 @@ pub fn get_boosts_from_db(filepath: &String, index: u64, max: u64, direction: bo //Get the last boost index number from the database -pub fn get_last_boost_index_from_db(filepath: &String) -> Result> { +pub fn get_last_boost_index_from_db(filepath: &Path) -> Result> { let conn = connect_to_database(false, filepath)?; let mut boosts: Vec = Vec::new(); let max = 1; @@ -225,4 +226,4 @@ pub fn get_last_boost_index_from_db(filepath: &String) -> Result; type Error = Box; const HELIPAD_CONFIG_FILE: &str = "./helipad.conf"; -const HELIPAD_DATABASE_DIR: &str = "database.db"; -const HELIPAD_STANDARD_PORT: &str = "2112"; -const LND_STANDARD_GRPC_URL: &str = "https://127.0.0.1:10009"; -const LND_STANDARD_MACAROON_LOCATION: &str = "/lnd/data/chain/bitcoin/mainnet/admin.macaroon"; -const LND_STANDARD_TLSCERT_LOCATION: &str = "/lnd/tls.cert"; //Structs ---------------------------------------------------------------------------------------------------- //------------------------------------------------------------------------------------------------------------ @@ -45,21 +41,13 @@ pub struct AppState { pub remote_ip: String, } -#[derive(Clone, Debug)] -pub struct HelipadConfig { - pub database_file_path: String, - pub listen_port: String, - pub macaroon_path: String, - pub cert_path: String, -} - #[derive(Debug)] pub struct Context { pub state: AppState, pub req: Request, pub path: String, pub params: Params, - pub database_file_path: String, + pub database_file_path: PathBuf, body_bytes: Option, } @@ -160,14 +148,6 @@ async fn main() { println!("Version: {}", version); println!("--------------------"); - //Configuration - let mut helipad_config = HelipadConfig { - database_file_path: "".to_string(), - listen_port: "".to_string(), - macaroon_path: "".to_string(), - cert_path: "".to_string(), - }; - //Bring in the configuration info let (server_config, _remaining_args) = Config::including_optional_config_files(&[HELIPAD_CONFIG_FILE]).unwrap_or_exit(); @@ -177,49 +157,8 @@ async fn main() { println!("Config file(macaroon): {:#?}", server_config.macaroon); println!("Config file(cert): {:#?}", server_config.cert); - //LISTEN PORT ----- - println!("\nDiscovering listen port..."); - let mut listen_port = String::from(HELIPAD_STANDARD_PORT); - let args: Vec = env::args().collect(); - let env_listen_port = std::env::var("HELIPAD_LISTEN_PORT"); - //First try from the environment - if env_listen_port.is_ok() { - listen_port = env_listen_port.unwrap(); - println!(" - Using environment var(HELIPAD_LISTEN_PORT): [{}]", listen_port); - } else if server_config.listen_port.is_some() { - //If that fails, try from the config file - listen_port = server_config.listen_port.unwrap().to_string(); - println!(" - Using config file({}): [{}]", HELIPAD_CONFIG_FILE, listen_port); - } else if let Some(arg_port) = args.get(1) { - //If that fails, try from the command line - listen_port = arg_port.to_owned(); - println!(" - Using arg from command line: [{}]", listen_port); - } else { - //If everything fails, then just use the default port - println!(" - Nothing else found. Using default: [{}]...", listen_port); - } - helipad_config.listen_port = listen_port.clone(); - - //DATABASE FILE ----- - //First try to get the database file location from the environment - println!("\nDiscovering database location..."); - let env_database_file_path = std::env::var("HELIPAD_DATABASE_DIR"); - if env_database_file_path.is_ok() { - helipad_config.database_file_path = env_database_file_path.unwrap(); - println!(" - Using environment var(HELIPAD_DATABASE_DIR): [{}]", helipad_config.database_file_path); - } else { - //If that fails, try to get it from the config file - if server_config.database_dir.is_some() { - helipad_config.database_file_path = server_config.database_dir.clone().unwrap().to_string(); - println!(" - Using config file({}): [{}]", HELIPAD_CONFIG_FILE, helipad_config.database_file_path); - } else { - //If that fails just fall back to the local directory - helipad_config.database_file_path = HELIPAD_DATABASE_DIR.to_string(); - println!(" - Nothing else found. Using default: [{}]", helipad_config.database_file_path); - } - } //Create the database file - match dbif::create_database(&helipad_config.database_file_path) { + match dbif::create_database(&server_config.database_dir) { Ok(_) => { println!("Database file is ready..."); } @@ -229,9 +168,12 @@ async fn main() { } } + let db_filepath = server_config.database_dir.clone(); + let listen_port = server_config.listen_port; + //Start the LND polling thread. This thread will poll LND every few seconds to //get the latest invoices and store them in the database. - tokio::spawn(lnd_poller(server_config, helipad_config.database_file_path.clone())); + tokio::spawn(lnd_poller(server_config)); //Router let some_state = "state".to_string(); @@ -252,7 +194,6 @@ async fn main() { //router.get("/streams", Box::new(handler::streams)); let shared_router = Arc::new(router); - let db_filepath: String = helipad_config.database_file_path.clone(); let new_service = make_service_fn(move |conn: &AddrStream| { let app_state = AppState { state_thing: some_state.clone(), @@ -269,8 +210,7 @@ async fn main() { } }); - let binding = format!("0.0.0.0:{}", &listen_port); - let addr = binding.parse().expect("address creation works"); + let addr = ([0, 0, 0, 0], listen_port).into(); let server = Server::bind(&addr).serve(new_service); println!("\nHelipad is listening on http://{}", addr); @@ -299,7 +239,7 @@ async fn route( router: Arc, req: Request, app_state: AppState, - database_file_path: String, + database_file_path: PathBuf, ) -> Result { let found_handler = router.route(req.uri().path(), req.method()); let path = req.uri().path().to_owned(); @@ -311,7 +251,7 @@ async fn route( } impl Context { - pub fn new(state: AppState, reqbody: Request, path: &str, params: Params, database_file_path: String) -> Context { + pub fn new(state: AppState, reqbody: Request, path: &str, params: Params, database_file_path: PathBuf) -> Context { Context { state: state, req: reqbody, @@ -336,27 +276,12 @@ impl Context { } //The LND poller runs in a thread and pulls new invoices -async fn lnd_poller(server_config: Config, database_file_path: String) { - - let db_filepath = database_file_path; - - //Get the macaroon and cert files. Look in the local directory first as an override. - //If the files are not found in the currect working directory, look for them at their - //normal LND directory locations - println!("\nDiscovering macaroon file path..."); - let macaroon_path; - let env_macaroon_path = std::env::var("LND_ADMINMACAROON"); - //First try from the environment - if env_macaroon_path.is_ok() { - macaroon_path = env_macaroon_path.unwrap(); - println!(" - Trying environment var(LND_ADMINMACAROON): [{}]", macaroon_path); - } else if server_config.macaroon.is_some() { - macaroon_path = server_config.macaroon.unwrap(); - println!(" - Trying config file({}): [{}]", HELIPAD_CONFIG_FILE, macaroon_path); - } else { - macaroon_path = "admin.macaroon".to_string(); - println!(" - Trying current directory: [{}]", macaroon_path); - } +async fn lnd_poller(server_config: Config) { + + let db_filepath = server_config.database_dir; + + println!("\nReading macaroon file..."); + let macaroon_path = server_config.macaroon; let macaroon: Vec; match fs::read(macaroon_path.clone()) { Ok(macaroon_content) => { @@ -364,33 +289,13 @@ async fn lnd_poller(server_config: Config, database_file_path: String) { macaroon = macaroon_content; } Err(_) => { - println!(" - Error reading macaroon from: [{}]", macaroon_path); - println!(" - Last fallback attempt: [{}]", LND_STANDARD_MACAROON_LOCATION); - match fs::read(LND_STANDARD_MACAROON_LOCATION) { - Ok(macaroon_content) => { - macaroon = macaroon_content; - } - Err(_) => { - eprintln!("Cannot find a valid admin.macaroon file"); - std::process::exit(1); - } - } + println!(" - Error reading macaroon from: [{}]", macaroon_path.display()); + std::process::exit(1); } } - println!("\nDiscovering certificate file path..."); - let cert_path; - let env_cert_path = std::env::var("LND_TLSCERT"); - if env_cert_path.is_ok() { - cert_path = env_cert_path.unwrap(); - println!(" - Trying environment var(LND_TLSCERT): [{}]", cert_path); - } else if server_config.cert.is_some() { - cert_path = server_config.cert.unwrap(); - println!(" - Trying config file({}): [{}]", HELIPAD_CONFIG_FILE, cert_path); - } else { - cert_path = "tls.cert".to_string(); - println!(" - Trying current directory: [{}]", cert_path); - } + println!("\nReading certificate file..."); + let cert_path = server_config.cert; let cert: Vec; match fs::read(cert_path.clone()) { Ok(cert_content) => { @@ -398,34 +303,14 @@ async fn lnd_poller(server_config: Config, database_file_path: String) { cert = cert_content; } Err(_) => { - println!(" - Error reading certificate from: [{}]", cert_path); - println!(" - Last fallback attempt: [{}]", LND_STANDARD_TLSCERT_LOCATION); - match fs::read(LND_STANDARD_TLSCERT_LOCATION) { - Ok(cert_content) => { - cert = cert_content; - } - Err(_) => { - eprintln!("Cannot find a valid tls.cert file"); - std::process::exit(2); - } - } + println!(" - Error reading certificate from: [{}]", cert_path.display()); + std::process::exit(2); } } //Get the url connection string of the lnd node println!("\nDiscovering LND node address..."); - let node_address; - let env_lnd_url = std::env::var("LND_URL"); - if env_lnd_url.is_ok() { - node_address = "https://".to_owned() + env_lnd_url.unwrap().as_str(); - println!(" - Trying environment var(LND_URL): [{}]", node_address); - } else if server_config.lnd_url.is_some() { - node_address = server_config.lnd_url.unwrap(); - println!(" - Trying config file({}): [{}]", HELIPAD_CONFIG_FILE, node_address); - } else { - node_address = String::from(LND_STANDARD_GRPC_URL); - println!(" - Trying localhost default: [{}].", node_address); - } + let node_address = server_config.lnd_url; //Make the connection to LND let mut lightning; @@ -435,7 +320,7 @@ async fn lnd_poller(server_config: Config, database_file_path: String) { lightning = lndconn; } Err(e) => { - println!("Could not connect to: [{}] using tls: [{}] and macaroon: [{}]", node_address, cert_path, macaroon_path); + println!("Could not connect to: [{}] using tls: [{}] and macaroon: [{}]", node_address, cert_path.display(), macaroon_path.display()); eprintln!("{:#?}", e); std::process::exit(1); } @@ -544,4 +429,4 @@ async fn lnd_poller(server_config: Config, database_file_path: String) { std::thread::sleep(std::time::Duration::from_millis(9000)); } -} \ No newline at end of file +}