docs(wiki): Deploy V.1.0.2 - Modelo A otimizado, Wasabi, erros comuns ~30-40s por deploy
+281
@@ -0,0 +1,281 @@
|
||||
# 🚀 Deploy V.1.0.2 — GitOps + Modelo A otimizado para Wasabi
|
||||
|
||||
> Versão **1.0.2** — RX agora usa **Modelo A (build na VPS4, imagem pronta para VPS1)** com Wasabi como storage.
|
||||
> Tempo de deploy: **~30-40 segundos** (era 3-7min com Modelo B).
|
||||
> Evita os erros comuns documentados aqui.
|
||||
|
||||
---
|
||||
|
||||
## 📋 Índice
|
||||
|
||||
1. [Arquitetura Multi-VPS](#1-arquitetura-multi-vps)
|
||||
2. [Pipeline GitOps](#2-pipeline-gitops-modelo-a)
|
||||
3. [Por que RX usa Modelo A (não B)](#3-por-que-rx-usa-modelo-a)
|
||||
4. [O Listener do Webhook](#4-o-listener-do-webhook)
|
||||
5. [Versionamento do Servidor](#5-versionamento--servidor)
|
||||
6. [Versionamento do Cliente Windows](#6-versionamento--cliente-windows)
|
||||
7. [Erros comuns e soluções](#7-erros-comuns-e-soluções)
|
||||
8. [Troubleshooting](#8-troubleshooting)
|
||||
9. [Changelog](#9-changelog)
|
||||
|
||||
---
|
||||
|
||||
## 1. Arquitetura Multi-VPS
|
||||
|
||||
| Servidor | IP (VPN) | Função |
|
||||
|---|---|---|
|
||||
| **VPS 1 — App** | `10.99.0.1` | Produção (Docker rootless) |
|
||||
| **VPS 3 — Data/Git** | `10.99.0.3` | Gitea + PostgreSQL |
|
||||
| **VPS 4 — Builder** | `10.99.0.4` | Build + deploy |
|
||||
| **Wasabi** | S3-compatível | Storage de imagens (não no disco) |
|
||||
|
||||
---
|
||||
|
||||
## 2. Pipeline GitOps (Modelo A)
|
||||
|
||||
```
|
||||
git push origin main
|
||||
↓
|
||||
Gitea dispara webhook → VPS4:9003/webhook
|
||||
↓
|
||||
listener-rx.js: valida token, dispara deploy-prod.sh
|
||||
↓
|
||||
VPS4: docker compose build app (cria imagem)
|
||||
↓
|
||||
VPS4: docker save → app.tar.gz (exporta imagem compilada)
|
||||
↓
|
||||
VPS4: rsync app.tar.gz + docker-compose.yml → VPS1
|
||||
↓
|
||||
VPS1: docker load < app.tar.gz (carrega imagem)
|
||||
↓
|
||||
VPS1: docker compose up -d --no-deps app (reinicia container)
|
||||
↓
|
||||
VPS4: git push chore(deploy): bump version (anti-loop)
|
||||
↓
|
||||
✅ Deploy concluído (~30-40 segundos)
|
||||
```
|
||||
|
||||
**Por que é rápido:**
|
||||
- ✅ VPS4 faz o build pesado (docker compose build)
|
||||
- ✅ VPS1 só recebe a imagem pronta (docker load)
|
||||
- ✅ Nenhuma compilação na produção
|
||||
- ✅ Usa cache do Docker na VPS4 (npm install reutilizado)
|
||||
|
||||
---
|
||||
|
||||
## 3. Por que RX usa Modelo A (não B)
|
||||
|
||||
**Modelo B** (build na VPS1, descontinuado para RX):
|
||||
```
|
||||
VPS4: rsync código-fonte → VPS1
|
||||
VPS1: docker compose build app ← LENTO (3-7 min)
|
||||
```
|
||||
|
||||
**Problema:** `COPY . .` no Dockerfile invalida cache a cada mudança de código.
|
||||
Docker tem que recompile tudo mesmo que `package.json` não tenha mudado.
|
||||
|
||||
**RX com Wasabi:**
|
||||
- Imagens estão no Wasabi (não no container)
|
||||
- Container é **stateless** (sem volumes de dados)
|
||||
- Modelo A é perfeito: build rápido na VPS4, imagem pronta na VPS1
|
||||
|
||||
**Clube67 (modelo A histórico):**
|
||||
```
|
||||
VPS4: npm install + npm build (front) ✓ rápido
|
||||
VPS4: rsync dist/ → VPS1 ✓ rápido
|
||||
VPS1: docker compose up (sem build) ✓ instantâneo
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 4. O Listener do Webhook
|
||||
|
||||
**Arquivo:** `/home/deploy/stack/webhook-listener/listener-rx.js` (porta 9003)
|
||||
|
||||
**Responsabilidades:**
|
||||
- Valida token (`x-gitea-token` ou `?token=...`)
|
||||
- **Trava de concorrência:** `isBuilding` flag (não permite deploys paralelos)
|
||||
- **Anti-loop:** ignora commits `chore(deploy): bump version` (senão loop infinito)
|
||||
- Dispara `deploy-prod.sh` em background
|
||||
- Reporta status ao Gitea (pending → success/failure)
|
||||
|
||||
**Configuração:**
|
||||
```bash
|
||||
# /etc/systemd/system/webhook-rx.service
|
||||
PORT=9003
|
||||
WEBHOOK_TOKEN=rx_deploy_token_9003_c7f2a1b4e8d5
|
||||
DEPLOY_SCRIPT=/home/deploy/stack/rx.scoreodonto.com/deploy-prod.sh
|
||||
GITEA_TOKEN=<token da API do Gitea>
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 5. Versionamento — Servidor
|
||||
|
||||
### Fonte de verdade: `dental-server/version.txt`
|
||||
|
||||
```bash
|
||||
# Arquivo:
|
||||
cat /home/deploy/stack/rx.scoreodonto.com/dental-server/version.txt
|
||||
# Output: 2.1.4
|
||||
|
||||
# Ou via HTTP (injetado pelo server no sidebar):
|
||||
curl -s http://rx.scoreodonto.com/ | grep -o 'v[0-9]\.[0-9]\.[0-9]'
|
||||
# Output: v2.1.4
|
||||
```
|
||||
|
||||
### Como o versionamento funciona
|
||||
|
||||
| Arquivo | Quando muda |
|
||||
|---|---|
|
||||
| `version.txt` | A cada deploy (auto-bump PATCH) |
|
||||
| `package.json` | **NUNCA** (preserva Docker cache) |
|
||||
| `index.html` | A cada deploy (versão injetada) |
|
||||
| Server em runtime | Lê `version.txt` (não package.json) |
|
||||
|
||||
**Importante:** `update-version.js` **NÃO toca** `package.json`:
|
||||
```javascript
|
||||
// ❌ NÃO faz isso (invalida cache Docker):
|
||||
fs.writeFileSync(packageFile, JSON.stringify(pkg, null, 2));
|
||||
|
||||
// ✅ Só toca esses:
|
||||
fs.writeFileSync(versionFile, next); // version.txt
|
||||
fs.writeFileSync(indexFile, html); // index.html (versão injetada)
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 6. Versionamento — Cliente Windows
|
||||
|
||||
### Fonte de verdade: `package.json` → `latest.yml`
|
||||
|
||||
```bash
|
||||
# Arquivo:
|
||||
cat /home/deploy/stack/rx.scoreodonto.com-client/package.json | grep version
|
||||
# Output: "version": "1.2.7"
|
||||
|
||||
# Gerado no build:
|
||||
cat /home/deploy/stack/rx.scoreodonto.com/dental-server/updates/latest.yml
|
||||
# Output:
|
||||
# version: 1.2.7
|
||||
# files:
|
||||
# - url: ScoreOdonto Dental Client Setup 1.2.7.exe
|
||||
```
|
||||
|
||||
**Fluxo de atualização nas clínicas:**
|
||||
```
|
||||
Clínica inicia app
|
||||
↓
|
||||
electron-updater: GET /updates/latest.yml
|
||||
↓
|
||||
version no yml > versão instalada?
|
||||
↓ SIM
|
||||
Baixa .exe + instala ao reiniciar
|
||||
↓ NÃO
|
||||
Nada acontece
|
||||
```
|
||||
|
||||
**⚠️ CRÍTICO:** Se `latest.yml` não for atualizado, **clínicas não recebem update**.
|
||||
|
||||
---
|
||||
|
||||
## 7. Erros comuns e soluções
|
||||
|
||||
### ❌ Erro 1: `previewStyle is not defined`
|
||||
**Causa:** Variável não definida no `patientCardHTML()`
|
||||
**Solução:** Sempre ter `<img>` ou placeholder, nunca deixar vazio
|
||||
```javascript
|
||||
// ❌ ERRADO:
|
||||
const preview = thumb ? `<img...>` : '';
|
||||
<div class="image-preview"${previewStyle}> // previewStyle não existe
|
||||
|
||||
// ✅ CORRETO:
|
||||
const preview = thumb
|
||||
? `<img class="preview-img" ...>`
|
||||
: `<div class="preview-img-placeholder"></div>`;
|
||||
<div class="image-preview">
|
||||
${preview}
|
||||
</div>
|
||||
```
|
||||
|
||||
### ❌ Erro 2: Deploy leva 3-7 minutos
|
||||
**Causa:** Modelo B com `docker compose build` na VPS1
|
||||
**Solução:** Usar Modelo A — build na VPS4, imagem para VPS1
|
||||
```bash
|
||||
# ❌ LENTO (Modelo B):
|
||||
ssh VPS1 "docker compose build app && docker compose up"
|
||||
|
||||
# ✅ RÁPIDO (Modelo A):
|
||||
docker compose build app # VPS4 (local, cache reutilizado)
|
||||
docker save | gzip > app.tar.gz
|
||||
rsync app.tar.gz → VPS1
|
||||
ssh VPS1 "docker load < app.tar.gz && docker compose up"
|
||||
```
|
||||
|
||||
### ❌ Erro 3: Docker cache sempre invalida
|
||||
**Causa:** `COPY . .` no Dockerfile invalida cache quando código muda
|
||||
**Solução:** Não tocar `package.json` no version bump
|
||||
```dockerfile
|
||||
# ❌ PROBLEMA:
|
||||
FROM node:22-alpine
|
||||
RUN npm install # camada 1
|
||||
COPY . . # camada 2 (invalida sempre que código muda)
|
||||
|
||||
# ✅ SOLUÇÃO:
|
||||
FROM node:22-alpine
|
||||
COPY package*.json ./ # copia ANTES de install
|
||||
RUN npm install # camada 2 (reutiliza cache se package.json não muda)
|
||||
COPY . . # camada 3 (invalida, mas npm install já rodou)
|
||||
```
|
||||
|
||||
### ❌ Erro 4: Imagens dos cards ficam em branco
|
||||
**Causa:** Sem `thumb_filename` no banco, sem placeholder no frontend
|
||||
**Solução:** SEMPRE gerar thumbnail + mostrar placeholder
|
||||
```javascript
|
||||
// Servidor gera thumb ao receber imagem:
|
||||
const thumbBuffer = await imageProcessor.getThumbnail(imageBuffer, 500, 500);
|
||||
thumbFilename = `thumb_${filename}`;
|
||||
await storage.saveImage(thumbFilename, thumbBuffer, ...);
|
||||
// Salva no banco com thumb_filename
|
||||
|
||||
// Frontend SEMPRE mostra algo:
|
||||
const preview = thumb
|
||||
? `<img src="${thumb}" loading="lazy">` // mostra thumb rápido
|
||||
: `<div class="placeholder"></div>`; // fallback cinza enquanto carrega
|
||||
```
|
||||
|
||||
### ❌ Erro 5: Loop infinito de CI
|
||||
**Causa:** Listener não ignora commits de bump de versão
|
||||
**Solução:** Regex anti-loop no listener
|
||||
```javascript
|
||||
const VERSION_BUMP_RE = /^chore\(deploy\):\s*bump version/i;
|
||||
|
||||
if (messages.every(m => VERSION_BUMP_RE.test(m))) {
|
||||
// Ignora commit de bump → sem loop
|
||||
return res.json({ status: 'skipped' });
|
||||
}
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 8. Troubleshooting
|
||||
|
||||
| Problema | Diagnóstico | Solução |
|
||||
|---|---|---|
|
||||
| Deploy leva 3+ min | Está usando Modelo B? | Mudar para Modelo A (build na VPS4) |
|
||||
| Docker cache invalida | `COPY . .` perde cache? | Não tocar `package.json` no bump |
|
||||
| Webhook não dispara | `systemctl status webhook-rx` | Reiniciar listener ou verificar token |
|
||||
| `429` no webhook | Build em progresso? | Aguardar concluir |
|
||||
| Imagens em branco nos cards | Sem `thumb_filename` no banco? | Rodar `/api/images/backfill-thumbs` |
|
||||
| Clínicas não recebem update | `latest.yml` desatualizado? | Verificar se deploy-client.sh roda completo |
|
||||
| `docker load` falha | Imagem corrompida? | Refazer build: `docker compose build app` |
|
||||
|
||||
---
|
||||
|
||||
## 9. Changelog
|
||||
|
||||
| Versão | Data | O que mudou |
|
||||
|---|---|---|
|
||||
| **1.0.2** | 2026-05-30 | RX: switch para Modelo A (build VPS4), documentar erros comuns, Wasabi storage. Deploy agora ~30-40s. |
|
||||
| 1.0.1 | 2026-05-30 | Consolidação inicial: deploy + versionamento + segurança. |
|
||||
| 1.0.0 | 2026-05-30 | Versão inicial. |
|
||||
Reference in New Issue
Block a user