255 lines
8.1 KiB
Rust
255 lines
8.1 KiB
Rust
#[macro_use]
|
|
extern crate ini;
|
|
extern crate exitcode;
|
|
|
|
use std::env;
|
|
use std::thread;
|
|
use std::time::Duration;
|
|
use std::process::Command;
|
|
|
|
/// Rule description structure
|
|
pub struct Rule {
|
|
/// Monitored service name
|
|
pub service: String,
|
|
/// Monitored URI
|
|
pub uri: 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,
|
|
}
|
|
|
|
/// Checks service availability
|
|
///
|
|
/// # Arguments
|
|
///
|
|
/// * `uri` - Service URI to be checked
|
|
///
|
|
/// # Example
|
|
///
|
|
/// ```
|
|
/// if if check("https://somesite.local/api/v1/".to_string()).await {
|
|
/// println!("ok");
|
|
/// } else {
|
|
/// println!("uh-oh... something wrong!");
|
|
/// }
|
|
/// ```
|
|
async fn check(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 cfg_file = if args.len() > 1 {
|
|
&args[1]
|
|
} else if cfg!(windows) {
|
|
"cai-watchdog.ini"
|
|
} else {
|
|
"/etc/cai-watchdog.conf"
|
|
};
|
|
|
|
let cfg = ini!(cfg_file);
|
|
|
|
let check_interval = cfg["main"]["check_interval"].clone().unwrap().parse::<u64>().unwrap() * 1000;
|
|
let rules_count = cfg["main"]["rules_count"].clone().unwrap().parse::<u8>().unwrap();
|
|
|
|
let mut tasks = vec![];
|
|
|
|
for i in 1..rules_count + 1 {
|
|
let service = cfg[&format!("{}{}", "rule", i)]["service"].clone().unwrap();
|
|
let uri = cfg[&format!("{}{}", "rule", i)]["uri"].clone().unwrap();
|
|
let email = cfg[&format!("{}{}", "rule", i)]["email"].clone().unwrap();
|
|
let command = cfg[&format!("{}{}", "rule", i)]["command"].clone().unwrap();
|
|
|
|
debug_log(format!("rule {}", i));
|
|
debug_log(format!("service {}", service));
|
|
debug_log(format!("uri {}", uri));
|
|
debug_log(format!("email {}", email));
|
|
debug_log(format!("command {}", command));
|
|
|
|
tasks.push(
|
|
Rule{
|
|
service: cfg[&format!("{}{}", "rule", i)]["service"].clone().unwrap().to_string(),
|
|
uri: cfg[&format!("{}{}", "rule", i)]["uri"].clone().unwrap().to_string(),
|
|
email: cfg[&format!("{}{}", "rule", i)]["email"].clone().unwrap().to_string(),
|
|
command: cfg[&format!("{}{}", "rule", i)]["command"].clone().unwrap().to_string(),
|
|
last_state: false,
|
|
}
|
|
);
|
|
}
|
|
|
|
let start_notification = cfg["notifications"]["service_start"].clone().unwrap().to_string() == "true".to_string();
|
|
let notification_email = cfg["notifications"]["email"].clone().unwrap();
|
|
let notification_command = cfg["notifications"]["command"].clone().unwrap();
|
|
|
|
if start_notification {
|
|
debug_log(format!("Service started"));
|
|
|
|
let shell_cmd = notification_command.to_string()
|
|
.replace("<email>", ¬ification_email.to_string())
|
|
.replace("<subject>", &format!("\"Watchdog service started\""))
|
|
.replace("<message>", &format!("\"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 tasks[i].last_state != true {
|
|
debug_log(format!("{} state changed to true", tasks[i].uri));
|
|
|
|
let shell_cmd = tasks[i].command.to_string()
|
|
.replace("<email>", &tasks[i].email)
|
|
.replace("<subject>", &format!("\"Service {} ({}) is online\"", tasks[i].service, tasks[i].uri))
|
|
.replace("<message>", &format!("\"Service {} ({}) is now online\"", tasks[i].service, tasks[i].uri));
|
|
|
|
debug_log(format!("execute {}", shell_cmd));
|
|
|
|
execute(shell_cmd);
|
|
}
|
|
|
|
debug_log(format!("{} check is ok", tasks[i].uri));
|
|
|
|
tasks[i].last_state = true
|
|
} else {
|
|
if tasks[i].last_state != false {
|
|
debug_log(format!("{} state changed to false", tasks[i].uri));
|
|
|
|
let shell_cmd = tasks[i].command.to_string()
|
|
.replace("<email>", &tasks[i].email)
|
|
.replace("<subject>", &format!("\"Service {} ({}) is offline\"", tasks[i].service, tasks[i].uri))
|
|
.replace("<message>", &format!("\"Service {} ({}) is now offline\"", tasks[i].service, tasks[i].uri));
|
|
|
|
debug_log(format!("execute {}", shell_cmd));
|
|
execute(shell_cmd);
|
|
}
|
|
|
|
debug_log(format!("{} check failed", tasks[i].uri));
|
|
|
|
tasks[i].last_state = false
|
|
}
|
|
});
|
|
}
|
|
|
|
thread::sleep(Duration::from_millis(check_interval));
|
|
}
|
|
}
|