1#[cfg(windows)]
4pub mod winsvc;
5
6#[cfg(target_os = "macos")]
7pub mod launchd;
8
9#[cfg(all(target_os = "linux", feature = "systemd"))]
10#[cfg_attr(
11 docsrs,
12 doc(cfg(all(all(target_os = "linux", feature = "installer"))))
13)]
14pub mod systemd;
15
16#[cfg(feature = "clap")]
19use clap::ArgMatches;
20
21use itertools::Itertools;
22
23use crate::{err::Error, lumberjack::LogLevel};
24
25
26#[derive(Default)]
110pub enum Account {
111 #[default]
116 System,
117
118 #[cfg(windows)]
120 #[cfg_attr(docsrs, doc(cfg(windows)))]
121 Service,
122
123 #[cfg(windows)]
125 #[cfg_attr(docsrs, doc(cfg(windows)))]
126 Network,
127
128 #[cfg(unix)]
129 User(String),
130
131 #[cfg(windows)]
132 UserAndPass(String, String)
133}
134
135
136#[derive(Debug, Default)]
137pub struct RunAs {
138 user: Option<String>,
139 group: Option<String>,
140
141 #[cfg(target_os = "macos")]
142 initgroups: bool,
143
144 #[cfg(any(
145 target_os = "macos",
146 all(target_os = "linux", feature = "systemd")
147 ))]
148 umask: Option<String>
149}
150
151
152#[cfg(windows)]
153pub type BoxRegCb =
154 Box<dyn FnOnce(&str, &mut windows_registry::Key) -> Result<(), Error>>;
155
156
157#[allow(clippy::struct_excessive_bools)]
158pub struct RegSvc {
159 pub force: bool,
161
162 pub qsu_argp: bool,
167
168 pub svcname: String,
169
170 pub display_name: Option<String>,
174
175 pub description: Option<String>,
179
180 pub conf_reload: bool,
182
183 pub netservice: bool,
187
188 #[cfg(windows)]
189 pub regconf: Option<BoxRegCb>,
190
191 pub args: Vec<String>,
193
194 pub envs: Vec<(String, String)>,
196
197 pub autostart: bool,
202
203 pub(crate) workdir: Option<String>,
204
205 deps: Vec<Depend>,
207
208 log_level: Option<LogLevel>,
209
210 log_filter: Option<String>,
211
212 trace_filter: Option<String>,
213
214 trace_file: Option<String>,
215
216 runas: RunAs
217}
218
219pub enum Depend {
220 Network,
221 Custom(Vec<String>)
222}
223
224impl RegSvc {
225 #[must_use]
226 pub fn new(svcname: &str) -> Self {
227 Self {
228 force: false,
229
230 qsu_argp: false,
231
232 svcname: svcname.to_string(),
233
234 display_name: None,
235
236 description: None,
237
238 conf_reload: false,
239
240 netservice: false,
241
242 #[cfg(windows)]
243 regconf: None,
244
245 args: Vec::new(),
246
247 envs: Vec::new(),
248
249 autostart: false,
250
251 workdir: None,
252
253 deps: Vec::new(),
254
255 log_level: None,
256
257 log_filter: None,
258
259 trace_filter: None,
260
261 trace_file: None,
262
263 runas: RunAs::default()
264 }
265 }
266
267 #[cfg(feature = "clap")]
268 #[allow(clippy::missing_panics_doc)]
269 pub fn from_cmd_match(matches: &ArgMatches) -> Self {
270 let force = matches.get_flag("force");
271
272 let svcname = matches.get_one::<String>("svcname").unwrap().to_owned();
274 let autostart = matches.get_flag("auto_start");
275
276 let dispname = matches.get_one::<String>("display_name");
277
278 let descr = matches.get_one::<String>("description");
279 let args: Vec<String> = matches
280 .get_many::<String>("arg")
281 .map_or_else(Vec::new, |vr| vr.map(String::from).collect());
282
283 let envs: Vec<String> = matches
284 .get_many::<String>("env")
285 .map_or_else(Vec::new, |vr| vr.map(String::from).collect());
286
287 let workdir = matches.get_one::<String>("workdir");
296
297 let mut environ = Vec::new();
298 let mut it = envs.into_iter();
299 while let Some((key, value)) = it.next_tuple() {
300 environ.push((key, value));
301 }
302
303 let log_level = matches.get_one::<LogLevel>("log_level").copied();
304 let log_filter = matches.get_one::<String>("log_filter").cloned();
305 let trace_filter = matches.get_one::<String>("trace_filter").cloned();
306 let trace_file = matches.get_one::<String>("trace_file").cloned();
307
308 let runas = RunAs::default();
309
310 Self {
311 force,
312 qsu_argp: true,
313 svcname,
314 display_name: dispname.cloned(),
315 description: descr.cloned(),
316 conf_reload: false,
317 netservice: false,
318 #[cfg(windows)]
319 regconf: None,
320 args,
321 envs: environ,
322 autostart,
323 workdir: workdir.cloned(),
324 deps: Vec::new(),
325 log_level,
326 log_filter,
327 trace_filter,
328 trace_file,
329 runas
330 }
331 }
332
333 #[must_use]
334 pub fn svcname(&self) -> &str {
335 &self.svcname
336 }
337
338 #[must_use]
342 pub fn display_name(mut self, name: impl ToString) -> Self {
343 self.display_name_ref(name);
344 self
345 }
346
347 #[allow(clippy::needless_pass_by_value)]
351 pub fn display_name_ref(&mut self, name: impl ToString) -> &mut Self {
352 self.display_name = Some(name.to_string());
353 self
354 }
355
356 #[must_use]
360 pub fn description(mut self, text: impl ToString) -> Self {
361 self.description_ref(text);
362 self
363 }
364
365 #[allow(clippy::needless_pass_by_value)]
369 pub fn description_ref(&mut self, text: impl ToString) -> &mut Self {
370 self.description = Some(text.to_string());
371 self
372 }
373
374 #[must_use]
376 pub const fn conf_reload(mut self) -> Self {
377 self.conf_reload_ref();
378 self
379 }
380
381 pub const fn conf_reload_ref(&mut self) -> &mut Self {
383 self.conf_reload = true;
384 self
385 }
386
387 #[must_use]
392 pub fn netservice(mut self) -> Self {
393 self.netservice_ref();
394 self
395 }
396
397 #[cfg_attr(not(windows), allow(clippy::missing_const_for_fn))]
402 pub fn netservice_ref(&mut self) -> &mut Self {
403 self.netservice = true;
404
405 #[cfg(windows)]
406 self.deps.push(Depend::Network);
407
408 self
409 }
410
411 #[cfg(windows)]
413 #[cfg_attr(docsrs, doc(cfg(windows)))]
414 #[must_use]
415 pub fn regconf<F>(mut self, f: F) -> Self
416 where
417 F: FnOnce(&str, &mut windows_registry::Key) -> Result<(), Error> + 'static
418 {
419 self.regconf = Some(Box::new(f));
420 self
421 }
422
423 #[cfg(windows)]
425 #[cfg_attr(docsrs, doc(cfg(windows)))]
426 pub fn regconf_ref<F>(&mut self, f: F) -> &mut Self
427 where
428 F: FnOnce(&str, &mut windows_registry::Key) -> Result<(), Error> + 'static
429 {
430 self.regconf = Some(Box::new(f));
431 self
432 }
433
434 #[allow(clippy::needless_pass_by_value)]
436 #[must_use]
437 pub fn arg(mut self, arg: impl ToString) -> Self {
438 self.args.push(arg.to_string());
439 self
440 }
441
442 #[allow(clippy::needless_pass_by_value)]
444 pub fn arg_ref(&mut self, arg: impl ToString) -> &mut Self {
445 self.args.push(arg.to_string());
446 self
447 }
448
449 #[must_use]
451 pub fn args<I, S>(mut self, args: I) -> Self
452 where
453 I: IntoIterator<Item = S>,
454 S: ToString
455 {
456 for arg in args {
457 self.args.push(arg.to_string());
458 }
459 self
460 }
461
462 pub fn args_ref<I, S>(&mut self, args: I) -> &mut Self
464 where
465 I: IntoIterator<Item = S>,
466 S: ToString
467 {
468 for arg in args {
469 self.arg_ref(arg.to_string());
470 }
471 self
472 }
473
474 #[must_use]
475 pub const fn have_args(&self) -> bool {
476 !self.args.is_empty()
477 }
478
479 #[allow(clippy::needless_pass_by_value)]
481 #[must_use]
482 pub fn env<K, V>(mut self, key: K, val: V) -> Self
483 where
484 K: ToString,
485 V: ToString
486 {
487 self.envs.push((key.to_string(), val.to_string()));
488 self
489 }
490
491 #[allow(clippy::needless_pass_by_value)]
493 pub fn env_ref<K, V>(&mut self, key: K, val: V) -> &mut Self
494 where
495 K: ToString,
496 V: ToString
497 {
498 self.envs.push((key.to_string(), val.to_string()));
499 self
500 }
501
502 #[must_use]
504 pub fn envs<I, K, V>(mut self, envs: I) -> Self
505 where
506 I: IntoIterator<Item = (K, V)>,
507 K: ToString,
508 V: ToString
509 {
510 for (key, val) in envs {
511 self.envs.push((key.to_string(), val.to_string()));
512 }
513 self
514 }
515
516 pub fn envs_ref<I, K, V>(&mut self, args: I) -> &mut Self
518 where
519 I: IntoIterator<Item = (K, V)>,
520 K: ToString,
521 V: ToString
522 {
523 for (key, val) in args {
524 self.env_ref(key.to_string(), val.to_string());
525 }
526 self
527 }
528
529 #[must_use]
530 pub const fn have_envs(&self) -> bool {
531 !self.envs.is_empty()
532 }
533
534 #[must_use]
536 pub const fn autostart(mut self) -> Self {
537 self.autostart = true;
538 self
539 }
540
541 pub const fn autostart_ref(&mut self) -> &mut Self {
543 self.autostart = true;
544 self
545 }
546
547 #[allow(clippy::needless_pass_by_value)]
552 #[must_use]
553 pub fn workdir(mut self, workdir: impl ToString) -> Self {
554 self.workdir = Some(workdir.to_string());
555 self
556 }
557
558 #[allow(clippy::needless_pass_by_value)]
560 pub fn workdir_ref(&mut self, workdir: impl ToString) -> &mut Self {
561 self.workdir = Some(workdir.to_string());
562 self
563 }
564
565 #[must_use]
569 pub fn depend(mut self, dep: Depend) -> Self {
570 self.deps.push(dep);
571 self
572 }
573
574 pub fn depend_ref(&mut self, dep: Depend) -> &mut Self {
578 self.deps.push(dep);
579 self
580 }
581
582 pub fn register(self) -> Result<(), Error> {
587 #[cfg(windows)]
588 winsvc::install(self)?;
589
590 #[cfg(target_os = "macos")]
591 launchd::install(self)?;
592
593 #[cfg(all(target_os = "linux", feature = "systemd"))]
594 systemd::install(self)?;
595
596 Ok(())
597 }
598}
599
600
601#[allow(unreachable_code)]
606pub fn uninstall(svcname: &str) -> Result<(), Error> {
607 #[cfg(windows)]
608 {
609 winsvc::uninstall(svcname)?;
610 return Ok(());
611 }
612
613 #[cfg(target_os = "macos")]
614 {
615 launchd::uninstall(svcname)?;
616 return Ok(());
617 }
618
619 #[cfg(all(target_os = "linux", feature = "systemd"))]
620 {
621 systemd::uninstall(svcname)?;
622 return Ok(());
623 }
624
625 Err(Error::Unsupported)
626}
627
628