diff --git a/conduwuit-example.toml b/conduwuit-example.toml index 8534e5c6..4062ba99 100644 --- a/conduwuit-example.toml +++ b/conduwuit-example.toml @@ -1362,6 +1362,13 @@ # #admin_execute_errors_ignore = false +# List of admin commands to execute on SIGUSR2. +# +# Similar to admin_execute, but these commands are executed when the +# server receives SIGUSR2 on supporting platforms. +# +#admin_signal_execute = [] + # Controls the max log level for admin command log captures (logs # generated from running admin commands). Defaults to "info" on release # builds, else "debug" on debug builds. diff --git a/src/core/config/mod.rs b/src/core/config/mod.rs index 8e8176ab..415c9ba9 100644 --- a/src/core/config/mod.rs +++ b/src/core/config/mod.rs @@ -1554,6 +1554,15 @@ pub struct Config { #[serde(default)] pub admin_execute_errors_ignore: bool, + /// List of admin commands to execute on SIGUSR2. + /// + /// Similar to admin_execute, but these commands are executed when the + /// server receives SIGUSR2 on supporting platforms. + /// + /// default: [] + #[serde(default)] + pub admin_signal_execute: Vec, + /// Controls the max log level for admin command log captures (logs /// generated from running admin commands). Defaults to "info" on release /// builds, else "debug" on debug builds. diff --git a/src/main/signal.rs b/src/main/signal.rs index dfdca1d5..343b95c9 100644 --- a/src/main/signal.rs +++ b/src/main/signal.rs @@ -17,6 +17,7 @@ pub(super) async fn signal(server: Arc) { let mut quit = unix::signal(SignalKind::quit()).expect("SIGQUIT handler"); let mut term = unix::signal(SignalKind::terminate()).expect("SIGTERM handler"); let mut usr1 = unix::signal(SignalKind::user_defined1()).expect("SIGUSR1 handler"); + let mut usr2 = unix::signal(SignalKind::user_defined2()).expect("SIGUSR2 handler"); loop { trace!("Installed signal handlers"); let sig: &'static str; @@ -25,6 +26,7 @@ pub(super) async fn signal(server: Arc) { _ = quit.recv() => { sig = "SIGQUIT"; }, _ = term.recv() => { sig = "SIGTERM"; }, _ = usr1.recv() => { sig = "SIGUSR1"; }, + _ = usr2.recv() => { sig = "SIGUSR2"; }, } warn!("Received {sig}"); diff --git a/src/service/admin/startup.rs b/src/service/admin/execute.rs similarity index 57% rename from src/service/admin/startup.rs rename to src/service/admin/execute.rs index 582e863d..462681da 100644 --- a/src/service/admin/startup.rs +++ b/src/service/admin/execute.rs @@ -2,6 +2,8 @@ use conduwuit::{debug, debug_info, error, implement, info, Err, Result}; use ruma::events::room::message::RoomMessageEventContent; use tokio::time::{sleep, Duration}; +pub(super) const SIGNAL: &str = "SIGUSR2"; + /// Possibly spawn the terminal console at startup if configured. #[implement(super::Service)] pub(super) async fn console_auto_start(&self) { @@ -22,7 +24,7 @@ pub(super) async fn console_auto_stop(&self) { /// Execute admin commands after startup #[implement(super::Service)] -pub(super) async fn startup_execute(&self) -> Result<()> { +pub(super) async fn startup_execute(&self) -> Result { // List of comamnds to execute let commands = &self.services.server.config.admin_execute; @@ -36,7 +38,7 @@ pub(super) async fn startup_execute(&self) -> Result<()> { sleep(Duration::from_millis(500)).await; for (i, command) in commands.iter().enumerate() { - if let Err(e) = self.startup_execute_command(i, command.clone()).await { + if let Err(e) = self.execute_command(i, command.clone()).await { if !errors { return Err(e); } @@ -59,16 +61,38 @@ pub(super) async fn startup_execute(&self) -> Result<()> { Ok(()) } -/// Execute one admin command after startup +/// Execute admin commands after signal #[implement(super::Service)] -async fn startup_execute_command(&self, i: usize, command: String) -> Result<()> { - debug!("Startup command #{i}: executing {command:?}"); +pub(super) async fn signal_execute(&self) -> Result { + // List of comamnds to execute + let commands = self.services.server.config.admin_signal_execute.clone(); + + // When true, errors are ignored and execution continues. + let ignore_errors = self.services.server.config.admin_execute_errors_ignore; + + for (i, command) in commands.iter().enumerate() { + if let Err(e) = self.execute_command(i, command.clone()).await { + if !ignore_errors { + return Err(e); + } + } + + tokio::task::yield_now().await; + } + + Ok(()) +} + +/// Execute one admin command after startup or signal +#[implement(super::Service)] +async fn execute_command(&self, i: usize, command: String) -> Result { + debug!("Execute command #{i}: executing {command:?}"); match self.command_in_place(command, None).await { - | Ok(Some(output)) => Self::startup_command_output(i, &output), - | Err(output) => Self::startup_command_error(i, &output), + | Ok(Some(output)) => Self::execute_command_output(i, &output), + | Err(output) => Self::execute_command_error(i, &output), | Ok(None) => { - info!("Startup command #{i} completed (no output)."); + info!("Execute command #{i} completed (no output)."); Ok(()) }, } @@ -76,28 +100,28 @@ async fn startup_execute_command(&self, i: usize, command: String) -> Result<()> #[cfg(feature = "console")] #[implement(super::Service)] -fn startup_command_output(i: usize, content: &RoomMessageEventContent) -> Result<()> { - debug_info!("Startup command #{i} completed:"); +fn execute_command_output(i: usize, content: &RoomMessageEventContent) -> Result { + debug_info!("Execute command #{i} completed:"); super::console::print(content.body()); Ok(()) } #[cfg(feature = "console")] #[implement(super::Service)] -fn startup_command_error(i: usize, content: &RoomMessageEventContent) -> Result<()> { +fn execute_command_error(i: usize, content: &RoomMessageEventContent) -> Result { super::console::print_err(content.body()); - Err!(debug_error!("Startup command #{i} failed.")) + Err!(debug_error!("Execute command #{i} failed.")) } #[cfg(not(feature = "console"))] #[implement(super::Service)] -fn startup_command_output(i: usize, content: &RoomMessageEventContent) -> Result<()> { - info!("Startup command #{i} completed:\n{:#}", content.body()); +fn execute_command_output(i: usize, content: &RoomMessageEventContent) -> Result { + info!("Execute command #{i} completed:\n{:#}", content.body()); Ok(()) } #[cfg(not(feature = "console"))] #[implement(super::Service)] -fn startup_command_error(i: usize, content: &RoomMessageEventContent) -> Result<()> { - Err!(error!("Startup command #{i} failed:\n{:#}", content.body())) +fn execute_command_error(i: usize, content: &RoomMessageEventContent) -> Result { + Err!(error!("Execute command #{i} failed:\n{:#}", content.body())) } diff --git a/src/service/admin/mod.rs b/src/service/admin/mod.rs index bc410631..31b046b7 100644 --- a/src/service/admin/mod.rs +++ b/src/service/admin/mod.rs @@ -1,7 +1,7 @@ pub mod console; mod create; +mod execute; mod grant; -mod startup; use std::{ future::Future, @@ -183,7 +183,11 @@ impl Service { .map(|complete| complete(command)) } - async fn handle_signal(&self, #[allow(unused_variables)] sig: &'static str) { + async fn handle_signal(&self, sig: &'static str) { + if sig == execute::SIGNAL { + self.signal_execute().await.ok(); + } + #[cfg(feature = "console")] self.console.handle_signal(sig).await; } diff --git a/src/service/config/mod.rs b/src/service/config/mod.rs index ef98f176..8bd09a52 100644 --- a/src/service/config/mod.rs +++ b/src/service/config/mod.rs @@ -1,4 +1,4 @@ -use std::{iter, path::Path, sync::Arc}; +use std::{iter, ops::Deref, path::Path, sync::Arc}; use async_trait::async_trait; use conduwuit::{ @@ -33,6 +33,13 @@ impl crate::Service for Service { fn name(&self) -> &str { crate::service::make_name(std::module_path!()) } } +impl Deref for Service { + type Target = Arc; + + #[inline] + fn deref(&self) -> &Self::Target { &self.server.config } +} + #[implement(Service)] fn handle_reload(&self) -> Result { if self.server.config.config_reload_signal {