Pular para conteúdo

Implantação — integrador no Coolify (um recurso por cliente)

Status: Aprovado · Responsável: Gustavo Madruga · Atualizado em: 2026-09-15

Como uma instância do integrador fica de pé no Coolify, como se troca o jar pelo native, como se confere que subiu e como se desliga. O integrador é multi-tenant por implantação: cada cliente tem o seu recurso Coolify, o seu subdomínio e o seu banco — não há um servidor único servindo vários.

O desenho — componentes, fluxo de dados, modelo de dados, autenticação — é do livro: Documentação Completa. A lista completa de propriedades e ENV é da configuração. Por que native e jar convivem e a ordem da troca estão na decisão 0028. Aqui fica só o que é de operar.

Quando usar

  • Ao provisionar um cliente novo.
  • Ao trocar o jar pelo native numa instância (§ Troca jar → native).
  • Ao atualizar a instância de um cliente, ou ao reverter uma versão.
  • Ao precisar desligar o webhook de saída sem derrubar o app (§ Reversão).
  • Ao instalar um integrador-client no cliente (§ Ao instalar um integrador-client).

Implantação de uma integração inteira (com int-pied, PowerSync e clientes) tem runbook próprio: Maxsul (PIED).

Pré-requisitos

  • Postgres do cliente provisionado, com um usuário que tenha DDL no schema (o Flyway roda no start do app).
  • DNS do subdomínio do cliente (int.<cli>.xadm.biz) apontando para o Coolify; na troca, também o do jar (int-jar.<cli>.xadm.biz).
  • Segredos do § Variáveis à mão (cofre da X-Adm).
  • Imagem publicada: o pipeline.yml builda e publica fonte.xadm.biz/xadm/integrador-server com as tags móveis native-amd64 e jar-amd64; o Coolify puxa, não builda. A primeira imagem vem de uma release ou de um workflow_dispatch.

Topologia de operação

%% caption: O que se implanta, por cliente (durante a troca, os dois recursos no ar)
flowchart TB
    PROXY["Proxy do Coolify<br/>(TLS automático)"]
    NAT["integrador-server-&lt;cli&gt;-native-pull<br/>imagem native-amd64<br/>porta interna 8080"]
    JAR["integrador-server-&lt;cli&gt;-jar-pull<br/>imagem jar-amd64<br/>porta interna 8080"]
    DB[("Postgres do cliente<br/>(db_&lt;cliente&gt;)")]

    PROXY -->|"int.&lt;cli&gt;.xadm.biz"| NAT
    PROXY -->|"int.&lt;cli&gt;.xadm.biz até provar o native,<br/>depois int-jar.&lt;cli&gt;.xadm.biz"| JAR
    NAT --> DB
    JAR --> DB
Peça Onde Nota
Integrador native recurso integrador-server-<cli>-native-pull, Build Pack Docker Image o alvo padrão; porta interna 8080
Integrador jar recurso integrador-server-<cli>-jar-pull, Build Pack Docker Image fica no ar ao lado do native até a sexta instância trocar, no int.<cli> até a instância provar o native e no int-jar.<cli> depois; em seguida, parado como fallback
Proxy Traefik do Coolify termina TLS; o contrato com apps e proxies é o host (DNS), não a porta
Postgres banco do cliente o app é dono do schema e roda o Flyway no start

Subdomínio: um por cliente, int.<cli>.xadm.biz (ex.: a instância da Maxsul é int.maxsul.xadm.biz). O FQDN de cada cliente já implantado é o que está no recurso Coolify — confira lá, não presuma pelo padrão.

Como o deploy acha o recurso: o reconcile do control-plane do central tira a instância do primeiro FQDN do recurso, na forma int.<cli>.xadm.biz e, no jar da troca, int-jar.<cli>.xadm.biz (Coolify da casa). O native leva o int.<cli> como primeiro domínio. O jar leva o int.<cli> até a instância provar o native e o int-jar.<cli> depois — só quando o reconcile no ar aceita esse host: o INSTANCE_FROM_FQDN do DeployReconcileService do central-backend casa int-jar. Um recurso com domínio que o reconcile não lê fica sem instância e colide no mapa de deploy com os de outros clientes.

Variáveis de ambiente

A lista completa, com defaults e o mapeamento propriedade Micronaut ↔ ENV, é da configuração — não se copia aqui. O mínimo para subir:

Variável Nota
CLIENTE identificador do tenant; vira a tag cliente de todo evento no GlitchTip
DATASOURCES_DEFAULT_URL JDBC do banco do cliente
DATASOURCES_DEFAULT_USERNAME / DATASOURCES_DEFAULT_PASSWORD segredo — usuário com DDL no schema
INTEGRADOR_API_TOKEN segredo, obrigatório — Bearer estático de /api/**. Nome por destino; o antigo API_BEARER_TOKEN não é lido (sem fallback). Ausente ou vazia, o app não sobe: o BearerTokenGuard da xadm-seguranca recusa o boot — ver segurança
SENTRY_DSN obrigatório — DSN do GlitchTip (client-grade, fonte: docs/app.json). O nome GLITCHTIP_DSN não é lido: sem SENTRY_DSN o Sentry não inicializa e erro nenhum é reportado, só um WARN no boot (observabilidade)
SENTRY_ENVIRONMENT staging ou production

Sem MICRONAUT_ENVIRONMENTS: produção = application.yml + ENV. Opcionais por cliente: PUSH_* (FCM), AUTH_* (login Google — segurança), WEBSTORM_* (poke de saída ao tradutor, só onde há tradutor) e CENTRAL_API_TOKEN + CLIENTE_ID (heartbeat, só onde há integrador-client — § Ao instalar um integrador-client). Segredos vão em secret do Coolify, nunca no repo. Os recursos native e jar de uma instância têm as mesmas variáveis.

Pares que têm de casar

Ponta A Ponta B Sintoma se divergirem
INTEGRADOR_API_TOKEN (aqui; sem fallback — o antigo API_BEARER_TOKEN é ignorado) INTEGRADOR_API_TOKEN do tradutor (adapter CSV) / Bearer do cliente X-Adm / int-pied / uploadToken dos clientes PowerSync 401 no PUT /api/v1/xadm ou no write-back; nada entra e o remetente acumula erro
WEBSTORM_API_TOKEN (aqui; sem fallback — ${WEBSTORM_API_TOKEN:} também não é aninhado) WEBSTORM_API_TOKEN (tradutor) o poke responde 401; o sweep do tradutor mascara — envia atrasado e ninguém nota
CENTRAL_API_TOKEN (aqui) CENTRAL_API_TOKEN dos recursos do central-backend o heartbeat responde 502 heartbeat_recusado + ERROR (invalid_token); a instalação some do painel e, depois de 6h úteis, o central alerta silêncio
CLIENTE_ID (aqui) clientes.cliente_id no central-backend 422 cliente_desconhecido no central, 502 heartbeat_recusado + ERROR aqui; a instalação nunca é registrada
variáveis do recurso native variáveis do recurso jar da mesma instância cada cópia se comporta diferente conforme a requisição cai numa ou noutra

Passos — cliente novo

  1. Banco. Garantir o Postgres do cliente acessível e o usuário com DDL no schema.
  2. Recurso. + New Resource → Build Pack Docker Image → fonte.xadm.biz/xadm/integrador-server:native-amd64, nome integrador-server-<cli>-native-pull. Auto-deploy desligado: o deploy é o job deploy do pipeline.yml, pelo control-plane.
  3. Porta e domínio. Porta interna 8080; FQDN https://int.<cli>.xadm.biz como primeiro domínio.
  4. Health check. GET /health (o HEALTHCHECK da imagem já bate no mesmo endpoint).
  5. Limite e reserva de memória do recurso, pela baseline de operação do Coolify.
  6. Variáveis. Preencher o § Variáveis.
  7. Deploy. Redeploy no recurso (ou a próxima release); o Flyway roda no start — acompanhar o log da primeira subida.

Troca jar → native (instância que roda jar)

A ordem entre as instâncias está na decisão 0028: a primeira é uma instância com o push ligado, e cada seguinte só depois de smoke verde e GlitchTip sem exceção nova na anterior.

  1. Crie o integrador-server-<cli>-native-pull como nos passos 2–6 acima, com as mesmas variáveis do -jar-pull da instância e o mesmo FQDN (int.<cli>.xadm.biz) como primeiro domínio. Não mexa no recurso jar.
  2. Deploy do native: Redeploy no recurso novo. O proxy passa a dividir o tráfego da instância entre jar e native.
  3. Confira pelo § Verificação, e o split pelo flavor: 20 chamadas ao /health têm de mostrar "flavor":"native" e "flavor":"jvm".
  4. Mova o jar para o int-jar.<cli>.xadm.biz, com smoke verde e GlitchTip sem exceção nova na instância. Antes, confira as duas pré-condições: o reconcile no ar aceita o host (§ Topologia, "Como o deploy acha o recurso") e o DNS de int-jar.<cli>.xadm.biz resolve para o Coolify. Sem o reconcile, o jar segue no int.<cli> e este passo espera. No -jar-pull, troque o domínio por https://int-jar.<cli>.xadm.biz e faça Redeploy. O native fica sozinho no int.<cli>: 20 chamadas ao /health só mostram "flavor":"native", e o https://int-jar.<cli>.xadm.biz/health mostra "flavor":"jvm". Rode o reconcile e confira no mapa de deploy o -jar-pull com a instância <cli> e o alvo jar.
  5. Deixe os dois no ar. O jar desta instância não para agora: enquanto jar estiver no build.targets, cada release redeploya o -jar-pull, e um recurso parado voltaria a subir.
  6. Depois da sexta instância: a release que tira jar do build.targets; em seguida, pare os seis -jar-pull (ficam parados como fallback, nunca apagados).

Verificação

Resultado esperado conferido no código, não no que parece razoável.

  1. GET /health → {"status":"UP","versao":"X.Y.Z","flavor":"native",…} — shape do /health da xadm-comum-web. O {"status":"ok"} é do /api/health, outro endpoint: esperar "ok" no /health dá falso alarme no dia do deploy. A versao tem de bater com a tag implantada (health e versão).
  2. Abrir a raiz (/) → as telas sobem com o cabeçalho X-Adm e o nome do cliente (UI das telas).
  3. PUT /api/v1/xadm sem o Bearer → 401. Com o Bearer → 200.
  4. Provocar um erro controlado e conferir o evento no GlitchTip filtrando por cliente=<CLIENTE>.
  5. Log do start sem falha de Flyway (migration pendente aplicada).
  6. Na instância piloto da troca: uma nota que dispara push chega uma vez ao celular, e aparece uma linha por mensagem em /push-enviadas.

Ao instalar um integrador-client

O heartbeat do integrador-client passa por esta instância a caminho do central-backend (contrato). Nas instâncias sem client as duas envs ficam vazias: nenhum heartbeat chega, e a config vazia só responde 503 se alguém chamar a rota.

  1. Ordem. A versão do integrador-server com POST /api/v1/heartbeat tem de estar no ar antes do jar do client com heartbeat. Ao contrário, o client leva 404 e só avisa, mas aqui cada 404 autenticado vira ERROR no GlitchTip — uma issue por hora.
  2. Central. Conferir que o cliente existe na tabela clientes do central-backend (o cliente_id, ex.: maxsul).
  3. Envs. CLIENTE_ID = esse cliente_id; CENTRAL_API_TOKEN = o mesmo valor cadastrado nos recursos do central — nos recursos native e jar da instância. Redeploy.
  4. Prova, antes do client. curl -X POST https://<fqdn>/api/v1/heartbeat -H "Authorization: Bearer <INTEGRADOR_API_TOKEN>" -H "Content-Type: application/json" -d '{"versao":"smoke","fluxos":[]}' → 204. A instalação aparece no painel do central com a versão smoke até o client real bater (no máximo 1h depois da instalação do jar). 502/503 aqui: ver incidentes comuns.

Reversão

Caminho Efeito
Parar o -native-pull da instância (durante a troca) Com o jar ainda no int.<cli>, ele segue atendendo sozinho. Com o jar já no int-jar.<cli>, devolva antes o int.<cli> como primeiro domínio do -jar-pull e faça Redeploy: o recurso que fica assume o domínio antes de o outro parar, senão o int.<cli> responde 503. É a volta do native
Parar os recursos da instância (Coolify) Para tudo nesta instância. Mais simples e garantido
Apontar a tag imutável <sha>-native (ou <sha>-jar) no recurso e Redeploy Volta a versão. As migrations são aditivas — reverter o container não desfaz schema; migration nova em produção só se reverte à mão
WEBSTORM_WEBHOOK_ENABLED=false Corta só o poke de saída ao tradutor (EstoqueWebhookListener). ⚠️ Não para a integração: o tradutor tem sweep próprio e segue descobrindo mudança no banco sozinho — desligar o envio ao parceiro é no repo do tradutor
PUSH_ENABLED=false Corta só o push FCM. Não afeta ingestão nem replicação
Esvaziar CLIENTE_ID ou CENTRAL_API_TOKEN ⚠️ Não é um desligamento limpo — não há flag do heartbeat neste server: cada heartbeat passa a responder 503 heartbeat_desligado com ERROR no GlitchTip. Desligar o heartbeat é no client (heartbeat.intervaloMinutos=0); parar de acompanhar a instalação é no painel do central

Nenhuma dessas flags corta a entrada: enquanto o app está no ar, o X-Adm e o int-pied seguem podendo PUT /api/v1/xadm com o Bearer válido. Para fechar a entrada, pare o container ou rotacione o INTEGRADOR_API_TOKEN.

Referências