use crate::Result;
use crate::daemon::Daemon;
use crate::daemon_id::DaemonId;
use crate::env;
use crate::ipc::client::IpcClient;
use crate::pitchfork_toml::StopSignal;
use crate::procs::PROCS;
use crate::state_file::StateFile;
use crate::supervisor::supervisor_record_is_live;
mod run;
mod start;
mod status;
mod stop;
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum KillOrStopOutcome {
Killed,
AlreadyDead,
StillRunning,
Unidentified,
}
pub fn unidentified_supervisor_error() -> miette::Report {
miette::miette!(
"a pitchfork supervisor is listening on {}, but the state file does not record its pid, \
so it cannot be stopped or replaced automatically. Stop its `pitchfork supervisor run` \
process manually.",
crate::ipc::socket_display()
)
}
#[derive(Debug, usage_rs::Args)]
#[usage(verbatim_doc_comment)]
pub struct Supervisor {
#[usage(subcommand)]
command: Commands,
}
#[derive(Debug, usage_rs::Subcommands)]
enum Commands {
Run(run::Run),
Start(start::Start),
Status(status::Status),
Stop(stop::Stop),
}
impl Supervisor {
pub async fn run(self) -> Result<()> {
match self.command {
Commands::Run(run) => run.run().await,
Commands::Start(start) => start.run().await,
Commands::Status(status) => status.run().await,
Commands::Stop(stop) => stop.run().await,
}
}
}
pub async fn kill_or_stop(record: &Daemon, force: bool) -> Result<KillOrStopOutcome> {
let Some(existing_pid) = record.pid else {
return Ok(KillOrStopOutcome::AlreadyDead);
};
if !supervisor_record_is_live(record) {
return Ok(KillOrStopOutcome::AlreadyDead);
}
if !force {
return Ok(KillOrStopOutcome::StillRunning);
}
debug!("killing pid {existing_pid}");
let stop_signal: i32 = StopSignal::default().into();
let Some(expected_start_time) = record.start_time.or_else(|| PROCS.start_time(existing_pid))
else {
return Err(miette::miette!(
"cannot verify the identity of supervisor pid {existing_pid}: its start time is unreadable; not signalling it. Try rerun with sudo."
));
};
let killed = PROCS
.kill_if_start_time_matches_async(
existing_pid,
Some(expected_start_time),
stop_signal,
None,
)
.await;
match killed {
Ok(true) => Ok(KillOrStopOutcome::Killed),
Ok(false) => Ok(KillOrStopOutcome::AlreadyDead),
Err(e) => Err(miette::miette!("{e}. Try rerun with sudo.")),
}
}
pub fn existing_supervisor() -> Result<Option<Daemon>> {
let sf = StateFile::read(&*env::PITCHFORK_STATE_FILE)?;
Ok(sf.daemons.get(&DaemonId::pitchfork()).cloned())
}
pub async fn resolve_existing_supervisor(force: bool) -> Result<(Option<u32>, KillOrStopOutcome)> {
let mut record = existing_supervisor()?;
if !record.as_ref().is_some_and(supervisor_record_is_live) {
if !crate::ipc::supervisor_listening().await {
let existing_pid = record.and_then(|d| d.pid);
return Ok((existing_pid, KillOrStopOutcome::AlreadyDead));
}
debug!("supervisor is listening on the IPC socket but not recorded; asking it to restore");
if let Err(err) = IpcClient::connect(false).await {
debug!("failed to connect to the unrecorded supervisor: {err:?}");
}
record = existing_supervisor()?;
if !record.as_ref().is_some_and(supervisor_record_is_live) {
return Ok((None, KillOrStopOutcome::Unidentified));
}
}
let record = record.expect("a live record was found above");
let outcome = kill_or_stop(&record, force).await?;
Ok((record.pid, outcome))
}