- payload.config.ts: vercelBlobStorage -> s3Storage (@payloadcms/storage-s3) com endpoint Garage (forcePathStyle, region garage); bucket privado — o staticHandler do plugin streama os objetos em /api/media/file/<filename>, mantendo o contrato de URL relativa das 40 rows pré-existentes - src/utilities/mediaStorage.ts: resolveS3StorageEnv fail-closed (S3_* parcial -> throw no boot com a lista exata; nenhuma -> storage local dev/test) - seed-posts: remove pre-delete do @vercel/blob (S3 sobrescreve nativamente) - worktree-env.mjs: s3EnvCopiedLines all-or-nothing (unit-testada) - envs S3_BUCKET/S3_ENDPOINT/S3_REGION/S3_ACCESS_KEY_ID/S3_SECRET_ACCESS_KEY no .env.example/.env.test; cloud-setup.sh sem o sed morto do blob - deps: -storage-vercel-blob -@vercel/blob +storage-s3; importMap regenerado - docs: impl plan (revisão A->B: Garage v2 sem acesso anônimo na API S3), AGENTS.md, changelog 2026-08-18-ops52
2.1 KiB
Recently resolved (2026-08-18): OPS52 — a media saiu do Vercel Blob para o Garage S3 do homeserver (bucket teqo-media + key teqo-key, já existentes no Garage v2.1.0). O plugin @payloadcms/storage-vercel-blob (e o @vercel/blob do seed) foi trocado por @payloadcms/storage-s3 na config, no padrão R2 da doc do Payload (endpoint + forcePathStyle: true, region garage). Decisão de arquitetura (revisão A→B durante a execução, gate com o humano): o Garage v2.1.0 não suporta acesso anônimo na API S3 (sem --public-read; source: "when we support anonymous access in the future"; testado: anonymous/anon → NoSuchAccessKey), então o bucket fica privado e a app serve a media por proxy — sem disablePayloadAccessControl, o staticHandler do plugin streama o objeto do Garage em /api/media/file/<filename> (range + etag/304 nativos) — o mesmo contrato de URL relativa que as 40 rows pré-existentes já têm (a recuperação dos arquivos é a Issue #10/OPS52-media). Fail-closed: resolveS3StorageEnv (src/utilities/mediaStorage.ts, unit-testado) — envs S3_* parciais → throw no boot com a lista exata de faltantes; nenhuma → storage local (dev/test). Envs novas: S3_BUCKET, S3_ENDPOINT, S3_REGION (default garage), S3_ACCESS_KEY_ID, S3_SECRET_ACCESS_KEY (.env.example, .env.test, worktree.mjs com cópia all-or-nothing). Seed de posts: pre-delete do Blob removido (o S3 sobrescreve o key determinístico nativamente — idempotência intacta). Prova de contrato real: PUT/GET/DELETE via SDK contra o Garage (tailnet http://100.119.220.31:3900), depois media criada pelo Payload local com S3 ativo → media.url relativa + GET /api/media/file/... → 200 image/png; bucket devolvido a 0 objects e .env.local do worktree limpo (sem credenciais de prod). Sem migration (config-only; importMap regenerado sem VercelBlobClientUploadHandler). Runbook de cutover registrado no impl plan: setar as 5 S3_* no ~/stack/teqo-1313.env (endpoint tailnet) antes do próximo deploy (OPS53). Repo: docs/plans/ops52-blob-vercel-para-garage-impl.md; infra-solla STATE.md atualizado à parte.