15 KiB
Raven + RustDesk Remote Access — Dossiê Técnico
1. Visão Geral
| Componente | Caminho/Fonte | Papel |
|---|---|---|
| Raven (app desktop Tauri) | apps/desktop/src/main.tsx + apps/desktop/src-tauri/src/*.rs |
Provisiona máquinas, coleta inventário e automatiza instalação/config do RustDesk. |
| Serviço RustDesk | Binário Windows (C:\Program Files\RustDesk\rustdesk.exe) comandado via Tauri |
Obtém ID (--get-id), aplica senha (--password) e escreve TOML em C:\ProgramData\RustDesk. |
| Backend Convex | convex/machines.ts |
Mantém tokens de máquina, dados de acesso remoto e funções de auto-heal (upsertRemoteAccessViaToken, heartbeat). |
| API Next.js | src/app/api/machines/remote-access/route.ts etc. |
Camada HTTP que o Raven chama para sincronizar o RustDesk. |
| Admin UI | src/components/admin/devices/admin-devices-overview.tsx |
Exibe os acessos remotos (device.remoteAccess). |
Fluxo ideal:
- Raven registra a máquina (
/api/machines/register), recebemachineTokene salva em%LOCALAPPDATA%\br.com.esdrasrenan.sistemadechamados\machine-agent.jsonvia@tauri-apps/plugin-store(STORE_FILENAME = "machine-agent.json"). - Provisionamento RustDesk — módulo Rust (
apps/desktop/src-tauri/src/rustdesk.rs) executa:rustdesk.exe --silent-install(se necessário)--import-configcomRustDesk2.toml--password <default>(hojeFMQ9MA>e73r.FI<b*34Vmx_8P)--get-id+ gravação nos TOML emC:\ProgramData\RustDesk\confige%APPDATA%\RustDesk\config.- Logs ficam em
%LOCALAPPDATA%\br.com.esdrasrenan.sistemadechamados\logs\rustdesk.log.
- Sincronização com o backend —
syncRustdeskAccess()emapps/desktop/src/main.tsxenviaPOST /api/machines/remote-accesscom{machineToken, provider: "RustDesk", identifier: ID, url, password, notes}. O Convex (upsertRemoteAccessViaToken) converte emmachine.remoteAccess[]. - Admin UI pega
remoteAccessviaapi.devices.listByTenant/getByIde exibe botão “Conectar via RustDesk”.
2. Implementação Atual (Novembro/2025)
2.1 Raven (apps/desktop/src/main.tsx)
- Persistência local:
Store.load()tentaexecutableDir()/data(ex.:C:\Program Files\Raven\data\machine-agent.json), fallback em%LOCALAPPDATA%. Estrutura salva:{ "token": "<machineToken>", "config": { "machineId": "...", "companySlug": "contabil", "collaboratorEmail": "renan.pac@...", "provisioningCode": "2353e53c...", ... }, "rustdesk": { "id": "372726409", "password": "FMQ9MA>...", "installedVersion": "1.4.3", "lastProvisionedAt": 1762895600000, "lastSyncedAt": null, "lastError": null } } - Provisionamento RustDesk:
provision_rustdeskinvocarustdesk::provision(&machine_id)(apps/desktop/src-tauri/src/rustdesk.rs). Esse módulo:-
Baixa release mais recente via GitHub API (
RELEASES_API). -
Instala/atualiza binário, configura
RustDesk2.tomlcomrelay-server,api-server, etc. -
Define ID determinístico (hash do
machine_id, mas depois compara com--get-ide usa o “reportado” caso o serviço tenha um ID próprio). -
Reinicia serviço
RustDesk(sc start RustDesk— pode falhar comstatus Some(5)se não há privilégio admin). Mesmo com falha, a CLI continua e grava o ID nos arquivos. -
Autoelevação única: na primeira execução do botão “Preparar”, o Raven dispara um PowerShell elevado (
takeown + icacls) para liberar ACL dos perfisLocalServiceeLocalSystem. O sucesso gravarustdeskAclUnlockedAtdentro de%LOCALAPPDATA%\br.com.esdrasrenan.sistemadechamados\machine-agent.jsone cria o flagrustdesk_acl_unlocked.flag, evitando novos prompts de UAC nas execuções seguintes. -
Kill/restart seguro: antes de tocar nos TOML, o Raven roda
sc stop RustDesk+taskkill /F /T /IM rustdesk.exe. Isso garante que nenhum cliente sobrescreva oRustDesk_local.tomlenquanto aplicamos a senha. -
Serviço em background reforçado:
ensure_service_runningsempre instala o serviço, forçastart= auto, aplicasc failure ... actions= restart/5000/...e revalida se o serviço ficou emRUNNING. Se não subir, reaplica--install-service+sc startantes de seguir. Assim ele permanece no background/bandeja após boot, sem depender do usuário abrir a UI. -
Replicação completa de perfis: após aplicar
--password, limpamos quaisquer arquivos antigos (RustDesk*.toml,password,passwd*) em%APPDATA%,ProgramData,LocalServiceeLocalSysteme, em seguida, reescrevemos tudo. Agora osRustDesk2.tomlherdados recebemverification-method = "use-permanent-password"eapprove-mode = "password"no bloco[options]antes mesmo do serviço subir, evitando que o RustDesk volte para "Use both". -
Validação por arquivo: o Raven agora registra no
rustdesk.logse opasswordgravado em cadaRustDesk_local.tomlcoincide com o PIN configurado. Para inspecionar manualmente, rode no PowerShell (e valide que todas as linhas exibem o PIN esperado):$targets = @( "$env:APPDATA\RustDesk\config\RustDesk.toml", "$env:APPDATA\RustDesk\config\RustDesk_local.toml", "$env:ProgramData\RustDesk\config\RustDesk.toml", "$env:ProgramData\RustDesk\config\RustDesk_local.toml", "C:\\Windows\\ServiceProfiles\\LocalService\\AppData\\Roaming\\RustDesk\\config\\RustDesk.toml", "C:\\Windows\\ServiceProfiles\\LocalService\\AppData\\Roaming\\RustDesk\\config\\RustDesk_local.toml", "C:\\Windows\\System32\\config\\systemprofile\\AppData\\Roaming\\RustDesk\\config\\RustDesk.toml", "C:\\Windows\\System32\\config\\systemprofile\\AppData\\Roaming\\RustDesk\\config\\RustDesk_local.toml" ) foreach ($path in $targets) { if (Test-Path $path) { "`n$path" Select-String -Path $path -Pattern '^password' } else { "`n$path (inexistente)" } } -
Checklist pós-preparo: todo ciclo bem-sucedido sempre termina com este bloco no
rustdesk.log(nessa ordem):Senha padrão definida com sucessoAplicando senha nos perfis do RustDesk- Quatro linhas
Senha escrita via fallback .../verification-method .../approve-mode ...cobrindo%APPDATA%,ProgramData,LocalService,SystemProfile Senha e flags de segurança gravadas ...seguido porSenha confirmada em ... (FM***8P)para cadaRustDesk.toml- Logs de propagação (
RustDesk.toml propagado para ...,RustDesk_local.toml propagado para ...,RustDesk2.toml propagado ...) Serviço RustDesk reiniciado/run ativoeremote_id atualizado para ...
Observação: as mensagens
Aviso: chave 'password' não encontrada em ...RustDesk_local.tomlsão esperadas — esse arquivo nunca armazena a senha, apenasverification-method/approve-mode. -
Quando algo der errado:
- Se o log parar em
Senha padrão definida...e nada mais aparecer, algum erro interrompeu o módulo antes de gravar os arquivos. Rode o Raven com DevTools (Ctrl+Shift) e capture o stack trace. - Se aparecer “senha divergente” para algum diretório, execute o script PowerShell acima para confirmar e, se necessário, rode “Preparar” novamente (ele sempre derruba
rustdesk.exe, limpa os perfis e reaplica o PIN). - Se
Serviço RustDesk reiniciado...não surgir, pare/inicie manualmente (Stop-Service RustDesk; Start-Service RustDesk) após verificar que os arquivos já trazem a senha nova.
- Se o log parar em
-
Reforço contínuo: toda nova execução do botão “Preparar” repete o ciclo (kill → copiar → gravar flags →
sc start). Se alguém voltar manualmente para “Use both”, o Raven mata o processo, reescreve os TOML e reinicia o serviço — o RustDesk volta travado na senha permanente com o PIN registrado nos logs (rustdesk.log).
-
- Sincronização:
syncRustdeskAccess(machineToken, info)chama/api/machines/remote-access. Há retries automáticos:
Além disso, o heartbeat (if (response.status === 401/500 contendo "token revogado") { await attemptSelfHeal("remote-access") // re-register + replay }/api/machines/heartbeat) enviametadata.remoteAccessSnapshotpara garantir que o Convex receba o RustDesk mesmo que o POST falhe. - Self-heal (
attemptSelfHeal+reRegisterMachine): usa oprovisioningCodesalvo para chamar/api/machines/registernovamente quando o backend diz “token revogado”. Isso reaproveita o mesmomachineIde grava token novo sem intervenção. - Logs adicionais (commit
cdf3fea):logDesktop("remoteAccess:sync:success", {...})escreve no console do Raven (DevTools). Assim conseguimos saber se o POST rodou e se houve erro (remoteAccess:sync:failed).
2.2 Backend Convex (convex/machines.ts)
- Tokens: tabela
machineTokensagora possuirevokedAt. Ao registrar (registermutation), todos os tokens antigos são marcadosrevoked=true,revokedAt=now. - Grace window:
REMOTE_ACCESS_TOKEN_GRACE_MS(default 15 min) permite queupsertRemoteAccessViaTokenaceite tokens recém-revogados — ideal para reinstalações onde o Raven ainda não gravou o token novo. - Heartbeat Snapshot: se
metadata.remoteAccessSnapshotvier,upsertRemoteAccessSnapshotFromHeartbeatnormaliza e grava diretamente emmachine.remoteAccess.
2.3 API Next.js
src/app/api/machines/remote-access/route.ts: recebe{machineToken, provider, identifier, url, password, notes}e chamaclient.mutation(api.devices.upsertRemoteAccessViaToken, payload).src/components/admin/devices/admin-devices-overview.tsx: lêdevice.remoteAccessEntriese monta o cartão com “Copiar ID”, “Conectar via RustDesk”, etc.
3. Problemas Encontrados
3.1 Token fica inválido após reinstalação
- Motivo:
registerrevoga todos os tokens anteriores. O Raven reinstalado continuava com o token antigo, logo qualquer POST recebiaConvexError: Token de dispositivo revogado. - Mitigação já aplicada: grace window + self-heal automático (re-register). Além disso,
heartbeatcom snapshot garante que, mesmo sem POST, o RustDesk seja reaplicado.
3.2 machine-agent.json sem a chave rustdesk
- Observado no dump enviado: apenas
tokeneconfig. Isso significa quewriteRustdeskInfo(store, info)não executou (o provisioning terminou, mas algo impediu o salvamento). Sem essa chave,syncRustdeskAccessnunca roda porque ele depende derustdeskInfocarregado nouseEffect. - Logs do
rustdesk.logsó mostraram o provisioning, nenhuma linha de “sincronização” ou erro. Ou seja: o app não chegou a chamar o POST. - Melhorias: commit
cdf3feaadicionoulogDesktop()e passa a salvarlastError, permitindo ver no console se houve tentativa/erro.
3.3 Deploys derrubando WebSocket (Convex)
- Cada
docker stack deployreinicia o serviçosistema_convex_backend, derrubando o socket por ~40s. Durante esse período o navegador mostraWebSocket closed code 1011/1006,Unexpected response 504, mas reconecta depois do boot. - Sem 2ª réplica, esse “buraco” é inevitável. Solução futura: escalar para 2 replicas com
update_config: order=start-firstou evitar redeploys frequentes.
3.4 Falha na sincronização automática
- Cenário observado: reinstalação -> ID/senha gerados, mas
remoteAccesssó aparece quando rodamoscurlmanual. Diagnóstico atual: o Raven não salvourustdeskInfo(ou não executousyncRustdeskAccess). Precisamos capturar o log do console (remoteAccess:sync:*) para confirmar.
4. Como estamos operando hoje
- Ambiente Linux (dev): fazemos alterações no WSL/Linux, rodamos
bun run lint/build/test, ersynco diretório inteiro para/srv/apps/sistemaviascp/rsync -e "ssh -i ./codex_ed25519". Depoisdocker stack deploy ...e, seconvex/mudou,docker run ... bun x convex deployusando.ci.env(comCONVEX_SELF_HOSTED_URL+CONVEX_SELF_HOSTED_ADMIN_KEY). - Ambiente Windows (para Tauri): há uma cópia do repositório sincronizada com
git pull origin main. Lá rodamosbun install,bun run --cwd apps/desktop tauri build, geramos o instalador.exe, reinstalamos o Raven e testamos na estação real. Os testes de sincronização dependem de olhar%LOCALAPPDATA%\...\machine-agent.jsonerustdesk.log. - Sem app.log: atualmente só temos
rustdesk.log. Os novosconsole.logservem como “log” enquanto não adicionamos um arquivo dedicado. Próximo passo: escrever também em%LOCALAPPDATA%\...\logs\app.log(não feito ainda).
5. Próximos passos recomendados
- Distribuir o build com logs (commit
cdf3fea) e reproduzir a instalação. Abrir o Raven comCTRL+SHIFTpara inspecionar o console. Precisamos ver uma dessas linhas após o provisioning:remoteAccess:sync:success { id: "372726409" }remoteAccess:sync:failed { error: "..." }Isso dirá se o POST está falhando ou nem tenta.
- Persistir logs em arquivo — criar
app.logcomlogDesktopescrevendo em%LOCALAPPDATA%. Hoje o console só aparece se o usuário abre as DevTools; um arquivo facilita coleta remota. - Verificar permissões do Store — se
machine-agent.jsoncontinua sendo gravado emProgram Files\Raven\data, pode haver bloqueio de escrita sem admin. Talvez forçar o uso de%LOCALAPPDATA%resolva. - Adicionar telemetria no backend — logar sempre que
upsertRemoteAccessViaTokené chamado (hoje só loga no erro). Assim sabemos se alguém tentou. - Documentar processo de reinstall para a equipe: “Resetar agente → reinstalar Raven → aguardar log
remoteAccess:sync:success”.
6. Referências rápidas
- Provisionamento manual (PowerShell) — pode ser útil para comparação:
$rustdesk_pw = (-join ((65..90)+(97..122) | Get-Random -Count 12 | % {[char]$_})) $rustdesk_cfg = "configstring" # fornecido pela portal RustDesk Invoke-WebRequest https://github.com/rustdesk/rustdesk/releases/latest ... Start-Process .\rustdesk.exe --silent-install .\rustdesk.exe --password $rustdesk_pw .\rustdesk.exe --get-id - curl para testar backend:
curl -X POST https://tickets.esdrasrenan.com.br/api/machines/remote-access \ -H "Content-Type: application/json" \ -d '{"machineToken":"<TOKEN>","provider":"RustDesk","identifier":"372726409","url":"rustdesk://372726409","password":"FMQ9MA>e73r.FI<b*34Vmx_8P"}'
Este dossiê compila tudo o que sabemos até agora sobre o provisionamento automático e onde ainda estamos com lacunas (principalmente o salvamento do rustdeskInfo). Com ele conseguimos alinhar próximos testes e, se necessário, dividir tarefas (ex.: criar app.log, investigar permissões no Store, etc.).