397 lines
13 KiB
Rust
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));
|
|
}
|
|
}
|