Compare commits

..

16 Commits

Author SHA1 Message Date
611850435d dependencies upgrade
Dependencies upgraded to latest versions
2025-01-29 01:07:45 +03:00
8244282fa4 code refactoring
code refactoring
2025-01-28 01:33:22 +03:00
b67f987031 code refactoring
code refactoring
2025-01-28 01:23:23 +03:00
74cbb0bb5f libs updated
libs updated
2025-01-27 21:27:46 +03:00
b4672e4057 tiny refactoring
tiny refactoring
2023-11-20 01:53:23 +03:00
b91b30b1e0 dependencies updated
dependencies updated
2023-11-20 01:14:16 +03:00
b01a9f3828 dependencies updated
dependencies updated
2023-11-20 00:56:45 +03:00
a99c60d529 fixed typo in file name
fixed typo in file name
2022-12-15 20:58:09 +03:00
82f1c744dc Tiny doc update
Tiny doc update
2022-11-12 19:40:13 +03:00
011e79e676 Locales are in external files now
Locales are in external files now
2022-11-12 19:08:33 +03:00
3ea4c96cfc Localization support added
Localization support added
2022-11-06 22:04:45 +03:00
bf96109742 Web-checks now runs in separate thread. Some fixes in scripts.
Web-checks now runs in separate thread. Some fixes in scripts.
2022-11-04 01:07:07 +03:00
87e326e869 Minor changes
Minor changes
2022-10-30 23:38:19 +03:00
c78bb31b69 Update README.md 2022-10-30 22:59:39 +03:00
85654f51a0 on-start script updated, localized scripts added
on-start script updated, localized scripts added
2022-10-29 21:15:02 +03:00
fafcdfa9c7 tiny bug fixed
tiny bug fixed
2022-10-29 21:06:28 +03:00
20 changed files with 1656 additions and 472 deletions

1179
Cargo.lock generated

File diff suppressed because it is too large Load Diff

View File

@@ -1,20 +1,20 @@
[package]
name = "cai-watchdog"
version = "0.5.0"
version = "0.9.0"
authors = ["Alexander I. Chebykin <alex.chebykin@gmail.com>"]
edition = "2018"
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
[dependencies]
file-utils = "0.1.5"
chrono = "0.4.22"
file-utils = "0.1"
chrono = "0.4"
# Sync
#reqwest = { version = "0.11", features = ["blocking"] }
# Async
reqwest = "0.11"
tokio = { version = "1", features = ["full"] }
ini = "1.3.0"
exitcode = "1.1.2"
hashmap = "0.0.1"
sysinfo = "0.26.6"
reqwest = "0.12"
tokio = { version = "1.43", features = ["full"] }
ini = "1.3"
exitcode = "1.1"
sysinfo = "0.33"
sys-locale = "0.3"

View File

@@ -127,7 +127,7 @@ Next you need to find your Telegram Chat ID.
### Service configuration (*nix)
If you want to get messages when watchdog service is stopped, uncomment following line: ```ExecStopPost=send-telegram 'ATTENTION! Watchdog is stopped!'``` or ```#ExecStopPost=/usr/local/sbin/cai-watchdog-stopped``` if you want message with exclamation icon (&#x26A0;)
If you want to get messages when watchdog service is stopped, uncomment following line: ```ExecStopPost=/etc/cai-watchdog/on-stop```
## User logins monitoring (*nix)
@@ -147,7 +147,7 @@ Watchdog can send notifications on user login. Just add to ```/etc/profile.d/ssh
IP=$(echo $SSH_CONNECTION | awk '{ print $1 == "" ? "127.0.0.1" : $1 }')
CAI_WATCHDOG_PATH=/etc/cai-watchdog
source ${CAI_WATCHDOG_PATH}/inc-icons
${CAI_WATCHDOG_PATH}//send-mail your@mail.addr '${ICON_INFO} SSH: User ${User} is logged in' '${ICON_INFO} SSH: User ${User} is logged in from ${IP}'
${CAI_WATCHDOG_PATH}/send-mail your@mail.addr '${ICON_INFO} SSH: User ${User} is logged in' '${ICON_INFO} SSH: User ${User} is logged in from ${IP}'
```
## User logouts monitoring (*nix)
@@ -158,8 +158,9 @@ Watchdog can send notifications on user login. Just add to ```/etc/profile.d/ssh
```
#!/bin/sh
CAI_WATCHDOG_PATH=/etc/cai-watchdog
if [ "$PAM_TYPE" = "close_session" ]; then
send-telegram "SSH: User is logged out"
${CAI_WATCHDOG_PATH}/send-telegram "SSH: User ${PAM_USER} is logged out"
fi
```
@@ -167,10 +168,16 @@ Watchdog can send notifications on user login. Just add to ```/etc/profile.d/ssh
```
#!/bin/sh
CAI_WATCHDOG_PATH=/etc/cai-watchdog
if [ "$PAM_TYPE" = "close_session" ]; then
send-mail your@mail.addr 'SSH: User is logged out' 'SSH: User is logged out'
${CAI_WATCHDOG_PATH}/send-mail your@mail.addr 'SSH: User ${PAM_USER} is logged out' 'SSH: User is logged out'
fi
```
and set executable flag on it
1. Modify ```/etc/pam.d/sshd```, add line ```session optional pam_exec.so quiet /etc/pam.d/pam_session.sh```
## Localization
You can find locales in ```/etc/cai-watchdog/locales/``` in *nix and in ```locales``` subfolder in Windows.
locale files must be named like locale_name.conf, for example: Russian locale is ru-RU.

View File

@@ -0,0 +1,26 @@
[rules]
cant_find_config_file = Не могу найти файл конфигурации
check_failed = Проверка завершилась неудачей
check_is_ok = проверка завершилась успешно
configuration_file = Конфигурационный файл
error = Ошибка
execute = Выполняю
exiting = Завершаю работу...
failed_to_execute_process = Не удалось выполнить процесс
is_not_running = не запущен
is_not_running_now = сейчас не запущен
is_offline = неактивна
is_offline_now = сейчас неактивна
is_online = активна
is_online_now = сейчас активна
is_running = запущен
is_running_now = сейчас запущен
process = Процесс
service = Служба
service_started = Служба запущена
state_changed_to_false = состояние изменилось на истину
state_changed_to_true = состояние изменилось на ложь
this_help_message = Это справочное сообщение
usage = Использование
ver = версия
version_info = Информация о версии

View File

@@ -8,4 +8,4 @@ if [[ -z "$APP_PATH" ]] ; then
fi
source ${APP_PATH}/inc-icons
${APP_PATH}/send-telegram "${ICON_INFO} ATTENTION! Watchdog is stopped!"
${APP_PATH}/send-telegram "${ICON_INFO} Watchdog is started!"

View File

@@ -0,0 +1,11 @@
#!/bin/bash
APP_PATH=$(dirname "$0") # relative
APP_PATH=$(cd "$APP_PATH" && pwd) # absolutized and normalized
if [[ -z "$APP_PATH" ]] ; then
# error; for some reason, the path is not accessible
# to the script (e.g. permissions re-evaled after suid)
APP_PATH="/etc/cai-watchdog"
fi
source ${APP_PATH}/inc-icons
${APP_PATH}/send-telegram "${ICON_INFO} Watchdog запущен!"

View File

@@ -0,0 +1,11 @@
#!/bin/bash
APP_PATH=$(dirname "$0") # relative
APP_PATH=$(cd "$APP_PATH" && pwd) # absolutized and normalized
if [[ -z "$APP_PATH" ]] ; then
# error; for some reason, the path is not accessible
# to the script (e.g. permissions re-evaled after suid)
APP_PATH="/etc/cai-watchdog"
fi
source ${APP_PATH}/inc-icons
${APP_PATH}/send-telegram "${ICON_WARNING} ВНИМАНИЕ! Watchdog остановлен!"

View File

@@ -7,6 +7,8 @@ if [[ -z "$APP_PATH" ]] ; then
APP_PATH="/etc/cai-watchdog"
fi
source ${APP_PATH}/inc-icons
RCPT=$1
SERVICE=$2
PROCESS_OR_URI=$3

View File

@@ -7,6 +7,8 @@ if [[ -z "$APP_PATH" ]] ; then
APP_PATH="/etc/cai-watchdog"
fi
source ${APP_PATH}/inc-icons
SCRIPT_NAME=$0
MESSAGE_TEXT=$1
@@ -15,7 +17,7 @@ if [ "$#" -ne 1 ]; then
exit 0
fi
ARGS=$(xargs echo $(perl -anle 's/^[^:]+//g && s/:\s+//g && print' ${APP_PATH}/email.conf) < /dev/null)
ARGS=$(xargs echo $(perl -anle 's/^[^:]+//g && s/:\s+//g && print' ${APP_PATH}/telegram.conf) < /dev/null)
set -- $ARGS "$@";
GROUP_ID=$1

View File

@@ -44,7 +44,7 @@ if [ "$#" -ne 3 ]; then
exit 0
fi
ARGS=$(xargs echo $(perl -anle 's/^[^:]+//g && s/:\s+//g && print' ${APP_PATH}/email.conf) < /dev/null)
ARGS=$(xargs echo $(perl -anle 's/^[^:]+//g && s/:\s+//g && print' ${APP_PATH}/telegram.conf) < /dev/null)
set -- $ARGS "$@";
GROUP_ID=$1

View File

@@ -6,8 +6,7 @@ User=wwwrun
Group=www
Type=simple
ExecStart=/usr/local/sbin/cai-watchdog
#ExecStopPost=/etc/cai-watchdog/on-stop
#ExecStopPost=send-telegram 'ATTENTION! Watchdog is stopped!'
ExecStopPost=/etc/cai-watchdog/on-stop
KillMode=control-group
NotifyAccess=all

View File

@@ -0,0 +1,26 @@
[rules]
cant_find_config_file = Не могу найти файл конфигурации
check_failed = Проверка завершилась неудачей
check_is_ok = проверка завершилась успешно
configuration_file = Конфигурационный файл
error = Ошибка
execute = Выполняю
exiting = Завершаю работу...
failed_to_execute_process = Не удалось выполнить процесс
is_not_running = не запущен
is_not_running_now = сейчас не запущен
is_offline = неактивна
is_offline_now = сейчас неактивна
is_online = активна
is_online_now = сейчас активна
is_running = запущен
is_running_now = сейчас запущен
process = Процесс
service = Служба
service_started = Служба запущена
state_changed_to_false = состояние изменилось на истину
state_changed_to_true = состояние изменилось на ложь
this_help_message = Это справочное сообщение
usage = Использование
ver = версия
version_info = Информация о версии

View File

@@ -1,29 +1,22 @@
//! CAI-Watchdog is a lightweigth watchdog for *nix and Windows
//! Author: Alexander I. Chebykin (CAI) <alex.chebykin@gmail.com>
#[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,
}
mod mods;
use crate::mods::mod_debug::*;
use crate::mods::mod_fs::*;
use crate::mods::mod_i18n::*;
use crate::mods::mod_os::*;
use crate::mods::mod_tasks::*;
/// Check rule
///
@@ -48,34 +41,6 @@ async fn check(rule: &Rule) -> bool {
};
}
/// 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
@@ -99,57 +64,6 @@ async fn check_uri(uri: String) -> bool {
}
}
/// 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
@@ -164,39 +78,46 @@ fn execute(command: String) {
/// print_help(args.clone());
/// ```
fn print_help(args: Vec<String>) {
let locale = Locale::new();
if cfg!(windows) {
if args.len() > 1 && (args[1].to_string() == "/help".to_string() || args[1].to_string() == "/?".to_string()) {
const VERSION: &str = env!("CARGO_PKG_VERSION");
println!("CAI Watchdog {} {}", locale.t().ver, VERSION);
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");
println!(" /? | /help : {}", locale.t().this_help_message);
println!(" /v | /ver : {}", locale.t().version_info);
println!(" config_file : {}", locale.t().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);
println!("CAI Watchdog {} {}", locale.t().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()) {
const VERSION: &str = env!("CARGO_PKG_VERSION");
println!("CAI Watchdog {} {}", locale.t().ver, VERSION);
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");
println!("{}: {} [-h | --help | -v | --ver | config_file]", locale.t().usage, &args[0]);
println!(" -h | --help : {}", locale.t().this_help_message);
println!(" -v | --ver : {}", locale.t().version_info);
println!(" config_file : {}", locale.t().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);
println!("CAI Watchdog {} {}", locale.t().ver, VERSION);
std::process::exit(exitcode::OK);
}
@@ -204,23 +125,32 @@ fn print_help(args: Vec<String>) {
}
fn main() {
let locale = Locale::new();
let args: Vec<String> = env::args().collect();
print_help(args.clone());
let mut just_started = true;
let mut config_file = get_exe_path();
if cfg!(windows) {
config_file = format!("{}\\cai-watchdog.ini", config_file);
} else {
config_file = "/etc/cai-watchdog/cai-watchdog.conf".to_string();
}
let cfg_file = if args.len() > 1 {
&args[1]
} else if cfg!(windows) {
"cai-watchdog.ini"
} else {
"/etc/cai-watchdog/cai-watchdog.conf"
&config_file
};
if !Path::new(cfg_file).exists() {
println!("\u{26a0} Error! Can't find configuration file.");
println!("Exiting...");
println!("\u{26a0} {}! {}.",
locale.t().error,
locale.t().cant_find_config_file
);
println!("{}", locale.t().exiting);
std::process::exit(exitcode::CONFIG);
}
@@ -239,35 +169,36 @@ fn main() {
0
};
let mut tasks = vec![];
let mut tasks_prc = vec![];
let mut tasks_web = 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()
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()
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()
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()
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()
let command = if cfg[&format!("rule{}", i)].contains_key("command") {
cfg[&format!("rule{}", i)]["command"].clone().unwrap()
} else {
"".to_string()
};
@@ -279,16 +210,29 @@ fn main() {
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,
}
);
if uri != "".to_string() {
tasks_web.push(
Rule{
service: service,
uri: uri,
process: process,
email: email,
command: command,
last_state: false,
}
);
} else {
tasks_prc.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") {
@@ -297,99 +241,50 @@ fn main() {
"".to_string()
};
debug_log("\u{2139} Service started".to_string());
debug_log(format!("\u{2139} {}", locale.t().service_started));
if on_start_command.to_string() != "" {
execute(on_start_command);
}
let mut just_started_web = true;
std::thread::spawn(move || {
loop {
for i in 0..tasks_web.len() {
let rt = tokio::runtime::Runtime::new().unwrap();
rt.block_on(async {
if check(&tasks_web[i]).await {
execute_web_task(&mut tasks_web[i], true, just_started_web);
} else {
execute_web_task(&mut tasks_web[i], false, just_started_web);
}
});
}
just_started_web = false;
thread::sleep(Duration::from_millis(check_interval));
}
});
let mut just_started_prc = true;
loop {
for i in 0..tasks.len() {
for i in 0..tasks_prc.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;
if check(&tasks_prc[i]).await {
execute_os_task(&mut tasks_prc[i], true, just_started_prc);
} 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;
execute_os_task(&mut tasks_prc[i], false, just_started_prc);
}
});
}
just_started = false;
just_started_prc = false;
thread::sleep(Duration::from_millis(check_interval));
}

5
src/mods/mod.rs Normal file
View File

@@ -0,0 +1,5 @@
pub mod mod_debug;
pub mod mod_fs;
pub mod mod_i18n;
pub mod mod_os;
pub mod mod_tasks;

16
src/mods/mod_debug.rs Normal file
View File

@@ -0,0 +1,16 @@
/// Output to console only in debug version
///
/// # Arguments
///
/// * `text` - Message text
///
/// # Example
///
/// ```
/// debug_log("Debug version started".to_string());
/// ```
pub fn debug_log(text: String) {
if cfg!(debug_assertions) {
println!("{}", text);
}
}

26
src/mods/mod_fs.rs Normal file
View File

@@ -0,0 +1,26 @@
//! CAI-Watchdog filesystem module
//! Author: Alexander I. Chebykin (CAI) <alex.chebykin@gmail.com>
use std::env;
use std::path::PathBuf;
/// Return executable path
///
/// Return executable path or . if can't determine it
pub fn get_exe_path() -> String {
let mut dir;
let exe_path: String;
match env::current_exe() {
Ok(full_name) => {
dir = PathBuf::from(full_name);
dir.pop();
exe_path = dir.clone().into_os_string().into_string().unwrap();
},
Err(_e) => {
exe_path = ".".to_string();
},
}
return exe_path;
}

185
src/mods/mod_i18n.rs Normal file
View File

@@ -0,0 +1,185 @@
//! CAI-Watchdog localization module
//! Author: Alexander I. Chebykin (CAI) <alex.chebykin@gmail.com>
//!
//! External localizations stored in /locales/ directory
use std::borrow::Borrow;
use std::path::Path;
use crate::mods::mod_fs;
use sys_locale::get_locale;
pub struct Lang {
pub cant_find_config_file: String,
pub check_failed: String,
pub check_is_ok: String,
pub configuration_file: String,
pub error: String,
pub execute: String,
pub exiting: String,
pub failed_to_execute_process: String,
pub is_not_running: String,
pub is_not_running_now: String,
pub is_offline: String,
pub is_offline_now: String,
pub is_online: String,
pub is_online_now: String,
pub is_running: String,
pub is_running_now: String,
pub process: String,
pub service: String,
pub service_started: String,
pub state_changed_to_false: String,
pub state_changed_to_true: String,
pub this_help_message: String,
pub usage: String,
pub ver: String,
pub version_info: String
}
pub struct Locale {
pub locale:Lang,
}
impl Locale {
pub fn new() -> Self {
let mut l:Lang = Lang{
cant_find_config_file: "Can't find configuration file".to_string(),
check_failed: "check failed".to_string(),
check_is_ok: "check is ok".to_string(),
configuration_file: "Configuration file".to_string(),
error: "Error".to_string(),
execute: "Execute".to_string(),
exiting: "Exiting...".to_string(),
failed_to_execute_process: "Can't execute process".to_string(),
is_not_running: "is not running".to_string(),
is_not_running_now: "is not running now".to_string(),
is_offline: "is offline".to_string(),
is_offline_now: "is offline now".to_string(),
is_online: "is online".to_string(),
is_online_now: "is online now".to_string(),
is_running: "is running".to_string(),
is_running_now: "is running now".to_string(),
process: "Process".to_string(),
service: "Service".to_string(),
service_started: "Service started".to_string(),
state_changed_to_false: "state changed to false".to_string(),
state_changed_to_true: "state changed to true".to_string(),
this_help_message: "This help message".to_string(),
usage: "Usage".to_string(),
ver: "ver".to_string(),
version_info: "Version info".to_string()
};
let locale = get_locale().unwrap_or_else(|| String::from("en-US"));
let mut locale_file = mod_fs::get_exe_path();
locale_file = if cfg!(windows) {
format!("{}\\locales\\{}.conf", locale_file, locale)
} else {
format!("/etc/cai-watchdog/locales/{}.conf", locale)
};
if Path::new(&locale_file).exists() {
let cfg = ini!(&locale_file);
if cfg["rules"].contains_key("cant_find_config_file") {
l.cant_find_config_file = cfg["rules"]["cant_find_config_file"].clone().unwrap();
}
if cfg["rules"].contains_key("check_failed") {
l.check_failed = cfg["rules"]["check_failed"].clone().unwrap();
}
if cfg["rules"].contains_key("check_is_ok") {
l.check_is_ok = cfg["rules"]["check_is_ok"].clone().unwrap();
}
if cfg["rules"].contains_key("configuration_file") {
l.configuration_file = cfg["rules"]["configuration_file"].clone().unwrap();
}
if cfg["rules"].contains_key("error") {
l.error = cfg["rules"]["error"].clone().unwrap();
}
if cfg["rules"].contains_key("execute") {
l.execute = cfg["rules"]["execute"].clone().unwrap();
}
if cfg["rules"].contains_key("exiting") {
l.exiting = cfg["rules"]["exiting"].clone().unwrap();
}
if cfg["rules"].contains_key("failed_to_execute_process") {
l.failed_to_execute_process = cfg["rules"]["failed_to_execute_process"].clone().unwrap();
}
if cfg["rules"].contains_key("is_not_running") {
l.is_not_running = cfg["rules"]["is_not_running"].clone().unwrap();
}
if cfg["rules"].contains_key("is_not_running_now") {
l.is_not_running_now = cfg["rules"]["is_not_running_now"].clone().unwrap();
}
if cfg["rules"].contains_key("is_offline") {
l.is_offline = cfg["rules"]["is_offline"].clone().unwrap();
}
if cfg["rules"].contains_key("is_offline_now") {
l.is_offline_now = cfg["rules"]["is_offline_now"].clone().unwrap();
}
if cfg["rules"].contains_key("is_online") {
l.is_online = cfg["rules"]["is_online"].clone().unwrap();
}
if cfg["rules"].contains_key("is_online_now") {
l.is_online_now = cfg["rules"]["is_online_now"].clone().unwrap();
}
if cfg["rules"].contains_key("is_running") {
l.is_running = cfg["rules"]["is_running"].clone().unwrap();
}
if cfg["rules"].contains_key("is_running_now") {
l.is_running_now = cfg["rules"]["is_running_now"].clone().unwrap();
}
if cfg["rules"].contains_key("process") {
l.process = cfg["rules"]["process"].clone().unwrap();
}
if cfg["rules"].contains_key("service") {
l.service = cfg["rules"]["service"].clone().unwrap();
}
if cfg["rules"].contains_key("service_started") {
l.service_started = cfg["rules"]["service_started"].clone().unwrap();
}
if cfg["rules"].contains_key("state_changed_to_false") {
l.state_changed_to_false = cfg["rules"]["state_changed_to_false"].clone().unwrap();
}
if cfg["rules"].contains_key("state_changed_to_true") {
l.state_changed_to_true = cfg["rules"]["state_changed_to_true"].clone().unwrap();
}
if cfg["rules"].contains_key("this_help_message") {
l.this_help_message = cfg["rules"]["this_help_message"].clone().unwrap();
}
if cfg["rules"].contains_key("usage") {
l.usage = cfg["rules"]["usage"].clone().unwrap();
}
if cfg["rules"].contains_key("ver") {
l.ver = cfg["rules"]["ver"].clone().unwrap();
}
if cfg["rules"].contains_key("version_info") {
l.version_info = cfg["rules"]["version_info"].clone().unwrap();
}
}
Self {
locale: l
}
}
/// Return object with locale data
///
/// If there is no localization file for current system locale, fallback (English) locale will be returned
///
/// # Example
///
/// ```
/// let locale = mod_locales::Locale::new();
///
/// println!("\u{26a0} {}! {}.", locale.t().error, locale.t().cant_find_config_file);
/// println!("{}", locale.t().exiting);
/// ```
pub fn t(&self) -> &Lang {
return self.locale.borrow();
}
}

70
src/mods/mod_os.rs Normal file
View File

@@ -0,0 +1,70 @@
use std::ffi::OsStr;
use std::process::Command;
use sysinfo::System;
use crate::mods::mod_debug::*;
use crate::mods::mod_i18n::*;
/// 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!");
/// }
/// ```
pub 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(OsStr::new(&process_name)) {
result = true;
}
return result;
}
/// 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());
/// ```
pub fn execute(command: String) {
let locale = Locale::new();
debug_log(format!("{} {}", locale.t().execute, command));
let output = if cfg!(target_os = "windows") {
Command::new("powershell")
.args(["-NoLogo", "-NonInteractive", "-Command", &command])
.output()
.expect(&locale.t().failed_to_execute_process) //.expect("failed to execute process")
} else {
Command::new("sh")
.arg("-c")
.arg(&command)
.output()
.expect(&locale.t().failed_to_execute_process) //.expect("failed to execute process")
};
let result = output.stdout;
debug_log(format!("{:?}", result));
}

206
src/mods/mod_tasks.rs Normal file
View File

@@ -0,0 +1,206 @@
use crate::mods::mod_debug::*;
use crate::mods::mod_i18n::*;
use crate::mods::mod_os::*;
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,
}
pub fn execute_web_task(task: &mut Rule, current_state:bool, just_started: bool) {
let locale = Locale::new();
if task.last_state != current_state || just_started {
if task.command.to_string() == "".to_string() {
if current_state {
println!("\u{2705} {} {}", task.uri, locale.t().state_changed_to_true.clone());
} else {
println!("\u{274c} {} {}", task.uri, locale.t().state_changed_to_false.clone());
}
} else {
if current_state {
debug_log(
format!(
"\u{2705} {} {}",
task.uri,
locale.t().state_changed_to_true.clone()
)
);
} else {
debug_log(
format!(
"\u{274c} {} {}",
task.uri,
locale.t().state_changed_to_false.clone()
)
);
}
let subject: String = if current_state {
format!(
"\"\u{2705} {} {} ({}) {}\"",
locale.t().service,
task.service,
task.uri,
locale.t().is_online.clone()
)
} else {
format!(
"\"\u{274c} {} {} ({}) {}\"",
locale.t().service,
task.service,
task.uri,
locale.t().is_offline.clone()
)
};
let shell_cmd =
task.command.to_string()
.replace("<email>", &task.email)
.replace("<subject>", &subject)
.replace("<message>", &subject)
.replace("<service>", &task.service)
.replace("<uri>", &task.uri)
.replace(
"<state>",
if current_state {
"online"
} else {
"offline"
}
);
debug_log(format!("{} {}", locale.t().execute, shell_cmd));
execute(shell_cmd);
}
}
if current_state {
debug_log(
format!(
"\u{2705} {} {}",
task.uri,
locale.t().check_is_ok.clone()
)
)
} else {
debug_log(
format!(
"\u{274c} {} {}",
task.uri,
locale.t().check_failed.clone()
)
);
}
task.last_state = current_state;
}
pub fn execute_os_task(task: &mut Rule, current_state:bool, just_started: bool) {
let locale = Locale::new();
if task.last_state != current_state || just_started {
if task.command.to_string() == "".to_string() {
if current_state {
println!(
"\u{2705} {} {}",
task.uri,
locale.t().state_changed_to_true.clone()
);
} else {
println!(
"\u{274c} {} {}",
task.uri,
locale.t().state_changed_to_false.clone()
)
}
} else {
if current_state {
debug_log(
format!(
"\u{2705} {} {}",
task.uri,
locale.t().state_changed_to_true.clone()
)
)
} else {
debug_log(
format!(
"\u{274c} {} {}",
task.uri,
locale.t().state_changed_to_false.clone()
)
)
}
let subject: String = if current_state {
format!(
"\"\u{2705} {} {} ({}) {}\"",
locale.t().process,
task.service,
task.process,
locale.t().is_running.clone()
)
} else {
format!(
"\"\u{274c} {} {} ({}) {}\"",
locale.t().process,
task.service,
task.process,
locale.t().is_not_running.clone()
)
};
let shell_cmd =
task.command.to_string()
.replace("<email>", &task.email)
.replace("<subject>", &subject)
.replace("<message>", &subject)
.replace("<service>", &task.service)
.replace("<process>", &task.process)
.replace(
"<state>",
if current_state {
"running"
} else {
"stopped"
}
);
debug_log(format!("{} {}", locale.t().execute, shell_cmd));
execute(shell_cmd);
}
}
if current_state {
debug_log(
format!(
"\u{2705} {} {}",
task.process,
locale.t().is_running
)
);
} else {
debug_log(
format!(
"\u{274c} {} {}",
task.process,
locale.t().is_not_running
)
);
}
task.last_state = current_state;
}