# 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`.