docs: adiciona Deploy-V.1.0.4 — nomes de sistema e cliente congelados
+319
@@ -0,0 +1,319 @@
|
||||
# 🚀 Deploy V.1.0.4 — Nomes de Sistema e Cliente Congelados
|
||||
|
||||
> Versão **1.0.4** — inclui tudo da 1.0.3 e formaliza a **regra de nomes congelados**:
|
||||
> o nome do sistema (`Score Client`) e o nome do executável do cliente Windows
|
||||
> (`ScoreOdonto Dental Client Setup X.Y.Z.exe`) são **fixos e imutáveis** —
|
||||
> apenas o número de versão pode mudar.
|
||||
|
||||
---
|
||||
|
||||
## 📋 Índice
|
||||
|
||||
1. [Arquitetura Multi-VPS](#1-arquitetura-multi-vps)
|
||||
2. [Pipeline GitOps (Servidor — Modelo A)](#2-pipeline-gitops-modelo-a)
|
||||
3. [O Listener do Webhook](#3-o-listener-do-webhook)
|
||||
4. [Versionamento — Servidor](#4-versionamento--servidor)
|
||||
5. [Cliente Windows — Build, Publicação e Auto-Update](#5-cliente-windows--build-publicacao-e-auto-update)
|
||||
6. [🔒 Nomes Congelados — Regra Obrigatória](#6-nomes-congelados--regra-obrigatória)
|
||||
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) + serve `/updates/` do cliente |
|
||||
| **VPS 3 — Data/Git** | `10.99.0.3` | Gitea + PostgreSQL |
|
||||
| **VPS 4 — Builder** | `10.99.0.4` | Build do servidor **e** do cliente Windows (cross-compile) |
|
||||
| **Wasabi** | S3-compatível | Storage de imagens (não no disco) |
|
||||
|
||||
---
|
||||
|
||||
## 2. Pipeline GitOps (Modelo A)
|
||||
|
||||
```
|
||||
git push origin main (rx.scoreodonto.com)
|
||||
↓
|
||||
Gitea dispara webhook → VPS4:9003/webhook
|
||||
↓
|
||||
listener-rx.js → deploy-prod.sh
|
||||
↓
|
||||
VPS4: build React (Vite) → docker compose build app → docker save → app.tar.gz
|
||||
↓
|
||||
VPS4: rsync app.tar.gz + docker-compose.yml + version.txt → VPS1
|
||||
↓
|
||||
VPS1: docker load + docker compose up -d --no-deps app
|
||||
↓
|
||||
✅ Deploy concluído (~30-40 s)
|
||||
```
|
||||
|
||||
> ⚠️ O build do React roda com `NODE_ENV=development npm install --include=dev` porque
|
||||
> o `vite` é devDependency e o listener roda com `NODE_ENV=production` (que pula devDeps).
|
||||
|
||||
---
|
||||
|
||||
## 3. O Listener do Webhook
|
||||
|
||||
**Arquivo:** `/home/deploy/stack/webhook-listener/listener-rx.js` (porta 9003)
|
||||
|
||||
- Valida token (`x-gitea-token` ou `?token=...`)
|
||||
- **Trava de concorrência** (`isBuilding`) + **anti-loop** (ignora `chore(deploy): bump version`)
|
||||
- Dispara `deploy-prod.sh` e reporta status ao Gitea (pending → success/failure)
|
||||
|
||||
> 📌 **O cliente Windows NÃO tem webhook.** O `deploy-client.sh` é **manual** (ver seção 5).
|
||||
> Essa é a causa nº 1 de defasagem de versão do cliente.
|
||||
|
||||
---
|
||||
|
||||
## 4. Versionamento — Servidor
|
||||
|
||||
**Fonte de verdade:** `dental-server/version.txt` (lido em runtime, injetado no sidebar).
|
||||
`package.json` **nunca** é tocado no bump (preserva cache do Docker). Detalhes na 1.0.2.
|
||||
|
||||
```bash
|
||||
# Versão servida (dentro do container é a fonte de verdade):
|
||||
ssh deploy@10.99.0.1 "docker exec rx_scoreodonto_app cat /app/version.txt"
|
||||
curl -s https://rx.scoreodonto.com/ | grep -o 'v[0-9]\.[0-9]\.[0-9]'
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 5. Cliente Windows — Build, Publicação e Auto-Update
|
||||
|
||||
Projeto: **`rx.scoreodonto.com-client`** (Electron + electron-updater).
|
||||
|
||||
### 5.1. Fonte de verdade
|
||||
|
||||
Tudo parte do `package.json` do cliente:
|
||||
|
||||
```bash
|
||||
grep '"version"' /home/deploy/stack/rx.scoreodonto.com-client/package.json
|
||||
# "version": "1.2.7"
|
||||
```
|
||||
|
||||
O `electron-builder` lê esse número e gera **3 artefatos** por versão:
|
||||
`ScoreOdonto Dental Client Setup X.Y.Z.exe`, `...exe.blockmap` e `latest.yml`.
|
||||
|
||||
### 5.2. As DUAS pastas (e por que confundem)
|
||||
|
||||
| Pasta | Onde | O que é |
|
||||
|---|---|---|
|
||||
| `rx.scoreodonto.com-client/dist/` | **VPS4** | **Saída do build.** Tem todos os `.exe` já gerados. |
|
||||
| `rx.scoreodonto.com/dental-server/updates/` | **VPS1** | **O que é servido** em `https://rx.scoreodonto.com/updates/`. É o que as clínicas enxergam. |
|
||||
|
||||
> 🔑 **Buildar (gerar no `dist/`) ≠ publicar (copiar para `updates/` na VPS1).**
|
||||
> O cliente só "existe" para as clínicas quando o `latest.yml` da **VPS1** aponta para a nova versão.
|
||||
|
||||
### 5.3. Ciclo completo — `deploy-client.sh`
|
||||
|
||||
```bash
|
||||
cd /home/deploy/stack/rx.scoreodonto.com-client
|
||||
bash deploy-client.sh
|
||||
```
|
||||
|
||||
O script faz, em ordem:
|
||||
```
|
||||
1. npm install
|
||||
2. npx electron-builder --win --x64 --publish never → gera dist/*.exe + dist/latest.yml (VPS4)
|
||||
3. valida que dist/ tem .exe e latest.yml
|
||||
4. rsync dist/*.exe dist/*.yml dist/*.blockmap → VPS1:/.../dental-server/updates/ ← PUBLICAÇÃO
|
||||
```
|
||||
|
||||
**Só termina o trabalho quando o passo 4 roda.** Se você buildar manualmente
|
||||
(`npm run dist`) e parar aí, o `dist/` na VPS4 fica novo, mas a VPS1 continua na versão antiga.
|
||||
|
||||
### 5.4. Fluxo de auto-update nas clínicas
|
||||
|
||||
```
|
||||
App do cliente inicia
|
||||
↓
|
||||
electron-updater: GET https://rx.scoreodonto.com/updates/latest.yml
|
||||
↓
|
||||
versão do yml > versão instalada? → baixa o .exe e instala ao reiniciar
|
||||
→ senão, nada acontece
|
||||
```
|
||||
|
||||
### 5.5. Verificação rápida (3 pontos têm que bater)
|
||||
|
||||
```bash
|
||||
# 1. Fonte (código):
|
||||
grep '"version"' /home/deploy/stack/rx.scoreodonto.com-client/package.json
|
||||
|
||||
# 2. Publicado na VPS1 (o que conta):
|
||||
ssh deploy@10.99.0.1 "head -1 /home/deploy/stack/rx.scoreodonto.com/dental-server/updates/latest.yml"
|
||||
|
||||
# 3. O que o auto-updater realmente baixa (HTTP público):
|
||||
curl -s https://rx.scoreodonto.com/updates/latest.yml | head -1
|
||||
```
|
||||
Se os três não baterem → rode `deploy-client.sh` até o fim.
|
||||
|
||||
---
|
||||
|
||||
## 6. 🔒 Nomes Congelados — Regra Obrigatória
|
||||
|
||||
> ⛔ **Esta seção define regras imutáveis do projeto. Agentes de IA e desenvolvedores
|
||||
> NÃO devem alterar os nomes abaixo sem aprovação explícita do responsável pelo projeto.**
|
||||
|
||||
### 6.1. Nome do sistema (painel web)
|
||||
|
||||
| Local | Valor fixo |
|
||||
|---|---|
|
||||
| `dental-client/index.html` → `<title>` | `Score Client — Gerenciador de Imagens` |
|
||||
| `src/components/Sidebar.jsx` → cabeçalho | `Score Client` |
|
||||
| `src/pages/LoginPage.jsx` → `<h1>` | `Score Client` |
|
||||
| `src/pages/DownloadPage.jsx` → `<h2>` | `Score Client — Cliente Windows` |
|
||||
|
||||
**O nome `Score Client` é fixo.** Nunca substituir por variações como `RF Dental`, `RFDental`,
|
||||
`ScoreOdonto`, `RX Dental` ou qualquer outro. Se houver refatoração de UI, manter exatamente
|
||||
`Score Client`.
|
||||
|
||||
### 6.2. Nome do executável do cliente Windows
|
||||
|
||||
O `electron-builder` gera o instalador com o nome definido em `package.json` → campo `productName`:
|
||||
|
||||
```json
|
||||
{
|
||||
"productName": "ScoreOdonto Dental Client"
|
||||
}
|
||||
```
|
||||
|
||||
Isso resulta em arquivos com o padrão:
|
||||
|
||||
```
|
||||
ScoreOdonto Dental Client Setup X.Y.Z.exe
|
||||
ScoreOdonto Dental Client Setup X.Y.Z.exe.blockmap
|
||||
latest.yml → path: ScoreOdonto Dental Client Setup X.Y.Z.exe
|
||||
```
|
||||
|
||||
**Apenas `X.Y.Z` (o número de versão) pode mudar.** O prefixo `ScoreOdonto Dental Client Setup `
|
||||
é imutável. Alterar o `productName` no `package.json` quebra:
|
||||
- O `latest.yml` gerado (nome do `path` muda → auto-updater não encontra o arquivo)
|
||||
- A URL de download no painel web (que lê `path:` do `latest.yml` dinamicamente)
|
||||
- O histórico de instalações nas clínicas (Windows trata como produto diferente)
|
||||
|
||||
### 6.3. URL de download — como funciona (e por que não usar nome hardcoded)
|
||||
|
||||
O `DownloadPage.jsx` lê o nome real do arquivo a partir do `latest.yml` **em tempo de execução**:
|
||||
|
||||
```javascript
|
||||
fetch('/updates/latest.yml')
|
||||
.then(r => r.text())
|
||||
.then(t => {
|
||||
const vMatch = t.match(/version:\s*([\d.]+)/);
|
||||
if (vMatch) setVersion(vMatch[1]); // ex: "1.2.6"
|
||||
const pMatch = t.match(/^path:\s*(.+)$/m);
|
||||
if (pMatch) setExeFilename(pMatch[1].trim()); // ex: "ScoreOdonto Dental Client Setup 1.2.6.exe"
|
||||
});
|
||||
|
||||
// URL final construída:
|
||||
const downloadUrl = `/updates/${encodeURIComponent(exeFilename)}`;
|
||||
```
|
||||
|
||||
> ✅ Isso garante que o botão de download sempre aponta para o arquivo correto da versão em produção,
|
||||
> sem hardcode de nome — mas **depende de que o `productName` no `package.json` seja mantido exato**.
|
||||
|
||||
### 6.4. Checklist antes de qualquer refatoração de UI ou cliente
|
||||
|
||||
- [ ] O título da aba do browser ainda é `Score Client — Gerenciador de Imagens`?
|
||||
- [ ] O cabeçalho da sidebar ainda exibe `Score Client`?
|
||||
- [ ] O `<h1>` da tela de login ainda é `Score Client`?
|
||||
- [ ] O `productName` no `package.json` do cliente ainda é `ScoreOdonto Dental Client`?
|
||||
- [ ] O `latest.yml` em produção tem `path: ScoreOdonto Dental Client Setup X.Y.Z.exe`?
|
||||
|
||||
Se qualquer item acima for `NÃO` → reverter antes de continuar.
|
||||
|
||||
---
|
||||
|
||||
## 7. Erros comuns e soluções
|
||||
|
||||
### ❌ Erro A — Cliente em uma versão, servidor servindo outra (mais antiga)
|
||||
**Sintoma:** `package.json` = `1.2.7`, mas `updates/latest.yml` na VPS1 = `1.2.6`.
|
||||
As clínicas não recebem o update e novas instalações baixam a versão velha.
|
||||
|
||||
**Causa:** a versão foi buildada no `dist/` da VPS4 mas **o `rsync → VPS1` (passo 4 do
|
||||
`deploy-client.sh`) não rodou**. Buildar não publica.
|
||||
|
||||
**Diagnóstico:**
|
||||
```bash
|
||||
# Build existe no dist/ (VPS4)?
|
||||
ls -1 /home/deploy/stack/rx.scoreodonto.com-client/dist/*.exe | sort -V | tail -1
|
||||
cat /home/deploy/stack/rx.scoreodonto.com-client/dist/latest.yml | head -1 # ex: 1.2.7
|
||||
|
||||
# Mas a VPS1 está atrás?
|
||||
ssh deploy@10.99.0.1 "head -1 /home/deploy/.../dental-server/updates/latest.yml" # ex: 1.2.6
|
||||
```
|
||||
Versões diferentes = build não publicado.
|
||||
|
||||
**Solução (rápida, sem rebuild — artefato já existe e é válido):**
|
||||
```bash
|
||||
cd /home/deploy/stack/rx.scoreodonto.com-client
|
||||
rsync -avz dist/*.exe dist/*.yml dist/*.blockmap \
|
||||
deploy@10.99.0.1:/home/deploy/stack/rx.scoreodonto.com/dental-server/updates/
|
||||
```
|
||||
**Solução (canônica):** `bash deploy-client.sh` (rebuilda e publica).
|
||||
|
||||
---
|
||||
|
||||
### ❌ Erro B — Botão de download diz "arquivo não disponível"
|
||||
**Sintoma:** ao clicar em "Baixar Instalador" na página `/download`, o browser retorna 404.
|
||||
|
||||
**Causa mais comum:** o `path:` no `latest.yml` não bate com o nome real do arquivo em `updates/`.
|
||||
Isso acontece se o `productName` foi alterado no `package.json` do cliente, gerando um `.exe`
|
||||
com nome diferente do que o `latest.yml` antigo listava.
|
||||
|
||||
**Diagnóstico:**
|
||||
```bash
|
||||
# Nome que o painel web vai pedir:
|
||||
ssh deploy@10.99.0.1 "grep '^path:' /home/deploy/stack/rx.scoreodonto.com/dental-server/updates/latest.yml"
|
||||
# ex: path: ScoreOdonto Dental Client Setup 1.2.6.exe
|
||||
|
||||
# Arquivos que existem de fato:
|
||||
ssh deploy@10.99.0.1 "ls /home/deploy/stack/rx.scoreodonto.com/dental-server/updates/*.exe"
|
||||
```
|
||||
Se os nomes não baterem → o `productName` foi alterado ou o `rsync` não foi executado.
|
||||
|
||||
**Solução:** restaurar o `productName` para `ScoreOdonto Dental Client` e rodar `deploy-client.sh`.
|
||||
|
||||
---
|
||||
|
||||
### ❌ Erro C — `vite: not found` no deploy do servidor
|
||||
**Causa:** listener roda com `NODE_ENV=production` → `npm install` pula devDependencies (vite).
|
||||
**Solução:** no `deploy-prod.sh`, `NODE_ENV=development npm install --include=dev` antes do `vite build`.
|
||||
|
||||
### ❌ Erro D — Imagens do RX não aparecem (placeholder cinza, sem erro no DevTools)
|
||||
**Causa:** endpoint de thumbnail retornava JSON `{url}` enquanto o `<img src>` apontava direto para ele.
|
||||
**Solução:** o endpoint serve **bytes** (`image/jpeg`); gera a thumb on-the-fly e faz backfill no Wasabi.
|
||||
|
||||
### ❌ Erro E — Wasabi 403 após trocar credenciais
|
||||
**Causa:** salvar credenciais no banco **não recarregava** o cliente S3 em memória.
|
||||
**Solução:** `storage-config` chama `storage.loadConfigFromDb()` após salvar.
|
||||
|
||||
---
|
||||
|
||||
## 8. Troubleshooting
|
||||
|
||||
| Problema | Diagnóstico | Solução |
|
||||
|---|---|---|
|
||||
| **Cliente desatualizado nas clínicas** | `package.json` ≠ `updates/latest.yml` na VPS1? | Rodar `deploy-client.sh` **até o passo 4** (ou rsync do `dist/`) |
|
||||
| **Botão download → 404** | `path:` no `latest.yml` ≠ arquivo real em `updates/`? | Verificar `productName`; restaurar para `ScoreOdonto Dental Client` e rebuildar |
|
||||
| Build do cliente falha | Wine/electron-builder na VPS4 | Ver log; `npx electron-builder --win --x64 --publish never` |
|
||||
| Clínica não atualiza mesmo com yml novo | versão instalada ≥ yml? | Auto-update só sobe versão; conferir semver |
|
||||
| Deploy do servidor `vite: not found` | `NODE_ENV=production` no install | `--include=dev` no `deploy-prod.sh` |
|
||||
| Imagens em branco | thumb retornando JSON? | Endpoint deve servir **bytes** image/jpeg |
|
||||
| Wasabi 403 | credenciais recarregadas? | `loadConfigFromDb()` após salvar config |
|
||||
| Deploy do servidor lento (3+ min) | Modelo B? | Modelo A (build na VPS4) |
|
||||
|
||||
---
|
||||
|
||||
## 9. Changelog
|
||||
|
||||
| Versão | Data | O que mudou |
|
||||
|---|---|---|
|
||||
| **1.0.4** | 2026-05-31 | **Nomes congelados:** formaliza que `Score Client` (sistema) e `ScoreOdonto Dental Client Setup` (executável) são imutáveis. Documenta o Erro B (404 no download por nome errado), checklist de refatoração e como o `DownloadPage.jsx` resolve o nome dinamicamente via `latest.yml`. |
|
||||
| 1.0.3 | 2026-05-31 | Ciclo completo do Cliente Windows (build `dist/` na VPS4 ≠ publicação `updates/` na VPS1). Documenta a armadilha "buildado mas não publicado", verificação em 3 pontos, e o `vite: not found`. |
|
||||
| 1.0.2 | 2026-05-30 | RX: switch para Modelo A (build VPS4), erros comuns, Wasabi. Deploy ~30-40s. |
|
||||
| 1.0.1 | 2026-05-30 | Consolidação inicial: deploy + versionamento + segurança. |
|
||||
| 1.0.0 | 2026-05-30 | Versão inicial. |
|
||||
+4
-3
@@ -4,12 +4,13 @@ Documentação da automação de deploy GitOps (Gitea + Multi-VPS) e versionamen
|
||||
|
||||
## Versão atual
|
||||
|
||||
- **[Deploy V.1.0.3](Deploy-V.1.0.3)** ← versão mais recente — ciclo completo do **Cliente Windows**
|
||||
(build na VPS4 vs publicação na VPS1), armadilha "buildado mas não publicado", verificação em 3 pontos,
|
||||
além de tudo do servidor (Modelo A, Wasabi, versionamento).
|
||||
- **[Deploy V.1.0.4](Deploy-V.1.0.4)** ← versão mais recente — **nomes congelados**: `Score Client`
|
||||
(sistema) e `ScoreOdonto Dental Client Setup` (executável) são imutáveis; apenas o número de versão
|
||||
pode mudar. Inclui checklist de refatoração, Erro B (404 no download) e tudo da 1.0.3.
|
||||
|
||||
## Arquivo de versões anteriores
|
||||
|
||||
- [Deploy V.1.0.3](Deploy-V.1.0.3) — ciclo completo do Cliente Windows (build na VPS4 vs publicação na VPS1).
|
||||
- [Deploy V.1.0.2](Deploy-V.1.0.2) — Modelo A otimizado para Wasabi (deploy ~30-40s).
|
||||
- [Deploy V.1.0.1](Deploy-V.1.0.1) — consolidação: deploy + versionamento + segurança.
|
||||
- [Deploy V.1.0.0](Deploy-V.1.0.0) — versão inicial (apenas deploy, sem versionamento).
|
||||
|
||||
Reference in New Issue
Block a user