ArboreoLab OCR Worker - macOS Desktop App
Visão Geral
Aplicativo desktop para macOS que gerencia workers Python de OCR (Vision) com conexão SSH reversa ao servidor ArboreoLab.
Versão Atual: 1.0.26
Stack Tecnológico
| Tecnologia | Versão | Propósito |
|---|---|---|
| Quasar Framework | 2.18.3 | UI Framework (Material Design) |
| Vue.js | 3.5.13 | Framework reativo (Composition API) |
| Electron | 39.2.7 | Runtime desktop cross-platform |
| TypeScript | 5.7.x | Tipagem estática |
| Pinia | 2.3.0 | State management |
| ssh2 | 1.17.0 | Conexão SSH nativa (sem dependência de CLI) |
| Socket.IO | 5.10.0 | WebSocket bidirecional (complementa SSH) |
| Vite | 6.4.1 | Build tool |
| electron-builder | 26.0.12 | Empacotamento |
Estrutura do Projeto
macos-worker-interface/
├── src/ # Frontend Vue.js
│ ├── App.vue # Componente raiz
│ ├── css/
│ │ └── quasar.variables.scss # Variáveis de tema (dark mode)
│ ├── components/
│ │ ├── DashboardPanel.vue # Painel principal com status
│ │ ├── JobQueue.vue # Fila de jobs OCR
│ │ ├── LogViewer.vue # Visualizador de logs em tempo real
│ │ └── SettingsPanel.vue # Configurações (Token, Python, SSH)
│ ├── layouts/
│ │ └── MainLayout.vue # Layout com sidebar e header
│ ├── pages/
│ │ ├── DashboardPage.vue # Página do dashboard
│ │ ├── JobsPage.vue # Página de jobs
│ │ ├── LogsPage.vue # Página de logs
│ │ └── SettingsPage.vue # Página de configurações
│ ├── router/
│ │ └── index.ts # Vue Router configuração
│ ├── stores/
│ │ ├── app.ts # Store principal (conexão, status)
│ │ ├── logs.ts # Store centralizado de logs (singleton)
│ │ └── settings.ts # Store de configurações persistentes
│ └── types/
│ └── index.ts # Tipos TypeScript compartilhados
├── src-electron/ # Backend Electron (Node.js)
│ ├── electron-main.ts # Processo principal
│ ├── electron-preload.ts # Bridge seguro (contextBridge)
│ └── services/
│ ├── LogWatcher.ts # Monitor de logs do worker
│ ├── PythonManager.ts # Gerenciador Python/venv
│ ├── SshTunnelManager.ts # Gerenciador de túnel SSH
│ └── TokenManager.ts # Gerenciador de tokens de acesso
├── resources/
│ └── python/
│ ├── worker_server.py # Worker Python (Vision OCR)
│ ├── websocket_client.py # Cliente Socket.IO (WebSocket)
│ └── requirements.txt # Dependências Python
├── build/
│ ├── icon.png # Ícone 1024x1024
│ ├── icon.icns # Ícone macOS
│ └── icons/ # Ícones em vários tamanhos
├── quasar.config.ts # Configuração Quasar
├── electron-builder.yml # Configuração de empacotamento
├── package.json # Dependências e scripts
└── tsconfig.json # Configuração TypeScript
Arquitetura
Separação de Processos (Electron)
┌─────────────────────────────────────────────────────────────────┐
│ RENDERER PROCESS │
│ (Vue.js 3 + Quasar + Pinia) │
│ │
│ ┌──────────────┐ ┌──────────────┐ ┌──────────────┐ │
│ │ DashboardPanel│ │ JobQueue │ │ LogViewer │ │
│ └──────────────┘ └──────────────┘ └──────────────┘ │
│ │ │
│ window.electronAPI │
└────────────────────────────┼────────────────────────────────────┘
│ IPC (contextBridge)
┌────────────────────────────┼────────────────────────────────────┐
│ MAIN PROCESS │
│ (Node.js + Electron) │
│ │
│ ┌──────────────┐ ┌──────────────┐ ┌──────────────┐ │
│ │ TokenManager │ │ SshTunnel │ │ PythonManager│ │
│ └──────────────┘ │ Manager │ └──────────────┘ │
│ └──────────────┘ │
│ │ │
│ ssh2 (nativo) │
└────────────────────────────┼────────────────────────────────────┘
│
┌────────▼────────┐
│ Servidor SSH │
│ (ArboreoLab) │
│ │
│ Reverse Tunnel │
│ 9001 → 8766 │
└─────────────────┘
Arquitetura de Comunicação (Dual-Path)
┌─────────────────┐ ┌─────────────────┐
│ Mac Worker │ WebSocket (Socket.IO) │ Node Backend │
│ (Python) │◄─────────────────────────────────►│ (Express) │
│ │ wss:// - bidirectional │ │
│ │ │ │
│ │ SSH Tunnel (Reverso) │ │
│ │◄─────────────────────────────────►│ │
│ │ Port 9001 → 8766 │ │
└─────────────────┘ └─────────────────┘
Redundância: WebSocket e SSH tunnel funcionam em paralelo para máxima resiliência.
Fluxo de Dados
- Token de Acesso: Usuário importa arquivo
.arbtokencom credenciais SSH - Conexão SSH:
SshTunnelManagerestabelece túnel reverso usandossh2 - Worker Python:
PythonManagergerencia venv e executaworker_server.py - Comunicação: Servidor acessa worker local via porta 8766 (túnel reverso)
Serviços do Main Process
TokenManager (src-electron/services/TokenManager.ts)
Gerencia tokens de acesso ArboreoLab.
interface AccessToken {
version: string;
created: string;
project: string;
host: string;
port: number;
username: string;
privateKey: string;
remotePort: number;
localPort: number;
}
// Métodos principais
getToken(): AccessToken | null
saveToken(token: AccessToken): void
importTokenFile(filePath: string): AccessToken
clearToken(): void
Armazenamento: ~/.arboreolab/access_token.json
SshTunnelManager (src-electron/services/SshTunnelManager.ts)
Gerencia conexão SSH com túnel reverso usando biblioteca ssh2 nativa.
Features (v1.0.22+):
- Reconexão infinita (autossh-like behavior)
- Backoff exponencial + jitter (evita thundering herd)
- Tunnel health check ativo (exec dummy a cada 60s)
- Estatísticas de conexão (observability)
// Configuração do túnel
connect(config: {
host: string;
port: number;
username: string;
privateKey: string;
remotePort: number; // Porta no servidor (9001)
localPort: number; // Porta local do worker (8766)
}): Promise<void>
disconnect(): void
getStatus(): { connected: boolean; stats: ConnectionStats; uptime?: number }
Parâmetros de Resiliência:
| Parâmetro | Valor | Descrição |
|---|---|---|
maxReconnectAttempts | 0 (infinito) | Nunca desiste |
reconnectDelay | 5000ms | Delay base |
maxReconnectDelay | 60000ms | Delay máximo |
keepaliveInterval | 30000ms | SSH keepalive |
tunnelHealthInterval | 60000ms | Health check ativo |
Túnel Reverso: O servidor conecta na porta remotePort (9001) e o tráfego é redirecionado para localPort (8766) onde o worker Python escuta.
PythonManager (src-electron/services/PythonManager.ts)
Gerencia ambiente Python e worker.
Features (v1.0.20+):
- Health check tolerante (3 falhas consecutivas antes de alertar)
- Intervalo aumentado (60s) para não sobrecarregar durante OCR
- Timeout aumentado (15s) para workers ocupados
Features (v1.0.24+):
isStartinglock para evitar múltiplas inicializações simultâneas- Método
start()agora éasyncpara sequenciamento correto
Features (v1.0.26+):
killProcessOnPort(port): Limpa processos órfãos antes de iniciar worker- Usa
lsof -ti:${port}para encontrar PIDs ocupando a porta - Previne erro "address already in use" em restarts
// Verificar ambiente
checkEnvironment(): Promise<{
pythonInstalled: boolean;
pythonPath: string;
pythonVersion: string;
venvExists: boolean;
venvPath: string;
requirementsInstalled: boolean;
}>
// Configurar venv
setupVenv(onProgress?: (msg: string) => void): Promise<void>
// Remover venv
removeVenv(): Promise<void>
// Executar worker (async, com cleanup de porta)
async start(): Promise<void> // v1.0.26: async + killProcessOnPort
stopWorker(): void
// Limpar processos órfãos (v1.0.26+)
async killProcessOnPort(port: number): Promise<void>
Parâmetros de Health Check:
| Parâmetro | Valor | Descrição |
|---|---|---|
HEALTH_CHECK_INTERVAL | 60000ms | Intervalo entre checks |
HEALTH_CHECK_TIMEOUT | 15000ms | Timeout por check |
MAX_HEALTH_FAILURES | 3 | Falhas antes de alertar |
Venv: Criado em ~/.arboreolab/venv/
Worker: Executa worker_server.py na porta 8766
🎨 Padrões de Catálogo de Arte
O worker inclui padrões especializados para processamento de catálogos de arte, integrados diretamente na função _process_column_to_markdown().
Padrões Reconhecidos
| Tipo | Regex | Exemplo | Formatação |
|---|---|---|---|
| Artista | ^[A-Z]{2,}(?:\s+[A-Za-z]+)+$ | IKEDA Masuo | **negrito** |
| Dimensão | ^\d+\s*[Xx×]\s*\d+ | 162 X 130 | *itálico* |
| Ano | ^(18|19|20)\d{2}$ | 1962 | texto simples |
| Técnica | Lista de palavras-chave | Litografía | *itálico* |
| Nº Catálogo | ^\d{1,4}\.\s+\S | 33. Orbita | - lista |
| Tiragem | ^\d+/\d+$ ou A.P. | 1/50 | texto simples |
Prioridade de Processamento
1. CATALOG_PATTERNS (artistas, dimensões, técnicas)
↓ não match
2. Heurísticas genéricas (CAPS = título, números = lista)
Isso garante que dimensões (162 X 130) nunca sejam classificadas como títulos (## 162 X 130).
LogWatcher (src-electron/services/LogWatcher.ts)
Monitora logs do worker Python.
start(logPath: string): void
stop(): void
onLog(callback: (line: string) => void): void
🌐 WebSocket (Socket.IO) - v1.0.23+
Comunicação bidirecional em tempo real que complementa o SSH tunnel.
Arquitetura
┌─────────────────┐ ┌─────────────────┐
│ Worker Python │ Socket.IO Client │ Node Backend │
│ websocket_ │◄────────────────────────►│ workerSocket │
│ client.py │ Auto-reconnect │ Service.js │
│ │ Heartbeat 30s │ │
└─────────────────┘ └─────────────────┘
Cliente Python (resources/python/websocket_client.py)
class WorkerWebSocketClient:
def __init__(
self,
server_url: str, # https://srv1.arboreolab.com.br
worker_name: str, # mac-vision-01
auth_token: str = None,
on_job_received: Callable = None,
on_update_available: Callable = None
)
async def connect() -> bool
async def disconnect()
async def emit_job_progress(job_id: int, progress: int, message: str)
async def emit_job_completed(job_id: int, result: dict)
async def emit_job_failed(job_id: int, error: str)
Servidor Node.js (node/backend/services/workerSocketService.js)
// Inicialização
initialize(httpServer, options)
// Enviar job para worker específico
sendJobToWorker(workerName, jobData): Promise
// Enviar job para qualquer worker disponível
sendJobToAnyWorker(jobData): Promise
// Status de todos workers
getWorkersStatus(): { total, idle, processing, workers[] }
// Notificar update
notifyWorkersOfUpdate(updateInfo)
Endpoints REST (WebSocket)
| Endpoint | Método | Descrição |
|---|---|---|
/api/workers/websocket/status | GET | Status de workers conectados |
/api/workers/websocket/job | POST | Enviar job via WebSocket |
/api/workers/websocket/notify-update | POST | Notificar update para todos |
Eventos Socket.IO
| Evento | Direção | Payload |
|---|---|---|
heartbeat | Worker → Server | { status, currentJob, timestamp } |
job:process | Server → Worker | { jobId, fileIds, callbackUrl, ... } |
job:progress | Worker → Server | { jobId, progress, message } |
job:completed | Worker → Server | { jobId, result } |
job:failed | Worker → Server | { jobId, error } |
update:available | Server → Worker | { version, downloadUrl, changelog } |
Configuração
# Habilitar/desabilitar WebSocket no worker
WEBSOCKET_ENABLED=true # default
# Dependência Python
pip install python-socketio[asyncio_client]
🐍 Worker Python (resources/python/worker_server.py)
Servidor FastAPI que processa OCR usando Apple Vision Framework.
Configuração
| Variável | Default | Descrição |
|---|---|---|
WORKER_NAME | mac-vision-01 | Nome único do worker |
LINUX_SERVER_URL | https://srv1.arboreolab.com.br | URL do servidor |
CALLBACK_TIMEOUT | 180 | Timeout base para callbacks (segundos) |
WEBSOCKET_ENABLED | true | Habilitar WebSocket |
OCR_TEMP_DIR | /tmp/ocr_jobs | Diretório temporário |
Callback com Retry (v1.0.21+)
O worker implementa retry automático para callbacks:
# Configuração de retry
MAX_RETRIES = 3
BACKOFF_DELAYS = [10, 20, 30] # segundos entre tentativas
TIMEOUT_PROGRESSION = [180, 240, 300] # timeout crescente
| Tentativa | Wait Before | Timeout | Total Elapsed |
|---|---|---|---|
| 1 | 0s | 180s | 180s |
| 2 | 10s | 240s | 430s |
| 3 | 20s | 300s | 750s |
Importante: O job é marcado como sucesso se o OCR completou, mesmo que o callback falhe após todas as tentativas.
Endpoints
| Endpoint | Método | Descrição |
|---|---|---|
/health | GET | Health check do worker |
/process | POST | Iniciar job OCR |
/jobs/{id} | GET | Status de job específico |
/update/notify | POST | Receber notificação de update |
Progress Reporting
O worker reporta progresso via HTTP e/ou WebSocket:
async def report_progress(job_id: str, progress: int, message: str):
# Tenta WebSocket primeiro (mais eficiente)
if ws_client and ws_client.is_connected:
await ws_client.emit_job_progress(...)
return
# Fallback para HTTP
async with httpx.AsyncClient() as client:
await client.post(f"{SERVER}/api/workers/jobs/{job_id}/progress", ...)
Frontend (Vue.js 3)
Stores (Pinia)
AppStore (src/stores/app.ts)
interface AppState {
connectionStatus: 'disconnected' | 'connecting' | 'connected' | 'error';
workerStatus: 'stopped' | 'starting' | 'running' | 'error';
pythonStatus: PythonEnvironment | null;
logs: LogEntry[];
jobs: Job[];
}
SettingsStore (src/stores/settings.ts)
interface Settings {
token: AccessToken | null;
autoConnect: boolean;
autoStartWorker: boolean;
theme: 'dark' | 'light';
}
LogsStore (src/stores/logs.ts) - v1.0.25+
Store centralizado para gerenciamento de logs com padrão singleton global.
Problema resolvido: Logs duplicados 18-60x devido a múltiplos listeners IPC registrados em hot-reload.
Solução: Flags globais fora do Pinia store (variáveis no nível do módulo).
// ⚠️ CRÍTICO: Flags FORA do defineStore para persistir entre re-instanciações
let globalListenersInitialized = false;
let globalListenerRegistrations: (() => void)[] = [];
interface LogsState {
logs: LogEntry[];
maxLogs: number;
}
// Métodos principais
initListeners(): void // Registra IPC listeners (apenas 1x)
addLog(log: LogEntry): void
clearLogs(): void
Uso correto:
// MainLayout.vue - inicializa UMA vez no app
import { useLogsStore } from '@/stores/logs';
const logsStore = useLogsStore();
logsStore.initListeners(); // Safe: só executa se !globalListenersInitialized
Por que funciona:
globalListenersInitializedestá no escopo do módulo ES- Módulos são singletons em JavaScript (cached após primeiro import)
- Mesmo que Pinia re-instancie a store, a flag permanece
true
Componentes Principais
SettingsPanel.vue
Configurações divididas em seções expansíveis:
- Token de Acesso: Importar/visualizar token
- Ambiente Python: Status do venv, botão de setup
- Conexão SSH: Status e controle do túnel
- Worker Python: Status e controle do processo
DashboardPanel.vue
Visão geral com cards de status:
- Status da conexão SSH
- Status do worker Python
- Jobs processados
- Última atividade
LogViewer.vue
Terminal virtual com logs em tempo real:
- Auto-scroll
- Colorização por nível (info, warn, error)
- Filtros
IPC (Inter-Process Communication)
Preload API (electron-preload.ts)
// Exposto via contextBridge como window.electronAPI
interface ElectronAPI {
// Token
getToken(): Promise<AccessToken | null>;
importToken(filePath: string): Promise<AccessToken>;
clearToken(): Promise<void>;
// SSH
connectSsh(config: SshConfig): Promise<void>;
disconnectSsh(): Promise<void>;
getSshStatus(): Promise<ConnectionStatus>;
onSshStatus(callback: (status: string) => void): void;
// Python
checkPythonEnvironment(): Promise<PythonEnvironment>;
setupPythonVenv(): Promise<void>;
removePythonVenv(): Promise<void>;
onPythonProgress(callback: (msg: string) => void): void;
// Worker
startWorker(): Promise<void>;
stopWorker(): Promise<void>;
getWorkerStatus(): Promise<WorkerStatus>;
// Logs
onLog(callback: (log: LogEntry) => void): void;
// Dialog
showOpenDialog(options: OpenDialogOptions): Promise<string[]>;
}
Handlers no Main Process
// Token handlers
ipcMain.handle('token:get', () => tokenManager.getToken());
ipcMain.handle('token:import', (_, path) => tokenManager.importTokenFile(path));
ipcMain.handle('token:clear', () => tokenManager.clearToken());
// SSH handlers
ipcMain.handle('ssh:connect', (_, config) => sshManager.connect(config));
ipcMain.handle('ssh:disconnect', () => sshManager.disconnect());
ipcMain.handle('ssh:status', () => sshManager.getStatus());
// Python handlers
ipcMain.handle('python:check', () => pythonManager.checkEnvironment());
ipcMain.handle('python:setup', () => pythonManager.setupVenv());
ipcMain.handle('python:remove', () => pythonManager.removeVenv());
Configuração Quasar (quasar.config.ts)
export default configure(() => ({
build: {
target: { browser: ['chrome130'] },
vueRouterMode: 'hash',
},
framework: {
plugins: ['Notify', 'Dialog', 'Loading'],
iconSet: 'material-icons',
},
electron: {
bundler: 'builder',
builder: {
appId: 'com.arboreolab.ocrworker',
productName: 'ArboreoLab OCR Worker',
mac: {
identity: null, // Sem assinatura
target: ['dir', 'zip'],
},
},
},
}));
Build e Distribuição
Scripts npm
# Desenvolvimento
npm run dev # Inicia em modo dev com hot-reload
# Build
npm run build # Build Quasar + Electron
# Empacotamento macOS (Apple Silicon)
npx electron-builder --mac --arm64 --config electron-builder.yml
electron-builder.yml
appId: com.arboreolab.ocrworker
productName: ArboreoLab OCR Worker
electronVersion: "39.2.7"
directories:
output: dist/electron/Packaged
buildResources: build
app: dist/electron/UnPackaged
mac:
category: public.app-category.productivity
icon: build/icon.png
identity: null # Sem assinatura
hardenedRuntime: false
gatekeeperAssess: false
target:
- target: dir
arch: [arm64] # Apple Silicon only
- target: zip
arch: [arm64]
extraResources:
- from: "resources/python"
to: "python"
Sistema de Auto-Update
O app possui sistema de atualização automática que:
- Recebe notificação do servidor via endpoint
/update/notify - Baixa o tarball fonte do servidor
- Extrai e recompila localmente no Mac
- Substitui o app atual
# Notificar workers sobre nova versão
curl -X POST http://localhost:9001/update/notify \
-H "Content-Type: application/json" \
-d '{
"version": "1.0.14",
"changelog": "Descrição da atualização",
"downloadUrl": "https://srv1.arboreolab.com.br/downloads/macos-worker-v1.0.14-src.tar.gz",
"size": 135029
}'
Build Local no macOS (Recomendado)
O build nativo no macOS é necessário para Apple Silicon:
# No Mac (Apple Silicon)
cd macos-worker-interface
npm install
npm run build
npx electron-builder --mac --arm64 --config electron-builder.yml
# Output: dist/electron/Packaged/ArboreoLab OCR Worker-1.0.14-arm64-mac.zip
Importante: Intel (x64) não é mais suportado. Apenas Apple Silicon (arm64).
Instalação no macOS (Usuário Final)
Sem Assinatura Apple
O app não possui assinatura, então o macOS bloqueará na primeira execução:
# Remover quarentena
xattr -cr '/Applications/ArboreoLab OCR Worker.app'
Ou via Preferências do Sistema → Privacidade e Segurança → "Abrir Mesmo Assim"
Requisitos
- macOS 11.0+ (Big Sur ou superior) - Apple Silicon (M1/M2/M3/M4)
- Python 3.8+ instalado (para o worker)
- Node.js 18+ e npm (para auto-update/recompilação)
- Conexão com internet (para SSH e updates)
Padrões de Código
TypeScript Strict
// tsconfig.json
{
"compilerOptions": {
"strict": true,
"noImplicitAny": true,
"strictNullChecks": true
}
}
Vue 3 Composition API
<script setup lang="ts">
import { ref, computed, onMounted } from 'vue';
import { useAppStore } from '@/stores/app';
const appStore = useAppStore();
const isConnected = computed(() => appStore.connectionStatus === 'connected');
onMounted(async () => {
await appStore.initialize();
});
</script>
Tratamento de Erros
// Main process - sempre try/catch
ipcMain.handle('ssh:connect', async (_, config) => {
try {
await sshManager.connect(config);
return { success: true };
} catch (error) {
console.error('SSH connection failed:', error);
return { success: false, error: (error as Error).message };
}
});
// Renderer - usar composable useToast
const { showError } = useToast();
try {
await window.electronAPI.connectSsh(config);
} catch (error) {
showError('Falha na conexão SSH');
}
Troubleshooting
Erro: "Object has been destroyed" (Electron crash)
Causa: Chamadas IPC para mainWindow após a janela ser destruída (fechar app durante evento SSH).
Solução (v1.0.18+): Usar safeSend() pattern em todos os services:
private isWindowAvailable(): boolean {
return !!(this.mainWindow && !this.mainWindow.isDestroyed());
}
private safeSend(channel: string, data: unknown): void {
if (this.isWindowAvailable()) {
try {
this.mainWindow!.webContents.send(channel, data);
} catch (e) {
console.warn(`Janela destruída: ${channel}`);
}
}
}
Erro: "Not allowed to load local resource"
Causa: Caminho do index.html incorreto no electron-main.ts
Solução: Verificar mainWindow.loadFile() usa caminho correto:
// Em produção, index.html está na raiz do app
mainWindow.loadFile(path.join(__dirname, 'index.html'));
Erro: httpx.ReadTimeout no callback
Causa: Timeout muito curto para payloads grandes via SSH tunnel.
Solução (v1.0.21+): Callback retry automático com timeout progressivo:
# Configurado no worker_server.py
CALLBACK_TIMEOUT = 180 # Base
# Retries: 180s → 240s → 300s
Erro: Logs duplicados 18-60x (v1.0.26 fix)
Causa: Múltiplos listeners IPC registrados durante hot-reload ou re-renderização de componentes.
Sintomas:
- Cada log aparece 18-60 vezes no LogViewer
- Worker reinicia em loop infinito
- Mensagens "address already in use" para porta 8766
Solução (v1.0.25-26):
- Store centralizado:
src/stores/logs.tscom flags globais - Flags fora do Pinia: Variáveis no nível do módulo ES
- Inicialização única:
initListeners()só executa 1x
// src/stores/logs.ts
let globalListenersInitialized = false; // FORA do defineStore!
export const useLogsStore = defineStore('logs', () => {
function initListeners() {
if (globalListenersInitialized) return; // Previne duplicação
globalListenersInitialized = true;
window.electronAPI.onLog((log) => {
addLog(log);
});
}
});
Erro: "address already in use" porta 8766 (v1.0.26 fix)
Causa: Processos Python órfãos ocupando a porta após crash/restart.
Solução (v1.0.26): killProcessOnPort() no PythonManager:
// src-electron/services/PythonManager.ts
async function killProcessOnPort(port: number): Promise<void> {
const { exec } = require('child_process');
exec(`lsof -ti:${port} | xargs kill -9`, (error) => {
// Ignora erros se não houver processo
});
}
// Chamado antes de iniciar worker
async start(): Promise<void> {
await killProcessOnPort(8766); // Limpa órfãos
// ... spawn worker
}
Erro no Auto-Update
Causa: URL de download incorreta ou arquivo não encontrado
Solução: Verificar se o tarball está no servidor correto:
# URL correta
https://srv1.arboreolab.com.br/downloads/macos-worker-vX.X.X-src.tar.gz
# NÃO usar geopoliticas.com (não tem /downloads configurado)
SSH: "object could not be cloned"
Causa: Objeto reativo Vue sendo passado para IPC
Solução: Converter para objeto plano:
const config = JSON.parse(JSON.stringify(toRaw(reactiveConfig)));
await window.electronAPI.connectSsh(config);
SSH: Host verification failed
Causa: Chave do host desconhecida
Solução: No SshTunnelManager:
hostVerifier: () => true // Aceitar qualquer host (dev/produção controlada)
Referências
- Quasar Framework v2
- Vue.js 3 Composition API
- Electron Security
- ssh2 Documentation
- Socket.IO
- python-socketio
- electron-builder
📋 Histórico de Versões
| Versão | Data | Principais Mudanças |
|---|---|---|
| 1.0.26 | 2026-01-06 | Fix definitivo log duplication (global singleton fora Pinia), killProcessOnPort() para limpar processos órfãos |
| 1.0.25 | 2026-01-06 | Centralized logs store (src/stores/logs.ts), refatoração LogViewer/DashboardPage |
| 1.0.24 | 2026-01-06 | Fix worker restart loop, isStarting lock no PythonManager |
| 1.0.23 | 2026-01-06 | WebSocket (Socket.IO) bidirecional, dual-path communication |
| 1.0.22 | 2026-01-06 | Reconexão infinita SSH (autossh-like), backoff + jitter, tunnel health check |
| 1.0.21 | 2026-01-06 | Callback retry (3 tentativas, backoff exponencial) |
| 1.0.20 | 2026-01-06 | Health check tolerante (3 falhas antes de alertar) |
| 1.0.19 | 2026-01-06 | Progress indicator no frontend |
| 1.0.18 | 2026-01-06 | Fix crash "Object has been destroyed", CALLBACK_TIMEOUT 180s |