O conserto do PR #140 estava certo; o portão em volta dele não. Cinco pontos. 1. A suíte travava na forma que ela mesma documenta. O dublê de `crontab` era `cat >/dev/null` INCONDICIONAL. O kit faz `crontab -l | grep -qF ...`: o `cat` ficava lendo o stdin herdado — num terminal, o tty, que nunca dá EOF — e o `grep` esperava um fim que não vinha. Medido no HEAD anterior, num PTY de verdade (`script`/`openpty`): morta com SIGKILL aos 90s; sabotando de volta só o dublê no código já corrigido, morta aos 240s. Com o dublê novo a suíte fecha em 18,4s, exit code 0, 133 OK / 0 FAIL — no PTY e com stdin fechado. Um dublê só consome stdin onde o comando real consumiria (`crontab -` e `crontab <arquivo>`), nunca em `crontab -l`. O dublê também grava ATOMICAMENTE (temporário + mv), como o crontab de verdade. Sem isso ele mentia num ponto que importa: nos dois lados de `crontab -l | ... | crontab -` rodando ao mesmo tempo, um `cat > arquivo` truncava o arquivo que o leitor ainda lia, o merge recebia um crontab vazio e cada gravação apagava a anterior. Medido: 1 linha capturada antes, 3 depois. 2. Os testes escreveram no crontab REAL. Agora o dublê grava num sandbox e a suíte compara `crontab -l` antes/depois, com controle positivo: se o sandbox não recebeu nenhuma linha do kit, o caso reprova por INCONCLUSIVO em vez de passar por não ter medido nada. Os dois braços foram provados mordendo, sem tocar no crontab da máquina: com um `crontab` de fachada no PATH fazendo o papel do real, remover o dublê do fixture derruba pela vacuidade (e vazam as 4 linhas órfãs, a do Bearer inclusive); um dublê que grava no sandbox E vaza derruba pela comparação, com o diff na tela. 3. O guard de rede só existia no install.sh. `nome_do_projeto_compose` e a checagem/criação da bridge saíram para `_common.sh` (`garantir_rede_do_proxy`) e agora valem nos dois caminhos: o `dc up -d` do update.sh corre o mesmo risco — a bridge some num `docker network prune` ou no `down -v` que o próprio kit ensina — e quem roda o update.sh é o agent.sh a cada 5 minutos, sem ninguém lendo a tela. Entra um teste de integração que roda o update.sh inteiro contra dublês e cobra a ORDEM (criar depois do `up -d` não serviria de nada). 4. A eleição do Traefik em modo host era frouxa. `docker ps --filter network=host` elegia qualquer Traefik único como dono de 80/443 sem nenhuma prova: em modo host a coluna Ports sai vazia para TODO mundo, então o que aquela varredura responde é "há um único Traefik em modo host aqui", não "é ele quem está com as portas". Com um nginx nativo segurando as portas, o CRM subia atrás de um proxy que não atende — "instalou com sucesso" e site mudo. Agora a eleição vem MARCADA e o desfecho é fechado na ação, aberto na informação: interativo confirma dizendo o que achou; `--yes` recusa ensinando a saída (declarar REVERSE_PROXY=traefik no .env, que segue valendo). Quem é eleito pela coluna Ports não perde nada — provado pelo cenário Coolify novo, e pelas duas sabotagens (sempre `segue` e sempre `pergunta`) mordendo cada uma o seu lado. 5. `.env.hostgator.example` prometia `<pasta>_proxy` enquanto o código cria com o nome normalizado. Numa pasta `CRM.Host_Teste` o doc dizia `CRM.Host_Teste_proxy` e o instalador criava `crmhost_teste_proxy`. Era o defeito que o commit anterior corrigiu no código sobrevivendo na prosa. README e CLAUDE.md do kit também diziam "detecta isso sozinho" sem ressalva — agora não é mais verdade no modo host, e o texto diz isso. Achado ao fechar o item 3: a extração levou junto o `TRAEFIK_NETWORK="${…:-traefik}"` que estava solto no fluxo, para dentro de uma função que retorna cedo em modo caddy. O `.env` é escrito num `{ … } > .env` e `envq TRAEFIK_NETWORK` é incondicional, então a VPS LIMPA — a instalação mais comum de todas — morria em `set -u` e deixava o .env pela metade, parando exatamente na linha seguinte a REVERSE_PROXY. Com os quatro cenários de proxy externo verdes. Não havia teste de integração nenhum no caminho do Caddy; agora há, e ele reprova contra o refactor e passa contra o HEAD anterior — o controle que separa "regressão minha" de "defeito que já existia". A asserção olha a última linha do bloco, não a mensagem de erro: `set -u` fala a língua do shell de quem roda, e a primeira versão do teste passou batido justamente por procurar "unbound variable" num shell em pt-BR. Portões: `bash -n` nos 12 .sh do kit; a suíte num PTY de verdade (18,4s, exit 0) e com stdin fechado (18s, exit 0), 133 OK / 0 FAIL nos dois; e o sha256 do crontab da máquina igual no começo e no fim de tudo. Refs #139 Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01HstH7nNmrTvCtZsasppeHj
7.9 KiB
DeskcommCRM — Kit de Instalação (HostGator)
Este kit sobe o DeskcommCRM no seu servidor VPS da HostGator. Você tem dois caminhos:
Ainda nem tem servidor? Comece por
comecar.sh— ele roda no seu computador, antes de existir VPS, e responde a pergunta que trava todo mundo no início: o que eu preciso contratar? Ele nomeia o plano (VPS Turing, 2 vCPU / 4 GB — o Cartesius não dá conta do WhatsApp), abre a página se você quiser, e devolve o comando exato do seu caso. Depois que a VPS existir, o caminho é oinstall.shdaqui de baixo.bash comecar.sh
Outra hospedagem? O kit é feito para a HostGator (é a parceria do projeto e o caminho testado de ponta a ponta), mas roda em qualquer VPS com Docker. Se a sua já vem com um proxy reverso próprio ocupando as portas 80/443 — caso de Hostinger, Coolify, Dokploy e CapRover —, o instalador detecta isso sozinho e publica o CRM através dele, em vez de tentar subir um Caddy que não caberia. Ver VPS que já vem com proxy próprio.
🤖 Caminho fácil: deixe o Claude Code fazer
- Contrate um VPS na HostGator e acesse-o por SSH.
- Jogue esta pasta (ou o
.zip) no chat do Claude Code rodando dentro do VPS. - Diga: "instala o DeskcommCRM pra mim". Ele lê o
CLAUDE.mde conduz tudo — cria o banco, gera as senhas, sobe o CRM e te ajuda a conectar o WhatsApp.
⚙️ Caminho manual: um comando
Dentro do VPS:
bash install.sh
VPS sem Docker? O instalador resolve. Se não encontrar o Docker, ele pergunta antes e instala pelo
get.docker.com— o instalador oficial da Docker, que roda como root, como manda a documentação deles. Com--yesele segue sem perguntar, que é o contrato desse modo. Se preferir instalar por conta própria, respondane rodecurl -fsSL https://get.docker.com | shantes.
O instalador pergunta o que precisa (domínio, chaves do Supabase e da Anthropic, e-mail/senha do admin), gera o resto e sobe tudo.
Modo não-interativo: copie
.env.hostgator.example(do repositório) para.env, preencha, e rodebash install.sh --yes.
Criar o Supabase automaticamente (opcional)
Criar o projeto no navegador e copiar as 4 credenciais é o passo mais demorado da instalação — e o mais fácil de errar (copiar a Direct connection, que é IPv6-only e não conecta de um VPS IPv4, é a armadilha mais comum). Dá para pular tudo isso:
export SUPABASE_ACCESS_TOKEN=sbp_... # supabase.com/dashboard/account/tokens
bash install.sh # cria o projeto e segue a instalação
O install.sh chama o provisionamento sozinho quando encontra o token e as
credenciais ainda vazias — as 4 variáveis entram no fluxo sem copiar e colar.
Para criar só o projeto, sem instalar, o script também roda sozinho:
bash supabase-provision.sh "Nome do Projeto" sa-east-1 >> .env
O script cria o projeto, espera o banco ficar ACTIVE_HEALTHY (projeto novo não
nasce pronto), busca as chaves e descobre o host do pooler testando conexão real em
vez de adivinhar. Imprime as 4 linhas prontas para colar no .env.
⚠️ O token é uma chave mestra — dá acesso a todos os projetos da conta. Ele é lido do ambiente e nunca gravado em disco. Instalando para terceiros, use o token DO CLIENTE, ou rode o script na sua máquina e leve só as 4 credenciais para o servidor dele.
⚠️ Plano grátis: 2 projetos por usuário, contados em todas as organizações onde ele é Owner/Admin. Não dá para hospedar vários clientes numa conta só.
O que você precisa antes
| Item | Onde conseguir |
|---|---|
| VPS (Docker) | HostGator — VPS com Docker (n8n/OpenClaw/GatorClaw). Outras hospedagens com Docker também servem — se a sua já tiver proxy próprio nas portas 80/443, veja aqui |
| Domínio | Registro de domínio (aponte um A-record pro IP do VPS) |
| Banco de dados | Conta grátis no supabase.com (3 chaves + connection string) |
| IA | Chave da Anthropic |
| Seu número — conectado por QR code no onboarding |
Requisitos do VPS
- 4 GB RAM recomendados. A imagem é pré-buildada, então o servidor não compila nada e a
stack SOBE com 2 GB — mas operar é outra coisa: são 7 contêineres, e o WAHA consome
~150 MB por sessão de WhatsApp além de ~300 MB de overhead do Node. Com 2 GB você roda
no limite e vai precisar de swap. Ver
docs/runbooks/waha-hostgator.md. - Portas 80 e 443 abertas (
ufw allow 80,443,22/tcp). - Docker + Docker Compose v2 — o
install.shinstala o Docker sozinho se faltar (ver acima).
VPS que já vem com proxy próprio (Hostinger, Coolify, Dokploy…)
Algumas hospedagens entregam a VPS com um Traefik já ocupando as portas 80/443 — é ele
que dá HTTPS automático ao que o painel instala. O Caddy do kit quer as mesmas portas e não
sobe. O instalador detecta isso sozinho e grava REVERSE_PROXY=traefik no .env; a
partir daí os scripts do kit incluem o override que desliga o Caddy e publica o app pelo
Traefik da hospedagem. Rodando compose na mão nessas instalações, use os dois arquivos:
docker compose -f docker-compose.prod.yml -f docker-compose.traefik.yml up -d
Não desligue o Traefik da hospedagem para liberar as portas — isso quebra as automações do
painel dela. Se o seu Traefik usa nomes diferentes de websecure/letsencrypt, ajuste
TRAEFIK_ENTRYPOINT e TRAEFIK_CERTRESOLVER no .env.
Há um caso em que o instalador pergunta em vez de decidir: quando o Traefik da
hospedagem roda em --network host (a Hostinger faz assim), o Docker não mostra porta
publicada em contêiner nenhum, e então não dá para provar que é ele quem atende o seu
domínio — poderia ser um nginx instalado direto no servidor. Como publicar o CRM atrás do
proxy errado deixa o site no ar sem responder, o instalador mostra o que encontrou e pede
confirmação. Em bash install.sh --yes não há a quem perguntar: ele para e pede que você
declare REVERSE_PROXY=traefik no .env — aí a escolha é sua e ele segue sem perguntar.
Scripts do kit
| Script | Função |
|---|---|
install.sh |
Instala tudo (idempotente) |
update.sh |
Atualiza pra versão nova |
backup.sh |
Backup do banco + sessões WhatsApp |
restore.sh |
Restaura um backup |
reset-password.sh |
Redefine senha de um usuário |
reset-mfa.sh |
Remove o MFA de um usuário travado |
healthcheck.sh |
Diagnóstico dos serviços |
Automações e webhooks
O install.sh (e o update.sh, a cada atualização) já ativa sozinho um cron que roda todo minuto e "puxa" a fila de eventos pendentes (/api/v1/cron/event-log-drain) — é isso que faz uma automação disparar de verdade no seu servidor (ex.: enviar uma mensagem de WhatsApp quando um pedido muda de status). Sem esse cron, as automações ficam paradas na fila e nunca rodam — é um requisito, não um extra.
Rodar de novo o install.sh/update.sh não duplica a linha do cron (ele mesmo substitui a antiga). Na 1ª vez que o cron é ativado numa instalação que já existia há um tempo, o script também limpa eventos pendentes com mais de 7 dias (marcando como concluídos, sem apagar histórico) — assim o primeiro drain não sai disparando efeitos atrasados de semanas atrás.
Pra testar na mão, rode no próprio VPS (usa o INTERNAL_SECRET do seu .env):
source .env && curl -s -H "Authorization: Bearer ${INTERNAL_SECRET}" "${NEXT_PUBLIC_APP_URL}/api/v1/cron/event-log-drain"
Resposta esperada: {"data":{"scanned":N,...}} (N pode ser 0 se não houver eventos na fila — o importante é receber esse formato, não um erro de autenticação ou de conexão).
Suporte
Problemas comuns e como resolver estão no CLAUDE.md (seção "Quando der problema").