diff --git a/desktop-client/tunnel-helper/src/main.rs b/desktop-client/tunnel-helper/src/main.rs index 4974343..7e4687e 100644 --- a/desktop-client/tunnel-helper/src/main.rs +++ b/desktop-client/tunnel-helper/src/main.rs @@ -76,6 +76,7 @@ fn run() -> Result<(), String> { return match command.as_str() { "service" => run_windows_service_dispatcher(), "install-service" => install_windows_service(), + "install-service-direct" => install_windows_service_direct(), "uninstall-service" => uninstall_windows_service(), "status" => { let profile = parse_profile_arg(args)?; @@ -257,6 +258,32 @@ fn handle_service_client(mut stream: TcpStream) -> Result<(), String> { #[cfg(target_os = "windows")] fn install_windows_service() -> Result<(), String> { + if is_windows_admin()? { + return install_windows_service_direct(); + } + + let helper_path = env::current_exe().map_err(|err| format!("Unable to resolve helper path: {err}"))?; + let status = Command::new("powershell") + .arg("-NoProfile") + .arg("-Command") + .arg(format!( + "Start-Process -FilePath '{}' -ArgumentList 'install-service-direct' -Verb RunAs -Wait", + helper_path.display() + )) + .creation_flags(CREATE_NO_WINDOW) + .status() + .map_err(|err| format!("Unable to request elevated tunnel service install: {err}"))?; + + if !status.success() { + return Err("NexaVPN tunnel service installation was cancelled or failed.".into()); + } + + std::thread::sleep(Duration::from_secs(2)); + Ok(()) +} + +#[cfg(target_os = "windows")] +fn install_windows_service_direct() -> Result<(), String> { let manager = ServiceManager::local_computer( None::<&str>, ServiceManagerAccess::CONNECT | ServiceManagerAccess::CREATE_SERVICE, @@ -308,10 +335,7 @@ fn uninstall_windows_service() -> Result<(), String> { #[cfg(target_os = "windows")] fn windows_client_request(action: &str, profile: &Path) -> Result<(), String> { - let mut stream = TcpStream::connect(IPC_BIND_ADDR).map_err(|_| { - "NexaVPN background service is not available. Reinstall NexaVPN or install the tunnel service once as administrator." - .to_string() - })?; + let mut stream = connect_to_service()?; let payload = serde_json::to_string(&TunnelRequest { action: action.to_string(), @@ -343,10 +367,7 @@ fn windows_client_request(action: &str, profile: &Path) -> Result<(), String> { #[cfg(target_os = "windows")] fn windows_client_status(profile: &Path) -> Result { - let mut stream = TcpStream::connect(IPC_BIND_ADDR).map_err(|_| { - "NexaVPN background service is not available. Reinstall NexaVPN or install the tunnel service once as administrator." - .to_string() - })?; + let mut stream = connect_to_service()?; let payload = serde_json::to_string(&TunnelRequest { action: "status".to_string(), @@ -376,6 +397,53 @@ fn windows_client_status(profile: &Path) -> Result { Ok(response.active.unwrap_or(false)) } +#[cfg(target_os = "windows")] +fn connect_to_service() -> Result { + if let Ok(stream) = TcpStream::connect(IPC_BIND_ADDR) { + return Ok(stream); + } + + let _ = start_windows_service(); + std::thread::sleep(Duration::from_millis(700)); + if let Ok(stream) = TcpStream::connect(IPC_BIND_ADDR) { + return Ok(stream); + } + + install_windows_service()?; + std::thread::sleep(Duration::from_secs(2)); + if let Ok(stream) = TcpStream::connect(IPC_BIND_ADDR) { + return Ok(stream); + } + + Err("NexaVPN background service is not available. Reinstall NexaVPN or install the tunnel service once as administrator.".into()) +} + +#[cfg(target_os = "windows")] +fn start_windows_service() -> Result<(), String> { + let status = Command::new("sc") + .arg("start") + .arg(SERVICE_NAME) + .creation_flags(CREATE_NO_WINDOW) + .status() + .map_err(|err| format!("Unable to start NexaVPN tunnel service: {err}"))?; + + if status.success() { + return Ok(()); + } + + Err("Unable to start NexaVPN tunnel service.".into()) +} + +#[cfg(target_os = "windows")] +fn is_windows_admin() -> Result { + let status = Command::new("net") + .arg("session") + .creation_flags(CREATE_NO_WINDOW) + .status() + .map_err(|err| format!("Unable to determine Windows privilege level: {err}"))?; + Ok(status.success()) +} + fn connect_direct(profile: &Path) -> Result<(), String> { #[cfg(target_os = "windows")] {