Skip to content

Instance Deployment Runbook — IJCP (2026-08-16) ​

Este documento é o registro de memória do deploy da primeira instância de produção real (IJCP). Ele contém: (1) inventário das instâncias, (2) o que foi feito, (3) lições aprendidas, (4) o que lembrar desta instância e (5) o runbook para futuros deploys.

Sem segredos aqui. Tokens, senhas e chaves privadas vivem fora do repo (env vars, GitHub secrets, wrangler secret put, arquivos gitignored) — ver as seções correspondentes.


1. Inventário de instâncias ​

InstânciaFrontend (Pages)Backend (Worker)D1R2Papel
Demoapp.neemias.app (neemias)api.neemias.app (api-neemias, workers/wrangler.toml)neemias (c965d6fc-ee7e-41dd-bc81-65b05652aa17)neemias-uploadsDemo pública: DEMO_MODE=true, reseed semanal (cron)
IJCPijcp.neemias.app (neemias-ijcp)api-ijcp.neemias.app (api-ijcp, workers/wrangler.ijcp.toml)ijcp (fa6e5e02-bfab-4b1a-8db5-daf972fd421d)ijcp-uploadsPrimeira produção real: sem demo, sem cron, licenciada
  • Conta Cloudflare: Shaveslavers@gmail.com's Account (6ac86791e3fed01df841ff3beffd1f0f), zona neemias.app (2b2249fc4b9b5c873eef562651d88f64).
  • DNS (zona neemias.app, ambos proxied/CNAME):
    • ijcp → neemias-ijcp.pages.dev
    • api-ijcp → api-ijcp.shaveslavers.workers.dev
  • Deploy CI: .github/workflows/deploy-ijcp.yml (tags ijcp-v* ou workflow_dispatch, runner self-hosted).
  • Durable Objects (SQLite) por worker: CAPACITY_FEED_DO, NOTIFICATION_FEED_DO, ATTENDANCE_FEED_DO.

2. O que foi feito no deploy (2026-08-16) ​

  1. Fix crítico de auth (ADR-0031) — o PBKDF2 de duas passagens travava todo usuário após o 1º login: o cliente enviava um pre-hash com salt aleatório a cada login, então o verifier pbkdf2:client: do servidor nunca mais conferia. Provado com probe; corrigido com clientPrehash() determinístico (salt derivado da senha) em app/src/modules/auth/clientPrehash.ts; backendLogin() o usa. ADR-0031 corrigido.
  2. Licenciamento — par de chaves Ed25519 de produção gerado; pública embutida em workers/src/license/key.ts; privada em workers/.prod-license-private.pem (gitignored, backup com o operador). Licença IJCP emitida: ijcp / nucleus,volunteers / expira 2027-08-16. Fixtures de teste re-assinados.
  3. Email — Cloudflare Email Sending exige Workers Paid (não disponível no plano grátis) → IJCP usa Resend (RESEND_API_KEY secret; RESEND_FROM_EMAIL=contato@neemias.app). O código suporta o binding [[send_email]] para um futuro upgrade de plano.
  4. Recursos Cloudflare (via API/wrangler) — D1 ijcp, R2 ijcp-uploads, Pages neemias-ijcp; migrations aplicadas (0001-0003, roles do sistema incluídas); Worker api-ijcp + rota; secrets AUTH_JWT_SECRET e RESEND_API_KEY.
  5. Frontend — build IJCP (DEPLOY_ENV=production, VITE_BACKEND_URL=https://api-ijcp.neemias.app, VITE_DEMO_MODE=false — sobrescreve o .env.production da demo) → Pages neemias-ijcp.
  6. Admin pré-criado — admin@ijcp.neemias.app (papel ADMIN, hash v1 que auto-upgrade para v2 estável no 1º login). Senha entregue fora do repo.
  7. Deploy via CI validado — tag ijcp-v0.1.0 disparou o workflow end-to-end (migrations idempotentes → worker → secrets → Pages → domínio → health).
  8. Roles pre-instaladas (pós-deploy) — o backend já tinha as 8 roles do sistema, mas o app lê a tabela roles local (IndexedDB), que nasce vazia em produção → tela "Papéis" vazia. Fix: hydrateRoles() no login/ refresh busca GET /api/v1/roles (admin) e faz upsert local (todas as instâncias).
  9. Avatares DiceBear — perfis sem foto passam a renderizar avatar de iniciais gerado localmente (@dicebear/core + @dicebear/collection, offline-safe, deterministico por nome) em lista/perfil/checkin/checkout/ turmas/formulário (todas as instâncias).
  10. Fix da demo — admin@neemias.local/senha123 parou de funcionar (hash v2 corrompido pelo bug do item 1). Hash resetado para v1 no D1 da demo + redeploy do frontend da demo com o fix.

3. Lições aprendidas ​

  1. Salt do pre-hash do cliente tem que ser determinístico. ADR-0031 exige que o clientHash enviado no login seja estável por senha; com salt aleatório, o 2º login falha para sempre. Sempre teste "login → logout → login" (o E2E de 1 login só não pega isso).
  2. Rotacionar a chave de licença invalida fixtures e licenças dev.key.ts embute a pública; após a troca, re-assine os tokens hardcoded em workers/src/__tests__/{gating-fetch,license-route}.test.ts (comando no próprio arquivo) e re-emita licenças de clientes.
  3. Escopo do token da Cloudflare. Um token "account-scope" (Workers/ Pages/D1/R2) não cria registros DNS (403). São necessários dois perfis: token de operação (account) + token com Zone → neemias.app → DNS:Edit. O GitHub secret CLOUDFLARE_API_TOKEN precisa do token com DNS para o passo de DNS do workflow funcionar.
  4. Rota de Worker ≠ DNS. wrangler deploy com [[routes]] registra a rota, mas a hostname só resolve com um CNAME (proxied) na zona. O custom domain do Pages também precisa do CNAME ("CNAME record not set").
  5. wrangler pages deploy em tag → deployment PREVIEW. O runner faz checkout em detached HEAD; sem --branch main o deploy cai no branch "HEAD" (preview) e o domínio de produção não atualiza. Sempre usar --branch main no workflow.
  6. Email Sending (Cloudflare) exige Workers Paid. No plano grátis, use Resend (código já tem fallback). Verificar o domínio em resend.com/domains.
  7. Turbo cache e env de build. turbo.json precisa declarar na chave de cache todo env que afeta o bundle (DEPLOY_ENV, VITE_DEMO_MODE, VITE_BACKEND_URL) — senão um build da demo é reutilizado para outra instância (com demo-seed vazando para produção!).
  8. .env.production pertence à DEMO. (VITE_DEMO_MODE=true). Instâncias de produção precisam sobrescrever com VITE_DEMO_MODE=false no build.
  9. Roles vivem no backend; o app espelha localmente. A tela "Papéis" lê a tabela roles do IndexedDB. Instância nova → vazia. Hidratar no login (hydrateRoles) resolve para todas as instâncias.
  10. Baselines de teste. scripts/thresholds.sh (TEST_COUNT_BASELINE) e scripts/count-as-any.sh precisam ser atualizados quando o count muda — o pre-commit falha se não.
  11. Admin pré-criado em produção: inserir com hash v1 (pbkdf2:) — o 1º login valida pela senha crua e faz upgrade para v2 com o clientHash determinístico (fica verificável para sempre).
  12. Segredos colados no chat devem ser rotacionados após a sessão (tokens Cloudflare/Resend).

4. Memória operacional — INSTÂNCIA IJCP ​

Renovações (calendarizar):

Configuração viva (não mexer sem necessidade):

  • AUTH_JWT_SECRET do IJCP: rotacionar derruba todas as sessões. Está em wrangler secret put (worker) + GitHub secret IJCP_AUTH_JWT_SECRET.
  • RESEND_API_KEY (worker secret) + verificação de contato@neemias.app em resend.com/domains (sem verificação, OTP/notificações não saem).
  • Chave privada de licença: workers/.prod-license-private.pem (gitignored, backup com o operador). Perder = rotacionar chave = invalidar licenças.
  • DNS: os 2 CNAMEs acima; o passo DNS do workflow é best-effort (falha silenciosa se o token não tiver Zone→DNS).

Deploys IJCP: push de tag ijcp-v* (ex.: ijcp-v0.1.1) ou workflow_dispatch. O workflow faz: migrations → worker → secret (se faltar) → build frontend (env IJCP) → Pages --branch main → domínio → health.


5. Runbook — FUTUROS DEPLOYS (nova instância) ​

  1. Domínios: escolher <sub> e api-<sub>.neemias.app na zona neemias.app.
  2. Config: copiar workers/wrangler.ijcp.toml → wrangler.<id>.toml (nome do worker, [[routes]], ALLOWED_ORIGINS, ENVIRONMENT=production, LICENSE, email; sem cron/DEMO_MODE).
  3. Recursos (token de operação):wrangler d1 create <id> → preencher database_id no toml; wrangler r2 bucket create <id>-uploads; wrangler pages project create neemias-<id> --production-branch main.
  4. Migrations: npx wrangler d1 migrations apply <id> --remote --config workers/wrangler.<id>.toml.
  5. Secrets: AUTH_JWT_SECRET (openssl rand -hex 32), RESEND_API_KEY (se Resend) via wrangler secret put --config workers/wrangler.<id>.toml.
  6. Worker: npx wrangler deploy --config workers/wrangler.<id>.toml (cria a rota custom).
  7. Frontend (online-only, guardado — #685/#703):VITE_ONLINE_ONLY=true DEPLOY_ENV=production VITE_BACKEND_URL=https://api-<sub>.neemias.app VITE_DEMO_MODE=false pnpm build:app → bash scripts/verify-online-build.sh (falha se o flag foi omitido) → npx wrangler pages deploy app/dist --project-name neemias-<id> --branch main.
  8. DNS (token com Zone→DNS:Edit): CNAME <sub> → neemias-<id>.pages.dev e api-<sub> → api-<id>.shaveslavers.workers.dev (proxied).
  9. Domínio Pages: POST /accounts/{id}/pages/projects/neemias-<id>/domains {"name":"<sub>.neemias.app"}.
  10. Admin pré-criado: gerar SQL com hash v1 (hashPassword(senha, 1) via workers/src/modules/auth/password.ts) → INSERT users + INSERT user_roles(... 'ADMIN') no D1 remoto. Entregar a senha fora do repo.
  11. Licença: pnpm sign-license <id> <modulos> --plan production --key-file workers/.prod-license-private.pem → LICENSE no toml.
  12. CI: clonar o job do deploy-ijcp.yml (config, secrets do repo: CLOUDFLARE_ACCOUNT_ID, CLOUDFLARE_API_TOKEN com DNS, <ID>_AUTH_JWT_SECRET).
  13. Verificar: health, login ×2 (regressão do lockout), CORS do origin, CSP (connect-src com o novo api-*), domínios ativos no Pages.
  14. Docs: atualizar environments.md, production-checklist.md (seção de instância), este runbook e o CHANGELOG.

Ver também: environments.md · deployment.md · production-checklist.md · licensing.md · ADR-0031

Distribuído sob licença MIT.