use std::path::PathBuf;
pub type DispatchFn = fn();
#[derive(Debug)]
pub enum StartStopError {
NoSystemCtl,
SystemCtlFailed,
}
#[macro_export]
macro_rules! ServiceMacro {
($entry:ident, $function:ident, $t:ident) => {
fn $entry() {
$function(None, None);
}
};
}
#[macro_export]
macro_rules! DispatchAsync {
($self:ident, $function:ident) => {{
$function().await;
let r: Result<(), u32> = Ok(());
r
}};
}
#[cfg(feature = "async")]
#[macro_export]
macro_rules! ServiceAsyncMacro {
($entry:ident, $function:ident, $t:ident) => {
async fn $entry() {
$function().await;
}
};
}
#[derive(Debug)]
pub enum CreateError {
NoSystemCtl,
SystemCtlFailed,
SystemCtlReloadFailed,
FileIoError(std::io::Error),
}
impl From<StartStopError> for CreateError {
fn from(value: StartStopError) -> Self {
match value {
StartStopError::NoSystemCtl => Self::NoSystemCtl,
StartStopError::SystemCtlFailed => Self::SystemCtlFailed,
}
}
}
#[derive(Debug)]
pub struct Session(String);
pub struct ServiceConfig {
arguments: Vec<String>,
description: String,
binary: PathBuf,
username: Option<String>,
pub config_path: PathBuf,
}
impl ServiceConfig {
pub fn new(
arguments: Vec<String>,
description: String,
binary: PathBuf,
username: Option<String>,
) -> Self {
Self {
arguments,
description,
binary,
config_path: PathBuf::new(),
username,
}
}
}
pub struct Service {
name: String,
}
impl Service {
pub fn new(name: String) -> Self {
Self { name }
}
pub fn new_log(&self, level: super::LogLevel) {
simple_logger::SimpleLogger::new().with_level(level.level_filter()).init().unwrap();
}
pub fn systemd_path(&self) -> PathBuf {
PathBuf::from("/etc/systemd/system")
}
pub fn exists(&self) -> bool {
let systemd_path = self.systemd_path();
let pb = systemd_path.join(format!("{}.service", self.name));
pb.exists()
}
pub fn stop(&mut self) -> Result<(), StartStopError> {
let o = std::process::Command::new("systemctl")
.arg("stop")
.arg(&self.name)
.output()
.map_err(|_| StartStopError::NoSystemCtl)?;
if !o.status.success() {
Err(StartStopError::SystemCtlFailed)
} else {
Ok(())
}
}
pub fn start(&mut self) -> Result<(), StartStopError> {
let o = std::process::Command::new("systemctl")
.arg("start")
.arg(&self.name)
.output()
.map_err(|_| StartStopError::NoSystemCtl)?;
if !o.status.success() {
Err(StartStopError::SystemCtlFailed)
} else {
Ok(())
}
}
pub fn delete(&mut self) -> Result<(), std::io::Error> {
let pb = self.systemd_path().join(format!("{}.service", self.name));
println!("Deleting {}", pb.display());
std::fs::remove_file(pb)
}
#[cfg(feature = "async")]
pub async fn delete_async(&mut self) -> Result<(), std::io::Error> {
let pb = self.systemd_path().join(format!("{}.service", self.name));
println!("Deleting {}", pb.display());
tokio::fs::remove_file(pb).await
}
fn reload(&mut self) -> Result<(), StartStopError> {
let o = std::process::Command::new("systemctl")
.arg("daemon-reload")
.output()
.map_err(|_| StartStopError::NoSystemCtl)?;
if !o.status.success() {
Err(StartStopError::SystemCtlFailed)
} else {
Ok(())
}
}
fn build_systemd_file(&self, config: ServiceConfig) -> String {
let mut con = String::new();
con.push_str("[Unit]\n");
con.push_str(&format!("Description={}\n", config.description));
con.push_str("[Service]\n");
if let Some(user) = config.username {
con.push_str(&format!("User={}\n", user));
}
con.push_str(&format!(
"WorkingDirectory={}\n",
config.config_path.display()
));
con.push_str(&format!(
"ExecStart={} {}\n",
config.binary.display(),
config.arguments.join(" ")
));
con.push_str("\n[Install]\nWantedBy=multi-user.target\n");
con
}
pub fn create(&mut self, config: ServiceConfig) -> Result<(), CreateError> {
use std::io::Write;
let con = self.build_systemd_file(config);
let pb = self.systemd_path().join(format!("{}.service", self.name));
println!("Saving service file as {}", pb.display());
let mut fpw = std::fs::File::create(pb).map_err(CreateError::FileIoError)?;
fpw.write_all(con.as_bytes())
.map_err(CreateError::FileIoError)?;
Ok(self.reload()?)
}
#[cfg(feature = "async")]
pub async fn create_async(&mut self, config: ServiceConfig) -> Result<(), CreateError> {
use tokio::io::AsyncWriteExt;
let con = self.build_systemd_file(config);
let pb = self.systemd_path().join(format!("{}.service", self.name));
println!("Saving service file as {}", pb.display());
let mut fpw = tokio::fs::File::create(pb)
.await
.map_err(CreateError::FileIoError)?;
fpw.write_all(con.as_bytes())
.await
.map_err(CreateError::FileIoError)?;
Ok(self.reload()?)
}
pub fn dispatch(&self, service_main: DispatchFn) -> Result<(), u32> {
service_main();
Ok(())
}
}