forked from github-starred/komodo
622 lines
18 KiB
Rust
622 lines
18 KiB
Rust
use std::{
|
|
env,
|
|
fs::{self, File},
|
|
io::{Read, Write},
|
|
net::IpAddr,
|
|
path::PathBuf,
|
|
str::FromStr,
|
|
};
|
|
|
|
use clap::ArgMatches;
|
|
use colored::Colorize;
|
|
use monitor_types::{CoreConfig, MongoConfig, PeripheryConfig, RestartMode, Timelength};
|
|
use rand::{distributions::Alphanumeric, Rng};
|
|
use run_command::run_command_pipe_to_terminal;
|
|
use serde::Serialize;
|
|
|
|
const CORE_IMAGE_NAME: &str = "mbecker2020/monitor_core";
|
|
const PERIPHERY_IMAGE_NAME: &str = "mbecker2020/monitor_periphery";
|
|
const PERIPHERY_CRATE: &str = "monitor_periphery";
|
|
|
|
pub fn gen_core_config(sub_matches: &ArgMatches) {
|
|
let host = sub_matches
|
|
.get_one::<String>("host")
|
|
.map(|p| p.as_str())
|
|
.unwrap_or("http://localhost:9000")
|
|
.to_string();
|
|
|
|
let path = sub_matches
|
|
.get_one::<String>("path")
|
|
.map(|p| p.as_str())
|
|
.unwrap_or("~/.monitor/core.config.toml")
|
|
.to_string();
|
|
|
|
let port = sub_matches
|
|
.get_one::<String>("port")
|
|
.map(|p| p.as_str())
|
|
.unwrap_or("9000")
|
|
.parse::<u16>()
|
|
.expect("invalid port");
|
|
|
|
let mongo_uri = sub_matches
|
|
.get_one::<String>("mongo-uri")
|
|
.map(|p| p.as_str())
|
|
.unwrap_or("mongodb://monitor-mongo")
|
|
.to_string();
|
|
|
|
let mongo_db_name = sub_matches
|
|
.get_one::<String>("mongo-db-name")
|
|
.map(|p| p.as_str())
|
|
.unwrap_or("monitor")
|
|
.to_string();
|
|
|
|
let jwt_valid_for = sub_matches
|
|
.get_one::<String>("jwt-valid-for")
|
|
.map(|p| p.as_str())
|
|
.unwrap_or("1-wk")
|
|
.parse()
|
|
.expect("invalid jwt-valid-for");
|
|
|
|
let slack_url = sub_matches
|
|
.get_one::<String>("slack-url")
|
|
.map(|p| p.to_owned());
|
|
|
|
let config = CoreConfig {
|
|
title: String::from("monitor"),
|
|
host,
|
|
port,
|
|
jwt_valid_for,
|
|
monitoring_interval: Timelength::OneMinute,
|
|
daily_offset_hours: 0,
|
|
keep_stats_for_days: 120,
|
|
slack_url,
|
|
local_auth: true,
|
|
github_oauth: Default::default(),
|
|
google_oauth: Default::default(),
|
|
aws: Default::default(),
|
|
docker_organizations: Default::default(),
|
|
mongo: MongoConfig {
|
|
uri: mongo_uri,
|
|
db_name: mongo_db_name,
|
|
app_name: "monitor".to_string(),
|
|
},
|
|
jwt_secret: generate_secret(40),
|
|
github_webhook_secret: generate_secret(30),
|
|
github_webhook_base_url: None,
|
|
passkey: generate_secret(30),
|
|
};
|
|
|
|
write_to_toml(&path, &config);
|
|
|
|
println!(
|
|
"\n✅ {} has been generated at {path} ✅\n",
|
|
"core config".bold()
|
|
);
|
|
}
|
|
|
|
pub fn start_mongo(sub_matches: &ArgMatches) {
|
|
let username = sub_matches.get_one::<String>("username");
|
|
let password = sub_matches.get_one::<String>("password");
|
|
|
|
if (username.is_some() && password.is_none()) {
|
|
println!(
|
|
"\n❌ must provide {} if username is provided ❌\n",
|
|
"--password".bold()
|
|
);
|
|
return;
|
|
}
|
|
if (username.is_none() && password.is_some()) {
|
|
println!(
|
|
"\n❌ must provide {} if password is provided ❌\n",
|
|
"--username".bold()
|
|
);
|
|
return;
|
|
}
|
|
|
|
let skip_enter = *sub_matches.get_one::<bool>("yes").unwrap_or(&false);
|
|
|
|
let name = sub_matches
|
|
.get_one::<String>("name")
|
|
.map(|p| p.as_str())
|
|
.unwrap_or("monitor-mongo");
|
|
|
|
let port = sub_matches
|
|
.get_one::<String>("port")
|
|
.map(|p| p.as_str())
|
|
.unwrap_or("27017")
|
|
.parse::<u16>()
|
|
.expect("invalid port");
|
|
|
|
let network = sub_matches
|
|
.get_one::<String>("network")
|
|
.map(|p| p.as_str())
|
|
.unwrap_or("bridge");
|
|
|
|
let mount = sub_matches
|
|
.get_one::<String>("mount")
|
|
.map(|p| p.as_str())
|
|
.unwrap_or("~/.monitor/db");
|
|
|
|
let restart = sub_matches
|
|
.get_one::<String>("restart")
|
|
.map(|p| p.as_str())
|
|
.unwrap_or("unless-stopped")
|
|
.parse::<RestartMode>()
|
|
.expect("invalid restart mode");
|
|
|
|
let env = if let (Some(username), Some(password)) = (username, password) {
|
|
format!(" --env MONGO_INITDB_ROOT_USERNAME={username} --env MONGO_INITDB_ROOT_PASSWORD={password}")
|
|
} else {
|
|
String::new()
|
|
};
|
|
|
|
println!(
|
|
"\n====================\n {} \n====================\n",
|
|
"mongo config".bold()
|
|
);
|
|
if let Some(username) = username {
|
|
println!("{}: {username}", "mongo username".dimmed());
|
|
}
|
|
println!("{}: {name}", "container name".dimmed());
|
|
println!("{}: {port}", "port".dimmed());
|
|
println!("{}: {mount}", "mount".dimmed());
|
|
println!("{}: {network}", "network".dimmed());
|
|
println!("{}: {restart}", "restart".dimmed());
|
|
|
|
if !skip_enter {
|
|
println!(
|
|
"\npress {} to start {}. {}",
|
|
"ENTER".green().bold(),
|
|
"MongoDB".bold(),
|
|
"(ctrl-c to cancel)".dimmed()
|
|
);
|
|
|
|
let buffer = &mut [0u8];
|
|
let res = std::io::stdin().read_exact(buffer);
|
|
|
|
if res.is_err() {
|
|
println!("pressed another button, exiting");
|
|
}
|
|
}
|
|
|
|
let stop =
|
|
run_command_pipe_to_terminal(&format!("docker stop {name} && docker container rm {name}"));
|
|
|
|
let command = format!("docker run -d --name {name} -p {port}:27017 --network {network} -v {mount}:/data/db{env} --restart {restart} --log-opt max-size=15m --log-opt max-file=3 mongo --quiet");
|
|
|
|
let output = run_command_pipe_to_terminal(&command);
|
|
|
|
if output.success() {
|
|
println!("\n✅ {} has been started up ✅\n", "monitor mongo".bold())
|
|
} else {
|
|
eprintln!("\n❌ there was some {} on startup ❌\n", "error".red())
|
|
}
|
|
}
|
|
|
|
pub fn start_core(sub_matches: &ArgMatches) {
|
|
let skip_enter = *sub_matches.get_one::<bool>("yes").unwrap_or(&false);
|
|
|
|
let config_path = sub_matches
|
|
.get_one::<String>("config-path")
|
|
.map(|p| p.as_str())
|
|
.unwrap_or("~/.monitor/core.config.toml")
|
|
.to_string();
|
|
|
|
let name = sub_matches
|
|
.get_one::<String>("name")
|
|
.map(|p| p.as_str())
|
|
.unwrap_or("monitor-core");
|
|
|
|
let port = sub_matches
|
|
.get_one::<String>("port")
|
|
.map(|p| p.as_str())
|
|
.unwrap_or("9000")
|
|
.parse::<u16>()
|
|
.expect("invalid port");
|
|
|
|
let network = sub_matches
|
|
.get_one::<String>("network")
|
|
.map(|p| p.as_str())
|
|
.unwrap_or("bridge");
|
|
|
|
let restart = sub_matches
|
|
.get_one::<String>("restart")
|
|
.map(|p| p.as_str())
|
|
.unwrap_or("unless-stopped")
|
|
.parse::<RestartMode>()
|
|
.expect("invalid restart mode");
|
|
|
|
let add_host = sub_matches
|
|
.get_one::<bool>("add-internal-host")
|
|
.map(|p| *p)
|
|
.unwrap_or(true);
|
|
|
|
println!(
|
|
"\n===================\n {} \n===================\n",
|
|
"core config".bold()
|
|
);
|
|
println!("{}: {name}", "container name".dimmed());
|
|
println!("{}: {config_path}", "config path".dimmed());
|
|
println!("{}: {port}", "port".dimmed());
|
|
println!("{}: {network}", "network".dimmed());
|
|
println!("{}: {restart}", "restart".dimmed());
|
|
println!("{}: {add_host}", "add internal host".dimmed());
|
|
|
|
if !skip_enter {
|
|
println!(
|
|
"\npress {} to start {}. {}",
|
|
"ENTER".green().bold(),
|
|
"monitor core".bold(),
|
|
"(ctrl-c to cancel)".dimmed()
|
|
);
|
|
|
|
let buffer = &mut [0u8];
|
|
let res = std::io::stdin().read_exact(buffer);
|
|
|
|
if res.is_err() {
|
|
println!("pressed another button, exiting");
|
|
}
|
|
}
|
|
|
|
println!("\nstarting monitor core container...\n");
|
|
|
|
let _ = run_command_pipe_to_terminal(&format!("docker pull {CORE_IMAGE_NAME}"));
|
|
|
|
let _ =
|
|
run_command_pipe_to_terminal(&format!("docker stop {name} && docker container rm {name}"));
|
|
|
|
let add_host = if add_host {
|
|
" --add-host host.docker.internal:host-gateway"
|
|
} else {
|
|
""
|
|
};
|
|
|
|
let command = format!("docker run -d --name {name} -p {port}:9000 --network {network} -v {config_path}:/config/config.toml --restart {restart}{add_host} {CORE_IMAGE_NAME}");
|
|
|
|
let output = run_command_pipe_to_terminal(&command);
|
|
|
|
if output.success() {
|
|
println!("\n✅ {} has been started up ✅\n", "monitor core".bold())
|
|
} else {
|
|
eprintln!("\n❌ there was some {} on startup ❌\n", "error".red())
|
|
}
|
|
}
|
|
|
|
pub fn gen_periphery_config(sub_matches: &ArgMatches) {
|
|
let path = sub_matches
|
|
.get_one::<String>("path")
|
|
.map(|p| p.as_str())
|
|
.unwrap_or("~/.monitor/periphery.config.toml")
|
|
.to_string();
|
|
|
|
let port = sub_matches
|
|
.get_one::<String>("port")
|
|
.map(|p| p.as_str())
|
|
.unwrap_or("8000")
|
|
.parse::<u16>()
|
|
.expect("invalid port");
|
|
|
|
let stats_polling_rate = sub_matches
|
|
.get_one::<String>("stats-polling-rate")
|
|
.map(|p| p.as_str())
|
|
.unwrap_or("5-sec")
|
|
.parse::<Timelength>()
|
|
.expect("invalid timelength");
|
|
|
|
let allowed_ips = sub_matches
|
|
.get_one::<String>("allowed-ips")
|
|
.map(|p| p.as_str())
|
|
.unwrap_or("")
|
|
.split(",")
|
|
.filter(|ip| ip.len() > 0)
|
|
.map(|ip| {
|
|
ip.parse()
|
|
.expect("given allowed ip address is not valid ip")
|
|
})
|
|
.collect::<Vec<IpAddr>>();
|
|
|
|
let repo_dir = sub_matches
|
|
.get_one::<String>("repo-dir")
|
|
.map(|p| p.as_str())
|
|
.unwrap_or("~/.monitor/repos")
|
|
.to_string()
|
|
.replace("~", env::var("HOME").unwrap().as_str())
|
|
.parse()
|
|
.expect("failed to parse --repo_dir as path");
|
|
|
|
let config = PeripheryConfig {
|
|
port,
|
|
repo_dir,
|
|
stats_polling_rate,
|
|
allowed_ips,
|
|
passkeys: vec![],
|
|
secrets: Default::default(),
|
|
github_accounts: Default::default(),
|
|
docker_accounts: Default::default(),
|
|
};
|
|
|
|
write_to_toml(&path, &config);
|
|
|
|
println!(
|
|
"\n✅ {} generated at {path} ✅\n",
|
|
"periphery config".bold()
|
|
);
|
|
}
|
|
|
|
pub fn start_periphery_systemd(sub_matches: &ArgMatches) {
|
|
let skip_enter = *sub_matches.get_one::<bool>("yes").unwrap_or(&false);
|
|
|
|
let install = *sub_matches.get_one::<bool>("install").unwrap_or(&false);
|
|
|
|
let config_path = sub_matches
|
|
.get_one::<String>("config-path")
|
|
.map(|p| p.as_str())
|
|
.unwrap_or("~/.monitor/periphery.config.toml")
|
|
.to_string();
|
|
|
|
println!(
|
|
"\n========================\n {} \n========================\n",
|
|
"periphery config".bold()
|
|
);
|
|
println!("{}: systemd", "run with".dimmed());
|
|
println!("{}: {config_path}", "config path".dimmed());
|
|
|
|
if !skip_enter {
|
|
println!(
|
|
"\npress {} to start {}. {}",
|
|
"ENTER".green().bold(),
|
|
"monitor periphery".bold(),
|
|
"(ctrl-c to cancel)".dimmed()
|
|
);
|
|
|
|
let buffer = &mut [0u8];
|
|
let res = std::io::stdin().read_exact(buffer);
|
|
|
|
if res.is_err() {
|
|
println!("pressed another button, exiting");
|
|
}
|
|
}
|
|
|
|
if install {
|
|
install_periphery_from_crates_io();
|
|
}
|
|
|
|
gen_periphery_service_file(&config_path);
|
|
|
|
let user = env::var("USER").expect("failed to find $USER env var");
|
|
|
|
let command =
|
|
format!("systemctl --user daemon-reload && systemctl --user enable --now periphery && loginctl enable-linger {user}");
|
|
|
|
let output = run_command_pipe_to_terminal(&command);
|
|
|
|
if output.success() {
|
|
println!(
|
|
"\n✅ {} has been started up ✅\n",
|
|
"monitor periphery".bold()
|
|
)
|
|
} else {
|
|
eprintln!("\n❌ there was some {} on startup ❌\n", "error".red())
|
|
}
|
|
}
|
|
|
|
pub fn start_periphery_daemon(sub_matches: &ArgMatches) {
|
|
let skip_enter = *sub_matches.get_one::<bool>("yes").unwrap_or(&false);
|
|
|
|
let install = *sub_matches.get_one::<bool>("install").unwrap_or(&false);
|
|
|
|
let config_path = sub_matches
|
|
.get_one::<String>("config-path")
|
|
.map(|p| p.as_str())
|
|
.unwrap_or("~/.monitor/periphery.config.toml")
|
|
.to_string();
|
|
|
|
let stdout = sub_matches
|
|
.get_one::<String>("stdout")
|
|
.map(|p| p.as_str())
|
|
.unwrap_or("~/.monitor/periphery.log.out")
|
|
.to_string();
|
|
|
|
let stderr = sub_matches
|
|
.get_one::<String>("stderr")
|
|
.map(|p| p.as_str())
|
|
.unwrap_or("~/.monitor/periphery.log.err")
|
|
.to_string();
|
|
|
|
println!(
|
|
"\n========================\n {} \n========================\n",
|
|
"periphery config".bold()
|
|
);
|
|
println!("{}: daemon", "run as".dimmed());
|
|
println!("{}: {config_path}", "config path".dimmed());
|
|
println!("{}: {stdout}", "stdout".dimmed());
|
|
println!("{}: {stderr}", "stderr".dimmed());
|
|
|
|
if !skip_enter {
|
|
println!(
|
|
"\npress {} to start {}. {}",
|
|
"ENTER".green().bold(),
|
|
"monitor periphery".bold(),
|
|
"(ctrl-c to cancel)".dimmed()
|
|
);
|
|
|
|
let buffer = &mut [0u8];
|
|
let res = std::io::stdin().read_exact(buffer);
|
|
|
|
if res.is_err() {
|
|
println!("pressed another button, exiting");
|
|
}
|
|
}
|
|
|
|
if install {
|
|
install_periphery_from_crates_io();
|
|
}
|
|
|
|
let command = format!("if pgrep periphery; then pkill periphery; fi && periphery --daemon --config-path {config_path} --stdout {stdout} --stderr {stderr}");
|
|
|
|
let output = run_command_pipe_to_terminal(&command);
|
|
|
|
if output.success() {
|
|
println!(
|
|
"\n✅ {} has been started up ✅\n",
|
|
"monitor periphery".bold()
|
|
)
|
|
} else {
|
|
eprintln!("\n❌ there was some {} on startup ❌\n", "error".red())
|
|
}
|
|
}
|
|
|
|
pub fn start_periphery_container(sub_matches: &ArgMatches) {
|
|
let skip_enter = *sub_matches.get_one::<bool>("yes").unwrap_or(&false);
|
|
|
|
let config_path = sub_matches
|
|
.get_one::<String>("config-path")
|
|
.map(|p| p.as_str())
|
|
.unwrap_or("~/.monitor/periphery.config.toml")
|
|
.to_string();
|
|
|
|
let repo_dir = sub_matches
|
|
.get_one::<String>("repo-dir")
|
|
.map(|p| p.as_str())
|
|
.unwrap_or("~/.monitor/repos")
|
|
.to_string();
|
|
|
|
let name = sub_matches
|
|
.get_one::<String>("name")
|
|
.map(|p| p.as_str())
|
|
.unwrap_or("monitor-periphery");
|
|
|
|
let port = sub_matches
|
|
.get_one::<String>("port")
|
|
.map(|p| p.as_str())
|
|
.unwrap_or("8000")
|
|
.parse::<u16>()
|
|
.expect("invalid port");
|
|
|
|
let network = sub_matches
|
|
.get_one::<String>("network")
|
|
.map(|p| p.as_str())
|
|
.unwrap_or("bridge");
|
|
|
|
let restart = sub_matches
|
|
.get_one::<String>("restart")
|
|
.map(|p| p.as_str())
|
|
.unwrap_or("unless-stopped")
|
|
.parse::<RestartMode>()
|
|
.expect("invalid restart mode");
|
|
|
|
println!(
|
|
"\n========================\n {} \n========================\n",
|
|
"periphery config".bold()
|
|
);
|
|
println!("{}: container", "run as".dimmed());
|
|
println!("{}: {name}", "container name".dimmed());
|
|
println!("{}: {config_path}", "config path".dimmed());
|
|
println!("{}: {repo_dir}", "repo folder".dimmed());
|
|
println!("{}: {port}", "port".dimmed());
|
|
println!("{}: {network}", "network".dimmed());
|
|
println!("{}: {restart}", "restart".dimmed());
|
|
|
|
if !skip_enter {
|
|
println!(
|
|
"\npress {} to start {}. {}",
|
|
"ENTER".green().bold(),
|
|
"monitor periphery".bold(),
|
|
"(ctrl-c to cancel)".dimmed()
|
|
);
|
|
|
|
let buffer = &mut [0u8];
|
|
let res = std::io::stdin().read_exact(buffer);
|
|
|
|
if res.is_err() {
|
|
println!("pressed another button, exiting");
|
|
}
|
|
}
|
|
|
|
println!("\nstarting monitor periphery container...\n");
|
|
|
|
let _ = run_command_pipe_to_terminal(&format!("docker pull {PERIPHERY_IMAGE_NAME}"));
|
|
|
|
let _ =
|
|
run_command_pipe_to_terminal(&format!("docker stop {name} && docker container rm {name}"));
|
|
|
|
let command = format!("docker run -d --name {name} -p {port}:8000 --network {network} -v {config_path}:/config/config.toml -v {repo_dir}:/repos -v /var/run/docker.sock:/var/run/docker.sock --restart {restart} {PERIPHERY_IMAGE_NAME}");
|
|
|
|
let output = run_command_pipe_to_terminal(&command);
|
|
|
|
if output.success() {
|
|
println!(
|
|
"\n✅ {} has been started up ✅\n",
|
|
"monitor periphery".bold()
|
|
)
|
|
} else {
|
|
eprintln!("\n❌ there was some {} on startup ❌\n", "error".red())
|
|
}
|
|
}
|
|
|
|
pub fn gen_periphery_service_file(config_path: &str) {
|
|
let home = env::var("HOME").expect("failed to find $HOME env var");
|
|
let _ = std::fs::create_dir_all(format!("{home}/.config/systemd/user"));
|
|
let mut file = File::create(format!("{home}/.config/systemd/user/periphery.service"))
|
|
.expect("failed to create user systemd unit file");
|
|
file.write_all(periphery_unit_file(config_path).as_bytes())
|
|
.expect("failed to write config file");
|
|
}
|
|
|
|
fn write_to_toml(path: &str, toml: impl Serialize) {
|
|
let path = PathBuf::from_str(&path.replace("~", &std::env::var("HOME").unwrap()))
|
|
.expect("not a valid path");
|
|
let _ = fs::create_dir_all(pop_path(&path));
|
|
fs::write(
|
|
path,
|
|
toml::to_string(&toml).expect("failed to parse config into toml"),
|
|
)
|
|
.expect("❌ failed to write toml to file ❌");
|
|
}
|
|
|
|
fn pop_path(path: &PathBuf) -> PathBuf {
|
|
let mut clone = path.clone();
|
|
clone.pop();
|
|
clone
|
|
}
|
|
|
|
fn generate_secret(length: usize) -> String {
|
|
rand::thread_rng()
|
|
.sample_iter(&Alphanumeric)
|
|
.take(length)
|
|
.map(char::from)
|
|
.collect()
|
|
}
|
|
|
|
fn periphery_unit_file(config_path: &str) -> String {
|
|
let home = env::var("HOME").expect("failed to find $HOME env var");
|
|
let user = env::var("USER").expect("failed to find $USER env var");
|
|
format!(
|
|
"[Unit]
|
|
Description=agent to connect with monitor core
|
|
|
|
[Service]
|
|
ExecStart={home}/.monitor/bin/periphery --config-path {config_path} --home-dir {home}
|
|
Restart=on-failure
|
|
TimeoutStartSec=0
|
|
|
|
[Install]
|
|
WantedBy=default.target"
|
|
)
|
|
}
|
|
|
|
fn install_periphery_from_crates_io() {
|
|
println!("\ninstalling periphery binary...\n");
|
|
|
|
let install_output = run_command_pipe_to_terminal(&format!("cargo install {PERIPHERY_CRATE}"));
|
|
|
|
if install_output.success() {
|
|
println!("\ninstallation finished, starting monitor periphery daemon\n");
|
|
} else {
|
|
panic!(
|
|
"\n❌ there was some {} during periphery installation ❌\n",
|
|
"error".red()
|
|
)
|
|
}
|
|
}
|