init
This commit is contained in:
commit
5e65b44adf
|
@ -0,0 +1 @@
|
|||
/target
|
File diff suppressed because it is too large
Load Diff
|
@ -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"
|
|
@ -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");
|
||||
}
|
|
@ -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(())
|
||||
}
|
|
@ -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}");
|
||||
}
|
||||
}
|
|
@ -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(())
|
||||
}
|
|
@ -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(())
|
||||
}
|
|
@ -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