feat: add automatic Windows service installation and startup with privilege elevation
Add connect_to_service helper that attempts service connection with automatic fallback to service start and installation. Add install-service-direct command for elevated service installation. Split install_windows_service into privilege-checking wrapper and install_windows_service_direct for actual installation. Add start_windows_service function using sc start command. Add is_windows_admin helper using net session to
This commit is contained in:
@@ -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<bool, 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: "status".to_string(),
|
||||
@@ -376,6 +397,53 @@ fn windows_client_status(profile: &Path) -> Result<bool, String> {
|
||||
Ok(response.active.unwrap_or(false))
|
||||
}
|
||||
|
||||
#[cfg(target_os = "windows")]
|
||||
fn connect_to_service() -> Result<TcpStream, String> {
|
||||
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<bool, String> {
|
||||
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")]
|
||||
{
|
||||
|
||||
Reference in New Issue
Block a user