From 9f4538786bf068a9844d373fcddbe441a376553a Mon Sep 17 00:00:00 2001 From: VPS 4 Builder Date: Wed, 27 May 2026 02:36:06 +0200 Subject: [PATCH] fix: adiciona JWT_SECRET e ALLOWED_ORIGINS no docker-compose, e corrige enabled=1 para PostgreSQL nas rotas de imagens --- INSTRUCOES-SERVIDOR-VPS.txt | 277 +++++++++++++++++++++++++++++++++ TAREFAS.md | 6 +- dental-server/routes/images.js | 4 +- dental-server/server.js | 4 +- docker-compose.yml | 2 + 5 files changed, 286 insertions(+), 7 deletions(-) create mode 100644 INSTRUCOES-SERVIDOR-VPS.txt diff --git a/INSTRUCOES-SERVIDOR-VPS.txt b/INSTRUCOES-SERVIDOR-VPS.txt new file mode 100644 index 0000000..098df8b --- /dev/null +++ b/INSTRUCOES-SERVIDOR-VPS.txt @@ -0,0 +1,277 @@ +# 🦷 GUIA DE IMPLANTAÇÃO E ARQUITETURA - RX.SCOREODONTO.COM +=================================================================== +Última atualização: 26/05/2026 (00:47) + +## 📦 Versões Atuais + +| Componente | Versão | Localização | +|------------------------|----------|------------------------------------------| +| Server (dental-server) | v2.0.0 | VPS 1 (prod) / VPS 4 (staging) | +| Client Windows | v1.1.0 | C:\ProgramData\RF\dental-client\ | +| Node.js (runtime) | 22 LTS | Docker - node:22-alpine | +| PostgreSQL | 18 | VPS 3 (10.99.0.3:5432) | +| DragonflyDB (Redis) | latest | VPS 3 (10.99.0.3:6379) | + +As versões são exibidas visualmente: +- **Servidor Web**: Badge no rodapé da sidebar → "Server v2.0.0" +- **Client Windows**: Badge no cabeçalho da janela de configuração → "Client v1.1.0" + +--- + +## 🌍 Estrutura do Ecossistema e IPs + +| VPS | IP VPN | Papel | Acesso SSH | +|-----|-------------|------------------------------|---------------------| +| 1 | 10.99.0.1 | Produção (Traefik + App) | `ssh 10.99.0.1` | +| 3 | 10.99.0.3 | Banco de dados + Gitea | (sem acesso direto) | +| 4 | 10.99.0.4 | Dev / Staging / Build | `ssh 10.99.0.04` | + +### ⚠️ Diretórios CRÍTICOS — NÃO TOCAR + +**VPS 1 (Produção):** +- `/home/deploy/stack/traefik/` — Roteador de borda (proxy reverso) +- `/home/deploy/stack/portainer/` — Gerenciamento de containers +- `/home/deploy/stack/soc/` — CrowdSec / firewall de segurança + +**VPS 4 (Laboratório):** +- `/home/deploy/stack/webhook-listener/` — Pipeline CI/CD (porta 9000) +- `/home/deploy/stack/scoreodonto.com/` — Staging do ScoreOdonto +- `/home/deploy/stack/subdominio.clube67.com/` — Subdomínio manager +- `/home/deploy/stack/self-healing/` — Auto-recuperação de serviços + +--- + +## 📁 Organização de Pastas Locais (Seu Computador) + +`C:\ProgramData\RF\` + +| Pasta | Descrição | +|--------------------|--------------------------------------------------------------------| +| `dental-server/` | **Fonte principal do servidor** (Node.js/Express). Usar para edições. | +| `dental-client/` | **Fonte do client desktop** (Electron). Compilar daqui. | +| `deploy.ps1` | **Script de deploy** — sincroniza notebook → VPS 4 → VPS 1. | +| `docs-client/` | Documentação do client. | + +> ✅ `vps-server-src/` foi **apagada** em 26/05/2026 (versão legada MySQL/SQLite, substituída). + +--- + +## 🔄 Fluxo de Deploy — Script Automático (RECOMENDADO) + +> ✅ **Use sempre o `deploy.ps1`.** Não é necessário entrar na VPS manualmente. +> Fazer `docker compose restart` ou `docker compose down` na VPS 1 MANUALMENTE +> **DERRUBA sessões ativas de todos os clientes conectados.** NUNCA faça isso. + +### ⚡ Comando rápido (uso diário): + +Abra PowerShell em `C:\ProgramData\RF\` e execute: + +```powershell +# Deploy completo (tudo) +.\deploy.ps1 + +# Com mensagem de commit direta +.\deploy.ps1 -msg "feat: nova funcionalidade X" + +# Só o frontend (HTML/CSS/JS da pasta public/) +.\deploy.ps1 -soPublic -msg "fix: corrigir botao sair" + +# Só o backend (server.js, routes/, auth.js, etc) +.\deploy.ps1 -soServidor -msg "fix: corrigir query SQL" +``` + +### Como funciona internamente o pipeline: + +``` +[Edição local dental-server/ no notebook] + ↓ +[.\deploy.ps1 ← roda no notebook] + ↓ +[SCP: copia arquivos alterados para VPS 4] + ↓ +[git add -A + commit + push na VPS 4] + ↓ +[Gitea (VPS 3) recebe o push no branch main] + ↓ +[Webhook Global → http://10.99.0.4:9000/webhook] + ↓ +[webhook-listener (VPS 4) identifica o repositório] + ↓ +[Executa /home/deploy/stack/rx.scoreodonto.com/deploy-prod.sh] + ↓ +[Script SSH na VPS 1: git pull + docker compose build + up --no-deps] + ↓ +[Container atualizado SEM DOWNTIME — sessões preservadas] + ↓ +[Gitea marca o commit como ✅ success] +``` + +### Acompanhar deploy em tempo real: +```bash +ssh 10.99.0.04 "tail -f /home/deploy/stack/webhook-listener/deploy.log" +``` + +--- + +## 🔧 Comandos Úteis — Apenas Para Emergências + +> Use apenas quando o pipeline automático falhar ou para diagnóstico. + +### VPS 4 (Dev/Staging): +```bash +ssh 10.99.0.04 +cd /home/deploy/stack/rx.scoreodonto.com + +# Ver logs do container +docker logs -f rx_scoreodonto_app + +# Rebuild e restart (staging apenas) +docker compose up -d --build + +# Status do webhook listener +systemctl status webhook-listener + +# Log do CI/CD +tail -50 /home/deploy/stack/webhook-listener/deploy.log +``` + +### VPS 1 (Produção) — CUIDADO: +```bash +ssh 10.99.0.1 +# SEMPRE prefixar com DOCKER_HOST em todos os comandos docker: +export DOCKER_HOST=unix:///run/user/1000/docker.sock + +# Ver logs (ok fazer) +docker logs -f rx_scoreodonto_app + +# ⚠️ Rebuild manual (apenas se o pipeline automático falhar) +# Isso causa breve downtime — avise antes +cd /home/deploy/stack/rx.scoreodonto.com +git pull origin main +docker compose build app +docker compose up -d --no-deps app +``` + +--- + +## ⚙️ Variáveis de Ambiente do Servidor + +Configuradas no `docker-compose.yml` (sem `.env` em produção): + +```env +PORT=3000 +NODE_ENV=production +DB_TYPE=postgres +DB_HOST=10.99.0.3 +DB_PORT=5432 +DB_USER=clube67 +DB_PASSWORD=clube67_db_pass_9903 +DB_NAME=dental_images +REDIS_HOST=10.99.0.3 +REDIS_PORT=6379 +REDIS_PASSWORD=clube67_db_pass_9903 +UPLOAD_DIR=/app/uploads +PROCESSED_DIR=/app/processed +``` + +> ⚠️ **PENDENTE**: Adicionar `JWT_SECRET` fixo no docker-compose.yml para que tokens +> sobrevivam a restarts. Atualmente usa o valor padrão hardcoded em `auth.js`. + +--- + +## 💻 Compilação do Client Windows (Electron) + +### Pré-requisitos: +- Node.js 18+ instalado na máquina local +- Executar `npm install` na primeira vez + +### Compilar instalador `.exe`: +```powershell +cd C:\ProgramData\RF\dental-client + +# Instalar dependências (só na primeira vez ou após mudança no package.json) +npm install + +# Gerar instalador Windows (.exe via NSIS) +npm run dist +``` + +O instalador será gerado em: +``` +C:\ProgramData\RF\dental-client\dist\ScoreOdonto Dental Client Setup 1.1.0.exe +``` + +### Testar sem compilar (modo dev): +```powershell +cd C:\ProgramData\RF\dental-client +npm start +``` + +### Configurações do build (`package.json`): +- **appId**: `com.scoreodonto.dentalclient` +- **productName**: `ScoreOdonto Dental Client` +- **Installer**: NSIS one-click, instalação para todos os usuários +- **Atalhos**: Área de trabalho + Menu Iniciar +- **Ícone**: `favicon.png` + +--- + +## 🔑 Autenticação e Tokens + +### Fluxo do Administrador (Painel Web): +1. Login em `/login` com usuário/senha do servidor +2. JWT gerado e salvo no `localStorage` (chave `token`) +3. Todas as chamadas API enviam `Authorization: Bearer ` +4. Rota `/admin-clinics.html` → requer `is_admin = true` no JWT + +### Fluxo do Client Desktop (ScoreOdonto Dental Client): +1. Admin acessa o painel web em **Gerenciar Clínicas** (`/admin-clinics.html`) +2. Clica em **+ Nova Clínica** e preenche: Nome da Clínica, Email, Senha, Nome do PC +3. Sistema gera um `machine_token` (UUID) exibido na tela após salvar — **copie imediatamente** +4. No Client Windows: abra Configurações → cole o token no campo **🔑 Token do Servidor** +5. O client usa esse token para autenticar via Socket.IO no servidor + +--- + +## 🏗️ Arquitetura do Webhook CI/CD (Técnica) + +``` +Gitea (VPS 3:3000) + └── System Webhook Global (hook ID: 10) + URL: http://10.99.0.4:9000/webhook + Token: gitea_webhook_secret_clube67_58e4a92c3d18 + Evento: push (branch *) + +webhook-listener (VPS 4, systemd service) + └── /home/deploy/stack/webhook-listener/listener.js + Porta: 9000 (bind: 10.99.0.4) + Roteamento por repositório: + rx.scoreodonto.com → deploy-prod.sh + clube67 → deploy-prod-builder.sh + +deploy-prod.sh (VPS 4, executado por repositório) + └── /home/deploy/stack/rx.scoreodonto.com/deploy-prod.sh + SSH → VPS 1: + git pull origin main + docker compose build app + docker compose up -d --no-deps app ← sem derrubar sessões +``` + +--- + +## ✨ Histórico de Atualizações + +| Data | Versão | O que mudou | +|------------|--------|---------------------------------------------------------------------------------| +| 26/05/2026 | v2.0.0 | **Script `deploy.ps1`**: deploy em um comando, notebook → VPS 4 → VPS 1 automático | +| 26/05/2026 | v2.0.0 | **Apagada `vps-server-src/`** (legado MySQL/SQLite, não usada) | +| 26/05/2026 | v2.0.0 | Fix `admin-clinics.js`: redirect para login se sem token + verificar Content-Type antes de parsear JSON | +| 26/05/2026 | v2.0.0 | **Pipeline CI/CD corrigido**: token configurado no Webhook Global, listener atualizado com roteamento por repositório, deploy-prod.sh sem downtime | +| 26/05/2026 | v2.0.0 | Correção auth admin-clinics (req.session → req.user JWT), fix erro JSON 500 | +| 26/05/2026 | v2.0.0 | Restauração da UI (modal RX, sidebar dropdown), badge de versão em todos os painéis | +| 25/05/2026 | v2.0.0 | Botão Sair no sidebar, link Gerenciar Clínicas no menu lateral | +| 25/05/2026 | v2.0.0 | Correção PostgreSQL GROUP BY no endpoint `/api/images/patients` | +| 25/05/2026 | v2.0.0 | Migração SQLite/MySQL → PostgreSQL 18 + DragonflyDB na VPS 3 | +| 25/05/2026 | v1.1.0 | Client: painel de configuração com token de máquina e autenticação JWT | + +=================================================================== diff --git a/TAREFAS.md b/TAREFAS.md index 5e3c9ff..0fd7898 100644 --- a/TAREFAS.md +++ b/TAREFAS.md @@ -5,13 +5,13 @@ ## 🔴 CRÍTICO — Produção em risco -- [ ] **[SERVER] JWT_SECRET fixo no docker-compose.yml** +- [x] **[SERVER] JWT_SECRET fixo no docker-compose.yml** - Atualmente usa fallback hardcoded `'sua-chave-secreta-alterar-em-producao'` no `auth.js` - O valor correto **existe no `.env` da VPS4** mas **não está no `docker-compose.yml`** da VPS1 - Impacto: toda vez que o container reinicia, todos os tokens JWT de usuários web expiram - Fix: adicionar `- JWT_SECRET=` na seção `environment:` do `docker-compose.yml` -- [ ] **[SERVER] `enabled = 1` — sintaxe MySQL em queries PostgreSQL** +- [x] **[SERVER] `enabled = 1` — sintaxe MySQL em queries PostgreSQL** - Arquivos afetados: - `server.js` linhas 750 e 874 - `routes/images.js` linhas 142 e 308 @@ -19,7 +19,7 @@ - Pode retornar resultados errados ou quebrar queries silenciosamente - Fix: substituir `enabled = 1` por `enabled = true` em todas as ocorrências -- [ ] **[SERVER] CORS aberto com `*` em produção** +- [x] **[SERVER] CORS aberto com `*` em produção** - `ALLOWED_ORIGINS` não está definido no `docker-compose.yml` - Resultado: Socket.IO aceita conexões de qualquer origem - Fix: adicionar `- ALLOWED_ORIGINS=https://rx.scoreodonto.com` no `docker-compose.yml` diff --git a/dental-server/routes/images.js b/dental-server/routes/images.js index 27ba5d1..c77a3f9 100644 --- a/dental-server/routes/images.js +++ b/dental-server/routes/images.js @@ -139,7 +139,7 @@ router.get('/:id', async (req, res) => { router.get('/stats', async (req, res) => { try { const total = await db.get('SELECT COUNT(*) as count FROM images'); - const enabled = await db.get('SELECT COUNT(*) as count FROM images WHERE enabled = 1'); + const enabled = await db.get('SELECT COUNT(*) as count FROM images WHERE enabled = true'); const withTooth = await db.get('SELECT COUNT(*) as count FROM images WHERE tooth_number IS NOT NULL'); res.json({ @@ -305,7 +305,7 @@ router.put('/:id/patient-info', async (req, res) => { patient_name_resumed = $1, tooth_number = $2, tooth_side = $3, - enabled = 1 + enabled = true WHERE id = $4`, [patientNameResumed, toothNumber, toothSide, req.params.id] ); diff --git a/dental-server/server.js b/dental-server/server.js index ef1d14b..c670471 100644 --- a/dental-server/server.js +++ b/dental-server/server.js @@ -747,7 +747,7 @@ io.on('connection', (socket) => { // Verificar se a imagem já foi inserida no banco const existing = await db.get( - "SELECT id, filename FROM images WHERE original_filename = $1 AND enabled = 1 LIMIT 1", + "SELECT id, filename FROM images WHERE original_filename = $1 AND enabled = true LIMIT 1", [data.metadata.fileName] ); if (existing) { @@ -871,7 +871,7 @@ io.on('connection', (socket) => { } const existing = await db.get( - "SELECT id, filename FROM images WHERE original_filename = $1 AND enabled = 1 LIMIT 1", + "SELECT id, filename FROM images WHERE original_filename = $1 AND enabled = true LIMIT 1", [data.fileName] ); diff --git a/docker-compose.yml b/docker-compose.yml index d4d6e07..68988c1 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -23,6 +23,8 @@ services: - REDIS_PASSWORD=clube67_db_pass_9903 - UPLOAD_DIR=/app/uploads - PROCESSED_DIR=/app/processed + - JWT_SECRET=9f8e7d6c5b4a39281802384f5e6d7c8b9a0b1c2d3e4f5a6b7c8d9e0f1a2b3c4d5 + - ALLOWED_ORIGINS=https://rx.scoreodonto.com volumes: - ./uploads:/app/uploads - ./processed:/app/processed