Files
PessoaandClaude Opus 5 73857d4881 docs: a Vercel deixa de aparecer como onde o CRM roda e valida
O projeto do CRM na Vercel foi desvinculado do GitHub por decisao do dono: a
plataforma fica so com a landing page, em outro repositorio. Medido com a mesma
sonda nos dois lados — `gh api repos/.../commits/<sha>/status` devolve `Vercel`
no commit f65d04667 (antes) e vazio em a8c9e1ad3, 7168961ad e no head do #1081
(depois).

Com isso, dezenas de afirmacoes do repositorio publico viraram falsas. As que
falavam no presente sairam:

- mensagem automatica ao contribuinte, molde de PR, CONTRIBUTING e os espelhos
  na skill de contribuir prometiam um check "Vercel" vermelho que nao aparece
  mais. No lugar, a mensagem passa a dizer onde olhar o que trava o merge — os
  checks marcados Required no proprio PR, com a ressalva de que `--required` so
  lista os que ja reportaram;
- runbooks de operacao mandavam abrir o painel da plataforma e redeployar a
  main; passam a descrever o `.env` da instalacao e a recriacao dos conteineres;
- `lib/env.ts` mandava, no boot, ajustar variavel "na Vercel";
- specs e PRDs descreviam topologia, crons e guarda de segredo na plataforma;
- comentarios de codigo ancoravam decisoes vivas em premissa morta.

Registros datados (pesquisa, pitch, epicos, handoffs) nao foram reescritos:
ganharam uma linha dizendo que sao registro da fase hospedada.

O `vercel.ts` fica: ele serve quem hospeda um fork, e o gate
`tests/unit/cron-routes-scheduled.test.ts` continua reprovando divergencia de
rotas entre ele e o crontab do scheduler.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-09-17 15:00:03 -03:00

13 KiB

title, status, last_review, owner
title status last_review owner
Runbook — WAHA em produção (VPS Hostgator) canônico 2026-09-17 Rafael Melgaço

Runbook — WAHA em produção (VPS Hostgator)

Guia passo-a-passo pra subir, operar e recuperar a instância WAHA Plus em produção sobre VPS Hostgator. Hostgator é parceiro comercial; este runbook substitui qualquer doc histórico que mencionasse Hetzner.


1. Especificações alvo

Item Valor recomendado Notas
Plano Hostgator VPS Turing (ou superior) Cartesius (1 vCPU/2GB) é insuficiente — puppeteer/baileys + 5+ sessões saturam.
OS Ubuntu 22.04 LTS ou 24.04 LTS NOWEB engine testado em ambos. CentOS funciona mas docs do compose pressupõem Debian-family.
CPU/RAM mín. 2 vCPU / 4 GB RAM NOWEB usa ~150 MB por sessão; +overhead Node ~300 MB.
Disco mín. 80 GB SSD Mídia inline mínima (vai pro Supabase Storage), mas .sessions cresce com histórico WhatsApp Web.
Datacenter São Paulo (default Hostgator BR) Latência <30ms pro Meta SP — relevante pra anti-banimento e UX de QR.
IP público Estático (incluso no plano) Necessário pra DNS A record + egress allowlist.

Sem parceria com Hostgator? Substitua por Hetzner CX22 ($5/mês, datacenter EU) ou DigitalOcean Droplet 4GB ($24/mês). Tudo neste runbook funciona idêntico — só não terá a vantagem de latência BR.


2. Pré-requisitos

  1. Acesso SSH ao VPS (Hostgator entrega via cPanel ou root SSH; preferir SSH-only).
  2. Domínio com DNS gerenciado em Cloudflare (ou outro provider) — ex.: waha.deskcomm.com.br.
  3. Conta Backblaze B2 com bucket deskcomm-waha-backup (R$0,06/GB/mês ≈ $0.005/GB).
  4. Licença ativa WAHA Plus (https://waha.devlike.pro — ~$30/mês).
  5. Uma instalação do CRM já de pé, com WAHA_API_BASE_URL, WAHA_API_KEY, WAHA_WEBHOOK_BASE_URL e WAHA_HMAC_SECRET no .env dela (ainda apontando pra dev — atualizamos no fim).

3. Bootstrap inicial do VPS

3.1 Acessar e endurecer SSH

ssh root@<IP_DO_VPS>

# usuário não-root
adduser deskcomm
usermod -aG sudo deskcomm
mkdir -p /home/deskcomm/.ssh
cp ~/.ssh/authorized_keys /home/deskcomm/.ssh/
chown -R deskcomm:deskcomm /home/deskcomm/.ssh
chmod 700 /home/deskcomm/.ssh && chmod 600 /home/deskcomm/.ssh/authorized_keys

# desabilitar password auth + root login
sed -i 's/#\?PasswordAuthentication.*/PasswordAuthentication no/' /etc/ssh/sshd_config
sed -i 's/#\?PermitRootLogin.*/PermitRootLogin no/' /etc/ssh/sshd_config
systemctl reload sshd

A partir daqui: ssh deskcomm@<IP> + sudo para tudo.

3.2 Instalar dependências

sudo apt-get update && sudo apt-get upgrade -y
sudo apt-get install -y \
  docker.io docker-compose-plugin \
  nginx certbot python3-certbot-nginx \
  ufw fail2ban restic curl jq

sudo usermod -aG docker deskcomm
# logout + login pra grupo aplicar

3.3 Firewall

sudo ufw default deny incoming
sudo ufw default allow outgoing
sudo ufw allow 22/tcp
sudo ufw allow 80/tcp     # certbot challenge
sudo ufw allow 443/tcp
sudo ufw enable

3.4 Fail2ban (SSH + Nginx 401)

sudo tee /etc/fail2ban/jail.d/deskcomm.conf > /dev/null <<'EOF'
[sshd]
enabled = true
maxretry = 3
bantime = 1h

[nginx-http-auth]
enabled = true
maxretry = 5
bantime = 1h
EOF
sudo systemctl restart fail2ban

4. DNS + TLS

No Cloudflare (ou seu DNS provider):

  • waha.deskcomm.com.br → A record → IP do VPS Hostgator
  • Proxy = DNS only (cinza). Cloudflare Proxy (laranja) interfere em SSE/WebSocket que o WAHA usa.
sudo certbot --nginx -d waha.deskcomm.com.br \
  --non-interactive --agree-tos -m ops@deskcomm.com.br

Certbot já injeta SSL no /etc/nginx/sites-available/default.


5. Deploy do WAHA

5.1 Estrutura

sudo mkdir -p /opt/deskcomm-waha
sudo chown deskcomm:deskcomm /opt/deskcomm-waha
cd /opt/deskcomm-waha

Copiar (via scp ou git clone) o docker-compose.yml do repo (raiz do DeskcommCRM). Ajustes obrigatórios pra prod:

services:
  waha:
    image: devlikeapro/waha-plus@sha256:<DIGEST_PINADO>   # NÃO usar :latest
    ports:
      - "127.0.0.1:3000:3000"                             # bind localhost; Nginx termina TLS
    environment:
      WAHA_API_KEY: ${WAHA_API_KEY}                       # plaintext rotacionado
      WHATSAPP_HOOK_URL: ${WAHA_WEBHOOK_BASE_URL}/api/v1/webhooks/waha
      WHATSAPP_HOOK_HMAC: ${WAHA_HMAC_SECRET}
      WAHA_DEFAULT_ENGINE: NOWEB
      WAHA_DASHBOARD_ENABLED: "false"                     # sem dashboard em prod

Pin de digest: rode docker pull devlikeapro/waha-plus:latest && docker inspect ... | jq -r '.[0].RepoDigests[0]' e cole. Cada upgrade vira PR explícito (a gente já comeu uma migração silenciosa de env var nesse repo).

5.2 .env de produção

/opt/deskcomm-waha/.env (chmod 600):

WAHA_API_KEY=<plaintext gerado novo, 64 chars hex>
WAHA_WEBHOOK_BASE_URL=https://<dominio-da-sua-instalacao-do-crm>
WAHA_HMAC_SECRET=<32 bytes random distinto da api key>
chmod 600 /opt/deskcomm-waha/.env

5.3 Subir

cd /opt/deskcomm-waha
docker compose up -d
docker compose logs -f waha       # confere "Nest application successfully started"

Healthcheck:

PLAIN=$(grep WAHA_API_KEY .env | cut -d= -f2-)
curl -s -H "X-Api-Key: $PLAIN" http://127.0.0.1:3000/api/sessions
# → [] (array vazio inicial)

6. Nginx reverse proxy

/etc/nginx/sites-available/waha:

server {
    listen 443 ssl http2;
    server_name waha.deskcomm.com.br;

    ssl_certificate     /etc/letsencrypt/live/waha.deskcomm.com.br/fullchain.pem;
    ssl_certificate_key /etc/letsencrypt/live/waha.deskcomm.com.br/privkey.pem;

    # Egress allowlist — só o servidor onde o CRM roda pode chamar.
    include /etc/nginx/conf.d/crm-egress-allowlist.conf;
    deny all;

    proxy_buffering off;        # SSE / streaming WAHA
    proxy_read_timeout 300s;
    proxy_send_timeout 300s;
    client_max_body_size 50M;   # uploads de mídia

    location / {
        proxy_pass http://127.0.0.1:3000;
        proxy_set_header Host $host;
        proxy_set_header X-Real-IP $remote_addr;
        proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
        proxy_set_header X-Forwarded-Proto $scheme;
    }
}

server {
    listen 80;
    server_name waha.deskcomm.com.br;
    return 301 https://$host$request_uri;
}

/etc/nginx/conf.d/crm-egress-allowlist.conf — o IP público do servidor onde o CRM roda:

echo "allow <IP_DO_SERVIDOR_DO_CRM>;" | sudo tee /etc/nginx/conf.d/crm-egress-allowlist.conf
sudo nginx -t && sudo systemctl reload nginx

É um endereço só, escrito à mão: não há lista para sincronizar nem cron para manter. Quem o muda é você, ao mover o CRM de servidor — quando isso acontecer, reescreva este arquivo e recarregue o Nginx. Para descobrir o IP de saída, rode de dentro do próprio servidor do CRM: curl -s https://ifconfig.me.

sudo ln -s /etc/nginx/sites-available/waha /etc/nginx/sites-enabled/waha
sudo nginx -t && sudo systemctl reload nginx
curl -I https://waha.deskcomm.com.br/api/health    # 401 esperado sem header

7. Backup com restic + Backblaze B2

7.1 Setup

sudo tee /opt/deskcomm-waha/backup.env > /dev/null <<EOF
B2_ACCOUNT_ID=<key id>
B2_ACCOUNT_KEY=<key>
RESTIC_REPOSITORY=b2:deskcomm-waha-backup:/waha
RESTIC_PASSWORD=<senha forte armazenada no 1Password>
EOF
sudo chmod 600 /opt/deskcomm-waha/backup.env

source /opt/deskcomm-waha/backup.env
restic init    # uma vez só

7.2 Cron diário (3am)

/etc/cron.d/waha-backup:

0 3 * * * deskcomm . /opt/deskcomm-waha/backup.env && restic backup /var/lib/docker/volumes/deskcomm-waha_waha-data --tag daily && restic forget --keep-daily 7 --keep-weekly 4 --keep-monthly 6 --prune

7.3 Restore drill (rodar mensal)

# em VPS de teste:
docker compose down
sudo rm -rf /var/lib/docker/volumes/deskcomm-waha_waha-data/*
. /opt/deskcomm-waha/backup.env
restic restore latest --target /
docker compose up -d
# verificar que sessões voltaram sem precisar re-parear

Nunca confie em backup que você não testou restaurando.


8. Monitoramento

8.1 Healthcheck externo

UptimeRobot (free tier, 5min) apontando pra https://waha.deskcomm.com.br/ping (rota pública sem auth). Alerta → email + WhatsApp via webhook do próprio app.

8.2 Watchdog local

sudo tee /usr/local/bin/waha-watchdog.sh > /dev/null <<'EOF'
#!/bin/bash
FAILS=$(cat /tmp/waha-fails 2>/dev/null || echo 0)
if curl -s -f http://127.0.0.1:3000/ping > /dev/null; then
  echo 0 > /tmp/waha-fails
else
  FAILS=$((FAILS+1))
  echo $FAILS > /tmp/waha-fails
  if [ "$FAILS" -ge 3 ]; then
    cd /opt/deskcomm-waha && docker compose restart waha
    curl -s -X POST "https://hooks.sentry.io/..." -d "WAHA restarted após $FAILS falhas"
    echo 0 > /tmp/waha-fails
  fi
fi
EOF
sudo chmod +x /usr/local/bin/waha-watchdog.sh
echo "*/1 * * * * deskcomm /usr/local/bin/waha-watchdog.sh" | sudo tee /etc/cron.d/waha-watchdog

8.3 Log shipping (opcional Fase 2)

Better Stack ou Datadog Agent → docker logs deskcomm-waha. Sem isso, logs ficam só locais e somem em rotação.


9. Apontar o CRM para este WAHA

As variáveis do CRM vivem no .env da instalação dele — não num painel. Edite lá:

WAHA_API_BASE_URL=https://waha.deskcomm.com.br
WAHA_API_KEY=<mesmo plaintext do .env do VPS>
WAHA_WEBHOOK_BASE_URL=https://<dominio-da-sua-instalacao-do-crm>
WAHA_HMAC_SECRET=<mesmo do VPS>

Env var é lida no boot: editar o .env não muda nada até os contêineres serem recriados.

Não tente descobrir quais são eles com um grep env_file: esse grep não enxerga o serviço waha do compose, que recebe WAHA_WEBHOOK_BASE_URL e WAHA_HMAC_SECRET por interpolação (${...} dentro do bloco environment:), sem env_file nenhum — a resposta sai incompleta com cara de completa.

Numa instalação real, quem recria tudo é bash hostgator-setup-kit/update.sh: ele roda o up -d sem nomear serviço. Subindo à mão, o comando está em deploy.md — numa VPS com proxy reverso próprio o up -d precisa dos dois arquivos de compose, senão o domínio passa a responder 404.


10. Checklist final pré-go-live

  • Digest pinado em docker-compose.yml (zero :latest)
  • WAHA_DASHBOARD_ENABLED=false
  • UFW ativo, só 22/80/443
  • SSH password disabled, root login disabled
  • fail2ban com jails de SSH + nginx-http-auth
  • Egress allowlist Nginx com o IP do servidor do CRM
  • TLS válido (testar https://www.ssllabs.com/ssltest/ ≥ A)
  • Backup restic rodando + restore drill executado uma vez
  • UptimeRobot configurado
  • Watchdog cron de 1min ativo
  • Sentry release tagging do app capturando erros do lib/waha/client
  • .env do CRM apontando pro domínio público do WAHA
  • Webhook entrante funcionando (mensagem de teste WhatsApp → aparece na Inbox)
  • API key WAHA documentada em 1Password com data de rotação +90d

11. Diferenças operacionais Hostgator vs cloud-native (Hetzner/DO)

Aspecto Hostgator Hetzner / DO
Volume snapshots nativos ❌ não tem ✅ tem
Latência pro Meta BR ✅ <30ms (SP) ⚠️ 150-200ms (EU) ou 80ms (NYC)
Custo (mín. 2 vCPU/4GB) R$140/mês (~$28) $5-10/mês
Painel cPanel/WHM (web GUI) Console + API
Provisionamento via API ❌ limitado ✅ Terraform-friendly
Suporte 24x7 PT-BR ✅ incluso ⚠️ EN-only, ticket lento

Conclusão: Hostgator paga prêmio pela parceria + suporte BR + datacenter SP. Pra MVP/scale-out até ~50 tenants é OK; acima disso, considerar diversificação (instância secundária Hetzner como DR cross-region).


12. Troubleshooting rápido

Sintoma Diagnóstico Fix
App recebe 401 do WAHA Env var WAHA_API_KEY desalinhada (.env do CRM vs .env do WAHA) Confirmar plaintext idêntico nos dois lados
WAHA cria session mas não inicia start: true ignorado em algumas versões Chamar POST /api/sessions/:name/start explicitamente
Webhook não chega Nginx allowlist bloqueando, ou Cloudflare Proxy ON tail -f /var/log/nginx/access.log + desligar proxy CF
QR expira sempre RTT alto, ou clock drift no VPS timedatectl set-ntp true, conferir RTT pro web.whatsapp.com
Container OOMKilled Sessões demais pro plano Upgrade Hostgator pra plano superior, ou particionar tenants em VPS secundário
Session WORKING mas mensagens não saem Daily limit atingido, ou janela horária Conferir channel_sessions.daily_message_limit + lib/waha/throttle

13. Histórico de decisão

  • 2026-09-17 — O passo 9 deixou de mandar editar env var em painel de hospedagem: o CRM é self-host, e as variáveis dele vivem no .env da instalação. A allowlist de egress do Nginx passou a liberar o IP do servidor onde o CRM roda, no lugar dos ranges da plataforma — que eram atualizados por cron diário e sobrescreveriam qualquer ajuste manual.
  • 2026-05-04 — Trocamos referências Hetzner→Hostgator nos docs por parceria comercial existente. Custo subiu (~$5 → ~$28) mas latência BR melhora pareamento e suporte ticketing fica em PT-BR. Hetzner mantido como plano B documentado em §1.