Config self convex #48

Merged
killer-cf merged 3 commits from config-self-convex into master 2025-11-26 18:42:47 +00:00
12 changed files with 505 additions and 673 deletions

1
.dockerignore Normal file
View File

@@ -0,0 +1 @@
node_modules

View File

@@ -32,3 +32,6 @@ jobs:
tags: |
killercf/sgc:latest
platforms: linux/amd64
build-args: |
PUBLIC_CONVEX_URL=${{ secrets.PUBLIC_CONVEX_URL }}
PUBLIC_CONVEX_SITE_URL=${{ secrets.PUBLIC_CONVEX_SITE_URL }}

2
.gitignore vendored
View File

@@ -49,3 +49,5 @@ coverage
tmp
temp
.eslintcache
out

View File

@@ -1,70 +0,0 @@
# Use the official Bun image
FROM oven/bun:1 AS base
# Set the working directory inside the container
WORKDIR /usr/src/app
# Create a non-root user for security
RUN addgroup --system --gid 1001 sveltekit
RUN adduser --system --uid 1001 sveltekit
# Copy package.json and bun.lock (if available)
COPY package.json bun.lock* ./
# Copy workspace packages first (needed for bun install to resolve local dependencies)
# Copy package.json files to establish workspace structure
COPY packages/backend/package.json ./packages/backend/
COPY packages/eslint-config/package.json ./packages/eslint-config/
# Copy eslint-config files (needed for the package to be valid)
COPY packages/eslint-config/*.js ./packages/eslint-config/
# Copy apps/web package.json (also part of the workspace)
COPY apps/web/package.json ./apps/web/
# Install dependencies (including dev dependencies for build)
# This will resolve workspace dependencies using the package.json files we just copied
RUN bun install --frozen-lockfile
# Copy the rest of the source code
COPY . .
# Prepare SvelteKit and build the application
# Navigate to web app directory and build
WORKDIR /usr/src/app/apps/web
RUN bun run prepare
RUN bun run build
# Production stage
FROM oven/bun:1-slim AS production
# Set working directory
WORKDIR /usr/src/app
# Create non-root user
RUN addgroup --system --gid 1001 sveltekit
RUN adduser --system --uid 1001 sveltekit
# Copy built application from base stage
COPY --from=base --chown=sveltekit:sveltekit /usr/src/app/apps/web/build ./build
COPY --from=base --chown=sveltekit:sveltekit /usr/src/app/apps/web/package.json ./package.json
# Copy node_modules from root (Bun manages workspaces centrally)
COPY --from=base --chown=sveltekit:sveltekit /usr/src/app/node_modules ./node_modules
# Copy any additional files needed for runtime
COPY --from=base --chown=sveltekit:sveltekit /usr/src/app/apps/web/static ./static
# Switch to non-root user
USER sveltekit
# Expose the port that the app runs on
EXPOSE 5173
# Set environment variables
ENV NODE_ENV=production
ENV PORT=5173
# Health check
HEALTHCHECK --interval=30s --timeout=3s --start-period=5s --retries=3 \
CMD bun --version || exit 1
# Start the application
CMD ["bun", "./build/index.js"]

View File

@@ -1,29 +0,0 @@
# ⚙️ Configuração de Variáveis de Ambiente
## 📁 Arquivo .env
Crie um arquivo `.env` na pasta `apps/web/` com as seguintes variáveis:
```env
# Google Maps API Key (opcional)
# Obtenha sua chave em: https://console.cloud.google.com/
# Ative a "Geocoding API" para buscar coordenadas por endereço
# Deixe vazio para usar OpenStreetMap (gratuito, sem necessidade de chave)
VITE_GOOGLE_MAPS_API_KEY=
# VAPID Public Key para Push Notifications (opcional)
VITE_VAPID_PUBLIC_KEY=
```
## 📖 Documentação Completa
Para instruções detalhadas sobre como obter e configurar a Google Maps API Key, consulte:
📄 **[GOOGLE_MAPS_SETUP.md](./GOOGLE_MAPS_SETUP.md)**
## ⚠️ Importante
- O arquivo `.env` não deve ser commitado no Git (já está no .gitignore)
- Variáveis de ambiente começam com `VITE_` para serem acessíveis no frontend
- Reinicie o servidor de desenvolvimento após alterar o arquivo `.env`

69
apps/web/Dockerfile Normal file
View File

@@ -0,0 +1,69 @@
# Use the official Bun image
FROM oven/bun:1 AS base
# Set the working directory inside the container
WORKDIR /app
# ---
FROM base AS prepare
RUN bun add -g turbo@^2
COPY . .
RUN turbo prune web --docker
# ---
FROM base AS builder
# First install the dependencies (as they change less often)
COPY --from=prepare /app/out/json/ .
RUN bun install
# Build the project
COPY --from=prepare /app/out/full/ .
# Copy the rest of the source code
COPY --from=prepare /app/out/full/ .
ARG PUBLIC_CONVEX_URL
ENV PUBLIC_CONVEX_URL=$PUBLIC_CONVEX_URL
ARG PUBLIC_CONVEX_SITE_URL
ENV PUBLIC_CONVEX_SITE_URL=$PUBLIC_CONVEX_SITE_URL
RUN bunx turbo build
# Production stage
FROM oven/bun:1-slim AS production
# Set working directory
WORKDIR /app
# Create non-root user
RUN addgroup --system --gid 1001 sveltekit
RUN adduser --system --uid 1001 sveltekit
# Copy built application from base stage
COPY --from=builder --chown=sveltekit:sveltekit /app/apps/web/build ./build
COPY --from=builder --chown=sveltekit:sveltekit /app/apps/web/package.json ./package.json
# Copy node_modules from root (Bun manages workspaces centrally)
COPY --from=builder --chown=sveltekit:sveltekit /app/apps/web/node_modules ./node_modules
# Copy any additional files needed for runtime
COPY --from=builder --chown=sveltekit:sveltekit /app/apps/web/static ./static
# Switch to non-root user
USER sveltekit
# Expose the port that the app runs on
EXPOSE 5173
# Set environment variables
ENV NODE_ENV=production
ENV PORT=5173
# Health check
HEALTHCHECK --interval=30s --timeout=3s --start-period=5s --retries=3 \
CMD bun --version || exit 1
# Start the application
CMD ["bun", "./build/index.js"]

View File

@@ -1,174 +0,0 @@
# 📍 Configuração do Google Maps API para Busca de Coordenadas
Este guia explica como configurar a API do Google Maps para obter coordenadas GPS de forma automática e precisa no sistema de Endereços de Marcação.
## 🎯 Por que usar Google Maps?
-**Maior Precisão**: Resultados mais exatos para endereços brasileiros
-**Melhor Cobertura**: Banco de dados mais completo e atualizado
-**Geocoding Avançado**: Entende melhor endereços incompletos ou parciais
> **Nota**: O sistema funciona perfeitamente sem a API key do Google Maps, usando OpenStreetMap (gratuito). A configuração do Google Maps é opcional.
---
## 📋 Passo a Passo
### 1. Criar Projeto no Google Cloud Platform
1. Acesse [Google Cloud Console](https://console.cloud.google.com/)
2. Clique em **"Criar Projeto"** ou selecione um projeto existente
3. Preencha o nome do projeto (ex: "SGSE-App")
4. Clique em **"Criar"**
### 2. Ativar a Geocoding API
1. No menu lateral, vá em **"APIs e Serviços"** > **"Biblioteca"**
2. Procure por **"Geocoding API"**
3. Clique no resultado e depois em **"Ativar"**
4. Aguarde alguns segundos para a ativação
### 3. Criar Chave de API
1. Ainda em **"APIs e Serviços"**, vá em **"Credenciais"**
2. Clique em **"Criar Credenciais"** > **"Chave de API"**
3. Copie a chave gerada (você precisará dela depois)
### 4. Configurar Restrições de Segurança (Recomendado)
Para proteger sua chave de API:
1. Clique na chave criada para editá-la
2. Em **"Restrições de API"**:
- Selecione **"Restringir chave"**
- Escolha **"Geocoding API"**
3. Em **"Restrições de aplicativo"**:
- Para desenvolvimento local: escolha **"Referenciadores de sites HTTP"**
- Adicione: `http://localhost:*` e `http://127.0.0.1:*`
- Para produção: adicione o domínio do seu site
4. Clique em **"Salvar"**
### 5. Configurar no Projeto
1. No diretório `apps/web/`, copie o arquivo de exemplo:
```bash
cp .env.example .env
```
2. Abra o arquivo `.env` e adicione sua chave:
```env
VITE_GOOGLE_MAPS_API_KEY=sua_chave_aqui
```
3. Reinicie o servidor de desenvolvimento:
```bash
npm run dev
```
### 6. Verificar se está funcionando
1. Acesse a página de **Endereços de Marcação** (`/ti/configuracoes-ponto/enderecos`)
2. Clique em **"Novo Endereço"**
3. Preencha um endereço e clique em **"Buscar GPS"**
4. Se configurado corretamente, verá a mensagem: *"Coordenadas encontradas via Google Maps!"*
---
## 💰 Custos
### Google Maps Geocoding API
- **$5.00 por 1.000 requisições** (primeiros 40.000 são gratuitos por mês)
- **$0.005 por requisição** após os 40.000 gratuitos
> 💡 Para a maioria dos casos de uso, os 40.000 gratuitos são suficientes!
### OpenStreetMap (Fallback)
- **100% Gratuito** e ilimitado
- Sem necessidade de configuração
- Precisão levemente menor, mas ainda muito boa
---
## 🔄 Como funciona o sistema
O sistema foi projetado para usar uma estratégia de **fallback inteligente**:
1. **Primeiro**: Tenta buscar via Google Maps (se API key configurada)
2. **Se falhar ou não tiver API key**: Usa automaticamente OpenStreetMap
3. **Feedback**: Informa qual serviço foi usado na mensagem de sucesso
Isso garante que o sistema sempre funcione, mesmo sem a API key do Google Maps.
---
## 🔒 Segurança
### ⚠️ Importante
- **Nunca** commite o arquivo `.env` no Git (já está no .gitignore)
- **Nunca** compartilhe sua chave de API publicamente
- Configure **restrições de API** no Google Cloud Console
- Para produção, use variáveis de ambiente seguras no seu provedor de hospedagem
### Configuração em Produção
Para ambientes de produção (Vercel, Netlify, etc.):
1. Acesse as configurações do projeto no seu provedor
2. Vá em **"Environment Variables"** ou **"Variáveis de Ambiente"**
3. Adicione: `VITE_GOOGLE_MAPS_API_KEY` com o valor da sua chave
4. Faça o deploy novamente
---
## ❓ Solução de Problemas
### A busca não está usando Google Maps
- Verifique se a variável `VITE_GOOGLE_MAPS_API_KEY` está no arquivo `.env`
- Reinicie o servidor de desenvolvimento
- Verifique no console do navegador se há erros
### Erro: "This API project is not authorized to use this API"
- Verifique se a **Geocoding API** está ativada no projeto
- Aguarde alguns minutos após a ativação (pode levar até 5 minutos)
### Erro: "API key not valid"
- Verifique se copiou a chave corretamente
- Verifique se as restrições de API permitem o uso da Geocoding API
- Verifique se as restrições de aplicativo permitem seu domínio/endereço
### Mensagem: "Coordenadas encontradas via OpenStreetMap"
- Isso é normal se:
- Não há API key configurada
- A API key não é válida
- O Google Maps falhou na busca
- O sistema continua funcionando normalmente com OpenStreetMap
---
## 📚 Recursos Úteis
- [Google Cloud Console](https://console.cloud.google.com/)
- [Documentação Geocoding API](https://developers.google.com/maps/documentation/geocoding)
- [Preços Google Maps](https://developers.google.com/maps/billing-and-pricing/pricing)
- [OpenStreetMap Nominatim](https://nominatim.org/)
---
## ✅ Resumo
1. ✅ Crie projeto no Google Cloud
2. ✅ Ative Geocoding API
3. ✅ Crie chave de API
4. ✅ Configure restrições (recomendado)
5. ✅ Adicione `VITE_GOOGLE_MAPS_API_KEY` no `.env`
6. ✅ Reinicie o servidor
**Pronto!** O sistema agora usará Google Maps para busca de coordenadas com maior precisão.

View File

@@ -4,9 +4,9 @@
"version": "0.0.1",
"type": "module",
"scripts": {
"dev": "vite dev",
"build": "vite build",
"preview": "vite preview",
"dev": "bunx --bun vite dev",
"build": "bunx --bun vite build",
"preview": "bunx --bun vite preview",
"prepare": "svelte-kit sync || echo ''",
"check": "svelte-kit sync && svelte-check --tsconfig ./tsconfig.json",
"check:watch": "svelte-kit sync && svelte-check --tsconfig ./tsconfig.json --watch"
@@ -22,6 +22,7 @@
"esbuild": "^0.25.11",
"postcss": "^8.5.6",
"svelte": "^5.38.1",
"svelte-adapter-bun": "^1.0.1",
"svelte-check": "^4.3.1",
"svelte-dnd-action": "^0.9.67",
"tailwindcss": "^4.1.12",

View File

@@ -1,4 +1,4 @@
import adapter from "@sveltejs/adapter-auto";
import adapter from "svelte-adapter-bun";
import { vitePreprocess } from "@sveltejs/vite-plugin-svelte";
/** @type {import('@sveltejs/kit').Config} */

View File

@@ -67,6 +67,7 @@
"esbuild": "^0.25.11",
"postcss": "^8.5.6",
"svelte": "^5.38.1",
"svelte-adapter-bun": "^1.0.1",
"svelte-check": "^4.3.1",
"svelte-dnd-action": "^0.9.67",
"tailwindcss": "^4.1.12",
@@ -85,7 +86,6 @@
"better-auth": "catalog:",
"convex": "catalog:",
"nodemailer": "^7.0.10",
"ssh2": "^1.17.0",
},
"devDependencies": {
"@sgse-app/eslint-config": "*",
@@ -271,6 +271,12 @@
"@dicebear/thumbs": ["@dicebear/thumbs@9.2.4", "", { "peerDependencies": { "@dicebear/core": "^9.0.0" } }, "sha512-EL4sMqv9p2+1Xy3d8e8UxyeKZV2+cgt3X2x2RTRzEOIIhobtkL8u6lJxmJbiGbpVtVALmrt5e7gjmwqpryYDpg=="],
"@emnapi/core": ["@emnapi/core@1.6.0", "", { "dependencies": { "@emnapi/wasi-threads": "1.1.0", "tslib": "^2.4.0" } }, "sha512-zq/ay+9fNIJJtJiZxdTnXS20PllcYMX3OE23ESc4HK/bdYu3cOWYVhsOhVnXALfU/uqJIxn5NBPd9z4v+SfoSg=="],
"@emnapi/runtime": ["@emnapi/runtime@1.6.0", "", { "dependencies": { "tslib": "^2.4.0" } }, "sha512-obtUmAHTMjll499P+D9A3axeJFlhdjOWdKUNs/U6QIGT7V5RjcUW1xToAzjvmgTSQhDbYn/NwfTRoJcQ2rNBxA=="],
"@emnapi/wasi-threads": ["@emnapi/wasi-threads@1.1.0", "", { "dependencies": { "tslib": "^2.4.0" } }, "sha512-WI0DdZ8xFSbgMjR1sFsKABJ/C5OnRrjT06JXbZKexJGrDuPTzZdDYfFlsgcCXCyf+suG5QU2e/y1Wo2V/OapLQ=="],
"@esbuild/aix-ppc64": ["@esbuild/aix-ppc64@0.25.11", "", { "os": "aix", "cpu": "ppc64" }, "sha512-Xt1dOL13m8u0WE8iplx9Ibbm+hFAO0GsU2P34UNoDGvZYkY8ifSiy6Zuc1lYxfG7svWE2fzqCUmFp5HCn51gJg=="],
"@esbuild/android-arm": ["@esbuild/android-arm@0.25.11", "", { "os": "android", "cpu": "arm" }, "sha512-uoa7dU+Dt3HYsethkJ1k6Z9YdcHjTrSb5NUy66ZfZaSV8hEYGD5ZHbEMXnqLFlbBflLsl89Zke7CAdDJ4JI+Gg=="],
@@ -379,6 +385,8 @@
"@mmailaender/convex-better-auth-svelte": ["@mmailaender/convex-better-auth-svelte@0.2.0", "", { "dependencies": { "is-network-error": "^1.1.0" }, "peerDependencies": { "@convex-dev/better-auth": "^0.9.0", "better-auth": "^1.3.27", "convex": "^1.27.0", "convex-svelte": "^0.0.11", "svelte": "^5.0.0" } }, "sha512-qzahOJg30xErb4ZW+aeszQw4ydhCmKFXn8CeRSA77YxR/dDMgZl+vdWLE4EKsDN0Jd748ecWMnk1fDNNUdgDcg=="],
"@napi-rs/wasm-runtime": ["@napi-rs/wasm-runtime@0.2.12", "", { "dependencies": { "@emnapi/core": "^1.4.3", "@emnapi/runtime": "^1.4.3", "@tybys/wasm-util": "^0.10.0" } }, "sha512-ZVWUcfwY4E/yPitQJl481FjFo3K22D6qF0DuFH6Y/nbnE11GY5uguDxZMGXPQ8WQ0128MXQD7TnfHyK4oWoIJQ=="],
"@next/eslint-plugin-next": ["@next/eslint-plugin-next@15.5.6", "", { "dependencies": { "fast-glob": "3.3.1" } }, "sha512-YxDvsT2fwy1j5gMqk3ppXlsgDopHnkM4BoxSVASbvvgh5zgsK8lvWerDzPip8k3WVzsTZ1O7A7si1KNfN4OZfQ=="],
"@noble/ciphers": ["@noble/ciphers@2.0.1", "", {}, "sha512-xHK3XHPUW8DTAobU+G0XT+/w+JLM7/8k1UFdB5xg/zTFPnFCobhftzw8wl4Lw2aq/Rvir5pxfZV5fEazmeCJ2g=="],
@@ -391,6 +399,10 @@
"@nodelib/fs.walk": ["@nodelib/fs.walk@1.2.8", "", { "dependencies": { "@nodelib/fs.scandir": "2.1.5", "fastq": "^1.6.0" } }, "sha512-oGB+UxlgWcgQkgwo8GcEGwemoTFt3FIO9ababBmaGwXIoBKZ+GTy0pP185beGg7Llih/NSHSV2XAs1lnznocSg=="],
"@oxc-project/runtime": ["@oxc-project/runtime@0.71.0", "", {}, "sha512-QwoF5WUXIGFQ+hSxWEib4U/aeLoiDN9JlP18MnBgx9LLPRDfn1iICtcow7Jgey6HLH4XFceWXQD5WBJ39dyJcw=="],
"@oxc-project/types": ["@oxc-project/types@0.71.0", "", {}, "sha512-5CwQ4MI+P4MQbjLWXgNurA+igGwu/opNetIE13LBs9+V93R64MLvDKOOLZIXSzEfovU3Zef3q3GjPnMTgJTn2w=="],
"@peculiar/asn1-android": ["@peculiar/asn1-android@2.5.0", "", { "dependencies": { "@peculiar/asn1-schema": "^2.5.0", "asn1js": "^3.0.6", "tslib": "^2.8.1" } }, "sha512-t8A83hgghWQkcneRsgGs2ebAlRe54ns88p7ouv8PW2tzF1nAW4yHcL4uZKrFpIU+uszIRzTkcCuie37gpkId0A=="],
"@peculiar/asn1-cms": ["@peculiar/asn1-cms@2.5.0", "", { "dependencies": { "@peculiar/asn1-schema": "^2.5.0", "@peculiar/asn1-x509": "^2.5.0", "@peculiar/asn1-x509-attr": "^2.5.0", "asn1js": "^3.0.6", "tslib": "^2.8.1" } }, "sha512-p0SjJ3TuuleIvjPM4aYfvYw8Fk1Hn/zAVyPJZTtZ2eE9/MIer6/18ROxX6N/e6edVSfvuZBqhxAj3YgsmSjQ/A=="],
@@ -417,6 +429,32 @@
"@polka/url": ["@polka/url@1.0.0-next.29", "", {}, "sha512-wwQAWhWSuHaag8c4q/KN/vCoeOJYshAIvMQwD4GpSb3OiZklFfvAgmj0VCBBImRpuF/aFgIRzllXlVX93Jevww=="],
"@rolldown/binding-darwin-arm64": ["@rolldown/binding-darwin-arm64@1.0.0-beta.9-commit.d91dfb5", "", { "os": "darwin", "cpu": "arm64" }, "sha512-Mp0/gqiPdepHjjVm7e0yL1acWvI0rJVVFQEADSezvAjon9sjQ7CEg9JnXICD4B1YrPmN9qV/e7cQZCp87tTV4w=="],
"@rolldown/binding-darwin-x64": ["@rolldown/binding-darwin-x64@1.0.0-beta.9-commit.d91dfb5", "", { "os": "darwin", "cpu": "x64" }, "sha512-40re4rMNrsi57oavRzIOpRGmg3QRlW6Ea8Q3znaqgOuJuKVrrm2bIQInTfkZJG7a4/5YMX7T951d0+toGLTdCA=="],
"@rolldown/binding-freebsd-x64": ["@rolldown/binding-freebsd-x64@1.0.0-beta.9-commit.d91dfb5", "", { "os": "freebsd", "cpu": "x64" }, "sha512-8BDM939bbMariZupiHp3OmP5N+LXPT4mULA0hZjDaq970PCxv4krZOSMG+HkWUUwmuQROtV+/00xw39EO0P+8g=="],
"@rolldown/binding-linux-arm-gnueabihf": ["@rolldown/binding-linux-arm-gnueabihf@1.0.0-beta.9-commit.d91dfb5", "", { "os": "linux", "cpu": "arm" }, "sha512-sntsPaPgrECpBB/+2xrQzVUt0r493TMPI+4kWRMhvMsmrxOqH1Ep5lM0Wua/ZdbfZNwm1aVa5pcESQfNfM4Fhw=="],
"@rolldown/binding-linux-arm64-gnu": ["@rolldown/binding-linux-arm64-gnu@1.0.0-beta.9-commit.d91dfb5", "", { "os": "linux", "cpu": "arm64" }, "sha512-5clBW/I+er9F2uM1OFjJFWX86y7Lcy0M+NqsN4s3o07W+8467Zk8oQa4B45vdaXoNUF/yqIAgKkA/OEdQDxZqA=="],
"@rolldown/binding-linux-arm64-musl": ["@rolldown/binding-linux-arm64-musl@1.0.0-beta.9-commit.d91dfb5", "", { "os": "linux", "cpu": "arm64" }, "sha512-wv+rnAfQDk9p/CheX8/Kmqk2o1WaFa4xhWI9gOyDMk/ljvOX0u0ubeM8nI1Qfox7Tnh71eV5AjzSePXUhFOyOg=="],
"@rolldown/binding-linux-x64-gnu": ["@rolldown/binding-linux-x64-gnu@1.0.0-beta.9-commit.d91dfb5", "", { "os": "linux", "cpu": "x64" }, "sha512-gxD0/xhU4Py47IH3bKZbWtvB99tMkUPGPJFRfSc5UB9Osoje0l0j1PPbxpUtXIELurYCqwLBKXIMTQGifox1BQ=="],
"@rolldown/binding-linux-x64-musl": ["@rolldown/binding-linux-x64-musl@1.0.0-beta.9-commit.d91dfb5", "", { "os": "linux", "cpu": "x64" }, "sha512-HotuVe3XUjDwqqEMbm3o3IRkP9gdm8raY/btd/6KE3JGLF/cv4+3ff1l6nOhAZI8wulWDPEXPtE7v+HQEaTXnA=="],
"@rolldown/binding-wasm32-wasi": ["@rolldown/binding-wasm32-wasi@1.0.0-beta.9-commit.d91dfb5", "", { "dependencies": { "@napi-rs/wasm-runtime": "^0.2.4" }, "cpu": "none" }, "sha512-8Cx+ucbd8n2dIr21FqBh6rUvTVL0uTgEtKR7l+MUZ5BgY4dFh1e4mPVX8oqmoYwOxBiXrsD2JIOCz4AyKLKxWA=="],
"@rolldown/binding-win32-arm64-msvc": ["@rolldown/binding-win32-arm64-msvc@1.0.0-beta.9-commit.d91dfb5", "", { "os": "win32", "cpu": "arm64" }, "sha512-Vhq5vikrVDxAa75fxsyqj0c0Y/uti/TwshXI71Xb8IeUQJOBnmLUsn5dgYf5ljpYYkNa0z9BPAvUDIDMmyDi+w=="],
"@rolldown/binding-win32-ia32-msvc": ["@rolldown/binding-win32-ia32-msvc@1.0.0-beta.9-commit.d91dfb5", "", { "os": "win32", "cpu": "ia32" }, "sha512-lN7RIg9Iugn08zP2aZN9y/MIdG8iOOCE93M1UrFlrxMTqPf8X+fDzmR/OKhTSd1A2pYNipZHjyTcb5H8kyQSow=="],
"@rolldown/binding-win32-x64-msvc": ["@rolldown/binding-win32-x64-msvc@1.0.0-beta.9-commit.d91dfb5", "", { "os": "win32", "cpu": "x64" }, "sha512-7/7cLIn48Y+EpQ4CePvf8reFl63F15yPUlg4ZAhl+RXJIfydkdak1WD8Ir3AwAO+bJBXzrfNL+XQbxm0mcQZmw=="],
"@rolldown/pluginutils": ["@rolldown/pluginutils@1.0.0-beta.9-commit.d91dfb5", "", {}, "sha512-8sExkWRK+zVybw3+2/kBkYBFeLnEUWz1fT7BLHplpzmtqkOfTbAQ9gkt4pzwGIIZmg4Qn5US5ACjUBenrhezwQ=="],
"@rollup/rollup-android-arm-eabi": ["@rollup/rollup-android-arm-eabi@4.52.5", "", { "os": "android", "cpu": "arm" }, "sha512-8c1vW4ocv3UOMp9K+gToY5zL2XiiVw3k7f1ksf4yO1FlDFQ1C2u72iACFnSOceJFsWskc2WZNqeRhFRPzv+wtQ=="],
"@rollup/rollup-android-arm64": ["@rollup/rollup-android-arm64@4.52.5", "", { "os": "android", "cpu": "arm64" }, "sha512-mQGfsIEFcu21mvqkEKKu2dYmtuSZOBMmAl5CFlPGLY94Vlcm+zWApK7F/eocsNzp8tKmbeBP8yXyAbx0XHsFNA=="],
@@ -605,6 +643,8 @@
"@tanstack/svelte-store": ["@tanstack/svelte-store@0.7.7", "", { "dependencies": { "@tanstack/store": "0.7.7" }, "peerDependencies": { "svelte": "^5.0.0" } }, "sha512-JeDyY7SxBi6EKzkf2wWoghdaC2bvmwNL9X/dgkx7LKEvJVle+te7tlELI3cqRNGbjXt9sx+97jx9M5dCCHcuog=="],
"@tybys/wasm-util": ["@tybys/wasm-util@0.10.1", "", { "dependencies": { "tslib": "^2.4.0" } }, "sha512-9tTaPJLSiejZKx+Bmog4uSubteqTvFrVrURwkmHixBo0G4seD0zUxp98E1DzUBJxLQ3NPwXrGKDiVjwx/DpPsg=="],
"@types/cookie": ["@types/cookie@1.0.0", "", { "dependencies": { "cookie": "*" } }, "sha512-mGFXbkDQJ6kAXByHS7QAggRXgols0mAdP4MuXgloGY1tXokvzaFFM4SMqWvf7AH0oafI7zlFJwoGWzmhDqTZ9w=="],
"@types/estree": ["@types/estree@1.0.8", "", {}, "sha512-dWHzHa2WqEXI/O1E9OjrocMTKJl2mSrEolh1Iomrv6U+JuNwaHXsXx9bLu5gG7BUWFIN0skIQJQ/L1rIex4X6w=="],
@@ -653,6 +693,8 @@
"ansi-styles": ["ansi-styles@4.3.0", "", { "dependencies": { "color-convert": "^2.0.1" } }, "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg=="],
"ansis": ["ansis@4.2.0", "", {}, "sha512-HqZ5rWlFjGiV0tDm3UxxgNRqsOTniqoKZu0pIAfh7TZQMGuZK+hH0drySty0si0QXj1ieop4+SkSfPZBPPkHig=="],
"argparse": ["argparse@2.0.1", "", {}, "sha512-8+9WqebbFzpX9OR+Wa6O29asIogeRMzcGtAINdpMHHyAg10f05aSFVBbcEqGf/PXw1EjAZ+q2/bEBg3DvurK3Q=="],
"aria-query": ["aria-query@5.3.2", "", {}, "sha512-COROpnaoap1E2F000S62r6A60uHZnmlvomhfyT2DlTcrY1OrBKn2UhH7qn5wTC9zMvD0AY7csdPSNwKP+7WiQw=="],
@@ -671,8 +713,6 @@
"arraybuffer.prototype.slice": ["arraybuffer.prototype.slice@1.0.4", "", { "dependencies": { "array-buffer-byte-length": "^1.0.1", "call-bind": "^1.0.8", "define-properties": "^1.2.1", "es-abstract": "^1.23.5", "es-errors": "^1.3.0", "get-intrinsic": "^1.2.6", "is-array-buffer": "^3.0.4" } }, "sha512-BNoCY6SXXPQ7gF2opIP4GBE+Xw7U+pHMYKuzjgCN3GwiaIR09UUeKfheyIry77QtrCBlC0KK0q5/TER/tYh3PQ=="],
"asn1": ["asn1@0.2.6", "", { "dependencies": { "safer-buffer": "~2.1.0" } }, "sha512-ix/FxPn0MDjeyJ7i/yoHGFt/EX6LyNbxSEhPPXODPL+KB0VPk86UYfL0lMdy+KCnv+fmvIzySwaK5COwqVbWTQ=="],
"asn1js": ["asn1js@3.0.6", "", { "dependencies": { "pvtsutils": "^1.3.6", "pvutils": "^1.1.3", "tslib": "^2.8.1" } }, "sha512-UOCGPYbl0tv8+006qks/dTgV9ajs97X2p0FAbyS2iyCRrmLSRolDaHdp+v/CLgnzHc3fVB+CwYiUmei7ndFcgA=="],
"async-function": ["async-function@1.0.0", "", {}, "sha512-hsU18Ae8CDTR6Kgu9DYf0EbCr/a5iGL0rytQDobUcdpYOKokk8LEjVphnXkDkgpi0wYVsqrXuP0bZxJaTqdgoA=="],
@@ -689,8 +729,6 @@
"baseline-browser-mapping": ["baseline-browser-mapping@2.8.21", "", { "bin": { "baseline-browser-mapping": "dist/cli.js" } }, "sha512-JU0h5APyQNsHOlAM7HnQnPToSDQoEBZqzu/YBlqDnEeymPnZDREeXJA3KBMQee+dKteAxZ2AtvQEvVYdZf241Q=="],
"bcrypt-pbkdf": ["bcrypt-pbkdf@1.0.2", "", { "dependencies": { "tweetnacl": "^0.14.3" } }, "sha512-qeFIXtP4MSoi6NLqO12WfqARWWuCKi2Rn/9hJLEmtB5yTNr9DqFWkJRCf2qShWzPeAMRnOgCrq0sg/KLv5ES9w=="],
"better-auth": ["better-auth@1.3.27", "", { "dependencies": { "@better-auth/core": "1.3.27", "@better-auth/utils": "0.3.0", "@better-fetch/fetch": "1.1.18", "@noble/ciphers": "^2.0.0", "@noble/hashes": "^2.0.0", "@simplewebauthn/browser": "^13.1.2", "@simplewebauthn/server": "^13.1.2", "better-call": "1.0.19", "defu": "^6.1.4", "jose": "^6.1.0", "kysely": "^0.28.5", "nanostores": "^1.0.1", "zod": "^4.1.5" } }, "sha512-SwiGAJ7yU6dBhNg0NdV1h5M8T5sa7/AszZVc4vBfMDrLLmvUfbt9JoJ0uRUJUEdKRAAxTyl9yA+F3+GhtAD80w=="],
"better-call": ["better-call@1.0.19", "", { "dependencies": { "@better-auth/utils": "^0.3.0", "@better-fetch/fetch": "^1.1.4", "rou3": "^0.5.1", "set-cookie-parser": "^2.7.1", "uncrypto": "^0.1.3" } }, "sha512-sI3GcA1SCVa3H+CDHl8W8qzhlrckwXOTKhqq3OOPXjgn5aTOMIqGY34zLY/pHA6tRRMjTUC3lz5Mi7EbDA24Kw=="],
@@ -703,8 +741,6 @@
"browserslist": ["browserslist@4.27.0", "", { "dependencies": { "baseline-browser-mapping": "^2.8.19", "caniuse-lite": "^1.0.30001751", "electron-to-chromium": "^1.5.238", "node-releases": "^2.0.26", "update-browserslist-db": "^1.1.4" }, "bin": { "browserslist": "cli.js" } }, "sha512-AXVQwdhot1eqLihwasPElhX2tAZiBjWdJ9i/Zcj2S6QYIjkx62OKSfnobkriB81C3l4w0rVy3Nt4jaTBltYEpw=="],
"buildcheck": ["buildcheck@0.0.6", "", {}, "sha512-8f9ZJCUXyT1M35Jx7MkBgmBMo3oHTTBIPLiY9xyL0pl3T5RwcPEY8cUHr5LBNfu/fk6c2T4DJZuVM/8ZZT2D2A=="],
"call-bind": ["call-bind@1.0.8", "", { "dependencies": { "call-bind-apply-helpers": "^1.0.0", "es-define-property": "^1.0.0", "get-intrinsic": "^1.2.4", "set-function-length": "^1.2.2" } }, "sha512-oKlSFMcMwpUg2ednkhQ454wfWiU/ul3CkJe/PEHcTKuiX6RpbehUiFMXu13HalGZxfUwCQzZG747YXBn1im9ww=="],
"call-bind-apply-helpers": ["call-bind-apply-helpers@1.0.2", "", { "dependencies": { "es-errors": "^1.3.0", "function-bind": "^1.1.2" } }, "sha512-Sp1ablJ0ivDkSzjcaJdxEunN5/XvksFJ2sMBFfq6x0ryhQV/2b/KwFe21cMpmHtPOSij8K99/wSfoEuTObmuMQ=="],
@@ -743,8 +779,6 @@
"core-js": ["core-js@3.46.0", "", {}, "sha512-vDMm9B0xnqqZ8uSBpZ8sNtRtOdmfShrvT6h2TuQGLs0Is+cR0DYbj/KWP6ALVNbWPpqA/qPLoOuppJN07humpA=="],
"cpu-features": ["cpu-features@0.0.10", "", { "dependencies": { "buildcheck": "~0.0.6", "nan": "^2.19.0" } }, "sha512-9IkYqtX3YHPCzoVg1Py+o9057a3i0fp7S530UWokCSaFVTc7CwXPRiOjRjBQQ18ZCNafx78YfnG+HALxtVmOGA=="],
"cross-spawn": ["cross-spawn@7.0.6", "", { "dependencies": { "path-key": "^3.1.0", "shebang-command": "^2.0.0", "which": "^2.0.1" } }, "sha512-uV2QOWP2nWzsy2aMp8aRibhi9dlzF5Hgh5SHaB9OiTGEyDTiJJyx0uy51QXdyWbtAHNua4XJzUKca3OzKUd3vA=="],
"css-line-break": ["css-line-break@2.1.0", "", { "dependencies": { "utrie": "^1.0.2" } }, "sha512-FHcKFCZcAha3LwfVBhCQbW2nCNbkZXn7KVUJcsT5/P8YmfsVja0FMPJr0B903j/E69HUphKiV9iQArX8SDYA4w=="],
@@ -1075,8 +1109,6 @@
"ms": ["ms@2.1.3", "", {}, "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA=="],
"nan": ["nan@2.23.1", "", {}, "sha512-r7bBUGKzlqk8oPBDYxt6Z0aEdF1G1rwlMcLk8LCOMbOzf0mG+JUfUzG4fIMWwHWP0iyaLWEQZJmtB7nOHEm/qw=="],
"nanoid": ["nanoid@3.3.11", "", { "bin": { "nanoid": "bin/nanoid.cjs" } }, "sha512-N8SpfPUnUp1bK+PMYW8qSWdl9U+wwNWI4QKxOYDy9JAro3WMX7p2OeVRF9v+347pnakNevPmiHhNmZ2HbFA76w=="],
"nanostores": ["nanostores@1.0.1", "", {}, "sha512-kNZ9xnoJYKg/AfxjrVL4SS0fKX++4awQReGqWnwTRHxeHGZ1FJFVgTqr/eMrNQdp0Tz7M7tG/TDaX8QfHDwVCw=="],
@@ -1191,6 +1223,8 @@
"rgbcolor": ["rgbcolor@1.0.1", "", {}, "sha512-9aZLIrhRaD97sgVhtJOW6ckOEh6/GnvQtdVNfdZ6s67+3/XwLS9lBcQYzEEhYVeUowN7pRzMLsyGhK2i/xvWbw=="],
"rolldown": ["rolldown@1.0.0-beta.9-commit.d91dfb5", "", { "dependencies": { "@oxc-project/runtime": "0.71.0", "@oxc-project/types": "0.71.0", "@rolldown/pluginutils": "1.0.0-beta.9-commit.d91dfb5", "ansis": "^4.0.0" }, "optionalDependencies": { "@rolldown/binding-darwin-arm64": "1.0.0-beta.9-commit.d91dfb5", "@rolldown/binding-darwin-x64": "1.0.0-beta.9-commit.d91dfb5", "@rolldown/binding-freebsd-x64": "1.0.0-beta.9-commit.d91dfb5", "@rolldown/binding-linux-arm-gnueabihf": "1.0.0-beta.9-commit.d91dfb5", "@rolldown/binding-linux-arm64-gnu": "1.0.0-beta.9-commit.d91dfb5", "@rolldown/binding-linux-arm64-musl": "1.0.0-beta.9-commit.d91dfb5", "@rolldown/binding-linux-x64-gnu": "1.0.0-beta.9-commit.d91dfb5", "@rolldown/binding-linux-x64-musl": "1.0.0-beta.9-commit.d91dfb5", "@rolldown/binding-wasm32-wasi": "1.0.0-beta.9-commit.d91dfb5", "@rolldown/binding-win32-arm64-msvc": "1.0.0-beta.9-commit.d91dfb5", "@rolldown/binding-win32-ia32-msvc": "1.0.0-beta.9-commit.d91dfb5", "@rolldown/binding-win32-x64-msvc": "1.0.0-beta.9-commit.d91dfb5" }, "bin": { "rolldown": "bin/cli.mjs" } }, "sha512-FHkj6gGEiEgmAXQchglofvUUdwj2Oiw603Rs+zgFAnn9Cb7T7z3fiaEc0DbN3ja4wYkW6sF2rzMEtC1V4BGx/g=="],
"rollup": ["rollup@4.52.5", "", { "dependencies": { "@types/estree": "1.0.8" }, "optionalDependencies": { "@rollup/rollup-android-arm-eabi": "4.52.5", "@rollup/rollup-android-arm64": "4.52.5", "@rollup/rollup-darwin-arm64": "4.52.5", "@rollup/rollup-darwin-x64": "4.52.5", "@rollup/rollup-freebsd-arm64": "4.52.5", "@rollup/rollup-freebsd-x64": "4.52.5", "@rollup/rollup-linux-arm-gnueabihf": "4.52.5", "@rollup/rollup-linux-arm-musleabihf": "4.52.5", "@rollup/rollup-linux-arm64-gnu": "4.52.5", "@rollup/rollup-linux-arm64-musl": "4.52.5", "@rollup/rollup-linux-loong64-gnu": "4.52.5", "@rollup/rollup-linux-ppc64-gnu": "4.52.5", "@rollup/rollup-linux-riscv64-gnu": "4.52.5", "@rollup/rollup-linux-riscv64-musl": "4.52.5", "@rollup/rollup-linux-s390x-gnu": "4.52.5", "@rollup/rollup-linux-x64-gnu": "4.52.5", "@rollup/rollup-linux-x64-musl": "4.52.5", "@rollup/rollup-openharmony-arm64": "4.52.5", "@rollup/rollup-win32-arm64-msvc": "4.52.5", "@rollup/rollup-win32-ia32-msvc": "4.52.5", "@rollup/rollup-win32-x64-gnu": "4.52.5", "@rollup/rollup-win32-x64-msvc": "4.52.5", "fsevents": "~2.3.2" }, "bin": { "rollup": "dist/bin/rollup" } }, "sha512-3GuObel8h7Kqdjt0gxkEzaifHTqLVW56Y/bjN7PSQtkKr0w3V/QYSdt6QWYtd7A1xUtYQigtdUfgj1RvWVtorw=="],
"rou3": ["rou3@0.5.1", "", {}, "sha512-OXMmJ3zRk2xeXFGfA3K+EOPHC5u7RDFG7lIOx0X1pdnhUkI8MdVrbV+sNsD80ElpUZ+MRHdyxPnFthq9VHs8uQ=="],
@@ -1207,8 +1241,6 @@
"safe-regex-test": ["safe-regex-test@1.1.0", "", { "dependencies": { "call-bound": "^1.0.2", "es-errors": "^1.3.0", "is-regex": "^1.2.1" } }, "sha512-x/+Cz4YrimQxQccJf5mKEbIa1NzeCRNI5Ecl/ekmlYaampdNLPalVyIcCZNNH3MvmqBugV5TMYZXv0ljslUlaw=="],
"safer-buffer": ["safer-buffer@2.1.2", "", {}, "sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg=="],
"scheduler": ["scheduler@0.27.0", "", {}, "sha512-eNv+WrVbKu1f3vbYJT/xtiF5syA5HPIMtf9IgY/nKg0sWqzAUEvqY/xm7OcZc/qafLx/iO9FgOmeSAp4v5ti/Q=="],
"semver": ["semver@7.7.3", "", { "bin": { "semver": "bin/semver.js" } }, "sha512-SdsKMrI9TdgjdweUSR9MweHA4EJ8YxHn8DFaDisvhVlUOe4BF1tLD7GAj0lIqWVl+dPb/rExr0Btby5loQm20Q=="],
@@ -1237,8 +1269,6 @@
"source-map-js": ["source-map-js@1.2.1", "", {}, "sha512-UXWMKhLOwVKb728IUtQPXxfYU+usdybtUrK/8uGE8CQMvrhOpwvzDBwj0QhSL7MQc7vIsISBG8VQ8+IDQxpfQA=="],
"ssh2": ["ssh2@1.17.0", "", { "dependencies": { "asn1": "^0.2.6", "bcrypt-pbkdf": "^1.0.2" }, "optionalDependencies": { "cpu-features": "~0.0.10", "nan": "^2.23.0" } }, "sha512-wPldCk3asibAjQ/kziWQQt1Wh3PgDFpC0XpwclzKcdT1vql6KeYxf5LIt4nlFkUeR8WuphYMKqUA56X4rjbfgQ=="],
"stackblur-canvas": ["stackblur-canvas@2.7.0", "", {}, "sha512-yf7OENo23AGJhBriGx0QivY5JP6Y1HbrrDI6WLt6C5auYZXlQrheoY8hD4ibekFKz1HOfE48Ww8kMWMnJD/zcQ=="],
"stop-iteration-iterator": ["stop-iteration-iterator@1.1.0", "", { "dependencies": { "es-errors": "^1.3.0", "internal-slot": "^1.1.0" } }, "sha512-eLoXW/DHyl62zxY4SCaIgnRhuMr6ri4juEYARS8E6sCEqzKpOiE521Ucofdx+KnDZl5xmvGYaaKCk5FEOxJCoQ=="],
@@ -1263,6 +1293,8 @@
"svelte": ["svelte@5.43.2", "", { "dependencies": { "@jridgewell/remapping": "^2.3.4", "@jridgewell/sourcemap-codec": "^1.5.0", "@sveltejs/acorn-typescript": "^1.0.5", "@types/estree": "^1.0.5", "acorn": "^8.12.1", "aria-query": "^5.3.1", "axobject-query": "^4.1.0", "clsx": "^2.1.1", "esm-env": "^1.2.1", "esrap": "^2.1.0", "is-reference": "^3.0.3", "locate-character": "^3.0.0", "magic-string": "^0.30.11", "zimmerframe": "^1.1.2" } }, "sha512-ro1umEzX8rT5JpCmlf0PPv7ncD8MdVob9e18bhwqTKNoLjS8kDvhVpaoYVPc+qMwDAOfcwJtyY7ZFSDbOaNPgA=="],
"svelte-adapter-bun": ["svelte-adapter-bun@1.0.1", "", { "dependencies": { "rolldown": "^1.0.0-beta.38" }, "peerDependencies": { "@sveltejs/kit": "^2.4.0", "typescript": "^5" } }, "sha512-tNOvfm8BGgG+rmEA7hkmqtq07v7zoo4skLQc+hIoQ79J+1fkEMpJEA2RzCIe3aPc8JdrsMJkv3mpiZPMsgahjA=="],
"svelte-chartjs": ["svelte-chartjs@3.1.5", "", { "peerDependencies": { "chart.js": "^3.5.0 || ^4.0.0", "svelte": "^4.0.0" } }, "sha512-ka2zh7v5FiwfAX1oMflZ0HkNkgjHjFqANgRyC+vNYXfxtx2ku68Zo+2KgbKeBH2nS1ThDqkIACPzGxy4T0UaoA=="],
"svelte-check": ["svelte-check@4.3.3", "", { "dependencies": { "@jridgewell/trace-mapping": "^0.3.25", "chokidar": "^4.0.1", "fdir": "^6.2.0", "picocolors": "^1.0.0", "sade": "^1.7.4" }, "peerDependencies": { "svelte": "^4.0.0 || ^5.0.0-next.0", "typescript": ">=5.0.0" }, "bin": { "svelte-check": "bin/svelte-check" } }, "sha512-RYP0bEwenDXzfv0P1sKAwjZSlaRyqBn0Fz1TVni58lqyEiqgwztTpmodJrGzP6ZT2aHl4MbTvWP6gbmQ3FOnBg=="],
@@ -1307,8 +1339,6 @@
"turbo-windows-arm64": ["turbo-windows-arm64@2.5.8", "", { "os": "win32", "cpu": "arm64" }, "sha512-eFC5XzLmgXJfnAK3UMTmVECCwuBcORrWdewoiXBnUm934DY6QN8YowC/srhNnROMpaKaqNeRpoB5FxCww3eteQ=="],
"tweetnacl": ["tweetnacl@0.14.5", "", {}, "sha512-KXXFFdAbFXY4geFIwoyNK+f5Z1b7swfXABfL7HXCmoIWMKU3dmS26672A4EeQtDzLKy7SXmfBu51JolvEKwtGA=="],
"type-check": ["type-check@0.4.0", "", { "dependencies": { "prelude-ls": "^1.2.1" } }, "sha512-XleUoc9uwGXqjWwXaUTZAmzMcFZ5858QA2vvx1Ur5xIcixXIP+8LnFDgRplU30us6teqdlskFfu+ae4K79Ooew=="],
"type-fest": ["type-fest@4.41.0", "", {}, "sha512-TeTSQ6H5YHvpqVwBRcnLDCBnDOHWYu7IvGbHT6N8AOymcr9PJGjc1GTtiWZTYg0NCgYwvnYWEkVChQAr9bjfwA=="],

View File

@@ -1,432 +1,432 @@
"use node";
// "use node";
import { action } from "../_generated/server";
import { v } from "convex/values";
import { api, internal } from "../_generated/api";
import { Client } from "ssh2";
import { readFileSync } from "fs";
import { decryptSMTPPasswordNode } from "./utils/nodeCrypto";
// import { action } from "../_generated/server";
// import { v } from "convex/values";
// import { api, internal } from "../_generated/api";
// import { Client } from "ssh2";
// import { readFileSync } from "fs";
// import { decryptSMTPPasswordNode } from "./utils/nodeCrypto";
/**
* Interface para configuração SSH
*/
interface SSHConfig {
host: string;
port: number;
username: string;
password?: string;
keyPath?: string;
}
// /**
// * Interface para configuração SSH
// */
// interface SSHConfig {
// host: string;
// port: number;
// username: string;
// password?: string;
// keyPath?: string;
// }
/**
* Executar comando via SSH
*/
async function executarComandoSSH(
config: SSHConfig,
comando: string
): Promise<{ sucesso: boolean; output: string; erro?: string }> {
return new Promise((resolve) => {
const conn = new Client();
let output = "";
let errorOutput = "";
// /**
// * Executar comando via SSH
// */
// async function executarComandoSSH(
// config: SSHConfig,
// comando: string
// ): Promise<{ sucesso: boolean; output: string; erro?: string }> {
// return new Promise((resolve) => {
// const conn = new Client();
// let output = "";
// let errorOutput = "";
conn.on("ready", () => {
conn.exec(comando, (err, stream) => {
if (err) {
conn.end();
resolve({ sucesso: false, output: "", erro: err.message });
return;
}
// conn.on("ready", () => {
// conn.exec(comando, (err, stream) => {
// if (err) {
// conn.end();
// resolve({ sucesso: false, output: "", erro: err.message });
// return;
// }
stream
.on("close", (code: number | null, signal: string | null) => {
conn.end();
if (code === 0) {
resolve({ sucesso: true, output: output.trim() });
} else {
resolve({
sucesso: false,
output: output.trim(),
erro: `Comando retornou código ${code}${signal ? ` (signal: ${signal})` : ""}. ${errorOutput}`,
});
}
})
.on("data", (data: Buffer) => {
output += data.toString();
})
.stderr.on("data", (data: Buffer) => {
errorOutput += data.toString();
});
});
}).on("error", (err) => {
resolve({ sucesso: false, output: "", erro: err.message });
}).connect({
host: config.host,
port: config.port,
username: config.username,
password: config.password,
privateKey: config.keyPath ? readFileSync(config.keyPath) : undefined,
readyTimeout: 10000,
});
});
}
// stream
// .on("close", (code: number | null, signal: string | null) => {
// conn.end();
// if (code === 0) {
// resolve({ sucesso: true, output: output.trim() });
// } else {
// resolve({
// sucesso: false,
// output: output.trim(),
// erro: `Comando retornou código ${code}${signal ? ` (signal: ${signal})` : ""}. ${errorOutput}`,
// });
// }
// })
// .on("data", (data: Buffer) => {
// output += data.toString();
// })
// .stderr.on("data", (data: Buffer) => {
// errorOutput += data.toString();
// });
// });
// }).on("error", (err) => {
// resolve({ sucesso: false, output: "", erro: err.message });
// }).connect({
// host: config.host,
// port: config.port,
// username: config.username,
// password: config.password,
// privateKey: config.keyPath ? readFileSync(config.keyPath) : undefined,
// readyTimeout: 10000,
// });
// });
// }
/**
* Ler arquivo via SSH
*/
async function lerArquivoSSH(
config: SSHConfig,
caminho: string
): Promise<{ sucesso: boolean; conteudo?: string; erro?: string }> {
const comando = `cat "${caminho}" 2>&1`;
const resultado = await executarComandoSSH(config, comando);
// /**
// * Ler arquivo via SSH
// */
// async function lerArquivoSSH(
// config: SSHConfig,
// caminho: string
// ): Promise<{ sucesso: boolean; conteudo?: string; erro?: string }> {
// const comando = `cat "${caminho}" 2>&1`;
// const resultado = await executarComandoSSH(config, comando);
if (!resultado.sucesso) {
return { sucesso: false, erro: resultado.erro || "Erro ao ler arquivo" };
}
// if (!resultado.sucesso) {
// return { sucesso: false, erro: resultado.erro || "Erro ao ler arquivo" };
// }
return { sucesso: true, conteudo: resultado.output };
}
// return { sucesso: true, conteudo: resultado.output };
// }
/**
* Escrever arquivo via SSH
*/
async function escreverArquivoSSH(
config: SSHConfig,
caminho: string,
conteudo: string
): Promise<{ sucesso: boolean; erro?: string }> {
// Escapar conteúdo para shell
const conteudoEscapado = conteudo
.replace(/\\/g, "\\\\")
.replace(/"/g, '\\"')
.replace(/\$/g, "\\$")
.replace(/`/g, "\\`");
// /**
// * Escrever arquivo via SSH
// */
// async function escreverArquivoSSH(
// config: SSHConfig,
// caminho: string,
// conteudo: string
// ): Promise<{ sucesso: boolean; erro?: string }> {
// // Escapar conteúdo para shell
// const conteudoEscapado = conteudo
// .replace(/\\/g, "\\\\")
// .replace(/"/g, '\\"')
// .replace(/\$/g, "\\$")
// .replace(/`/g, "\\`");
const comando = `cat > "${caminho}" << 'JITSI_CONFIG_EOF'
${conteudo}
JITSI_CONFIG_EOF`;
// const comando = `cat > "${caminho}" << 'JITSI_CONFIG_EOF'
// ${conteudo}
// JITSI_CONFIG_EOF`;
const resultado = await executarComandoSSH(config, comando);
// const resultado = await executarComandoSSH(config, comando);
if (!resultado.sucesso) {
return { sucesso: false, erro: resultado.erro || "Erro ao escrever arquivo" };
}
// if (!resultado.sucesso) {
// return { sucesso: false, erro: resultado.erro || "Erro ao escrever arquivo" };
// }
return { sucesso: true };
}
// return { sucesso: true };
// }
/**
* Aplicar configurações do Jitsi no servidor Docker via SSH
*/
export const aplicarConfiguracaoServidor = action({
args: {
configId: v.id("configuracaoJitsi"),
sshPassword: v.optional(v.string()), // Senha SSH (se não usar chave)
},
returns: v.union(
v.object({
sucesso: v.literal(true),
mensagem: v.string(),
detalhes: v.optional(v.string()),
}),
v.object({ sucesso: v.literal(false), erro: v.string() })
),
handler: async (ctx, args): Promise<
| { sucesso: true; mensagem: string; detalhes?: string }
| { sucesso: false; erro: string }
> => {
try {
// Buscar configuração
const config = await ctx.runQuery(api.configuracaoJitsi.obterConfigJitsi, {});
// /**
// * Aplicar configurações do Jitsi no servidor Docker via SSH
// */
// export const aplicarConfiguracaoServidor = action({
// args: {
// configId: v.id("configuracaoJitsi"),
// sshPassword: v.optional(v.string()), // Senha SSH (se não usar chave)
// },
// returns: v.union(
// v.object({
// sucesso: v.literal(true),
// mensagem: v.string(),
// detalhes: v.optional(v.string()),
// }),
// v.object({ sucesso: v.literal(false), erro: v.string() })
// ),
// handler: async (ctx, args): Promise<
// | { sucesso: true; mensagem: string; detalhes?: string }
// | { sucesso: false; erro: string }
// > => {
// try {
// // Buscar configuração
// const config = await ctx.runQuery(api.configuracaoJitsi.obterConfigJitsi, {});
if (!config || config._id !== args.configId) {
return { sucesso: false as const, erro: "Configuração não encontrada" };
}
// if (!config || config._id !== args.configId) {
// return { sucesso: false as const, erro: "Configuração não encontrada" };
// }
// Verificar se tem configurações SSH
const configFull = await ctx.runQuery(api.configuracaoJitsi.obterConfigJitsiCompleta, {
configId: args.configId,
});
// // Verificar se tem configurações SSH
// const configFull = await ctx.runQuery(api.configuracaoJitsi.obterConfigJitsiCompleta, {
// configId: args.configId,
// });
if (!configFull || !configFull.sshHost) {
return {
sucesso: false as const,
erro: "Configurações SSH não estão definidas. Configure o servidor SSH primeiro.",
};
}
// if (!configFull || !configFull.sshHost) {
// return {
// sucesso: false as const,
// erro: "Configurações SSH não estão definidas. Configure o servidor SSH primeiro.",
// };
// }
// Configurar SSH
let sshPasswordDecrypted: string | undefined = undefined;
// // Configurar SSH
// let sshPasswordDecrypted: string | undefined = undefined;
// Se senha foi fornecida, usar ela. Caso contrário, tentar descriptografar a armazenada
if (args.sshPassword) {
sshPasswordDecrypted = args.sshPassword;
} else if (configFull.sshPasswordHash && configFull.sshPasswordHash !== "********") {
// Tentar descriptografar senha armazenada
try {
sshPasswordDecrypted = await decryptSMTPPasswordNode(configFull.sshPasswordHash);
} catch (error) {
return {
sucesso: false as const,
erro: "Não foi possível descriptografar a senha SSH armazenada. Forneça a senha novamente.",
};
}
}
// // Se senha foi fornecida, usar ela. Caso contrário, tentar descriptografar a armazenada
// if (args.sshPassword) {
// sshPasswordDecrypted = args.sshPassword;
// } else if (configFull.sshPasswordHash && configFull.sshPasswordHash !== "********") {
// // Tentar descriptografar senha armazenada
// try {
// sshPasswordDecrypted = await decryptSMTPPasswordNode(configFull.sshPasswordHash);
// } catch (error) {
// return {
// sucesso: false as const,
// erro: "Não foi possível descriptografar a senha SSH armazenada. Forneça a senha novamente.",
// };
// }
// }
const sshConfig: SSHConfig = {
host: configFull.sshHost,
port: configFull.sshPort || 22,
username: configFull.sshUsername || "",
password: sshPasswordDecrypted,
keyPath: configFull.sshKeyPath || undefined,
};
// const sshConfig: SSHConfig = {
// host: configFull.sshHost,
// port: configFull.sshPort || 22,
// username: configFull.sshUsername || "",
// password: sshPasswordDecrypted,
// keyPath: configFull.sshKeyPath || undefined,
// };
if (!sshConfig.username) {
return { sucesso: false as const, erro: "Usuário SSH não configurado" };
}
// if (!sshConfig.username) {
// return { sucesso: false as const, erro: "Usuário SSH não configurado" };
// }
if (!sshConfig.password && !sshConfig.keyPath) {
return {
sucesso: false as const,
erro: "Senha SSH ou caminho da chave deve ser fornecido",
};
}
// if (!sshConfig.password && !sshConfig.keyPath) {
// return {
// sucesso: false as const,
// erro: "Senha SSH ou caminho da chave deve ser fornecido",
// };
// }
const basePath = configFull.jitsiConfigPath || "~/.jitsi-meet-cfg";
const dockerComposePath = configFull.dockerComposePath || ".";
// const basePath = configFull.jitsiConfigPath || "~/.jitsi-meet-cfg";
// const dockerComposePath = configFull.dockerComposePath || ".";
// Extrair host e porta do domain
const [host, portStr] = configFull.domain.split(":");
const port = portStr ? parseInt(portStr, 10) : configFull.useHttps ? 443 : 80;
const protocol = configFull.useHttps ? "https" : "http";
// // Extrair host e porta do domain
// const [host, portStr] = configFull.domain.split(":");
// const port = portStr ? parseInt(portStr, 10) : configFull.useHttps ? 443 : 80;
// const protocol = configFull.useHttps ? "https" : "http";
const detalhes: string[] = [];
// const detalhes: string[] = [];
// 1. Atualizar arquivo .env do docker-compose
if (dockerComposePath) {
const envContent = `# Configuração Jitsi - Atualizada automaticamente pelo SGSE
CONFIG=${basePath}
TZ=America/Recife
ENABLE_LETSENCRYPT=0
HTTP_PORT=${protocol === "https" ? 8000 : port}
HTTPS_PORT=${configFull.useHttps ? port : 8443}
PUBLIC_URL=${protocol}://${host}${portStr ? `:${port}` : ""}
DOMAIN=${host}
ENABLE_AUTH=0
ENABLE_GUESTS=1
ENABLE_TRANSCRIPTION=0
ENABLE_RECORDING=0
ENABLE_PREJOIN_PAGE=0
START_AUDIO_MUTED=0
START_VIDEO_MUTED=0
ENABLE_XMPP_WEBSOCKET=0
ENABLE_P2P=1
MAX_NUMBER_OF_PARTICIPANTS=10
RESOLUTION_WIDTH=1280
RESOLUTION_HEIGHT=720
JWT_APP_ID=${configFull.appId}
JWT_APP_SECRET=
`;
// // 1. Atualizar arquivo .env do docker-compose
// if (dockerComposePath) {
// const envContent = `# Configuração Jitsi - Atualizada automaticamente pelo SGSE
// CONFIG=${basePath}
// TZ=America/Recife
// ENABLE_LETSENCRYPT=0
// HTTP_PORT=${protocol === "https" ? 8000 : port}
// HTTPS_PORT=${configFull.useHttps ? port : 8443}
// PUBLIC_URL=${protocol}://${host}${portStr ? `:${port}` : ""}
// DOMAIN=${host}
// ENABLE_AUTH=0
// ENABLE_GUESTS=1
// ENABLE_TRANSCRIPTION=0
// ENABLE_RECORDING=0
// ENABLE_PREJOIN_PAGE=0
// START_AUDIO_MUTED=0
// START_VIDEO_MUTED=0
// ENABLE_XMPP_WEBSOCKET=0
// ENABLE_P2P=1
// MAX_NUMBER_OF_PARTICIPANTS=10
// RESOLUTION_WIDTH=1280
// RESOLUTION_HEIGHT=720
// JWT_APP_ID=${configFull.appId}
// JWT_APP_SECRET=
// `;
const envPath = `${dockerComposePath}/.env`;
const resultadoEnv = await escreverArquivoSSH(sshConfig, envPath, envContent);
// const envPath = `${dockerComposePath}/.env`;
// const resultadoEnv = await escreverArquivoSSH(sshConfig, envPath, envContent);
if (!resultadoEnv.sucesso) {
return {
sucesso: false as const,
erro: `Erro ao atualizar .env: ${resultadoEnv.erro}`,
};
}
// if (!resultadoEnv.sucesso) {
// return {
// sucesso: false as const,
// erro: `Erro ao atualizar .env: ${resultadoEnv.erro}`,
// };
// }
detalhes.push(`✓ Arquivo .env atualizado: ${envPath}`);
}
// detalhes.push(`✓ Arquivo .env atualizado: ${envPath}`);
// }
// 2. Atualizar configuração do Prosody (conforme documentação oficial)
const prosodyConfigPath = `${basePath}/prosody/config/${host}.cfg.lua`;
const prosodyContent = `-- Configuração Prosody para ${host}
-- Gerada automaticamente pelo SGSE
-- Baseado na documentação oficial do Jitsi Meet
// // 2. Atualizar configuração do Prosody (conforme documentação oficial)
// const prosodyConfigPath = `${basePath}/prosody/config/${host}.cfg.lua`;
// const prosodyContent = `-- Configuração Prosody para ${host}
// -- Gerada automaticamente pelo SGSE
// -- Baseado na documentação oficial do Jitsi Meet
VirtualHost "${host}"
authentication = "anonymous"
modules_enabled = {
"bosh";
"websocket";
"ping";
"speakerstats";
"turncredentials";
"presence";
"conference_duration";
"stats";
}
c2s_require_encryption = false
allow_anonymous_s2s = false
bosh_max_inactivity = 60
bosh_max_polling = 5
bosh_max_stanzas = 5
// VirtualHost "${host}"
// authentication = "anonymous"
// modules_enabled = {
// "bosh";
// "websocket";
// "ping";
// "speakerstats";
// "turncredentials";
// "presence";
// "conference_duration";
// "stats";
// }
// c2s_require_encryption = false
// allow_anonymous_s2s = false
// bosh_max_inactivity = 60
// bosh_max_polling = 5
// bosh_max_stanzas = 5
Component "conference.${host}" "muc"
storage = "memory"
muc_room_locking = false
muc_room_default_public_jids = true
muc_room_cache_size = 1000
muc_log_presences = true
// Component "conference.${host}" "muc"
// storage = "memory"
// muc_room_locking = false
// muc_room_default_public_jids = true
// muc_room_cache_size = 1000
// muc_log_presences = true
Component "jitsi-videobridge.${host}"
component_secret = ""
// Component "jitsi-videobridge.${host}"
// component_secret = ""
Component "focus.${host}"
component_secret = ""
`;
// Component "focus.${host}"
// component_secret = ""
// `;
const resultadoProsody = await escreverArquivoSSH(sshConfig, prosodyConfigPath, prosodyContent);
// const resultadoProsody = await escreverArquivoSSH(sshConfig, prosodyConfigPath, prosodyContent);
if (!resultadoProsody.sucesso) {
return {
sucesso: false as const,
erro: `Erro ao atualizar Prosody: ${resultadoProsody.erro}`,
};
}
// if (!resultadoProsody.sucesso) {
// return {
// sucesso: false as const,
// erro: `Erro ao atualizar Prosody: ${resultadoProsody.erro}`,
// };
// }
detalhes.push(`✓ Configuração Prosody atualizada: ${prosodyConfigPath}`);
// detalhes.push(`✓ Configuração Prosody atualizada: ${prosodyConfigPath}`);
// 3. Atualizar configuração do Jicofo
const jicofoConfigPath = `${basePath}/jicofo/sip-communicator.properties`;
const jicofoContent = `# Configuração Jicofo
# Gerada automaticamente pelo SGSE
org.jitsi.jicofo.BRIDGE_MUC=JvbBrewery@internal.${host}
org.jitsi.jicofo.jid=XMPP_USER@${host}
org.jitsi.jicofo.BRIDGE_MUC_JID=MUC_BRIDGE_JID@internal.${host}
org.jitsi.jicofo.app.ID=${configFull.appId}
`;
// // 3. Atualizar configuração do Jicofo
// const jicofoConfigPath = `${basePath}/jicofo/sip-communicator.properties`;
// const jicofoContent = `# Configuração Jicofo
// # Gerada automaticamente pelo SGSE
// org.jitsi.jicofo.BRIDGE_MUC=JvbBrewery@internal.${host}
// org.jitsi.jicofo.jid=XMPP_USER@${host}
// org.jitsi.jicofo.BRIDGE_MUC_JID=MUC_BRIDGE_JID@internal.${host}
// org.jitsi.jicofo.app.ID=${configFull.appId}
// `;
const resultadoJicofo = await escreverArquivoSSH(sshConfig, jicofoConfigPath, jicofoContent);
// const resultadoJicofo = await escreverArquivoSSH(sshConfig, jicofoConfigPath, jicofoContent);
if (!resultadoJicofo.sucesso) {
return {
sucesso: false as const,
erro: `Erro ao atualizar Jicofo: ${resultadoJicofo.erro}`,
};
}
// if (!resultadoJicofo.sucesso) {
// return {
// sucesso: false as const,
// erro: `Erro ao atualizar Jicofo: ${resultadoJicofo.erro}`,
// };
// }
detalhes.push(`✓ Configuração Jicofo atualizada: ${jicofoConfigPath}`);
// detalhes.push(`✓ Configuração Jicofo atualizada: ${jicofoConfigPath}`);
// 4. Atualizar configuração do JVB
const jvbConfigPath = `${basePath}/jvb/sip-communicator.properties`;
const jvbContent = `# Configuração JVB (Jitsi Video Bridge)
# Gerada automaticamente pelo SGSE
org.jitsi.videobridge.AUTHORIZED_SOURCE_REGEXP=.*@${host}/.*
org.jitsi.videobridge.xmpp.user.shard.HOSTNAME=${host}
org.jitsi.videobridge.xmpp.user.shard.DOMAIN=auth.${host}
org.jitsi.videobridge.xmpp.user.shard.USERNAME=jvb
org.jitsi.videobridge.xmpp.user.shard.MUC_JIDS=JvbBrewery@internal.${host}
`;
// // 4. Atualizar configuração do JVB
// const jvbConfigPath = `${basePath}/jvb/sip-communicator.properties`;
// const jvbContent = `# Configuração JVB (Jitsi Video Bridge)
// # Gerada automaticamente pelo SGSE
// org.jitsi.videobridge.AUTHORIZED_SOURCE_REGEXP=.*@${host}/.*
// org.jitsi.videobridge.xmpp.user.shard.HOSTNAME=${host}
// org.jitsi.videobridge.xmpp.user.shard.DOMAIN=auth.${host}
// org.jitsi.videobridge.xmpp.user.shard.USERNAME=jvb
// org.jitsi.videobridge.xmpp.user.shard.MUC_JIDS=JvbBrewery@internal.${host}
// `;
const resultadoJvb = await escreverArquivoSSH(sshConfig, jvbConfigPath, jvbContent);
// const resultadoJvb = await escreverArquivoSSH(sshConfig, jvbConfigPath, jvbContent);
if (!resultadoJvb.sucesso) {
return {
sucesso: false as const,
erro: `Erro ao atualizar JVB: ${resultadoJvb.erro}`,
};
}
// if (!resultadoJvb.sucesso) {
// return {
// sucesso: false as const,
// erro: `Erro ao atualizar JVB: ${resultadoJvb.erro}`,
// };
// }
detalhes.push(`✓ Configuração JVB atualizada: ${jvbConfigPath}`);
// detalhes.push(`✓ Configuração JVB atualizada: ${jvbConfigPath}`);
// 5. Reiniciar containers Docker
if (dockerComposePath) {
const resultadoRestart = await executarComandoSSH(
sshConfig,
`cd "${dockerComposePath}" && docker-compose restart 2>&1 || docker compose restart 2>&1`
);
// // 5. Reiniciar containers Docker
// if (dockerComposePath) {
// const resultadoRestart = await executarComandoSSH(
// sshConfig,
// `cd "${dockerComposePath}" && docker-compose restart 2>&1 || docker compose restart 2>&1`
// );
if (!resultadoRestart.sucesso) {
return {
sucesso: false as const,
erro: `Erro ao reiniciar containers: ${resultadoRestart.erro}`,
};
}
// if (!resultadoRestart.sucesso) {
// return {
// sucesso: false as const,
// erro: `Erro ao reiniciar containers: ${resultadoRestart.erro}`,
// };
// }
detalhes.push(`✓ Containers Docker reiniciados`);
}
// detalhes.push(`✓ Containers Docker reiniciados`);
// }
// Atualizar timestamp de configuração no servidor
await ctx.runMutation(internal.configuracaoJitsi.marcarConfiguradoNoServidor, {
configId: args.configId,
});
// // Atualizar timestamp de configuração no servidor
// await ctx.runMutation(internal.configuracaoJitsi.marcarConfiguradoNoServidor, {
// configId: args.configId,
// });
return {
sucesso: true as const,
mensagem: "Configurações aplicadas com sucesso no servidor Jitsi",
detalhes: detalhes.join("\n"),
};
} catch (error: unknown) {
const errorMessage = error instanceof Error ? error.message : String(error);
return {
sucesso: false as const,
erro: `Erro ao aplicar configurações: ${errorMessage}`,
};
}
},
});
// return {
// sucesso: true as const,
// mensagem: "Configurações aplicadas com sucesso no servidor Jitsi",
// detalhes: detalhes.join("\n"),
// };
// } catch (error: unknown) {
// const errorMessage = error instanceof Error ? error.message : String(error);
// return {
// sucesso: false as const,
// erro: `Erro ao aplicar configurações: ${errorMessage}`,
// };
// }
// },
// });
/**
* Testar conexão SSH
*/
export const testarConexaoSSH = action({
args: {
sshHost: v.string(),
sshPort: v.optional(v.number()),
sshUsername: v.string(),
sshPassword: v.optional(v.string()),
sshKeyPath: v.optional(v.string()),
},
returns: v.union(
v.object({ sucesso: v.literal(true), mensagem: v.string() }),
v.object({ sucesso: v.literal(false), erro: v.string() })
),
handler: async (ctx, args): Promise<
| { sucesso: true; mensagem: string }
| { sucesso: false; erro: string }
> => {
try {
if (!args.sshPassword && !args.sshKeyPath) {
return {
sucesso: false as const,
erro: "Senha SSH ou caminho da chave deve ser fornecido",
};
}
// /**
// * Testar conexão SSH
// */
// export const testarConexaoSSH = action({
// args: {
// sshHost: v.string(),
// sshPort: v.optional(v.number()),
// sshUsername: v.string(),
// sshPassword: v.optional(v.string()),
// sshKeyPath: v.optional(v.string()),
// },
// returns: v.union(
// v.object({ sucesso: v.literal(true), mensagem: v.string() }),
// v.object({ sucesso: v.literal(false), erro: v.string() })
// ),
// handler: async (ctx, args): Promise<
// | { sucesso: true; mensagem: string }
// | { sucesso: false; erro: string }
// > => {
// try {
// if (!args.sshPassword && !args.sshKeyPath) {
// return {
// sucesso: false as const,
// erro: "Senha SSH ou caminho da chave deve ser fornecido",
// };
// }
const sshConfig: SSHConfig = {
host: args.sshHost,
port: args.sshPort || 22,
username: args.sshUsername,
password: args.sshPassword || undefined,
keyPath: args.sshKeyPath || undefined,
};
// const sshConfig: SSHConfig = {
// host: args.sshHost,
// port: args.sshPort || 22,
// username: args.sshUsername,
// password: args.sshPassword || undefined,
// keyPath: args.sshKeyPath || undefined,
// };
// Tentar executar um comando simples
const resultado = await executarComandoSSH(sshConfig, "echo 'SSH_OK'");
// // Tentar executar um comando simples
// const resultado = await executarComandoSSH(sshConfig, "echo 'SSH_OK'");
if (resultado.sucesso && resultado.output.includes("SSH_OK")) {
return {
sucesso: true as const,
mensagem: "Conexão SSH estabelecida com sucesso",
};
}
// if (resultado.sucesso && resultado.output.includes("SSH_OK")) {
// return {
// sucesso: true as const,
// mensagem: "Conexão SSH estabelecida com sucesso",
// };
// }
return {
sucesso: false as const,
erro: resultado.erro || "Falha ao estabelecer conexão SSH",
};
} catch (error: unknown) {
const errorMessage = error instanceof Error ? error.message : String(error);
return {
sucesso: false as const,
erro: `Erro ao testar SSH: ${errorMessage}`,
};
}
},
});
// return {
// sucesso: false as const,
// erro: resultado.erro || "Falha ao estabelecer conexão SSH",
// };
// } catch (error: unknown) {
// const errorMessage = error instanceof Error ? error.message : String(error);
// return {
// sucesso: false as const,
// erro: `Erro ao testar SSH: ${errorMessage}`,
// };
// }
// },
// });

View File

@@ -28,7 +28,6 @@
"@types/ssh2": "^1.15.5",
"better-auth": "catalog:",
"convex": "catalog:",
"nodemailer": "^7.0.10",
"ssh2": "^1.17.0"
"nodemailer": "^7.0.10"
}
}