- Implementa Windows Service (raven-service) para operacoes privilegiadas - Comunicacao via Named Pipes sem necessidade de UAC adicional - Adiciona single-instance para evitar multiplos icones na bandeja - Corrige todos os warnings do clippy (rustdesk, lib, usb_control, agent) - Remove fallback de elevacao para evitar UAC desnecessario - USB Policy e RustDesk provisioning agora usam o servico quando disponivel 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
244 lines
6.9 KiB
Rust
244 lines
6.9 KiB
Rust
//! Cliente IPC para comunicacao com o Raven Service
|
|
//!
|
|
//! Este modulo permite que o app Tauri se comunique com o Raven Service
|
|
//! via Named Pipes para executar operacoes privilegiadas.
|
|
|
|
#![allow(dead_code)]
|
|
|
|
use serde::{Deserialize, Serialize};
|
|
use std::io::{BufRead, BufReader, Write};
|
|
use std::time::Duration;
|
|
use thiserror::Error;
|
|
|
|
const PIPE_NAME: &str = r"\\.\pipe\RavenService";
|
|
|
|
#[derive(Debug, Error)]
|
|
pub enum ServiceClientError {
|
|
#[error("Servico nao disponivel: {0}")]
|
|
ServiceUnavailable(String),
|
|
|
|
#[error("Erro de comunicacao: {0}")]
|
|
CommunicationError(String),
|
|
|
|
#[error("Erro de serializacao: {0}")]
|
|
SerializationError(#[from] serde_json::Error),
|
|
|
|
#[error("Erro do servico: {message} (code: {code})")]
|
|
ServiceError { code: i32, message: String },
|
|
|
|
#[error("Timeout aguardando resposta")]
|
|
Timeout,
|
|
}
|
|
|
|
#[derive(Debug, Serialize)]
|
|
struct Request {
|
|
id: String,
|
|
method: String,
|
|
params: serde_json::Value,
|
|
}
|
|
|
|
#[derive(Debug, Deserialize)]
|
|
struct Response {
|
|
id: String,
|
|
result: Option<serde_json::Value>,
|
|
error: Option<ErrorResponse>,
|
|
}
|
|
|
|
#[derive(Debug, Deserialize)]
|
|
struct ErrorResponse {
|
|
code: i32,
|
|
message: String,
|
|
}
|
|
|
|
// =============================================================================
|
|
// Tipos de Resultado
|
|
// =============================================================================
|
|
|
|
#[derive(Debug, Clone, Serialize, Deserialize)]
|
|
#[serde(rename_all = "camelCase")]
|
|
pub struct UsbPolicyResult {
|
|
pub success: bool,
|
|
pub policy: String,
|
|
pub error: Option<String>,
|
|
pub applied_at: Option<i64>,
|
|
}
|
|
|
|
#[derive(Debug, Clone, Serialize, Deserialize)]
|
|
#[serde(rename_all = "camelCase")]
|
|
pub struct RustdeskResult {
|
|
pub id: String,
|
|
pub password: String,
|
|
pub installed_version: Option<String>,
|
|
pub updated: bool,
|
|
pub last_provisioned_at: i64,
|
|
}
|
|
|
|
#[derive(Debug, Clone, Serialize, Deserialize)]
|
|
#[serde(rename_all = "camelCase")]
|
|
pub struct RustdeskStatus {
|
|
pub installed: bool,
|
|
pub running: bool,
|
|
pub id: Option<String>,
|
|
pub version: Option<String>,
|
|
}
|
|
|
|
#[derive(Debug, Clone, Serialize, Deserialize)]
|
|
#[serde(rename_all = "camelCase")]
|
|
pub struct HealthCheckResult {
|
|
pub status: String,
|
|
pub service: String,
|
|
pub version: String,
|
|
pub timestamp: i64,
|
|
}
|
|
|
|
// =============================================================================
|
|
// Cliente
|
|
// =============================================================================
|
|
|
|
/// Verifica se o servico esta disponivel
|
|
pub fn is_service_available() -> bool {
|
|
health_check().is_ok()
|
|
}
|
|
|
|
/// Verifica saude do servico
|
|
pub fn health_check() -> Result<HealthCheckResult, ServiceClientError> {
|
|
let response = call_service("health_check", serde_json::json!({}))?;
|
|
serde_json::from_value(response).map_err(|e| e.into())
|
|
}
|
|
|
|
/// Aplica politica de USB
|
|
pub fn apply_usb_policy(policy: &str) -> Result<UsbPolicyResult, ServiceClientError> {
|
|
let response = call_service(
|
|
"apply_usb_policy",
|
|
serde_json::json!({ "policy": policy }),
|
|
)?;
|
|
serde_json::from_value(response).map_err(|e| e.into())
|
|
}
|
|
|
|
/// Obtem politica de USB atual
|
|
pub fn get_usb_policy() -> Result<String, ServiceClientError> {
|
|
let response = call_service("get_usb_policy", serde_json::json!({}))?;
|
|
response
|
|
.get("policy")
|
|
.and_then(|p| p.as_str())
|
|
.map(String::from)
|
|
.ok_or_else(|| ServiceClientError::CommunicationError("Resposta invalida".into()))
|
|
}
|
|
|
|
/// Provisiona RustDesk
|
|
pub fn provision_rustdesk(
|
|
config: Option<&str>,
|
|
password: Option<&str>,
|
|
machine_id: Option<&str>,
|
|
) -> Result<RustdeskResult, ServiceClientError> {
|
|
let params = serde_json::json!({
|
|
"config": config,
|
|
"password": password,
|
|
"machineId": machine_id,
|
|
});
|
|
let response = call_service("provision_rustdesk", params)?;
|
|
serde_json::from_value(response).map_err(|e| e.into())
|
|
}
|
|
|
|
/// Obtem status do RustDesk
|
|
pub fn get_rustdesk_status() -> Result<RustdeskStatus, ServiceClientError> {
|
|
let response = call_service("get_rustdesk_status", serde_json::json!({}))?;
|
|
serde_json::from_value(response).map_err(|e| e.into())
|
|
}
|
|
|
|
// =============================================================================
|
|
// Comunicacao IPC
|
|
// =============================================================================
|
|
|
|
fn call_service(
|
|
method: &str,
|
|
params: serde_json::Value,
|
|
) -> Result<serde_json::Value, ServiceClientError> {
|
|
// Gera ID unico para a requisicao
|
|
let id = uuid::Uuid::new_v4().to_string();
|
|
|
|
let request = Request {
|
|
id: id.clone(),
|
|
method: method.to_string(),
|
|
params,
|
|
};
|
|
|
|
// Serializa requisicao
|
|
let request_json = serde_json::to_string(&request)?;
|
|
|
|
// Conecta ao pipe
|
|
let mut pipe = connect_to_pipe()?;
|
|
|
|
// Envia requisicao
|
|
writeln!(pipe, "{}", request_json).map_err(|e| {
|
|
ServiceClientError::CommunicationError(format!("Erro ao enviar requisicao: {}", e))
|
|
})?;
|
|
pipe.flush().map_err(|e| {
|
|
ServiceClientError::CommunicationError(format!("Erro ao flush: {}", e))
|
|
})?;
|
|
|
|
// Le resposta
|
|
let mut reader = BufReader::new(pipe);
|
|
let mut response_line = String::new();
|
|
|
|
reader.read_line(&mut response_line).map_err(|e| {
|
|
ServiceClientError::CommunicationError(format!("Erro ao ler resposta: {}", e))
|
|
})?;
|
|
|
|
// Parse da resposta
|
|
let response: Response = serde_json::from_str(&response_line)?;
|
|
|
|
// Verifica se o ID bate
|
|
if response.id != id {
|
|
return Err(ServiceClientError::CommunicationError(
|
|
"ID de resposta nao corresponde".into(),
|
|
));
|
|
}
|
|
|
|
// Verifica erro
|
|
if let Some(error) = response.error {
|
|
return Err(ServiceClientError::ServiceError {
|
|
code: error.code,
|
|
message: error.message,
|
|
});
|
|
}
|
|
|
|
// Retorna resultado
|
|
response
|
|
.result
|
|
.ok_or_else(|| ServiceClientError::CommunicationError("Resposta sem resultado".into()))
|
|
}
|
|
|
|
#[cfg(target_os = "windows")]
|
|
fn connect_to_pipe() -> Result<std::fs::File, ServiceClientError> {
|
|
// Tenta conectar ao pipe com retry
|
|
let mut attempts = 0;
|
|
let max_attempts = 3;
|
|
|
|
loop {
|
|
match std::fs::OpenOptions::new()
|
|
.read(true)
|
|
.write(true)
|
|
.open(PIPE_NAME)
|
|
{
|
|
Ok(file) => return Ok(file),
|
|
Err(e) => {
|
|
attempts += 1;
|
|
if attempts >= max_attempts {
|
|
return Err(ServiceClientError::ServiceUnavailable(format!(
|
|
"Nao foi possivel conectar ao servico apos {} tentativas: {}",
|
|
max_attempts, e
|
|
)));
|
|
}
|
|
std::thread::sleep(Duration::from_millis(500));
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
#[cfg(not(target_os = "windows"))]
|
|
fn connect_to_pipe() -> Result<std::fs::File, ServiceClientError> {
|
|
Err(ServiceClientError::ServiceUnavailable(
|
|
"Named Pipes so estao disponiveis no Windows".into(),
|
|
))
|
|
}
|