diff --git a/Cargo.lock b/Cargo.lock index 9ac867e..8efe916 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1623,6 +1623,7 @@ dependencies = [ "url", "uuid", "wildmatch", + "windows-service", "windows-sys 0.52.0", "winreg 0.52.0", "zerocopy", @@ -7223,6 +7224,12 @@ dependencies = [ "rustix", ] +[[package]] +name = "widestring" +version = "1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7219d36b6eac893fa81e84ebe06485e7dcbb616177469b142df14f1f4deb1311" + [[package]] name = "wildmatch" version = "2.3.4" @@ -7344,6 +7351,17 @@ dependencies = [ "windows-targets 0.52.6", ] +[[package]] +name = "windows-service" +version = "0.7.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d24d6bcc7f734a4091ecf8d7a64c5f7d7066f45585c1861eba06449909609c8a" +dependencies = [ + "bitflags 2.6.0", + "widestring", + "windows-sys 0.52.0", +] + [[package]] name = "windows-sys" version = "0.45.0" diff --git a/easytier/Cargo.toml b/easytier/Cargo.toml index 74e9538..456aa42 100644 --- a/easytier/Cargo.toml +++ b/easytier/Cargo.toml @@ -191,6 +191,7 @@ windows-sys = { version = "0.52", features = [ ] } encoding = "0.2" winreg = "0.52" +windows-service = "0.7.0" [build-dependencies] tonic-build = "0.12" diff --git a/easytier/src/easytier-core.rs b/easytier/src/easytier-core.rs index 2b86714..3887b9a 100644 --- a/easytier/src/easytier-core.rs +++ b/easytier/src/easytier-core.rs @@ -4,8 +4,7 @@ mod tests; use std::{ - net::{Ipv4Addr, SocketAddr}, - path::PathBuf, + ffi::OsString, net::{Ipv4Addr, SocketAddr}, path::PathBuf }; #[macro_use] @@ -44,6 +43,9 @@ use crate::{ utils::init_logger, }; +#[cfg(target_os = "windows")] +windows_service::define_windows_service!(ffi_service_main, win_service_main); + #[cfg(feature = "mimalloc")] use mimalloc_rust::*; @@ -658,6 +660,95 @@ pub fn handle_event(mut events: EventBusSubscriber) -> tokio::task::JoinHandle<( }) } +#[cfg(target_os = "windows")] +fn win_service_event_loop( + stop_notify: std::sync::Arc, + inst: launcher::NetworkInstance, + status_handle: windows_service::service_control_handler::ServiceStatusHandle, +) { + use tokio::runtime::Runtime; + use std::time::Duration; + use windows_service::service::*; + + std::thread::spawn(move || { + let rt = Runtime::new().unwrap(); + rt.block_on(async move { + tokio::select! { + res = inst.wait() => { + if let Some(e) = res { + status_handle.set_service_status(ServiceStatus { + service_type: ServiceType::OWN_PROCESS, + current_state: ServiceState::Stopped, + controls_accepted: ServiceControlAccept::empty(), + checkpoint: 0, + wait_hint: Duration::default(), + exit_code: ServiceExitCode::ServiceSpecific(1u32), + process_id: None + }).unwrap(); + panic!("launcher error: {:?}", e); + } + }, + _ = stop_notify.notified() => { + status_handle.set_service_status(ServiceStatus { + service_type: ServiceType::OWN_PROCESS, + current_state: ServiceState::Stopped, + controls_accepted: ServiceControlAccept::empty(), + checkpoint: 0, + wait_hint: Duration::default(), + exit_code: ServiceExitCode::Win32(0), + process_id: None + }).unwrap(); + } + } + }); + }); +} + +#[cfg(target_os = "windows")] +fn win_service_main(_: Vec) { + use std::time::Duration; + use windows_service::service_control_handler::*; + use windows_service::service::*; + use std::sync::Arc; + use tokio::sync::Notify; + + let cli = Cli::parse(); + let cfg = TomlConfigLoader::from(cli); + + init_logger(&cfg, false).unwrap(); + + let stop_notify_send = Arc::new(Notify::new()); + let stop_notify_recv = Arc::clone(&stop_notify_send); + let event_handler = move |control_event| -> ServiceControlHandlerResult { + match control_event { + ServiceControl::Interrogate => { + ServiceControlHandlerResult::NoError + } + ServiceControl::Stop => + { + stop_notify_send.notify_one(); + ServiceControlHandlerResult::NoError + } + _ => ServiceControlHandlerResult::NotImplemented, + } + }; + let status_handle = register(String::new(), event_handler).expect("register service fail"); + let next_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, + }; + let mut inst = launcher::NetworkInstance::new(cfg).set_fetch_node_info(false); + + inst.start().unwrap(); + status_handle.set_service_status(next_status).expect("set service status fail"); + win_service_event_loop(stop_notify_recv, inst, status_handle); +} + #[tokio::main] async fn main() { setup_panic_handler(); @@ -665,6 +756,13 @@ async fn main() { let locale = sys_locale::get_locale().unwrap_or_else(|| String::from("en-US")); rust_i18n::set_locale(&locale); + #[cfg(target_os = "windows")] + { + use windows_service::service_dispatcher; + if let Ok(()) = service_dispatcher::start(String::new(), ffi_service_main) { + return; + } + } let cli = Cli::parse(); let cfg = TomlConfigLoader::from(cli); init_logger(&cfg, false).unwrap();