commit
79a3188095
|
@ -0,0 +1,2 @@
|
||||||
|
[registries.jojo-dev]
|
||||||
|
index = "https://git.jojodev.com/joeyahines/_cargo-index.git"
|
|
@ -0,0 +1,29 @@
|
||||||
|
use config::Config;
|
||||||
|
use serde::{Deserialize, Serialize};
|
||||||
|
use std::path::PathBuf;
|
||||||
|
use structopt::StructOpt;
|
||||||
|
|
||||||
|
#[derive(StructOpt)]
|
||||||
|
pub struct Args {
|
||||||
|
pub config_path: PathBuf,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Debug, Clone, Serialize, Deserialize)]
|
||||||
|
pub struct DaemonConfig {
|
||||||
|
pub discord_api_token: String,
|
||||||
|
pub db_path: PathBuf,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl DaemonConfig {
|
||||||
|
pub fn new(config: PathBuf) -> DaemonConfig {
|
||||||
|
let daemon_config = Config::builder()
|
||||||
|
.add_source(config::File::new(
|
||||||
|
config.to_str().unwrap(),
|
||||||
|
config::FileFormat::Toml,
|
||||||
|
))
|
||||||
|
.build()
|
||||||
|
.unwrap();
|
||||||
|
|
||||||
|
daemon_config.try_deserialize().unwrap()
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,148 @@
|
||||||
|
use crate::config::DaemonConfig;
|
||||||
|
use crate::models::service::Service;
|
||||||
|
use j_db::database::Database;
|
||||||
|
use poise::serenity_prelude as serenity;
|
||||||
|
use poise::serenity_prelude::MessageBuilder;
|
||||||
|
|
||||||
|
#[allow(dead_code)]
|
||||||
|
struct Data {
|
||||||
|
db: Database,
|
||||||
|
config: DaemonConfig,
|
||||||
|
}
|
||||||
|
type Error = Box<dyn std::error::Error + Send + Sync>;
|
||||||
|
type Context<'a> = poise::Context<'a, Data, Error>;
|
||||||
|
|
||||||
|
#[poise::command(slash_command)]
|
||||||
|
async fn add_service(
|
||||||
|
ctx: Context<'_>,
|
||||||
|
#[description = "Service unit name to add"] service_name: String,
|
||||||
|
) -> Result<(), Error> {
|
||||||
|
let filter = format!("*{}*", service_name);
|
||||||
|
let services = systemctl::list_units_full(Some("service"), None, Some(&filter))?;
|
||||||
|
|
||||||
|
if let Some(service) = services
|
||||||
|
.iter()
|
||||||
|
.find(|s| s.unit_file.contains(&service_name))
|
||||||
|
{
|
||||||
|
let service = Service::new(&service.unit_file);
|
||||||
|
|
||||||
|
ctx.data().db.insert(service)?;
|
||||||
|
ctx.reply(format!(
|
||||||
|
"`{}` was added to the list of known services.",
|
||||||
|
service_name
|
||||||
|
))
|
||||||
|
.await?;
|
||||||
|
} else {
|
||||||
|
ctx.reply(format!("`{}` was not found.", service_name))
|
||||||
|
.await?;
|
||||||
|
}
|
||||||
|
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
#[poise::command(slash_command)]
|
||||||
|
async fn list_services(ctx: Context<'_>) -> Result<(), Error> {
|
||||||
|
let mut message_builder = serenity::MessageBuilder::new();
|
||||||
|
|
||||||
|
let services: Vec<String> = ctx
|
||||||
|
.data()
|
||||||
|
.db
|
||||||
|
.filter(|_, _s: &Service| true)?
|
||||||
|
.map(|s: Service| s.name)
|
||||||
|
.collect();
|
||||||
|
|
||||||
|
for service in &services {
|
||||||
|
let status = systemctl::is_active(service)?;
|
||||||
|
let status_emoji = if status {
|
||||||
|
":green_circle:"
|
||||||
|
} else {
|
||||||
|
":red_circle:"
|
||||||
|
};
|
||||||
|
message_builder.push_line(format!("* **{}** {}", service, status_emoji));
|
||||||
|
}
|
||||||
|
|
||||||
|
ctx.reply(message_builder.build()).await.unwrap();
|
||||||
|
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
#[poise::command(slash_command)]
|
||||||
|
async fn restart_service(
|
||||||
|
ctx: Context<'_>,
|
||||||
|
#[description = "Service unit name"] service_name: String,
|
||||||
|
) -> Result<(), Error> {
|
||||||
|
let service: Option<Service> = ctx
|
||||||
|
.data()
|
||||||
|
.db
|
||||||
|
.filter(|_, s: &Service| s.name.contains(&service_name))?
|
||||||
|
.next();
|
||||||
|
|
||||||
|
if let Some(service) = service {
|
||||||
|
systemctl::restart(&service.name)?;
|
||||||
|
ctx.reply(format!("`{}` has been restarted", service_name))
|
||||||
|
.await?;
|
||||||
|
} else {
|
||||||
|
ctx.reply(format!("Unknown service `{}`", service_name))
|
||||||
|
.await?;
|
||||||
|
}
|
||||||
|
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
#[poise::command(slash_command)]
|
||||||
|
async fn service_status(
|
||||||
|
ctx: Context<'_>,
|
||||||
|
#[description = "Service unit name"] service_name: String,
|
||||||
|
) -> Result<(), Error> {
|
||||||
|
let service: Option<Service> = ctx
|
||||||
|
.data()
|
||||||
|
.db
|
||||||
|
.filter(|_, s: &Service| s.name.contains(&service_name))?
|
||||||
|
.next();
|
||||||
|
|
||||||
|
if let Some(service) = service {
|
||||||
|
let status = systemctl::status(&service.name)?;
|
||||||
|
let mut msg = MessageBuilder::new();
|
||||||
|
|
||||||
|
msg.push_codeblock_safe(status, None);
|
||||||
|
|
||||||
|
ctx.reply(msg.build()).await?;
|
||||||
|
} else {
|
||||||
|
ctx.reply(format!("Unknown service `{}`", service_name))
|
||||||
|
.await?;
|
||||||
|
}
|
||||||
|
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
pub async fn run_bot(db: Database, config: DaemonConfig) {
|
||||||
|
let intents = serenity::GatewayIntents::non_privileged();
|
||||||
|
|
||||||
|
let data = Data {
|
||||||
|
db,
|
||||||
|
config: config.clone(),
|
||||||
|
};
|
||||||
|
|
||||||
|
let framework = poise::Framework::builder()
|
||||||
|
.options(poise::FrameworkOptions {
|
||||||
|
commands: vec![
|
||||||
|
list_services(),
|
||||||
|
add_service(),
|
||||||
|
restart_service(),
|
||||||
|
service_status(),
|
||||||
|
],
|
||||||
|
..Default::default()
|
||||||
|
})
|
||||||
|
.setup(|ctx, _ready, framework| {
|
||||||
|
Box::pin(async move {
|
||||||
|
poise::builtins::register_globally(ctx, &framework.options().commands).await?;
|
||||||
|
Ok(data)
|
||||||
|
})
|
||||||
|
})
|
||||||
|
.build();
|
||||||
|
|
||||||
|
let client = serenity::ClientBuilder::new(config.discord_api_token, intents)
|
||||||
|
.framework(framework)
|
||||||
|
.await;
|
||||||
|
client.unwrap().start().await.unwrap();
|
||||||
|
}
|
|
@ -0,0 +1 @@
|
||||||
|
pub mod service;
|
|
@ -0,0 +1,31 @@
|
||||||
|
use serde::{Deserialize, Serialize};
|
||||||
|
|
||||||
|
#[derive(Serialize, Deserialize, Debug, Clone)]
|
||||||
|
pub struct Service {
|
||||||
|
pub name: String,
|
||||||
|
|
||||||
|
id: Option<u64>,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Service {
|
||||||
|
pub fn new(name: &str) -> Self {
|
||||||
|
Self {
|
||||||
|
name: name.to_string(),
|
||||||
|
id: None,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl j_db::model::JdbModel for Service {
|
||||||
|
fn id(&self) -> Option<u64> {
|
||||||
|
self.id
|
||||||
|
}
|
||||||
|
|
||||||
|
fn set_id(&mut self, id: u64) {
|
||||||
|
self.id = Some(id)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn tree() -> String {
|
||||||
|
"Service".to_string()
|
||||||
|
}
|
||||||
|
}
|
Loading…
Reference in New Issue