From 6b5ebd48caef0c69dc8be58589762c1e9f3efe9f Mon Sep 17 00:00:00 2001 From: VPS 4 Builder Date: Sat, 4 Jul 2026 19:18:39 +0200 Subject: [PATCH] =?UTF-8?q?docs(newwhats):=20runbook=20de=20migra=C3=A7?= =?UTF-8?q?=C3=A3o=20de=20m=C3=ADdia=20local=20=E2=86=92=20Wasabi=20+=20li?= =?UTF-8?q?mpeza=20de=20disco?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - 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 --- .../newwhats.clube67.com/wasabi-midias.md | 3 + .../wasabi-migracao-e-limpeza.md | 97 +++++++++++++++++++ 2 files changed, 100 insertions(+) create mode 100644 projetos/newwhats.clube67.com/wasabi-migracao-e-limpeza.md diff --git a/projetos/newwhats.clube67.com/wasabi-midias.md b/projetos/newwhats.clube67.com/wasabi-midias.md index 93a59f9..396d721 100644 --- a/projetos/newwhats.clube67.com/wasabi-midias.md +++ b/projetos/newwhats.clube67.com/wasabi-midias.md @@ -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 diff --git a/projetos/newwhats.clube67.com/wasabi-migracao-e-limpeza.md b/projetos/newwhats.clube67.com/wasabi-migracao-e-limpeza.md new file mode 100644 index 0000000..812bcfa --- /dev/null +++ b/projetos/newwhats.clube67.com/wasabi-migracao-e-limpeza.md @@ -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///-` | já no **Wasabi** (servido remoto) | não | +| `/media//` | **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 -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///- +``` + +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" = `; +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 `.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`.