docs(newwhats): runbook de migração de mídia local → Wasabi + limpeza de disco
- novo: wasabi-migracao-e-limpeza.md (fluxo executado 04/jul, v1.0.70) - wasabi-midias.md: ver-também cruzado Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
@@ -2,6 +2,9 @@
|
||||
|
||||
> Estado atual + desenho do que falta. Storage = **Wasabi (S3-compatível)** via
|
||||
> `StorageProvider` (`backend/src/core/StorageProvider.ts`). Última revisão: 28/jun/2026.
|
||||
>
|
||||
> **Ver também:** `wasabi-migracao-e-limpeza.md` — runbook de migração da mídia local
|
||||
> `/media/` → Wasabi + esvaziamento do disco (executado 04/jul/2026, v1.0.70).
|
||||
|
||||
## Como funciona hoje
|
||||
- **Upload** (`uploadFile`): fire-and-forget após receber a mídia/avatar; grava no
|
||||
|
||||
@@ -0,0 +1,97 @@
|
||||
# newwhats.clube67.com — Migração de mídia local → Wasabi & limpeza de disco
|
||||
|
||||
> Runbook **executado no DEV em 04/jul/2026 (release v1.0.70)** para tirar TODA a mídia
|
||||
> do disco do servidor e deixar só no Wasabi. Repetível em produção com os alvos de PROD.
|
||||
> Complementa `wasabi-midias.md` (design de index + delete em lote); aqui é o **fluxo de
|
||||
> migração one-shot + limpeza**, a feature de **thumbnails** e o **guard de recuperabilidade**.
|
||||
|
||||
## Contexto: onde a mídia vive
|
||||
|
||||
Campo `mediaUrl` na tabela `messages` (Postgres `newwhats_dev` / prod equivalente; colunas
|
||||
camelCase, model Prisma `@@map("messages")`) tem 3 estados:
|
||||
|
||||
| `mediaUrl` | Significado | Arquivo em disco? |
|
||||
|---|---|---|
|
||||
| `whatsapp/<tenant>/<instance>/<ts>-<nome>` | já no **Wasabi** (servido remoto) | não |
|
||||
| `/media/<instanceId>/<file>` | **LOCAL** — servido do disco | sim (`/app/media/...`) |
|
||||
| `NULL` | lazy — re-baixável do WhatsApp via `mediaPayload` | não |
|
||||
|
||||
Mídia local mora em `newwhats.dev/data/media` (montado como `/app/media` no container
|
||||
`newwhats-backend-dev`). **`newwhats.dev` é a cópia em execução (git-ignored); `newwhats.local`
|
||||
é a fonte versionada.** O container monta `newwhats.dev/plugins`.
|
||||
|
||||
## Config do Wasabi (importante: NÃO está em env)
|
||||
|
||||
Vem do **Dragonfly**, chave `plugin:config:UnifiedStorageProvider` (DB `/1` em
|
||||
`redis://…@10.99.0.3:6379/1`; a senha está na `DRAGONFLY_URL` do container). Ler:
|
||||
|
||||
```
|
||||
redis-cli -h 10.99.0.3 -p 6379 -a <senha> -n 1 GET plugin:config:UnifiedStorageProvider
|
||||
```
|
||||
|
||||
Valores DEV: bucket `whatsbasemidia`, endpoint `https://s3.wasabisys.com`, region `us-east-1`.
|
||||
Cliente S3 = `forcePathStyle:true`; upload com `ACL:'public-read'`.
|
||||
|
||||
## Convenção de key (replicar EXATAMENTE)
|
||||
|
||||
Plugin ativo `newwhats.dev/plugins/UnifiedStorageProvider/index.js` (~L330):
|
||||
|
||||
```
|
||||
whatsapp/<tenantId>/<instanceId>/<Date.now()>-<nomeSanitizado>
|
||||
```
|
||||
|
||||
As mídias já no Wasabi usam isso — se a key divergir, o serving quebra.
|
||||
|
||||
## Passo a passo da migração
|
||||
|
||||
Rodar **DENTRO do container** `newwhats-backend-dev` (tem `@aws-sdk/client-s3`, `pg`, os
|
||||
arquivos em `/app/media` e `DATABASE_URL`): `export NODE_PATH=/app/node_modules; cd /app`.
|
||||
|
||||
Para cada linha `WHERE "mediaUrl" LIKE '/media/%'`:
|
||||
1. ler o arquivo local (`/app/media/` + `mediaUrl` sem o prefixo `/media/`);
|
||||
2. `PutObject` no Wasabi com a key acima, `ContentType` do mime, `ACL:'public-read'`;
|
||||
3. **`HeadObject` conferindo `ContentLength == tamanho local`** (rede de segurança);
|
||||
4. só então `UPDATE messages SET "mediaUrl" = <key>`;
|
||||
5. `unlinkSync` do arquivo local.
|
||||
|
||||
Idempotente e resumível (ao migrar, o `mediaUrl` deixa de casar `/media/%`). **Rodar
|
||||
`DRY_RUN=1` primeiro** (verifica existência/tamanho, sem enviar/apagar). Script de
|
||||
referência: `migrate_media.js`.
|
||||
|
||||
### Refs mortas
|
||||
`/media/…` **sem arquivo E sem `mediaPayload`** = referência quebrada (no DEV eram 257).
|
||||
`UPDATE messages SET "mediaUrl" = NULL` → aí o guard `media_recoverable=false` exibe
|
||||
"indisponível" de forma limpa (em vez de tentar carregar path quebrado → 404).
|
||||
|
||||
### Órfãos e esvaziamento final
|
||||
Após migrar tudo, nenhum `mediaUrl` referencia arquivo local → qualquer arquivo que
|
||||
sobrar em `data/media` é órfão. Confirmar `erros=0` no resumo e então esvaziar
|
||||
(`find data/media -mindepth 1 -delete`). No DEV liberou ~2,1 GB.
|
||||
|
||||
## Feature acoplada (mesma release)
|
||||
|
||||
- **Thumbnails**: no re-download, além de subir a mídia, gera `sharp → 400px → webp q60`
|
||||
e grava `<mediaUrl>.thumb.webp` no Wasabi. O `MessageBubble` pede a thumb na prévia e
|
||||
cai pro full via `onError`. (commit `2c8eb41` / submódulo `09edeb9`)
|
||||
- **`mediaRecoverable`**: `GET /inbox/:chatId/messages` devolve `Boolean(mediaUrl || mediaPath
|
||||
|| mediaPayload)`; o frontend mapeia p/ `media_recoverable` e o guard evita o `400` de
|
||||
`/media/:id/download` em mídia legada.
|
||||
|
||||
## Resultado no DEV (04/jul/2026)
|
||||
|
||||
- 659 mídias migradas (~2,0 GB), 0 erros; 257 refs mortas → NULL; disco: 2,1 GB → 0.
|
||||
- Banco final: 2.231 no Wasabi, 26.793 lazy (NULL+payload), 0 refs `/media/`.
|
||||
|
||||
## Cuidados
|
||||
|
||||
- **Sempre** `HeadObject` antes de apagar o local; nunca apagar sem upload confirmado.
|
||||
- O **"janitor"** do plugin (`setInterval` 120s) só varre `localFallbackDir`/`uploads` e
|
||||
**não atualiza o banco** — não serve para migrar a mídia do WhatsApp.
|
||||
- Limpar temporários com credenciais depois (`/tmp/wasabi.env`, creds do Dragonfly).
|
||||
- Testar sempre no `dev.newwhats` antes; **pedir aprovação antes de rodar em produção**.
|
||||
|
||||
## Para PRODUÇÃO
|
||||
|
||||
O newwhats de produção **não roda na VPS4**. Aplicar o mesmo fluxo contra o diretório de
|
||||
mídia, o banco e a config Dragonfly **de produção**. Ver `ambiente-dev.md` e
|
||||
`infra/maquinas-e-rede.md`.
|
||||
Reference in New Issue
Block a user