use anyhow::Result;
use colored::Colorize;
#[cfg(target_os = "macos")]
const LAUNCHD_LABEL: &str = "com.trusty.analyze";
#[derive(Debug, Clone)]
pub enum ServiceAction {
Install,
Uninstall,
Status,
Logs,
}
pub fn run_service_action(action: ServiceAction) -> Result<()> {
#[cfg(target_os = "macos")]
{
match action {
ServiceAction::Install => service_install(),
ServiceAction::Uninstall => service_uninstall(),
ServiceAction::Status => service_status(),
ServiceAction::Logs => service_logs(),
}
}
#[cfg(not(target_os = "macos"))]
{
let _ = action;
eprintln!(
"{} `trusty-analyze service` is not supported on this platform — \
use your distro's service manager (systemd, OpenRC, etc.) directly.",
"✗".red()
);
std::process::exit(1);
}
}
#[cfg(target_os = "macos")]
fn launchd_log_dir() -> Result<std::path::PathBuf> {
let home = dirs::home_dir().ok_or_else(|| anyhow::anyhow!("could not resolve $HOME"))?;
let dir = home.join(".trusty-analyze").join("logs");
std::fs::create_dir_all(&dir)?;
Ok(dir)
}
#[cfg(target_os = "macos")]
fn launchd_config() -> Result<trusty_common::launchd::LaunchdConfig> {
use trusty_common::launchd::{KeepAlive, LaunchdConfig};
let exe = std::env::current_exe()
.map_err(|e| anyhow::anyhow!("could not resolve current exe: {e}"))?;
let log_dir = launchd_log_dir()?;
Ok(LaunchdConfig {
label: LAUNCHD_LABEL.to_string(),
exe_path: exe,
args: vec!["serve".to_string()],
log_dir,
keep_alive: KeepAlive::Always,
throttle_interval: 10,
env_vars: Vec::new(),
fd_limit: None,
})
}
#[cfg(target_os = "macos")]
pub fn launchd_plist_path() -> Result<std::path::PathBuf> {
launchd_config()?.plist_path()
}
#[cfg(target_os = "macos")]
pub fn service_install() -> Result<()> {
let cfg = launchd_config()?;
cfg.install()
.map_err(|e| anyhow::anyhow!("install LaunchAgent plist: {e}"))?;
let plist_path = cfg.plist_path()?;
println!(
"{} Wrote LaunchAgent plist: {}",
"✓".green(),
plist_path.display()
);
cfg.bootstrap()
.map_err(|e| anyhow::anyhow!("launchctl bootstrap: {e}"))?;
let domain = format!("gui/{}", trusty_common::launchd::current_uid());
println!(
"{} trusty-analyze service installed and started ({} loaded into {}).",
"✓".green(),
LAUNCHD_LABEL,
domain
);
println!(
" Logs: {}\n Status: {}",
cfg.log_dir.display().to_string().dimmed(),
"trusty-analyze service status".cyan(),
);
Ok(())
}
#[cfg(target_os = "macos")]
fn service_uninstall() -> Result<()> {
let cfg = launchd_config()?;
let plist_path = cfg.plist_path()?;
if plist_path.exists() {
let _ = cfg.bootout();
std::fs::remove_file(&plist_path)
.map_err(|e| anyhow::anyhow!("remove {}: {e}", plist_path.display()))?;
println!(
"{} trusty-analyze service uninstalled ({} removed).",
"✓".green(),
plist_path.display()
);
} else {
println!(
"{} {} not installed — nothing to do",
"·".dimmed(),
plist_path.display()
);
}
Ok(())
}
#[cfg(target_os = "macos")]
fn service_status() -> Result<()> {
let uid = trusty_common::launchd::current_uid();
let target = format!("gui/{uid}/{LAUNCHD_LABEL}");
let output = std::process::Command::new("launchctl")
.args(["print", &target])
.output()
.map_err(|e| anyhow::anyhow!("launchctl print failed: {e}"))?;
if output.status.success() {
println!("{}", String::from_utf8_lossy(&output.stdout));
} else {
eprintln!(
"{} {} is not loaded ({})",
"✗".red(),
target,
String::from_utf8_lossy(&output.stderr).trim()
);
eprintln!(
" Install with: {}",
"trusty-analyze service install".cyan()
);
std::process::exit(1);
}
Ok(())
}
#[cfg(target_os = "macos")]
fn service_logs() -> Result<()> {
use std::os::unix::process::CommandExt;
let log_dir = launchd_log_dir()?;
let stdout_log = log_dir.join("stdout.log");
let stderr_log = log_dir.join("stderr.log");
if !stdout_log.exists() && !stderr_log.exists() {
eprintln!(
"{} No logs at {} yet — start the service first.",
"·".dimmed(),
log_dir.display()
);
return Ok(());
}
let err = std::process::Command::new("tail")
.arg("-F")
.arg(&stdout_log)
.arg(&stderr_log)
.exec();
Err(anyhow::anyhow!("exec tail failed: {err}"))
}