#![cfg(target_os = "windows")] use crate::RustdeskProvisioningResult; use once_cell::sync::Lazy; use parking_lot::Mutex; use reqwest::blocking::Client; use serde::Deserialize; use sha2::{Digest, Sha256}; use std::env; use std::fs::{self, File}; use std::io::{self, Write}; use std::path::{Path, PathBuf}; use std::process::{Command, Stdio}; use std::thread; use std::time::Duration; use thiserror::Error; const RELEASES_API: &str = "https://api.github.com/repos/rustdesk/rustdesk/releases/latest"; const USER_AGENT: &str = "RavenDesktop/1.0"; const SERVER_HOST: &str = "rust.rever.com.br"; const SERVER_KEY: &str = "0mxocQKmK6GvTZQYKgjrG9tlNkKOqf81gKgqwAmnZuI="; const DEFAULT_PASSWORD: &str = "FMQ9MA>e73r.FI> = Lazy::new(|| Mutex::new(())); #[derive(Debug, Error)] pub enum RustdeskError { #[error("HTTP error: {0}")] Http(#[from] reqwest::Error), #[error("I/O error: {0}")] Io(#[from] io::Error), #[error("Release asset não encontrado para Windows x86_64")] AssetMissing, #[error("Falha ao executar comando {command}: status {status:?}")] CommandFailed { command: String, status: Option }, #[error("Falha ao detectar ID do RustDesk")] MissingId, } #[derive(Debug, Deserialize)] struct ReleaseAsset { name: String, browser_download_url: String, } #[derive(Debug, Deserialize)] struct ReleaseResponse { tag_name: String, assets: Vec, } pub fn provision(machine_id: &str) -> Result { let _guard = PROVISION_MUTEX.lock(); let exe_path = detect_executable_path(); let (installed_version, freshly_installed) = ensure_installed(&exe_path)?; let config_path = write_config_files()?; apply_config(&exe_path, &config_path)?; set_password(&exe_path)?; let custom_id = set_custom_id(&exe_path, machine_id)?; ensure_service_running()?; let reported_id = query_id(&exe_path).unwrap_or(custom_id.clone()); let version = query_version(&exe_path).ok().or(installed_version); Ok(RustdeskProvisioningResult { id: reported_id, password: DEFAULT_PASSWORD.to_string(), installed_version: version, updated: freshly_installed, }) } fn detect_executable_path() -> PathBuf { let program_files = env::var("PROGRAMFILES").unwrap_or_else(|_| "C:/Program Files".to_string()); Path::new(&program_files).join("RustDesk").join("rustdesk.exe") } fn ensure_installed(exe_path: &Path) -> Result<(Option, bool), RustdeskError> { if exe_path.exists() { return Ok((None, false)); } let cache_root = PathBuf::from(env::var("PROGRAMDATA").unwrap_or_else(|_| "C:/ProgramData".to_string())) .join(CACHE_DIR_NAME); fs::create_dir_all(&cache_root)?; let (installer_path, version_tag) = download_latest_installer(&cache_root)?; run_installer(&installer_path)?; thread::sleep(Duration::from_secs(20)); Ok((Some(version_tag), true)) } fn download_latest_installer(cache_root: &Path) -> Result<(PathBuf, String), RustdeskError> { let client = Client::builder() .user_agent(USER_AGENT) .timeout(Duration::from_secs(60)) .build()?; let release: ReleaseResponse = client.get(RELEASES_API).send()?.error_for_status()?.json()?; let asset = release .assets .iter() .find(|a| a.name.ends_with("x86_64.exe")) .ok_or(RustdeskError::AssetMissing)?; let target_path = cache_root.join(&asset.name); if target_path.exists() { return Ok((target_path, release.tag_name)); } let mut response = client.get(&asset.browser_download_url).send()?.error_for_status()?; let mut output = File::create(&target_path)?; response.copy_to(&mut output)?; Ok((target_path, release.tag_name)) } fn run_installer(installer_path: &Path) -> Result<(), RustdeskError> { let status = Command::new(installer_path) .arg("--silent-install") .stdout(Stdio::null()) .stderr(Stdio::null()) .status()?; if !status.success() { return Err(RustdeskError::CommandFailed { command: format!("{} --silent-install", installer_path.display()), status: status.code(), }); } Ok(()) } fn write_config_files() -> Result { let config_contents = build_config_contents(); let program_data = PathBuf::from(env::var("PROGRAMDATA").unwrap_or_else(|_| "C:/ProgramData".to_string())); let main_path = program_data.join("RustDesk").join("config").join("RustDesk2.toml"); write_file(&main_path, &config_contents)?; let service_profile = PathBuf::from(r"C:\\Windows\\ServiceProfiles\\LocalService\\AppData\\Roaming\\RustDesk\\config\\RustDesk2.toml"); write_file(&service_profile, &config_contents).ok(); Ok(main_path) } fn write_file(path: &Path, contents: &str) -> Result<(), io::Error> { if let Some(parent) = path.parent() { fs::create_dir_all(parent)?; } fs::write(path, contents) } fn build_config_contents() -> String { format!( r#"[options] key = "{key}" relay-server = "{host}" custom-rendezvous-server = "{host}" api-server = "https://{host}" "#, host = SERVER_HOST, key = SERVER_KEY ) } fn apply_config(exe_path: &Path, config_path: &Path) -> Result<(), RustdeskError> { let status = Command::new(exe_path) .arg("--import-config") .arg(config_path) .stdout(Stdio::null()) .stderr(Stdio::null()) .status()?; if !status.success() { return Err(RustdeskError::CommandFailed { command: format!("{} --import-config {}", exe_path.display(), config_path.display()), status: status.code(), }); } Ok(()) } fn set_password(exe_path: &Path) -> Result<(), RustdeskError> { run_with_args(exe_path, &["--password", DEFAULT_PASSWORD]) } fn set_custom_id(exe_path: &Path, machine_id: &str) -> Result { let custom_id = derive_numeric_id(machine_id); run_with_args(exe_path, &["--set-id", &custom_id])?; Ok(custom_id) } fn derive_numeric_id(machine_id: &str) -> String { let mut hasher = Sha256::new(); hasher.update(machine_id.as_bytes()); let hash = hasher.finalize(); let mut bytes = [0u8; 8]; bytes.copy_from_slice(&hash[..8]); let value = u64::from_le_bytes(bytes); let num = (value % 900_000_000) + 100_000_000; format!("{:09}", num) } fn ensure_service_running() -> Result<(), RustdeskError> { let _ = run_sc(&["stop", SERVICE_NAME]); thread::sleep(Duration::from_secs(2)); let _ = run_sc(&["config", SERVICE_NAME, &format!("start= {}", "auto")]); run_sc(&["start", SERVICE_NAME]) } fn run_sc(args: &[&str]) -> Result<(), RustdeskError> { let status = Command::new("sc") .args(args) .stdout(Stdio::null()) .stderr(Stdio::null()) .status()?; if !status.success() { return Err(RustdeskError::CommandFailed { command: format!("sc {}", args.join(" ")), status: status.code(), }); } Ok(()) } fn query_id(exe_path: &Path) -> Result { let output = Command::new(exe_path) .arg("--get-id") .output()?; if !output.status.success() { return Err(RustdeskError::CommandFailed { command: format!("{} --get-id", exe_path.display()), status: output.status.code(), }); } let stdout = String::from_utf8_lossy(&output.stdout).trim().to_string(); if stdout.is_empty() { return Err(RustdeskError::MissingId); } Ok(stdout) } fn query_version(exe_path: &Path) -> Result { let output = Command::new(exe_path) .arg("--version") .output()?; if !output.status.success() { return Err(RustdeskError::CommandFailed { command: format!("{} --version", exe_path.display()), status: output.status.code(), }); } Ok(String::from_utf8_lossy(&output.stdout).trim().to_string()) } fn run_with_args(exe_path: &Path, args: &[&str]) -> Result<(), RustdeskError> { let status = Command::new(exe_path) .args(args) .stdout(Stdio::null()) .stderr(Stdio::null()) .status()?; if !status.success() { return Err(RustdeskError::CommandFailed { command: format!("{} {}", exe_path.display(), args.join(" ")), status: status.code(), }); } Ok(()) }