#[cfg(windows)]
pub mod winsvc;
#[cfg(target_os = "macos")]
pub mod launchd;
#[cfg(all(target_os = "linux", feature = "systemd"))]
#[cfg_attr(
docsrs,
doc(cfg(all(all(target_os = "linux", feature = "installer"))))
)]
pub mod systemd;
#[cfg(feature = "clap")]
use clap::ArgMatches;
use itertools::Itertools;
use crate::{err::Error, lumberjack::LogLevel};
#[derive(Default)]
pub enum Account {
#[default]
System,
#[cfg(windows)]
#[cfg_attr(docsrs, doc(cfg(windows)))]
Service,
#[cfg(windows)]
#[cfg_attr(docsrs, doc(cfg(windows)))]
Network,
#[cfg(unix)]
User(String),
#[cfg(windows)]
UserAndPass(String, String)
}
#[derive(Debug, Default)]
pub struct RunAs {
user: Option<String>,
group: Option<String>,
#[cfg(target_os = "macos")]
initgroups: bool,
#[cfg(any(
target_os = "macos",
all(target_os = "linux", feature = "systemd")
))]
umask: Option<String>
}
#[cfg(windows)]
pub type BoxRegCb =
Box<dyn FnOnce(&str, &mut windows_registry::Key) -> Result<(), Error>>;
#[allow(clippy::struct_excessive_bools)]
pub struct RegSvc {
pub force: bool,
pub qsu_argp: bool,
pub svcname: String,
pub display_name: Option<String>,
pub description: Option<String>,
pub conf_reload: bool,
pub netservice: bool,
#[cfg(windows)]
pub regconf: Option<BoxRegCb>,
pub args: Vec<String>,
pub envs: Vec<(String, String)>,
pub autostart: bool,
pub(crate) workdir: Option<String>,
deps: Vec<Depend>,
log_level: Option<LogLevel>,
log_filter: Option<String>,
trace_filter: Option<String>,
trace_file: Option<String>,
runas: RunAs
}
pub enum Depend {
Network,
Custom(Vec<String>)
}
impl RegSvc {
#[must_use]
pub fn new(svcname: &str) -> Self {
Self {
force: false,
qsu_argp: false,
svcname: svcname.to_string(),
display_name: None,
description: None,
conf_reload: false,
netservice: false,
#[cfg(windows)]
regconf: None,
args: Vec::new(),
envs: Vec::new(),
autostart: false,
workdir: None,
deps: Vec::new(),
log_level: None,
log_filter: None,
trace_filter: None,
trace_file: None,
runas: RunAs::default()
}
}
#[cfg(feature = "clap")]
#[allow(clippy::missing_panics_doc)]
pub fn from_cmd_match(matches: &ArgMatches) -> Self {
let force = matches.get_flag("force");
let svcname = matches.get_one::<String>("svcname").unwrap().to_owned();
let autostart = matches.get_flag("auto_start");
let dispname = matches.get_one::<String>("display_name");
let descr = matches.get_one::<String>("description");
let args: Vec<String> = matches
.get_many::<String>("arg")
.map_or_else(Vec::new, |vr| vr.map(String::from).collect());
let envs: Vec<String> = matches
.get_many::<String>("env")
.map_or_else(Vec::new, |vr| vr.map(String::from).collect());
let workdir = matches.get_one::<String>("workdir");
let mut environ = Vec::new();
let mut it = envs.into_iter();
while let Some((key, value)) = it.next_tuple() {
environ.push((key, value));
}
let log_level = matches.get_one::<LogLevel>("log_level").copied();
let log_filter = matches.get_one::<String>("log_filter").cloned();
let trace_filter = matches.get_one::<String>("trace_filter").cloned();
let trace_file = matches.get_one::<String>("trace_file").cloned();
let runas = RunAs::default();
Self {
force,
qsu_argp: true,
svcname,
display_name: dispname.cloned(),
description: descr.cloned(),
conf_reload: false,
netservice: false,
#[cfg(windows)]
regconf: None,
args,
envs: environ,
autostart,
workdir: workdir.cloned(),
deps: Vec::new(),
log_level,
log_filter,
trace_filter,
trace_file,
runas
}
}
#[must_use]
pub fn svcname(&self) -> &str {
&self.svcname
}
#[must_use]
pub fn display_name(mut self, name: impl ToString) -> Self {
self.display_name_ref(name);
self
}
#[allow(clippy::needless_pass_by_value)]
pub fn display_name_ref(&mut self, name: impl ToString) -> &mut Self {
self.display_name = Some(name.to_string());
self
}
#[must_use]
pub fn description(mut self, text: impl ToString) -> Self {
self.description_ref(text);
self
}
#[allow(clippy::needless_pass_by_value)]
pub fn description_ref(&mut self, text: impl ToString) -> &mut Self {
self.description = Some(text.to_string());
self
}
#[must_use]
pub const fn conf_reload(mut self) -> Self {
self.conf_reload_ref();
self
}
pub const fn conf_reload_ref(&mut self) -> &mut Self {
self.conf_reload = true;
self
}
#[must_use]
pub fn netservice(mut self) -> Self {
self.netservice_ref();
self
}
#[cfg_attr(not(windows), allow(clippy::missing_const_for_fn))]
pub fn netservice_ref(&mut self) -> &mut Self {
self.netservice = true;
#[cfg(windows)]
self.deps.push(Depend::Network);
self
}
#[cfg(windows)]
#[cfg_attr(docsrs, doc(cfg(windows)))]
#[must_use]
pub fn regconf<F>(mut self, f: F) -> Self
where
F: FnOnce(&str, &mut windows_registry::Key) -> Result<(), Error> + 'static
{
self.regconf = Some(Box::new(f));
self
}
#[cfg(windows)]
#[cfg_attr(docsrs, doc(cfg(windows)))]
pub fn regconf_ref<F>(&mut self, f: F) -> &mut Self
where
F: FnOnce(&str, &mut windows_registry::Key) -> Result<(), Error> + 'static
{
self.regconf = Some(Box::new(f));
self
}
#[allow(clippy::needless_pass_by_value)]
#[must_use]
pub fn arg(mut self, arg: impl ToString) -> Self {
self.args.push(arg.to_string());
self
}
#[allow(clippy::needless_pass_by_value)]
pub fn arg_ref(&mut self, arg: impl ToString) -> &mut Self {
self.args.push(arg.to_string());
self
}
#[must_use]
pub fn args<I, S>(mut self, args: I) -> Self
where
I: IntoIterator<Item = S>,
S: ToString
{
for arg in args {
self.args.push(arg.to_string());
}
self
}
pub fn args_ref<I, S>(&mut self, args: I) -> &mut Self
where
I: IntoIterator<Item = S>,
S: ToString
{
for arg in args {
self.arg_ref(arg.to_string());
}
self
}
#[must_use]
pub const fn have_args(&self) -> bool {
!self.args.is_empty()
}
#[allow(clippy::needless_pass_by_value)]
#[must_use]
pub fn env<K, V>(mut self, key: K, val: V) -> Self
where
K: ToString,
V: ToString
{
self.envs.push((key.to_string(), val.to_string()));
self
}
#[allow(clippy::needless_pass_by_value)]
pub fn env_ref<K, V>(&mut self, key: K, val: V) -> &mut Self
where
K: ToString,
V: ToString
{
self.envs.push((key.to_string(), val.to_string()));
self
}
#[must_use]
pub fn envs<I, K, V>(mut self, envs: I) -> Self
where
I: IntoIterator<Item = (K, V)>,
K: ToString,
V: ToString
{
for (key, val) in envs {
self.envs.push((key.to_string(), val.to_string()));
}
self
}
pub fn envs_ref<I, K, V>(&mut self, args: I) -> &mut Self
where
I: IntoIterator<Item = (K, V)>,
K: ToString,
V: ToString
{
for (key, val) in args {
self.env_ref(key.to_string(), val.to_string());
}
self
}
#[must_use]
pub const fn have_envs(&self) -> bool {
!self.envs.is_empty()
}
#[must_use]
pub const fn autostart(mut self) -> Self {
self.autostart = true;
self
}
pub const fn autostart_ref(&mut self) -> &mut Self {
self.autostart = true;
self
}
#[allow(clippy::needless_pass_by_value)]
#[must_use]
pub fn workdir(mut self, workdir: impl ToString) -> Self {
self.workdir = Some(workdir.to_string());
self
}
#[allow(clippy::needless_pass_by_value)]
pub fn workdir_ref(&mut self, workdir: impl ToString) -> &mut Self {
self.workdir = Some(workdir.to_string());
self
}
#[must_use]
pub fn depend(mut self, dep: Depend) -> Self {
self.deps.push(dep);
self
}
pub fn depend_ref(&mut self, dep: Depend) -> &mut Self {
self.deps.push(dep);
self
}
pub fn register(self) -> Result<(), Error> {
#[cfg(windows)]
winsvc::install(self)?;
#[cfg(target_os = "macos")]
launchd::install(self)?;
#[cfg(all(target_os = "linux", feature = "systemd"))]
systemd::install(self)?;
Ok(())
}
}
#[allow(unreachable_code)]
pub fn uninstall(svcname: &str) -> Result<(), Error> {
#[cfg(windows)]
{
winsvc::uninstall(svcname)?;
return Ok(());
}
#[cfg(target_os = "macos")]
{
launchd::uninstall(svcname)?;
return Ok(());
}
#[cfg(all(target_os = "linux", feature = "systemd"))]
{
systemd::uninstall(svcname)?;
return Ok(());
}
Err(Error::Unsupported)
}