pitchfork_cli/
boot_manager.rs1use crate::{Result, env};
2#[cfg(target_os = "linux")]
3use auto_launcher::LinuxLaunchMode;
4#[cfg(target_os = "macos")]
5use auto_launcher::MacOSLaunchMode;
6use auto_launcher::{AutoLaunch, AutoLaunchBuilder};
7use miette::IntoDiagnostic;
8
9#[cfg(any(target_os = "macos", target_os = "linux"))]
10fn build_launcher(
11 app_path: &str,
12 #[cfg(target_os = "macos")] macos_mode: MacOSLaunchMode,
13 #[cfg(target_os = "linux")] linux_mode: LinuxLaunchMode,
14) -> Result<AutoLaunch> {
15 let mut builder = AutoLaunchBuilder::new();
16 builder
17 .set_app_name("pitchfork")
18 .set_app_path(app_path)
19 .set_args(&["supervisor", "run", "--boot"]);
20
21 #[cfg(target_os = "macos")]
22 builder.set_macos_launch_mode(macos_mode);
23
24 #[cfg(target_os = "linux")]
25 builder.set_linux_launch_mode(linux_mode);
26
27 builder.build().into_diagnostic()
28}
29
30pub struct BootManager {
31 current: AutoLaunch,
33 other: AutoLaunch,
35 #[cfg(target_os = "macos")]
38 legacy: AutoLaunch,
39}
40
41impl BootManager {
42 pub fn new() -> Result<Self> {
43 let app_path = env::PITCHFORK_BIN.to_string_lossy().to_string();
44
45 #[cfg(target_os = "macos")]
46 let (current, other, legacy) = {
47 let is_root = nix::unistd::Uid::effective().is_root();
48 let (current_mode, other_mode) = if is_root {
49 (
50 MacOSLaunchMode::LaunchDaemonSystem,
51 MacOSLaunchMode::LaunchAgentUser,
52 )
53 } else {
54 (
55 MacOSLaunchMode::LaunchAgentUser,
56 MacOSLaunchMode::LaunchDaemonSystem,
57 )
58 };
59 (
60 build_launcher(&app_path, current_mode)?,
61 build_launcher(&app_path, other_mode)?,
62 build_launcher(&app_path, MacOSLaunchMode::LaunchAgentSystem)?,
63 )
64 };
65
66 #[cfg(target_os = "linux")]
67 let (current, other) = {
68 let is_root = nix::unistd::Uid::effective().is_root();
69 let (current_mode, other_mode) = if is_root {
70 (LinuxLaunchMode::SystemdSystem, LinuxLaunchMode::SystemdUser)
71 } else {
72 (LinuxLaunchMode::SystemdUser, LinuxLaunchMode::SystemdSystem)
73 };
74 (
75 build_launcher(&app_path, current_mode)?,
76 build_launcher(&app_path, other_mode)?,
77 )
78 };
79
80 #[cfg(windows)]
83 let (current, other) = (
84 AutoLaunchBuilder::new()
85 .set_app_name("pitchfork")
86 .set_app_path(&app_path)
87 .set_args(&["supervisor", "run", "--boot"])
88 .build()
89 .into_diagnostic()?,
90 AutoLaunchBuilder::new()
91 .set_app_name("pitchfork")
92 .set_app_path(&app_path)
93 .set_args(&["supervisor", "run", "--boot"])
94 .build()
95 .into_diagnostic()?,
96 );
97
98 #[cfg(not(any(target_os = "macos", target_os = "linux", windows)))]
100 compile_error!("pitchfork boot management is only supported on macOS, Linux, and Windows");
101
102 #[cfg(target_os = "macos")]
103 return Ok(Self {
104 current,
105 other,
106 legacy,
107 });
108
109 #[cfg(not(target_os = "macos"))]
110 Ok(Self { current, other })
111 }
112
113 pub fn is_enabled(&self) -> Result<bool> {
115 #[cfg(target_os = "macos")]
116 return Ok(self.current.is_enabled().into_diagnostic()?
117 || self.other.is_enabled().into_diagnostic()?
118 || self.legacy.is_enabled().into_diagnostic()?);
119
120 #[cfg(not(target_os = "macos"))]
121 Ok(self.current.is_enabled().into_diagnostic()?
122 || self.other.is_enabled().into_diagnostic()?)
123 }
124
125 pub fn is_current_level_enabled(&self) -> Result<bool> {
127 self.current.is_enabled().into_diagnostic()
128 }
129
130 pub fn is_other_level_enabled(&self) -> Result<bool> {
135 #[cfg(target_os = "macos")]
136 return Ok(self.other.is_enabled().into_diagnostic()?
137 || (!nix::unistd::Uid::effective().is_root()
138 && self.legacy.is_enabled().into_diagnostic()?));
139
140 #[cfg(not(target_os = "macos"))]
141 self.other.is_enabled().into_diagnostic()
142 }
143
144 #[cfg(target_os = "macos")]
151 pub fn cleanup_legacy(&self, migrated: bool) -> Result<()> {
152 if nix::unistd::Uid::effective().is_root() && self.legacy.is_enabled().into_diagnostic()? {
153 self.legacy.disable().into_diagnostic()?;
154 if migrated {
155 info!(
156 "migrated legacy system-level launch entry from /Library/LaunchAgents/ to /Library/LaunchDaemons/"
157 );
158 } else {
159 info!("removed legacy system-level launch entry from /Library/LaunchAgents/");
160 }
161 }
162 Ok(())
163 }
164
165 pub fn enable(&self) -> Result<()> {
173 #[cfg(target_os = "macos")]
176 let other_conflict = if nix::unistd::Uid::effective().is_root() {
177 self.other.is_enabled().into_diagnostic()?
178 } else {
179 self.is_other_level_enabled()?
180 };
181
182 #[cfg(not(target_os = "macos"))]
183 let other_conflict = self.other.is_enabled().into_diagnostic()?;
184
185 if other_conflict {
186 miette::bail!(
187 "boot start is already registered at the other privilege level; \
188 run `pitchfork boot disable` (with appropriate privileges) to remove \
189 it first"
190 );
191 }
192
193 self.current.enable().into_diagnostic()?;
194
195 #[cfg(target_os = "macos")]
196 self.cleanup_legacy(true)?;
197
198 Ok(())
199 }
200
201 pub fn disable(&self) -> Result<()> {
207 if self.current.is_enabled().into_diagnostic()? {
208 self.current.disable().into_diagnostic()?;
209 }
210 if self.other.is_enabled().into_diagnostic()? {
211 self.other.disable().into_diagnostic()?;
212 }
213 #[cfg(target_os = "macos")]
214 if nix::unistd::Uid::effective().is_root() && self.legacy.is_enabled().into_diagnostic()? {
215 self.legacy.disable().into_diagnostic()?;
216 }
217 Ok(())
218 }
219}