feat: event-driven rustdesk sync

This commit is contained in:
Esdras Renan 2025-11-11 20:26:29 -03:00
parent e410a4874c
commit e0bb6bb80f
4 changed files with 263 additions and 96 deletions

View file

@ -3,6 +3,7 @@ mod agent;
mod rustdesk;
use agent::{collect_inventory_plain, collect_profile, AgentRuntime, MachineProfile};
use tauri::Emitter;
use tauri_plugin_store::Builder as StorePluginBuilder;
#[derive(Debug, serde::Serialize)]
@ -12,6 +13,7 @@ pub struct RustdeskProvisioningResult {
pub password: String,
pub installed_version: Option<String>,
pub updated: bool,
pub last_provisioned_at: i64,
}
#[tauri::command]
@ -50,22 +52,45 @@ fn open_devtools(window: tauri::WebviewWindow) -> Result<(), String> {
}
#[tauri::command]
async fn provision_rustdesk(machine_id: Option<String>) -> Result<RustdeskProvisioningResult, String> {
let machine_id = machine_id
.filter(|value| !value.trim().is_empty())
.ok_or_else(|| "Informe o identificador da máquina para provisionar o RustDesk.".to_string())?;
tauri::async_runtime::spawn_blocking(move || run_rustdesk_provision(machine_id))
.await
.map_err(|error| error.to_string())?
async fn ensure_rustdesk_and_emit(
app: tauri::AppHandle,
config_string: Option<String>,
password: Option<String>,
machine_id: Option<String>,
) -> Result<RustdeskProvisioningResult, String> {
let result = tauri::async_runtime::spawn_blocking(move || {
run_rustdesk_ensure(config_string, password, machine_id)
})
.await
.map_err(|error| error.to_string())??;
if let Err(error) = app.emit("raven://remote-access/provisioned", &result) {
eprintln!("[rustdesk] falha ao emitir evento raven://remote-access/provisioned: {error}");
}
Ok(result)
}
#[cfg(target_os = "windows")]
fn run_rustdesk_provision(machine_id: String) -> Result<RustdeskProvisioningResult, String> {
rustdesk::provision(&machine_id).map_err(|error| error.to_string())
fn run_rustdesk_ensure(
config_string: Option<String>,
password: Option<String>,
machine_id: Option<String>,
) -> Result<RustdeskProvisioningResult, String> {
rustdesk::ensure_rustdesk(
config_string.as_deref(),
password.as_deref(),
machine_id.as_deref(),
)
.map_err(|error| error.to_string())
}
#[cfg(not(target_os = "windows"))]
fn run_rustdesk_provision(_machine_id: String) -> Result<RustdeskProvisioningResult, String> {
fn run_rustdesk_ensure(
_config_string: Option<String>,
_password: Option<String>,
_machine_id: Option<String>,
) -> Result<RustdeskProvisioningResult, String> {
Err("Provisionamento automático do RustDesk está disponível apenas no Windows.".to_string())
}
@ -83,7 +108,7 @@ pub fn run() {
start_machine_agent,
stop_machine_agent,
open_devtools,
provision_rustdesk
ensure_rustdesk_and_emit
])
.run(tauri::generate_context!())
.expect("error while running tauri application");

View file

@ -1,7 +1,7 @@
#![cfg(target_os = "windows")]
use crate::RustdeskProvisioningResult;
use chrono::Local;
use chrono::{Local, Utc};
use once_cell::sync::Lazy;
use parking_lot::Mutex;
use reqwest::blocking::Client;
@ -53,9 +53,13 @@ struct ReleaseResponse {
assets: Vec<ReleaseAsset>,
}
pub fn provision(machine_id: &str) -> Result<RustdeskProvisioningResult, RustdeskError> {
pub fn ensure_rustdesk(
config_string: Option<&str>,
password_override: Option<&str>,
machine_id: Option<&str>,
) -> Result<RustdeskProvisioningResult, RustdeskError> {
let _guard = PROVISION_MUTEX.lock();
log_event("Iniciando provisionamento do RustDesk");
log_event("Iniciando preparo do RustDesk");
let exe_path = detect_executable_path();
let (installed_version, freshly_installed) = ensure_installed(&exe_path)?;
@ -65,26 +69,57 @@ pub fn provision(machine_id: &str) -> Result<RustdeskProvisioningResult, Rustdes
"RustDesk já instalado, usando binário existente"
});
let config_path = write_config_files()?;
log_event(&format!(
"Arquivo de configuração atualizado em {}",
config_path.display()
));
if let Err(error) = apply_config(&exe_path, &config_path) {
log_event(&format!("Falha ao aplicar configuração via CLI: {error}"));
if let Some(value) = config_string.and_then(|raw| {
let trimmed = raw.trim();
if trimmed.is_empty() { None } else { Some(trimmed) }
}) {
if let Err(error) = run_with_args(&exe_path, &["--config", value]) {
log_event(&format!("Falha ao aplicar configuração inline: {error}"));
} else {
log_event("Configuração aplicada via --config");
}
} else {
log_event("Configuração aplicada via CLI");
let config_path = write_config_files()?;
log_event(&format!(
"Arquivo de configuração atualizado em {}",
config_path.display()
));
if let Err(error) = apply_config(&exe_path, &config_path) {
log_event(&format!("Falha ao aplicar configuração via CLI: {error}"));
} else {
log_event("Configuração aplicada via CLI");
}
}
if let Err(error) = set_password(&exe_path) {
let password = password_override
.map(|value| value.trim().to_string())
.filter(|value| !value.is_empty())
.unwrap_or_else(|| DEFAULT_PASSWORD.to_string());
if let Err(error) = set_password(&exe_path, &password) {
log_event(&format!("Falha ao definir senha padrão: {error}"));
} else {
log_event("Senha padrão definida com sucesso");
}
let custom_id = set_custom_id(&exe_path, machine_id)?;
log_event(&format!("ID determinístico definido: {custom_id}"));
let custom_id = if let Some(value) = machine_id.and_then(|raw| {
let trimmed = raw.trim();
if trimmed.is_empty() { None } else { Some(trimmed) }
}) {
match set_custom_id(&exe_path, value) {
Ok(custom) => {
log_event(&format!("ID determinístico definido: {custom}"));
Some(custom)
}
Err(error) => {
log_event(&format!("Falha ao definir ID determinístico: {error}"));
None
}
}
} else {
None
};
if let Err(error) = ensure_service_running() {
log_event(&format!("Falha ao reiniciar serviço do RustDesk: {error}"));
@ -92,21 +127,26 @@ pub fn provision(machine_id: &str) -> Result<RustdeskProvisioningResult, Rustdes
log_event("Serviço RustDesk reiniciado/run ativo");
}
let reported_id = query_id(&exe_path)
.ok()
.filter(|value| !value.is_empty())
.or_else(|| {
let fallback = read_remote_id_from_profiles();
if let Some(value) = &fallback {
log_event(&format!("ID obtido via arquivos de perfil: {value}"));
let reported_id = match query_id_with_retries(&exe_path, 5) {
Ok(value) => value,
Err(error) => {
log_event(&format!("Falha ao obter ID após múltiplas tentativas: {error}"));
match read_remote_id_from_profiles().or_else(|| custom_id.clone()) {
Some(value) => {
log_event(&format!("ID obtido via arquivos de perfil: {value}"));
value
}
None => return Err(error),
}
fallback
})
.unwrap_or_else(|| custom_id.clone());
if reported_id != custom_id {
log_event(&format!(
"ID retornado difere do determinístico ({custom_id}) -> aplicando {reported_id}"
));
}
};
if let Some(expected) = custom_id.as_ref() {
if expected != &reported_id {
log_event(&format!(
"ID retornado difere do determinístico ({expected}) -> aplicando {reported_id}"
));
}
}
ensure_remote_id_files(&reported_id);
@ -114,9 +154,10 @@ pub fn provision(machine_id: &str) -> Result<RustdeskProvisioningResult, Rustdes
let result = RustdeskProvisioningResult {
id: reported_id.clone(),
password: DEFAULT_PASSWORD.to_string(),
password: password.clone(),
installed_version: version.clone(),
updated: freshly_installed,
last_provisioned_at: Utc::now().timestamp_millis(),
};
log_event(&format!("Provisionamento concluído. ID final: {reported_id}. Versão: {:?}", version));
@ -266,8 +307,8 @@ fn apply_config(exe_path: &Path, config_path: &Path) -> Result<(), RustdeskError
Ok(())
}
fn set_password(exe_path: &Path) -> Result<(), RustdeskError> {
run_with_args(exe_path, &["--password", DEFAULT_PASSWORD])
fn set_password(exe_path: &Path, secret: &str) -> Result<(), RustdeskError> {
run_with_args(exe_path, &["--password", secret])
}
fn set_custom_id(exe_path: &Path, machine_id: &str) -> Result<String, RustdeskError> {
@ -309,6 +350,25 @@ fn run_sc(args: &[&str]) -> Result<(), RustdeskError> {
Ok(())
}
fn query_id_with_retries(exe_path: &Path, attempts: usize) -> Result<String, RustdeskError> {
let mut last_error: Option<RustdeskError> = None;
for attempt in 0..attempts {
match query_id(exe_path) {
Ok(value) if !value.trim().is_empty() => return Ok(value),
Ok(_) => {
last_error = Some(RustdeskError::MissingId);
}
Err(error) => {
last_error = Some(error);
}
}
if attempt + 1 < attempts {
thread::sleep(Duration::from_millis(800));
}
}
Err(last_error.unwrap_or(RustdeskError::MissingId))
}
fn query_id(exe_path: &Path) -> Result<String, RustdeskError> {
let output = Command::new(exe_path)
.arg("--get-id")