18ac699951
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
69 lines
1.9 KiB
Rust
69 lines
1.9 KiB
Rust
use anyhow::{Context, Result};
|
|
use std::path::PathBuf;
|
|
|
|
const LABEL: &str = "xyz.spacesh.daemon";
|
|
|
|
fn plist_path() -> Result<PathBuf> {
|
|
let home = dirs::home_dir().context("no home")?;
|
|
Ok(home.join("Library").join("LaunchAgents").join(format!("{LABEL}.plist")))
|
|
}
|
|
|
|
/// Render the launchd plist. `run_at_load` defaults to false in this slice.
|
|
pub fn render_plist(exe: &str, run_at_load: bool) -> 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>
|
|
</array>
|
|
<key>KeepAlive</key>
|
|
<true/>
|
|
<key>RunAtLoad</key>
|
|
<{run_at_load}/>
|
|
</dict>
|
|
</plist>
|
|
"#,
|
|
run_at_load = if run_at_load { "true" } else { "false" }
|
|
)
|
|
}
|
|
|
|
pub fn install_agent() -> Result<()> {
|
|
let exe = std::env::current_exe()?;
|
|
let plist = render_plist(&exe.to_string_lossy(), false);
|
|
let path = plist_path()?;
|
|
if let Some(parent) = path.parent() {
|
|
std::fs::create_dir_all(parent)?;
|
|
}
|
|
std::fs::write(&path, plist)?;
|
|
// Load it (best-effort; ignore "already loaded").
|
|
let _ = std::process::Command::new("launchctl")
|
|
.arg("load")
|
|
.arg(&path)
|
|
.status();
|
|
Ok(())
|
|
}
|
|
|
|
#[cfg(test)]
|
|
mod tests {
|
|
use super::*;
|
|
|
|
#[test]
|
|
fn plist_has_label_keepalive_and_exe() {
|
|
let p = render_plist("/usr/local/bin/spaceshd", false);
|
|
assert!(p.contains("xyz.spacesh.daemon"));
|
|
assert!(p.contains("/usr/local/bin/spaceshd"));
|
|
assert!(p.contains("<key>KeepAlive</key>\n <true/>"));
|
|
assert!(p.contains("<key>RunAtLoad</key>\n <false/>"));
|
|
}
|
|
|
|
#[test]
|
|
fn run_at_load_toggles() {
|
|
assert!(render_plist("x", true).contains("<key>RunAtLoad</key>\n <true/>"));
|
|
}
|
|
}
|