diff --git a/docs/RUSTDESK-PROVISIONING.md b/docs/RUSTDESK-PROVISIONING.md new file mode 100644 index 0000000..17da77b --- /dev/null +++ b/docs/RUSTDESK-PROVISIONING.md @@ -0,0 +1,130 @@ +# 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: + +1. **Raven registra a máquina** (`/api/machines/register`), recebe `machineToken` e salva em `%LOCALAPPDATA%\br.com.esdrasrenan.sistemadechamados\machine-agent.json` via `@tauri-apps/plugin-store` (`STORE_FILENAME = "machine-agent.json"`). +2. **Provisionamento RustDesk** — módulo Rust (`apps/desktop/src-tauri/src/rustdesk.rs`) executa: + - `rustdesk.exe --silent-install` (se necessário) + - `--import-config` com `RustDesk2.toml` + - `--password ` (hoje `FMQ9MA>e73r.FI", + "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_rustdesk` invoca `rustdesk::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.toml` com `relay-server`, `api-server`, etc. + - Define ID determinístico (hash do `machine_id`, mas depois compara com `--get-id` e usa o “reportado” caso o serviço tenha um ID próprio). + - Reinicia serviço `RustDesk` (`sc start RustDesk` — pode falhar com `status Some(5)` se não há privilégio admin). Mesmo com falha, a CLI continua e grava o ID nos arquivos. +- **Sincronização**: `syncRustdeskAccess(machineToken, info)` chama `/api/machines/remote-access`. Há retries automáticos: + ```ts + if (response.status === 401/500 contendo "token revogado") { + await attemptSelfHeal("remote-access") + // re-register + replay + } + ``` + Além disso, o heartbeat (`/api/machines/heartbeat`) envia `metadata.remoteAccessSnapshot` para garantir que o Convex receba o RustDesk mesmo que o POST falhe. +- **Self-heal** (`attemptSelfHeal` + `reRegisterMachine`): usa o `provisioningCode` salvo para chamar `/api/machines/register` novamente quando o backend diz “token revogado”. Isso reaproveita o mesmo `machineId` e 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 `machineTokens` agora possui `revokedAt`. Ao registrar (`register` mutation), todos os tokens antigos são marcados `revoked=true`, `revokedAt=now`. +- **Grace window**: `REMOTE_ACCESS_TOKEN_GRACE_MS` (default 15 min) permite que `upsertRemoteAccessViaToken` aceite tokens recém-revogados — ideal para reinstalações onde o Raven ainda não gravou o token novo. +- **Heartbeat Snapshot**: se `metadata.remoteAccessSnapshot` vier, `upsertRemoteAccessSnapshotFromHeartbeat` normaliza e grava diretamente em `machine.remoteAccess`. + +### 2.3 API Next.js + +- `src/app/api/machines/remote-access/route.ts`: recebe `{machineToken, provider, identifier, url, password, notes}` e chama `client.mutation(api.devices.upsertRemoteAccessViaToken, payload)`. +- `src/components/admin/devices/admin-devices-overview.tsx`: lê `device.remoteAccessEntries` e 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: `register` revoga todos os tokens anteriores. O Raven reinstalado continuava com o token antigo, logo qualquer POST recebia `ConvexError: Token de dispositivo revogado`. +- Mitigação já aplicada: grace window + self-heal automático (re-register). Além disso, `heartbeat` com snapshot garante que, mesmo sem POST, o RustDesk seja reaplicado. + +### 3.2 `machine-agent.json` sem a chave `rustdesk` +- Observado no dump enviado: apenas `token` e `config`. Isso significa que `writeRustdeskInfo(store, info)` não executou (o provisioning terminou, mas algo impediu o salvamento). Sem essa chave, `syncRustdeskAccess` nunca roda porque ele depende de `rustdeskInfo` carregado no `useEffect`. +- Logs do `rustdesk.log` só mostraram o provisioning, nenhuma linha de “sincronização” ou erro. Ou seja: o app não chegou a chamar o POST. +- Melhorias: commit `cdf3fea` adicionou `logDesktop()` e passa a salvar `lastError`, permitindo ver no console se houve tentativa/erro. + +### 3.3 Deploys derrubando WebSocket (Convex) +- Cada `docker stack deploy` reinicia o serviço `sistema_convex_backend`, derrubando o socket por ~40s. Durante esse período o navegador mostra `WebSocket 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-first` ou evitar redeploys frequentes. + +### 3.4 Falha na sincronização automática +- Cenário observado: reinstalação -> ID/senha gerados, mas `remoteAccess` só aparece quando rodamos `curl` manual. Diagnóstico atual: o Raven não salvou `rustdeskInfo` (ou não executou `syncRustdeskAccess`). 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`, e `rsync` o diretório inteiro para `/srv/apps/sistema` via `scp/rsync -e "ssh -i ./codex_ed25519"`. Depois `docker stack deploy ...` e, se `convex/` mudou, `docker run ... bun x convex deploy` usando `.ci.env` (com `CONVEX_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á rodamos `bun 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.json` e `rustdesk.log`. +- **Sem app.log**: atualmente só temos `rustdesk.log`. Os novos `console.log` servem 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 + +1. **Distribuir o build com logs** (commit `cdf3fea`) e reproduzir a instalação. Abrir o Raven com `CTRL+SHIFT` para 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. +2. **Persistir logs em arquivo** — criar `app.log` com `logDesktop` escrevendo em `%LOCALAPPDATA%`. Hoje o console só aparece se o usuário abre as DevTools; um arquivo facilita coleta remota. +3. **Verificar permissões do Store** — se `machine-agent.json` continua sendo gravado em `Program Files\Raven\data`, pode haver bloqueio de escrita sem admin. Talvez forçar o uso de `%LOCALAPPDATA%` resolva. +4. **Adicionar telemetria no backend** — logar sempre que `upsertRemoteAccessViaToken` é chamado (hoje só loga no erro). Assim sabemos se alguém tentou. +5. **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: + ```pwsh + $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**: + ```bash + curl -X POST https://tickets.esdrasrenan.com.br/api/machines/remote-access \ + -H "Content-Type: application/json" \ + -d '{"machineToken":"","provider":"RustDesk","identifier":"372726409","url":"rustdesk://372726409","password":"FMQ9MA>e73r.FI