diff --git a/easytier/src/arch/windows.rs b/easytier/src/arch/windows.rs index 497b5ad..cf631ba 100644 --- a/easytier/src/arch/windows.rs +++ b/easytier/src/arch/windows.rs @@ -1,5 +1,5 @@ use std::{ - ffi::{c_void, OsStr, OsString}, + ffi::c_void, io::{self, ErrorKind}, mem, net::SocketAddr, @@ -11,34 +11,13 @@ use network_interface::NetworkInterfaceConfig; use windows_sys::{ core::PCSTR, Win32::{ - Foundation::{BOOL, FALSE, ERROR_SERVICE_DOES_NOT_EXIST}, + Foundation::{BOOL, FALSE}, Networking::WinSock::{ htonl, setsockopt, WSAGetLastError, WSAIoctl, IPPROTO_IP, IPPROTO_IPV6, IPV6_UNICAST_IF, IP_UNICAST_IF, SIO_UDP_CONNRESET, SOCKET, SOCKET_ERROR, }, }, }; -use service_manager::{ - ServiceInstallCtx, - ServiceLevel, - ServiceStartCtx, - ServiceStatus, - ServiceStatusCtx, - ServiceUninstallCtx, - ServiceStopCtx -}; -use windows_service::service::{ - ServiceType, - ServiceErrorControl, - ServiceDependency, - ServiceInfo, - ServiceStartType, - ServiceAccess -}; -use windows_service::service_manager::{ - ServiceManagerAccess, - ServiceManager -}; pub fn disable_connection_reset(socket: &S) -> io::Result<()> { let handle = socket.as_raw_socket() as SOCKET; @@ -173,130 +152,4 @@ pub fn setup_socket_for_win( } Ok(()) -} - -pub struct WinServiceManager { - service_manager: ServiceManager, - display_name: Option, - description: Option, - dependencies: Vec -} - -impl WinServiceManager { - pub fn new(display_name: Option, description: Option, dependencies: Vec,) -> Result { - let service_manager = ServiceManager::local_computer( - None::<&str>, - ServiceManagerAccess::ALL_ACCESS, - )?; - Ok(Self { - service_manager, - display_name, - description, - dependencies, - }) - } -} - -impl service_manager::ServiceManager for WinServiceManager { - fn available(&self) -> io::Result { - Ok(true) - } - - fn install(&self, ctx: ServiceInstallCtx) -> io::Result<()> { - let start_type_ = if ctx.autostart { ServiceStartType::AutoStart } else { ServiceStartType::OnDemand }; - let srv_name = OsString::from(ctx.label.to_qualified_name()); - let dis_name = self.display_name.clone().unwrap_or_else(|| srv_name.clone()); - let dependencies = self.dependencies.iter().map(|dep| ServiceDependency::Service(dep.clone())).collect::>(); - let service_info = ServiceInfo { - name: srv_name, - display_name: dis_name, - service_type: ServiceType::OWN_PROCESS, - start_type: start_type_, - error_control: ServiceErrorControl::Normal, - executable_path: ctx.program, - launch_arguments: ctx.args, - dependencies: dependencies.clone(), - account_name: None, - account_password: None - }; - - let service = self.service_manager.create_service(&service_info, ServiceAccess::ALL_ACCESS).map_err(|e| { - io::Error::new(io::ErrorKind::Other, e) - })?; - - if let Some(s) = &self.description { - service.set_description(s.clone()).map_err(|e| { - io::Error::new(io::ErrorKind::Other, e) - })?; - } - - Ok(()) - } - - fn uninstall(&self, ctx: ServiceUninstallCtx) -> io::Result<()> { - let service = self.service_manager.open_service(ctx.label.to_qualified_name(), ServiceAccess::ALL_ACCESS).map_err(|e|{ - io::Error::new(io::ErrorKind::Other, e) - })?; - - service.delete().map_err(|e|{ - io::Error::new(io::ErrorKind::Other, e) - }) - } - - fn start(&self, ctx: ServiceStartCtx) -> io::Result<()> { - let service = self.service_manager.open_service(ctx.label.to_qualified_name(), ServiceAccess::ALL_ACCESS).map_err(|e|{ - io::Error::new(io::ErrorKind::Other, e) - })?; - - service.start(&[] as &[&OsStr]).map_err(|e|{ - io::Error::new(io::ErrorKind::Other, e) - }) - } - - fn stop(&self, ctx: ServiceStopCtx) -> io::Result<()> { - let service = self.service_manager.open_service(ctx.label.to_qualified_name(), ServiceAccess::ALL_ACCESS).map_err(|e|{ - io::Error::new(io::ErrorKind::Other, e) - })?; - - _ = service.stop().map_err(|e|{ - io::Error::new(io::ErrorKind::Other, e) - })?; - - Ok(()) - } - - fn level(&self) -> ServiceLevel { - ServiceLevel::System - } - - fn set_level(&mut self, level: ServiceLevel) -> io::Result<()> { - match level { - ServiceLevel::System => Ok(()), - _ => Err(io::Error::new(io::ErrorKind::Other, "Unsupported service level")) - } - } - - fn status(&self, ctx: ServiceStatusCtx) -> io::Result { - let service = match self.service_manager.open_service(ctx.label.to_qualified_name(), ServiceAccess::QUERY_STATUS) { - Ok(s) => s, - Err(e) => { - if let windows_service::Error::Winapi(ref win_err) = e { - if win_err.raw_os_error() == Some(ERROR_SERVICE_DOES_NOT_EXIST as i32) { - return Ok(ServiceStatus::NotInstalled); - } - } - return Err(io::Error::new(io::ErrorKind::Other, e)); - } - }; - - let status = service.query_status().map_err(|e|{ - io::Error::new(io::ErrorKind::Other, e) - })?; - - match status.current_state { - windows_service::service::ServiceState::Stopped => Ok(ServiceStatus::Stopped(None)), - _ => Ok(ServiceStatus::Running), - } - } -} - +} \ No newline at end of file diff --git a/easytier/src/easytier-cli.rs b/easytier/src/easytier-cli.rs index 943ed67..adda1af 100644 --- a/easytier/src/easytier-cli.rs +++ b/easytier/src/easytier-cli.rs @@ -1,10 +1,18 @@ -use std::{net::SocketAddr, sync::Mutex, time::Duration, vec}; +use std::{ + ffi::OsString, + net::SocketAddr, + path::PathBuf, + sync::Mutex, + time::Duration, + vec +}; use anyhow::{Context, Ok}; use clap::{command, Args, Parser, Subcommand}; use humansize::format_size; use tabled::settings::Style; use tokio::time::timeout; +use service_manager::*; use easytier::{ common::{ @@ -26,7 +34,7 @@ use easytier::{ rpc_types::controller::BaseController, }, tunnel::tcp::TcpTunnelConnector, - utils::{cost_to_str, float_to_str, PeerRoutePair}, + utils::{cost_to_str, float_to_str, PeerRoutePair}, }; rust_i18n::i18n!("locales", fallback = "en"); @@ -54,6 +62,7 @@ enum SubCommand { PeerCenter, VpnPortal, Node(NodeArgs), + Service(ServiceArgs) } #[derive(Args, Debug)] @@ -74,7 +83,7 @@ enum PeerSubCommand { Remove, List(PeerListArgs), ListForeign, - ListGlobalForeign, + ListGlobalForeign, } #[derive(Args, Debug)] @@ -120,6 +129,29 @@ struct NodeArgs { sub_command: Option, } +#[derive(Args, Debug)] +struct ServiceArgs{ + #[command(subcommand)] + sub_command: ServiceSubCommand +} + +#[derive(Subcommand, Debug)] +enum ServiceSubCommand { + Install(InstallArgs), + Uninstall, + Status, + Start, + Stop +} + +#[derive(Args, Debug)] +struct InstallArgs { + #[arg(short = 'p', long)] + core_path: Option, + #[arg(trailing_var_arg = true, allow_hyphen_values = true)] + core_args: Option> +} + type Error = anyhow::Error; struct CommandHandler { @@ -476,6 +508,120 @@ impl CommandHandler { } } +pub struct Service{ + lable: ServiceLabel, + service_manager: Box +} + +impl Service { + pub fn new() -> Result { + #[cfg(target_os = "windows")] + let service_manager = Box::new( + crate::win_service_manager::WinServiceManager::new( + Some(OsString::from("EasyTier Service")), + Some(OsString::from(env!("CARGO_PKG_DESCRIPTION"))), + vec![OsString::from("dnscache"), OsString::from("rpcss")], + )? + ); + + #[cfg(not(target_os = "windows"))] + let service_manager = ::native()?; + + Ok(Self { + lable: env!("CARGO_PKG_NAME").parse().unwrap(), + service_manager + }) + } + + pub fn install(&self, bin_path: std::path::PathBuf, bin_args: Vec) -> Result<(), Error> { + let ctx = ServiceInstallCtx { + label: self.lable.clone(), + contents: None, + program: bin_path, + args: bin_args, + autostart: true, + username: None, + working_directory: None, + environment: None, + }; + + if self.status()? != ServiceStatus::NotInstalled { + return Err(anyhow::anyhow!("Service is already installed")); + } + + self.service_manager.install(ctx).map_err(|e| anyhow::anyhow!("failed to install service: {}", e)) + } + + pub fn uninstall(&self) -> Result<(), Error> { + let ctx = ServiceUninstallCtx { + label: self.lable.clone(), + }; + let status = self.status()?; + + if status == ServiceStatus::NotInstalled { + return Err(anyhow::anyhow!("Service is not installed")); + } + + if status == ServiceStatus::Running { + self.service_manager.stop(ServiceStopCtx { + label: self.lable.clone(), + })?; + } + + self.service_manager.uninstall(ctx).map_err(|e| anyhow::anyhow!("failed to uninstall service: {}", e)) + } + + pub fn status(&self) -> Result { + let ctx = ServiceStatusCtx { + label: self.lable.clone(), + }; + let status = self.service_manager.status(ctx)?; + + Ok(status) + } + + pub fn start(&self) -> Result<(), Error> { + let ctx = ServiceStartCtx { + label: self.lable.clone(), + }; + let status = self.status()?; + + match status { + ServiceStatus::Running => { + Err(anyhow::anyhow!("Service is already running")) + } + ServiceStatus::Stopped(_) => { + self.service_manager.start(ctx).map_err(|e| anyhow::anyhow!("failed to start service: {}", e))?; + Ok(()) + } + ServiceStatus::NotInstalled => { + Err(anyhow::anyhow!("Service is not installed")) + } + } + } + + pub fn stop(&self) -> Result<(), Error> { + let ctx = ServiceStopCtx { + label: self.lable.clone(), + }; + let status = self.status()?; + + match status { + ServiceStatus::Running => { + self.service_manager.stop(ctx).map_err(|e| anyhow::anyhow!("failed to stop service: {}", e))?; + Ok(()) + } + ServiceStatus::Stopped(_) => { + Err(anyhow::anyhow!("Service is already stopped")) + } + ServiceStatus::NotInstalled => { + Err(anyhow::anyhow!("Service is not installed")) + } + } + } + +} + #[tokio::main] #[tracing::instrument] async fn main() -> Result<(), Error> { @@ -638,7 +784,203 @@ async fn main() -> Result<(), Error> { } } } + SubCommand::Service(service_args) => { + let service = Service::new()?; + match service_args.sub_command { + ServiceSubCommand::Install(install_args) => { + let bin_path = install_args.core_path.unwrap_or_else(|| { + let mut ret = std::env::current_exe() + .unwrap() + .parent() + .unwrap() + .join("easytier-core"); + #[cfg(target_os = "windows")] + ret.set_extension("exe"); + ret + }); + let bin_path = std::fs::canonicalize(bin_path).map_err(|e| { + anyhow::anyhow!("failed to get easytier core application: {}", e) + })?; + let bin_args = install_args.core_args.unwrap_or_default(); + service.install(bin_path, bin_args)?; + } + ServiceSubCommand::Uninstall => { + service.uninstall()?; + } + ServiceSubCommand::Status => { + let status = service.status()?; + match status { + ServiceStatus::Running => println!("Service is running"), + ServiceStatus::Stopped(_) => println!("Service is stopped"), + ServiceStatus::NotInstalled => println!("Service is not installed"), + } + } + ServiceSubCommand::Start => { + service.start()?; + } + ServiceSubCommand::Stop => { + service.stop()?; + } + } + } } Ok(()) } + +#[cfg(target_os = "windows")] +mod win_service_manager { + use windows_service::{ + service::{ + ServiceType, + ServiceErrorControl, + ServiceDependency, + ServiceInfo, + ServiceStartType, + ServiceAccess + }, + service_manager::{ + ServiceManagerAccess, + ServiceManager + } + }; + use std::{ + io, + ffi::OsString, + ffi::OsStr + }; + + use service_manager::{ + ServiceInstallCtx, + ServiceLevel, + ServiceStartCtx, + ServiceStatus, + ServiceStatusCtx, + ServiceUninstallCtx, + ServiceStopCtx + }; + + pub struct WinServiceManager { + service_manager: ServiceManager, + display_name: Option, + description: Option, + dependencies: Vec + } + + impl WinServiceManager { + pub fn new(display_name: Option, description: Option, dependencies: Vec,) -> Result { + let service_manager = ServiceManager::local_computer( + None::<&str>, + ServiceManagerAccess::ALL_ACCESS, + )?; + Ok(Self { + service_manager, + display_name, + description, + dependencies, + }) + } + } + impl service_manager::ServiceManager for WinServiceManager { + fn available(&self) -> io::Result { + Ok(true) + } + + fn install(&self, ctx: ServiceInstallCtx) -> io::Result<()> { + let start_type_ = if ctx.autostart { ServiceStartType::AutoStart } else { ServiceStartType::OnDemand }; + let srv_name = OsString::from(ctx.label.to_qualified_name()); + let dis_name = self.display_name.clone().unwrap_or_else(|| srv_name.clone()); + let dependencies = self.dependencies.iter().map(|dep| ServiceDependency::Service(dep.clone())).collect::>(); + let service_info = ServiceInfo { + name: srv_name, + display_name: dis_name, + service_type: ServiceType::OWN_PROCESS, + start_type: start_type_, + error_control: ServiceErrorControl::Normal, + executable_path: ctx.program, + launch_arguments: ctx.args, + dependencies: dependencies.clone(), + account_name: None, + account_password: None + }; + + let service = self.service_manager.create_service(&service_info, ServiceAccess::ALL_ACCESS).map_err(|e| { + io::Error::new(io::ErrorKind::Other, e) + })?; + + if let Some(s) = &self.description { + service.set_description(s.clone()).map_err(|e| { + io::Error::new(io::ErrorKind::Other, e) + })?; + } + + Ok(()) + } + + fn uninstall(&self, ctx: ServiceUninstallCtx) -> io::Result<()> { + let service = self.service_manager.open_service(ctx.label.to_qualified_name(), ServiceAccess::ALL_ACCESS).map_err(|e|{ + io::Error::new(io::ErrorKind::Other, e) + })?; + + service.delete().map_err(|e|{ + io::Error::new(io::ErrorKind::Other, e) + }) + } + + fn start(&self, ctx: ServiceStartCtx) -> io::Result<()> { + let service = self.service_manager.open_service(ctx.label.to_qualified_name(), ServiceAccess::ALL_ACCESS).map_err(|e|{ + io::Error::new(io::ErrorKind::Other, e) + })?; + + service.start(&[] as &[&OsStr]).map_err(|e|{ + io::Error::new(io::ErrorKind::Other, e) + }) + } + + fn stop(&self, ctx: ServiceStopCtx) -> io::Result<()> { + let service = self.service_manager.open_service(ctx.label.to_qualified_name(), ServiceAccess::ALL_ACCESS).map_err(|e|{ + io::Error::new(io::ErrorKind::Other, e) + })?; + + _ = service.stop().map_err(|e|{ + io::Error::new(io::ErrorKind::Other, e) + })?; + + Ok(()) + } + + fn level(&self) -> ServiceLevel { + ServiceLevel::System + } + + fn set_level(&mut self, level: ServiceLevel) -> io::Result<()> { + match level { + ServiceLevel::System => Ok(()), + _ => Err(io::Error::new(io::ErrorKind::Other, "Unsupported service level")) + } + } + + fn status(&self, ctx: ServiceStatusCtx) -> io::Result { + let service = match self.service_manager.open_service(ctx.label.to_qualified_name(), ServiceAccess::QUERY_STATUS) { + Ok(s) => s, + Err(e) => { + if let windows_service::Error::Winapi(ref win_err) = e { + if win_err.raw_os_error() == Some(0x424) { + return Ok(ServiceStatus::NotInstalled); + } + } + return Err(io::Error::new(io::ErrorKind::Other, e)); + } + }; + + let status = service.query_status().map_err(|e|{ + io::Error::new(io::ErrorKind::Other, e) + })?; + + match status.current_state { + windows_service::service::ServiceState::Stopped => Ok(ServiceStatus::Stopped(None)), + _ => Ok(ServiceStatus::Running), + } + } + } +} \ No newline at end of file diff --git a/easytier/src/easytier-core.rs b/easytier/src/easytier-core.rs index 093d6a5..9c05a0e 100644 --- a/easytier/src/easytier-core.rs +++ b/easytier/src/easytier-core.rs @@ -6,16 +6,10 @@ extern crate rust_i18n; use std::{ net::{Ipv4Addr, SocketAddr}, path::PathBuf, - ffi::OsString }; use anyhow::Context; -use clap::{ - command, - Parser, - Subcommand, - Args -}; +use clap::Parser; use tokio::net::TcpSocket; use easytier::{ @@ -30,7 +24,7 @@ use easytier::{ }, launcher, proto, tunnel::udp::UdpTunnelConnector, - utils::{init_logger, setup_panic_handler, Service}, + utils::{init_logger, setup_panic_handler}, web_client, }; @@ -45,14 +39,15 @@ use mimalloc_rust::GlobalMiMalloc; static GLOBAL_MIMALLOC: GlobalMiMalloc = GlobalMiMalloc; #[derive(Parser, Debug)] -struct RunArgs { +#[command(name = "easytier-core", author, version = EASYTIER_VERSION , about, long_about = None)] +struct Cli { #[arg( short = 'w', long, help = t!("core_clap.config_server").to_string() )] - config_server: Option, - + config_server: Option, + #[arg( short, long, @@ -294,47 +289,11 @@ struct RunArgs { help = t!("core_clap.ipv6_listener").to_string() )] ipv6_listener: Option, - - #[arg( - long, - help = t!("core_clap.work_dir").to_string() - )] - work_dir: Option -} - -#[derive(Parser, Debug)] -#[command(name = "easytier-core", author, version = EASYTIER_VERSION , about, long_about = None)] -struct Cli{ - #[command(subcommand)] - sub_command: SubCmd -} -#[derive(Subcommand, Debug)] -enum SubCmd { - #[command( - about = t!("core_clap.run").to_string() - )] - Run(RunArgs), - #[command( - about = t!("core_clap.service").to_string() - )] - Service(ServiceArgs) -} - -#[derive(Args, Debug)] -struct ServiceArgs{ - #[command(subcommand)] - sub_command: SrvSubCmd -} -#[derive(Subcommand, Debug)] -enum SrvSubCmd { - Install(RunArgs), - Uninstall, - Status } rust_i18n::i18n!("locales", fallback = "en"); -impl RunArgs { +impl Cli { fn parse_listeners(no_listener: bool, listeners: Vec) -> Vec { let proto_port_offset = vec![("tcp", 0), ("udp", 0), ("wg", 1), ("ws", 1), ("wss", 2)]; @@ -392,7 +351,7 @@ impl RunArgs { if port == 0 { // check tcp 15888 first for i in 15888..15900 { - if let Some(s) = RunArgs::check_tcp_available(i) { + if let Some(s) = Cli::check_tcp_available(i) { return s; } } @@ -405,27 +364,27 @@ impl RunArgs { } } -impl From for TomlConfigLoader { - fn from(args: RunArgs) -> Self { - if let Some(config_file) = &args.config_file { +impl From for TomlConfigLoader { + fn from(cli: Cli) -> Self { + if let Some(config_file) = &cli.config_file { println!( "NOTICE: loading config file: {:?}, will ignore all command line flags\n", config_file ); return TomlConfigLoader::new(config_file) - .with_context(|| format!("failed to load config file: {:?}", args.config_file)) + .with_context(|| format!("failed to load config file: {:?}", cli.config_file)) .unwrap(); } let cfg = TomlConfigLoader::default(); - cfg.set_hostname(args.hostname); + cfg.set_hostname(cli.hostname); - cfg.set_network_identity(NetworkIdentity::new(args.network_name, args.network_secret)); + cfg.set_network_identity(NetworkIdentity::new(cli.network_name, cli.network_secret)); - cfg.set_dhcp(args.dhcp); + cfg.set_dhcp(cli.dhcp); - if let Some(ipv4) = &args.ipv4 { + if let Some(ipv4) = &cli.ipv4 { cfg.set_ipv4(Some( ipv4.parse() .with_context(|| format!("failed to parse ipv4 address: {}", ipv4)) @@ -434,7 +393,7 @@ impl From for TomlConfigLoader { } cfg.set_peers( - args.peers + cli.peers .iter() .map(|s| PeerConfig { uri: s @@ -446,13 +405,13 @@ impl From for TomlConfigLoader { ); cfg.set_listeners( - RunArgs::parse_listeners(args.no_listener, args.listeners) + Cli::parse_listeners(cli.no_listener, cli.listeners) .into_iter() .map(|s| s.parse().unwrap()) .collect(), ); - for n in args.proxy_networks.iter() { + for n in cli.proxy_networks.iter() { cfg.add_proxy_cidr( n.parse() .with_context(|| format!("failed to parse proxy network: {}", n)) @@ -460,9 +419,9 @@ impl From for TomlConfigLoader { ); } - cfg.set_rpc_portal(RunArgs::parse_rpc_portal(args.rpc_portal)); + cfg.set_rpc_portal(Cli::parse_rpc_portal(cli.rpc_portal)); - if let Some(external_nodes) = args.external_node { + if let Some(external_nodes) = cli.external_node { let mut old_peers = cfg.get_peers(); old_peers.push(PeerConfig { uri: external_nodes @@ -475,23 +434,23 @@ impl From for TomlConfigLoader { cfg.set_peers(old_peers); } - if args.console_log_level.is_some() { + if cli.console_log_level.is_some() { cfg.set_console_logger_config(ConsoleLoggerConfig { - level: args.console_log_level, + level: cli.console_log_level, }); } - if args.file_log_dir.is_some() || args.file_log_level.is_some() { + if cli.file_log_dir.is_some() || cli.file_log_level.is_some() { cfg.set_file_logger_config(FileLoggerConfig { - level: args.file_log_level.clone(), - dir: args.file_log_dir.clone(), - file: Some(format!("easytier-{}", args.instance_name)), + level: cli.file_log_level.clone(), + dir: cli.file_log_dir.clone(), + file: Some(format!("easytier-{}", cli.instance_name)), }); } - cfg.set_inst_name(args.instance_name); + cfg.set_inst_name(cli.instance_name); - if let Some(vpn_portal) = args.vpn_portal { + if let Some(vpn_portal) = cli.vpn_portal { let url: url::Url = vpn_portal .parse() .with_context(|| format!("failed to parse vpn portal url: {}", vpn_portal)) @@ -515,7 +474,7 @@ impl From for TomlConfigLoader { }); } - if let Some(manual_routes) = args.manual_routes { + if let Some(manual_routes) = cli.manual_routes { cfg.set_routes(Some( manual_routes .iter() @@ -529,7 +488,7 @@ impl From for TomlConfigLoader { } #[cfg(feature = "socks5")] - if let Some(socks5_proxy) = args.socks5 { + if let Some(socks5_proxy) = cli.socks5 { cfg.set_socks5_portal(Some( format!("socks5://0.0.0.0:{}", socks5_proxy) .parse() @@ -538,34 +497,34 @@ impl From for TomlConfigLoader { } let mut f = cfg.get_flags(); - if args.default_protocol.is_some() { - f.default_protocol = args.default_protocol.as_ref().unwrap().clone(); + if cli.default_protocol.is_some() { + f.default_protocol = cli.default_protocol.as_ref().unwrap().clone(); } - f.enable_encryption = !args.disable_encryption; - f.enable_ipv6 = !args.disable_ipv6; - f.latency_first = args.latency_first; - f.dev_name = args.dev_name.unwrap_or_default(); - if let Some(mtu) = args.mtu { + f.enable_encryption = !cli.disable_encryption; + f.enable_ipv6 = !cli.disable_ipv6; + f.latency_first = cli.latency_first; + f.dev_name = cli.dev_name.unwrap_or_default(); + if let Some(mtu) = cli.mtu { f.mtu = mtu; } - f.enable_exit_node = args.enable_exit_node; - f.no_tun = args.no_tun || cfg!(not(feature = "tun")); - f.use_smoltcp = args.use_smoltcp; - if let Some(wl) = args.relay_network_whitelist { + f.enable_exit_node = cli.enable_exit_node; + f.no_tun = cli.no_tun || cfg!(not(feature = "tun")); + f.use_smoltcp = cli.use_smoltcp; + if let Some(wl) = cli.relay_network_whitelist { f.foreign_network_whitelist = wl.join(" "); } - f.disable_p2p = args.disable_p2p; - f.relay_all_peer_rpc = args.relay_all_peer_rpc; - if let Some(ipv6_listener) = args.ipv6_listener { + f.disable_p2p = cli.disable_p2p; + f.relay_all_peer_rpc = cli.relay_all_peer_rpc; + if let Some(ipv6_listener) = cli.ipv6_listener { f.ipv6_listener = ipv6_listener .parse() .with_context(|| format!("failed to parse ipv6 listener: {}", ipv6_listener)) .unwrap(); } - f.multi_thread = args.multi_thread; + f.multi_thread = cli.multi_thread; cfg.set_flags(f); - cfg.set_exit_nodes(args.exit_nodes.clone()); + cfg.set_exit_nodes(cli.exit_nodes.clone()); cfg } @@ -695,48 +654,42 @@ pub fn handle_event(mut events: EventBusSubscriber) -> tokio::task::JoinHandle<( #[cfg(target_os = "windows")] fn win_service_event_loop( - args: RunArgs, stop_notify: std::sync::Arc, - status_handle: windows_service::service_control_handler::ServiceStatusHandle, + inst: launcher::NetworkInstance, + status_handle: windows_service::service_control_handler::ServiceStatusHandle, ) { use tokio::runtime::Runtime; use std::time::Duration; use windows_service::service::*; - let err_stop = ServiceStatus { - service_type: ServiceType::OWN_PROCESS, - current_state: ServiceState::Stopped, - controls_accepted: ServiceControlAccept::empty(), - checkpoint: 0, - wait_hint: Duration::default(), - exit_code: ServiceExitCode::Win32(1), - process_id: None, - }; - let normal_stop = ServiceStatus { - service_type: ServiceType::OWN_PROCESS, - current_state: ServiceState::Stopped, - controls_accepted: ServiceControlAccept::STOP, - checkpoint: 0, - wait_hint: Duration::default(), - exit_code: ServiceExitCode::Win32(0), - process_id: None, - }; - std::thread::spawn(move || { let rt = Runtime::new().unwrap(); rt.block_on(async move { tokio::select! { - res = main_run(args) => { - if let Err(e) = res { - status_handle.set_service_status(err_stop).unwrap(); - panic!("{:?}", e); - } else { - status_handle.set_service_status(normal_stop).unwrap(); - std::process::exit(0); + 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(normal_stop).unwrap(); + 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(); std::process::exit(0); } } @@ -745,20 +698,17 @@ fn win_service_event_loop( } #[cfg(target_os = "windows")] -fn win_service_main(_: Vec) { +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 args = RunArgs::try_parse().unwrap_or_else(|_| { - if let SubCmd::Run(args_) = Cli::parse().sub_command { - args_ - } else { - panic!("invalid args") - } - }); + 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); @@ -785,81 +735,38 @@ fn win_service_main(_: Vec) { 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(args, stop_notify_recv, status_handle); + win_service_event_loop(stop_notify_recv, inst, status_handle); } -fn service_manager_handle(srv_arg: ServiceArgs) -> Result<(), String> { - use service_manager::ServiceStatus; - let service = Service::new().map_err(|e| { - format!("Service manager init failed: {:?}", e) - })?; - let status = service.status().map_err(|e|{ - format!("Failed to get service info: {:?}", e) - })?; +#[tokio::main] +async fn main() { + let locale = sys_locale::get_locale().unwrap_or_else(|| String::from("en-US")); + rust_i18n::set_locale(&locale); + setup_panic_handler(); - match srv_arg.sub_command { - SrvSubCmd::Install(_) => { - if status == ServiceStatus::NotInstalled { - let mut args = std::env::args_os().skip(3).collect::>(); - args.insert(0, OsString::from("run")); - - if let Some(work_dir) = args.iter().position(|x| x == "--work-dir") { - let d = std::fs::canonicalize(&args[work_dir + 1]).map_err(|e| { - format!("failed to get work dir: {:?}", e) - })?; - args[work_dir + 1] = OsString::from(d); - } else { - let d = std::env::current_exe().unwrap().parent().unwrap().to_path_buf(); - args.push(OsString::from("--work-dir")); - args.push(OsString::from(d)); - } - service.install(args).map_err(|e| { - format!("Service install failed: {:?}", e) - })?; - println!("Service installed successfully."); - } - else { - return Err("Service already installed, please uninstall it first.".to_string()); - } - } - SrvSubCmd::Uninstall => { - if status != ServiceStatus::NotInstalled { - if status == ServiceStatus::Running{ - service.stop().map_err(|e| { - format!("Service stop failed: {:?}", e) - })?; - } - service.uninstall().map_err(|e| { - format!("Service uninstall failed: {:?}", e) - })?; - println!("Service uninstalled successfully."); - } - else { - eprint!("Service not installed."); - } - } - SrvSubCmd::Status => { - println!("Service status: {}", match status { - ServiceStatus::NotInstalled => "Not Installed", - ServiceStatus::Stopped(_) => "Stopped", - ServiceStatus::Running => "Running", - }); - } - } - Ok(()) -} + #[cfg(target_os = "windows")] + match windows_service::service_dispatcher::start(String::new(), ffi_service_main) { + Ok(_) => std::thread::park(), + Err(e) => + { + let should_panic = if let windows_service::Error::Winapi(ref io_error) = e { + io_error.raw_os_error() != Some(0x427) // ERROR_FAILED_SERVICE_CONTROLLER_CONNECT + } else { true }; + + if should_panic { + panic!("SCM start an error: {}", e); + } + } + }; + + let cli = Cli::parse(); -async fn main_run(args: RunArgs) -> Result<(), String> { - if let Some(work_dir) = &args.work_dir { - std::env::set_current_dir(work_dir).map_err(|e| { - format!("failed to set work dir: {:?}", e) - })?; - } - - if args.config_server.is_some() { - let config_server_url_s = args.config_server.clone().unwrap(); + if cli.config_server.is_some() { + let config_server_url_s = cli.config_server.clone().unwrap(); let config_server_url = match url::Url::parse(&config_server_url_s) { Ok(u) => u, Err(_) => format!( @@ -889,11 +796,10 @@ async fn main_run(args: RunArgs) -> Result<(), String> { let _wc = web_client::WebClient::new(UdpTunnelConnector::new(c_url), token.to_string()); tokio::signal::ctrl_c().await.unwrap(); - return Ok(()); + return; } - let cfg = TomlConfigLoader::from(args); - + let cfg = TomlConfigLoader::from(cli); init_logger(&cfg, false).unwrap(); println!("Starting easytier with config:"); @@ -903,53 +809,7 @@ async fn main_run(args: RunArgs) -> Result<(), String> { let mut l = launcher::NetworkInstance::new(cfg).set_fetch_node_info(false); let _t = ScopedTask::from(handle_event(l.start().unwrap())); - if let Some(e) = l.wait().await{ - Err(format!("launcher error: {}", e)) - } else { - Ok(()) + if let Some(e) = l.wait().await { + panic!("launcher error: {:?}", e); } - -} - -#[tokio::main] -async fn main() { - setup_panic_handler(); - - let locale = sys_locale::get_locale().unwrap_or_else(|| String::from("en-US")); - rust_i18n::set_locale(&locale); - - #[cfg(target_os = "windows")] - match windows_service::service_dispatcher::start(String::new(), ffi_service_main) { - Ok(_) => std::thread::park(), - Err(e) => - { - let should_panic = if let windows_service::Error::Winapi(ref io_error) = e { - io_error.raw_os_error() != Some(0x427) // ERROR_FAILED_SERVICE_CONTROLLER_CONNECT - } else { true }; - - if should_panic { - panic!("SCM start an error: {}", e); - } - } - }; - - let run_result = if let Ok(args) = RunArgs::try_parse() { - main_run(args).await - } else { - match Cli::parse().sub_command { - SubCmd::Run(args) => main_run(args).await, - SubCmd::Service(serv_args) => { - if let Err(e) = service_manager_handle(serv_args) { - eprint!("{}", e); - std::process::exit(1); - } - return; - } - } - }; - - if let Err(e) = run_result { - panic!("{:?}", e); - } -} - +} \ No newline at end of file diff --git a/easytier/src/utils.rs b/easytier/src/utils.rs index 5c0a16a..b62a241 100644 --- a/easytier/src/utils.rs +++ b/easytier/src/utils.rs @@ -1,8 +1,6 @@ use anyhow::Context; use tracing::level_filters::LevelFilter; use tracing_subscriber::{layer::SubscriberExt, util::SubscriberInitExt, EnvFilter, Layer}; -use std::ffi::OsString; -use service_manager::*; use crate::common::{config::ConfigLoader, get_logger_timer_rfc3339}; @@ -155,76 +153,6 @@ pub fn setup_panic_handler() { })); } -pub struct Service{ - lable: ServiceLabel, - service_manager: Box -} - -impl Service { - pub fn new() -> Result { - #[cfg(target_os = "windows")] - let service_manager = Box::new( - crate::arch::windows::WinServiceManager::new( - Some(OsString::from("EasyTier Service")), - Some(OsString::from(env!("CARGO_PKG_DESCRIPTION"))), - vec![OsString::from("dnscache"), OsString::from("rpcss")], - )? - ); - - #[cfg(not(target_os = "windows"))] - let service_manager = ::native()?; - - Ok(Self { - lable: env!("CARGO_PKG_NAME").parse().unwrap(), - service_manager - }) - } - - pub fn install(&self, bin_args: Vec) -> Result<(), std::io::Error> { - let ctx = ServiceInstallCtx { - label: self.lable.clone(), - contents: None, - program: std::env::current_exe().unwrap(), - args: bin_args, - autostart: true, - username: None, - working_directory: None, - environment: None, - }; - self.service_manager.install(ctx)?; - Ok(()) - } - - pub fn uninstall(&self) -> Result<(), std::io::Error> { - let ctx = ServiceUninstallCtx { - label: self.lable.clone(), - }; - self.service_manager.uninstall(ctx) - } - - pub fn status(&self) -> Result { - let ctx = ServiceStatusCtx { - label: self.lable.clone(), - }; - self.service_manager.status(ctx) - } - - pub fn start(&self) -> Result<(), std::io::Error> { - let ctx = ServiceStartCtx { - label: self.lable.clone(), - }; - self.service_manager.start(ctx) - } - - pub fn stop(&self) -> Result<(), std::io::Error> { - let ctx = ServiceStopCtx { - label: self.lable.clone(), - }; - self.service_manager.stop(ctx) - } - -} - #[cfg(test)] mod tests { use crate::common::config::{self}; @@ -240,4 +168,4 @@ mod tests { tokio::time::sleep(tokio::time::Duration::from_secs(1)).await; tracing::debug!("test display debug"); } -} +} \ No newline at end of file