use crate::term::{self, human_println as println};
use anyhow::{Context, Result};
use std::path::{Path, PathBuf};
const LABEL: &str = "ninja.kunobi.kache";
const MACOS_BUNDLE_IDENTIFIER: &str = LABEL;
const PLIST_NAME: &str = "ninja.kunobi.kache.plist";
const LEGACY_LABEL: &str = "com.zondax.kache";
const LEGACY_PLIST_NAME: &str = "com.zondax.kache.plist";
const UNIT_NAME: &str = "kache.service";
const TASK_NAME: &str = "kache-daemon";
fn plist_path() -> PathBuf {
dirs::home_dir()
.unwrap_or_default()
.join("Library/LaunchAgents")
.join(PLIST_NAME)
}
fn legacy_plist_path() -> PathBuf {
dirs::home_dir()
.unwrap_or_default()
.join("Library/LaunchAgents")
.join(LEGACY_PLIST_NAME)
}
fn unit_path() -> PathBuf {
dirs::home_dir()
.unwrap_or_default()
.join(".config/systemd/user")
.join(UNIT_NAME)
}
fn task_xml_path() -> PathBuf {
dirs::data_local_dir()
.unwrap_or_else(|| PathBuf::from("."))
.join("kache")
.join("kache-task.xml")
}
pub fn service_file_path() -> Option<PathBuf> {
if cfg!(target_os = "macos") {
Some(plist_path())
} else if cfg!(target_os = "linux") {
Some(unit_path())
} else if cfg!(windows) {
Some(task_xml_path())
} else {
None
}
}
fn log_dir() -> PathBuf {
dirs::home_dir()
.unwrap_or_default()
.join("Library/Logs/kache")
}
fn stop_launchd_service(uid: u32, label: &str, plist: &std::path::Path) {
let bootout = std::process::Command::new("launchctl")
.args(["bootout", &format!("gui/{uid}/{label}")])
.output();
if !matches!(bootout, Ok(out) if out.status.success()) {
let _ = std::process::Command::new("launchctl")
.args(["unload", &plist.display().to_string()])
.output();
}
}
fn launchd_service_registered(uid: u32) -> bool {
std::process::Command::new("launchctl")
.args(["print", &format!("gui/{uid}/{LABEL}")])
.output()
.is_ok_and(|out| out.status.success())
}
fn canonical_or_original(path: &Path) -> PathBuf {
path.canonicalize().unwrap_or_else(|_| path.to_path_buf())
}
#[derive(Debug, Clone, PartialEq, Eq)]
pub(crate) enum ServiceExeProblem {
Missing {
recorded: PathBuf,
},
NotExecutable {
recorded: PathBuf,
},
OtherBinary {
recorded: PathBuf,
current: PathBuf,
},
Unverifiable {
recorded: PathBuf,
current: PathBuf,
},
}
impl ServiceExeProblem {
pub(crate) fn detail(&self) -> String {
match self {
Self::Missing { recorded } => {
format!(
"the service runs {}, which no longer exists",
recorded.display()
)
}
Self::NotExecutable { recorded } => {
format!(
"the service runs {}, which is not executable",
recorded.display()
)
}
Self::OtherBinary { recorded, current } => format!(
"the service runs {}, a different file from this kache ({})",
recorded.display(),
current.display()
),
Self::Unverifiable { recorded, current } => format!(
"the service runs {}, which cannot be compared with this kache ({}): its file cannot be read",
recorded.display(),
current.display()
),
}
}
}
fn recorded_exe_problem(
recorded: &Path,
current: &Path,
current_id: Option<kache_shims::InodeId>,
fs: &dyn kache_shims::Fs,
) -> Option<ServiceExeProblem> {
let recorded_id = fs.identity(recorded);
if recorded_id.is_none() {
return Some(ServiceExeProblem::Missing {
recorded: recorded.to_path_buf(),
});
}
if !fs.is_executable_file(recorded) {
return Some(ServiceExeProblem::NotExecutable {
recorded: recorded.to_path_buf(),
});
}
let (recorded, current) = (recorded.to_path_buf(), current.to_path_buf());
match current_id {
None => Some(ServiceExeProblem::Unverifiable { recorded, current }),
Some(id) if recorded_id != Some(id) => {
Some(ServiceExeProblem::OtherBinary { recorded, current })
}
Some(_) => None,
}
}
pub(crate) fn service_exe_problem(path: &Path) -> Option<ServiceExeProblem> {
let recorded = parse_exe_from_service_file(path)?;
let current = std::env::current_exe().unwrap_or_default();
let current_id = kache_shims::running_identity();
recorded_exe_problem(&recorded, ¤t, current_id, &kache_shims::RealFs)
}
pub fn login_service_available() -> bool {
if !cfg!(target_os = "linux") {
return true;
}
systemd_user_manager_reachable()
}
fn systemd_user_manager_reachable() -> bool {
std::process::Command::new("systemctl")
.args(["--user", "show-environment"])
.stdin(std::process::Stdio::null())
.stdout(std::process::Stdio::null())
.stderr(std::process::Stdio::null())
.status()
.is_ok_and(|status| status.success())
}
pub fn install() -> Result<()> {
let selection = kache_shims::detect().context("locating the kache binary")?;
for line in selection_lines(&selection) {
println!("{line}");
}
let exe = selection.path;
if cfg!(target_os = "macos") {
install_launchd(&exe)
} else if cfg!(target_os = "linux") {
install_systemd(&exe)
} else if cfg!(windows) {
install_task_scheduler(&exe)
} else {
anyhow::bail!("unsupported platform");
}
}
fn selection_lines(selection: &kache_shims::Selection) -> Vec<String> {
let mut lines = vec![format!(
"Recording {} ({}, {}).",
selection.path.display(),
selection.kind,
selection.stability
)];
match selection.stability {
kache_shims::Stability::Versioned => lines.push(format!(
" Note: {}. Rerun `kache daemon install` after upgrading kache.",
selection.reason
)),
kache_shims::Stability::Unverified => lines.push(format!(
" Warning: {}. The service may run as a user who cannot reach this path.",
selection.reason
)),
_ => {}
}
lines
}
fn launchd_plist_content(
exe: &std::path::Path,
stdout_log: &std::path::Path,
stderr_log: &std::path::Path,
) -> String {
let exe_str = exe.display();
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_str}</string>
<string>daemon</string>
<string>run</string>
</array>
<key>AssociatedBundleIdentifiers</key>
<array>
<string>{MACOS_BUNDLE_IDENTIFIER}</string>
</array>
<key>RunAtLoad</key>
<true/>
<key>KeepAlive</key>
<dict>
<key>SuccessfulExit</key>
<false/>
</dict>
<key>StandardOutPath</key>
<string>{stdout}</string>
<key>StandardErrorPath</key>
<string>{stderr}</string>
<key>EnvironmentVariables</key>
<dict>
<key>KACHE_LOG</key>
<string>kache=info</string>
</dict>
<key>ThrottleInterval</key>
<integer>5</integer>
</dict>
</plist>
"#,
stdout = stdout_log.display(),
stderr = stderr_log.display(),
)
}
fn install_launchd(exe: &std::path::Path) -> Result<()> {
let plist = plist_path();
let legacy_plist = legacy_plist_path();
let uid = crate::platform::current_uid();
if plist.exists() || legacy_plist.exists() {
println!("Existing service found — upgrading in place...");
stop_launchd_service(uid, LABEL, &plist);
stop_launchd_service(uid, LEGACY_LABEL, &legacy_plist);
}
if legacy_plist.exists() {
std::fs::remove_file(&legacy_plist).context("removing legacy plist")?;
}
if let Some(parent) = plist.parent() {
std::fs::create_dir_all(parent).context("creating LaunchAgents directory")?;
}
let log_dir = log_dir();
std::fs::create_dir_all(&log_dir).context("creating log directory")?;
let stdout_log = log_dir.join("out.log");
let stderr_log = log_dir.join("err.log");
let content = launchd_plist_content(exe, &stdout_log, &stderr_log);
std::fs::write(&plist, &content).context("writing plist")?;
let bootstrap = std::process::Command::new("launchctl")
.args([
"bootstrap",
&format!("gui/{uid}"),
&plist.display().to_string(),
])
.output();
match bootstrap {
Ok(out) if out.status.success() => {}
bootstrap_result => {
let load = std::process::Command::new("launchctl")
.args(["load", "-w", &plist.display().to_string()])
.output()
.context("running launchctl load")?;
if !load.status.success() {
let bootstrap_stderr = bootstrap_result
.as_ref()
.ok()
.map(|out| String::from_utf8_lossy(&out.stderr).trim().to_string())
.filter(|s| !s.is_empty())
.unwrap_or_else(|| "launchctl bootstrap failed".to_string());
let stderr = String::from_utf8_lossy(&load.stderr);
anyhow::bail!(
"launchctl bootstrap failed: {bootstrap_stderr}; launchctl load failed: {stderr}"
);
}
}
}
if !launchd_service_registered(uid) {
anyhow::bail!(
"launchctl did not register {LABEL}; try running `launchctl bootstrap gui/{uid} {}`",
plist.display()
);
}
println!("Service installed and started.");
println!(" plist: {}", plist.display());
println!(" logs: {}", log_dir.display());
println!("\nThe daemon will now start automatically on login and restart on crash.");
println!("Use `kache daemon` to verify, `kache daemon log` to stream logs.");
Ok(())
}
fn systemd_unit_content(exe: &std::path::Path) -> String {
format!(
r#"[Unit]
Description=kache build cache daemon
After=default.target
[Service]
Type=simple
ExecStart={exe} daemon run
Restart=on-failure
RestartSec=5s
Environment=KACHE_LOG=kache=info
[Install]
WantedBy=default.target
"#,
exe = exe.display(),
)
}
fn install_systemd(exe: &std::path::Path) -> Result<()> {
anyhow::ensure!(
systemd_user_manager_reachable(),
"no systemd user session is available (systemctl --user cannot connect); \
start the daemon with `kache daemon start` instead"
);
let unit = unit_path();
if unit.exists() {
println!("Existing service found — upgrading in place...");
let _ = std::process::Command::new("systemctl")
.args(["--user", "stop", UNIT_NAME])
.output();
}
if let Some(parent) = unit.parent() {
std::fs::create_dir_all(parent).context("creating systemd user directory")?;
}
let content = systemd_unit_content(exe);
let created = !unit.exists();
std::fs::write(&unit, &content).context("writing systemd unit")?;
let registered = run_systemctl_user(&["daemon-reload"])
.and_then(|()| run_systemctl_user(&["enable", "--now", UNIT_NAME]));
if let Err(error) = registered {
if created {
let _ = std::fs::remove_file(&unit);
}
return Err(error);
}
let user = std::env::var("USER").unwrap_or_default();
if !user.is_empty() {
let _ = std::process::Command::new("loginctl")
.args(["enable-linger", &user])
.output();
}
println!("Service installed and started.");
println!(" unit: {}", unit.display());
println!(" logs: journalctl --user -u {UNIT_NAME}");
println!("\nThe daemon will now start automatically on login and restart on crash.");
println!("Use `kache daemon` to verify, `kache daemon log` to stream logs.");
Ok(())
}
fn run_systemctl_user(args: &[&str]) -> Result<()> {
let command = args.join(" ");
let output = std::process::Command::new("systemctl")
.arg("--user")
.args(args)
.output()
.with_context(|| format!("running systemctl --user {command}"))?;
anyhow::ensure!(
output.status.success(),
"systemctl --user {command} failed: {}",
String::from_utf8_lossy(&output.stderr).trim()
);
Ok(())
}
fn task_scheduler_xml_content(exe: &std::path::Path, username: &str) -> String {
let exe_str = exe.display().to_string().replace('/', "\\");
format!(
r#"<?xml version="1.0" encoding="UTF-16"?>
<Task version="1.2" xmlns="http://schemas.microsoft.com/windows/2004/02/mit/task">
<RegistrationInfo>
<Description>kache build cache daemon — starts at login, restarts on crash</Description>
</RegistrationInfo>
<Triggers>
<LogonTrigger>
<Enabled>true</Enabled>
<UserId>{username}</UserId>
</LogonTrigger>
</Triggers>
<Principals>
<Principal id="Author">
<UserId>{username}</UserId>
<LogonType>InteractiveToken</LogonType>
<RunLevel>LeastPrivilege</RunLevel>
</Principal>
</Principals>
<Settings>
<MultipleInstancesPolicy>IgnoreNew</MultipleInstancesPolicy>
<DisallowStartIfOnBatteries>false</DisallowStartIfOnBatteries>
<StopIfGoingOnBatteries>false</StopIfGoingOnBatteries>
<ExecutionTimeLimit>PT0S</ExecutionTimeLimit>
<RestartOnFailure>
<Interval>PT1M</Interval>
<Count>999</Count>
</RestartOnFailure>
<Hidden>false</Hidden>
</Settings>
<Actions Context="Author">
<Exec>
<Command>conhost.exe</Command>
<Arguments>--headless "{exe_str}" daemon run</Arguments>
</Exec>
</Actions>
</Task>
"#,
)
}
fn install_task_scheduler(exe: &std::path::Path) -> Result<()> {
let xml_path = task_xml_path();
if task_scheduler_installed() {
println!("Existing task found — upgrading in place...");
let _ = std::process::Command::new("schtasks")
.args(["/delete", "/tn", TASK_NAME, "/f"])
.output();
}
if let Some(parent) = xml_path.parent() {
std::fs::create_dir_all(parent).context("creating kache data directory")?;
}
let username = std::env::var("USERNAME").unwrap_or_else(|_| "".into());
let log_path = crate::config::Config::load()
.map(|c| c.socket_path().with_extension("log"))
.unwrap_or_else(|_| {
dirs::data_local_dir()
.unwrap_or_else(|| PathBuf::from("."))
.join("kache")
.join("daemon.log")
});
let content = task_scheduler_xml_content(exe, &username);
let utf16: Vec<u16> = content.encode_utf16().collect();
let mut bytes = vec![0xFF, 0xFE]; for word in &utf16 {
bytes.extend_from_slice(&word.to_le_bytes());
}
std::fs::write(&xml_path, &bytes).context("writing task XML")?;
let create = std::process::Command::new("schtasks")
.args([
"/create",
"/tn",
TASK_NAME,
"/xml",
&xml_path.display().to_string(),
"/f",
])
.output()
.context("running schtasks /create")?;
if !create.status.success() {
let stderr = String::from_utf8_lossy(&create.stderr);
if stderr.contains("Access") || stderr.contains("acceso") || stderr.contains("denied") {
anyhow::bail!(
"schtasks requires administrator privileges.\n\
Run this command from an elevated (admin) terminal:\n\n\
kache daemon install"
);
}
anyhow::bail!("schtasks /create failed: {stderr}");
}
let _ = std::process::Command::new("schtasks")
.args(["/run", "/tn", TASK_NAME])
.output();
println!("Service installed and started.");
println!(" task: {TASK_NAME}");
println!(" xml: {}", xml_path.display());
println!(" logs: {}", log_path.display());
println!("\nThe daemon will now start automatically on login and restart on crash.");
println!("Use `kache daemon` to verify, `kache daemon log` to stream logs.");
Ok(())
}
fn task_scheduler_installed() -> bool {
if !cfg!(windows) {
return false;
}
std::process::Command::new("schtasks")
.args(["/query", "/tn", TASK_NAME])
.stdout(std::process::Stdio::null())
.stderr(std::process::Stdio::null())
.status()
.is_ok_and(|s| s.success())
}
pub(crate) fn manages_instance(config: &crate::config::Config) -> Result<bool> {
if !service_file_path().is_some_and(|path| path.is_file()) {
return Ok(false);
}
configured_instance_matches(
&config.socket_path(),
crate::config::default_cache_dir(),
crate::config::host_config_path()
.into_iter()
.chain(std::iter::once(crate::config::config_file_path())),
)
}
#[derive(Debug, Clone, PartialEq, Eq)]
pub(crate) enum StartupLog {
File(PathBuf),
Journal,
Discarded,
}
impl std::fmt::Display for StartupLog {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
match self {
Self::File(path) => write!(f, "{}", path.display()),
Self::Journal => write!(f, "journalctl --user -u {UNIT_NAME}"),
Self::Discarded => write!(
f,
"not kept by the scheduled task; run `kache daemon run` to see it"
),
}
}
}
pub(crate) fn startup_log(config: &crate::config::Config) -> StartupLog {
startup_log_for(
manages_instance(config).unwrap_or(false),
config.socket_path().with_extension("log"),
)
}
fn startup_log_for(managed: bool, runtime_log: PathBuf) -> StartupLog {
if !managed {
return StartupLog::File(runtime_log);
}
if cfg!(target_os = "macos") {
StartupLog::File(log_dir().join("err.log"))
} else if cfg!(windows) {
StartupLog::Discarded
} else {
StartupLog::Journal
}
}
fn configured_instance_matches(
requested_socket: &Path,
mut store: PathBuf,
config_paths: impl IntoIterator<Item = PathBuf>,
) -> Result<bool> {
let mut runtime = None;
for path in config_paths {
let text = match std::fs::read_to_string(&path) {
Ok(text) => text,
Err(error) if error.kind() == std::io::ErrorKind::NotFound => continue,
Err(error) => return Err(error).context("reading login service configuration"),
};
let file: crate::config::FileConfig =
toml::from_str(&text).context("parsing login service configuration")?;
if let Some(cache) = file.cache {
if let Some(path) = cache.local_store {
store = crate::config::shellexpand(&path);
}
if let Some(path) = cache.runtime_dir {
runtime = Some(crate::config::shellexpand(&path));
}
}
}
let socket = runtime.unwrap_or(store).join("daemon.sock");
Ok(canonical_or_original(requested_socket) == canonical_or_original(&socket))
}
pub fn kickstart(deadline: std::time::Instant) -> Result<bool> {
#[cfg(target_os = "macos")]
{
let plist = plist_path();
if !plist.exists() {
return Ok(false);
}
let uid = crate::platform::current_uid();
let target = format!("gui/{uid}/{LABEL}");
let domain = format!("gui/{uid}");
start_launchd_job(
&target,
|| {
command_output_until(
std::process::Command::new("launchctl").args(["kickstart", &target]),
deadline,
)
.context("running launchctl kickstart")
},
|| {
command_output_until(
std::process::Command::new("launchctl")
.arg("bootstrap")
.arg(&domain)
.arg(&plist),
deadline,
)
.context("running launchctl bootstrap")
},
)
}
#[cfg(target_os = "linux")]
{
let unit = unit_path();
if !unit.exists() {
return Ok(false);
}
let out = command_output_until(
std::process::Command::new("systemctl").args(["--user", "start", UNIT_NAME]),
deadline,
)
.context("running systemctl --user start")?;
if !out.status.success() {
let stderr = String::from_utf8_lossy(&out.stderr);
anyhow::bail!("systemctl --user start {UNIT_NAME} failed: {stderr}");
}
Ok(true)
}
#[cfg(windows)]
{
let installed = command_output_until(
std::process::Command::new("schtasks").args(["/query", "/tn", TASK_NAME]),
deadline,
)?;
start_scheduled_task(installed, || {
command_output_until(
std::process::Command::new("schtasks").args(["/run", "/tn", TASK_NAME]),
deadline,
)
.context("running schtasks /run")
})
}
#[cfg(not(any(target_os = "macos", target_os = "linux", windows)))]
{
let _ = deadline;
Ok(false)
}
}
#[cfg(any(target_os = "macos", all(test, unix)))]
const LAUNCHCTL_NO_SUCH_SERVICE: i32 = 113;
#[cfg(any(target_os = "macos", all(test, unix)))]
fn start_launchd_job(
target: &str,
kickstart: impl FnOnce() -> Result<std::process::Output>,
bootstrap: impl FnOnce() -> Result<std::process::Output>,
) -> Result<bool> {
let started = kickstart()?;
if started.status.success() {
return Ok(true);
}
if started.status.code() != Some(LAUNCHCTL_NO_SUCH_SERVICE) {
anyhow::bail!(
"launchctl kickstart {target} failed: {}",
String::from_utf8_lossy(&started.stderr).trim()
);
}
let loaded = bootstrap()?;
anyhow::ensure!(
loaded.status.success(),
"{target} is installed but not loaded, and launchctl bootstrap failed: {}",
String::from_utf8_lossy(&loaded.stderr).trim()
);
Ok(true)
}
#[cfg(any(windows, test))]
fn start_scheduled_task(
query: std::process::Output,
start: impl FnOnce() -> Result<std::process::Output>,
) -> Result<bool> {
if !query.status.success() {
return Ok(false);
}
let started = start()?;
anyhow::ensure!(
started.status.success(),
"schtasks /run {TASK_NAME} failed: {}",
String::from_utf8_lossy(&started.stderr)
);
Ok(true)
}
fn command_output_until(
command: &mut std::process::Command,
deadline: std::time::Instant,
) -> Result<std::process::Output> {
use std::io::{Read, Seek};
anyhow::ensure!(
std::time::Instant::now() < deadline,
"service command deadline expired"
);
let mut stderr = tempfile::tempfile()?;
let process = command
.stdin(std::process::Stdio::null())
.stdout(std::process::Stdio::null())
.stderr(stderr.try_clone()?)
.spawn()?;
let mut child = kunobi_daemon::Candidate::new(process);
let status = kunobi_daemon::readiness::wait_until(deadline, |_| child.try_wait())?
.context("service manager command timed out; startup remains unverified")?;
stderr.rewind()?;
let mut diagnostic = Vec::new();
stderr.take(16_384).read_to_end(&mut diagnostic)?;
Ok(std::process::Output {
status,
stdout: Vec::new(),
stderr: diagnostic,
})
}
pub fn uninstall() -> Result<()> {
if cfg!(target_os = "macos") {
uninstall_launchd()
} else if cfg!(target_os = "linux") {
uninstall_systemd()
} else if cfg!(windows) {
uninstall_task_scheduler()
} else {
anyhow::bail!("unsupported platform");
}
}
fn uninstall_launchd() -> Result<()> {
let plist = plist_path();
let legacy_plist = legacy_plist_path();
let uid = crate::platform::current_uid();
let had_plist = plist.exists();
let had_legacy_plist = legacy_plist.exists();
if !had_plist && !had_legacy_plist {
println!("Service is not installed (no plist found).");
return Ok(());
}
stop_launchd_service(uid, LABEL, &plist);
stop_launchd_service(uid, LEGACY_LABEL, &legacy_plist);
if had_plist {
std::fs::remove_file(&plist).context("removing plist")?;
}
if had_legacy_plist {
std::fs::remove_file(&legacy_plist).context("removing legacy plist")?;
}
println!("Service stopped and removed.");
if had_plist {
println!(" removed: {}", plist.display());
}
if had_legacy_plist {
println!(" removed: {}", legacy_plist.display());
}
Ok(())
}
fn uninstall_systemd() -> Result<()> {
let unit = unit_path();
if !unit.exists() {
println!("Service is not installed (no unit file found).");
return Ok(());
}
let _ = std::process::Command::new("systemctl")
.args(["--user", "disable", "--now", UNIT_NAME])
.output();
std::fs::remove_file(&unit).context("removing unit file")?;
let _ = std::process::Command::new("systemctl")
.args(["--user", "daemon-reload"])
.output();
println!("Service stopped and removed.");
println!(" removed: {}", unit.display());
Ok(())
}
fn uninstall_task_scheduler() -> Result<()> {
if !task_scheduler_installed() {
println!("Service is not installed (no scheduled task found).");
return Ok(());
}
let _ = std::process::Command::new("schtasks")
.args(["/end", "/tn", TASK_NAME])
.output();
let delete = std::process::Command::new("schtasks")
.args(["/delete", "/tn", TASK_NAME, "/f"])
.output()
.context("running schtasks /delete")?;
if !delete.status.success() {
let stderr = String::from_utf8_lossy(&delete.stderr);
anyhow::bail!("schtasks /delete failed: {stderr}");
}
let xml_path = task_xml_path();
if xml_path.exists() {
let _ = std::fs::remove_file(&xml_path);
}
println!("Service stopped and removed.");
println!(" task: {TASK_NAME}");
Ok(())
}
pub fn status(json: bool) -> Result<()> {
let config = crate::config::Config::load().ok();
let service_path = service_file_path();
let installed_service_path = service_path
.as_ref()
.and_then(|path| {
if cfg!(windows) {
task_scheduler_installed().then(|| path.clone())
} else {
path.exists().then(|| path.clone())
}
})
.or_else(|| {
if cfg!(target_os = "macos") {
let legacy_path = legacy_plist_path();
legacy_path.exists().then_some(legacy_path)
} else {
None
}
});
let legacy_service_installed = installed_service_path
.as_ref()
.and_then(|path| path.file_name().and_then(|name| name.to_str()))
== Some(LEGACY_PLIST_NAME);
if !json {
println!("{}\n", term::heading("kache · daemon"));
println!("{}", term::heading("Status"));
println!(
" kache: v{} (epoch {})",
crate::VERSION,
crate::daemon::build_epoch(),
);
for line in format_service_line(
installed_service_path.as_deref(),
legacy_service_installed,
service_path.is_some(),
) {
println!("{line}");
}
}
let running = if let Some(ref cfg) = config {
crate::transport::is_reachable(&cfg.socket_path())
} else {
false
};
let daemon_stats = if running {
config
.as_ref()
.and_then(|cfg| crate::daemon::send_stats_request_without_restart(cfg, false).ok())
} else {
None
};
let exe_problem = installed_service_path
.as_deref()
.and_then(service_exe_problem);
if json {
#[derive(serde::Serialize)]
struct Body {
version: &'static str,
epoch: u64,
daemon_running: bool,
service_installed: bool,
service_path: Option<String>,
socket: Option<String>,
daemon_version: Option<String>,
daemon_epoch: Option<u64>,
daemon_config_path: Option<String>,
startup_log: Option<String>,
service_executable_mismatch: bool,
}
return crate::machine::emit(
"daemon-status",
Body {
version: crate::VERSION,
epoch: crate::daemon::build_epoch(),
daemon_running: running,
service_installed: installed_service_path.is_some(),
service_path: installed_service_path
.as_ref()
.map(|path| path.display().to_string()),
socket: config
.as_ref()
.map(|cfg| cfg.socket_path().display().to_string()),
daemon_version: daemon_stats.as_ref().map(|stats| stats.version.clone()),
daemon_epoch: daemon_stats.as_ref().map(|stats| stats.build_epoch),
daemon_config_path: daemon_stats
.as_ref()
.and_then(|stats| stats.effective_config.as_ref())
.map(|config| config.config_path.clone()),
startup_log: config.as_ref().map(|cfg| startup_log(cfg).to_string()),
service_executable_mismatch: exe_problem.is_some(),
},
if running {
Vec::new()
} else {
vec![crate::machine::NextAction {
argv: vec!["kache".into(), "daemon".into(), "start".into()],
why: "daemon is not running".into(),
}]
},
);
}
if running {
println!(" Daemon: \x1b[32mrunning\x1b[0m");
} else {
println!(" Daemon: \x1b[31mnot running\x1b[0m");
}
println!("\n{}", term::heading("Runtime"));
if let Some(ref cfg) = config {
println!(" Socket: {}", cfg.socket_path().display());
}
let diag = crate::diagnostic_log_path();
if diag.exists() {
println!(" Logs: {}", diag.display());
} else if cfg!(target_os = "macos") {
println!(" Logs: {}", log_dir().join("err.log").display());
} else if cfg!(target_os = "linux") {
println!(" Logs: journalctl --user -u {UNIT_NAME}");
}
if let Some(ref cfg) = config {
println!(
" Startup: {} (why a daemon failed to start)",
startup_log(cfg)
);
}
if let Some(stats) = daemon_stats.as_ref() {
for line in format_version_status(&stats.version, crate::VERSION) {
println!("{line}");
}
if let Some(line) = format_config_status(
stats
.effective_config
.as_ref()
.map(|effective| effective.config_path.as_str()),
) {
println!("{line}");
}
}
if let Some(problem) = exe_problem {
println!();
println!(" \x1b[33mWarning: {}\x1b[0m", problem.detail());
println!(" run `kache daemon install` to update");
}
println!();
Ok(())
}
fn format_config_status(config_path: Option<&str>) -> Option<String> {
config_path.map(|path| format!(" Config: {path} (loaded by the daemon)"))
}
fn format_service_line(
installed_path: Option<&Path>,
legacy: bool,
supported: bool,
) -> Vec<String> {
if let Some(path) = installed_path {
let mut lines = term::rows(&[(
"Service",
term::paint("installed", term::Style::Success),
term::home_path(path),
)]);
if legacy {
lines.push(format!(
" \x1b[33mlegacy label detected — run `kache daemon install` to migrate to {LABEL}\x1b[0m"
));
}
lines
} else if supported {
term::rows(&[
(
"Service",
term::paint("not installed", term::Style::Warning),
String::new(),
),
(
"",
String::new(),
"run `kache daemon install` to set up".into(),
),
])
} else {
term::rows(&[(
"Service",
term::paint("unsupported platform", term::Style::Muted),
String::new(),
)])
}
}
fn format_version_status(daemon_version: &str, my_version: &str) -> Vec<String> {
let (label, style, note) =
match crate::daemon::compare_daemon_version(my_version, daemon_version) {
Some(std::cmp::Ordering::Equal) => (
format!("v{daemon_version}"),
term::Style::Success,
String::new(),
),
Some(std::cmp::Ordering::Greater) => (
format!("v{daemon_version}"),
term::Style::Warning,
format!("binary is v{my_version}; upgrade available"),
),
Some(std::cmp::Ordering::Less) => (
format!("v{daemon_version}"),
term::Style::Success,
format!("binary is v{my_version}; newer daemon retained"),
),
None => (
"unknown".into(),
term::Style::Warning,
"automatic upgrade disabled".into(),
),
};
term::rows(&[("Version", term::paint(label, style), note)])
}
pub(crate) fn parse_exe_from_service_file(path: &std::path::Path) -> Option<PathBuf> {
let content = std::fs::read_to_string(path).ok()?;
if cfg!(target_os = "macos") {
let after_prog = content.split("ProgramArguments").nth(1)?;
let start = after_prog.find("<string>")? + "<string>".len();
let end = after_prog[start..].find("</string>")? + start;
Some(PathBuf::from(after_prog[start..end].trim()))
} else if cfg!(windows) {
let args_start = content.find("<Arguments>")? + "<Arguments>".len();
let args_end = content[args_start..].find("</Arguments>")? + args_start;
let args = content[args_start..args_end].trim();
let exe = args
.strip_prefix("--headless ")?
.split("\" ")
.next()?
.trim_matches('"');
Some(PathBuf::from(exe))
} else {
for line in content.lines() {
if let Some(rest) = line.strip_prefix("ExecStart=") {
let exe = rest.split_whitespace().next()?;
return Some(PathBuf::from(exe));
}
}
None
}
}
pub fn log() -> Result<()> {
if cfg!(target_os = "macos") {
let diag_log = crate::diagnostic_log_path();
let err_log = log_dir().join("err.log");
let log_file = if diag_log.exists() {
diag_log
} else if err_log.exists() {
err_log
} else {
anyhow::bail!(
"no log files found in {}\nIs the service installed? Run `kache daemon install`",
log_dir().display()
);
};
eprintln!("Streaming {}", log_file.display());
let status = std::process::Command::new("tail")
.args(["-f", &log_file.display().to_string()])
.status()
.context("running tail -f")?;
std::process::exit(status.code().unwrap_or(1));
} else if cfg!(target_os = "linux") {
let status = std::process::Command::new("journalctl")
.args(["--user", "-u", UNIT_NAME, "-f"])
.status()
.context("running journalctl")?;
std::process::exit(status.code().unwrap_or(1));
} else if cfg!(windows) {
let diag_log = crate::diagnostic_log_path();
let fallback_log = crate::config::Config::load()
.map(|c| c.socket_path().with_extension("log"))
.ok();
let log_file = if diag_log.exists() {
diag_log
} else if let Some(ref fb) = fallback_log
&& fb.exists()
{
fb.clone()
} else {
anyhow::bail!("no log files found.\nIs the daemon running? Run `kache daemon start`");
};
eprintln!("Streaming {}", log_file.display());
let status = std::process::Command::new("powershell")
.args([
"-Command",
&format!("Get-Content -Wait -Tail 50 '{}'", log_file.display()),
])
.status()
.context("running powershell Get-Content -Wait")?;
std::process::exit(status.code().unwrap_or(1));
} else {
anyhow::bail!("unsupported platform");
}
}
#[cfg(test)]
mod tests {
use super::*;
use std::fs;
fn write_service_file(path: &Path, exe: &Path) {
if cfg!(target_os = "macos") {
let content = format!(
r#"<?xml version="1.0" encoding="UTF-8"?>
<plist version="1.0">
<dict>
<key>ProgramArguments</key>
<array>
<string>{}</string>
<string>daemon</string>
<string>run</string>
</array>
</dict>
</plist>"#,
exe.display()
);
fs::write(path, content).unwrap();
} else if cfg!(target_os = "linux") {
fs::write(path, format!("ExecStart={} daemon run\n", exe.display())).unwrap();
}
}
#[test]
fn a_startup_failure_is_looked_for_where_the_starter_sent_stderr() {
let runtime_log = PathBuf::from("/run/kache/daemon.log");
assert_eq!(
startup_log_for(false, runtime_log.clone()),
StartupLog::File(runtime_log.clone())
);
let managed = startup_log_for(true, runtime_log.clone());
if cfg!(target_os = "macos") {
assert_eq!(managed, StartupLog::File(log_dir().join("err.log")));
} else if cfg!(windows) {
assert_eq!(managed, StartupLog::Discarded);
} else {
assert_eq!(managed, StartupLog::Journal);
}
assert_eq!(
StartupLog::File(runtime_log.clone()).to_string(),
runtime_log.display().to_string()
);
assert_eq!(
StartupLog::Journal.to_string(),
"journalctl --user -u kache.service"
);
assert!(
StartupLog::Discarded
.to_string()
.contains("kache daemon run")
);
}
#[cfg(unix)]
#[test]
fn task_scheduler_start_distinguishes_absent_started_and_failed() {
use std::os::unix::process::ExitStatusExt;
let output = |success| std::process::Output {
status: std::process::ExitStatus::from_raw(if success { 0 } else { 256 }),
stdout: Vec::new(),
stderr: b"scheduler refused".to_vec(),
};
assert!(
!start_scheduled_task(output(false), || panic!("missing task must not start")).unwrap()
);
assert!(start_scheduled_task(output(true), || Ok(output(true))).unwrap());
let error = start_scheduled_task(output(true), || Ok(output(false))).unwrap_err();
assert!(error.to_string().contains("scheduler refused"));
assert!(
start_scheduled_task(output(true), || Err(anyhow::anyhow!("manager unavailable")))
.is_err()
);
}
#[cfg(unix)]
#[test]
fn a_launchd_job_that_is_not_loaded_is_bootstrapped_instead() {
use std::os::unix::process::ExitStatusExt;
let output = |code: i32, stderr: &str| std::process::Output {
status: std::process::ExitStatus::from_raw(code << 8),
stdout: Vec::new(),
stderr: stderr.as_bytes().to_vec(),
};
assert_eq!(LAUNCHCTL_NO_SUCH_SERVICE, 113);
let target = "gui/501/ninja.kunobi.kache";
assert!(
start_launchd_job(
target,
|| Ok(output(0, "")),
|| panic!("a loaded job is not bootstrapped")
)
.unwrap()
);
let bootstrapped = std::cell::Cell::new(false);
assert!(
start_launchd_job(
target,
|| Ok(output(LAUNCHCTL_NO_SUCH_SERVICE, "Could not find service")),
|| {
bootstrapped.set(true);
Ok(output(0, ""))
}
)
.unwrap()
);
assert!(bootstrapped.get());
let error = start_launchd_job(
target,
|| Ok(output(LAUNCHCTL_NO_SUCH_SERVICE - 1, "refused")),
|| panic!("only a missing job is bootstrapped"),
)
.unwrap_err()
.to_string();
assert!(
error.contains("kickstart") && error.contains("refused"),
"{error}"
);
let error = start_launchd_job(
target,
|| Ok(output(LAUNCHCTL_NO_SUCH_SERVICE, "")),
|| Ok(output(5, "Input/output error")),
)
.unwrap_err()
.to_string();
assert!(
error.contains("not loaded") && error.contains("Input/output error"),
"{error}"
);
}
#[test]
fn login_service_configuration_distinguishes_missing_from_unreadable() {
let root = tempfile::tempdir().unwrap();
let store = root.path().join("cache");
let file = root.path().join("config.toml");
assert!(
configured_instance_matches(&store.join("daemon.sock"), store.clone(), [file.clone()])
.unwrap()
);
std::fs::create_dir(&file).unwrap();
let error =
configured_instance_matches(&store.join("daemon.sock"), store, [file]).unwrap_err();
assert!(
error
.to_string()
.contains("reading login service configuration")
);
}
#[test]
fn login_service_configuration_does_not_claim_another_cache_instance() {
let root = tempfile::tempdir().unwrap();
let store = root.path().join("cache");
let configured = root.path().join("runtime");
let file = root.path().join("config.toml");
let text = format!(
"[cache]\nruntime_dir = {:?}\n",
configured.to_str().unwrap()
);
std::fs::write(&file, text).unwrap();
assert!(
configured_instance_matches(&store.join("daemon.sock"), store.clone(), []).unwrap()
);
assert!(
!configured_instance_matches(&configured.join("daemon.sock"), store.clone(), [])
.unwrap()
);
assert!(
configured_instance_matches(
&configured.join("daemon.sock"),
store.clone(),
[file.clone()]
)
.unwrap()
);
assert!(
!configured_instance_matches(&store.join("daemon.sock"), store.clone(), [file.clone()])
.unwrap()
);
std::fs::write(&file, "malformed = [").unwrap();
assert!(configured_instance_matches(&store.join("daemon.sock"), store, [file]).is_err());
}
#[cfg(unix)]
#[test]
fn manager_command_deadline_bounds_wait_and_caps_diagnostics() {
use std::{
process::Command,
time::{Duration, Instant},
};
let error = command_output_until(
Command::new("sh").args(["-c", "exec sleep 30"]),
Instant::now() + Duration::from_millis(100),
)
.unwrap_err();
assert!(error.to_string().contains("timed out"));
let output = command_output_until(
Command::new("sh").args(["-c", "head -c 20000 /dev/zero >&2; exit 3"]),
Instant::now() + Duration::from_secs(5),
)
.unwrap();
assert_eq!(output.status.code(), Some(3));
assert_eq!(output.stderr.len(), 16_384);
assert!(output.stdout.is_empty());
assert!(
command_output_until(Command::new("sh").arg("-c"), Instant::now())
.unwrap_err()
.to_string()
.contains("expired")
);
}
#[test]
fn test_plist_path() {
let p = plist_path();
assert!(p.to_string_lossy().contains("LaunchAgents"));
assert!(p.to_string_lossy().contains(PLIST_NAME));
}
#[test]
fn test_unit_path() {
let p = unit_path();
assert!(p.to_string_lossy().contains("systemd/user"));
assert!(p.to_string_lossy().contains(UNIT_NAME));
}
#[test]
fn test_service_file_path_returns_some() {
let result = service_file_path();
if cfg!(target_os = "macos") || cfg!(target_os = "linux") {
assert!(result.is_some());
}
}
#[test]
fn test_log_dir() {
let d = log_dir();
assert!(d.to_string_lossy().contains("Logs/kache"));
}
#[test]
fn test_parse_exe_from_plist() {
let dir = tempfile::tempdir().unwrap();
let plist_file = dir.path().join("test.plist");
let content = r#"<?xml version="1.0" encoding="UTF-8"?>
<plist version="1.0">
<dict>
<key>Label</key>
<string>ninja.kunobi.kache</string>
<key>ProgramArguments</key>
<array>
<string>/usr/local/bin/kache</string>
<string>daemon</string>
<string>run</string>
</array>
</dict>
</plist>"#;
fs::write(&plist_file, content).unwrap();
if cfg!(target_os = "macos") {
let exe = parse_exe_from_service_file(&plist_file);
assert_eq!(exe, Some(PathBuf::from("/usr/local/bin/kache")));
}
}
#[test]
fn test_parse_exe_from_systemd_unit() {
let dir = tempfile::tempdir().unwrap();
let unit_file = dir.path().join("kache.service");
let content = r#"[Unit]
Description=kache build cache daemon
[Service]
Type=simple
ExecStart=/home/user/.cargo/bin/kache daemon run
Restart=on-failure
[Install]
WantedBy=default.target
"#;
fs::write(&unit_file, content).unwrap();
if cfg!(target_os = "linux") {
let exe = parse_exe_from_service_file(&unit_file);
assert_eq!(exe, Some(PathBuf::from("/home/user/.cargo/bin/kache")));
}
}
#[test]
fn test_parse_exe_from_nonexistent_file() {
let result = parse_exe_from_service_file(std::path::Path::new("/nonexistent/path"));
assert!(result.is_none());
}
#[test]
fn test_parse_exe_from_empty_file() {
let dir = tempfile::tempdir().unwrap();
let file = dir.path().join("empty");
fs::write(&file, "").unwrap();
let result = parse_exe_from_service_file(&file);
assert!(result.is_none());
}
#[test]
fn test_service_exe_problem_accepts_current_exe() {
if !(cfg!(target_os = "macos") || cfg!(target_os = "linux")) {
return;
}
let dir = tempfile::tempdir().unwrap();
let service_file = dir.path().join("service");
let current = std::env::current_exe().unwrap();
write_service_file(&service_file, ¤t);
assert_eq!(service_exe_problem(&service_file), None);
}
#[test]
fn test_service_exe_problem_detects_a_removed_exe() {
if !(cfg!(target_os = "macos") || cfg!(target_os = "linux")) {
return;
}
let dir = tempfile::tempdir().unwrap();
let service_file = dir.path().join("service");
let stale = dir.path().join("old-kache");
write_service_file(&service_file, &stale);
assert_eq!(
service_exe_problem(&service_file),
Some(ServiceExeProblem::Missing { recorded: stale })
);
}
#[cfg(unix)]
#[test]
fn recorded_exe_problems_are_told_apart() {
use std::os::unix::fs::PermissionsExt;
let dir = tempfile::tempdir().unwrap();
let current = dir.path().join("kache");
fs::write(¤t, "#!/bin/sh\n").unwrap();
fs::set_permissions(¤t, fs::Permissions::from_mode(0o755)).unwrap();
let link = dir.path().join("opt-kache");
std::os::unix::fs::symlink(¤t, &link).unwrap();
let hardlink = dir.path().join("hardlinked-kache");
fs::hard_link(¤t, &hardlink).unwrap();
let other = dir.path().join("other-kache");
fs::copy(¤t, &other).unwrap();
let plain = dir.path().join("plain-kache");
fs::write(&plain, "").unwrap();
let real = kache_shims::RealFs;
let id = kache_shims::Fs::identity(&real, ¤t);
let check = |recorded: &Path| recorded_exe_problem(recorded, ¤t, id, &real);
assert_eq!(check(¤t), None);
assert_eq!(check(&link), None);
assert_eq!(check(&hardlink), None);
assert_eq!(
check(&other),
Some(ServiceExeProblem::OtherBinary {
recorded: other.clone(),
current: current.clone()
})
);
assert_eq!(
check(&plain),
Some(ServiceExeProblem::NotExecutable {
recorded: plain.clone()
})
);
let gone = dir.path().join("gone");
assert_eq!(
check(&gone),
Some(ServiceExeProblem::Missing {
recorded: gone.clone()
})
);
assert_eq!(
recorded_exe_problem(¤t, &gone, None, &real),
Some(ServiceExeProblem::Unverifiable {
recorded: current.clone(),
current: gone.clone()
})
);
}
#[test]
fn service_exe_problems_name_the_recorded_path() {
let recorded = PathBuf::from("/nix/store/abc-kache/bin/kache");
assert_eq!(
ServiceExeProblem::Missing {
recorded: recorded.clone()
}
.detail(),
"the service runs /nix/store/abc-kache/bin/kache, which no longer exists"
);
assert_eq!(
ServiceExeProblem::NotExecutable {
recorded: recorded.clone()
}
.detail(),
"the service runs /nix/store/abc-kache/bin/kache, which is not executable"
);
assert_eq!(
ServiceExeProblem::OtherBinary {
recorded,
current: "/usr/bin/kache".into()
}
.detail(),
"the service runs /nix/store/abc-kache/bin/kache, a different file from this kache (/usr/bin/kache)"
);
assert_eq!(
ServiceExeProblem::Unverifiable {
recorded: "/usr/bin/kache".into(),
current: "/usr/bin/kache (deleted)".into()
}
.detail(),
"the service runs /usr/bin/kache, which cannot be compared with this kache (/usr/bin/kache (deleted)): its file cannot be read"
);
}
#[test]
fn install_reports_the_recorded_path_and_warns_when_it_is_versioned() {
let mut selection = kache_shims::Selection {
path: "/opt/homebrew/opt/kache/bin/kache".into(),
kind: kache_shims::Kind::Homebrew,
stability: kache_shims::Stability::InstallerManaged,
reason: "Homebrew's opt link for kache, which each upgrade repoints".into(),
};
assert_eq!(
selection_lines(&selection),
["Recording /opt/homebrew/opt/kache/bin/kache (Homebrew, installer-managed)."]
);
selection.stability = kache_shims::Stability::Versioned;
let lines = selection_lines(&selection);
assert_eq!(lines.len(), 2);
assert!(
lines[1].contains("Rerun `kache daemon install`"),
"{lines:?}"
);
selection.stability = kache_shims::Stability::Unverified;
let lines = selection_lines(&selection);
assert_eq!(lines.len(), 2);
assert!(lines[1].starts_with(" Warning: "), "{lines:?}");
assert!(
lines[1].contains("may run as a user who cannot reach"),
"{lines:?}"
);
}
#[test]
fn test_label_constant() {
assert_eq!(LABEL, "ninja.kunobi.kache");
}
#[test]
fn test_plist_name_constant() {
assert_eq!(PLIST_NAME, "ninja.kunobi.kache.plist");
}
#[test]
fn test_unit_name_constant() {
assert_eq!(UNIT_NAME, "kache.service");
}
#[test]
fn test_launchd_plist_content_includes_exe_and_logs() {
let content = launchd_plist_content(
std::path::Path::new("/opt/kache/bin/kache"),
std::path::Path::new("/var/log/kache/out.log"),
std::path::Path::new("/var/log/kache/err.log"),
);
assert!(content.contains(&format!("<string>{LABEL}</string>")));
assert!(content.contains("/opt/kache/bin/kache"));
assert!(content.contains("<string>daemon</string>"));
assert!(content.contains("<string>run</string>"));
assert!(content.contains("AssociatedBundleIdentifiers"));
assert!(content.contains(&format!("<string>{MACOS_BUNDLE_IDENTIFIER}</string>")));
assert!(content.contains("/var/log/kache/out.log"));
assert!(content.contains("/var/log/kache/err.log"));
assert!(content.contains("RunAtLoad"));
}
#[test]
fn test_embedded_macos_info_plist_matches_launchd_identity() {
let info = include_str!("../assets/macos/Info.plist");
assert!(info.contains(&format!("<string>{MACOS_BUNDLE_IDENTIFIER}</string>")));
assert!(info.contains("NSLocalNetworkUsageDescription"));
assert!(info.contains("build-cache servers configured on your local network"));
}
#[test]
fn test_systemd_unit_content_runs_daemon() {
let content = systemd_unit_content(std::path::Path::new("/opt/kache/bin/kache"));
assert!(content.contains("ExecStart=/opt/kache/bin/kache daemon run"));
assert!(content.contains("Restart=on-failure"));
assert!(content.contains("WantedBy=default.target"));
assert!(content.contains("KACHE_LOG=kache=info"));
}
#[test]
fn test_format_service_line_all_states() {
let installed = format_service_line(Some(Path::new("/x/kache.plist")), false, true);
assert_eq!(installed.len(), 1);
assert!(installed[0].contains("installed") && installed[0].contains("/x/kache.plist"));
let legacy = format_service_line(Some(Path::new("/x/old.plist")), true, true);
assert_eq!(legacy.len(), 2);
assert!(legacy[1].contains("legacy label detected"));
let not = format_service_line(None, false, true);
assert_eq!(not.len(), 2);
assert!(not[0].contains("not installed"));
assert!(not[1].contains("kache daemon install"));
let unsup = format_service_line(None, false, false);
assert_eq!(unsup.len(), 1);
assert!(unsup[0].contains("unsupported platform"));
}
#[test]
fn version_status_reports_releases_without_pending_restart_claims() {
for (daemon, expected) in [
("", "unknown"),
("1.2.3+build.2", "v1.2.3+build.2"),
("1.2.2", "upgrade available"),
("1.3.0", "newer daemon retained"),
] {
let lines = format_version_status(daemon, "1.2.3+build.1");
assert_eq!(lines.len(), 1);
assert!(lines[0].contains(expected), "{lines:?}");
assert!(!lines[0].contains("restart is pending"));
}
}
#[test]
fn test_format_config_status_requires_a_daemon_report() {
assert_eq!(
format_config_status(Some("/daemon/config.toml")).as_deref(),
Some(" Config: /daemon/config.toml (loaded by the daemon)")
);
assert!(format_config_status(None).is_none());
}
#[test]
fn test_task_scheduler_xml_content_runs_daemon_via_conhost() {
let content = task_scheduler_xml_content(
std::path::Path::new("C:/Program Files/kache/kache.exe"),
"alice",
);
assert!(content.contains(r#"--headless "C:\Program Files\kache\kache.exe" daemon run"#));
assert!(content.contains("<Command>conhost.exe</Command>"));
assert!(content.contains("<UserId>alice</UserId>"));
assert!(content.contains("<LogonTrigger>"));
assert!(content.contains("<RestartOnFailure>"));
assert!(content.contains(r#"encoding="UTF-16""#));
}
}