use anyhow::{Context, Result};
use std::os::unix::fs::PermissionsExt;
use std::path::PathBuf;
use std::process::Command;
const SERVICE_NAME: &str = "atradio";
const BIN_PLACEHOLDER: &str = "%%ATRADIO_BIN%%";
#[cfg(target_os = "freebsd")]
const SCRIPT_TEMPLATE: &str = include_str!("atradio.freebsd.rc");
#[cfg(target_os = "netbsd")]
const SCRIPT_TEMPLATE: &str = include_str!("atradio.netbsd.rc");
fn script_path() -> PathBuf {
#[cfg(target_os = "freebsd")]
let dir = "/usr/local/etc/rc.d";
#[cfg(target_os = "netbsd")]
let dir = "/etc/rc.d";
PathBuf::from(dir).join(SERVICE_NAME)
}
fn run(program: &str, args: &[&str]) -> Result<()> {
Command::new(program)
.args(args)
.status()
.with_context(|| format!("failed to run `{program}`"))?;
Ok(())
}
pub fn install() -> Result<()> {
let path = script_path();
if path.exists() {
println!(
"Service already installed at {}. Nothing to do.",
path.display()
);
return Ok(());
}
let exe = std::env::current_exe().context("could not resolve the atradio binary path")?;
let script = SCRIPT_TEMPLATE.replace(BIN_PLACEHOLDER, &exe.display().to_string());
std::fs::write(&path, script).with_context(|| {
format!(
"could not write {} — installing a system service needs root; try `sudo atradio service install`",
path.display()
)
})?;
std::fs::set_permissions(&path, std::fs::Permissions::from_mode(0o755))
.with_context(|| format!("could not make {} executable", path.display()))?;
enable_and_start()?;
println!("✓ atradio service installed and started.");
Ok(())
}
pub fn status() -> Result<()> {
let path = script_path();
if !path.exists() {
println!("atradio service is not installed. Run `atradio service install`.");
return Ok(());
}
run(&path.display().to_string(), &["status"])
}
pub fn uninstall() -> Result<()> {
let path = script_path();
if !path.exists() {
println!("atradio service is not installed. Nothing to do.");
return Ok(());
}
let _ = run(&path.display().to_string(), &["stop"]);
disable()?;
std::fs::remove_file(&path).with_context(|| {
format!(
"could not remove {} — try `sudo atradio service uninstall`",
path.display()
)
})?;
println!("✓ atradio service uninstalled.");
Ok(())
}
#[cfg(target_os = "freebsd")]
fn enable_and_start() -> Result<()> {
run("sysrc", &[&format!("{SERVICE_NAME}_enable=YES")])?;
run("service", &[SERVICE_NAME, "start"])?;
Ok(())
}
#[cfg(target_os = "freebsd")]
fn disable() -> Result<()> {
run("sysrc", &["-x", &format!("{SERVICE_NAME}_enable")])
}
#[cfg(target_os = "netbsd")]
fn enable_and_start() -> Result<()> {
set_rc_conf_enabled(true)?;
run(&script_path().display().to_string(), &["start"])
}
#[cfg(target_os = "netbsd")]
fn disable() -> Result<()> {
set_rc_conf_enabled(false)
}
#[cfg(target_os = "netbsd")]
fn set_rc_conf_enabled(enabled: bool) -> Result<()> {
const RC_CONF: &str = "/etc/rc.conf";
let existing = std::fs::read_to_string(RC_CONF).unwrap_or_default();
let line = format!("{SERVICE_NAME}=YES");
let mut kept: Vec<&str> = existing
.lines()
.filter(|l| {
l.trim_start() != line && !l.trim_start().starts_with(&format!("{SERVICE_NAME}="))
})
.collect();
if enabled {
kept.push(&line);
}
let mut out = kept.join("\n");
if !out.is_empty() {
out.push('\n');
}
std::fs::write(RC_CONF, out).with_context(|| {
format!("could not update {RC_CONF} — try `sudo atradio service install`")
})?;
Ok(())
}