init
This commit is contained in:
commit
5e65b44adf
1
.gitignore
vendored
Normal file
1
.gitignore
vendored
Normal file
@ -0,0 +1 @@
|
|||||||
|
/target
|
1031
Cargo.lock
generated
Normal file
1031
Cargo.lock
generated
Normal file
File diff suppressed because it is too large
Load Diff
15
Cargo.toml
Normal file
15
Cargo.toml
Normal file
@ -0,0 +1,15 @@
|
|||||||
|
[package]
|
||||||
|
name = "windows-service-test"
|
||||||
|
version = "0.1.0"
|
||||||
|
edition = "2021"
|
||||||
|
|
||||||
|
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
|
||||||
|
|
||||||
|
[dependencies]
|
||||||
|
tracing = "0.1"
|
||||||
|
tracing-subscriber = "0.3"
|
||||||
|
tracing-appender = "0.2"
|
||||||
|
windows-service = "0.5"
|
||||||
|
tokio = { version = "1", features = ["full"] }
|
||||||
|
error-stack = "0.2"
|
||||||
|
axum = "0.6"
|
31
src/main.rs
Normal file
31
src/main.rs
Normal file
@ -0,0 +1,31 @@
|
|||||||
|
use tracing::error;
|
||||||
|
|
||||||
|
mod service;
|
||||||
|
|
||||||
|
#[tokio::main]
|
||||||
|
async fn main() {
|
||||||
|
let file_appender = tracing_appender::rolling::hourly(
|
||||||
|
&std::env::current_exe()
|
||||||
|
.unwrap()
|
||||||
|
.parent()
|
||||||
|
.unwrap()
|
||||||
|
.display()
|
||||||
|
.to_string(),
|
||||||
|
"prefix.log",
|
||||||
|
);
|
||||||
|
let (non_blocking, _guard) = tracing_appender::non_blocking(file_appender);
|
||||||
|
|
||||||
|
tracing_subscriber::fmt()
|
||||||
|
.with_max_level(tracing::Level::DEBUG)
|
||||||
|
.with_target(false)
|
||||||
|
.with_writer(non_blocking)
|
||||||
|
// .with_timer(timer)
|
||||||
|
.pretty()
|
||||||
|
.init();
|
||||||
|
|
||||||
|
if let Err(e) = service::run_service().await {
|
||||||
|
error!("{e:?}");
|
||||||
|
}
|
||||||
|
|
||||||
|
println!("end of running");
|
||||||
|
}
|
21
src/service/execute.rs
Normal file
21
src/service/execute.rs
Normal file
@ -0,0 +1,21 @@
|
|||||||
|
use axum::{routing::get, Router};
|
||||||
|
use error_stack::Report;
|
||||||
|
use tokio::sync::mpsc::Receiver;
|
||||||
|
|
||||||
|
use super::ServiceError;
|
||||||
|
|
||||||
|
pub(super) async fn execute(mut stop_signal: Receiver<()>) -> Result<(), Report<ServiceError>> {
|
||||||
|
// build our application with a single route
|
||||||
|
let app = Router::new().route("/", get(|| async { "Hello, World!" }));
|
||||||
|
|
||||||
|
// run it with hyper on localhost:3000
|
||||||
|
axum::Server::bind(&"0.0.0.0:3000".parse().unwrap())
|
||||||
|
.serve(app.into_make_service())
|
||||||
|
.with_graceful_shutdown(async {
|
||||||
|
stop_signal.recv().await;
|
||||||
|
})
|
||||||
|
.await
|
||||||
|
.unwrap();
|
||||||
|
|
||||||
|
Ok(())
|
||||||
|
}
|
125
src/service/mod.rs
Normal file
125
src/service/mod.rs
Normal file
@ -0,0 +1,125 @@
|
|||||||
|
mod execute;
|
||||||
|
#[cfg(windows)]
|
||||||
|
mod windows;
|
||||||
|
|
||||||
|
use std::fmt;
|
||||||
|
|
||||||
|
#[cfg(windows)]
|
||||||
|
use windows::service_main;
|
||||||
|
#[cfg(windows)]
|
||||||
|
use windows_service::define_windows_service;
|
||||||
|
|
||||||
|
use error_stack::{Context, Report};
|
||||||
|
|
||||||
|
use crate::service::execute::execute;
|
||||||
|
|
||||||
|
// Generate the windows service boilerplate.
|
||||||
|
// The boilerplate contains the low-level service entry function (ffi_service_main) that parses
|
||||||
|
// incoming service arguments into Vec<OsString> and passes them to user defined service
|
||||||
|
// entry (my_service_main).
|
||||||
|
#[cfg(windows)]
|
||||||
|
define_windows_service!(ffi_service_main, service_main);
|
||||||
|
|
||||||
|
#[derive(Debug)]
|
||||||
|
pub enum ServiceError {
|
||||||
|
Starting,
|
||||||
|
Installing,
|
||||||
|
Uninstalling,
|
||||||
|
}
|
||||||
|
impl fmt::Display for ServiceError {
|
||||||
|
fn fmt(&self, fmt: &mut fmt::Formatter<'_>) -> fmt::Result {
|
||||||
|
fmt.write_str(&match self {
|
||||||
|
Self::Starting => format!("Could not start {} service", env!("CARGO_PKG_NAME")),
|
||||||
|
Self::Installing => format!(
|
||||||
|
"Error on installing {} as a windows service",
|
||||||
|
env!("CARGO_PKG_NAME")
|
||||||
|
),
|
||||||
|
Self::Uninstalling => format!(
|
||||||
|
"Error on installing {} as a windows service",
|
||||||
|
env!("CARGO_PKG_NAME")
|
||||||
|
),
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
impl Context for ServiceError {}
|
||||||
|
|
||||||
|
#[cfg(not(windows))]
|
||||||
|
pub async fn run_service() -> Result<(), Report<ServiceStartingError>> {
|
||||||
|
// Create a channel to be able to poll a stop event from the service worker loop.
|
||||||
|
|
||||||
|
use std::sync::mpsc;
|
||||||
|
let (_shutdown_tx, shutdown_rx) = mpsc::channel();
|
||||||
|
|
||||||
|
// run service - blocking thread
|
||||||
|
execute(shutdown_rx).await
|
||||||
|
}
|
||||||
|
|
||||||
|
#[cfg(windows)]
|
||||||
|
pub async fn run_service() -> Result<(), Report<ServiceError>> {
|
||||||
|
// Get command line parameters
|
||||||
|
|
||||||
|
use error_stack::{IntoReport, ResultExt};
|
||||||
|
use windows_service::service_dispatcher;
|
||||||
|
let args: Vec<String> = std::env::args().collect();
|
||||||
|
|
||||||
|
let usage_output = format!(
|
||||||
|
"
|
||||||
|
DaRL - Data and Runtime Logger\n
|
||||||
|
Usage: {}.exe service [OPTIONS]\n
|
||||||
|
OPTIONS:
|
||||||
|
install \tCreate windows service
|
||||||
|
uninstall \tDelete windows service
|
||||||
|
",
|
||||||
|
env!("CARGO_PKG_NAME")
|
||||||
|
);
|
||||||
|
match args.get(1).map(|s| s.as_str()) {
|
||||||
|
Some("service") => match args.get(2).map(|s| s.as_str()) {
|
||||||
|
Some("install") => match windows::install() {
|
||||||
|
Ok(_) => println!("Successfully installed as service"),
|
||||||
|
Err(e) => println!("Error installing service: {e:?}"),
|
||||||
|
},
|
||||||
|
|
||||||
|
Some("uninstall") => match windows::uninstall() {
|
||||||
|
Ok(_) => println!("Successfully uninstalled"),
|
||||||
|
Err(e) => println!("Error uninstalling service: {e:?}"),
|
||||||
|
},
|
||||||
|
Some(_) => println!("{usage_output}"),
|
||||||
|
None => println!("{usage_output}"),
|
||||||
|
},
|
||||||
|
Some("--serviced") => {
|
||||||
|
// Register generated `ffi_service_main` with the system and start the service, blocking
|
||||||
|
// this thread until the service is stopped.
|
||||||
|
service_dispatcher::start(env!("CARGO_PKG_NAME"), ffi_service_main)
|
||||||
|
.into_report()
|
||||||
|
.change_context(ServiceError::Starting)?;
|
||||||
|
}
|
||||||
|
Some(_) => println!("{usage_output}"),
|
||||||
|
None => {
|
||||||
|
use tokio::sync::mpsc;
|
||||||
|
let (_shutdown_tx, shutdown_rx) = mpsc::channel(1);
|
||||||
|
execute(shutdown_rx).await?;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
#[cfg(test)]
|
||||||
|
mod print_tests {
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn print() {
|
||||||
|
let usage_output = format!(
|
||||||
|
"
|
||||||
|
DaRL - Data and Runtime Logger\n
|
||||||
|
Usage: {}.exe service [OPTIONS]\n
|
||||||
|
OPTIONS:
|
||||||
|
install \tCreate windows service
|
||||||
|
uninstall \tDelete windows service
|
||||||
|
",
|
||||||
|
env!("CARGO_PKG_NAME")
|
||||||
|
);
|
||||||
|
|
||||||
|
println!("{usage_output}");
|
||||||
|
}
|
||||||
|
}
|
47
src/service/windows/install.rs
Normal file
47
src/service/windows/install.rs
Normal file
@ -0,0 +1,47 @@
|
|||||||
|
use std::ffi::OsString;
|
||||||
|
|
||||||
|
use error_stack::{IntoReport, Report, ResultExt};
|
||||||
|
use windows_service::{
|
||||||
|
service::{ServiceAccess, ServiceErrorControl, ServiceInfo, ServiceStartType, ServiceType},
|
||||||
|
service_manager::{ServiceManager, ServiceManagerAccess},
|
||||||
|
};
|
||||||
|
|
||||||
|
use crate::service::ServiceError;
|
||||||
|
|
||||||
|
pub fn install() -> Result<(), Report<ServiceError>> {
|
||||||
|
let manager_access = ServiceManagerAccess::CONNECT | ServiceManagerAccess::CREATE_SERVICE;
|
||||||
|
let service_manager = ServiceManager::local_computer(None::<&str>, manager_access)
|
||||||
|
.into_report()
|
||||||
|
.change_context(ServiceError::Installing)?;
|
||||||
|
|
||||||
|
// This example installs the service defined in `examples/ping_service.rs`.
|
||||||
|
// In the real world code you would set the executable path to point to your own binary
|
||||||
|
// that implements windows service.
|
||||||
|
let service_binary_path = ::std::env::current_exe()
|
||||||
|
.into_report()
|
||||||
|
.change_context(ServiceError::Installing)?
|
||||||
|
.with_file_name(format!("{}.exe", env!("CARGO_PKG_NAME")));
|
||||||
|
|
||||||
|
let service_info = ServiceInfo {
|
||||||
|
name: OsString::from(env!("CARGO_PKG_NAME")),
|
||||||
|
display_name: OsString::from("DaRL"),
|
||||||
|
service_type: ServiceType::OWN_PROCESS,
|
||||||
|
start_type: ServiceStartType::AutoStart,
|
||||||
|
error_control: ServiceErrorControl::Normal,
|
||||||
|
executable_path: service_binary_path,
|
||||||
|
launch_arguments: vec!["--serviced".into()],
|
||||||
|
dependencies: vec![],
|
||||||
|
account_name: None, // run as System
|
||||||
|
account_password: None,
|
||||||
|
};
|
||||||
|
let service = service_manager
|
||||||
|
.create_service(&service_info, ServiceAccess::CHANGE_CONFIG)
|
||||||
|
.into_report()
|
||||||
|
.change_context(ServiceError::Installing)?;
|
||||||
|
|
||||||
|
service
|
||||||
|
.set_description("DaRL - Data and Runtime Logger service")
|
||||||
|
.into_report()
|
||||||
|
.change_context(ServiceError::Installing)?;
|
||||||
|
Ok(())
|
||||||
|
}
|
97
src/service/windows/mod.rs
Normal file
97
src/service/windows/mod.rs
Normal file
@ -0,0 +1,97 @@
|
|||||||
|
mod install;
|
||||||
|
mod uninstall;
|
||||||
|
|
||||||
|
use std::{ffi::OsString, time::Duration};
|
||||||
|
|
||||||
|
use error_stack::{IntoReport, Report, ResultExt};
|
||||||
|
pub(super) use install::install;
|
||||||
|
use tracing::error;
|
||||||
|
pub(super) use uninstall::uninstall;
|
||||||
|
use windows_service::{
|
||||||
|
service::{
|
||||||
|
ServiceControl, ServiceControlAccept, ServiceExitCode, ServiceState, ServiceStatus,
|
||||||
|
ServiceType,
|
||||||
|
},
|
||||||
|
service_control_handler::{self, ServiceControlHandlerResult},
|
||||||
|
};
|
||||||
|
|
||||||
|
use super::{execute, ServiceError};
|
||||||
|
|
||||||
|
// Service entry function which is called on background thread by the system with service
|
||||||
|
// parameters. There is no stdout or stderr at this point so make sure to configure the log
|
||||||
|
// output to file if needed.
|
||||||
|
pub fn service_main(_arguments: Vec<OsString>) {
|
||||||
|
if let Err(_e) = run_service() {
|
||||||
|
// Handle the error, by logging or something.
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn run_service() -> Result<(), Report<ServiceError>> {
|
||||||
|
// Create a channel to be able to poll a stop event from the service worker loop.
|
||||||
|
let (shutdown_tx, shutdown_rx) = tokio::sync::mpsc::channel(1);
|
||||||
|
|
||||||
|
// Define system service event handler that will be receiving service events.
|
||||||
|
let event_handler = move |control_event| -> ServiceControlHandlerResult {
|
||||||
|
match control_event {
|
||||||
|
// Notifies a service to report its current status information to the service
|
||||||
|
// control manager. Always return NoError even if not implemented.
|
||||||
|
ServiceControl::Interrogate => ServiceControlHandlerResult::NoError,
|
||||||
|
|
||||||
|
// Handle stop
|
||||||
|
ServiceControl::Stop => {
|
||||||
|
shutdown_tx.try_send(()).unwrap();
|
||||||
|
ServiceControlHandlerResult::NoError
|
||||||
|
}
|
||||||
|
|
||||||
|
_ => ServiceControlHandlerResult::NotImplemented,
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
// Register system service event handler.
|
||||||
|
// The returned status handle should be used to report service status changes to the system.
|
||||||
|
let status_handle = service_control_handler::register(env!("CARGO_PKG_NAME"), event_handler)
|
||||||
|
.into_report()
|
||||||
|
.change_context(ServiceError::Starting)?;
|
||||||
|
|
||||||
|
// Tell the system that service is running
|
||||||
|
status_handle
|
||||||
|
.set_service_status(ServiceStatus {
|
||||||
|
service_type: ServiceType::OWN_PROCESS,
|
||||||
|
current_state: ServiceState::Running,
|
||||||
|
controls_accepted: ServiceControlAccept::STOP,
|
||||||
|
exit_code: ServiceExitCode::Win32(0),
|
||||||
|
checkpoint: 0,
|
||||||
|
wait_hint: Duration::default(),
|
||||||
|
process_id: None,
|
||||||
|
})
|
||||||
|
.into_report()
|
||||||
|
.change_context(ServiceError::Starting)?;
|
||||||
|
|
||||||
|
// run service - blocking thread
|
||||||
|
let rt = tokio::runtime::Builder::new_current_thread()
|
||||||
|
.enable_all()
|
||||||
|
.build()
|
||||||
|
.unwrap();
|
||||||
|
|
||||||
|
rt.block_on(async move {
|
||||||
|
if let Err(e) = execute(shutdown_rx).await {
|
||||||
|
error!("{e:?}");
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
// Tell the system that service has stopped.
|
||||||
|
status_handle
|
||||||
|
.set_service_status(ServiceStatus {
|
||||||
|
service_type: ServiceType::OWN_PROCESS,
|
||||||
|
current_state: ServiceState::Stopped,
|
||||||
|
controls_accepted: ServiceControlAccept::empty(),
|
||||||
|
exit_code: ServiceExitCode::Win32(0),
|
||||||
|
checkpoint: 0,
|
||||||
|
wait_hint: Duration::default(),
|
||||||
|
process_id: None,
|
||||||
|
})
|
||||||
|
.into_report()
|
||||||
|
.change_context(ServiceError::Starting)?;
|
||||||
|
|
||||||
|
Ok(())
|
||||||
|
}
|
42
src/service/windows/uninstall.rs
Normal file
42
src/service/windows/uninstall.rs
Normal file
@ -0,0 +1,42 @@
|
|||||||
|
use std::{thread, time::Duration};
|
||||||
|
|
||||||
|
use error_stack::{IntoReport, Report, ResultExt};
|
||||||
|
use windows_service::{
|
||||||
|
service::{ServiceAccess, ServiceState},
|
||||||
|
service_manager::{ServiceManager, ServiceManagerAccess},
|
||||||
|
};
|
||||||
|
|
||||||
|
use crate::service::ServiceError;
|
||||||
|
|
||||||
|
pub fn uninstall() -> Result<(), Report<ServiceError>> {
|
||||||
|
let manager_access = ServiceManagerAccess::CONNECT;
|
||||||
|
let service_manager = ServiceManager::local_computer(None::<&str>, manager_access)
|
||||||
|
.into_report()
|
||||||
|
.change_context(ServiceError::Uninstalling)?;
|
||||||
|
|
||||||
|
let service_access = ServiceAccess::QUERY_STATUS | ServiceAccess::STOP | ServiceAccess::DELETE;
|
||||||
|
let service = service_manager
|
||||||
|
.open_service(env!("CARGO_PKG_NAME"), service_access)
|
||||||
|
.into_report()
|
||||||
|
.change_context(ServiceError::Uninstalling)?;
|
||||||
|
|
||||||
|
let service_status = service
|
||||||
|
.query_status()
|
||||||
|
.into_report()
|
||||||
|
.change_context(ServiceError::Uninstalling)?;
|
||||||
|
if service_status.current_state != ServiceState::Stopped {
|
||||||
|
service
|
||||||
|
.stop()
|
||||||
|
.into_report()
|
||||||
|
.change_context(ServiceError::Uninstalling)?;
|
||||||
|
// Wait for service to stop
|
||||||
|
thread::sleep(Duration::from_secs(1));
|
||||||
|
}
|
||||||
|
|
||||||
|
service
|
||||||
|
.delete()
|
||||||
|
.into_report()
|
||||||
|
.change_context(ServiceError::Uninstalling)?;
|
||||||
|
|
||||||
|
Ok(())
|
||||||
|
}
|
Loading…
Reference in New Issue
Block a user