mirror of
https://github.com/melgarafael/DeskcommCRM.git
synced 2026-10-02 01:28:34 +08:00
O diagnóstico da issue #139 está certo: numa VPS Hostinger o Traefik da hospedagem roda com `--network host`, e a detecção gravava TRAEFIK_NETWORK="host" — uma rede de driver host, inutilizável. A correção anterior, porém, não chegava a rodar, e onde rodava não subia. 1. Ramo inalcançável. `traefik_container` só era atribuído dentro do `case` de `decide_proxy`, que só devolve "traefik" quando alguém casa `:80->`/`:443->` na coluna Ports do `docker ps`. Medido no docker 28.3.2: contêiner em `--network host` sai com Ports=[] mesmo subido com `-p 80:80` (o daemon avisa "Published ports are discarded when using host network mode"); controle positivo, o mesmo nginx numa bridge com `-p 8080:80` sai com "0.0.0.0:8080->80/tcp". Ou seja: um Traefik em modo host nunca virava `traefik_container`. Na VPS real o instalador morria no painel "porta 80 já ocupada", que manda pôr REVERSE_PROXY=traefik no .env — caminho que pulava o `case` e morria adiante em "Não consegui descobrir a rede do seu Traefik". Agora há uma segunda varredura (`docker ps --filter network=host`) e a atribuição de `traefik_container` saiu de dentro do `case`. As duas falham FECHADO no plural: com dois Traefiks ninguém é eleito. 2. A rede escolhida não existia. O ramo gravava `<projeto>_internal`, criada pelo `dc up -d` lá na frente — e o compose declara TRAEFIK_NETWORK como rede EXTERNA, checada ANTES de criar qualquer coisa. Medido com o compose v2.38.2 contra o próprio docker-compose.traefik.yml: `up -d` morre em "network crmreal_internal declared as external, but could not be found". Só instalava quem já tivesse instalado antes. Agora o instalador usa uma bridge dedicada `<projeto>_proxy` e a CRIA antes de precisar dela — medido: contêiner em `--network host` alcança por IP um contêiner numa bridge separada (HTTP 200), então qualquer bridge serve, e uma separada evita colidir com a rede que o compose cria e não expõe o redis sem senha da `internal`. O guard segue recusando driver != bridge e rede inexistente que não seja a nossa, e a mensagem parou de mandar quem está em modo host procurar "a rede do seu Traefik", que não existe. 3. `basename "$PWD"` cru ignorava `nome_do_projeto_compose()`, que existe no mesmo arquivo para replicar o NormalizeProjectName do compose. Numa pasta `CRM.Host_Teste` o instalador criaria `CRM.Host_Teste_proxy` e o compose procuraria `crmhost_teste_proxy`. Os comentários que a correção anterior inverteu foram reescritos com os DOIS cenários lado a lado, sem apagar a medição antiga do Traefik em bridge própria (HTTP 000 x HTTP 200). Em docker-compose.traefik.yml some a menção a TRAEFIK_DOCKER_NETWORK — variável que não existe em lugar nenhum do repo. Testes: os fixtures do kit só tinham proxy com porta publicada, então o cenário da issue nunca era exercitado. Entram 15 casos de unidade e um teste de integração que roda o install.sh inteiro contra um `docker` dublê que imita a Hostinger. Provado reprovando: contra o código anterior o teste de integração cai em "As portas 80 e 443 já estão ocupadas por um programa do próprio servidor" — a mensagem da VPS real. Cada asserção foi sabotada em separado e mordeu a sua. O dublê passou a incluir `crontab`: o teste vai até o fim do instalador, e sem isso a suíte agendava cron na máquina de quem a roda, apontando para o diretório temporário que ela mesma apaga. Refs #139 Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01HstH7nNmrTvCtZsasppeHj
144 lines
7.9 KiB
YAML
144 lines
7.9 KiB
YAML
# DeskcommCRM — override para VPS que JÁ TEM um proxy reverso Traefik.
|
|
#
|
|
# docker compose -f docker-compose.prod.yml -f docker-compose.traefik.yml up -d
|
|
#
|
|
# QUANDO USAR
|
|
# -----------
|
|
# O stack padrão sobe um Caddy que publica as portas 80 e 443. Isso é o certo
|
|
# num VPS "cru". Mas várias hospedagens entregam a VPS com um Traefik próprio
|
|
# já ocupando essas portas — Hostinger, Coolify, Dokploy, CapRover e afins. É
|
|
# esse Traefik que dá HTTPS automático a tudo que o painel instala; desligá-lo
|
|
# quebraria as automações da hospedagem, presentes e futuras.
|
|
#
|
|
# Dois processos não podem ouvir a mesma porta, então o `up -d` padrão falha no
|
|
# bind e a instalação morre no meio. Com este override:
|
|
#
|
|
# 1. o serviço `caddy` ganha um profile que nunca é ativado — o compose
|
|
# simplesmente não o cria (o Traefik da hospedagem faz o papel dele);
|
|
# 2. o serviço `app` ganha labels do Traefik que reproduzem, uma a uma, as
|
|
# regras do Caddyfile.
|
|
#
|
|
# O comportamento padrão (sem este arquivo) fica EXATAMENTE como antes: quem
|
|
# não passa o `-f docker-compose.traefik.yml` não é afetado por nada aqui.
|
|
#
|
|
# EQUIVALÊNCIA COM O Caddyfile
|
|
# ----------------------------
|
|
# - `reverse_proxy app:3000` → router `deskcomm` + loadbalancer.server.port
|
|
# - `encode gzip` → middleware `deskcomm-compress`
|
|
# - `respond @waha_global 403`→ router `deskcomm-waha-block` (ver abaixo)
|
|
#
|
|
# Os timeouts longos que o Caddyfile dá a /api/internal/agents/run* NÃO precisam
|
|
# de equivalente: no Traefik o writeTimeout e o responseHeaderTimeout já são
|
|
# ilimitados por padrão, então uma resposta de 5 minutos passa inteira.
|
|
#
|
|
# PRÉ-REQUISITOS NO TRAEFIK DA HOSPEDAGEM
|
|
# ---------------------------------------
|
|
# Espera-se um entrypoint `websecure` (443) e um certresolver `letsencrypt` —
|
|
# os nomes padrão dessas instalações. Se a sua usa outros nomes, ajuste
|
|
# TRAEFIK_ENTRYPOINT e TRAEFIK_CERTRESOLVER no .env.
|
|
#
|
|
# A REDE onde o Traefik encontra o app é TRAEFIK_NETWORK, e ela varia por
|
|
# instalação. O install.sh a descobre e grava no .env — são dois cenários com
|
|
# respostas OPOSTAS, ambos medidos (o porquê de cada um está na função
|
|
# `rede_do_traefik`, em hostgator-setup-kit/install.sh):
|
|
#
|
|
# Traefik numa bridge PRÓPRIA (Coolify, Dokploy) → a rede DELE, que já existe.
|
|
# Traefik em `--network host` (Hostinger) → uma bridge "<projeto>_proxy"
|
|
# que o install.sh CRIA. Em modo host o proxy não está em rede nenhuma do
|
|
# Docker, e alcança qualquer bridge pela stack do host.
|
|
#
|
|
# O default abaixo ("traefik") cobre a instalação manual mais comum.
|
|
|
|
services:
|
|
app:
|
|
# Só o `app` entra na rede do proxy. O inverso — juntar o Traefik à rede
|
|
# `internal` — colocaria um contêiner que serve OUTROS tenants da VPS dentro
|
|
# de uma rede onde o redis roda sem senha (docker-compose.prod.yml: `redis-server
|
|
# --save '' --appendonly no`, sem requirepass). waha, redis e srh ficam de fora.
|
|
networks:
|
|
- internal
|
|
- proxy
|
|
labels:
|
|
traefik.enable: "true"
|
|
# Este label diz ao Traefik em qual rede pegar o IP do contêiner. Com o
|
|
# Traefik numa bridge própria, é a rede do PROXY — apontando para a
|
|
# `internal` ele mira um IP que não alcança e a requisição pendura até dar
|
|
# timeout, mesmo com o contêiner já conectado nas duas redes. Medido com
|
|
# Traefik v3.3 real:
|
|
# app só na internal, label=internal -> HTTP 000 (timeout)
|
|
# app nas duas redes, label=internal -> HTTP 000 (ainda o IP errado)
|
|
# app nas duas redes, label=proxy -> HTTP 200
|
|
# Com o Traefik em modo host a conclusão não se inverte, ela se generaliza:
|
|
# continua sendo a rede `proxy` — só que lá ela é a bridge que o install.sh
|
|
# cria para este projeto, porque um proxy em modo host não tem rede própria
|
|
# (e alcança qualquer bridge pela stack do host).
|
|
traefik.docker.network: "${TRAEFIK_NETWORK:-traefik}"
|
|
|
|
# Rota principal — equivale ao `reverse_proxy app:3000` do Caddyfile.
|
|
traefik.http.routers.deskcomm.rule: "Host(`${DOMAIN}`)"
|
|
traefik.http.routers.deskcomm.entrypoints: "${TRAEFIK_ENTRYPOINT:-websecure}"
|
|
traefik.http.routers.deskcomm.tls.certresolver: "${TRAEFIK_CERTRESOLVER:-letsencrypt}"
|
|
traefik.http.routers.deskcomm.middlewares: "deskcomm-compress"
|
|
traefik.http.services.deskcomm.loadbalancer.server.port: "3000"
|
|
|
|
# `encode gzip` do Caddyfile.
|
|
traefik.http.middlewares.deskcomm-compress.compress: "true"
|
|
|
|
# `respond @waha_global 403` do Caddyfile. O webhook GLOBAL do WAHA não
|
|
# pode ser alcançável da internet: quem soubesse o endereço injetaria
|
|
# mensagem falsa no CRM e faria o WhatsApp do dono responder a um número
|
|
# arbitrário. Aqui dentro o WAHA fala com o app pela rede do Docker, então
|
|
# esta rota nunca precisou ser pública.
|
|
#
|
|
# O Traefik não tem um "responda 403" direto. O equivalente é um
|
|
# ipallowlist cuja única faixa permitida é 192.0.2.1/32 — TEST-NET-1
|
|
# (RFC 5737), um endereço reservado para documentação que nenhum cliente
|
|
# real tem. Toda requisição àquele caminho cai fora da lista e recebe 403,
|
|
# que é exatamente o comportamento desejado. A prioridade alta é o que faz
|
|
# esta rota vencer a principal, que casaria o mesmo Host.
|
|
traefik.http.routers.deskcomm-waha-block.rule: "Host(`${DOMAIN}`) && Path(`/api/v1/webhooks/waha`)"
|
|
traefik.http.routers.deskcomm-waha-block.entrypoints: "${TRAEFIK_ENTRYPOINT:-websecure}"
|
|
traefik.http.routers.deskcomm-waha-block.tls.certresolver: "${TRAEFIK_CERTRESOLVER:-letsencrypt}"
|
|
traefik.http.routers.deskcomm-waha-block.priority: "1000"
|
|
traefik.http.routers.deskcomm-waha-block.service: "deskcomm"
|
|
traefik.http.routers.deskcomm-waha-block.middlewares: "deskcomm-deny"
|
|
traefik.http.middlewares.deskcomm-deny.ipallowlist.sourcerange: "192.0.2.1/32"
|
|
|
|
# HTTP -> HTTPS. O Caddy fazia isto automaticamente; no Traefik precisa ser
|
|
# dito. Sem este router, quem digitar http:// não recebe resposta nenhuma.
|
|
traefik.http.routers.deskcomm-http.rule: "Host(`${DOMAIN}`)"
|
|
traefik.http.routers.deskcomm-http.entrypoints: "${TRAEFIK_ENTRYPOINT_HTTP:-web}"
|
|
traefik.http.routers.deskcomm-http.middlewares: "deskcomm-https"
|
|
traefik.http.routers.deskcomm-http.service: "deskcomm"
|
|
traefik.http.middlewares.deskcomm-https.redirectscheme.scheme: "https"
|
|
traefik.http.middlewares.deskcomm-https.redirectscheme.permanent: "true"
|
|
|
|
# O Caddy não é removido do compose (isso exigiria editar o arquivo base e
|
|
# quebraria quem usa o padrão) — ele só é posto num profile que nunca é
|
|
# ligado. Sem profile ativo, o compose não cria o contêiner e as portas 80/443
|
|
# seguem com o Traefik da hospedagem.
|
|
#
|
|
# ATENÇÃO a um detalhe do Compose: nomear o serviço explicitamente
|
|
# (`docker compose up -d caddy`) ATIVA o profile dele automaticamente e o
|
|
# contêiner sobe assim mesmo, indo bater de frente com o Traefik. Por isso o
|
|
# update.sh checa REVERSE_PROXY antes de recriar o proxy.
|
|
caddy:
|
|
profiles: ["caddy-nao-usado-com-proxy-externo"]
|
|
|
|
# `external: true` = quem cria esta rede não é o compose. Nos dois cenários ela
|
|
# já existe quando o `up -d` roda: ou é a rede do proxy da hospedagem (coolify,
|
|
# dokploy-network, traefik...), ou é a bridge que o install.sh criou para este
|
|
# projeto quando o Traefik está em modo host.
|
|
#
|
|
# Rede externa AUSENTE é recusada ANTES de o compose criar qualquer coisa —
|
|
# medido com o compose v2.38.2 contra este mesmo arquivo: "network X declared as
|
|
# external, but could not be found", sem dizer de onde saiu o nome. Vale
|
|
# inclusive quando X é a `<projeto>_internal` que o próprio compose criaria neste
|
|
# mesmo `up` (a checagem de rede externa vem antes de qualquer criação); por isso
|
|
# a bridge do modo host é uma rede separada, e o install.sh a cria antes de subir
|
|
# a stack.
|
|
networks:
|
|
proxy:
|
|
external: true
|
|
name: "${TRAEFIK_NETWORK:-traefik}"
|