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 commitf65d04667(antes) e vazio ema8c9e1ad3,7168961ade 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>
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
- Acesso SSH ao VPS (Hostgator entrega via cPanel ou root SSH; preferir SSH-only).
- Domínio com DNS gerenciado em Cloudflare (ou outro provider) — ex.:
waha.deskcomm.com.br. - Conta Backblaze B2 com bucket
deskcomm-waha-backup(R$0,06/GB/mês ≈ $0.005/GB). - Licença ativa WAHA Plus (
https://waha.devlike.pro— ~$30/mês). - Uma instalação do CRM já de pé, com
WAHA_API_BASE_URL,WAHA_API_KEY,WAHA_WEBHOOK_BASE_URLeWAHA_HMAC_SECRETno.envdela (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
resticrodando + restore drill executado uma vez - UptimeRobot configurado
- Watchdog cron de 1min ativo
- Sentry release tagging do app capturando erros do
lib/waha/client .envdo 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
.envda 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.