use crate::Result;
use crate::daemon::Daemon;
use crate::daemon_id::DaemonId;
use crate::env;
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,
}
#[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 record = existing_supervisor()?;
let existing_pid = record.as_ref().and_then(|d| d.pid);
let outcome = if let Some(record) = &record {
kill_or_stop(record, force).await?
} else {
KillOrStopOutcome::AlreadyDead
};
Ok((existing_pid, outcome))
}