#[macro_use] extern crate ini; extern crate exitcode; use std::env; use std::path::Path; use std::process::Command; use std::thread; use std::time::Duration; use sysinfo::{System, SystemExt}; /// Rule description structure pub struct Rule { /// Monitored service name pub service: String, /// Monitored URI pub uri: String, /// Monitored process name pub process: String, /// E-mail address for messages pub email: String, /// This command will be executed on state change pub command: String, /// Last state pub last_state: bool, } /// Check rule /// /// # Arguments /// /// * `rule` Rule to be checked /// /// # Example /// /// ``` /// if check(&tasks[i]).await { /// println!("ok"); /// } else { /// println!("uh-oh... something wrong!"); /// } /// ``` async fn check(rule: &Rule) -> bool { return if rule.uri.to_string() != "".to_string() { check_uri(rule.uri.clone()).await } else { check_process(rule.process.clone()) }; } /// Check is process running /// /// # Arguments /// /// * `process_name` Process name to be checked /// /// # Example /// /// ``` /// if check_process("httpd".to_string()).await { /// println!("ok"); /// } else { /// println!("uh-oh... something wrong!"); /// } /// ``` fn check_process(process_name: String) -> bool { let mut sys = System::new_all(); let mut result: bool = false; sys.refresh_all(); for _process in sys.processes_by_exact_name(&process_name) { result = true; } return result; } /// Checks web-service availability /// /// # Arguments /// /// * `uri` - Service URI to be checked /// /// # Example /// /// ``` /// if check_uri("https://somesite.local/api/v1/".to_string()).await { /// println!("ok"); /// } else { /// println!("uh-oh... something wrong!"); /// } /// ``` async fn check_uri(uri: String) -> bool { if let Err(_e) = reqwest::get(uri).await { return false; } else { return true; } } /// Output to console only in debug version /// /// # Arguments /// /// * `text` - Message text /// /// # Example /// /// ``` /// debug_log("Debug version started".to_string()); /// ``` fn debug_log(text: String) { if cfg!(debug_assertions) { println!("{}", text); } } /// Execute shell command /// /// In linux sh command interpreter will be called. /// In Windows PowerShell command interpreter will be called. /// /// # Arguments /// /// * `command` - Command to be executed /// /// # Example /// /// ``` /// execute("gedit".to_string()); /// ``` fn execute(command: String) { debug_log(format!("execute {}", command)); let output = if cfg!(target_os = "windows") { Command::new("powershell") .args(["-NoLogo", "-NonInteractive", "-Command", &command]) .output() .expect("failed to execute process") } else { Command::new("sh") .arg("-c") .arg(&command) .output() .expect("failed to execute process") }; let result = output.stdout; debug_log(format!("{:?}", result)); } /// Print help and program version information /// /// # Arguments /// /// * `args` - Command-line arguments /// /// # Example /// /// ``` /// let args: Vec = env::args().collect(); /// /// print_help(args.clone()); /// ``` fn print_help(args: Vec) { if cfg!(windows) { if args.len() > 1 && (args[1].to_string() == "/help".to_string() || args[1].to_string() == "/?".to_string()) { println!(""); println!("Usage: {} [/? | /help | /v | /ver | config_file]", &args[0]); println!(" /? | /help : This help message"); println!(" /v | /ver : Version info"); println!(" config_file : Configuration file"); std::process::exit(exitcode::OK); } else if args.len() > 1 && (args[1].to_string() == "/ver".to_string() || args[1].to_string() == "/v".to_string()) { const VERSION: &str = env!("CARGO_PKG_VERSION"); println!(""); println!("CAI Watchdog ver {}", VERSION); std::process::exit(exitcode::OK); } } else { if args.len() > 1 && (args[1].to_string() == "--help".to_string() || args[1].to_string() == "-h".to_string()) { println!(""); println!("Usage: {} [-h | --help | -v | --ver | config_file]", &args[0]); println!(" -h | --help : This help message"); println!(" -v | --ver : Version info"); println!(" config_file : Configuration file"); std::process::exit(exitcode::OK); } else if args.len() > 1 && (args[1].to_string() == "--ver".to_string() || args[1].to_string() == "-v".to_string()) { const VERSION: &str = env!("CARGO_PKG_VERSION"); println!(""); println!("CAI Watchdog ver {}", VERSION); std::process::exit(exitcode::OK); } } } fn main() { let args: Vec = env::args().collect(); print_help(args.clone()); let mut just_started = true; if check_process("svchost.exe".to_string()) { println!("svchost is running"); } else { println!("svchost is not running"); } if check_process("httpd.exe".to_string()) { println!("httpd is running"); } else { println!("httpd is not running"); } let cfg_file = if args.len() > 1 { &args[1] } else if cfg!(windows) { "cai-watchdog.ini" } else { "/etc/cai-watchdog.conf" }; if !Path::new(cfg_file).exists() { println!("\u{26a0} Error! Can't find configuration file."); println!("Exiting..."); std::process::exit(exitcode::CONFIG); } let cfg = ini!(cfg_file); let check_interval = if cfg["main"].contains_key("check_interval") { cfg["main"]["check_interval"].clone().unwrap().parse::().unwrap() * 1000 } else { 10000 }; let rules_count = if cfg["main"].contains_key("check_interval") { cfg["main"]["rules_count"].clone().unwrap().parse::().unwrap() } else { 0 }; let mut tasks = vec![]; for i in 1..rules_count + 1 { let service = if cfg[&format!("{}{}", "rule", i)].contains_key("service") { cfg[&format!("{}{}", "rule", i)]["service"].clone().unwrap() } else { format!("Service {}", i) }; let uri = if cfg[&format!("{}{}", "rule", i)].contains_key("uri") { cfg[&format!("{}{}", "rule", i)]["uri"].clone().unwrap() } else { "".to_string() }; let process = if cfg[&format!("{}{}", "rule", i)].contains_key("process") { cfg[&format!("{}{}", "rule", i)]["process"].clone().unwrap() } else { "".to_string() }; let email = if cfg[&format!("{}{}", "rule", i)].contains_key("email") { cfg[&format!("{}{}", "rule", i)]["email"].clone().unwrap() } else { "".to_string() }; let command = if cfg[&format!("{}{}", "rule", i)].contains_key("command") { cfg[&format!("{}{}", "rule", i)]["command"].clone().unwrap() } else { "".to_string() }; debug_log(format!("rule {}", i)); debug_log(format!("service {}", service)); debug_log(format!("uri {}", uri)); debug_log(format!("process {}", process)); debug_log(format!("email {}", email)); debug_log(format!("command {}", command)); tasks.push( Rule{ service: service, uri: uri, process: process, email: email, command: command, last_state: false, } ); } let start_notification = if cfg["notifications"].contains_key("service_start") { cfg["notifications"]["service_start"].clone().unwrap().to_string() == "true".to_string() } else { true }; let notification_email = if cfg["notifications"].contains_key("email") { cfg["notifications"]["email"].clone().unwrap() } else { "".to_string() }; let notification_command = if cfg["notifications"].contains_key("command") { cfg["notifications"]["command"].clone().unwrap() } else { "".to_string() }; if start_notification && notification_command.to_string() != "" { debug_log("\u{2139} Service started".to_string()); let shell_cmd = notification_command.to_string() .replace("", ¬ification_email.to_string()) .replace("", "\"\u{2139} Watchdog service started\"") .replace("", "\"\u{2139} Watchdog service started\""); debug_log(format!("execute {}", shell_cmd)); execute(shell_cmd); } loop { for i in 0..tasks.len() { let rt = tokio::runtime::Runtime::new().unwrap(); rt.block_on(async { //if check(tasks[i].uri.clone()).await { if check(&tasks[i]).await { if tasks[i].last_state != true || just_started { if tasks[i].command.to_string() == "".to_string() { println!("\u{2705} {} state changed to true", tasks[i].uri); } else { debug_log(format!("\u{2705} {} state changed to true", tasks[i].uri)); let shell_cmd = if tasks[i].uri.to_string() != "".to_string() { tasks[i].command.to_string() .replace("", &tasks[i].email) .replace("", &format!("\"\u{2705} Service {} ({}) is online\"", tasks[i].service, tasks[i].uri)) .replace("", &format!("\"\u{2705} Service {} ({}) is online now\"", tasks[i].service, tasks[i].uri)) } else { tasks[i].command.to_string() .replace("", &tasks[i].email) .replace("", &format!("\"\u{2705} Process {} ({}) is running\"", tasks[i].service, tasks[i].process)) .replace("", &format!("\"\u{2705} Process {} ({}) is running now\"", tasks[i].service, tasks[i].process)) }; debug_log(format!("execute {}", shell_cmd)); execute(shell_cmd); } } if tasks[i].uri.to_string() != "".to_string() { debug_log(format!("\u{2705} {} check is ok", tasks[i].uri)); } else { debug_log(format!("\u{2705} {} is running", tasks[i].process)); } tasks[i].last_state = true; } else { if tasks[i].last_state != false || just_started { if tasks[i].command.to_string() == "".to_string() { println!("\u{274c} {} state changed to false", tasks[i].uri); } else { debug_log(format!("\u{274c} {} state changed to false", tasks[i].uri)); let shell_cmd = if tasks[i].uri.to_string() != "".to_string() { tasks[i].command.to_string() .replace("", &tasks[i].email) .replace("", &format!("\"\u{274c} Service {} ({}) is offline\"", tasks[i].service, tasks[i].uri)) .replace("", &format!("\"\u{274c} Service {} ({}) is now offline\"", tasks[i].service, tasks[i].uri)) } else { tasks[i].command.to_string() .replace("", &tasks[i].email) .replace("", &format!("\"\u{274c} Process {} ({}) is not running\"", tasks[i].service, tasks[i].process)) .replace("", &format!("\"\u{274c} Process {} ({}) is not running now\"", tasks[i].service, tasks[i].process)) }; debug_log(format!("execute {}", shell_cmd)); execute(shell_cmd); } } if tasks[i].uri.to_string() != "".to_string() { debug_log(format!("\u{274c} {} check failed", tasks[i].uri)); } else { debug_log(format!("\u{274c} {} is not running", tasks[i].process)); } tasks[i].last_state = false; } }); } just_started = false; thread::sleep(Duration::from_millis(check_interval)); } }