diff --git a/Cargo.lock b/Cargo.lock index 46f6d3c..6ae80b3 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1266,6 +1266,7 @@ dependencies = [ "rmp-serde", "serde", "serde_json", + "shell-words", "strum", "sysinfo", "tokio", @@ -1571,6 +1572,12 @@ dependencies = [ "windows-sys 0.59.0", ] +[[package]] +name = "shell-words" +version = "1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "24188a676b6ae68c3b2cb3a01be17fbf7240ce009799bb56d5b1409051e78fde" + [[package]] name = "shlex" version = "1.3.0" diff --git a/Cargo.toml b/Cargo.toml index a2426ed..e636724 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -36,6 +36,7 @@ rev_lines = "0.3" rmp-serde = "1" serde = { version = "1", features = ["derive"] } serde_json = { version = "1", features = ["indexmap"] } +shell-words = "1.1.0" strum = { version = "0.26", features = ["derive"] } sysinfo = "0.33" tokio = { version = "1", features = ["full"] } diff --git a/docs/cli/commands.json b/docs/cli/commands.json index 6d20525..b516896 100644 --- a/docs/cli/commands.json +++ b/docs/cli/commands.json @@ -93,13 +93,56 @@ "config", "add" ], - "usage": "config add", + "usage": "config add [--autostart] [--autostop] [ARGS]...", "subcommands": {}, - "args": [], - "flags": [], + "args": [ + { + "name": "ID", + "usage": "", + "help": "ID of the daemon to add", + "help_first_line": "ID of the daemon to add", + "required": true, + "hide": false + }, + { + "name": "ARGS", + "usage": "[ARGS]...", + "help": "Arguments to pass to the daemon", + "help_first_line": "Arguments to pass to the daemon", + "required": false, + "var": true, + "hide": false + } + ], + "flags": [ + { + "name": "autostart", + "usage": "--autostart", + "help": "Autostart the daemon when entering the directory", + "help_first_line": "Autostart the daemon when entering the directory", + "short": [], + "long": [ + "autostart" + ], + "hide": false, + "global": false + }, + { + "name": "autostop", + "usage": "--autostop", + "help": "Autostop the daemon when leaving the directory", + "help_first_line": "Autostop the daemon when leaving the directory", + "short": [], + "long": [ + "autostop" + ], + "hide": false, + "global": false + } + ], "mounts": [], "hide": false, - "help": "Add a new daemon to pitchfork.toml", + "help": "Add a new daemon to ./pitchfork.toml", "name": "add", "aliases": [ "a" @@ -112,9 +155,18 @@ "config", "remove" ], - "usage": "config remove", + "usage": "config remove ", "subcommands": {}, - "args": [], + "args": [ + { + "name": "ID", + "usage": "", + "help": "The ID of the daemon to remove", + "help_first_line": "The ID of the daemon to remove", + "required": true, + "hide": false + } + ], "flags": [], "mounts": [], "hide": false, @@ -316,7 +368,7 @@ "full_cmd": [ "run" ], - "usage": "run [-f --force] [CMD]...", + "usage": "run [-f --force] [RUN]...", "subcommands": {}, "args": [ { @@ -328,8 +380,8 @@ "hide": false }, { - "name": "CMD", - "usage": "[CMD]...", + "name": "RUN", + "usage": "[RUN]...", "required": false, "var": true, "hide": false @@ -376,7 +428,24 @@ "hide": false } ], - "flags": [], + "flags": [ + { + "name": "shell-pid", + "usage": "--shell-pid ", + "short": [], + "long": [ + "shell-pid" + ], + "hide": true, + "global": false, + "arg": { + "name": "SHELL_PID", + "usage": "", + "required": true, + "hide": false + } + } + ], "mounts": [], "hide": false, "help": "Starts a daemon from a pitchfork.toml file", diff --git a/docs/cli/config.md b/docs/cli/config.md index 1fa807f..c4fc082 100644 --- a/docs/cli/config.md +++ b/docs/cli/config.md @@ -9,5 +9,5 @@ without a subcommand, lists all pitchfork.toml files from the current directory ## Subcommands -- [`pitchfork config add`](/cli/config/add.md) -- [`pitchfork config remove`](/cli/config/remove.md) +- [`pitchfork config add [--autostart] [--autostop] [ARGS]...`](/cli/config/add.md) +- [`pitchfork config remove `](/cli/config/remove.md) diff --git a/docs/cli/config/add.md b/docs/cli/config/add.md index 1e6e516..b36621a 100644 --- a/docs/cli/config/add.md +++ b/docs/cli/config/add.md @@ -1,6 +1,26 @@ # `pitchfork config add` -- **Usage**: `pitchfork config add` +- **Usage**: `pitchfork config add [--autostart] [--autostop] [ARGS]...` - **Aliases**: `a` -Add a new daemon to pitchfork.toml +Add a new daemon to ./pitchfork.toml + +## Arguments + +### `` + +ID of the daemon to add + +### `[ARGS]...` + +Arguments to pass to the daemon + +## Flags + +### `--autostart` + +Autostart the daemon when entering the directory + +### `--autostop` + +Autostop the daemon when leaving the directory diff --git a/docs/cli/config/remove.md b/docs/cli/config/remove.md index 91ca82f..bbc16f8 100644 --- a/docs/cli/config/remove.md +++ b/docs/cli/config/remove.md @@ -1,6 +1,12 @@ # `pitchfork config remove` -- **Usage**: `pitchfork config remove` +- **Usage**: `pitchfork config remove ` - **Aliases**: `rm` Remove a daemon from pitchfork.toml + +## Arguments + +### `` + +The ID of the daemon to remove diff --git a/docs/cli/index.md b/docs/cli/index.md index 10db09a..f819377 100644 --- a/docs/cli/index.md +++ b/docs/cli/index.md @@ -9,14 +9,14 @@ - [`pitchfork activate `](/cli/activate.md) - [`pitchfork clean`](/cli/clean.md) - [`pitchfork config `](/cli/config.md) -- [`pitchfork config add`](/cli/config/add.md) -- [`pitchfork config remove`](/cli/config/remove.md) +- [`pitchfork config add [--autostart] [--autostop] [ARGS]...`](/cli/config/add.md) +- [`pitchfork config remove `](/cli/config/remove.md) - [`pitchfork completion `](/cli/completion.md) - [`pitchfork disable `](/cli/disable.md) - [`pitchfork enable `](/cli/enable.md) - [`pitchfork list [--hide-header]`](/cli/list.md) - [`pitchfork logs [-n ] [-t --tail] [ID]...`](/cli/logs.md) -- [`pitchfork run [-f --force] [CMD]...`](/cli/run.md) +- [`pitchfork run [-f --force] [RUN]...`](/cli/run.md) - [`pitchfork start [ID]...`](/cli/start.md) - [`pitchfork status `](/cli/status.md) - [`pitchfork stop `](/cli/stop.md) diff --git a/docs/cli/run.md b/docs/cli/run.md index 6db4f01..3c1d7d2 100644 --- a/docs/cli/run.md +++ b/docs/cli/run.md @@ -1,6 +1,6 @@ # `pitchfork run` -- **Usage**: `pitchfork run [-f --force] [CMD]...` +- **Usage**: `pitchfork run [-f --force] [RUN]...` - **Aliases**: `r` Runs a one-off daemon @@ -11,7 +11,7 @@ Runs a one-off daemon Name of the daemon to run -### `[CMD]...` +### `[RUN]...` Runs a one-off daemon diff --git a/pitchfork.usage.kdl b/pitchfork.usage.kdl index 3cddd98..02dafa3 100644 --- a/pitchfork.usage.kdl +++ b/pitchfork.usage.kdl @@ -20,11 +20,16 @@ cmd "config" help="manage/edit pitchfork.toml files" { long_help r"manage/edit pitchfork.toml files without a subcommand, lists all pitchfork.toml files from the current directory" - cmd "add" help="Add a new daemon to pitchfork.toml" { + cmd "add" help="Add a new daemon to ./pitchfork.toml" { alias "a" + flag "--autostart" help="Autostart the daemon when entering the directory" + flag "--autostop" help="Autostop the daemon when leaving the directory" + arg "" help="ID of the daemon to add" + arg "[ARGS]..." help="Arguments to pass to the daemon" var=true } cmd "remove" help="Remove a daemon from pitchfork.toml" { alias "rm" + arg "" help="The ID of the daemon to remove" } } cmd "completion" help="Generates shell completion scripts" { @@ -55,10 +60,13 @@ cmd "run" help="Runs a one-off daemon" { alias "r" flag "-f --force" arg "" help="Name of the daemon to run" - arg "[CMD]..." var=true + arg "[RUN]..." var=true } cmd "start" help="Starts a daemon from a pitchfork.toml file" { alias "s" + flag "--shell-pid" hide=true { + arg "" + } arg "[ID]..." help="ID of the daemon(s) in pitchfork.toml to start" var=true } cmd "status" help="Display the status of a daemon" { diff --git a/src/cli/activate.rs b/src/cli/activate.rs index 8f332ea..7e9d570 100644 --- a/src/cli/activate.rs +++ b/src/cli/activate.rs @@ -13,7 +13,7 @@ pub struct Activate { impl Activate { pub async fn run(&self) -> Result<()> { - let pitchfork = env::BIN_PATH.to_string_lossy().to_string(); + let pitchfork = env::PITCHFORK_BIN.to_string_lossy().to_string(); let s = match self.shell.as_str() { "bash" => format!( r#" diff --git a/src/cli/cd.rs b/src/cli/cd.rs index a795546..0f77e40 100644 --- a/src/cli/cd.rs +++ b/src/cli/cd.rs @@ -1,4 +1,8 @@ -use crate::Result; +use crate::pitchfork_toml::{PitchforkToml, PitchforkTomlAuto}; +use crate::{env, Result}; +use duct::cmd; +use itertools::Itertools; +use miette::IntoDiagnostic; #[derive(Debug, clap::Args)] #[clap(hide = true, verbatim_doc_comment)] @@ -9,7 +13,25 @@ pub struct Cd { impl Cd { pub async fn run(&self) -> Result<()> { - dbg!(self); + let pt = PitchforkToml::all_merged(); + let to_start = pt + .daemons + .into_iter() + .filter(|(_id, d)| d.auto.contains(&PitchforkTomlAuto::Start)) + .map(|(id, _d)| id) + .collect_vec(); + if to_start.is_empty() { + return Ok(()); + } + let mut args = vec![ + "start".into(), + "--shell-pid".into(), + self.shell_pid.to_string(), + ]; + for id in to_start { + args.push(id); + } + cmd(&*env::PITCHFORK_BIN, args).run().into_diagnostic()?; Ok(()) } } diff --git a/src/cli/config/add.rs b/src/cli/config/add.rs index 8f055f0..20baf97 100644 --- a/src/cli/config/add.rs +++ b/src/cli/config/add.rs @@ -1,12 +1,43 @@ +use crate::pitchfork_toml::{PitchforkToml, PitchforkTomlAuto, PitchforkTomlDaemon}; use crate::Result; -/// Add a new daemon to pitchfork.toml +/// Add a new daemon to ./pitchfork.toml #[derive(Debug, clap::Args)] #[clap(visible_alias = "a", verbatim_doc_comment)] -pub struct Add {} +pub struct Add { + /// ID of the daemon to add + pub id: String, + /// Arguments to pass to the daemon + #[clap(allow_hyphen_values = true, trailing_var_arg = true)] + pub args: Vec, + /// Autostart the daemon when entering the directory + #[clap(long)] + pub autostart: bool, + /// Autostop the daemon when leaving the directory + #[clap(long)] + pub autostop: bool, +} impl Add { pub async fn run(&self) -> Result<()> { + let mut pt = PitchforkToml::read("pitchfork.toml").unwrap_or_default(); + pt.path = pt.path.or(Some("pitchfork.toml".into())); + let mut auto = vec![]; + if self.autostart { + auto.push(PitchforkTomlAuto::Start); + } + if self.autostop { + auto.push(PitchforkTomlAuto::Stop); + } + pt.daemons.insert( + self.id.clone(), + PitchforkTomlDaemon { + run: shell_words::join(&self.args), + auto, + }, + ); + pt.write()?; + println!("added {} to {}", self.id, pt.path.unwrap().display()); Ok(()) } } diff --git a/src/cli/config/remove.rs b/src/cli/config/remove.rs index 18d8078..acc7248 100644 --- a/src/cli/config/remove.rs +++ b/src/cli/config/remove.rs @@ -1,12 +1,27 @@ +use crate::pitchfork_toml::PitchforkToml; use crate::Result; /// Remove a daemon from pitchfork.toml #[derive(Debug, clap::Args)] #[clap(visible_alias = "rm", verbatim_doc_comment)] -pub struct Remove {} +pub struct Remove { + /// The ID of the daemon to remove + id: String, +} impl Remove { pub async fn run(&self) -> Result<()> { + if let Some(path) = PitchforkToml::list_paths().first() { + let mut pt = PitchforkToml::read(path)?; + if pt.daemons.shift_remove(&self.id).is_some() { + pt.write()?; + println!("removed {} from {}", self.id, path.display()); + } else { + warn!("{} not found in {}", self.id, path.display()); + } + } else { + warn!("No pitchfork.toml files found"); + } Ok(()) } } diff --git a/src/cli/disable.rs b/src/cli/disable.rs index 03ab24c..3c60e50 100644 --- a/src/cli/disable.rs +++ b/src/cli/disable.rs @@ -1,6 +1,6 @@ -use crate::state_file::StateFile; +use crate::ipc::client::IpcClient; +use crate::ipc::{IpcRequest, IpcResponse}; use crate::Result; -use miette::bail; /// Prevent a daemon from restarting #[derive(Debug, clap::Args)] @@ -12,14 +12,20 @@ pub struct Disable { impl Disable { pub async fn run(&self) -> Result<()> { - let mut sf = StateFile::get().clone(); - if self.id == "pitchfork" { - bail!("Cannot disable pitchfork daemon"); - } - let disabled = sf.disabled.insert(self.id.clone()); - if disabled { - sf.write()?; - println!("disabled {}", self.id); + let ipc = IpcClient::connect().await?; + let rsp = ipc + .request(IpcRequest::Disable { + id: self.id.clone(), + }) + .await?; + match rsp { + IpcResponse::Yes => { + info!("disabled daemon {}", self.id); + } + IpcResponse::No => { + info!("daemon {} already disabled", self.id); + } + rsp => unreachable!("unexpected response: {rsp:?}"), } Ok(()) } diff --git a/src/cli/enable.rs b/src/cli/enable.rs index 13899f8..8ac8e75 100644 --- a/src/cli/enable.rs +++ b/src/cli/enable.rs @@ -1,6 +1,6 @@ -use crate::state_file::StateFile; +use crate::ipc::client::IpcClient; +use crate::ipc::{IpcRequest, IpcResponse}; use crate::Result; -use miette::bail; /// Allow a daemon to start #[derive(Debug, clap::Args)] @@ -12,14 +12,20 @@ pub struct Enable { impl Enable { pub async fn run(&self) -> Result<()> { - let mut sf = StateFile::get().clone(); - if self.id == "pitchfork" { - bail!("Cannot disable pitchfork daemon"); - } - let enabled = sf.disabled.remove(&self.id); - if enabled { - sf.write()?; - println!("enabled {}", self.id); + let ipc = IpcClient::connect().await?; + let rsp = ipc + .request(IpcRequest::Enable { + id: self.id.clone(), + }) + .await?; + match rsp { + IpcResponse::Yes => { + info!("enabled daemon {}", self.id); + } + IpcResponse::No => { + info!("daemon {} already enabled", self.id); + } + rsp => unreachable!("unexpected response: {rsp:?}"), } Ok(()) } diff --git a/src/cli/run.rs b/src/cli/run.rs index 9520754..b7a0364 100644 --- a/src/cli/run.rs +++ b/src/cli/run.rs @@ -1,4 +1,3 @@ -use crate::cli::supervisor; use crate::ipc::client::IpcClient; use crate::ipc::{IpcRequest, IpcResponse}; use crate::Result; @@ -11,16 +10,15 @@ pub struct Run { /// Name of the daemon to run id: String, #[clap(allow_hyphen_values = true, trailing_var_arg = true)] - cmd: Vec, + run: Vec, #[clap(short, long)] force: bool, } impl Run { pub async fn run(&self) -> Result<()> { - supervisor::start_if_not_running()?; info!("Running one-off daemon"); - if self.cmd.is_empty() { + if self.run.is_empty() { bail!("No command provided"); } @@ -46,7 +44,7 @@ impl Run { let rsp = ipc .request(IpcRequest::Run { id: self.id.clone(), - cmd: self.cmd.clone(), + cmd: self.run.clone(), }) .await?; match rsp { diff --git a/src/cli/start.rs b/src/cli/start.rs index cc91505..dc563aa 100644 --- a/src/cli/start.rs +++ b/src/cli/start.rs @@ -1,6 +1,8 @@ -use crate::cli::supervisor; +use crate::ipc::client::IpcClient; +use crate::ipc::IpcRequest; use crate::pitchfork_toml::PitchforkToml; use crate::Result; +use miette::IntoDiagnostic; /// Starts a daemon from a pitchfork.toml file #[derive(Debug, clap::Args)] @@ -8,15 +10,26 @@ use crate::Result; pub struct Start { /// ID of the daemon(s) in pitchfork.toml to start id: Vec, + #[clap(long, hide = true)] + shell_pid: Option, } impl Start { pub async fn run(&self) -> Result<()> { - supervisor::start_if_not_running()?; - // TODO: read all tomls - let pt = PitchforkToml::read("pitchfork.toml")?; - dbg!(&pt); - pt.write()?; + let pt = PitchforkToml::all_merged(); + let ipc = IpcClient::connect().await?; + for id in &self.id { + let daemon = pt.daemons.get(id); + if let Some(daemon) = daemon { + ipc.request(IpcRequest::Run { + id: id.clone(), + cmd: shell_words::split(&daemon.run.clone()).into_diagnostic()?, + }) + .await?; + } else { + warn!("Daemon {} not found", id); + } + } Ok(()) } } diff --git a/src/cli/supervisor/mod.rs b/src/cli/supervisor/mod.rs index e75424c..15de312 100644 --- a/src/cli/supervisor/mod.rs +++ b/src/cli/supervisor/mod.rs @@ -1,8 +1,5 @@ use crate::procs::PROCS; -use crate::state_file::StateFile; -use crate::{env, Result}; -use duct::cmd; -use miette::IntoDiagnostic; +use crate::Result; mod run; mod start; @@ -51,24 +48,3 @@ pub async fn kill_or_stop(existing_pid: u32, force: bool) -> Result { Ok(true) } } - -pub fn start() -> Result<()> { - cmd!(&*env::BIN_PATH, "supervisor", "run") - .stdout_null() - .stderr_null() - .start() - .into_diagnostic()?; - Ok(()) -} - -pub fn start_if_not_running() -> Result<()> { - let sf = StateFile::get(); - if let Some(d) = sf.daemons.get("pitchfork") { - if let Some(pid) = d.pid { - if PROCS.is_running(pid) { - return Ok(()); - } - } - } - start() -} diff --git a/src/cli/supervisor/start.rs b/src/cli/supervisor/start.rs index 12a90b3..50894a2 100644 --- a/src/cli/supervisor/start.rs +++ b/src/cli/supervisor/start.rs @@ -1,6 +1,4 @@ -use crate::cli::supervisor::{kill_or_stop, start}; use crate::ipc::client::IpcClient; -use crate::state_file::StateFile; use crate::Result; /// Starts the internal pitchfork daemon in the background @@ -14,20 +12,6 @@ pub struct Start { impl Start { pub async fn run(&self) -> Result<()> { - let sf = StateFile::get(); - let mut running = false; - if let Some(d) = sf.daemons.get("pitchfork") { - if let Some(pid) = d.pid { - if !(kill_or_stop(pid, self.force).await?) { - running = true; - } - } - } - - if !running { - start()?; - } - IpcClient::connect().await?; println!("Pitchfork daemon is running"); diff --git a/src/env.rs b/src/env.rs index 473778d..2879ab0 100644 --- a/src/env.rs +++ b/src/env.rs @@ -2,7 +2,8 @@ use once_cell::sync::Lazy; pub use std::env::*; use std::path::PathBuf; -pub static BIN_PATH: Lazy = Lazy::new(|| current_exe().unwrap().canonicalize().unwrap()); +pub static PITCHFORK_BIN: Lazy = + Lazy::new(|| current_exe().unwrap().canonicalize().unwrap()); pub static CWD: Lazy = Lazy::new(|| current_dir().unwrap_or_default()); pub static HOME_DIR: Lazy = Lazy::new(|| dirs::home_dir().unwrap_or_default()); diff --git a/src/ipc/client.rs b/src/ipc/client.rs index 08452a8..d5d1670 100644 --- a/src/ipc/client.rs +++ b/src/ipc/client.rs @@ -1,5 +1,5 @@ use crate::ipc::{deserialize, fs_name, serialize, IpcRequest, IpcResponse}; -use crate::Result; +use crate::{supervisor, Result}; use exponential_backoff::Backoff; use interprocess::local_socket::tokio::{RecvHalf, SendHalf}; use interprocess::local_socket::traits::tokio::Stream; @@ -21,6 +21,7 @@ const CONNECT_MAX_DELAY: Duration = Duration::from_secs(1); impl IpcClient { pub async fn connect() -> Result { + supervisor::start_if_not_running()?; let id = Uuid::new_v4().to_string(); let client = Self::connect_(&id, "main").await?; trace!("Connected to IPC socket"); diff --git a/src/ipc/mod.rs b/src/ipc/mod.rs index 5a5c508..0dc2577 100644 --- a/src/ipc/mod.rs +++ b/src/ipc/mod.rs @@ -26,11 +26,15 @@ pub enum IpcRequest { Connect, Stop { id: String }, Run { id: String, cmd: Vec }, + Enable { id: String }, + Disable { id: String }, } #[derive(Debug, Clone, serde::Serialize, serde::Deserialize, strum::Display, strum::EnumIs)] pub enum IpcResponse { Ok, + Yes, + No, Error(String), DaemonAlreadyStopped, DaemonAlreadyRunning, diff --git a/src/pitchfork_toml.rs b/src/pitchfork_toml.rs index eda3279..3e501d6 100644 --- a/src/pitchfork_toml.rs +++ b/src/pitchfork_toml.rs @@ -1,25 +1,36 @@ use crate::{env, Result}; use indexmap::IndexMap; -use miette::IntoDiagnostic; +use miette::{bail, IntoDiagnostic}; use std::path::{Path, PathBuf}; -#[derive(Debug, serde::Serialize, serde::Deserialize)] +#[derive(Debug, Default, serde::Serialize, serde::Deserialize)] pub struct PitchforkToml { pub daemons: IndexMap, #[serde(skip)] - pub path: PathBuf, + pub path: Option, } impl PitchforkToml { pub fn list_paths() -> Vec { xx::file::find_up_all(&env::CWD, &["pitchfork.toml"]) } + + pub fn all_merged() -> PitchforkToml { + let mut pt = Self::default(); + for p in Self::list_paths() { + match Self::read(&p) { + Ok(pt2) => pt.merge(pt2), + Err(e) => eprintln!("error reading {}: {}", p.display(), e), + } + } + pt + } } impl PitchforkToml { pub fn new(path: PathBuf) -> Self { Self { daemons: Default::default(), - path, + path: Some(path), } } @@ -31,19 +42,38 @@ impl PitchforkToml { let _lock = xx::fslock::get(path, false)?; let raw = xx::file::read_to_string(path)?; let mut pt: Self = toml::from_str(&raw).into_diagnostic()?; - pt.path = path.to_path_buf(); + pt.path = Some(path.to_path_buf()); Ok(pt) } pub fn write(&self) -> Result<()> { - let _lock = xx::fslock::get(&self.path, false)?; - let raw = toml::to_string(self).into_diagnostic()?; - xx::file::write(&self.path, raw)?; - Ok(()) + if let Some(path) = &self.path { + let _lock = xx::fslock::get(path, false)?; + let raw = toml::to_string(self).into_diagnostic()?; + xx::file::write(path, raw)?; + Ok(()) + } else { + bail!("no path to write to"); + } + } + + fn merge(&mut self, pt: Self) { + for (id, d) in pt.daemons { + self.daemons.insert(id, d); + } } } #[derive(Debug, serde::Serialize, serde::Deserialize)] pub struct PitchforkTomlDaemon { pub run: String, + pub auto: Vec, +} + +#[derive(Debug, Clone, Copy, serde::Serialize, serde::Deserialize)] +#[serde(rename_all = "snake_case")] +#[derive(PartialEq)] +pub enum PitchforkTomlAuto { + Start, + Stop, } diff --git a/src/supervisor.rs b/src/supervisor.rs index ae68484..b144a54 100644 --- a/src/supervisor.rs +++ b/src/supervisor.rs @@ -6,6 +6,7 @@ use crate::procs::PROCS; use crate::state_file::StateFile; use crate::watch_files::WatchFiles; use crate::{env, Result}; +use duct::cmd; use itertools::Itertools; use miette::IntoDiagnostic; use notify::RecursiveMode; @@ -33,6 +34,27 @@ const INTERVAL: Duration = Duration::from_secs(10); pub static SUPERVISOR: Lazy = Lazy::new(|| Supervisor::new().expect("Error creating supervisor")); +pub fn start_if_not_running() -> Result<()> { + let sf = StateFile::get(); + if let Some(d) = sf.daemons.get("pitchfork") { + if let Some(pid) = d.pid { + if PROCS.is_running(pid) { + return Ok(()); + } + } + } + start_in_background() +} + +pub fn start_in_background() -> Result<()> { + cmd!(&*env::PITCHFORK_BIN, "supervisor", "run") + .stdout_null() + .stderr_null() + .start() + .into_diagnostic()?; + Ok(()) +} + impl Supervisor { pub fn new() -> Result { Ok(Self { @@ -64,7 +86,6 @@ impl Supervisor { } async fn run(&self, id: &str, cmd: Vec) -> Result { - info!("received run message: {id:?} cmd: {cmd:?}"); let daemon = self.get_daemon(id).await; if let Some(daemon) = daemon { if let Some(pid) = daemon.pid { @@ -286,9 +307,20 @@ impl Supervisor { IpcResponse::Ok } IpcRequest::Stop { id } => self.stop(&id).await?, - IpcRequest::Run { id, cmd } => { - self.run(&id, cmd).await?; - IpcResponse::Ok + IpcRequest::Run { id, cmd } => self.run(&id, cmd).await?, + IpcRequest::Enable { id } => { + if self.enable(id).await? { + IpcResponse::Yes + } else { + IpcResponse::No + } + } + IpcRequest::Disable { id } => { + if self.disable(id).await? { + IpcResponse::Yes + } else { + IpcResponse::No + } } }; Ok(rsp) @@ -334,23 +366,36 @@ impl Supervisor { status: DaemonStatus, ) -> Result { info!("upserting daemon: {id} pid: {pid:?} status: {status}"); + let mut state_file = self.state_file.lock().await; let daemon = Daemon { id: id.to_string(), title: pid.and_then(|pid| PROCS.title(pid)), pid, status, }; - self.state_file - .lock() - .await - .daemons - .insert(id.to_string(), daemon.clone()); - if let Err(err) = self.state_file.lock().await.write() { + state_file.daemons.insert(id.to_string(), daemon.clone()); + if let Err(err) = state_file.write() { warn!("failed to update state file: {err:#}"); } Ok(daemon) } + async fn enable(&self, id: String) -> Result { + info!("enabling daemon: {id}"); + let mut state_file = self.state_file.lock().await; + let result = state_file.disabled.remove(&id); + state_file.write()?; + Ok(result) + } + + async fn disable(&self, id: String) -> Result { + info!("disabling daemon: {id}"); + let mut state_file = self.state_file.lock().await; + let result = state_file.disabled.insert(id); + state_file.write()?; + Ok(result) + } + async fn get_daemon(&self, id: &str) -> Option { self.state_file.lock().await.daemons.get(id).cloned() }