universal_service/lib.rs
1//! `universal-service` is a Rust crate that provides utilities for building services that
2//! can run across multiple platforms.
3//!
4//! For Windows users, this library provides support for running services under the Windows
5//! Service manager directly, rather than requiring Scheduled Tasks hacks or NSSM.
6
7#[cfg(windows)]
8pub mod windows_ffi_service_main;
9#[cfg(windows)]
10pub mod windows_simple_service;
11
12use std::sync::mpsc::channel;
13use std::sync::mpsc::Receiver;
14
15/// Type signature of a service's main function used by this library.
16///
17/// The three parameters are used as follows:
18/// * `Receiver<T>`: a [`std::sync::mpsc::Receiver<T>`] that is used to transmit service stop
19/// signals to the main function for handling.
20/// * `Vec<String>`: the operating system arguments passed to the binary at process load time.
21/// For a binary running as a Windows Service, these are the arguments passed to `-BinaryPath`
22/// * `Option<Vec<String>>`: the start parameters passed as part of Windows Service startup, if
23/// the binary is running as a Windows Service. This argument should be `None` if this is not
24/// running as a Windows Service
25pub type ServiceMain<T> =
26 dyn Fn(Receiver<T>, Vec<String>, Option<Vec<String>>) -> anyhow::Result<T> + Sync + Send;
27
28#[cfg(windows)]
29use windows_service_detector::is_running_as_windows_service;
30#[cfg(windows)]
31use windows_simple_service::run_simple_service;
32
33#[cfg(windows)]
34/// Run a "universal" service main function. On Windows, this will do service environment detection
35/// and choose whether to initialize as a Windows Service or a normal CLI binary. On other platforms
36/// it will run as normal process (or "simple" in systemd parlance).
37pub fn universal_service_main(
38 service_name: String,
39 service_main: Box<ServiceMain<()>>,
40) -> anyhow::Result<()> {
41 if is_running_as_windows_service().expect("failed to detect windows service environment") {
42 run_simple_service(service_name, service_main)?;
43 } else {
44 run_simple_nonservice(service_name, service_main)?;
45 }
46 Ok(())
47}
48
49#[cfg(not(windows))]
50pub fn universal_service_main(
51 service_name: String,
52 service_main: Box<ServiceMain<()>>,
53) -> anyhow::Result<()> {
54 run_simple_nonservice(service_name, service_main)?;
55 Ok(())
56}
57
58/// Run a ServiceMain function as a simple foreground process. Instead of registering
59/// as a service, this runs the function with just a signal handler that sends to
60/// the shutdown receiver.
61pub fn run_simple_nonservice(
62 _service_name: String,
63 service_main: Box<ServiceMain<()>>,
64) -> anyhow::Result<()> {
65 let (shutdown_tx, shutdown_rx) = channel();
66
67 ctrlc::set_handler(move || {
68 let _ = shutdown_tx.send(());
69 })
70 .expect("failed to set signal handler");
71
72 service_main(shutdown_rx, std::env::args().collect(), None)?;
73 Ok(())
74}