...
 
Commits (4)
target/
**/*.rs.bk
This diff is collapsed.
[package]
name = "failsafe_slack_bot"
version = "0.0.0"
authors = ["Jan Harasym <jan.harasym@massive.se>"]
[dependencies]
slack = "0.23.0"
ssh = "0.1.4"
const_env = "0.1.2"
# failsafe-slack-bot
![failsafe logo](https://i.imgur.com/0B8Gmz5.png)
Zeta is complicated; failsafe is not.
Failsafes job is to ensure that zeta is running.
To accomplish this goal we:
1) Copy a script over to our "working directory"
2) Execute a script on the machine where zeta can run.
* Script checks to see if the process+db is running
* If it's running return early
* If not running: start a new process in tmux.
## Usage
```
# runtime needs this:
$ export SLACK_API_TOKEN=xxxx
# compiler needs this:
$ export SSH_USER=xxxx
$ export SSH_HOST=xxxx
$ export SSH_PASS=xxxx
$ cargo run
```
extern crate slack;
mod zeta;
use self::slack::RtmClient;
fn ping(channel: &str, cli: &RtmClient) {
let _ = cli.sender().send_message(channel, "Failsafe is here!");
}
fn failsafe(channel: &str, cli: &RtmClient) {
zeta::send_script();
let (success, message) = zeta::try_start();
if !success {
let err = format!(
"[✗] Failed to activate failsafe, {}", &message);
println!("{}", &err);
let _ = cli.sender().send_message(channel, &err);
} else {
let success = format!(
"[✓] Successfully ensured zeta is alive!: {}", &message);
println!("{}", &success);
let _ = cli.sender().send_message(channel, &success);
}
}
pub fn respond(bot_id: &str, text: &str, channel: &str, cli: &RtmClient) {
let ping_pattern = format!("<@{}> {}", bot_id, "failping");
let failsafe_pattern = format!("<@{}> {}", bot_id, "failsafe");
if text.contains(&ping_pattern) {
ping(channel, cli)
}
else if text.contains(&failsafe_pattern) {
failsafe(channel, cli)
}
}
extern crate ssh;
extern crate const_env;
use self::const_env::from_env;
use self::ssh::*;
use std::io::Read;
use std::io::Write;
#[from_env("SSH_HOST")]
const HOST: &'static str = "placeholder_hostname";
#[from_env("SSH_USER")]
const USER: &'static str = "placeholder_username";
#[from_env("SSH_PASS")]
const PASS: &'static str = "placeholder_password";
pub fn send_script() {
/* Goal here is to upload the shell script we will execute later
* We could read this in from a file, but that would mean
* that I have to ship failsafe as more than just a single binary
*/
let script: &'static str = r#"#!/usr/bin/env bash
set -u
set -o noglob
set -o pipefail
workingdir='/Users/jharasym/projects/external/errbot'
function start_zeta() {
cd "${workingdir}";
pg_ctl -D database status >/dev/null || \
pg_ctl -D database start -l pgstartup.log
/usr/local/bin/tmux new -d \
"venv/bin/errbot -c config.py";
echo -n "Started Instance";
}
#/Users/jharasym/projects/external/errbot/venv/bin/errbot -c /Users/jharasym/projects/external/errbot/config.py
ps aux | grep -v grep | grep -q "$workingdir/venv/bin/errbot -c $workingdir/config.py"
if [[ $? != 0 ]] ; then
ps aux | grep -v grep | grep -q 'errbot -c config.py'
if [[ $? != 0 ]] ; then
lsof ${workingdir}/venv/bin/python > /dev/null;
if [[ $? != 0 ]] ; then
start_zeta;
#>&2 echo "Not running; not programmed to start"
#exit 1;
else
echo -n 'Zeta running (unknown start)'
#lsof ${workingdir}/venv/bin/python;
fi
else
echo -n 'Zeta running (manual start)'
fi
else
echo -n 'Zeta running (automated start)'
fi
"#;
let mut session=Session::new().unwrap();
session.set_host(&HOST).unwrap();
session.parse_config(None).unwrap();
session.connect().unwrap();
session.set_username(&USER).unwrap();
//session.userauth_publickey_auto(None).unwrap();
match session.userauth_password(&PASS) {
Ok(_) => {},
Err(_) => return,
}
{
let mut scp = session.scp_new(
WRITE,
"/Users/jharasym/projects/external/errbot/")
.unwrap();
scp.init().unwrap();
let buf=script.as_bytes().to_vec();
scp.push_file("ensure_alive.sh",buf.len(),0o700).unwrap();
scp.write(&buf).unwrap();
}
}
pub fn try_start() -> (bool, String) {
/* Goal of this function is to SSH to my mac
* And run a special idempotent shell/python shell shit
* to make sure zeta is running and responding.
* if this function returns, we better make damned sure
* zeta is running. Panicking here is not an option.
*/
let mut success = false;
let mut session=Session::new().unwrap();
session.set_host(&HOST).unwrap();
session.set_username(&USER).unwrap();
session.parse_config(None).unwrap();
match session.connect() {
Ok(_) => {},
Err(error) => return (false, error.to_string()),
}
//println!("{:?}",session.is_server_known());
match session.userauth_password(&PASS) {
Ok(_) => {},
Err(error) => return (false, error.to_string()),
}
{
let mut s=session.channel_new().unwrap();
s.open_session().unwrap();
s.request_exec(
b"/Users/jharasym/projects/external/errbot/ensure_alive.sh")
.unwrap();
let exit = s.get_exit_status().unwrap();
match exit {
0 => success=true,
_ => println!("[✗] Script exit status is: {:?}", exit),
}
s.send_eof().unwrap();
let mut buf=Vec::new();
if success {
s.stdout().read_to_end(&mut buf).unwrap();
} else {
// FIXME: this will crash if there's nothing in the buffer.
match s.stderr().read_to_end(&mut buf) {
Ok(_) => {},
Err(error) => println!("Got error while trying to read stderr: {:?}", error),
}
}
return (
success,
format!("{:?}", std::str::from_utf8(&buf).unwrap())
);
}
}
extern crate slack;
use action;
use self::slack::{Event, EventHandler, Message, RtmClient};
pub struct Handler;
#[allow(unused_variables)]
impl EventHandler for Handler {
fn on_event(&mut self, cli: &RtmClient, event: Event) {
//println!("on_event(event: {:?})", event);
match event.clone() {
Event::Message(message) => self.handle_message(
*message, cli, &event
),
_ => return
};
}
fn on_close(&mut self, cli: &RtmClient) {
println!("[✗] Disconnected!");
}
fn on_connect(&mut self, cli: &RtmClient) {
println!("[✓] Connected!");
}
}
#[allow(unused_variables)]
impl Handler {
fn handle_message(&mut self, message: Message, cli: &RtmClient, event: &Event) {
let message_standard = match message {
Message::Standard(message_standard) => message_standard,
_ => return
};
let channel: String = message_standard.channel.unwrap();
let bot_id: &str = cli
.start_response()
.slf
.as_ref()
.unwrap()
.id
.as_ref()
.unwrap();
if &message_standard.user.unwrap() == bot_id {
println!("[ℹ] ignore bot message");
return
}
let text: String = message_standard.text.unwrap();
if !text.contains(bot_id) {
println!("[ℹ] ignoring non-mentioned message");
return
}
action::respond(&bot_id, &text, &channel, &cli);
}
}
pub mod action;
pub mod handler;
extern crate failsafe_slack_bot;
extern crate slack;
use failsafe_slack_bot::handler;
use slack::RtmClient;
use std::{env, process};
fn main() {
println!(
" ___ _ _ __ __
/ __\\_ _(_) / _\\ __ _ / _| ___
/ _\\/ _` | | \\ \\ / _` | |_ / _ \\
/ / | (_| | | |\\ \\ (_| | _| __/
\\/ \\__,_|_|_\\__/\\__,_|_| \\___|
Zeta Integrity Protection System
"
);
let api_key: String = api_key();
let mut handler = handler::Handler;
let r = RtmClient::login_and_run(&api_key, &mut handler);
match r {
Ok(_) => {},
Err(err) => panic!("Error: {}", err)
}
}
fn api_key() -> String {
match env::var("SLACK_API_TOKEN") {
Ok(val) => val,
Err(_) => {
println!("Required the SLACK_API_TOKEN environment variable");
process::exit(1);
}
}
}