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 winreg::RegKey) -> 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 trace_filter: Option<String>,
211
212 trace_file: Option<String>,
213
214 runas: RunAs
215}
216
217pub enum Depend {
218 Network,
219 Custom(Vec<String>)
220}
221
222impl RegSvc {
223 #[must_use]
224 pub fn new(svcname: &str) -> Self {
225 Self {
226 force: false,
227
228 qsu_argp: false,
229
230 svcname: svcname.to_string(),
231
232 display_name: None,
233
234 description: None,
235
236 conf_reload: false,
237
238 netservice: false,
239
240 #[cfg(windows)]
241 regconf: None,
242
243 args: Vec::new(),
244
245 envs: Vec::new(),
246
247 autostart: false,
248
249 workdir: None,
250
251 deps: Vec::new(),
252
253 log_level: None,
254
255 trace_filter: None,
256
257 trace_file: None,
258
259 runas: RunAs::default()
260 }
261 }
262
263 #[cfg(feature = "clap")]
264 #[allow(clippy::missing_panics_doc)]
265 pub fn from_cmd_match(matches: &ArgMatches) -> Self {
266 let force = matches.get_flag("force");
267
268 let svcname = matches.get_one::<String>("svcname").unwrap().to_owned();
270 let autostart = matches.get_flag("auto_start");
271
272 let dispname = matches.get_one::<String>("display_name");
273
274 let descr = matches.get_one::<String>("description");
275 let args: Vec<String> = matches
276 .get_many::<String>("arg")
277 .map_or_else(Vec::new, |vr| vr.map(String::from).collect());
278
279 let envs: Vec<String> = matches
280 .get_many::<String>("env")
281 .map_or_else(Vec::new, |vr| vr.map(String::from).collect());
282
283 let workdir = matches.get_one::<String>("workdir");
292
293 let mut environ = Vec::new();
294 let mut it = envs.into_iter();
295 while let Some((key, value)) = it.next_tuple() {
296 environ.push((key, value));
297 }
298
299 let log_level = matches.get_one::<LogLevel>("log_level").copied();
300 let trace_filter = matches.get_one::<String>("trace_filter").cloned();
301 let trace_file = matches.get_one::<String>("trace_file").cloned();
302
303 let runas = RunAs::default();
304
305 Self {
306 force,
307 qsu_argp: true,
308 svcname,
309 display_name: dispname.cloned(),
310 description: descr.cloned(),
311 conf_reload: false,
312 netservice: false,
313 #[cfg(windows)]
314 regconf: None,
315 args,
316 envs: environ,
317 autostart,
318 workdir: workdir.cloned(),
319 deps: Vec::new(),
320 log_level,
321 trace_filter,
322 trace_file,
323 runas
324 }
325 }
326
327 #[must_use]
328 pub fn svcname(&self) -> &str {
329 &self.svcname
330 }
331
332 #[must_use]
336 pub fn display_name(mut self, name: impl ToString) -> Self {
337 self.display_name_ref(name);
338 self
339 }
340
341 #[allow(clippy::needless_pass_by_value)]
345 pub fn display_name_ref(&mut self, name: impl ToString) -> &mut Self {
346 self.display_name = Some(name.to_string());
347 self
348 }
349
350 #[must_use]
354 pub fn description(mut self, text: impl ToString) -> Self {
355 self.description_ref(text);
356 self
357 }
358
359 #[allow(clippy::needless_pass_by_value)]
363 pub fn description_ref(&mut self, text: impl ToString) -> &mut Self {
364 self.description = Some(text.to_string());
365 self
366 }
367
368 #[must_use]
370 pub fn conf_reload(mut self) -> Self {
371 self.conf_reload_ref();
372 self
373 }
374
375 pub fn conf_reload_ref(&mut self) -> &mut Self {
377 self.conf_reload = true;
378 self
379 }
380
381 #[must_use]
386 pub fn netservice(mut self) -> Self {
387 self.netservice_ref();
388 self
389 }
390
391 pub fn netservice_ref(&mut self) -> &mut Self {
396 self.netservice = true;
397
398 #[cfg(windows)]
399 self.deps.push(Depend::Network);
400
401 self
402 }
403
404 #[cfg(windows)]
406 #[cfg_attr(docsrs, doc(cfg(windows)))]
407 #[must_use]
408 pub fn regconf<F>(mut self, f: F) -> Self
409 where
410 F: FnOnce(&str, &mut winreg::RegKey) -> Result<(), Error> + 'static
411 {
412 self.regconf = Some(Box::new(f));
413 self
414 }
415
416 #[cfg(windows)]
418 #[cfg_attr(docsrs, doc(cfg(windows)))]
419 pub fn regconf_ref<F>(&mut self, f: F) -> &mut Self
420 where
421 F: FnOnce(&str, &mut winreg::RegKey) -> Result<(), Error> + 'static
422 {
423 self.regconf = Some(Box::new(f));
424 self
425 }
426
427 #[allow(clippy::needless_pass_by_value)]
429 #[must_use]
430 pub fn arg(mut self, arg: impl ToString) -> Self {
431 self.args.push(arg.to_string());
432 self
433 }
434
435 #[allow(clippy::needless_pass_by_value)]
437 pub fn arg_ref(&mut self, arg: impl ToString) -> &mut Self {
438 self.args.push(arg.to_string());
439 self
440 }
441
442 #[must_use]
444 pub fn args<I, S>(mut self, args: I) -> Self
445 where
446 I: IntoIterator<Item = S>,
447 S: ToString
448 {
449 for arg in args {
450 self.args.push(arg.to_string());
451 }
452 self
453 }
454
455 pub fn args_ref<I, S>(&mut self, args: I) -> &mut Self
457 where
458 I: IntoIterator<Item = S>,
459 S: ToString
460 {
461 for arg in args {
462 self.arg_ref(arg.to_string());
463 }
464 self
465 }
466
467 #[must_use]
468 pub fn have_args(&self) -> bool {
469 !self.args.is_empty()
470 }
471
472 #[allow(clippy::needless_pass_by_value)]
474 #[must_use]
475 pub fn env<K, V>(mut self, key: K, val: V) -> Self
476 where
477 K: ToString,
478 V: ToString
479 {
480 self.envs.push((key.to_string(), val.to_string()));
481 self
482 }
483
484 #[allow(clippy::needless_pass_by_value)]
486 pub fn env_ref<K, V>(&mut self, key: K, val: V) -> &mut Self
487 where
488 K: ToString,
489 V: ToString
490 {
491 self.envs.push((key.to_string(), val.to_string()));
492 self
493 }
494
495 #[must_use]
497 pub fn envs<I, K, V>(mut self, envs: I) -> Self
498 where
499 I: IntoIterator<Item = (K, V)>,
500 K: ToString,
501 V: ToString
502 {
503 for (key, val) in envs {
504 self.envs.push((key.to_string(), val.to_string()));
505 }
506 self
507 }
508
509 pub fn envs_ref<I, K, V>(&mut self, args: I) -> &mut Self
511 where
512 I: IntoIterator<Item = (K, V)>,
513 K: ToString,
514 V: ToString
515 {
516 for (key, val) in args {
517 self.env_ref(key.to_string(), val.to_string());
518 }
519 self
520 }
521
522 #[must_use]
523 pub fn have_envs(&self) -> bool {
524 !self.envs.is_empty()
525 }
526
527 #[must_use]
529 pub const fn autostart(mut self) -> Self {
530 self.autostart = true;
531 self
532 }
533
534 pub fn autostart_ref(&mut self) -> &mut Self {
536 self.autostart = true;
537 self
538 }
539
540 #[allow(clippy::needless_pass_by_value)]
545 #[must_use]
546 pub fn workdir(mut self, workdir: impl ToString) -> Self {
547 self.workdir = Some(workdir.to_string());
548 self
549 }
550
551 #[allow(clippy::needless_pass_by_value)]
553 pub fn workdir_ref(&mut self, workdir: impl ToString) -> &mut Self {
554 self.workdir = Some(workdir.to_string());
555 self
556 }
557
558 #[must_use]
562 pub fn depend(mut self, dep: Depend) -> Self {
563 self.deps.push(dep);
564 self
565 }
566
567 pub fn depend_ref(&mut self, dep: Depend) -> &mut Self {
571 self.deps.push(dep);
572 self
573 }
574
575 pub fn register(self) -> Result<(), Error> {
580 #[cfg(windows)]
581 winsvc::install(self)?;
582
583 #[cfg(target_os = "macos")]
584 launchd::install(self)?;
585
586 #[cfg(all(target_os = "linux", feature = "systemd"))]
587 systemd::install(self)?;
588
589 Ok(())
590 }
591}
592
593
594#[allow(unreachable_code)]
599pub fn uninstall(svcname: &str) -> Result<(), Error> {
600 #[cfg(windows)]
601 {
602 winsvc::uninstall(svcname)?;
603 return Ok(());
604 }
605
606 #[cfg(target_os = "macos")]
607 {
608 launchd::uninstall(svcname)?;
609 return Ok(());
610 }
611
612 #[cfg(all(target_os = "linux", feature = "systemd"))]
613 {
614 systemd::uninstall(svcname)?;
615 return Ok(());
616 }
617
618 Err(Error::Unsupported)
619}
620
621