iachat/docs/chatwoot-staging-deploy.md
Rodribm10 b457e84c2f fix(captain): route embeddings to legacy OpenAI + retry transient errors
Resolve duas camadas de problema identificadas em teste end-to-end:

1. Embeddings falhavam com HTTP 404 (/codex/v1/embeddings não existe).
   Solução: Captain::Llm::EmbeddingService sempre usa OpenAI tradicional
   via Llm::Config.with_api_key(legacy_settings). ProviderConfig expõe
   legacy_openai_settings pra isso.

2. Servidor Codex ocasionalmente responde com response.failed +
   code=server_error (instabilidade transitória). Client agora retenta
   até 2x com backoff exponencial (0.5s, 1.5s) em erros retryable:
   HTTP 5xx, server_error no response.failed, ou stream inacabado.

Outras correções nesta etapa:
- Scenario#agent_model: em modo Codex, ignora CAPTAIN_OPEN_AI_MODEL_SCENARIO
  (que pode ter gpt-4o legado) e usa ProviderConfig.model.
- ExtractionService/ContradictionCheckerService/TranslateQueryService:
  trocam constantes hardcoded gpt-4o-mini/gpt-4.1-nano por
  ProviderConfig.light_model (respeitando o provider ativo).
- ProviderConfig.DEFAULT_CODEX_MODEL agora é gpt-5.2 (reconhecido pelo
  RubyLLM; gpt-5.4 não está no catalog do gem).

Validado ponta-a-ponta: WhatsApp → Chatwoot → Jasmine → handoff Daniela
→ faq_lookup com embedding OK → resposta com preços corretos.

Docs em docs/captain-codex-oauth.md.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-04-22 17:42:31 -03:00

5.3 KiB

Chatwoot — Deploy de branch em staging paralela

Runbook pra subir qualquer branch do fork iachat como stack Swarm paralela isolada da produção, testar, e só depois fazer merge pra main.

Automação: existe uma skill do Claude Code que executa esse runbook passo a passo. Invoque com "subir branch X em staging" no chat. Arquivos: ~/.claude/skills/chatwoot-staging-deploy/. Este doc é o backup versionado pra quando não for usar a skill.

Arquitetura atual

  • Repo: github.com/rodribm10/iachat (fork do Chatwoot)
  • VPS: root@76.13.174.155 (Leo), Docker Swarm + Traefik v2.11 + Let's Encrypt
  • Prod: stack iachat em iachat.hoteis1001noites.com.br, imagem ghcr.io/rodribm10/iachat:vN
  • Workflow CI: .github/workflows/deploy_ghcr.yml → publica :latest + :v<run_number> a cada push
  • Credenciais da VPS: em docs/acessos_vps.md (gitignored — NÃO commitar)

Fluxo em 9 fases

1. Preparar commit local

# Trava credenciais fora do commit
git check-ignore docs/acessos_vps.md || (echo "PERIGO: credenciais não ignoradas"; exit 1)

git add -A
git diff --cached --name-only | grep -iE "acessos|vps" && { echo "FALHA: credenciais stageadas"; exit 1; }

Commit com mensagem estruturada (feat/fix + descrição).

Pre-commit hooks:

  • ESLint exige i18n — adicionar keys em app/javascript/dashboard/i18n/locale/{pt_BR,en}/captain.json
  • Rubocop metric violations: # rubocop:disable Metrics/MethodLength,Metrics/AbcSize antes da class

Nunca usar --no-verify.

2. Push + aguardar CI

git push origin <branch>
gh run list --repo rodribm10/iachat --branch <branch> --limit 1
# aguarda status "completed success" (~10-15min multi-arch)

# Pega número da tag gerada
gh run view --repo rodribm10/iachat <run_id> --log \
  | grep "imagetools create" -A 3 | grep -oE "v[0-9]+"
# → retorna "v67" (por exemplo)

Sempre usar a tag vN específica, NUNCA :latest (outras branches sobrescrevem).

3. Inspeção read-only da VPS

ssh root@76.13.174.155 '
docker stack ls
docker service inspect iachat_iachat_app --format "{{json .Spec}}" | python3 -m json.tool
docker service inspect iachat_iachat_app --format "{{range .Spec.TaskTemplate.ContainerSpec.Env}}{{println .}}{{end}}"
'

4. Gerar secrets únicos NA VPS

ssh root@76.13.174.155 "
mkdir -p /root/<stack-name>
cat > /root/<stack-name>/.secrets <<EOF
SECRET_KEY_BASE=\$(openssl rand -hex 64)
AR_PRIMARY=\$(openssl rand -hex 16)
AR_DETERMINISTIC=\$(openssl rand -hex 16)
AR_SALT=\$(openssl rand -hex 16)
POSTGRES_PASSWORD_NEW=\$(openssl rand -base64 24 | tr -d /=+)
EOF
chmod 600 /root/<stack-name>/.secrets
"

5. Criar stack.yml + app.env + postgres_password.txt

Templates em:

  • ~/.claude/skills/chatwoot-staging-deploy/stack.yml.template
  • ~/.claude/skills/chatwoot-staging-deploy/app.env.template

Pontos críticos:

  • Traefik rule=Host('<dns>') com priority=100 pra vencer catchall regex do prod
  • Network pública network_swarm_public (external)
  • Volumes isolados (postgres_data, redis, storage)
  • Postgres password via Docker Secret (arquivo /root/<stack>/postgres_password.txt)
  • POSTGRES_DATABASE=iachat_staging (não production)

6. Deploy

ssh root@76.13.174.155 "
docker pull ghcr.io/rodribm10/iachat:v<N>
docker stack deploy -c /root/<stack>/stack.yml --with-registry-auth <stack>
sleep 10
docker service ls --filter name=<stack>
"

Se <stack>_app reinicia em loop → é DB vazio. Próximo passo.

7. Schema + migrations (container one-off)

ssh root@76.13.174.155 "
docker run --rm --network <stack>_<stack>_internal \
  --env-file /root/<stack>/app.env \
  -e DISABLE_DATABASE_ENVIRONMENT_CHECK=1 \
  ghcr.io/rodribm10/iachat:v<N> \
  sh -c 'bundle exec rails db:schema:load db:migrate db:seed'
"

DISABLE_DATABASE_ENVIRONMENT_CHECK=1 é necessário — Rails bloqueia destrutivas em prod, mas aqui DB é zero.

8. Restart do app

ssh root@76.13.174.155 "docker service update --force <stack>_app"

Aguarde ~20s. docker service ps <stack>_app deve mostrar Running sem crash.

9. Teste HTTPS

curl -sSI https://<dns>/
# Esperado: 302 redirect to /installation/onboarding

302 → abre no browser e cria admin via onboarding.

Troubleshooting

Sintoma Fix
installation_configs does not exist Falta Fase 7 (schema:load)
ActiveRecord::ProtectedEnvironmentError Adicionar DISABLE_DATABASE_ENVIRONMENT_CHECK=1
Cert Let's Encrypt inválido Labels Traefik erradas; conferir priority=100 e rule=Host() não regex
302 pra página do prod Catchall ganhou; aumentar priority ou verificar rule específica
image not found no pull Tag errada; gh run list pra confirmar

Segurança

  1. docs/acessos_vps.md — gitignored. NUNCA commite.
  2. Senha Nicodemos1@@1 foi compartilhada no histórico — trocar nas 4 VPSs (Leo, Rodrigo, Financeiro, Oracle).
  3. Secrets por stack — gerar novos, nunca reutilizar entre envs.
  4. Tag :latest é sobrescrita por qualquer push — sempre usar vN.

Estado atual de exemplo

Primeira execução desse runbook: 2026-04-21

  • Branch: feat/captain-semantic-memory
  • Stack: iachat-v2
  • DNS: iachatv2.hoteis1001noites.com.br
  • Imagem: ghcr.io/rodribm10/iachat:v67
  • Status: deploy bem-sucedido, aguardando onboarding do admin