diff --git a/Cargo.lock b/Cargo.lock index 8d3b284..f398c67 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -449,6 +449,7 @@ dependencies = [ "textwrap 0.11.0 (registry+https://github.com/rust-lang/crates.io-index)", "unicode-width 0.1.7 (registry+https://github.com/rust-lang/crates.io-index)", "vec_map 0.8.1 (registry+https://github.com/rust-lang/crates.io-index)", + "yaml-rust 0.3.5 (registry+https://github.com/rust-lang/crates.io-index)", ] [[package]] @@ -1897,6 +1898,11 @@ dependencies = [ "winapi-build 0.1.1 (registry+https://github.com/rust-lang/crates.io-index)", ] +[[package]] +name = "yaml-rust" +version = "0.3.5" +source = "registry+https://github.com/rust-lang/crates.io-index" + [[package]] name = "yaml-rust" version = "0.4.3" @@ -2112,4 +2118,5 @@ dependencies = [ "checksum winreg 0.6.2 (registry+https://github.com/rust-lang/crates.io-index)" = "b2986deb581c4fe11b621998a5e53361efe6b48a151178d0cd9eeffa4dc6acc9" "checksum winutil 0.1.1 (registry+https://github.com/rust-lang/crates.io-index)" = "7daf138b6b14196e3830a588acf1e86966c694d3e8fb026fb105b8b5dca07e6e" "checksum ws2_32-sys 0.2.1 (registry+https://github.com/rust-lang/crates.io-index)" = "d59cefebd0c892fa2dd6de581e937301d8552cb44489cdff035c6187cb63fa5e" +"checksum yaml-rust 0.3.5 (registry+https://github.com/rust-lang/crates.io-index)" = "e66366e18dc58b46801afbf2ca7661a9f59cc8c5962c29892b6039b4f86fa992" "checksum yaml-rust 0.4.3 (registry+https://github.com/rust-lang/crates.io-index)" = "65923dd1784f44da1d2c3dbbc5e822045628c590ba72123e1c73d3c230c4434d" diff --git a/Cargo.toml b/Cargo.toml index 847081a..c713ed1 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -19,7 +19,7 @@ itertools = "0.8" libc = "0.2" log = "0.4" simple_logger = "1.3" -clap = "2.33" +clap = { version = "2.33", features = ["yaml"] } [profile.release] lto = true diff --git a/src/cli.yaml b/src/cli.yaml new file mode 100644 index 0000000..45cda6b --- /dev/null +++ b/src/cli.yaml @@ -0,0 +1,25 @@ +name: "bunbun" +about: "Search/jump multiplexer service" + +args: + - verbose: + short: "v" + long: "verbose" + multiple: true + help: Increases the log level to info, debug, and trace, respectively. + conflicts_with: "quiet" + - quiet: + short: "q" + long: "quiet" + multiple: true + help: Decreases the log level to error or no logging at all, respectively. + conflicts_with: "verbose" + - daemon: + short: "d" + long: "daemon" + help: "Run bunbun as a daemon." + - config: + short: "c" + long: "config" + default_value: "/etc/bunbun.toml" + help: Specify the location of the config file to read from. Needs read/write permissions. diff --git a/src/main.rs b/src/main.rs index 0bf794e..c0e27d2 100644 --- a/src/main.rs +++ b/src/main.rs @@ -1,5 +1,6 @@ use actix_web::middleware::Logger; use actix_web::{App, HttpServer}; +use clap::{crate_authors, crate_version, load_yaml, App as ClapApp}; use handlebars::Handlebars; use hotwatch::{Event, Hotwatch}; use libc::daemon; @@ -16,7 +17,6 @@ mod routes; mod template_args; static DEFAULT_CONFIG: &[u8] = include_bytes!("../bunbun.default.toml"); -static CONFIG_FILE: &str = "bunbun.toml"; #[derive(Debug)] #[allow(clippy::enum_variant_names)] @@ -65,10 +65,31 @@ pub struct State { } fn main() -> Result<(), BunBunError> { - simple_logger::init_with_level(log::Level::Info)?; - // simple_logger::init()?; + let yaml = load_yaml!("cli.yaml"); + let matches = ClapApp::from(yaml) + .version(crate_version!()) + .author(crate_authors!()) + .get_matches(); - let conf = read_config(CONFIG_FILE)?; + let log_level = match ( + matches.occurrences_of("quiet"), + matches.occurrences_of("verbose"), + ) { + (2..=std::u64::MAX, _) => None, + (1, _) => Some(log::Level::Error), + (0, 0) => Some(log::Level::Warn), + (_, 1) => Some(log::Level::Info), + (_, 2) => Some(log::Level::Debug), + (_, 3..=std::u64::MAX) => Some(log::Level::Trace), + }; + + if let Some(level) = log_level { + simple_logger::init_with_level(level)?; + } + + // config has default location provided + let conf_file_location = String::from(matches.value_of("config").unwrap()); + let conf = read_config(&conf_file_location)?; let renderer = compile_templates(); let state = Arc::from(RwLock::new(State { public_address: conf.public_address, @@ -76,16 +97,28 @@ fn main() -> Result<(), BunBunError> { routes: conf.routes, renderer, })); - let state_ref = state.clone(); + + // Daemonize after trying to read from config and before watching; allow user + // to see a bad config (daemon process sets std{in,out} to /dev/null) + if matches.is_present("daemon") { + unsafe { + debug!("Daemon flag provided. Running as a daemon."); + daemon(0, 0); + } + } let mut watch = Hotwatch::new_with_custom_delay(Duration::from_millis(500))?; + // TODO: keep retry watching in separate thread - watch.watch(CONFIG_FILE, move |e: Event| { + // Closures need their own copy of variables for proper lifecycle management + let state_ref = state.clone(); + let conf_file_location_clone = conf_file_location.clone(); + let watch_result = watch.watch(&conf_file_location, move |e: Event| { if let Event::Write(_) = e { trace!("Grabbing writer lock on state..."); let mut state = state.write().unwrap(); trace!("Obtained writer lock on state!"); - match read_config(CONFIG_FILE) { + match read_config(&conf_file_location_clone) { Ok(conf) => { state.public_address = conf.public_address; state.default_route = conf.default_route; @@ -97,13 +130,15 @@ fn main() -> Result<(), BunBunError> { } else { debug!("Saw event {:#?} but ignored it", e); } - })?; + }); - info!("Watcher is now watching {}", CONFIG_FILE); - - // unsafe { - // daemon(0, 0); - // } + match watch_result { + Ok(_) => info!("Watcher is now watching {}", &conf_file_location), + Err(e) => warn!( + "Couldn't watch {}: {}. Changes to this file won't be seen!", + &conf_file_location, e + ), + } HttpServer::new(move || { App::new()