#![deny(missing_docs)]
use std::path::PathBuf;
use std::process::ExitStatus;
use std::time::Duration;
use tokio::sync::mpsc;
pub mod clock;
pub mod error;
pub mod mode;
pub mod monitor;
pub mod spawner;
pub mod strict;
pub mod supervisor;
pub mod signals;
#[cfg(feature = "cli")]
pub mod cli;
#[cfg(feature = "cli")]
pub mod daemonizer;
#[cfg(feature = "cli")]
pub mod logging;
#[cfg(feature = "cli")]
pub mod pidfile;
pub use error::AutosshError;
#[non_exhaustive]
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
pub enum SignalKind {
Terminate,
Interrupt,
UserDefined1,
Hangup,
CtrlBreak,
}
#[non_exhaustive]
#[derive(Debug, Clone, Default, PartialEq, Eq, Hash)]
pub enum MonitorMode {
#[default]
None,
Active {
port: u16,
echo: Option<u16>,
},
}
#[non_exhaustive]
#[derive(Debug, Clone, Copy, Default, PartialEq, Eq, Hash)]
pub enum CompatibilityMode {
#[default]
Default,
Strict,
}
#[non_exhaustive]
#[derive(Debug)]
pub enum SupervisorEvent {
ChildSpawned {
pid: u32,
},
ChildExited {
status: ExitStatus,
},
ChildRespawned,
ProbeTimeout,
MaxStartReached {
attempts: u32,
},
MaxLifetimeReached,
SignalReceived(SignalKind),
}
#[derive(Debug, Default)]
pub struct SshSupervisorBuilder {
ssh_args: Vec<String>,
monitor_mode: MonitorMode,
ssh_path: Option<PathBuf>,
poll: Option<Duration>,
first_poll: Option<Duration>,
gate_time: Option<Duration>,
max_start: Option<Option<u32>>,
max_lifetime: Option<Option<Duration>>,
event_sender: Option<mpsc::Sender<SupervisorEvent>>,
message: Option<String>,
compatibility_mode: CompatibilityMode,
one_shot: bool,
pidfile_path: Option<PathBuf>,
logfile_path: Option<PathBuf>,
}
impl SshSupervisorBuilder {
pub fn new() -> Self {
Self::default()
}
pub fn ssh_args(mut self, args: Vec<String>) -> Self {
self.ssh_args = args;
self
}
pub fn monitor_mode(mut self, mode: MonitorMode) -> Self {
self.monitor_mode = mode;
self
}
pub fn ssh_path(mut self, path: PathBuf) -> Self {
self.ssh_path = Some(path);
self
}
pub fn poll(mut self, poll: Duration) -> Self {
self.poll = Some(poll);
self
}
pub fn first_poll(mut self, first_poll: Duration) -> Self {
self.first_poll = Some(first_poll);
self
}
pub fn gate_time(mut self, gate_time: Duration) -> Self {
self.gate_time = Some(gate_time);
self
}
pub fn max_start(mut self, max_start: Option<u32>) -> Self {
self.max_start = Some(max_start);
self
}
pub fn max_lifetime(mut self, max_lifetime: Option<Duration>) -> Self {
self.max_lifetime = Some(max_lifetime);
self
}
pub fn event_sender(mut self, tx: mpsc::Sender<SupervisorEvent>) -> Self {
self.event_sender = Some(tx);
self
}
pub fn message(mut self, message: String) -> Self {
self.message = Some(message);
self
}
pub fn compatibility_mode(mut self, mode: CompatibilityMode) -> Self {
self.compatibility_mode = mode;
self
}
pub fn one_shot(mut self, one_shot: bool) -> Self {
self.one_shot = one_shot;
self
}
pub fn pidfile_path(mut self, path: PathBuf) -> Self {
self.pidfile_path = Some(path);
self
}
pub fn logfile_path(mut self, path: PathBuf) -> Self {
self.logfile_path = Some(path);
self
}
pub fn build(self) -> Result<SshSupervisor, AutosshError> {
Ok(SshSupervisor {
ssh_args: self.ssh_args,
monitor_mode: self.monitor_mode,
ssh_path: self.ssh_path,
poll: self.poll.unwrap_or_else(|| Duration::from_secs(600)),
first_poll: self.first_poll,
gate_time: self.gate_time.unwrap_or_else(|| Duration::from_secs(30)),
max_start: self.max_start.unwrap_or(None),
max_lifetime: self.max_lifetime.unwrap_or(None),
event_sender: self.event_sender,
message: self.message,
compatibility_mode: self.compatibility_mode,
one_shot: self.one_shot,
pidfile_path: self.pidfile_path,
logfile_path: self.logfile_path,
})
}
}
#[derive(Debug)]
pub struct SshSupervisor {
ssh_args: Vec<String>,
monitor_mode: MonitorMode,
ssh_path: Option<PathBuf>,
poll: Duration,
first_poll: Option<Duration>,
gate_time: Duration,
max_start: Option<u32>,
max_lifetime: Option<Duration>,
event_sender: Option<mpsc::Sender<SupervisorEvent>>,
message: Option<String>,
compatibility_mode: CompatibilityMode,
one_shot: bool,
#[allow(dead_code)]
pidfile_path: Option<PathBuf>,
#[allow(dead_code)]
logfile_path: Option<PathBuf>,
}
impl SshSupervisor {
pub async fn run(&mut self) -> Result<(), AutosshError> {
use std::time::Instant;
let ssh_path = match &self.ssh_path {
Some(p) => p.clone(),
None => {
let autossh_path = std::env::var_os("AUTOSSH_PATH");
let path = std::env::var_os("PATH");
spawner::resolve_ssh_path(autossh_path.as_deref(), path.as_deref())?
}
};
let monitor = match &self.monitor_mode {
MonitorMode::None => None,
MonitorMode::Active { .. } => Some(monitor::ProbeLoop::bind(
&self.monitor_mode,
self.message.as_deref(),
)?),
};
#[cfg(feature = "cli")]
let pidfile_guard: Option<pidfile::PidfileGuard> = match &self.pidfile_path {
Some(p) => Some(pidfile::write_pid(p.clone(), std::process::id())?),
None => None,
};
#[cfg(feature = "cli")]
let _log_guard: Option<tracing_appender::non_blocking::WorkerGuard> =
match &self.logfile_path {
Some(p) => logging::init_logfile(Some(p.clone()), self.compatibility_mode)?,
None => None,
};
let monitor_mode = match (&self.monitor_mode, &monitor) {
(MonitorMode::Active { echo: Some(e), .. }, Some(m)) => MonitorMode::Active {
port: m.ports.port_in,
echo: Some(*e),
},
(MonitorMode::Active { echo: None, .. }, Some(m)) => MonitorMode::Active {
port: m.ports.port_in,
echo: None,
},
_ => self.monitor_mode.clone(),
};
let clock = PollClock {
poll: self.poll,
first_poll: self.first_poll.unwrap_or(self.poll),
gate_time: self.gate_time,
max_start: self.max_start,
max_lifetime: self.max_lifetime,
};
let signal_rx = Some(signals::spawn_signal_source());
let mut supervisor = supervisor::Supervisor {
child: None,
monitor,
clock,
mode: self.compatibility_mode,
monitor_mode,
ssh_path,
ssh_args: self.ssh_args.clone(),
retry_count: 0,
lifetime_start: Instant::now(),
child_spawn_instant: None,
event_tx: self.event_sender.clone(),
signal_rx,
one_shot: self.one_shot,
#[cfg(feature = "cli")]
pidfile_guard,
};
supervisor.run().await
}
}
use crate::clock::PollClock;
#[cfg(test)]
mod tests {
use super::*;
use static_assertions::assert_impl_all;
assert_impl_all!(SshSupervisorBuilder: Send, Sync);
assert_impl_all!(SshSupervisor: Send);
assert_impl_all!(MonitorMode: Send, Sync, Clone);
assert_impl_all!(SupervisorEvent: Send, Sync);
assert_impl_all!(AutosshError: Send, Sync);
assert_impl_all!(CompatibilityMode: Send, Sync, Clone, Copy);
fn _autossh_error_is_static() {
fn assert_static<T: 'static>() {}
assert_static::<AutosshError>();
}
}