deploy: atualiza modelo (registry VPN, poda) + delta de deploy prod do motor

- deploy/modelo.md: reescrito. Agora vale para scoreodonto E newwhats
  (padronizados). Documenta o registry PELA VPN (10.99.0.3:3000 — o proxy HTTPS
  corta uploads >~45MB em ~60s), o promote via API v2 (buildx não fala HTTP) e a
  poda de imagens no deploy-prod.sh. Versão anterior em desuso/ (17/jun).
- projetos/newwhats.clube67.com/deploy-prod.md: novo delta do motor — host único,
  build com contexto na raiz (plugins na imagem), banco newwhats_prod + Dragonfly /2
  na VPS3, rede soc, JWT do dev, sem migração Prisma no boot, auth do promote pelo
  docker login do runner, e o sync-config-dev-para-prod.sh como passo separado.

O HMR/dev já estava coberto (infra/ambiente-dev.md + delta do scoreodonto); o dev
do motor (next start + rsync, NÃO-HMR) já está em ambiente-dev.md do projeto.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
VPS 4 Builder
2026-07-11 04:00:08 +02:00
parent 6b5ebd48ca
commit 9b29f70bb0
3 changed files with 109 additions and 5 deletions
+19 -5
View File
@@ -1,6 +1,7 @@
# Deploy — Modelo (Build Once → Promote → Deploy)
> Situação: **como o deploy funciona, em conceito.** Estado real verificado em 17/jun/2026.
> Situação: **como o deploy funciona, em conceito.** Estado real verificado em 11/jul/2026.
> Vale para **scoreodonto.com** E **newwhats.clube67.com** (padronizados no mesmo modelo).
**Push** na `main` builda **uma vez** e publica a imagem por **commit**; a **tag `vX.Y.Z`** **re-tagueia** essa mesma imagem (sem rebuild) e deploya. **Produção nunca builda — só executa imagens prontas.**
@@ -9,12 +10,25 @@
│ │
▼ ▼
CI: build (1x) CI: promote (SEM build)
publica :C + :latest re-tag :C → :vX.Y.Z (MESMO digest) + deploy
publica :C + :latest copia manifest :C → :vX.Y.Z (MESMO digest) + deploy
▼ ▼
registry (Gitea) produção: pull :vX.Y.Z + up --no-build
registry (Gitea, pela VPN) produção (VPS1): pull :vX.Y.Z + up --no-build + poda
```
Garantia central: **a imagem em produção é byte-idêntica à que foi buildada e testada**. A tag é só um rótulo de release sobre um artefato imutável.
Garantia central: **a imagem em produção é byte-idêntica à que foi buildada e testada**. A tag é só um rótulo de release sobre um artefato imutável. O promote confere que o **digest** de `:vX.Y.Z` é igual ao de `:C` antes de deployar.
- **Por que importa:** acaba a recompilação na promoção (que podia gerar imagem diferente da testada) e a colisão de tags. Implementação de referência: scoreodonto.com (`v1.0.18`).
## Registry pela VPN (10.99.0.3:3000) — importante
As imagens são publicadas e puxadas pelo endereço **`10.99.0.3:3000`** (WireGuard), **não** por `git.clube67.com`. Motivo: o proxy HTTPS público **corta uploads em ~60 s** (banda ~0,75 MB/s), então blobs acima de **~45 MB** não sobem (o Gitea recebe o corpo truncado e responde 500). Pela VPN não há corte (~2,4 MB/s). O blob do backend do motor tem 175 MB — só passa pela VPN.
- Exige `insecure-registries: ["10.99.0.3:3000"]` no daemon Docker do **runner (VPS4)** e da **VPS1**. A porta 3000 **não** é exposta na internet, só pelo túnel — `insecure` aqui só desliga a exigência de TLS naquele endereço; o tráfego segue cifrado pelo WireGuard.
- O **promote** copia o manifest pela **API v2** do registry (`GET`+`PUT`, byte a byte ⇒ mesmo digest). Não usa `docker buildx imagetools`, que **não fala com registry HTTP**.
- Correção definitiva (pendente, não bloqueia): aumentar os timeouts do proxy da VPS3 (`client_body_timeout`/`proxy_send_timeout`/`proxy_read_timeout` ≥ 900s, `client_max_body_size 0`) resolveria também clones/downloads externos.
## Poda de imagens no deploy
O `deploy-prod.sh`, ao fim de cada deploy, **remove as imagens `vX.Y.Z` locais além das 2 mais recentes** de cada repo (backend/frontend) — a recém-subida e a anterior (rollback). Só toca em tags `vX.Y.Z`, nunca imagem em uso, e é best-effort (não aborta o deploy). Evita o acúmulo que chegou a dezenas de GB. (O build cache tem limpeza própria: `docker builder prune` semanal via cron na VPS4 e VPS1.)
- **Por que o modelo importa:** acaba a recompilação na promoção (que podia gerar imagem diferente da testada) e a colisão de tags.
- Implementações de referência: **scoreodonto.com** (`v1.0.40`) e **newwhats.clube67.com** (`v1.0.2`). O delta do motor está em `../projetos/newwhats.clube67.com/deploy-prod.md`.
- Situações relacionadas: `subir-release.md`, `rollback.md`, `versionamento.md`; infra em `../infra/`.
+23
View File
@@ -0,0 +1,23 @@
> **EM DESUSO** (arquivado em 2026-07-11). Versão histórica — não é instrução vigente.
> Substituído pela versão atual em `deploy/modelo.md` (registry pela VPN, newwhats padronizado, poda no deploy).
# Deploy — Modelo (Build Once → Promote → Deploy)
> Situação: **como o deploy funciona, em conceito.** Estado real verificado em 17/jun/2026.
**Push** na `main` builda **uma vez** e publica a imagem por **commit**; a **tag `vX.Y.Z`** **re-tagueia** essa mesma imagem (sem rebuild) e deploya. **Produção nunca builda — só executa imagens prontas.**
```
PUSH main (commit C) TAG vX.Y.Z (no commit C)
│ │
▼ ▼
CI: build (1x) CI: promote (SEM build)
publica :C + :latest re-tag :C → :vX.Y.Z (MESMO digest) + deploy
▼ ▼
registry (Gitea) produção: pull :vX.Y.Z + up --no-build
```
Garantia central: **a imagem em produção é byte-idêntica à que foi buildada e testada**. A tag é só um rótulo de release sobre um artefato imutável.
- **Por que importa:** acaba a recompilação na promoção (que podia gerar imagem diferente da testada) e a colisão de tags. Implementação de referência: scoreodonto.com (`v1.0.18`).
- Situações relacionadas: `subir-release.md`, `rollback.md`, `versionamento.md`; infra em `../infra/`.
@@ -0,0 +1,67 @@
# newwhats.clube67.com (motor) — Deploy de produção (delta)
> Delta deste projeto. Modelo universal em `../../deploy/modelo.md` (Build Once → Promote → Deploy),
> operação em `../../deploy/subir-release.md` e `../../deploy/rollback.md`. Aqui só o que **difere**.
> Estado real verificado em 11/jul/2026. Produção: **newwhats.clube67.com** (VPS1), atual **v1.0.2**.
## Contexto (por que existe este delta)
Até jul/2026 o motor **não tinha deploy funcional**: o antigo `deploy-prod-builder.sh` fazia `rsync` do
`dist` + `git reset --hard` num diretório sem git próprio, estava quebrado (entrypoint `dist/backend/src/index.js`
vs build `dist/server.js`) e, se rodasse, trocaria o que `clube67.com` serve. Foi **substituído** pelo modelo
universal (imagens versionadas, CI por tag). O container `newwhats-backend-prod` legado rodava, na verdade,
o **Clube de Benefícios** — hoje separado: motor em `newwhats.clube67.com`, Clube em `clube67.com` (stack
`/home/deploy/stack/clube67`, MySQL próprio).
## O que difere do universal
- **Host único:** o stack do motor serve **apenas `newwhats.clube67.com`** (roteador Traefik `priority=1000`
para vencer o roteador legado). `clube67.com`/`www` são **outro projeto**, não tocado por este deploy.
- **Workflow separado:** `.gitea/workflows/newwhats-build.yml` (dispara em `newwhats.clube67.com/**` e tags `v*`).
Nomes das imagens: `10.99.0.3:3000/ruicesar/newwhats-{backend,frontend}`.
- **Contexto do build:** o Dockerfile do backend builda com contexto na **raiz do motor** (não em `backend/`),
porque os **plugins entram na imagem** (em produção o loader lê `/app/plugins`, sem bind-mount). Um
`.dockerignore` na raiz derruba o contexto de 1,6 GB → 4 MB.
- **Auth do promote:** o passo de promote deriva o header `Authorization: Basic` do **`docker login` do runner**
(`~/.docker/config.json`) — o repo **não** tem o secret `REGISTRY_TOKEN` (o `docker login` mascarava isso;
o `curl` do promote precisa da credencial explícita).
- **`NEXT_PUBLIC_API_URL`** (frontend, inlined no build) é a **RAIZ do host** (`https://newwhats.clube67.com`),
não `.../api` — o código monta `${API}/api/...` e `${API}/media/...`.
- **Sem migração Prisma no boot:** o schema não é criado por `prisma migrate deploy`. As tabelas do core
nascem do `db/init.sql` (gerado por `prisma migrate diff --from-empty`) e as dos plugins (`sec_*`,
`plugin_config_store`) nascem no boot do backend. A config de plugin é **cifrada e durável no Postgres**
(chave derivada do `JWT_SECRET`).
## Banco e cache na VPS3
- **Banco:** `newwhats_prod` no Postgres nativo da **VPS3** (`10.99.0.3:5432`), role `newwhats`. **Cache:**
Dragonfly da VPS3, DB **`/2`** (o dev usa `/1`). Credenciais no `.env` do stack (`/home/deploy/stack/newwhats.clube67.com/.env`,
chmod 600): `JWT_SECRET`, `NEWWHATS_DB_PASS`, `DRAGONFLY_PASS`. **Não há mais Postgres/Redis local na VPS1.**
- **Rede:** o backend fica na rede Docker externa **`soc`** (além da `motor`) — é por ela que o container
alcança o WireGuard do host e, por ele, a VPS3. A rede `motor`, isolada, não roteia para `10.99.0.3`.
- **`JWT_SECRET` = o do dev:** adotado para o `plugin_config_store` (cifrado) decifrar direto; a Secretária
sobe com **`auto_reply=false`**.
## Passos (idênticos ao universal, com os nomes do motor)
```bash
# 1) VPS4 — validar em dev.newwhats.clube67.com (rebuild manual; NÃO é HMR — ver ambiente-dev.md)
# 2) push na main → CI builda :<commit> e publica no registry VPN (NÃO deploya)
git push origin main
# 3) tag → CI promove :<commit>→:vX.Y.Z (via API v2) e deploya na VPS1
git tag -a vX.Y.Z -m "release vX.Y.Z" && git push origin vX.Y.Z
# 4) verificar
curl -s https://newwhats.clube67.com/api/version # → vX.Y.Z · <commit> · PROD
```
**Rollback:** `/home/deploy/stack/newwhats.clube67.com/deploy-prod.sh <tag-anterior>` na VPS1 (o script
aceita a imagem local com a tag exata se o pull pela VPN falhar — nunca cai para `:latest`).
## Sincronização de config/contas dev→prod (passo SEPARADO)
O deploy de imagem **não** migra dados. Para levar config/contas de dev→prod há o
`sync-config-dev-para-prod.sh` (na raiz do projeto): **upsert** seguro (insere o que falta, atualiza o que
mudou, nunca apaga o que só existe em prod), **manual**, `dry-run` por padrão e `--apply` com backup.
Flag `--with-config` migra também a config cifrada do plugin (lê em claro do Dragonfly de dev, `auto_reply=false`,
regrava pelo backend de prod que recifra). Ver também o delta equivalente do scoreodonto.
Ver `ambiente-dev.md` (dev do motor), `integracao-satelites.md`, `secretaria-ia.md`.