use std::path::{Path, PathBuf};
use super::common::{moadim_exe, run};
const SYSTEMD_UNIT_NAME: &str = "moadim.service";
pub(super) fn systemctl_bin() -> String {
if let Ok(bin) = std::env::var("MOADIM_SYSTEMCTL_BIN") {
return bin;
}
#[cfg(test)]
let fallback = "/nonexistent/moadim-test-systemctl-guard".to_string();
#[cfg(not(test))]
let fallback = "systemctl".to_string();
fallback
}
pub(super) fn loginctl_bin() -> String {
if let Ok(bin) = std::env::var("MOADIM_LOGINCTL_BIN") {
return bin;
}
#[cfg(test)]
let fallback = "/nonexistent/moadim-test-loginctl-guard".to_string();
#[cfg(not(test))]
let fallback = "loginctl".to_string();
fallback
}
const LINGER_MARKER_NAME: &str = ".moadim-linger-enabled";
pub(super) fn linger_marker_path(unit: &Path) -> Option<PathBuf> {
unit.parent().map(|dir| dir.join(LINGER_MARKER_NAME))
}
pub(super) fn unit_path() -> anyhow::Result<PathBuf> {
unit_path_from_config_dir(dirs::config_dir())
}
pub(super) fn unit_path_from_config_dir(config_dir: Option<PathBuf>) -> anyhow::Result<PathBuf> {
let base =
config_dir.ok_or_else(|| anyhow::anyhow!("could not determine the config directory"))?;
Ok(base.join("systemd/user").join(SYSTEMD_UNIT_NAME))
}
pub(super) fn render_unit(exe: &Path) -> String {
format!(
"[Unit]\n\
Description=moadim routine scheduler / MCP/REST daemon\n\
After=network.target\n\
\n\
[Service]\n\
Type=simple\n\
ExecStart={exe} --interactive\n\
Restart=on-failure\n\
RestartSec=5\n\
\n\
[Install]\n\
WantedBy=default.target\n",
exe = exe.display(),
)
}
pub(super) fn write_unit(unit: &Path, exe: &Path) -> anyhow::Result<()> {
if let Some(dir) = unit.parent() {
std::fs::create_dir_all(dir)?;
}
std::fs::write(unit, render_unit(exe))?;
Ok(())
}
fn enable_unit() -> anyhow::Result<()> {
let systemctl = systemctl_bin();
run(&systemctl, &["--user", "daemon-reload"])?;
run(
&systemctl,
&["--user", "enable", "--now", SYSTEMD_UNIT_NAME],
)
}
fn report_installed(unit: &Path) {
println!("moadim installed as a systemd user service ({SYSTEMD_UNIT_NAME})");
println!(" unit {}", unit.display());
println!(" status systemctl --user status {SYSTEMD_UNIT_NAME}");
}
fn enable_linger(unit: &Path) {
if run(&loginctl_bin(), &["enable-linger"]).is_ok() {
if let Some(marker) = linger_marker_path(unit) {
let _ = std::fs::write(&marker, "");
}
println!(" linger enabled (survives logout and starts at boot)");
} else {
println!(" linger NOT enabled — service stops at logout, won't start at boot");
println!(" run `loginctl enable-linger` as this user for persistence");
}
}
pub(super) fn disable_linger_if_owned(unit: &Path) {
let Some(marker) = linger_marker_path(unit) else {
return;
};
if !marker.exists() {
return;
}
let _ = run(&loginctl_bin(), &["disable-linger"]);
let _ = std::fs::remove_file(&marker);
}
pub fn install() -> anyhow::Result<()> {
let exe = moadim_exe()?;
let unit = unit_path()?;
write_unit(&unit, &exe)?;
enable_unit()?;
report_installed(&unit);
enable_linger(&unit);
Ok(())
}
pub fn uninstall() -> anyhow::Result<()> {
let unit = unit_path()?;
if unit.exists() {
let systemctl = systemctl_bin();
let _ = run(
&systemctl,
&["--user", "disable", "--now", SYSTEMD_UNIT_NAME],
);
disable_linger_if_owned(&unit);
std::fs::remove_file(&unit)?;
let _ = run(&systemctl, &["--user", "daemon-reload"]);
println!("moadim systemd user service removed ({})", unit.display());
} else {
println!(
"moadim systemd user service is not installed (no unit at {})",
unit.display()
);
}
Ok(())
}