CAI-Watchdog/src/main.rs
2022-10-29 20:54:23 +03:00

397 lines
13 KiB
Rust

#[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
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<String> = env::args().collect();
///
/// print_help(args.clone());
/// ```
fn print_help(args: Vec<String>) {
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<String> = env::args().collect();
print_help(args.clone());
let mut just_started = true;
let cfg_file = if args.len() > 1 {
&args[1]
} else if cfg!(windows) {
"cai-watchdog.ini"
} else {
"/etc/cai-watchdog/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::<u64>().unwrap() * 1000
} else {
10000
};
let rules_count = if cfg["main"].contains_key("check_interval") {
cfg["main"]["rules_count"].clone().unwrap().parse::<u8>().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 on_start_command = if cfg["main"].contains_key("on_start_command") {
cfg["main"]["on_start_command"].clone().unwrap()
} else {
"".to_string()
};
debug_log("\u{2139} Service started".to_string());
if on_start_command.to_string() != "" {
execute(on_start_command);
}
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("<email>", &tasks[i].email)
.replace("<subject>", &format!("\"\u{2705} Service {} ({}) is online\"", tasks[i].service, tasks[i].uri))
.replace("<message>", &format!("\"\u{2705} Service {} ({}) is online now\"", tasks[i].service, tasks[i].uri))
.replace("<service>", &tasks[i].service)
.replace("<uri>", &tasks[i].uri)
.replace("<state", "online")
} else {
tasks[i].command.to_string()
.replace("<email>", &tasks[i].email)
.replace("<subject>", &format!("\"\u{2705} Process {} ({}) is running\"", tasks[i].service, tasks[i].process))
.replace("<message>", &format!("\"\u{2705} Process {} ({}) is running now\"", tasks[i].service, tasks[i].process))
.replace("<service>", &tasks[i].service)
.replace("<process>", &tasks[i].process)
.replace("<state>", "running")
};
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("<email>", &tasks[i].email)
.replace("<subject>", &format!("\"\u{274c} Service {} ({}) is offline\"", tasks[i].service, tasks[i].uri))
.replace("<message>", &format!("\"\u{274c} Service {} ({}) is offline now\"", tasks[i].service, tasks[i].uri))
.replace("<service>", &tasks[i].service)
.replace("<uri>", &tasks[i].uri)
.replace("<state>", "offline")
} else {
tasks[i].command.to_string()
.replace("<email>", &tasks[i].email)
.replace("<subject>", &format!("\"\u{274c} Process {} ({}) is not running\"", tasks[i].service, tasks[i].process))
.replace("<message>", &format!("\"\u{274c} Process {} ({}) is not running now\"", tasks[i].service, tasks[i].process))
.replace("<service>", &tasks[i].service)
.replace("<process>", &tasks[i].process)
.replace("<state>", "stopped")
};
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));
}
}