Skip to main content

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}