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