windows service
This commit is contained in:
		
							parent
							
								
									47af1e62b7
								
							
						
					
					
						commit
						96f7fead42
					
				@ -8,12 +8,10 @@ use routes::api_keys::models::ApiKey;
 | 
				
			|||||||
use sqlx::PgPool;
 | 
					use sqlx::PgPool;
 | 
				
			||||||
use tokio::net::TcpListener;
 | 
					use tokio::net::TcpListener;
 | 
				
			||||||
use tokio_util::sync::CancellationToken;
 | 
					use tokio_util::sync::CancellationToken;
 | 
				
			||||||
use tracing::{info, level_filters::LevelFilter, Level};
 | 
					use tracing::info;
 | 
				
			||||||
use tracing_rolling_file::{RollingConditionBase, RollingFileAppenderBase};
 | 
					 | 
				
			||||||
use tracing_subscriber::{fmt, layer::SubscriberExt, util::SubscriberInitExt};
 | 
					 | 
				
			||||||
 | 
					
 | 
				
			||||||
use crate::APP_NAME;
 | 
					use crate::APP_NAME;
 | 
				
			||||||
use crate::{config::Configuration, errors::AppError, ROOT_PATH};
 | 
					use crate::{config::Configuration, errors::AppError};
 | 
				
			||||||
 | 
					
 | 
				
			||||||
pub mod backend;
 | 
					pub mod backend;
 | 
				
			||||||
mod description;
 | 
					mod description;
 | 
				
			||||||
@ -23,44 +21,8 @@ pub async fn start(
 | 
				
			|||||||
    config: &Configuration,
 | 
					    config: &Configuration,
 | 
				
			||||||
    cancellation_token: CancellationToken,
 | 
					    cancellation_token: CancellationToken,
 | 
				
			||||||
) -> Result<(), Report<AppError>> {
 | 
					) -> Result<(), Report<AppError>> {
 | 
				
			||||||
    // Set Report Colour Mode to NONE
 | 
					 | 
				
			||||||
    Report::set_color_mode(error_stack::fmt::ColorMode::None);
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    let level_filter = LevelFilter::from_level(match config.debug {
 | 
					 | 
				
			||||||
        true => Level::DEBUG,
 | 
					 | 
				
			||||||
        false => Level::INFO,
 | 
					 | 
				
			||||||
    });
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    // Prepare logging to file
 | 
					 | 
				
			||||||
    let file_appender = RollingFileAppenderBase::new(
 | 
					 | 
				
			||||||
        ROOT_PATH.with_file_name(format!("{}.log", env!("CARGO_PKG_NAME"))),
 | 
					 | 
				
			||||||
        RollingConditionBase::new().max_size(1024 * 1024 * 2),
 | 
					 | 
				
			||||||
        5,
 | 
					 | 
				
			||||||
    )
 | 
					 | 
				
			||||||
    .change_context(AppError)?;
 | 
					 | 
				
			||||||
    let (non_blocking, _guard) = tracing_appender::non_blocking(file_appender);
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    // prepare initialization of logging
 | 
					 | 
				
			||||||
    let log_layers = tracing_subscriber::registry().with(level_filter).with(
 | 
					 | 
				
			||||||
        fmt::Layer::default()
 | 
					 | 
				
			||||||
            .with_target(false)
 | 
					 | 
				
			||||||
            .with_ansi(false)
 | 
					 | 
				
			||||||
            .with_writer(non_blocking),
 | 
					 | 
				
			||||||
    );
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    // also log to console in debug mode
 | 
					 | 
				
			||||||
    #[cfg(debug_assertions)]
 | 
					 | 
				
			||||||
    let stdout_log = tracing_subscriber::fmt::layer().pretty();
 | 
					 | 
				
			||||||
    #[cfg(debug_assertions)]
 | 
					 | 
				
			||||||
    let log_layers = log_layers.with(stdout_log);
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    // Initialize logging
 | 
					 | 
				
			||||||
    log_layers.init();
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    // Initialize database pool
 | 
					    // Initialize database pool
 | 
				
			||||||
    let pool = PgPool::connect(&config.database_url)
 | 
					    let pool = PgPool::connect_lazy(&config.database_url).change_context(AppError)?;
 | 
				
			||||||
        .await
 | 
					 | 
				
			||||||
        .change_context(AppError)?;
 | 
					 | 
				
			||||||
    // Get API Keys from database
 | 
					    // Get API Keys from database
 | 
				
			||||||
    let api_keys = ApiKey::get_all_with_secret_attached(&pool, config)
 | 
					    let api_keys = ApiKey::get_all_with_secret_attached(&pool, config)
 | 
				
			||||||
        .await
 | 
					        .await
 | 
				
			||||||
 | 
				
			|||||||
							
								
								
									
										234
									
								
								src/cli.rs
									
									
									
									
									
								
							
							
						
						
									
										234
									
								
								src/cli.rs
									
									
									
									
									
								
							@ -44,7 +44,7 @@ enum Commands {
 | 
				
			|||||||
#[derive(Debug, Subcommand)]
 | 
					#[derive(Debug, Subcommand)]
 | 
				
			||||||
enum ServiceCommands {
 | 
					enum ServiceCommands {
 | 
				
			||||||
    Install,
 | 
					    Install,
 | 
				
			||||||
    Uninstall,
 | 
					    Remove,
 | 
				
			||||||
    Run,
 | 
					    Run,
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@ -93,22 +93,35 @@ impl Cli {
 | 
				
			|||||||
                }
 | 
					                }
 | 
				
			||||||
                Commands::Service(service_commands) => match service_commands {
 | 
					                Commands::Service(service_commands) => match service_commands {
 | 
				
			||||||
                    ServiceCommands::Install => {
 | 
					                    ServiceCommands::Install => {
 | 
				
			||||||
                        // TODO do things
 | 
					                        #[cfg(windows)]
 | 
				
			||||||
 | 
					                        windows::install_service()?;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					                        // Print success message
 | 
				
			||||||
 | 
					                        println!("Succssfully installed service {APP_NAME}");
 | 
				
			||||||
 | 
					
 | 
				
			||||||
                        Ok(DaemonStatus::NotRunning)
 | 
					                        Ok(DaemonStatus::NotRunning)
 | 
				
			||||||
                    }
 | 
					                    }
 | 
				
			||||||
                    ServiceCommands::Uninstall => {
 | 
					                    ServiceCommands::Remove => {
 | 
				
			||||||
                        // TODO do things
 | 
					                        #[cfg(windows)]
 | 
				
			||||||
 | 
					                        windows::uninstall_service()?;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					                        // Print success message
 | 
				
			||||||
 | 
					                        println!("Succssfully removed service {APP_NAME}");
 | 
				
			||||||
 | 
					
 | 
				
			||||||
                        Ok(DaemonStatus::NotRunning)
 | 
					                        Ok(DaemonStatus::NotRunning)
 | 
				
			||||||
                    }
 | 
					                    }
 | 
				
			||||||
                    ServiceCommands::Run => {
 | 
					                    ServiceCommands::Run => {
 | 
				
			||||||
                        // TODO do things
 | 
					 | 
				
			||||||
                        // create shutdown signals
 | 
					 | 
				
			||||||
                        // let (shutdown_send, mut shutdown_recv) = mpsc::unbounded_channel();
 | 
					 | 
				
			||||||
                        // Register generated `ffi_service_main` with the system and start the service, blocking
 | 
					 | 
				
			||||||
                        // this thread until the service is stopped.
 | 
					 | 
				
			||||||
                        #[cfg(windows)]
 | 
					                        #[cfg(windows)]
 | 
				
			||||||
                        let _ = service_dispatcher::start(env!("CARGO_PKG_NAME"), ffi_service_main);
 | 
					                        {
 | 
				
			||||||
                        Ok(DaemonStatus::NotRunning)
 | 
					                            windows::run()?;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					                            Ok(DaemonStatus::NotRunning)
 | 
				
			||||||
 | 
					                        }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					                        #[cfg(not(windows))]
 | 
				
			||||||
 | 
					                        {
 | 
				
			||||||
 | 
					                            Ok(DaemonStatus::NotRunning)
 | 
				
			||||||
 | 
					                        }
 | 
				
			||||||
                    }
 | 
					                    }
 | 
				
			||||||
                },
 | 
					                },
 | 
				
			||||||
                Commands::Create(create_commands) => match create_commands {
 | 
					                Commands::Create(create_commands) => match create_commands {
 | 
				
			||||||
@ -160,18 +173,30 @@ async fn start_service(
 | 
				
			|||||||
mod windows {
 | 
					mod windows {
 | 
				
			||||||
    use error_stack::{Report, ResultExt};
 | 
					    use error_stack::{Report, ResultExt};
 | 
				
			||||||
    use std::{ffi::OsString, thread, time::Duration};
 | 
					    use std::{ffi::OsString, thread, time::Duration};
 | 
				
			||||||
 | 
					    use tokio_util::task::TaskTracker;
 | 
				
			||||||
 | 
					    use tracing::{error, info};
 | 
				
			||||||
    use windows_service::{
 | 
					    use windows_service::{
 | 
				
			||||||
 | 
					        define_windows_service,
 | 
				
			||||||
        service::{
 | 
					        service::{
 | 
				
			||||||
            ServiceAccess, ServiceErrorControl, ServiceInfo, ServiceStartType, ServiceState,
 | 
					            ServiceAccess, ServiceControl, ServiceControlAccept, ServiceErrorControl,
 | 
				
			||||||
 | 
					            ServiceExitCode, ServiceInfo, ServiceStartType, ServiceState, ServiceStatus,
 | 
				
			||||||
            ServiceType,
 | 
					            ServiceType,
 | 
				
			||||||
        },
 | 
					        },
 | 
				
			||||||
 | 
					        service_control_handler::{self, ServiceControlHandlerResult},
 | 
				
			||||||
 | 
					        service_dispatcher,
 | 
				
			||||||
        service_manager::{ServiceManager, ServiceManagerAccess},
 | 
					        service_manager::{ServiceManager, ServiceManagerAccess},
 | 
				
			||||||
    };
 | 
					    };
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    use crate::errors::AppError;
 | 
					    use crate::{
 | 
				
			||||||
    use crate::APP_NAME;
 | 
					        cli::{start_service, DaemonStatus},
 | 
				
			||||||
 | 
					        APP_NAME, ROOT_PATH,
 | 
				
			||||||
 | 
					    };
 | 
				
			||||||
 | 
					    use crate::{config::Configuration, errors::AppError};
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    pub fn install() -> Result<(), Report<AppError>> {
 | 
					    const SERVICE_NAME: &str = "GenericApiService";
 | 
				
			||||||
 | 
					    const SERVICE_DISPLAY_NAME: &str = APP_NAME;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    pub fn install_service() -> Result<(), Report<AppError>> {
 | 
				
			||||||
        let manager_access = ServiceManagerAccess::CONNECT | ServiceManagerAccess::CREATE_SERVICE;
 | 
					        let manager_access = ServiceManagerAccess::CONNECT | ServiceManagerAccess::CREATE_SERVICE;
 | 
				
			||||||
        let service_manager = ServiceManager::local_computer(None::<&str>, manager_access)
 | 
					        let service_manager = ServiceManager::local_computer(None::<&str>, manager_access)
 | 
				
			||||||
            .change_context(AppError)?;
 | 
					            .change_context(AppError)?;
 | 
				
			||||||
@ -184,30 +209,28 @@ mod windows {
 | 
				
			|||||||
            .with_file_name(format!("{}.exe", env!("CARGO_PKG_NAME")));
 | 
					            .with_file_name(format!("{}.exe", env!("CARGO_PKG_NAME")));
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        let service_info = ServiceInfo {
 | 
					        let service_info = ServiceInfo {
 | 
				
			||||||
            name: OsString::from(env!("CARGO_PKG_NAME")),
 | 
					            name: SERVICE_NAME.into(),
 | 
				
			||||||
            display_name: OsString::from(&format!("{APP_NAME}")),
 | 
					            display_name: SERVICE_DISPLAY_NAME.into(),
 | 
				
			||||||
            service_type: ServiceType::OWN_PROCESS,
 | 
					            service_type: ServiceType::OWN_PROCESS,
 | 
				
			||||||
            start_type: ServiceStartType::AutoStart,
 | 
					            start_type: ServiceStartType::AutoStart,
 | 
				
			||||||
            error_control: ServiceErrorControl::Normal,
 | 
					            error_control: ServiceErrorControl::Normal,
 | 
				
			||||||
            executable_path: service_binary_path,
 | 
					            executable_path: service_binary_path,
 | 
				
			||||||
            launch_arguments: vec!["service run".into()],
 | 
					            launch_arguments: vec!["service".into(), "run".into()],
 | 
				
			||||||
            dependencies: vec![],
 | 
					            dependencies: vec![],
 | 
				
			||||||
            account_name: None, // run as System
 | 
					            account_name: None, // run as System
 | 
				
			||||||
            account_password: None,
 | 
					            account_password: None,
 | 
				
			||||||
        };
 | 
					        };
 | 
				
			||||||
        let service = service_manager
 | 
					        let service = service_manager
 | 
				
			||||||
            .create_service(&service_info, ServiceAccess::CHANGE_CONFIG)
 | 
					            .create_service(&service_info, ServiceAccess::CHANGE_CONFIG)
 | 
				
			||||||
            .into_report()
 | 
					 | 
				
			||||||
            .change_context(AppError)?;
 | 
					            .change_context(AppError)?;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        service
 | 
					        service
 | 
				
			||||||
            .set_description(format!("{APP_NAME}"))
 | 
					            .set_description(format!("{APP_NAME}"))
 | 
				
			||||||
            .into_report()
 | 
					 | 
				
			||||||
            .change_context(AppError)?;
 | 
					            .change_context(AppError)?;
 | 
				
			||||||
        Ok(())
 | 
					        Ok(())
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    pub fn remove() -> Result<(), Report<AppError>> {
 | 
					    pub fn uninstall_service() -> Result<(), Report<AppError>> {
 | 
				
			||||||
        let manager_access = ServiceManagerAccess::CONNECT;
 | 
					        let manager_access = ServiceManagerAccess::CONNECT;
 | 
				
			||||||
        let service_manager = ServiceManager::local_computer(None::<&str>, manager_access)
 | 
					        let service_manager = ServiceManager::local_computer(None::<&str>, manager_access)
 | 
				
			||||||
            .change_context(AppError)?;
 | 
					            .change_context(AppError)?;
 | 
				
			||||||
@ -215,7 +238,7 @@ mod windows {
 | 
				
			|||||||
        let service_access =
 | 
					        let service_access =
 | 
				
			||||||
            ServiceAccess::QUERY_STATUS | ServiceAccess::STOP | ServiceAccess::DELETE;
 | 
					            ServiceAccess::QUERY_STATUS | ServiceAccess::STOP | ServiceAccess::DELETE;
 | 
				
			||||||
        let service = service_manager
 | 
					        let service = service_manager
 | 
				
			||||||
            .open_service(env!("CARGO_PKG_NAME"), service_access)
 | 
					            .open_service(SERVICE_NAME, service_access)
 | 
				
			||||||
            .change_context(AppError)?;
 | 
					            .change_context(AppError)?;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        let service_status = service.query_status().change_context(AppError)?;
 | 
					        let service_status = service.query_status().change_context(AppError)?;
 | 
				
			||||||
@ -225,86 +248,121 @@ mod windows {
 | 
				
			|||||||
            thread::sleep(Duration::from_secs(1));
 | 
					            thread::sleep(Duration::from_secs(1));
 | 
				
			||||||
        }
 | 
					        }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        service.delete().into_report().change_context(AppError)?;
 | 
					        service.delete().change_context(AppError)?;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        Ok(())
 | 
					        Ok(())
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    // Service entry function which is called on background thread by the system with service
 | 
					    pub fn run() -> Result<(), Report<AppError>> {
 | 
				
			||||||
    // parameters. There is no stdout or stderr at this point so make sure to configure the log
 | 
					        // Generate the windows service boilerplate.
 | 
				
			||||||
    // output to file if needed.
 | 
					        // The boilerplate contains the low-level service entry function (ffi_service_main) that parses
 | 
				
			||||||
    pub fn service_main(_arguments: Vec<OsString>) {
 | 
					        // incoming service arguments into Vec<OsString> and passes them to user defined service
 | 
				
			||||||
        if let Err(_e) = run_service() {
 | 
					        // entry (my_service_main).
 | 
				
			||||||
            // Handle the error, by logging or something.
 | 
					        define_windows_service!(ffi_service_main, service_main);
 | 
				
			||||||
        }
 | 
					 | 
				
			||||||
    }
 | 
					 | 
				
			||||||
 | 
					
 | 
				
			||||||
    fn run_service() -> Result<(), Report<AppError>> {
 | 
					        fn run_service() -> Result<(), Report<AppError>> {
 | 
				
			||||||
        // Create a channel to be able to poll a stop event from the service worker loop.
 | 
					            // 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);
 | 
					            let (shutdown_tx, shutdown_rx) = tokio::sync::mpsc::unbounded_channel();
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        // Define system service event handler that will be receiving service events.
 | 
					            // Define system service event handler that will be receiving service events.
 | 
				
			||||||
        let event_handler = move |control_event| -> ServiceControlHandlerResult {
 | 
					            let event_handler = move |control_event| -> ServiceControlHandlerResult {
 | 
				
			||||||
            match control_event {
 | 
					                match control_event {
 | 
				
			||||||
                // Notifies a service to report its current status information to the service
 | 
					                    // Notifies a service to report its current status information to the service
 | 
				
			||||||
                // control manager. Always return NoError even if not implemented.
 | 
					                    // control manager. Always return NoError even if not implemented.
 | 
				
			||||||
                ServiceControl::Interrogate => ServiceControlHandlerResult::NoError,
 | 
					                    ServiceControl::Interrogate => ServiceControlHandlerResult::NoError,
 | 
				
			||||||
 | 
					
 | 
				
			||||||
                // Handle stop
 | 
					                    // Handle stop
 | 
				
			||||||
                ServiceControl::Stop => {
 | 
					                    ServiceControl::Stop => {
 | 
				
			||||||
                    shutdown_tx.try_send(()).unwrap();
 | 
					                        let _ = shutdown_tx.send(());
 | 
				
			||||||
                    ServiceControlHandlerResult::NoError
 | 
					                        ServiceControlHandlerResult::NoError
 | 
				
			||||||
 | 
					                    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					                    _ => ServiceControlHandlerResult::NotImplemented,
 | 
				
			||||||
                }
 | 
					                }
 | 
				
			||||||
 | 
					            };
 | 
				
			||||||
 | 
					
 | 
				
			||||||
                _ => 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(SERVICE_NAME, event_handler)
 | 
				
			||||||
 | 
					 | 
				
			||||||
        // 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)
 | 
					 | 
				
			||||||
                .change_context(AppError)?;
 | 
					                .change_context(AppError)?;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        // Tell the system that service is running
 | 
					            // Tell the system that service is running
 | 
				
			||||||
        status_handle
 | 
					            status_handle
 | 
				
			||||||
            .set_service_status(ServiceStatus {
 | 
					                .set_service_status(ServiceStatus {
 | 
				
			||||||
                service_type: ServiceType::OWN_PROCESS,
 | 
					                    service_type: ServiceType::OWN_PROCESS,
 | 
				
			||||||
                current_state: ServiceState::Running,
 | 
					                    current_state: ServiceState::Running,
 | 
				
			||||||
                controls_accepted: ServiceControlAccept::STOP,
 | 
					                    controls_accepted: ServiceControlAccept::STOP,
 | 
				
			||||||
                exit_code: ServiceExitCode::Win32(0),
 | 
					                    exit_code: ServiceExitCode::Win32(0),
 | 
				
			||||||
                checkpoint: 0,
 | 
					                    checkpoint: 0,
 | 
				
			||||||
                wait_hint: Duration::default(),
 | 
					                    wait_hint: Duration::default(),
 | 
				
			||||||
                process_id: None,
 | 
					                    process_id: None,
 | 
				
			||||||
            })
 | 
					                })
 | 
				
			||||||
            .change_context(AppError)?;
 | 
					                .change_context(AppError)?;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        // run service - blocking thread
 | 
					            // load config file
 | 
				
			||||||
        let rt = tokio::runtime::Builder::new_current_thread()
 | 
					            let config = Configuration::new(ROOT_PATH.with_file_name("config.toml"))?;
 | 
				
			||||||
            .enable_all()
 | 
					            config.check()?;
 | 
				
			||||||
            .build()
 | 
					 | 
				
			||||||
            .unwrap();
 | 
					 | 
				
			||||||
 | 
					
 | 
				
			||||||
        rt.block_on(async move {
 | 
					            // run service - blocking thread
 | 
				
			||||||
            if let Err(e) = execute(shutdown_rx).await {
 | 
					            let rt = tokio::runtime::Builder::new_current_thread()
 | 
				
			||||||
                error!("{e:?}");
 | 
					                .enable_all()
 | 
				
			||||||
 | 
					                .build()
 | 
				
			||||||
 | 
					                .unwrap();
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					            rt.block_on(async move {
 | 
				
			||||||
 | 
					                // create task tracker
 | 
				
			||||||
 | 
					                let tracker = TaskTracker::new();
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					                match start_service(&config, Some(shutdown_rx)).await {
 | 
				
			||||||
 | 
					                    Ok(DaemonStatus::Running((cancellation_token, Some(mut shutdown_rx)))) => {
 | 
				
			||||||
 | 
					                        // wait for shutdown signal
 | 
				
			||||||
 | 
					                        shutdown_rx.recv().await;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					                        // send shutdown signal to application and wait
 | 
				
			||||||
 | 
					                        cancellation_token.cancel();
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					                        // close task tracker
 | 
				
			||||||
 | 
					                        tracker.close();
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					                        // Wait for everything to finish.
 | 
				
			||||||
 | 
					                        tracker.wait().await;
 | 
				
			||||||
 | 
					                        tokio::time::sleep(Duration::from_millis(200)).await;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					                        info!("{APP_NAME} gracefully shut down.");
 | 
				
			||||||
 | 
					                    }
 | 
				
			||||||
 | 
					                    Err(e) => error!("{e:?}"),
 | 
				
			||||||
 | 
					                    _ => unreachable!(),
 | 
				
			||||||
 | 
					                }
 | 
				
			||||||
 | 
					            });
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					            // 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,
 | 
				
			||||||
 | 
					                })
 | 
				
			||||||
 | 
					                .change_context(AppError)?;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					            Ok(())
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        // 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.
 | 
				
			||||||
 | 
					                error!("Error executing windows service: {e}")
 | 
				
			||||||
            }
 | 
					            }
 | 
				
			||||||
        });
 | 
					        }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        // Tell the system that service has stopped.
 | 
					        // Register generated `ffi_service_main` with the system and start the service, blocking
 | 
				
			||||||
        status_handle
 | 
					        // this thread until the service is stopped.
 | 
				
			||||||
            .set_service_status(ServiceStatus {
 | 
					        service_dispatcher::start(SERVICE_NAME, ffi_service_main).change_context(AppError)
 | 
				
			||||||
                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(())
 | 
					 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
				
			|||||||
							
								
								
									
										52
									
								
								src/main.rs
									
									
									
									
									
								
							
							
						
						
									
										52
									
								
								src/main.rs
									
									
									
									
									
								
							@ -1,12 +1,14 @@
 | 
				
			|||||||
use std::{path::PathBuf, time::Duration};
 | 
					use std::{path::PathBuf, time::Duration};
 | 
				
			||||||
 | 
					
 | 
				
			||||||
use clap::Parser;
 | 
					use clap::Parser;
 | 
				
			||||||
use error_stack::Report;
 | 
					use error_stack::{Report, ResultExt};
 | 
				
			||||||
use errors::AppError;
 | 
					use errors::AppError;
 | 
				
			||||||
use once_cell::sync::Lazy;
 | 
					use once_cell::sync::Lazy;
 | 
				
			||||||
use tokio::signal;
 | 
					use tokio::signal;
 | 
				
			||||||
use tokio_util::task::TaskTracker;
 | 
					use tokio_util::task::TaskTracker;
 | 
				
			||||||
use tracing::{error, info};
 | 
					use tracing::{error, info, level_filters::LevelFilter, Level};
 | 
				
			||||||
 | 
					use tracing_rolling_file::{RollingConditionBase, RollingFileAppenderBase};
 | 
				
			||||||
 | 
					use tracing_subscriber::{fmt, layer::SubscriberExt, util::SubscriberInitExt};
 | 
				
			||||||
 | 
					
 | 
				
			||||||
pub mod api;
 | 
					pub mod api;
 | 
				
			||||||
mod cli;
 | 
					mod cli;
 | 
				
			||||||
@ -29,17 +31,61 @@ pub const APP_NAME: &str = "Generic API Service";
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
#[tokio::main]
 | 
					#[tokio::main]
 | 
				
			||||||
async fn main() -> Result<(), Report<AppError>> {
 | 
					async fn main() -> Result<(), Report<AppError>> {
 | 
				
			||||||
 | 
					    let run_exit_code = run().await;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    if let Err(e) = run_exit_code.as_ref() {
 | 
				
			||||||
 | 
					        error!("{e}");
 | 
				
			||||||
 | 
					        tokio::time::sleep(Duration::from_secs(1)).await;
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    run_exit_code
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					async fn run() -> Result<(), Report<AppError>> {
 | 
				
			||||||
    dotenv::dotenv().ok();
 | 
					    dotenv::dotenv().ok();
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    // Set Report Colour Mode to NONE
 | 
				
			||||||
 | 
					    Report::set_color_mode(error_stack::fmt::ColorMode::None);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    // prepare CLI
 | 
					    // prepare CLI
 | 
				
			||||||
    let cli = cli::Cli::parse();
 | 
					    let cli = cli::Cli::parse();
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    // load config file
 | 
					    // load config file
 | 
				
			||||||
    let mut config = config::Configuration::new(cli.config())?;
 | 
					    let mut config = config::Configuration::new(cli.config())?;
 | 
				
			||||||
    config.check()?;
 | 
					    config.check()?;
 | 
				
			||||||
 | 
					 | 
				
			||||||
    println!("{config:?}");
 | 
					    println!("{config:?}");
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    let level_filter = LevelFilter::from_level(match config.debug {
 | 
				
			||||||
 | 
					        true => Level::DEBUG,
 | 
				
			||||||
 | 
					        false => Level::INFO,
 | 
				
			||||||
 | 
					    });
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    // Prepare logging to file
 | 
				
			||||||
 | 
					    let file_appender = RollingFileAppenderBase::new(
 | 
				
			||||||
 | 
					        ROOT_PATH.with_file_name(format!("{}.log", env!("CARGO_PKG_NAME"))),
 | 
				
			||||||
 | 
					        RollingConditionBase::new().max_size(1024 * 1024 * 2),
 | 
				
			||||||
 | 
					        5,
 | 
				
			||||||
 | 
					    )
 | 
				
			||||||
 | 
					    .change_context(AppError)?;
 | 
				
			||||||
 | 
					    let (non_blocking, _guard) = tracing_appender::non_blocking(file_appender);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    // prepare initialization of logging
 | 
				
			||||||
 | 
					    let log_layers = tracing_subscriber::registry().with(level_filter).with(
 | 
				
			||||||
 | 
					        fmt::Layer::default()
 | 
				
			||||||
 | 
					            .with_target(false)
 | 
				
			||||||
 | 
					            .with_ansi(false)
 | 
				
			||||||
 | 
					            .with_writer(non_blocking),
 | 
				
			||||||
 | 
					    );
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    // also log to console in debug mode
 | 
				
			||||||
 | 
					    #[cfg(debug_assertions)]
 | 
				
			||||||
 | 
					    let stdout_log = tracing_subscriber::fmt::layer().pretty();
 | 
				
			||||||
 | 
					    #[cfg(debug_assertions)]
 | 
				
			||||||
 | 
					    let log_layers = log_layers.with(stdout_log);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    // Initialize logging
 | 
				
			||||||
 | 
					    log_layers.init();
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    // handle CLI input
 | 
					    // handle CLI input
 | 
				
			||||||
    let daemon = cli.handle(&mut config).await?;
 | 
					    let daemon = cli.handle(&mut config).await?;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
				
			|||||||
		Loading…
	
		Reference in New Issue
	
	Block a user