use crate::{Result, env};
#[cfg(target_os = "linux")]
use auto_launcher::LinuxLaunchMode;
#[cfg(target_os = "macos")]
use auto_launcher::MacOSLaunchMode;
use auto_launcher::{AutoLaunch, AutoLaunchBuilder};
use miette::IntoDiagnostic;
#[cfg(any(target_os = "macos", target_os = "linux"))]
fn build_launcher(
app_path: &str,
#[cfg(target_os = "macos")] macos_mode: MacOSLaunchMode,
#[cfg(target_os = "linux")] linux_mode: LinuxLaunchMode,
) -> Result<AutoLaunch> {
let mut builder = AutoLaunchBuilder::new();
builder
.set_app_name("pitchfork")
.set_app_path(app_path)
.set_args(&["supervisor", "run", "--boot"]);
#[cfg(target_os = "macos")]
builder.set_macos_launch_mode(macos_mode);
#[cfg(target_os = "linux")]
builder.set_linux_launch_mode(linux_mode);
builder.build().into_diagnostic()
}
pub struct BootManager {
current: AutoLaunch,
other: AutoLaunch,
#[cfg(target_os = "macos")]
legacy: AutoLaunch,
}
impl BootManager {
pub fn new() -> Result<Self> {
let app_path = env::PITCHFORK_BIN.to_string_lossy().to_string();
#[cfg(target_os = "macos")]
let (current, other, legacy) = {
let is_root = nix::unistd::Uid::effective().is_root();
let (current_mode, other_mode) = if is_root {
(
MacOSLaunchMode::LaunchDaemonSystem,
MacOSLaunchMode::LaunchAgentUser,
)
} else {
(
MacOSLaunchMode::LaunchAgentUser,
MacOSLaunchMode::LaunchDaemonSystem,
)
};
(
build_launcher(&app_path, current_mode)?,
build_launcher(&app_path, other_mode)?,
build_launcher(&app_path, MacOSLaunchMode::LaunchAgentSystem)?,
)
};
#[cfg(target_os = "linux")]
let (current, other) = {
let is_root = nix::unistd::Uid::effective().is_root();
let (current_mode, other_mode) = if is_root {
(LinuxLaunchMode::SystemdSystem, LinuxLaunchMode::SystemdUser)
} else {
(LinuxLaunchMode::SystemdUser, LinuxLaunchMode::SystemdSystem)
};
(
build_launcher(&app_path, current_mode)?,
build_launcher(&app_path, other_mode)?,
)
};
#[cfg(windows)]
let (current, other) = (
AutoLaunchBuilder::new()
.set_app_name("pitchfork")
.set_app_path(&app_path)
.set_args(&["supervisor", "run", "--boot"])
.build()
.into_diagnostic()?,
AutoLaunchBuilder::new()
.set_app_name("pitchfork")
.set_app_path(&app_path)
.set_args(&["supervisor", "run", "--boot"])
.build()
.into_diagnostic()?,
);
#[cfg(not(any(target_os = "macos", target_os = "linux", windows)))]
compile_error!("pitchfork boot management is only supported on macOS, Linux, and Windows");
#[cfg(target_os = "macos")]
return Ok(Self {
current,
other,
legacy,
});
#[cfg(not(target_os = "macos"))]
Ok(Self { current, other })
}
pub fn is_enabled(&self) -> Result<bool> {
#[cfg(target_os = "macos")]
return Ok(self.current.is_enabled().into_diagnostic()?
|| self.other.is_enabled().into_diagnostic()?
|| self.legacy.is_enabled().into_diagnostic()?);
#[cfg(not(target_os = "macos"))]
Ok(self.current.is_enabled().into_diagnostic()?
|| self.other.is_enabled().into_diagnostic()?)
}
pub fn is_current_level_enabled(&self) -> Result<bool> {
self.current.is_enabled().into_diagnostic()
}
pub fn is_other_level_enabled(&self) -> Result<bool> {
#[cfg(target_os = "macos")]
return Ok(self.other.is_enabled().into_diagnostic()?
|| (!nix::unistd::Uid::effective().is_root()
&& self.legacy.is_enabled().into_diagnostic()?));
#[cfg(not(target_os = "macos"))]
self.other.is_enabled().into_diagnostic()
}
#[cfg(target_os = "macos")]
pub fn cleanup_legacy(&self, migrated: bool) -> Result<()> {
if nix::unistd::Uid::effective().is_root() && self.legacy.is_enabled().into_diagnostic()? {
self.legacy.disable().into_diagnostic()?;
if migrated {
info!(
"migrated legacy system-level launch entry from /Library/LaunchAgents/ to /Library/LaunchDaemons/"
);
} else {
info!("removed legacy system-level launch entry from /Library/LaunchAgents/");
}
}
Ok(())
}
pub fn enable(&self) -> Result<()> {
#[cfg(target_os = "macos")]
let other_conflict = if nix::unistd::Uid::effective().is_root() {
self.other.is_enabled().into_diagnostic()?
} else {
self.is_other_level_enabled()?
};
#[cfg(not(target_os = "macos"))]
let other_conflict = self.other.is_enabled().into_diagnostic()?;
if other_conflict {
miette::bail!(
"boot start is already registered at the other privilege level; \
run `pitchfork boot disable` (with appropriate privileges) to remove \
it first"
);
}
self.current.enable().into_diagnostic()?;
#[cfg(target_os = "macos")]
self.cleanup_legacy(true)?;
Ok(())
}
pub fn disable(&self) -> Result<()> {
if self.current.is_enabled().into_diagnostic()? {
self.current.disable().into_diagnostic()?;
}
if self.other.is_enabled().into_diagnostic()? {
self.other.disable().into_diagnostic()?;
}
#[cfg(target_os = "macos")]
if nix::unistd::Uid::effective().is_root() && self.legacy.is_enabled().into_diagnostic()? {
self.legacy.disable().into_diagnostic()?;
}
Ok(())
}
}