From e9046d929f7246c7cf0b4acfa5f0ea3652718966 Mon Sep 17 00:00:00 2001 From: winstonallo Date: Tue, 20 May 2025 16:07:37 +0200 Subject: [PATCH 01/17] Write PID file after creating the socket --- src/bin/taskmaster.rs | 12 +----------- src/run/daemon.rs | 10 ++++++++++ 2 files changed, 11 insertions(+), 11 deletions(-) diff --git a/src/bin/taskmaster.rs b/src/bin/taskmaster.rs index 9352f61..f25f8e2 100644 --- a/src/bin/taskmaster.rs +++ b/src/bin/taskmaster.rs @@ -2,19 +2,11 @@ use std::{ env::{self}, error::Error, fs::remove_file, - io::Write, }; use tasklib::{conf::Config, log, log_info, run::daemon::Daemon}; -const PID_FILE_PATH: &str = "/tmp/taskmaster.pid"; - -fn write_pid_file() -> Result<(), Box> { - let pid = unsafe { libc::getpid() }; - let mut pid_file = std::fs::File::create(PID_FILE_PATH)?; - pid_file.write_all(pid.to_string().as_bytes())?; - Ok(()) -} +pub const PID_FILE_PATH: &str = "/tmp/taskmaster.pid"; #[tokio::main] async fn main() -> Result<(), Box> { @@ -40,8 +32,6 @@ async fn main() -> Result<(), Box> { } }; - write_pid_file()?; - log::init(conf.logfile())?; let mut daemon = Daemon::from_config(conf, arg); diff --git a/src/run/daemon.rs b/src/run/daemon.rs index 27f5b8d..aa8f984 100644 --- a/src/run/daemon.rs +++ b/src/run/daemon.rs @@ -1,3 +1,4 @@ +use std::io::Write; use std::sync::atomic::{AtomicBool, Ordering}; use std::{collections::HashMap, error::Error, sync::Arc, time::Duration}; @@ -193,6 +194,13 @@ impl Daemon { Ok(()) } + fn write_pid_file() -> Result<(), Box> { + let pid = unsafe { libc::getpid() }; + let mut pid_file = std::fs::File::create(PID_FILE_PATH)?; + pid_file.write_all(pid.to_string().as_bytes())?; + Ok(()) + } + pub async fn run(&mut self) -> Result<(), Box> { unsafe { libc::signal(SIGHUP, handler_reload as usize); @@ -203,6 +211,8 @@ impl Daemon { Err(e) => return Err(Box::::from(format!("Failed starting the taskmaster daemon: {e}"))), }; + Self::write_pid_file()?; + let (sender, mut receiver) = tokio::sync::mpsc::channel(1024); let sender = Arc::new(sender); let mut sigint = signal(SignalKind::interrupt())?; From e4ca1363e674030c0aff681f0f56e350a9a46db0 Mon Sep 17 00:00:00 2001 From: winstonallo Date: Tue, 20 May 2025 16:08:23 +0200 Subject: [PATCH 02/17] Move PID file creation to right before daemon loop --- src/run/daemon.rs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/run/daemon.rs b/src/run/daemon.rs index aa8f984..41c69c9 100644 --- a/src/run/daemon.rs +++ b/src/run/daemon.rs @@ -211,12 +211,12 @@ impl Daemon { Err(e) => return Err(Box::::from(format!("Failed starting the taskmaster daemon: {e}"))), }; - Self::write_pid_file()?; - let (sender, mut receiver) = tokio::sync::mpsc::channel(1024); let sender = Arc::new(sender); let mut sigint = signal(SignalKind::interrupt())?; + Self::write_pid_file()?; + loop { tokio::select! { accept_result = listener.accept() => { From f68686a48f978504b66da2b9e1cac5c89cf40092 Mon Sep 17 00:00:00 2001 From: winstonallo Date: Tue, 20 May 2025 14:48:22 +0000 Subject: [PATCH 03/17] Add setters + getters to HealthCheckRunner --- src/run/statemachine/healthcheck.rs | 18 ++++++++++++++++++ 1 file changed, 18 insertions(+) diff --git a/src/run/statemachine/healthcheck.rs b/src/run/statemachine/healthcheck.rs index ffacdf0..a8ab603 100644 --- a/src/run/statemachine/healthcheck.rs +++ b/src/run/statemachine/healthcheck.rs @@ -35,6 +35,24 @@ impl HealthCheckRunner { backoff: hc.backoff(), } } + pub fn set_healthcheck(&mut self, check: HealthCheckType) -> &mut Self { + self.check = check; + self + } + + pub fn set_retries(&mut self, retries: usize) -> &mut Self { + self.retries = retries; + self + } + + pub fn set_backoff(&mut self, backoff: usize) -> &mut Self { + self.backoff = backoff; + self + } + + pub fn check(&self) -> HealthCheckType { + self.check.clone() + } pub fn has_command_healthcheck(&self) -> bool { matches!(self.check, HealthCheckType::Command { .. }) From c44d53b80a0a709013285da7bf69398bef398d8c Mon Sep 17 00:00:00 2001 From: winstonallo Date: Tue, 20 May 2025 14:50:18 +0000 Subject: [PATCH 04/17] Make all global config fields constants --- src/conf.rs | 35 ++++++++++++++++++----------------- 1 file changed, 18 insertions(+), 17 deletions(-) diff --git a/src/conf.rs b/src/conf.rs index ffde888..b7f85fd 100644 --- a/src/conf.rs +++ b/src/conf.rs @@ -1,12 +1,8 @@ use std::{collections::HashMap, error::Error, fs}; -#[cfg(test)] -use defaults::{dflt_logfile, dflt_socketpath}; +use defaults::{dflt_authgroup, dflt_logfile, dflt_socketpath}; -use proc::{ - ProcessConfig, - types::{AuthGroup, WritableFile}, -}; +use proc::ProcessConfig; use serde::Deserialize; pub const PID_FILE_PATH: &str = "/tmp/taskmaster.pid"; @@ -22,9 +18,9 @@ pub struct Config { /// /// Default: /// ```toml - /// socketpath = "/tmp/.taskmaster.sock" + /// socketpath = "/tmp/taskmaster.sock" /// ``` - #[serde(default = "defaults::dflt_socketpath")] + #[serde(skip)] socketpath: String, /// Name of the group to be used for authenticating the client (similarly to @@ -34,7 +30,8 @@ pub struct Config { /// ```toml /// authgroup = "taskmaster" /// ``` - authgroup: Option, + #[serde(skip)] + authgroup: String, /// Path to the file the logs will be written to. /// @@ -42,8 +39,8 @@ pub struct Config { /// ```toml /// logfile = "/tmp/tasmaster.log" /// ``` - #[serde(default = "defaults::dflt_logfile")] - logfile: WritableFile, + #[serde(skip)] + logfile: String, /// Map of processes to configure individually. For process-level configuration, /// see [`crate::conf::proc::ProcessConfig`]. @@ -77,13 +74,17 @@ impl Config { } fn parse(config_str: &str) -> Result> { - let conf: Config = match toml::from_str(config_str) { + let mut conf: Config = match toml::from_str(config_str) { Ok(cnf) => cnf, Err(err) => { return Err(err.into()); } }; + conf.authgroup = dflt_authgroup(); + conf.socketpath = dflt_socketpath(); + conf.logfile = dflt_logfile(); + if conf.processes.is_empty() { return Err("taskmaster expects at least one process to be defined to operate".into()); } @@ -99,12 +100,12 @@ impl Config { &self.socketpath } - pub fn authgroup(&self) -> &Option { + pub fn authgroup(&self) -> &str { &self.authgroup } pub fn logfile(&self) -> &str { - self.logfile.path() + &self.logfile } } @@ -113,7 +114,7 @@ impl Default for Config { fn default() -> Self { Self { socketpath: dflt_socketpath(), - authgroup: None, + authgroup: dflt_authgroup(), logfile: dflt_logfile(), processes: HashMap::new(), } @@ -127,8 +128,8 @@ impl Config { self } - pub fn set_authgroup(&mut self, authgroup: AuthGroup) -> &mut Self { - self.authgroup = Some(authgroup); + pub fn set_authgroup(&mut self, authgroup: &str) -> &mut Self { + self.authgroup = authgroup.into(); self } From 9f2bd8d97ed42374479077232957f10b5d0f8021 Mon Sep 17 00:00:00 2001 From: winstonallo Date: Tue, 20 May 2025 14:50:39 +0000 Subject: [PATCH 05/17] Add plain logger function --- src/log.rs | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/src/log.rs b/src/log.rs index b9a6457..c951021 100644 --- a/src/log.rs +++ b/src/log.rs @@ -40,6 +40,12 @@ impl Logger { String::from_utf8_lossy(&buf[..ret as usize]).into_owned() } + pub fn plain(&self, message: &str) { + let mut guard = self.logfile.lock().expect("Mutex lock panicked in another thread"); + let _ = guard.write_all(message.as_bytes()); + let _ = guard.write_all(b"\n"); + } + pub fn error(&self, message: fmt::Arguments, fields: BTreeMap) { let mut log_entry = fields; log_entry.insert("timestamp".to_string(), json!(Logger::get_time_fmt())); @@ -98,6 +104,10 @@ impl Logger { static mut INSTANCE: Option = None; static INIT: Once = Once::new(); +pub fn plain(message: &str) { + get_logger().plain(message); +} + pub fn error(message: fmt::Arguments, fields: BTreeMap) { get_logger().error(message, fields); } From 41f963836c41c6a1c4fa29ac6f4b20435f49bb82 Mon Sep 17 00:00:00 2001 From: winstonallo Date: Tue, 20 May 2025 14:51:10 +0000 Subject: [PATCH 06/17] Remove AuthGroup type in favor of String --- src/conf/defaults.rs | 6 +-- src/conf/proc/types.rs | 2 - src/conf/proc/types/authgroup.rs | 65 -------------------------------- src/jsonrpc/handlers.rs | 11 ++---- src/run/daemon.rs | 10 +++-- src/run/daemon/socket.rs | 34 +++++++++++------ 6 files changed, 35 insertions(+), 93 deletions(-) delete mode 100644 src/conf/proc/types/authgroup.rs diff --git a/src/conf/defaults.rs b/src/conf/defaults.rs index 47b33ba..7f1c0f9 100644 --- a/src/conf/defaults.rs +++ b/src/conf/defaults.rs @@ -1,5 +1,3 @@ -use super::proc::types::WritableFile; - pub fn dflt_socketpath() -> String { "/tmp/taskmaster.sock".to_string() } @@ -8,6 +6,6 @@ pub fn dflt_authgroup() -> String { "taskmaster".to_string() } -pub fn dflt_logfile() -> WritableFile { - WritableFile::from_path("/tmp/taskmaster.log") +pub fn dflt_logfile() -> String { + "/tmp/taskmaster.log".to_string() } diff --git a/src/conf/proc/types.rs b/src/conf/proc/types.rs index 5c816ac..f7509e3 100644 --- a/src/conf/proc/types.rs +++ b/src/conf/proc/types.rs @@ -1,4 +1,3 @@ -mod authgroup; mod autorestart; mod healthcheck; mod path; @@ -6,7 +5,6 @@ mod stopsignal; mod umask; pub use self::{ - authgroup::AuthGroup, autorestart::AutoRestart, healthcheck::{CommandHealthCheck, HealthCheck, HealthCheckType, UptimeHealthCheck}, path::{AccessibleDirectory, ExecutableFile, WritableFile}, diff --git a/src/conf/proc/types/authgroup.rs b/src/conf/proc/types/authgroup.rs deleted file mode 100644 index 82ff764..0000000 --- a/src/conf/proc/types/authgroup.rs +++ /dev/null @@ -1,65 +0,0 @@ -use serde::Deserialize; - -#[derive(Clone)] -pub struct AuthGroup { - id: u32, - name: String, -} - -impl AuthGroup { - pub fn from_group_name(group_name: &str) -> Result { - match get_group_id(group_name) { - Ok(id) => Ok(AuthGroup { id, name: group_name.into() }), - Err(e) => Err(e), - } - } - - pub fn id(&self) -> u32 { - self.id - } - - pub fn name(&self) -> &str { - &self.name - } -} - -fn get_group_id(group_name: &str) -> Result { - let c_group = std::ffi::CString::new(group_name).map_err(|e| format!("{e}"))?; - - unsafe { - let grp_ptr = libc::getgrnam(c_group.as_ptr()); - if grp_ptr.is_null() { - Err(format!("Group '{group_name}' not found")) - } else { - Ok((*grp_ptr).gr_gid) - } - } -} - -impl<'de> Deserialize<'de> for AuthGroup { - fn deserialize(deserializer: D) -> Result - where - D: serde::Deserializer<'de>, - { - let group_name = String::deserialize(deserializer)?; - match AuthGroup::from_group_name(&group_name) { - Ok(group) => Ok(group), - Err(e) => Err(serde::de::Error::custom(e.to_string())), - } - } -} - -#[cfg(test)] -mod tests { - use super::*; - - #[test] - fn get_group_id_success() { - assert_eq!(get_group_id("root").unwrap(), 0); - } - - #[test] - fn get_group_id_nonexisting() { - assert!(get_group_id("randomaaaahgroup").is_err()); - } -} diff --git a/src/jsonrpc/handlers.rs b/src/jsonrpc/handlers.rs index e4d6af5..408ad60 100644 --- a/src/jsonrpc/handlers.rs +++ b/src/jsonrpc/handlers.rs @@ -9,7 +9,6 @@ use super::{ response::ErrorCode, }; use crate::{ - conf::proc::types::AuthGroup, jsonrpc::{ response::{ResponseResult, ResponseType}, short_process::ShortProcess, @@ -186,7 +185,7 @@ async fn update_attach_stream(file: &mut tokio::fs::File, pos: u64, len: u64, li Ok(pos) } -async fn attach(socketpath: &str, to: &str, authgroup: &Option) -> Result<(), Box> { +async fn attach(socketpath: &str, to: &str, authgroup: &str) -> Result<(), Box> { let mut listener = AsyncUnixSocket::new(socketpath, authgroup).map_err(|e| Box::::from(format!("could not create new socket stream: {e}")))?; @@ -222,11 +221,7 @@ pub struct AttachmentManager { } enum AttachmentRequest { - New { - socketpath: String, - to: String, - authgroup: Option, - }, + New { socketpath: String, to: String, authgroup: String }, } impl Default for AttachmentManager { @@ -264,7 +259,7 @@ impl AttachmentManager { Self { tx } } - pub async fn attach(&self, socketpath: &str, to: &str, authgroup: &Option) -> Result<(), Box> { + pub async fn attach(&self, socketpath: &str, to: &str, authgroup: &str) -> Result<(), Box> { self.tx .send(AttachmentRequest::New { socketpath: socketpath.to_owned(), diff --git a/src/run/daemon.rs b/src/run/daemon.rs index 41c69c9..98663eb 100644 --- a/src/run/daemon.rs +++ b/src/run/daemon.rs @@ -12,7 +12,6 @@ use tokio::time::sleep; use super::proc::{self, Process}; use super::statemachine::states::ProcessState; -use crate::conf::proc::types::AuthGroup; use crate::conf::{Config, PID_FILE_PATH}; use crate::jsonrpc::handlers::AttachmentManager; use crate::jsonrpc::response::{Response, ResponseError, ResponseType}; @@ -28,7 +27,7 @@ pub mod socket; pub struct Daemon { processes: HashMap, socket_path: String, - auth_group: Option, + auth_group: String, config_path: String, shutting_down: bool, attachment_manager: AttachmentManager, @@ -73,7 +72,7 @@ impl Daemon { &self.socket_path } - pub fn auth_group(&self) -> &Option { + pub fn auth_group(&self) -> &str { &self.auth_group } @@ -171,6 +170,11 @@ impl Daemon { process_old.push_desired_state(ProcessState::Stopped); } *process_old.config_mut() = process_new.config().clone(); + (*process_old) + .healthcheck_mut() + .set_healthcheck(process_new.healthcheck().check()) + .set_backoff(process_new.healthcheck().backoff()) + .set_retries(process_new.healthcheck().retries()); match process_old.config().autostart() { false => process_old.push_desired_state(ProcessState::Idle), diff --git a/src/run/daemon/socket.rs b/src/run/daemon/socket.rs index e57f6e6..d4f6425 100644 --- a/src/run/daemon/socket.rs +++ b/src/run/daemon/socket.rs @@ -8,28 +8,40 @@ use tokio::{ net::{UnixListener, UnixStream, unix::SocketAddr}, }; -use crate::conf::proc::types::AuthGroup; - #[allow(unused)] pub struct AsyncUnixSocket { socketpath: String, - authgroup: Option, + authgroup: String, listener: Option, stream: Option, } #[cfg(not(test))] -fn set_permissions(socketpath: &str, authgroup: &AuthGroup) -> Result<(), String> { +fn get_group_id(group_name: &str) -> Result { + let c_group = std::ffi::CString::new(group_name).map_err(|e| format!("{e}"))?; + + unsafe { + let grp_ptr = libc::getgrnam(c_group.as_ptr()); + if grp_ptr.is_null() { + Err(format!("Group '{group_name}' not found")) + } else { + Ok((*grp_ptr).gr_gid) + } + } +} + +#[cfg(not(test))] +fn set_permissions(socketpath: &str, authgroup: &str, gid: u32) -> Result<(), String> { use std::os::unix::fs::PermissionsExt; let c_path = std::ffi::CString::new(socketpath).map_err(|e| format!("invalid path: {e}"))?; unsafe { - if libc::chown(c_path.as_ptr(), u32::MAX, authgroup.id() as libc::gid_t) != 0 { + if libc::chown(c_path.as_ptr(), u32::MAX, gid as libc::gid_t) != 0 { return Err(format!( "could not change group ownership: {} - do you have permissions for group '{}'?", std::io::Error::last_os_error(), - authgroup.name() + authgroup )); } } @@ -38,7 +50,7 @@ fn set_permissions(socketpath: &str, authgroup: &AuthGroup) -> Result<(), String } impl AsyncUnixSocket { - pub fn new(socketpath: &str, authgroup: &Option) -> Result { + pub fn new(socketpath: &str, authgroup: &str) -> Result { if fs::metadata(socketpath).is_ok() { let _ = fs::remove_file(socketpath); } @@ -51,10 +63,10 @@ impl AsyncUnixSocket { }; #[cfg(not(test))] - if authgroup.is_some() { - if let Err(e) = set_permissions(socketpath, authgroup.as_ref().unwrap()) { - return Err(format!("could not create UNIX socket at path {socketpath}: {e}")); - } + let gid = get_group_id(authgroup)?; + #[cfg(not(test))] + if let Err(e) = set_permissions(socketpath, authgroup, gid) { + return Err(format!("could not create UNIX socket at path {socketpath}: {e}")); } Ok(Self { From 822e5f339df3fe4de9e5bb6d3f445cea15898688 Mon Sep 17 00:00:00 2001 From: winstonallo Date: Tue, 20 May 2025 14:52:32 +0000 Subject: [PATCH 07/17] Fix failing tests due to extra config field socketpath --- src/jsonrpc/handlers.rs | 2 -- tests/configs/example.toml | 6 ------ tests/configs/sleep.toml | 2 -- 3 files changed, 10 deletions(-) diff --git a/src/jsonrpc/handlers.rs b/src/jsonrpc/handlers.rs index 408ad60..4cd2ce5 100644 --- a/src/jsonrpc/handlers.rs +++ b/src/jsonrpc/handlers.rs @@ -568,8 +568,6 @@ mod tests { #[tokio::test] async fn reload_config_file_gone() { let conf = r#" - socketpath = "/tmp/.taskmaster.sock" - [processes.sleep] cmd = "/usr/bin/sleep" args = ["2"] diff --git a/tests/configs/example.toml b/tests/configs/example.toml index fc4e8b9..7c67dbd 100644 --- a/tests/configs/example.toml +++ b/tests/configs/example.toml @@ -1,9 +1,3 @@ -# Example config for taskmaster. - -# Path to the socket used for connections between taskmaster and its client. -# Defaults to "/tmp/.taskmaster.sock". -socketpath = "/tmp/.taskmaster.sock" - # Process name. [processes.sleep] diff --git a/tests/configs/sleep.toml b/tests/configs/sleep.toml index ba3536d..087c723 100644 --- a/tests/configs/sleep.toml +++ b/tests/configs/sleep.toml @@ -1,5 +1,3 @@ -socketpath = "/tmp/.taskmaster.sock" - [processes.sleep] cmd = "/usr/bin/sleep" args = ["2"] From e012eaf697942a6b32901413e84308e81869eb8d Mon Sep 17 00:00:00 2001 From: winstonallo Date: Tue, 20 May 2025 15:05:19 +0000 Subject: [PATCH 08/17] Update all configs to comply with new struct --- config/attach.toml | 9 +++------ config/example.toml | 3 --- config/felix.toml | 3 --- config/healthcheck.toml | 5 +---- config/privilege_deescalation.toml | 2 -- config/signal.toml | 2 -- config/unexisting_group.toml | 1 - 7 files changed, 4 insertions(+), 21 deletions(-) diff --git a/config/attach.toml b/config/attach.toml index 06e7d07..6bfbb84 100644 --- a/config/attach.toml +++ b/config/attach.toml @@ -1,15 +1,12 @@ -socketpath = "/tmp/taskmaster.sock" -authgroup = "winstonallo" - [processes.sleepwrite] cmd = "/usr/bin/python3" -args = ["/home/winstonallo/mastery/taskmaster/executables/sleepwrite.py"] +args = ["executables/sleepwrite.py"] processes = 1 umask = "022" -workingdir = "/tmp" +workingdir = "." autostart = true autorestart = "always" -exitcodes = [0, 2] +exitcodes = [0] stopsignals = ["USR1"] stoptime = 5 stdout = "/tmp/sleepwrite.stdout" diff --git a/config/example.toml b/config/example.toml index ef2ea1c..5979bb8 100644 --- a/config/example.toml +++ b/config/example.toml @@ -1,6 +1,3 @@ -socketpath = "/tmp/.taskmaster.sock" -authgroup = "winstonallo" - [processes.sleep] cmd = "/usr/bin/sleep" args = ["1"] diff --git a/config/felix.toml b/config/felix.toml index 4c87369..c5a17c6 100644 --- a/config/felix.toml +++ b/config/felix.toml @@ -1,6 +1,3 @@ -socketpath = "/tmp/taskmaster.sock" -authgroup = "root" - [processes.sleep] cmd = "/bin/sleep" args = ["3"] diff --git a/config/healthcheck.toml b/config/healthcheck.toml index f0ecea2..b7b72a3 100644 --- a/config/healthcheck.toml +++ b/config/healthcheck.toml @@ -1,6 +1,3 @@ -socketpath = "/tmp/.taskmaster.sock" - - [processes.sleep] cmd = "/usr/bin/sleep" @@ -20,5 +17,5 @@ env = [["STARTED_BY", "abied-ch"], ["ANSWER", "42"]] cmd = "/usr/bin/sleep" args = ["2"] backoff = 1 -timeout = 1 +timeout = 5 retries = 1 diff --git a/config/privilege_deescalation.toml b/config/privilege_deescalation.toml index da237f4..98fab60 100644 --- a/config/privilege_deescalation.toml +++ b/config/privilege_deescalation.toml @@ -1,5 +1,3 @@ -authgroup = "root" - [processes.root] cmd = "/usr/bin/id" diff --git a/config/signal.toml b/config/signal.toml index 573a4ce..b646d60 100644 --- a/config/signal.toml +++ b/config/signal.toml @@ -1,5 +1,3 @@ -authgroup = "winstonallo" - [processes.signal] cmd = "/usr/bin/python3" diff --git a/config/unexisting_group.toml b/config/unexisting_group.toml index 7237d65..412afca 100644 --- a/config/unexisting_group.toml +++ b/config/unexisting_group.toml @@ -1,4 +1,3 @@ -authgroup = "asdpihgasbldkjalshdbliasjdlkasbdasd" [processes.sleep] cmd = "/usr/bin/sleep" From a393341bb63beda69a9c96f0b3b2762223e3851c Mon Sep 17 00:00:00 2001 From: Arthur Bied-Charreton Date: Tue, 20 May 2025 16:39:17 +0000 Subject: [PATCH 09/17] Add taskmaster group in Dockerfile --- .devcontainer/Dockerfile | 1 + 1 file changed, 1 insertion(+) diff --git a/.devcontainer/Dockerfile b/.devcontainer/Dockerfile index f0b7b36..d35d514 100644 --- a/.devcontainer/Dockerfile +++ b/.devcontainer/Dockerfile @@ -11,6 +11,7 @@ RUN apt-get update && apt-get install -y \ nginx \ && rm -rf /var/lib/apt/lists/* +RUN groupadd taskmaster RUN rustup component add rustfmt clippy RUN echo 'export PS1="\W> "' >> ~/.bashrc \ No newline at end of file From 0174b3428ece4778238751b07fdc5b7249dd1be9 Mon Sep 17 00:00:00 2001 From: Arthur Bied-Charreton Date: Tue, 20 May 2025 16:39:41 +0000 Subject: [PATCH 10/17] Add example configs --- config/autorestart.toml | 18 ++++++++++++++++++ config/autostart.toml | 9 +++++++++ config/command.toml | 5 +++++ config/env.toml | 7 +++++++ config/exitcode.toml | 13 +++++++++++++ config/processes.toml | 8 ++++++++ config/{attach.toml => redirect.toml} | 0 config/retries.toml | 21 +++++++++++++++++++++ config/signal.toml | 2 ++ config/stoptime.toml | 9 +++++++++ config/umask.toml | 15 +++++++++++++++ config/uptime.toml | 17 +++++++++++++++++ config/workingdir.toml | 13 +++++++++++++ executables/env.sh | 1 + 14 files changed, 138 insertions(+) create mode 100644 config/autorestart.toml create mode 100644 config/autostart.toml create mode 100644 config/command.toml create mode 100644 config/env.toml create mode 100644 config/exitcode.toml create mode 100644 config/processes.toml rename config/{attach.toml => redirect.toml} (100%) create mode 100644 config/retries.toml create mode 100644 config/stoptime.toml create mode 100644 config/umask.toml create mode 100644 config/uptime.toml create mode 100644 config/workingdir.toml create mode 100755 executables/env.sh diff --git a/config/autorestart.toml b/config/autorestart.toml new file mode 100644 index 0000000..fa929c0 --- /dev/null +++ b/config/autorestart.toml @@ -0,0 +1,18 @@ +[processes.no] +workingdir = "." +cmd = "/usr/bin/ls" +autorestart = "no" +autostart = true + +[processes.always] +workingdir = "." +cmd = "/usr/bin/ls" +autorestart = "always" +autostart = true + +[processes.on-failure] +workingdir = "." +cmd = "/usr/bin/ls" +autorestart = "on-failure[:5]" +exitcodes = [1] +autostart = true diff --git a/config/autostart.toml b/config/autostart.toml new file mode 100644 index 0000000..438a8b5 --- /dev/null +++ b/config/autostart.toml @@ -0,0 +1,9 @@ +[processes.autostart] +cmd = "/usr/bin/ls" +workingdir = "." +autostart = true + +[processes.notautostart] +cmd = "/usr/bin/ls" +workingdir = "." +autostart = false diff --git a/config/command.toml b/config/command.toml new file mode 100644 index 0000000..37b2079 --- /dev/null +++ b/config/command.toml @@ -0,0 +1,5 @@ +[processes.command] + +cmd = "/usr/bin/echo" +args = ["Hello, World!"] +workingdir = "." diff --git a/config/env.toml b/config/env.toml new file mode 100644 index 0000000..491bd5e --- /dev/null +++ b/config/env.toml @@ -0,0 +1,7 @@ +[processes.env] + +cmd = "/bin/bash" +args = ["executables/env.sh"] +env = [["HELLO", "WORLD"]] +workingdir = "." +stdout = "/tmp/env.stdout" diff --git a/config/exitcode.toml b/config/exitcode.toml new file mode 100644 index 0000000..4b27af7 --- /dev/null +++ b/config/exitcode.toml @@ -0,0 +1,13 @@ +[processes.succcess] +cmd = "/bin/bash" +args = ["-c", "exit 0"] +workingdir = "." +exitcodes = [0] +autostart = true + +[processes.failure] +cmd = "/bin/bash" +args = ["-c", "exit 1"] +workingdir = "." +exitcodes = [0] +autostart = true diff --git a/config/processes.toml b/config/processes.toml new file mode 100644 index 0000000..11776ba --- /dev/null +++ b/config/processes.toml @@ -0,0 +1,8 @@ +[processes.processes] + +cmd = "/usr/bin/sleep" +args = ["1"] +workingdir = "." +autorestart = "no" +autostart = true +processes = 10 diff --git a/config/attach.toml b/config/redirect.toml similarity index 100% rename from config/attach.toml rename to config/redirect.toml diff --git a/config/retries.toml b/config/retries.toml new file mode 100644 index 0000000..5e4fd12 --- /dev/null +++ b/config/retries.toml @@ -0,0 +1,21 @@ +[processes.retries_1] +cmd = "/usr/bin/sleep" +args = ["1"] +workingdir = "." +autostart = true +exitcodes = [1] +[processes.retries_1.healthcheck] +starttime = 2 +retries = 1 +backoff = 2 + +[processes.retries_5] +cmd = "/usr/bin/sleep" +args = ["1"] +workingdir = "." +autostart = true +exitcodes = [1] +[processes.retries_5.healthcheck] +starttime = 2 +retries = 5 +backoff = 2 diff --git a/config/signal.toml b/config/signal.toml index b646d60..eee2b31 100644 --- a/config/signal.toml +++ b/config/signal.toml @@ -4,3 +4,5 @@ cmd = "/usr/bin/python3" args = ["executables/signals.py"] workingdir = "." autostart = true +stopsignals = ["TERM", "USR1", "ILL"] +stdout = "/tmp/signal.stdout" \ No newline at end of file diff --git a/config/stoptime.toml b/config/stoptime.toml new file mode 100644 index 0000000..bdc3eef --- /dev/null +++ b/config/stoptime.toml @@ -0,0 +1,9 @@ +[processes.signal] + +cmd = "/usr/bin/python3" +args = ["executables/signals.py"] +workingdir = "." +autostart = true +stopsignals = ["TERM", "USR1", "ILL"] +stoptime = 5 +stdout = "/tmp/signal.stdout" diff --git a/config/umask.toml b/config/umask.toml new file mode 100644 index 0000000..b6fb669 --- /dev/null +++ b/config/umask.toml @@ -0,0 +1,15 @@ +[processes.000] + +cmd = "/usr/bin/touch" +args = ["000"] +workingdir = "." +autostart = true +umask = "000" + +[processes.022] + +cmd = "/usr/bin/touch" +args = ["022"] +workingdir = "." +autostart = true +umask = "022" diff --git a/config/uptime.toml b/config/uptime.toml new file mode 100644 index 0000000..4af69e7 --- /dev/null +++ b/config/uptime.toml @@ -0,0 +1,17 @@ +[processes.uptime_success] +cmd = "/usr/bin/sleep" +args = ["10"] +workingdir = "." +autostart = true +[processes.uptime_success.healthcheck] +starttime = 2 + +[processes.uptime_failure] +cmd = "/usr/bin/sleep" +args = ["1"] +workingdir = "." +autostart = true +exitcodes = [1] +[processes.uptime_failure.healthcheck] +starttime = 2 +retries = 1 diff --git a/config/workingdir.toml b/config/workingdir.toml new file mode 100644 index 0000000..f84a45c --- /dev/null +++ b/config/workingdir.toml @@ -0,0 +1,13 @@ +[processes.workingdir_dot] + +cmd = "/usr/bin/pwd" +workingdir = "." +stdout = "/tmp/workingdir_dot.stdout" +autostart = true + +[processes.workingdir_tmp] + +cmd = "/usr/bin/pwd" +workingdir = "/tmp" +stdout = "/tmp/workingdir_tmp.stdout" +autostart = true diff --git a/executables/env.sh b/executables/env.sh new file mode 100755 index 0000000..922b12c --- /dev/null +++ b/executables/env.sh @@ -0,0 +1 @@ +echo $HELLO \ No newline at end of file From 8eebc93dd41f1627005f4f4009ffd8ef4d104cae Mon Sep 17 00:00:00 2001 From: Arthur Bied-Charreton Date: Tue, 20 May 2025 16:40:00 +0000 Subject: [PATCH 11/17] Fix waiting for retry timing display --- src/jsonrpc/short_process.rs | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/jsonrpc/short_process.rs b/src/jsonrpc/short_process.rs index 6296fb8..1715fe8 100644 --- a/src/jsonrpc/short_process.rs +++ b/src/jsonrpc/short_process.rs @@ -1,4 +1,4 @@ -use std::fmt; +use std::{fmt, time::Instant}; use serde::{Deserialize, Serialize}; @@ -48,7 +48,7 @@ impl State { ProcessState::HealthCheck(instant) => Self::HealthCheck(instant.elapsed().as_secs()), ProcessState::Healthy => Self::Healthy, ProcessState::Failed(_) => Self::Failed, - ProcessState::WaitingForRetry(instant) => Self::WaitingForRetry(instant.elapsed().as_secs()), + ProcessState::WaitingForRetry(instant) => Self::WaitingForRetry(instant.duration_since(Instant::now()).as_secs()), ProcessState::Completed => Self::Completed, ProcessState::Stopping(instant) => Self::Stopping(instant.elapsed().as_secs()), ProcessState::Stopped => Self::Stopped, @@ -66,7 +66,7 @@ impl fmt::Display for State { HealthCheck(s) => format!("healthcheck since {s} seconds"), Healthy => "healthy".to_owned(), Failed => "failed".to_owned(), - WaitingForRetry(s) => format!("waiting for retry since {s} seconds"), + WaitingForRetry(s) => format!("waiting for retry - {s} seconds left"), Completed => "completed".to_owned(), Stopping(s) => format!("stopping since {s} seconds"), Stopped => "stopped".to_owned(), From 07183ca711171574ce1c3d09436f2c9457c2fe5a Mon Sep 17 00:00:00 2001 From: Arthur Bied-Charreton Date: Tue, 20 May 2025 16:40:19 +0000 Subject: [PATCH 12/17] Add log message in Daemon::reload --- src/run/daemon.rs | 15 ++++++++------- 1 file changed, 8 insertions(+), 7 deletions(-) diff --git a/src/run/daemon.rs b/src/run/daemon.rs index 98663eb..77f2b55 100644 --- a/src/run/daemon.rs +++ b/src/run/daemon.rs @@ -15,7 +15,7 @@ use super::statemachine::states::ProcessState; use crate::conf::{Config, PID_FILE_PATH}; use crate::jsonrpc::handlers::AttachmentManager; use crate::jsonrpc::response::{Response, ResponseError, ResponseType}; -use crate::log_info; +use crate::{log_info}; use crate::{ conf, jsonrpc::{handlers::handle_request, request::Request}, @@ -152,6 +152,7 @@ impl Daemon { } pub fn reload(&mut self) -> Result<(), String> { + log_info!("reloading configuration"); let conf = match Config::from_file(self.config_path()) { Ok(c) => c, Err(e) => return Err(format!("{}", e).to_owned()), @@ -168,21 +169,21 @@ impl Daemon { Some(process_old) => { if process_old.config() != process_new.config() { process_old.push_desired_state(ProcessState::Stopped); - } - *process_old.config_mut() = process_new.config().clone(); - (*process_old) + *process_old.config_mut() = process_new.config().clone(); + (*process_old) .healthcheck_mut() .set_healthcheck(process_new.healthcheck().check()) .set_backoff(process_new.healthcheck().backoff()) .set_retries(process_new.healthcheck().retries()); - + match process_old.config().autostart() { false => process_old.push_desired_state(ProcessState::Idle), true => process_old.push_desired_state(ProcessState::Healthy), } - - leftover.retain(|n| n != process_old.name()); + } + leftover.retain(|n| n != process_old.name()); + } None => { let _ = self.processes_mut().insert(process_name_new, process_new); } From f715adaebdd2c97da76a35095d5ac8b1770fb3dc Mon Sep 17 00:00:00 2001 From: Arthur Bied-Charreton Date: Tue, 20 May 2025 16:40:39 +0000 Subject: [PATCH 13/17] Retry retries times instead of retries - 1 --- src/run/statemachine/monitor.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/run/statemachine/monitor.rs b/src/run/statemachine/monitor.rs index b5116aa..c7add1f 100644 --- a/src/run/statemachine/monitor.rs +++ b/src/run/statemachine/monitor.rs @@ -147,7 +147,7 @@ pub fn failed_healthy(p: &mut Process) -> Option { pub fn failed_healthcheck(p: &mut Process) -> Option { p.increment_healthcheck_failures(); - if p.healthcheck_failures() == p.healthcheck().retries() { + if p.healthcheck_failures() > p.healthcheck().retries() { p.push_desired_state(ProcessState::Stopped); proc_warning!(p, "not healthy after {} attempts, giving up", p.healthcheck().retries()); From 11cd92e413c37a0a709ef9ef5f9b5b6d5f6a2d0c Mon Sep 17 00:00:00 2001 From: Arthur Bied-Charreton Date: Tue, 20 May 2025 16:42:43 +0000 Subject: [PATCH 14/17] Improve error message on Unix socket connection failure in attach --- src/bin/taskshell.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/bin/taskshell.rs b/src/bin/taskshell.rs index 41440d5..10359f3 100644 --- a/src/bin/taskshell.rs +++ b/src/bin/taskshell.rs @@ -200,7 +200,7 @@ async fn attach(name: &str, socket_path: &str, to: &str, mut orig: Option<&mut l let stream = match UnixStream::connect(socket_path).await { Ok(stream) => stream, Err(e) => { - eprintln!("could not establish connection on attach socket at path {socket_path}: {e}"); + eprintln!("Could not establish connection on attach socket at path {socket_path}: {e}.\nIs the process running?"); return "".to_string(); } }; From 965a9150bcd91c44e48a7007fb4fbe8efcc3d2fa Mon Sep 17 00:00:00 2001 From: Arthur Bied-Charreton Date: Tue, 20 May 2025 16:56:30 +0000 Subject: [PATCH 15/17] Add protection against duplicated output paths --- config/duplicatestdout.toml | 11 +++++++++++ src/conf.rs | 26 ++++++++++++++++++++++++-- 2 files changed, 35 insertions(+), 2 deletions(-) create mode 100644 config/duplicatestdout.toml diff --git a/config/duplicatestdout.toml b/config/duplicatestdout.toml new file mode 100644 index 0000000..b2a4997 --- /dev/null +++ b/config/duplicatestdout.toml @@ -0,0 +1,11 @@ +[processes.no] +workingdir = "." +cmd = "/usr/bin/ls" +stdout = "asd" +autostart = true + +[processes.always] +workingdir = "." +cmd = "/usr/bin/ls" +stdout = "asd" +autostart = true diff --git a/src/conf.rs b/src/conf.rs index b7f85fd..4e6a973 100644 --- a/src/conf.rs +++ b/src/conf.rs @@ -1,8 +1,8 @@ -use std::{collections::HashMap, error::Error, fs}; +use std::{collections::{HashMap, HashSet}, error::Error, fs}; use defaults::{dflt_authgroup, dflt_logfile, dflt_socketpath}; -use proc::ProcessConfig; +use proc::{ProcessConfig}; use serde::Deserialize; pub const PID_FILE_PATH: &str = "/tmp/taskmaster.pid"; @@ -89,6 +89,28 @@ impl Config { return Err("taskmaster expects at least one process to be defined to operate".into()); } + let mut seen = HashSet::new(); + let duplicates = conf.processes.iter() + .filter(|p| p.1.stdout().is_some()) + .map(|p| p.1.stdout().as_ref().unwrap().path().to_owned()) + .filter(|path| !seen.insert(path.clone())) + .collect::>(); + + if !duplicates.is_empty() { + return Err(Box::::from(format!("Found duplicated stdout paths: {duplicates:?}"))); + } + + let mut seen = HashSet::new(); + let duplicates = conf.processes.iter() + .filter(|p| p.1.stderr().is_some()) + .map(|p| p.1.stderr().as_ref().unwrap().path().to_owned()) + .filter(|path| !seen.insert(path.clone())) + .collect::>(); + + if !duplicates.is_empty() { + return Err(Box::::from(format!("Found duplicated stderr paths: {duplicates:?}"))); + } + Ok(conf) } From 85f7c4678c7942004b5e51190867d257ed41386b Mon Sep 17 00:00:00 2001 From: Arthur Bied-Charreton Date: Tue, 20 May 2025 16:58:43 +0000 Subject: [PATCH 16/17] Fix failing test --- src/run/daemon.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/run/daemon.rs b/src/run/daemon.rs index 77f2b55..62706f3 100644 --- a/src/run/daemon.rs +++ b/src/run/daemon.rs @@ -480,7 +480,7 @@ mod tests { let hc = hc .set_check(HealthCheckType::Uptime(UptimeHealthCheck { starttime: 2 })) .set_backoff(1) - .set_retries(1); + .set_retries(0); let mut proc = ProcessConfig::default(); let proc = proc .set_cmd("sh") From 42fb709b13fb8d3ce990cce0b568af3615f6de57 Mon Sep 17 00:00:00 2001 From: winstonallo Date: Tue, 20 May 2025 17:01:19 +0000 Subject: [PATCH 17/17] Format + spellcheck fixes --- config/exitcode.toml | 2 +- src/conf.rs | 16 ++++++++++++---- src/jsonrpc/short_process.rs | 2 +- src/run/daemon.rs | 23 +++++++++++------------ 4 files changed, 25 insertions(+), 18 deletions(-) diff --git a/config/exitcode.toml b/config/exitcode.toml index 4b27af7..ed0e48a 100644 --- a/config/exitcode.toml +++ b/config/exitcode.toml @@ -1,4 +1,4 @@ -[processes.succcess] +[processes.success] cmd = "/bin/bash" args = ["-c", "exit 0"] workingdir = "." diff --git a/src/conf.rs b/src/conf.rs index 4e6a973..b3ba3e7 100644 --- a/src/conf.rs +++ b/src/conf.rs @@ -1,8 +1,12 @@ -use std::{collections::{HashMap, HashSet}, error::Error, fs}; +use std::{ + collections::{HashMap, HashSet}, + error::Error, + fs, +}; use defaults::{dflt_authgroup, dflt_logfile, dflt_socketpath}; -use proc::{ProcessConfig}; +use proc::ProcessConfig; use serde::Deserialize; pub const PID_FILE_PATH: &str = "/tmp/taskmaster.pid"; @@ -90,7 +94,9 @@ impl Config { } let mut seen = HashSet::new(); - let duplicates = conf.processes.iter() + let duplicates = conf + .processes + .iter() .filter(|p| p.1.stdout().is_some()) .map(|p| p.1.stdout().as_ref().unwrap().path().to_owned()) .filter(|path| !seen.insert(path.clone())) @@ -101,7 +107,9 @@ impl Config { } let mut seen = HashSet::new(); - let duplicates = conf.processes.iter() + let duplicates = conf + .processes + .iter() .filter(|p| p.1.stderr().is_some()) .map(|p| p.1.stderr().as_ref().unwrap().path().to_owned()) .filter(|path| !seen.insert(path.clone())) diff --git a/src/jsonrpc/short_process.rs b/src/jsonrpc/short_process.rs index 1715fe8..f5c61ff 100644 --- a/src/jsonrpc/short_process.rs +++ b/src/jsonrpc/short_process.rs @@ -48,7 +48,7 @@ impl State { ProcessState::HealthCheck(instant) => Self::HealthCheck(instant.elapsed().as_secs()), ProcessState::Healthy => Self::Healthy, ProcessState::Failed(_) => Self::Failed, - ProcessState::WaitingForRetry(instant) => Self::WaitingForRetry(instant.duration_since(Instant::now()).as_secs()), + ProcessState::WaitingForRetry(instant) => Self::WaitingForRetry(instant.duration_since(Instant::now()).as_secs()), ProcessState::Completed => Self::Completed, ProcessState::Stopping(instant) => Self::Stopping(instant.elapsed().as_secs()), ProcessState::Stopped => Self::Stopped, diff --git a/src/run/daemon.rs b/src/run/daemon.rs index 62706f3..ce3f9d8 100644 --- a/src/run/daemon.rs +++ b/src/run/daemon.rs @@ -15,7 +15,7 @@ use super::statemachine::states::ProcessState; use crate::conf::{Config, PID_FILE_PATH}; use crate::jsonrpc::handlers::AttachmentManager; use crate::jsonrpc::response::{Response, ResponseError, ResponseType}; -use crate::{log_info}; +use crate::log_info; use crate::{ conf, jsonrpc::{handlers::handle_request, request::Request}, @@ -171,19 +171,18 @@ impl Daemon { process_old.push_desired_state(ProcessState::Stopped); *process_old.config_mut() = process_new.config().clone(); (*process_old) - .healthcheck_mut() - .set_healthcheck(process_new.healthcheck().check()) - .set_backoff(process_new.healthcheck().backoff()) - .set_retries(process_new.healthcheck().retries()); - - match process_old.config().autostart() { - false => process_old.push_desired_state(ProcessState::Idle), - true => process_old.push_desired_state(ProcessState::Healthy), + .healthcheck_mut() + .set_healthcheck(process_new.healthcheck().check()) + .set_backoff(process_new.healthcheck().backoff()) + .set_retries(process_new.healthcheck().retries()); + + match process_old.config().autostart() { + false => process_old.push_desired_state(ProcessState::Idle), + true => process_old.push_desired_state(ProcessState::Healthy), + } } - + leftover.retain(|n| n != process_old.name()); } - leftover.retain(|n| n != process_old.name()); - } None => { let _ = self.processes_mut().insert(process_name_new, process_new); }