feat(desktop): adiciona Raven Service e corrige UAC
- 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>
This commit is contained in:
parent
caa6c53b2b
commit
c4664ab1c7
16 changed files with 4209 additions and 143 deletions
244
apps/desktop/src-tauri/src/service_client.rs
Normal file
244
apps/desktop/src-tauri/src/service_client.rs
Normal file
|
|
@ -0,0 +1,244 @@
|
|||
//! 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(),
|
||||
))
|
||||
}
|
||||
Loading…
Add table
Add a link
Reference in a new issue