fix: adiciona JWT_SECRET e ALLOWED_ORIGINS no docker-compose, e corrige enabled=1 para PostgreSQL nas rotas de imagens
continuous-integration/webhook Deploy concluido (rx.scoreodonto.com)
continuous-integration/webhook Deploy concluido (rx.scoreodonto.com)
This commit is contained in:
@@ -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 <token>`
|
||||||
|
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 |
|
||||||
|
|
||||||
|
===================================================================
|
||||||
+3
-3
@@ -5,13 +5,13 @@
|
|||||||
|
|
||||||
## 🔴 CRÍTICO — Produção em risco
|
## 🔴 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`
|
- 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
|
- 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
|
- Impacto: toda vez que o container reinicia, todos os tokens JWT de usuários web expiram
|
||||||
- Fix: adicionar `- JWT_SECRET=<valor>` na seção `environment:` do `docker-compose.yml`
|
- Fix: adicionar `- JWT_SECRET=<valor>` 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:
|
- Arquivos afetados:
|
||||||
- `server.js` linhas 750 e 874
|
- `server.js` linhas 750 e 874
|
||||||
- `routes/images.js` linhas 142 e 308
|
- `routes/images.js` linhas 142 e 308
|
||||||
@@ -19,7 +19,7 @@
|
|||||||
- Pode retornar resultados errados ou quebrar queries silenciosamente
|
- Pode retornar resultados errados ou quebrar queries silenciosamente
|
||||||
- Fix: substituir `enabled = 1` por `enabled = true` em todas as ocorrências
|
- 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`
|
- `ALLOWED_ORIGINS` não está definido no `docker-compose.yml`
|
||||||
- Resultado: Socket.IO aceita conexões de qualquer origem
|
- Resultado: Socket.IO aceita conexões de qualquer origem
|
||||||
- Fix: adicionar `- ALLOWED_ORIGINS=https://rx.scoreodonto.com` no `docker-compose.yml`
|
- Fix: adicionar `- ALLOWED_ORIGINS=https://rx.scoreodonto.com` no `docker-compose.yml`
|
||||||
|
|||||||
@@ -139,7 +139,7 @@ router.get('/:id', async (req, res) => {
|
|||||||
router.get('/stats', async (req, res) => {
|
router.get('/stats', async (req, res) => {
|
||||||
try {
|
try {
|
||||||
const total = await db.get('SELECT COUNT(*) as count FROM images');
|
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');
|
const withTooth = await db.get('SELECT COUNT(*) as count FROM images WHERE tooth_number IS NOT NULL');
|
||||||
|
|
||||||
res.json({
|
res.json({
|
||||||
@@ -305,7 +305,7 @@ router.put('/:id/patient-info', async (req, res) => {
|
|||||||
patient_name_resumed = $1,
|
patient_name_resumed = $1,
|
||||||
tooth_number = $2,
|
tooth_number = $2,
|
||||||
tooth_side = $3,
|
tooth_side = $3,
|
||||||
enabled = 1
|
enabled = true
|
||||||
WHERE id = $4`,
|
WHERE id = $4`,
|
||||||
[patientNameResumed, toothNumber, toothSide, req.params.id]
|
[patientNameResumed, toothNumber, toothSide, req.params.id]
|
||||||
);
|
);
|
||||||
|
|||||||
@@ -747,7 +747,7 @@ io.on('connection', (socket) => {
|
|||||||
|
|
||||||
// Verificar se a imagem já foi inserida no banco
|
// Verificar se a imagem já foi inserida no banco
|
||||||
const existing = await db.get(
|
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]
|
[data.metadata.fileName]
|
||||||
);
|
);
|
||||||
if (existing) {
|
if (existing) {
|
||||||
@@ -871,7 +871,7 @@ io.on('connection', (socket) => {
|
|||||||
}
|
}
|
||||||
|
|
||||||
const existing = await db.get(
|
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]
|
[data.fileName]
|
||||||
);
|
);
|
||||||
|
|
||||||
|
|||||||
@@ -23,6 +23,8 @@ services:
|
|||||||
- REDIS_PASSWORD=clube67_db_pass_9903
|
- REDIS_PASSWORD=clube67_db_pass_9903
|
||||||
- UPLOAD_DIR=/app/uploads
|
- UPLOAD_DIR=/app/uploads
|
||||||
- PROCESSED_DIR=/app/processed
|
- PROCESSED_DIR=/app/processed
|
||||||
|
- JWT_SECRET=9f8e7d6c5b4a39281802384f5e6d7c8b9a0b1c2d3e4f5a6b7c8d9e0f1a2b3c4d5
|
||||||
|
- ALLOWED_ORIGINS=https://rx.scoreodonto.com
|
||||||
volumes:
|
volumes:
|
||||||
- ./uploads:/app/uploads
|
- ./uploads:/app/uploads
|
||||||
- ./processed:/app/processed
|
- ./processed:/app/processed
|
||||||
|
|||||||
Reference in New Issue
Block a user