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
268
apps/desktop/service/src/main.rs
Normal file
268
apps/desktop/service/src/main.rs
Normal file
|
|
@ -0,0 +1,268 @@
|
|||
//! Raven Service - Servico Windows para operacoes privilegiadas
|
||||
//!
|
||||
//! Este servico roda como LocalSystem e executa operacoes que requerem
|
||||
//! privilegios de administrador, como:
|
||||
//! - Aplicar politicas de USB
|
||||
//! - Provisionar e configurar RustDesk
|
||||
//! - Modificar chaves de registro em HKEY_LOCAL_MACHINE
|
||||
//!
|
||||
//! O app Raven UI comunica com este servico via Named Pipes.
|
||||
|
||||
mod ipc;
|
||||
mod rustdesk;
|
||||
mod usb_policy;
|
||||
|
||||
use std::ffi::OsString;
|
||||
use std::time::Duration;
|
||||
use tracing::{error, info};
|
||||
use windows_service::{
|
||||
define_windows_service,
|
||||
service::{
|
||||
ServiceControl, ServiceControlAccept, ServiceExitCode, ServiceState, ServiceStatus,
|
||||
ServiceType,
|
||||
},
|
||||
service_control_handler::{self, ServiceControlHandlerResult},
|
||||
service_dispatcher,
|
||||
};
|
||||
|
||||
const SERVICE_NAME: &str = "RavenService";
|
||||
const SERVICE_DISPLAY_NAME: &str = "Raven Desktop Service";
|
||||
const SERVICE_DESCRIPTION: &str = "Servico do Raven Desktop para operacoes privilegiadas (USB, RustDesk)";
|
||||
const PIPE_NAME: &str = r"\\.\pipe\RavenService";
|
||||
|
||||
define_windows_service!(ffi_service_main, service_main);
|
||||
|
||||
fn main() -> Result<(), Box<dyn std::error::Error>> {
|
||||
// Configura logging
|
||||
init_logging();
|
||||
|
||||
// Verifica argumentos de linha de comando
|
||||
let args: Vec<String> = std::env::args().collect();
|
||||
|
||||
if args.len() > 1 {
|
||||
match args[1].as_str() {
|
||||
"install" => {
|
||||
install_service()?;
|
||||
return Ok(());
|
||||
}
|
||||
"uninstall" => {
|
||||
uninstall_service()?;
|
||||
return Ok(());
|
||||
}
|
||||
"run" => {
|
||||
// Modo de teste: roda sem registrar como servico
|
||||
info!("Executando em modo de teste (nao como servico)");
|
||||
run_standalone()?;
|
||||
return Ok(());
|
||||
}
|
||||
_ => {}
|
||||
}
|
||||
}
|
||||
|
||||
// Inicia como servico Windows
|
||||
info!("Iniciando Raven Service...");
|
||||
service_dispatcher::start(SERVICE_NAME, ffi_service_main)?;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn init_logging() {
|
||||
use tracing_subscriber::{fmt, prelude::*, EnvFilter};
|
||||
|
||||
// Tenta criar diretorio de logs
|
||||
let log_dir = std::env::var("PROGRAMDATA")
|
||||
.map(|p| std::path::PathBuf::from(p).join("RavenService").join("logs"))
|
||||
.unwrap_or_else(|_| std::path::PathBuf::from("C:\\ProgramData\\RavenService\\logs"));
|
||||
|
||||
let _ = std::fs::create_dir_all(&log_dir);
|
||||
|
||||
// Arquivo de log
|
||||
let log_file = log_dir.join("service.log");
|
||||
let file = std::fs::OpenOptions::new()
|
||||
.create(true)
|
||||
.append(true)
|
||||
.open(&log_file)
|
||||
.ok();
|
||||
|
||||
let filter = EnvFilter::try_from_default_env()
|
||||
.unwrap_or_else(|_| EnvFilter::new("info"));
|
||||
|
||||
if let Some(file) = file {
|
||||
tracing_subscriber::registry()
|
||||
.with(filter)
|
||||
.with(fmt::layer().with_writer(file).with_ansi(false))
|
||||
.init();
|
||||
} else {
|
||||
tracing_subscriber::registry()
|
||||
.with(filter)
|
||||
.with(fmt::layer())
|
||||
.init();
|
||||
}
|
||||
}
|
||||
|
||||
fn service_main(arguments: Vec<OsString>) {
|
||||
if let Err(e) = run_service(arguments) {
|
||||
error!("Erro ao executar servico: {}", e);
|
||||
}
|
||||
}
|
||||
|
||||
fn run_service(_arguments: Vec<OsString>) -> Result<(), Box<dyn std::error::Error + Send + Sync>> {
|
||||
info!("Servico iniciando...");
|
||||
|
||||
// Canal para shutdown
|
||||
let (shutdown_tx, shutdown_rx) = tokio::sync::oneshot::channel::<()>();
|
||||
let shutdown_tx = std::sync::Arc::new(std::sync::Mutex::new(Some(shutdown_tx)));
|
||||
|
||||
// Registra handler de controle do servico
|
||||
let shutdown_tx_clone = shutdown_tx.clone();
|
||||
let status_handle = service_control_handler::register(SERVICE_NAME, move |control| {
|
||||
match control {
|
||||
ServiceControl::Stop | ServiceControl::Shutdown => {
|
||||
info!("Recebido comando de parada");
|
||||
if let Ok(mut guard) = shutdown_tx_clone.lock() {
|
||||
if let Some(tx) = guard.take() {
|
||||
let _ = tx.send(());
|
||||
}
|
||||
}
|
||||
ServiceControlHandlerResult::NoError
|
||||
}
|
||||
ServiceControl::Interrogate => ServiceControlHandlerResult::NoError,
|
||||
_ => ServiceControlHandlerResult::NotImplemented,
|
||||
}
|
||||
})?;
|
||||
|
||||
// Atualiza status para Running
|
||||
status_handle.set_service_status(ServiceStatus {
|
||||
service_type: ServiceType::OWN_PROCESS,
|
||||
current_state: ServiceState::Running,
|
||||
controls_accepted: ServiceControlAccept::STOP | ServiceControlAccept::SHUTDOWN,
|
||||
exit_code: ServiceExitCode::Win32(0),
|
||||
checkpoint: 0,
|
||||
wait_hint: Duration::default(),
|
||||
process_id: None,
|
||||
})?;
|
||||
|
||||
info!("Servico em execucao, aguardando conexoes...");
|
||||
|
||||
// Cria runtime Tokio
|
||||
let runtime = tokio::runtime::Runtime::new()?;
|
||||
|
||||
// Executa servidor IPC
|
||||
runtime.block_on(async {
|
||||
tokio::select! {
|
||||
result = ipc::run_server(PIPE_NAME) => {
|
||||
if let Err(e) = result {
|
||||
error!("Erro no servidor IPC: {}", e);
|
||||
}
|
||||
}
|
||||
_ = async {
|
||||
let _ = shutdown_rx.await;
|
||||
} => {
|
||||
info!("Shutdown solicitado");
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
// Atualiza status para Stopped
|
||||
status_handle.set_service_status(ServiceStatus {
|
||||
service_type: ServiceType::OWN_PROCESS,
|
||||
current_state: ServiceState::Stopped,
|
||||
controls_accepted: ServiceControlAccept::empty(),
|
||||
exit_code: ServiceExitCode::Win32(0),
|
||||
checkpoint: 0,
|
||||
wait_hint: Duration::default(),
|
||||
process_id: None,
|
||||
})?;
|
||||
|
||||
info!("Servico parado");
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn run_standalone() -> Result<(), Box<dyn std::error::Error>> {
|
||||
let runtime = tokio::runtime::Runtime::new()?;
|
||||
|
||||
runtime.block_on(async {
|
||||
info!("Servidor IPC iniciando em modo standalone...");
|
||||
|
||||
tokio::select! {
|
||||
result = ipc::run_server(PIPE_NAME) => {
|
||||
if let Err(e) = result {
|
||||
error!("Erro no servidor IPC: {}", e);
|
||||
}
|
||||
}
|
||||
_ = tokio::signal::ctrl_c() => {
|
||||
info!("Ctrl+C recebido, encerrando...");
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn install_service() -> Result<(), Box<dyn std::error::Error>> {
|
||||
use windows_service::{
|
||||
service::{ServiceAccess, ServiceErrorControl, ServiceInfo, ServiceStartType},
|
||||
service_manager::{ServiceManager, ServiceManagerAccess},
|
||||
};
|
||||
|
||||
info!("Instalando servico...");
|
||||
|
||||
let manager = ServiceManager::local_computer(None::<&str>, ServiceManagerAccess::CREATE_SERVICE)?;
|
||||
|
||||
let exe_path = std::env::current_exe()?;
|
||||
|
||||
let service_info = ServiceInfo {
|
||||
name: OsString::from(SERVICE_NAME),
|
||||
display_name: OsString::from(SERVICE_DISPLAY_NAME),
|
||||
service_type: ServiceType::OWN_PROCESS,
|
||||
start_type: ServiceStartType::AutoStart,
|
||||
error_control: ServiceErrorControl::Normal,
|
||||
executable_path: exe_path,
|
||||
launch_arguments: vec![],
|
||||
dependencies: vec![],
|
||||
account_name: None, // LocalSystem
|
||||
account_password: None,
|
||||
};
|
||||
|
||||
let service = manager.create_service(&service_info, ServiceAccess::CHANGE_CONFIG)?;
|
||||
|
||||
// Define descricao
|
||||
service.set_description(SERVICE_DESCRIPTION)?;
|
||||
|
||||
info!("Servico instalado com sucesso: {}", SERVICE_NAME);
|
||||
println!("Servico '{}' instalado com sucesso!", SERVICE_DISPLAY_NAME);
|
||||
println!("Para iniciar: sc start {}", SERVICE_NAME);
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn uninstall_service() -> Result<(), Box<dyn std::error::Error>> {
|
||||
use windows_service::{
|
||||
service::ServiceAccess,
|
||||
service_manager::{ServiceManager, ServiceManagerAccess},
|
||||
};
|
||||
|
||||
info!("Desinstalando servico...");
|
||||
|
||||
let manager = ServiceManager::local_computer(None::<&str>, ServiceManagerAccess::CONNECT)?;
|
||||
|
||||
let service = manager.open_service(
|
||||
SERVICE_NAME,
|
||||
ServiceAccess::STOP | ServiceAccess::DELETE | ServiceAccess::QUERY_STATUS,
|
||||
)?;
|
||||
|
||||
// Tenta parar o servico primeiro
|
||||
let status = service.query_status()?;
|
||||
if status.current_state != ServiceState::Stopped {
|
||||
info!("Parando servico...");
|
||||
let _ = service.stop();
|
||||
std::thread::sleep(Duration::from_secs(2));
|
||||
}
|
||||
|
||||
// Remove o servico
|
||||
service.delete()?;
|
||||
|
||||
info!("Servico desinstalado com sucesso");
|
||||
println!("Servico '{}' removido com sucesso!", SERVICE_DISPLAY_NAME);
|
||||
|
||||
Ok(())
|
||||
}
|
||||
Loading…
Add table
Add a link
Reference in a new issue