sistema-de-chamados/docs/OPERACAO-PRODUCAO.md

16 KiB
Raw Blame History

Runbook de Operação — Produção (Traefik + Convex SelfHosted) — Arquivo

Nota: este documento foi substituído por docs/operations.md e permanece aqui como histórico.

Documento vivo. Guia completo para (1) preparar a VPS, (2) fazer deploy com Traefik/Swarm, (3) publicar o backend Convex selfhosted, (4) popular seeds e (5) operar/atualizar com ou sem CI/CD. Tudo em PTBR.

Visão geral

  • Frontend (Next.js) público em tickets.esdrasrenan.com.br.
  • Backend Convex selfhosted em convex.esdrasrenan.com.br (imagem oficial Convex).
  • Traefik no Docker Swarm (rede traefik_public) roteando por hostname (sem conflito de portas).
  • Banco Prisma (SQLite) persistente via volume sistema_db (mapeado em /app/data).
  • Estado do Convex persistente via volume convex_data.
  • Seeds prontos (Better Auth e dados demo Convex).
  • Seeds Better Auth automáticos: o container do web executa bun run auth:seed após prisma migrate deploy, garantindo usuários padrão em toda inicialização (sem resetar senha existente por padrão).

Sessão de dispositivo (Desktop/Tauri)

  • A rota GET /machines/handshake?token=...&redirect=/portal|/dashboard é pública no middleware para permitir a criação da sessão "machine" a partir do agente desktop, mesmo sem login prévio.
  • Após o handshake, o servidor grava cookies de sessão (Better Auth) e um cookie machine_ctx com o vínculo (persona, assignedUser*, etc.). Em seguida, o App carrega /api/machines/session para preencher o contexto no cliente (machineContext).
  • Com esse contexto, o Portal exibe corretamente o nome/email do colaborador/gestor no cabeçalho e permite abrir chamados em nome do usuário vinculado.
  • Em sessões de dispositivo, o botão "Encerrar sessão" no menu do usuário é ocultado por padrão na UI interna.

Detalhes importantes (aprendidos em produção)

  • CORS com credenciais: as rotas POST /api/machines/sessions e GET /machines/handshake precisam enviar Access-Control-Allow-Credentials: true para que os cookies do Better Auth sejam aceitos na WebView.
  • Vários Set-Cookie: alguns navegadores/ambientes colapsam cabeçalhos. Para confiabilidade, usamos NextResponse.cookies.set(...) para cada cookie, em vez de repassar o cabeçalho bruto.
  • Top-level navigation: mesmo tentando criar a sessão via POST /api/machines/sessions, mantemos a navegação final pelo GET /machines/handshake (primeira parte) para maximizar a aceitação de cookies no WebView.
  • Front tolerante: o portal preenche machineContext mesmo quando /api/auth/get-session retorna null na WebView e deriva a role "machine" do contexto — assim o colaborador consegue abrir tickets normalmente.
  • Página de diagnóstico: GET /portal/debug exibe o status/JSON de /api/auth/get-session e /api/machines/session com os mesmos cookies da aba.

Troubleshooting rápido

  1. Abra o app desktop e deixe ele redirecionar para /portal/debug.
  2. Se machines/session for 200 e get-session for null, está OK — o portal usa machineContext assim mesmo.
  3. Se machines/session for 401/403:
    • Verifique CORS/credenciais (Access-Control-Allow-Credentials: true).
    • Garante que estamos usando cookies.set para aplicar todos os cookies da Better Auth.
    • Refaça o handshake (feche reabra o desktop). Opcional: renomeie EBWebView para limpar cookies no Windows.

Requisitos

  • VPS com Docker/Swarm e Traefik já em execução na rede externa traefik_public.
  • Portainer opcional (para gerenciar a stack “sistema”).
  • Domínios apontando para a VPS:
    • tickets.esdrasrenan.com.br (frontend)
    • convex.esdrasrenan.com.br (Convex backend)

Layout do servidor

  • Código do projeto: /srv/apps/sistema (bind no stack).
  • Volumes Swarm:
    • sistema_db/app/data (SQLite / Prisma)
    • convex_data/convex/data (Convex backend)

.env (produção)

Arquivo base: .env na raiz do projeto. Exemplo mínimo (substitua domínios/segredos):

NEXT_PUBLIC_APP_URL=https://tickets.esdrasrenan.com.br
BETTER_AUTH_URL=https://tickets.esdrasrenan.com.br
NEXT_PUBLIC_CONVEX_URL=https://convex.esdrasrenan.com.br
CONVEX_INTERNAL_URL=http://convex_backend:3210
BETTER_AUTH_SECRET=<hex forte gerado por `openssl rand -hex 32`>
DATABASE_URL=file:./prisma/db.sqlite

# Seeds automáticos (Better Auth)
# Garante usuários padrão sem resetar senhas existentes
SEED_ENSURE_ONLY=true

# SMTP
SMTP_ADDRESS=<smtp.host>
SMTP_PORT=465
SMTP_DOMAIN=<seu-dominio>
SMTP_USERNAME=<usuario>
SMTP_PASSWORD=<senha>
SMTP_AUTHENTICATION=login
SMTP_ENABLE_STARTTLS_AUTO=false
SMTP_TLS=true
MAILER_SENDER_EMAIL="Nome <no-reply@seu-dominio.com>"

# Dispositivo/inventário
MACHINE_PROVISIONING_SECRET=<hex forte>
MACHINE_TOKEN_TTL_MS=2592000000
FLEET_SYNC_SECRET=<hex forte ou igual ao de provisionamento>

# Conexões internas (Next.js -> Convex)
# CONVEX_INTERNAL_URL deve apontar para o hostname/porta do serviço no Swarm.

# Outros
CONVEX_SYNC_SECRET=dev-sync-secret
ALERTS_LOCAL_HOUR=8
SYNC_TENANT_ID=tenant-atlas
SEED_TENANT_ID=tenant-atlas

# Importante para self-hosted: comentar se existir
# CONVEX_DEPLOYMENT=...

Atenção

  • MAILER_SENDER_EMAIL precisa de aspas se contiver espaços.
  • Em selfhosted, NÃO usar CONVEX_DEPLOYMENT.

Stack (Docker Swarm + Traefik)

O arquivo do stack está versionado em stack.yml. Ele sobe:

  • web (Next.js) — builda e roda na porta interna 3000 (roteada pelo Traefik por hostname).
  • convex_backend — imagem oficial do Convex selfhosted (porta interna 3210, roteada pelo Traefik).

Bind dos volumes (absolutos, compatível com Portainer/Swarm):

  • /srv/apps/sistema:/app → código do projeto dentro do container web.
  • volume sistema_db/app/data (SQLite do Prisma).
  • volume convex_data/convex/data (estado do Convex backend).

Rótulos Traefik (labels) no stack mapeiam:

  • tickets.esdrasrenan.com.br → serviço web porta 3000.
  • convex.esdrasrenan.com.br → serviço convex_backend porta 3210.

Deploy da stack

Via Portainer (recomendado)

  1. Abra o Portainer → Stacks → Add/Update e cole o conteúdo de stack.yml (ou vincule ao repositório para “Pull/Deploy”).
  2. Clique em “Deploy the stack” (ou “Update the stack”).

Via CLI

docker stack deploy --with-registry-auth -c /srv/apps/sistema/stack.yml sistema

Verificação

  • Serviços: docker stack services sistema
  • Logs:
    • docker service logs -f sistema_web
    • docker service logs -f sistema_convex_backend

Acesso

  • App: https://tickets.esdrasrenan.com.br
  • Convex: https://convex.esdrasrenan.com.br (o importante é o WebSocket do cliente conectar; o path /version responde para sanitycheck)

Zerodowntime (sem queda durante deploy)

Para evitar interrupção perceptível no deploy, habilitamos rollout "start-first". Para este projeto, mantemos 1 réplica (web e Convex) por segurança, pois:

  • O web usa SQLite (Prisma); múltiplas réplicas concorrendo gravação no mesmo arquivo podem causar erros de lock/readonly.
  • O Convex backend selfhosted não é clusterizado.

O stack.yml já inclui:

  • replicas: 1 + update_config.order: start-first (Swarm sobe a nova task saudável antes de desligar a antiga).
  • failure_action: rollback.
  • healthcheck por porta local, garantindo que o Swarm só troque quando a nova task estiver OK.

Se quiser ajustar recursos/estratégia:

deploy:
  replicas: 2
  update_config:
    parallelism: 1
    order: start-first
    failure_action: rollback
  restart_policy:
    condition: any
healthcheck:
  # web
  test: ["CMD", "node", "-e", "fetch('http://localhost:3000').then(r=>process.exit(r.ok?0:1)).catch(()=>process.exit(1))"]
  interval: 10s
  timeout: 3s
  retries: 5
  start_period: 30s

Observação: o CI já força docker service update --force após stack deploy e passa RELEASE_SHA no ambiente para variar a spec em todo commit, assegurando rollout.

App Desktop (Tauri)

  • Build local por SO:
    • Linux: bun run --cwd apps/desktop tauri build
    • Windows/macOS: executar o mesmo comando no respectivo sistema (o Tauri gera .msi/.dmg/.app).
  • Por padrão, o executável em modo release usa https://tickets.esdrasrenan.com.br como APP_URL e API_BASE_URL.
  • Para customizar, crie apps/desktop/.env com VITE_APP_URL e VITE_API_BASE_URL.
  • Saída dos pacotes: apps/desktop/src-tauri/target/release/bundle/.

Alertas de postura (opcional)

  • Variáveis de ambiente para geração automática de tickets em alertas de postura (CPU alta, serviço parado, SMART em falha):
    • MACHINE_ALERTS_CREATE_TICKETS=true|false (padrão: true)
    • MACHINE_ALERTS_TICKET_REQUESTER_EMAIL=admin@sistema.dev (usuário solicitante dos tickets automáticos)

CI de Release do Desktop

  • Workflow: .github/workflows/desktop-release.yml (build Linux/Windows/macOS).
  • Preencha os Secrets no repositório (Settings > Secrets > Actions):
    • TAURI_PRIVATE_KEY
    • TAURI_KEY_PASSWORD
  • Disparo: tag desktop-v* ou via workflow_dispatch.

Dashboard (opcional)

Você pode expor o painel do Convex para inspeção em produção.

DNS

  • Criar convex-admin.esdrasrenan.com.br apontando para a VPS.

Stack

  • O serviço convex_dashboard já está definido em stack.yml com Traefik. Após atualizar a stack:
    • Acesse https://convex-admin.esdrasrenan.com.br.
    • Use a Admin Key gerada por ./generate_admin_key.sh para autenticar.

Convex selfhosted — configuração inicial

  1. Gerar Admin Key (uma vez, dentro do container do Convex):
# Console/exec no container sistema_convex_backend
./generate_admin_key.sh
# Copiar/guardar a chave: convex-self-hosted|...
  1. Publicar o código Convex (deploy das functions) — sem instalar nada na VPS:
docker run --rm -it \
  -v /srv/apps/sistema:/app \
  -w /app \
  -e CONVEX_SELF_HOSTED_URL=https://convex.esdrasrenan.com.br \
  -e CONVEX_SELF_HOSTED_ADMIN_KEY='COLE_A_CHAVE_AQUI' \
  oven/bun:1 bash -lc "bun install --frozen-lockfile && bun x convex deploy"

Observação

  • Sempre que alterar código em convex/, repita o comando acima para publicar as mudanças.

Variáveis do Convex (importante)

As functions do Convex leem variáveis via convex env, não do .env do container. No CI, defina os seguintes Secrets (Repo → Settings → Secrets and variables → Actions):

  • CONVEX_SELF_HOSTED_URL — ex.: https://convex.esdrasrenan.com.br
  • CONVEX_SELF_HOSTED_ADMIN_KEY — gerada por ./generate_admin_key.sh
  • MACHINE_PROVISIONING_SECRET — hex forte
  • (opcional) MACHINE_TOKEN_TTL_MS — ex.: 2592000000
  • (opcional) FLEET_SYNC_SECRET

O job convex_deploy sempre roda convex env set com os Secrets acima antes do convex deploy. Se preferir setar manualmente:

  • MACHINE_PROVISIONING_SECRET — obrigatório para /api/machines/register
  • (opcional) MACHINE_TOKEN_TTL_MS, FLEET_SYNC_SECRET

CLI manual (exemplo):

docker run --rm -it \
  -v /srv/apps/sistema:/app -w /app \
  -e CONVEX_SELF_HOSTED_URL=https://convex.esdrasrenan.com.br \
  -e CONVEX_SELF_HOSTED_ADMIN_KEY='COLE_A_CHAVE' \
  oven/bun:1 bash -lc "set -euo pipefail; bun install --frozen-lockfile; \
    unset CONVEX_DEPLOYMENT; \
    bun x convex env set MACHINE_PROVISIONING_SECRET 'seu-hex' -y; \
    bun x convex env list"

Smoke test pósdeploy (CI)

O pipeline executa um teste rápido após o deploy do Web:

  • Registra uma dispositivo fake usando MACHINE_PROVISIONING_SECRET do /srv/apps/sistema/.env
  • Espera HTTP 201 e extrai machineToken
  • Envia heartbeat e espera HTTP 200
  • Se falhar, o job é marcado como erro (evita regressões silenciosas)

Seeds

  • Dados de demonstração Convex: acesse uma vez https://tickets.esdrasrenan.com.br/dev/seed.
  • Usuários (Better Auth):
CONTAINER=$(docker ps --format '{{.ID}} {{.Names}}' | grep sistema_web | awk '{print $1}' | head -n1)
docker exec -it "$CONTAINER" bash -lc 'cd /app && bun run auth:seed'
  • Apenas um admin (em produção):
CONTAINER=$(docker ps --format '{{.ID}} {{.Names}}' | grep sistema_web | awk '{print $1}' | head -n1)
docker exec -it "$CONTAINER" bash -lc 'cd /app && \
  SEED_USER_EMAIL="seu-email@dominio.com" \
  SEED_USER_PASSWORD="suaSenhaForte" \
  SEED_USER_NAME="Seu Nome" \
  SEED_USER_ROLE="admin" \
  bun run auth:seed'
  • Filas padrão: docker exec -it "$CONTAINER" bash -lc 'cd /app && bun run queues:ensure'

Atualizações (sem CI)

  • App (Next.js):
cd /srv/apps/sistema
git pull
docker stack deploy --with-registry-auth -c stack.yml sistema
  • Convex (functions): repetir o container oven/bun:1 com bun x convex deploy (ver seção Convex).
  • Reiniciar serviços sem alterar o stack: docker service update --force sistema_web (ou sistema_convex_backend).

CI/CD (GitHub Actions + runner selfhosted)

  1. Registrar runner na VPS:
    • Repo → Settings → Actions → Runners → New selfhosted → Linux
    • Labels: self-hosted, linux, vps
  2. Ajustar job deploy em .github/workflows/ci-cd-web-desktop.yml para:
    • cd /srv/apps/sistema && git pull
    • docker stack deploy --with-registry-auth -c stack.yml sistema
  3. Adicionar job convex_deploy (opcional) no mesmo runner:
    • Executar container oven/bun:1 com envs CONVEX_SELF_HOSTED_URL e CONVEX_SELF_HOSTED_ADMIN_KEY (secrets do GitHub)
    • Rodar bun x convex deploy

Secrets necessários no GitHub (Repo → Settings → Secrets and variables → Actions)

  • CONVEX_SELF_HOSTED_URL = https://convex.esdrasrenan.com.br
  • CONVEX_SELF_HOSTED_ADMIN_KEY = chave retornada por ./generate_admin_key.sh
  • (Desktop) VPS_HOST, VPS_USER, VPS_SSH_KEY, TAURI_PRIVATE_KEY, TAURI_KEY_PASSWORD — se usar o job de release desktop

Benefícios

  • Push na main → pipeline atualiza app e (opcionalmente) publica mudanças no Convex.

Trocar domínio ou VPS (checklist)

  1. Criar DNS para novos domínios (app/convex) apontando para a VPS nova.
  2. Copiar o projeto para /srv/apps/sistema na nova VPS.
  3. Ajustar .env com novos domínios e SEGREDOS novos (gire novamente em produção).
  4. Deploy da stack.
  5. Convex: gerar Admin Key no novo container e convex deploy apontando para a nova URL.
  6. Testar front + WS (sem erro 1006) e seeds conforme necessário.

Problemas comuns e correções

  • WebSocket 1006 no front:
    • Convex não está recebendo/concluindo handshake WS → ver logs sistema_convex_backend.
    • DNS/Traefik incorretos → confirmar labels/hostnames e DNS.
  • MAILER_SENDER_EMAIL com erro de parsing:
    • Adicionar aspas no .env.
  • Lockfile desatualizado:
    • Rode bun install --frozen-lockfile sempre que ajustar dependências para manter o bun.lock consistente em produção.
  • Portainer erro de bind relativo:
    • Usar caminho absoluto /srv/apps/sistema:/app no stack (feito).
  • Prisma CLI “not found”:
    • Execute bun install no container de build garantindo a instalação das devDependencies (Prisma CLI fica disponível via bun x prisma ...).
  • Convex CLI pedindo interação:
    • Não usar CLI em produção; usamos imagem oficial convex-backend e convex deploy via container transitório com Admin Key.

Comandos úteis

  • Serviços: docker stack services sistema
  • Logs: docker service logs -f sistema_web / docker service logs -f sistema_convex_backend
  • Reiniciar: docker service update --force <service>
  • Status Traefik (se exposto): docker service logs -f traefik

Segurança

  • Nunca commit .env com segredos reais.
  • Guarde a Admin Key do Convex em local seguro; use secrets no GitHub.
  • Gire segredos ao migrar de VPS/domínio.

Referências

  • Stack do projeto: stack.yml
  • CI/CD (web + desktop): .github/workflows/ci-cd-web-desktop.yml
  • Guia CI/CD Desktop: apps/desktop/docs/guia-ci-cd-web-desktop.md
  • Docs Convex selfhosted: imagem oficial ghcr.io/get-convex/convex-backend

Bundlers (Next.js)

  • Em desenvolvimento utilizamos Turbopack (next dev --turbopack) pela velocidade incremental.
  • Builds de produção rodam com next build --webpack para evitar mismatches de chunks vistos com o Turbopack em produção.
  • Scripts principais (package.json):
    • dev: next dev --turbopack
    • build: next build --webpack
    • build:turbopack: next build --turbopack (uso pontual para debug)
  • O workflow de CI executa bun run build:bun (que agora roda next build --webpack via Bun) e a stack continua a usar bun run start:bun sobre o artefato gerado.