Table of Contents
- 🚀 Deploy V.1.0.2 — GitOps + Modelo A otimizado para Wasabi
- 📋 Índice
- 1. Arquitetura Multi-VPS
- 2. Pipeline GitOps (Modelo A)
- 3. Por que RX usa Modelo A (não B)
- 4. O Listener do Webhook
- 5. Versionamento — Servidor
- 6. Versionamento — Cliente Windows
- 7. Erros comuns e soluções
- ❌ Erro 1: previewStyle is not defined
- ❌ Erro 2: Deploy leva 3-7 minutos
- ❌ Erro 3: Docker cache sempre invalida
- ❌ Erro 4: Imagens dos cards ficam em branco
- ❌ Erro 5: Loop infinito de CI
- 8. Troubleshooting
- 9. Changelog
🚀 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
- Arquitetura Multi-VPS
- Pipeline GitOps
- Por que RX usa Modelo A (não B)
- O Listener do Webhook
- Versionamento do Servidor
- Versionamento do Cliente Windows
- Erros comuns e soluções
- Troubleshooting
- 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-tokenou?token=...) - Trava de concorrência:
isBuildingflag (não permite deploys paralelos) - Anti-loop: ignora commits
chore(deploy): bump version(senão loop infinito) - Dispara
deploy-prod.shem background - Reporta status ao Gitea (pending → success/failure)
Configuração:
# /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
# 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:
// ❌ 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
# 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
// ❌ 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
# ❌ 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
# ❌ 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
// 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
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. |