use std::path::{Path, PathBuf};
use super::common::{daemon_log, moadim_exe, run};
const LAUNCHD_LABEL: &str = "io.moadim.daemon";
pub(super) fn launchctl_bin() -> String {
std::env::var("MOADIM_LAUNCHCTL_BIN").unwrap_or_else(|_| "launchctl".to_string())
}
pub(super) fn xml_escape(text: &str) -> String {
text.replace('&', "&")
.replace('<', "<")
.replace('>', ">")
.replace('"', """)
.replace('\'', "'")
}
pub(super) fn plist_path() -> anyhow::Result<PathBuf> {
plist_path_from_home(dirs::home_dir())
}
pub(super) fn plist_path_from_home(home: Option<PathBuf>) -> anyhow::Result<PathBuf> {
let home = home.ok_or_else(|| anyhow::anyhow!("could not determine the home directory"))?;
Ok(home
.join("Library/LaunchAgents")
.join(format!("{LAUNCHD_LABEL}.plist")))
}
pub(super) fn render_plist(exe: &Path, log: &Path) -> String {
format!(
r#"<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
<key>Label</key>
<string>{label}</string>
<key>ProgramArguments</key>
<array>
<string>{exe}</string>
<string>--interactive</string>
</array>
<key>RunAtLoad</key>
<true/>
<key>KeepAlive</key>
<true/>
<key>StandardOutPath</key>
<string>{log}</string>
<key>StandardErrorPath</key>
<string>{log}</string>
</dict>
</plist>
"#,
label = LAUNCHD_LABEL,
exe = xml_escape(&exe.display().to_string()),
log = xml_escape(&log.display().to_string()),
)
}
pub(super) fn write_plist(plist: &Path, exe: &Path, log: &Path) -> anyhow::Result<()> {
if let Some(dir) = plist.parent() {
std::fs::create_dir_all(dir)?;
}
if let Some(dir) = log.parent() {
std::fs::create_dir_all(dir)?;
}
std::fs::write(plist, render_plist(exe, log))?;
Ok(())
}
fn reload_agent(plist: &Path) -> anyhow::Result<()> {
let plist_arg = plist.display().to_string();
let launchctl = launchctl_bin();
let _ = run(&launchctl, &["unload", &plist_arg]);
run(&launchctl, &["load", "-w", &plist_arg])
}
fn report_installed(plist: &Path, log: &Path) {
println!("moadim installed as a launchd agent ({LAUNCHD_LABEL})");
println!(" plist {}", plist.display());
println!(" logs {}", log.display());
println!(" status launchctl list | grep {LAUNCHD_LABEL}");
}
pub fn install() -> anyhow::Result<()> {
let exe = moadim_exe()?;
let log = daemon_log();
let plist = plist_path()?;
write_plist(&plist, &exe, &log)?;
reload_agent(&plist)?;
report_installed(&plist, &log);
Ok(())
}
pub fn uninstall() -> anyhow::Result<()> {
let plist = plist_path()?;
if plist.exists() {
let plist_arg = plist.display().to_string();
let _ = run(&launchctl_bin(), &["unload", "-w", &plist_arg]);
std::fs::remove_file(&plist)?;
println!("moadim launchd agent removed ({})", plist.display());
} else {
println!(
"moadim launchd agent is not installed (no plist at {})",
plist.display()
);
}
Ok(())
}