This commit is contained in:
Larsiiii 2023-01-22 18:16:31 +01:00
commit 5e65b44adf
9 changed files with 1410 additions and 0 deletions

1
.gitignore vendored Normal file
View File

@ -0,0 +1 @@
/target

1031
Cargo.lock generated Normal file

File diff suppressed because it is too large Load Diff

15
Cargo.toml Normal file
View 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
View 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
View 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
View 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}");
}
}

View 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(())
}

View 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(())
}

View 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(())
}