Merge pull request #110 from joseluisq/feature/windows_service_support
feat: windows service support
resolves #65
Diff
Cargo.lock | 57 +++++-
Cargo.toml | 4 +-
README.md | 1 +-
docs/content/configuration/command-line-arguments.md | 14 +-
docs/content/configuration/environment-variables.md | 7 +-
docs/content/features/windows-service.md | 143 ++++++++++++-
docs/content/index.md | 1 +-
docs/mkdocs.yml | 1 +-
src/bin/server.rs | 26 +-
src/lib.rs | 3 +-
src/logger.rs | 18 +-
src/server.rs | 60 +++--
src/settings/cli.rs | 31 +++-
src/settings/mod.rs | 10 +-
src/signals.rs | 27 +-
src/winservice.rs | 247 ++++++++++++++++++++-
16 files changed, 625 insertions(+), 25 deletions(-)
@@ -235,6 +235,20 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "e78d4f1cc4ae33bbfc157ed5d5a5ef3bc29227303d595861deb238fcec4e9457"
[[package]]
name = "err-derive"
version = "0.3.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "c34a887c8df3ed90498c1c437ce21f211c8e27672921a8ffa293cb8d6d4caa9e"
dependencies = [
"proc-macro-error",
"proc-macro2",
"quote",
"rustversion",
"syn",
"synstructure",
]
[[package]]
name = "flate2"
version = "1.0.23"
source = "registry+https://github.com/rust-lang/crates.io-index"
@@ -797,6 +811,12 @@ dependencies = [
]
[[package]]
name = "rustversion"
version = "1.0.6"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "f2cc38e8fa666e2de3c4aba7edeb5ffc5246c1c2ed0e3d17e560aeeba736b23f"
[[package]]
name = "scopeguard"
version = "1.1.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
@@ -956,6 +976,8 @@ dependencies = [
"toml",
"tracing",
"tracing-subscriber",
"windows-service",
"windows-sys",
]
[[package]]
@@ -994,6 +1016,18 @@ dependencies = [
]
[[package]]
name = "synstructure"
version = "0.12.6"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "f36bdaa60a83aca3921b5259d5400cbf5e90fc51931376a9bd4a0eb79aa7210f"
dependencies = [
"proc-macro2",
"quote",
"syn",
"unicode-xid",
]
[[package]]
name = "textwrap"
version = "0.11.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
@@ -1211,6 +1245,12 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "3ed742d4ea2bd1176e236172c8429aaf54486e7ac098db29ffe6529e0ce50973"
[[package]]
name = "unicode-xid"
version = "0.2.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "957e51f3646910546462e67d5f7599b9e4fb8acdd304b087a6494730f9eebf04"
[[package]]
name = "untrusted"
version = "0.7.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
@@ -1331,6 +1371,12 @@ dependencies = [
]
[[package]]
name = "widestring"
version = "0.5.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "17882f045410753661207383517a6f62ec3dbeb6a4ed2acce01f0728238d1983"
[[package]]
name = "winapi"
version = "0.3.9"
source = "registry+https://github.com/rust-lang/crates.io-index"
@@ -1353,6 +1399,17 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "712e227841d057c1ee1cd2fb22fa7e5a5461ae8e48fa2ca79ec42cfc1931183f"
[[package]]
name = "windows-service"
version = "0.4.0"
source = "git+https://github.com/joseluisq/windows-service-rs#5c9abd3d86a8a35d158807661f215c04661369dd"
dependencies = [
"bitflags",
"err-derive",
"widestring",
"windows-sys",
]
[[package]]
name = "windows-sys"
version = "0.36.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
@@ -64,6 +64,10 @@ version = "0.4"
signal-hook = { version = "0.3", features = ["extended-siginfo"] }
signal-hook-tokio = { version = "0.3", features = ["futures-v0_3"], default-features = false }
[target.'cfg(windows)'.dependencies]
windows-service = { git = "https://github.com/joseluisq/windows-service-rs" }
windows-sys = { version = "0.36.1", features = [ "Win32_Foundation", "Win32_NetworkManagement_IpHelper", "Win32_Networking_WinSock", "Win32_System_Memory" ] }
[dev-dependencies]
bytes = "1.1"
@@ -54,6 +54,7 @@ It's cross-platform and available for `Linux`, `macOS`, `Windows` and `FreeBSD`
- Basic HTTP Authentication.
- Customizable HTTP Response Headers for specific file requests via glob patterns.
- Fallback pages for 404 errors useful for Single-page applications.
- Run the server as [Windows Service](https://docs.microsoft.com/en-us/previous-versions/windows/it-pro/windows-server-2003/cc783643(v=ws.10)).
- Configurable using CLI arguments, environment variables or a file.
- Default and custom error pages.
- First-class [Docker](https://docs.docker.com/get-started/overview/) support. [Scratch](https://hub.docker.com/_/scratch) and latest [Alpine Linux](https://hub.docker.com/_/alpine) Docker images available.
@@ -100,3 +100,17 @@ OPTIONS:
32,768 though it is advised to keep this value on the smaller side [env: SERVER_THREADS_MULTIPLIER=]
[default: 1]
```
## Windows
Following options and commands are Windows platform specific.
```
-s, --windows-service <windows-service>
Run the web server as a Windows Service [env: SERVER_WINDOWS_SERVICE=] [default: false]
SUBCOMMANDS:
help Prints this message or the help of the given subcommand(s)
install Install a Windows Service for the web server
uninstall Uninstall the current Windows Service
```
@@ -71,3 +71,10 @@ Enable cache control headers for incoming requests based on a set of file types.
### SERVER_BASIC_AUTH
It provides [The "Basic" HTTP Authentication Scheme](https://datatracker.ietf.org/doc/html/rfc7617) using credentials as `user-id:password` pairs, encoded using `Base64`. Password must be encoded using the [BCrypt](https://en.wikipedia.org/wiki/Bcrypt) password-hashing function. Default empty (disabled).
## Windows
Following options and commands are Windows platform specific.
### SERVER_WINDOWS_SERVICE
Run the web server as a Windows Service. See [more details](../features/windows-service.md).
@@ -0,0 +1,143 @@
# Windows Service
**`SWS`** can be also executed as a [Windows Service](https://docs.microsoft.com/en-us/previous-versions/windows/it-pro/windows-server-2003/cc783643(v=ws.10)).
This feature is disabled by default and can be controlled by the boolean `-s, --windows-service` option or the equivalent [SERVER_WINDOWS_SERVICE](./../configuration/environment-variables.md#server_windows_service) env.

> _Static Web Server running as a Windows Service and displayed by 'services.msc' application._
**Important notes**
- This is a Windows platform specific feature. It means the `--windows-service` argument and its env will not be present in Unix-like systems.
- Running SWS as a Windows service doesn't require to enable it via the [configuration file](../configuration/config-file.md) (`windows-service = true`) because it's already enabled during the service installation.
## Service privileges
To either install or uninstall the SWS Windows service requires *administrator* privileges, so make sure to open the terminal application as administrator or give to your [Powershell](https://docs.microsoft.com/en-us/powershell/scripting/overview?view=powershell-7.2) session enough privileges otherwise you will get an `"Access is denied"` error.
We recommend a [Powershell](https://docs.microsoft.com/en-us/powershell/scripting/overview?view=powershell-7.2) session with administrator privileges.
## Install the service
To install the SWS service use the `install` command along with a [configuration file](../configuration/config-file.md) for further SWS options customization.
Make sure to provide a configuration file to run SWS service properly. In particular, configure the server `address`, `port` and `root` directory accordingly.
If not so then the service might not start.
The following command will create the SWS service called `static-web-server` with a "`Static Web Server`" display name.
```powershell
static-web-server.exe -w C:\Users\MyUser\config.toml install
# Windows Service (static-web-server) is installed successfully!
# Start the service typing: sc.exe start "static-web-server" (it requires administrator privileges) or using the 'services.msc' application.
```
## Interact with the service
SWS doesn't provide a way to interact with Windows services directly. Instead, use the Windows built-in tools to interact with the SWS service once created.
For that purpose you can use either the Windows [sc.exe](https://docs.microsoft.com/en-us/windows/win32/services/configuring-a-service-using-sc) or the [services.msc](https://docs.microsoft.com/en-us/windows/win32/services/services) application.
For example, using `sc.exe` you can show the SWS service configuration used once installed.
```powershell
sc.exe qc "static-web-server"
# [SC] QueryServiceConfig SUCCESS
# SERVICE_NAME: static-web-server
# TYPE : 10 WIN32_OWN_PROCESS
# START_TYPE : 3 DEMAND_START
# ERROR_CONTROL : 1 NORMAL
# BINARY_PATH_NAME : C:\Users\MyUser\static-web-server.exe
# --windows-service=true
# --config-file=C:\Users\MyUser\config.toml
# LOAD_ORDER_GROUP :
# TAG : 0
# DISPLAY_NAME : Static Web Server
# DEPENDENCIES :
# SERVICE_START_NAME : LocalSystem
```
Remember that alternatively you can also use the `services.msc` application if you prefer GUI service control.
### Start
To start the service use the following `sc.exe` command.
```powershell
sc.exe start "static-web-server"
# SERVICE_NAME: static-web-server
# TYPE : 10 WIN32_OWN_PROCESS
# STATE : 2 START_PENDING
# (NOT_STOPPABLE, NOT_PAUSABLE, IGNORES_SHUTDOWN)
# WIN32_EXIT_CODE : 0 (0x0)
# SERVICE_EXIT_CODE : 0 (0x0)
# CHECKPOINT : 0x0
# WAIT_HINT : 0x7d0
# PID : 3068
# FLAGS :
```
### Status
To show the service status use the following `sc.exe` command.
```powershell
sc.exe query "static-web-server"
# SERVICE_NAME: static-web-server
# TYPE : 10 WIN32_OWN_PROCESS
# STATE : 4 RUNNING
# (STOPPABLE, NOT_PAUSABLE, IGNORES_SHUTDOWN)
# WIN32_EXIT_CODE : 0 (0x0)
# SERVICE_EXIT_CODE : 0 (0x0)
# CHECKPOINT : 0x0
# WAIT_HINT : 0x0
```
### Stop
To stop the service use the following `sc.exe` command.
```powershell
sc.exe stop "static-web-server"
# SERVICE_NAME: static-web-server
# TYPE : 10 WIN32_OWN_PROCESS
# STATE : 3 STOP_PENDING
# (STOPPABLE, NOT_PAUSABLE, IGNORES_SHUTDOWN)
# WIN32_EXIT_CODE : 0 (0x0)
# SERVICE_EXIT_CODE : 0 (0x0)
# CHECKPOINT : 0x2
# WAIT_HINT : 0xbb8
```
After stopping the service you can also show its status.
```powershell
sc.exe query "static-web-server"
# SERVICE_NAME: static-web-server
# TYPE : 10 WIN32_OWN_PROCESS
# STATE : 1 STOPPED
# WIN32_EXIT_CODE : 0 (0x0)
# SERVICE_EXIT_CODE : 0 (0x0)
# CHECKPOINT : 0x0
# WAIT_HINT : 0x0
```
## Uninstall the service
To uninstall the SWS service just use the `uninstall` command. Note that the service should be first stopped before to uninstall it.
```powershell
static-web-server.exe uninstall
# Windows Service (static-web-server) is uninstalled!
```
After uninstalling the service you can verify if removed.
```powershell
sc.exe qc "static-web-server"
# [SC] OpenService FAILED 1060:
#
# The specified service does not exist as an installed service.
```
@@ -58,6 +58,7 @@ It's cross-platform and available for `Linux`, `macOS`, `Windows` and `FreeBSD`
- Basic HTTP Authentication.
- Customizable HTTP Response Headers for specific file requests via glob patterns.
- Fallback pages for 404 errors useful for Single-page applications.
- Run the server as [Windows Service](https://docs.microsoft.com/en-us/previous-versions/windows/it-pro/windows-server-2003/cc783643(v=ws.10)).
- Configurable using CLI arguments, environment variables or a file.
- Default and custom error pages.
- First-class [Docker](https://docs.docker.com/get-started/overview/) support. [Scratch](https://hub.docker.com/_/scratch) and latest [Alpine Linux](https://hub.docker.com/_/alpine) Docker images available.
@@ -139,6 +139,7 @@ nav:
- 'Worker Threads Customization': 'features/worker-threads.md'
- 'Error Pages': 'features/error-pages.md'
- 'Custom HTTP Headers': 'features/custom-http-headers.md'
- 'Windows Service': 'features/windows-service.md'
- 'Platforms & Architectures': 'platforms-architectures.md'
- 'Migration from v1 to v2': 'migration.md'
- 'Changelog v2 (latest stable)': 'https://github.com/joseluisq/static-web-server/blob/master/CHANGELOG.md'
@@ -7,10 +7,32 @@
#[global_allocator]
static GLOBAL: tikv_jemallocator::Jemalloc = tikv_jemallocator::Jemalloc;
use static_web_server::{Result, Server};
use static_web_server::Result;
fn main() -> Result {
Server::new()?.run()?;
#[cfg(windows)]
{
use static_web_server::settings::{Commands, Settings};
use static_web_server::winservice;
let opts = Settings::get()?;
if let Some(commands) = opts.general.commands {
match commands {
Commands::Install {} => {
return winservice::install_service(opts.general.config_file);
}
Commands::Uninstall {} => {
return winservice::uninstall_service();
}
}
} else if opts.general.windows_service {
return winservice::run_server_as_service();
}
}
static_web_server::Server::new()?.run_standalone()?;
Ok(())
}
@@ -28,6 +28,9 @@ pub mod static_files;
pub mod tls;
pub mod transport;
#[cfg(windows)]
pub mod winservice;
#[macro_use]
pub mod error;
@@ -1,11 +1,21 @@
use std::io;
use tracing::Level;
use tracing_subscriber::fmt::format::FmtSpan;
use crate::Result;
use crate::{Context, Result};
pub fn init(log_level: &str) -> Result {
let log_level = log_level.to_lowercase();
configure(&log_level).with_context(|| "failed to initialize logging")?;
tracing::info!("logging level: {}", log_level);
Ok(())
}
pub fn init(level: &str) -> Result {
fn configure(level: &str) -> Result {
let level = level.parse::<Level>()?;
#[cfg(unix)]
@@ -14,7 +24,7 @@ pub fn init(level: &str) -> Result {
let enable_ansi = false;
match tracing_subscriber::fmt()
.with_writer(io::stderr)
.with_writer(std::io::stderr)
.with_max_level(level)
.with_span_events(FmtSpan::CLOSE)
.with_ansi(enable_ansi)
@@ -3,6 +3,7 @@ use hyper::server::Server as HyperServer;
use listenfd::ListenFd;
use std::net::{IpAddr, SocketAddr, TcpListener};
use std::sync::Arc;
use tokio::sync::oneshot::Receiver;
use crate::handler::{RequestHandler, RequestHandlerOpts};
use crate::tls::{TlsAcceptor, TlsConfigBuilder};
@@ -31,17 +32,38 @@ impl Server {
Ok(Server { opts, threads })
}
pub fn run(self) -> Result {
pub fn run_standalone(self) -> Result {
logger::init(&self.opts.general.log_level)?;
self.run_server_on_rt(None, || {})
}
#[cfg(windows)]
pub fn run_as_service<F>(self, cancel: Option<Receiver<()>>, cancel_fn: F) -> Result
where
F: FnOnce(),
{
self.run_server_on_rt(cancel, cancel_fn)
}
fn run_server_on_rt<F>(self, cancel_recv: Option<Receiver<()>>, cancel_fn: F) -> Result
where
F: FnOnce(),
{
tracing::debug!("initializing tokio runtime with multi thread scheduler");
tokio::runtime::Builder::new_multi_thread()
.worker_threads(self.threads)
.thread_name("static-web-server")
.enable_all()
.build()?
.block_on(async {
let r = self.start_server().await;
if r.is_err() {
println!("server failed to start up: {:?}", r.unwrap_err());
if let Err(err) = self.start_server(cancel_recv, cancel_fn).await {
tracing::error!("server failed to start up: {:?}", err);
std::process::exit(1)
}
});
@@ -51,18 +73,16 @@ impl Server {
async fn start_server(self) -> Result {
async fn start_server<F>(self, _cancel_recv: Option<Receiver<()>>, _cancel_fn: F) -> Result
where
F: FnOnce(),
{
let general = self.opts.general;
let advanced_opts = self.opts.advanced;
let log_level = &general.log_level.to_lowercase();
logger::init(log_level).with_context(|| "failed to initialize logging")?;
tracing::info!("logging level: {}", log_level.to_lowercase());
if general.config_file.is_some() && general.config_file.is_some() {
tracing::info!("config file: {}", general.config_file.unwrap().display());
@@ -107,7 +127,7 @@ impl Server {
let threads = self.threads;
tracing::info!("runtime worker threads: {}", self.threads);
tracing::info!("runtime worker threads: {}", threads);
let security_headers = general.security_headers;
@@ -209,7 +229,11 @@ impl Server {
let server =
server.with_graceful_shutdown(signals::wait_for_signals(signals, grace_period));
#[cfg(windows)]
let server = server.with_graceful_shutdown(signals::wait_for_ctrl_c(grace_period));
let server = server.with_graceful_shutdown(signals::wait_for_ctrl_c(
_cancel_recv,
_cancel_fn,
grace_period,
));
tracing::info!(
parent: tracing::info_span!("Server::start_server", ?addr_str, ?threads),
@@ -232,6 +256,10 @@ impl Server {
#[cfg(unix)]
let handle = signals.handle();
tcp_listener
.set_nonblocking(true)
.with_context(|| "failed to set TCP non-blocking mode")?;
let server = HyperServer::from_tcp(tcp_listener)
.unwrap()
.tcp_nodelay(true)
@@ -241,7 +269,11 @@ impl Server {
let server =
server.with_graceful_shutdown(signals::wait_for_signals(signals, grace_period));
#[cfg(windows)]
let server = server.with_graceful_shutdown(signals::wait_for_ctrl_c(grace_period));
let server = server.with_graceful_shutdown(signals::wait_for_ctrl_c(
_cancel_recv,
_cancel_fn,
grace_period,
));
tracing::info!(
parent: tracing::info_span!("Server::start_server", ?addr_str, ?threads),
@@ -169,4 +169,35 @@ pub struct General {
#[structopt(long, short = "w", env = "SERVER_CONFIG_FILE")]
pub config_file: Option<PathBuf>,
#[cfg(windows)]
#[structopt(
long,
short = "s",
parse(try_from_str),
default_value = "false",
env = "SERVER_WINDOWS_SERVICE"
)]
pub windows_service: bool,
#[cfg(windows)]
#[structopt(subcommand)]
pub commands: Option<Commands>,
}
#[cfg(windows)]
#[derive(Debug, StructOpt)]
pub enum Commands {
#[structopt(name = "install")]
Install {},
#[structopt(name = "uninstall")]
Uninstall {},
}
@@ -7,6 +7,9 @@ use crate::{Context, Result};
mod cli;
pub mod file;
#[cfg(windows)]
pub use cli::Commands;
use cli::General;
@@ -203,6 +206,13 @@ impl Settings {
threads_multiplier,
grace_period,
page_fallback,
#[cfg(windows)]
windows_service: opts.windows_service,
#[cfg(windows)]
commands: opts.commands,
},
advanced: settings_advanced,
})
@@ -1,10 +1,13 @@
use tokio::time::{sleep, Duration};
#[cfg(unix)]
use {
crate::Result, futures_util::stream::StreamExt, signal_hook::consts::signal::*,
signal_hook_tokio::Signals,
};
use tokio::time::{sleep, Duration};
#[cfg(windows)]
use tokio::sync::oneshot::Receiver;
#[cfg(unix)]
@@ -48,10 +51,24 @@ async fn delay_graceful_shutdown(grace_period_secs: u8) {
#[cfg(windows)]
pub async fn wait_for_ctrl_c(grace_period_secs: u8) {
tokio::signal::ctrl_c()
.await
.expect("failed to install ctrl+c signal handler");
pub async fn wait_for_ctrl_c<F>(
cancel_recv: Option<Receiver<()>>,
cancel_fn: F,
grace_period_secs: u8,
) where
F: FnOnce(),
{
if let Some(recv) = cancel_recv {
if let Err(err) = recv.await {
tracing::error!("error during cancel recv: {:?}", err)
}
cancel_fn()
} else {
tokio::signal::ctrl_c()
.await
.expect("failed to install ctrl+c signal handler");
}
delay_graceful_shutdown(grace_period_secs).await;
tracing::info!("delegating server's graceful shutdown");
}
@@ -0,0 +1,247 @@
use std::ffi::OsString;
use std::thread;
use std::time::Duration;
use std::{env, path::PathBuf};
use windows_service::{
define_windows_service,
service::{
ServiceAccess, ServiceControl, ServiceControlAccept, ServiceErrorControl, ServiceExitCode,
ServiceInfo, ServiceStartType, ServiceState, ServiceStatus, ServiceType,
},
service_control_handler::{self, ServiceControlHandlerResult, ServiceStatusHandle},
service_dispatcher,
service_manager::{ServiceManager, ServiceManagerAccess},
};
use crate::{logger, Context, Result, Server, Settings};
const SERVICE_NAME: &str = "static-web-server";
const SERVICE_TYPE: ServiceType = ServiceType::OWN_PROCESS;
const SERVICE_EXE: &str = "static-web-server.exe";
const SERVICE_DESC: &str = "A blazing fast and asynchronous web server for static files-serving";
const SERVICE_DISPLAY_NAME: &str = "Static Web Server";
define_windows_service!(ffi_service_main, custom_service_main);
fn custom_service_main(_args: Vec<OsString>) {
if let Err(err) = run_service() {
tracing::error!("error starting the service: {:?}", err);
}
}
fn set_service_state(
status_handle: &ServiceStatusHandle,
current_state: ServiceState,
checkpoint: u32,
wait_hint: Duration,
) -> Result {
let next_status = ServiceStatus {
service_type: SERVICE_TYPE,
current_state,
controls_accepted: ServiceControlAccept::STOP,
exit_code: ServiceExitCode::Win32(0),
checkpoint,
wait_hint,
process_id: None,
};
Ok(status_handle.set_service_status(next_status)?)
}
fn run_service() -> Result {
let opts = Settings::get()?;
logger::init(&opts.general.log_level)?;
tracing::info!("windows service: starting service setup");
let (shutdown_tx, shutdown_rx) = tokio::sync::oneshot::channel();
let mut shutdown_tx = Some(shutdown_tx);
let event_handler = move |control_event| -> ServiceControlHandlerResult {
match control_event {
ServiceControl::Interrogate => ServiceControlHandlerResult::NoError,
ServiceControl::Stop => {
tracing::debug!("windows service: handled 'ServiceControl::Stop' event");
if let Some(sender) = shutdown_tx.take() {
tracing::debug!("windows service: delegated 'ServiceControl::Stop' event");
sender.send(()).unwrap();
}
ServiceControlHandlerResult::NoError
}
_ => ServiceControlHandlerResult::NotImplemented,
}
};
let status_handle = service_control_handler::register(SERVICE_NAME, event_handler)?;
tracing::info!("windows service: registering service");
set_service_state(
&status_handle,
ServiceState::Running,
1,
Duration::default(),
)?;
tracing::info!("windows service: set service 'Running' state");
let stop_handler = || {
match set_service_state(
&status_handle,
ServiceState::StopPending,
2,
Duration::from_secs(3),
) {
Ok(()) => tracing::info!("windows service: set service 'StopPending' state"),
Err(err) => tracing::error!(
"windows service: error when setting 'StopPending' state: {:?}",
err
),
}
};
match Server::new() {
Ok(server) => {
if let Err(err) = server.run_as_service(Some(shutdown_rx), stop_handler) {
tracing::error!(
"windows service: error after starting the server: {:?}",
err
);
}
}
Err(err) => {
tracing::info!("windows service: error starting the server: {:?}", err);
}
}
set_service_state(
&status_handle,
ServiceState::Stopped,
3,
Duration::from_secs(3),
)?;
tracing::info!("windows service: set service 'Stopped' state");
Ok(())
}
pub fn run_server_as_service() -> Result {
let mut path = env::current_exe().unwrap();
path.pop();
env::set_current_dir(&path).unwrap();
service_dispatcher::start(&SERVICE_NAME, ffi_service_main)
.with_context(|| "error registering generated `ffi_service_main` with the system")?;
Ok(())
}
fn adjust_canonicalization(p: PathBuf) -> String {
const VERBATIM_PREFIX: &str = r#"\\?\"#;
let p = p.to_str().unwrap_or_default();
let p = if p.starts_with(VERBATIM_PREFIX) {
p.strip_prefix(VERBATIM_PREFIX).unwrap_or_default()
} else {
p
};
p.to_owned()
}
pub fn install_service(config_file: Option<PathBuf>) -> Result {
let manager_access = ServiceManagerAccess::CONNECT | ServiceManagerAccess::CREATE_SERVICE;
let service_manager = ServiceManager::local_computer(None::<&str>, manager_access)?;
let service_binary_path = std::env::current_exe().unwrap().with_file_name(SERVICE_EXE);
let mut service_binary_arguments = vec![OsString::from("--windows-service=true")];
if let Some(f) = config_file {
let f = adjust_canonicalization(f);
if !f.is_empty() {
service_binary_arguments.push(OsString::from(["--config-file=", &f].concat()));
}
}
let service_info = ServiceInfo {
name: OsString::from(SERVICE_NAME),
display_name: OsString::from(SERVICE_DISPLAY_NAME),
service_type: SERVICE_TYPE,
start_type: ServiceStartType::OnDemand,
error_control: ServiceErrorControl::Normal,
executable_path: service_binary_path,
launch_arguments: service_binary_arguments,
dependencies: vec![],
account_name: None, account_password: None,
};
let service = service_manager.create_service(&service_info, ServiceAccess::CHANGE_CONFIG)?;
service.set_description(SERVICE_DESC)?;
println!(
"Windows Service ({}) is installed successfully!",
SERVICE_NAME
);
println!(
"Start the service typing: sc.exe start \"{}\" (it requires administrator privileges) or using the 'services.msc' application.",
SERVICE_NAME
);
Ok(())
}
pub fn uninstall_service() -> Result {
let manager_access = ServiceManagerAccess::CONNECT;
let service_manager = ServiceManager::local_computer(None::<&str>, manager_access)?;
let service_access = ServiceAccess::QUERY_STATUS | ServiceAccess::STOP | ServiceAccess::DELETE;
let service = service_manager.open_service(SERVICE_NAME, service_access)?;
let service_status = service.query_status()?;
if service_status.current_state != ServiceState::Stopped {
service.stop()?;
thread::sleep(Duration::from_secs(1));
}
service.delete()?;
println!("Windows Service ({}) is uninstalled!", SERVICE_NAME);
Ok(())
}