diff --git a/AGENTS.md b/AGENTS.md index 51ff7679..d5d57534 100644 --- a/AGENTS.md +++ b/AGENTS.md @@ -12,7 +12,7 @@ These are product/architecture constraints — not a ban on editing existing cod - Single Next.js app, three route groups: `(frontend)` public site, `(payload)` admin, `(campaign)` internal campaign tool. No separate Rust service or frontend app for `/campanha`; it is modeled with Payload collections. The campaign roles are `coordinator` (label "Coordenador Geral"), `advisor` ("Assessor"), `leader` ("Liderança"), and `candidate` ("Candidato", unrestricted visibility), all on the separate auth collection `campaignUser` (see "Campaign auth" below). An advisor sees and manages ONLY the municipalities they administer; a leader is locked down to the supporter contact tool. - **2026-07-23 remodel (DEPLOYED to production 2026-07-23): "Município" is the operational unit** (it replaced "Praça", which replaced "Núcleo Eleitoral"). Municípios are PREDEFINED (seeded, geography read-only): 435 rows — one per Bahia municipality (416), except Salvador (19 zone Municípios, ZE 1–19); Camaçari is a single whole municipality (ZE 170/171 aggregated); shared/leaky TSE zones are cut at the municipal boundary. Code identifier: `municipality`. Leaderships are one record per person (`contact` UNIQUE) linked to N municipalities and N organizations; vote pledges are per leadership×municipality with `declaredVotes` (staff-entered, leader-visible) vs `estimatedVotes` (three scenarios, staff-only, hidden from the leader). Campaign production data was reset by the remodel migrations (public-site data untouched). Master plan: `docs/plans/remodelagem-municipios.md` (supersedes `remodelagem-pracas.md`). Details: "Campaign Municípios model" below. -- Production is self-hosted on the homeserver (`jorgesolla1313.com.br`, behind the Cloudflare tunnel; container/database `teqo-1313`/`teqo_1313`). **There is no Vercel deploy anymore** — the Vercel deploy job, `vercel-promote.yml`, the Vercel scripts/`vercel.json` and the `VERCEL_*` repo secrets were removed in OPS50 (2026-08-17). The CI (`ci.yml` on `main`) is the verifier — full suite green — **plus the deploy step (OPS53, 2026-08-17)**: the `deploy` job (runs-on `host` on the workstation runner) streams `scripts/deploy-homeserver.sh` to the homeserver over SSH; the remote script builds the standalone image at the pushed SHA (BuildKit secrets from `~/stack/teqo-1313.env`), pushes it to the local registry (`localhost:5000`), applies pending migrations through the compose maintenance service `teqo-1313-migrate` **before** the rollout, and recreates the container. Merge em `main` verde == site atualizado. Runbook (rollback, falhas conhecidas): `docs/ops/teqo-1313-deploy.md`. **The production database lives on the homeserver Postgres (`teqo_1313`, migrated from Neon in OPS51, 2026-08-17)** — the full production content (contacts, signatures, subscriptions, campaign) was restored there via `pg_dump`/`pg_restore` (dump in `/srv/hdd/backups/teqo-neon-pre-migracao/`); Neon remains alive and untouched as the documented rollback (re-point `DATABASE_URL` in `~/stack/teqo-1313.env`). Media moved to the homeserver Garage S3 in OPS52 (2026-08-18): the `media` collection uploads via `@payloadcms/storage-s3` when the `S3_*` envs are set, and Payload proxies the files through `/api/media/file/...` (the bucket stays private). Production env lives in `~/stack/teqo-1313.env` (bucket `teqo-media`, endpoint tailnet `http://100.119.220.31:3900`, key `teqo-key`); a partial `S3_*` config aborts the boot, and no `S3_*` means local disk (dev/test). Render of the 40 pre-existing media rows is still broken (they reference local `/api/media/file/...` paths with no files anywhere) — recovery is `pnpm media:recover` (OPS52-media: restaura os objetos no bucket a partir do WordPress, sem tocar no DB; runbook em `docs/plans/ops52-media-recuperar-arquivos-impl.md`); the URLs themselves are the same contract the proxy now serves. Never point dev/test databases at the homeserver prod DB either — the local guards (`guard-dev-db`, `assertTestDatabase`) still enforce `localhost`/`_test`-suffixed targets. +- Production is self-hosted on the homeserver (`jorgesolla1313.com.br`, behind the Cloudflare tunnel; container/database `teqo-1313`/`teqo_1313`). **There is no Vercel deploy anymore** — the Vercel deploy job, `vercel-promote.yml`, the Vercel scripts/`vercel.json` and the `VERCEL_*` repo secrets were removed in OPS50 (2026-08-17). The CI (`ci.yml` on `main`) is the verifier — full suite green — **plus the deploy step (OPS53, 2026-08-17)**: the `deploy` job (runs-on `host` on the workstation runner) streams `scripts/deploy-homeserver.sh` to the homeserver over SSH; the remote script builds the standalone image at the pushed SHA (BuildKit secrets from `~/stack/teqo-1313.env`), pushes it to the local registry (`localhost:5000`), applies pending migrations through the compose maintenance service `teqo-1313-migrate` **before** the rollout, and recreates the container. Merge em `main` verde == site atualizado. Runbook (rollback, falhas conhecidas): `docs/ops/teqo-1313-deploy.md`. **The production database lives on the homeserver Postgres (`teqo_1313`, migrated from Neon in OPS51, 2026-08-17)** — the full production content (contacts, signatures, subscriptions, campaign) was restored there via `pg_dump`/`pg_restore` (dump in `/srv/hdd/backups/teqo-neon-pre-migracao/`); Neon remains alive and untouched as the documented rollback (re-point `DATABASE_URL` in `~/stack/teqo-1313.env`). Media moved to the homeserver Garage S3 in OPS52 (2026-08-18): the `media` collection uploads via `@payloadcms/storage-s3` when the `S3_*` envs are set, and Payload proxies the files through `/api/media/file/...` (the bucket stays private). Production env lives in `~/stack/teqo-1313.env` (bucket `teqo-media`, endpoint `http://host.docker.internal:3900` — o container alcança o Garage no host via `extra_hosts: host-gateway` + regras ufw `allow from 10.0.11.0/24 to any port 3900` (INPUT e route/FWD); a workstation/tailnet usa `http://100.119.220.31:3900`, key `teqo-key`); a partial `S3_*` config aborts the boot, and no `S3_*` means local disk (dev/test). The 40 pre-existing media rows are RECOVERED (OPS52-media, 2026-08-18): 38/40 objects restaurados no bucket a partir do WordPress (38 capas servindo 200 em `/api/media/file/...`; exceções sem fonte: `fim-escala-6x1.jpg` e `jorgesolla.jpg`) — re-executar via `pnpm media:recover` (restaura objetos sem tocar no DB; runbook em `docs/plans/ops52-media-recuperar-arquivos-impl.md`); the URLs themselves are the same contract the proxy now serves. Never point dev/test databases at the homeserver prod DB either — the local guards (`guard-dev-db`, `assertTestDatabase`) still enforce `localhost`/`_test`-suffixed targets. - Donations are NOT handled in this app — they run through QueroApoiar (`apoiar.me/jorgesolla`, TSE-homologated). The site only needs a "Doar" link/CTA out to that URL. - **Embasamento de produto/domínio:** `docs/research/` guarda o compêndio de literatura (campanha de DF na Bahia, geografia do voto, cartografia eleitoral) e o relatório de discovery aprovado (2026-07-21) com o playbook dado→decisão. Para métricas, análises territoriais, mapa e priorização em `/campanha`, consulte-o antes de inventar — o kernel: a disputa de DF é conta de quociente fragmentada; leitura útil é relativa e local (captura, LQ, quantis, cobertura de meta por pledges), nunca % estadual absoluto. diff --git a/docs/plans/ops52-media-recuperar-arquivos-impl.md b/docs/plans/ops52-media-recuperar-arquivos-impl.md index 299ba572..56f25127 100644 --- a/docs/plans/ops52-media-recuperar-arquivos-impl.md +++ b/docs/plans/ops52-media-recuperar-arquivos-impl.md @@ -166,8 +166,24 @@ contratos de guard diferentes no mesmo script; o seed é local-first por design) ## Runbook de execução em produção (pós-merge, humano) -Ordem obrigatória — o pre-flight separa "arquivo faltando" (esta Issue) de -"container prod sem rota ao Garage" (infra, sucessor OPS): +> **Estado 2026-08-18 (executado):** 38/40 objetos restaurados e servindo 200 em +> `/api/media/file/...`; exceções sem fonte: `fim-escala-6x1.jpg` e +> `jorgesolla.jpg` (404 honesto — rows manuais, não referenciadas por posts). +> **E3 resolvido na raiz:** o container prod não alcançava o Garage — o +> firewall do host derrubava TODA a sub-rede do docker (gateway, LAN e tailnet: +> tudo bloqueado — o `headObject` pendurava e a rota de media dava timeout). +> Fix aplicado no homeserver (reversível): +> +> 1. `sudo ufw allow from 10.0.11.0/24 to any port 3900 proto tcp` (INPUT) +> 2. `sudo ufw route allow from 10.0.11.0/24 to any port 3900 proto tcp` (FWD) +> 3. `extra_hosts: ["host.docker.internal:host-gateway"]` nos serviços +> `teqo-1313` e `teqo-1313-migrate` do `~/stack/docker-compose.yml` +> (sobrevive aos deploys — o OPS53 edita o compose in-place; backup +> `docker-compose.yml.pre-ops52-media-hostgateway`) +> 4. `S3_ENDPOINT=http://host.docker.internal:3900` no `~/stack/teqo-1313.env` +> (o tailnet IP continua sendo o endpoint da workstation) + +Ordem das próximas execuções (pós-merge, humano): 1. **Deploy do merge em `main`** (CI OPS53) — o script e a lib chegam ao homeserver com a imagem do SHA. @@ -181,12 +197,8 @@ Ordem obrigatória — o pre-flight separa "arquivo faltando" (esta Issue) de ``` Esperado: `headObject` de cada filename responde `presente`/`ausente` em segundos. Se TODAS as rows derem `erro` (timeout/ECONNREFUSED), o container - não alcança o Garage — diagnosticar antes de recuperar: - ```bash - docker compose exec teqo-1313 node -e "fetch('http://100.119.220.31:3900/').catch(e=>{console.error('SEM ROTA ao Garage:', e.message); process.exit(1)}).then(()=>console.log('Garage alcançável'))" - ``` - (o `.env.local` do worktree NUNCA recebe as credenciais de prod — rodar - sempre no homeserver). + não alcança o Garage — repetir o diagnóstico do estado acima (firewall + + extra_hosts + endpoint) antes de recuperar. 3. **Recuperar:** ```bash ALLOW_REMOTE_DB=true pnpm media:recover @@ -198,13 +210,14 @@ Ordem obrigatória — o pre-flight separa "arquivo faltando" (esta Issue) de ```bash ALLOW_REMOTE_DB=true pnpm media:recover --verify ``` - Esperado: `40/40 filenames respondendo 200 em https://jorgesolla1313.com.br` - — é o aceite da Issue. Se persistir pendurando (erro/timeout) com objetos no - bucket → confirmar container→Garage (passo 2) e abrir sucessor OPS de infra. + Esperado: `38/40 filenames respondendo 200 em https://jorgesolla1313.com.br` + (as 2 exceções seguem 404 — aceite da Issue contempla o registro honesto). + Se persistir pendurando (erro/timeout) com objetos no bucket → confirmar + container→Garage (passo 2) — agora a causa raiz conhecida é firewall. 5. **Check visual:** home de campanha (seção "Acompanhe de perto") e 1–2 páginas de artigo com capa; console do navegador sem erro de imagem. -6. **Exceções:** o 1 artigo sem cover no WP (43→42 resolvem) e a 40ª row órfã - se não resolver → reportar na Issue #10; o card degrada para a banda cinza +6. **Exceções:** `fim-escala-6x1.jpg` e `jorgesolla.jpg` (rows manuais sem + fonte no WP) → reportar na Issue #10; o card degrada para a banda cinza existente (sem placeholder, sem placeholder "em breve"). ## Rabbit holes / Não escopo (engenharia)