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