# Runbook de Operação — Produção (Traefik + Convex Self‑Hosted) — 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 self‑hosted, (4) popular seeds e (5) operar/atualizar com ou sem CI/CD. Tudo em PT‑BR. ## Visão geral - Frontend (Next.js) público em `tickets.esdrasrenan.com.br`. - Backend Convex self‑hosted 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/e‑mail 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. 4. Se o app desktop exibir `Falha no registro (500)` com `attempt to write a readonly database`, o volume `sistema_sistema_db` ficou com permissões incorretas (SQLite não consegue gravar). Rode `docker run --rm -v sistema_sistema_db:/data alpine:3 sh -lc 'chown -R 1000:1000 /data && chmod -R ug+rwX /data'` ou simplesmente dispare um deploy (o workflow CI agora corrige as permissões antes de publicar). ## 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= 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_PORT=465 SMTP_DOMAIN= SMTP_USERNAME= SMTP_PASSWORD= SMTP_AUTHENTICATION=login SMTP_ENABLE_STARTTLS_AUTO=false SMTP_TLS=true MAILER_SENDER_EMAIL="Nome " # Dispositivo/inventário MACHINE_PROVISIONING_SECRET= MACHINE_TOKEN_TTL_MS=2592000000 FLEET_SYNC_SECRET= # Conexões internas (Next.js -> Convex) # CONVEX_INTERNAL_URL deve apontar para o hostname/porta do serviço no Swarm. # Self-heal do Raven (opcional) REMOTE_ACCESS_TOKEN_GRACE_MS=900000 # 15 minutos para aceitar tokens recém-revogados # 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 self‑hosted, NÃO usar `CONVEX_DEPLOYMENT`. - O Raven armazena o código de provisionamento localmente. Se o token for revogado (reset/reinstalação), ele reprovisiona sozinho e reenfileira o acesso remoto do RustDesk — mantenha `REMOTE_ACCESS_TOKEN_GRACE_MS` alinhado ao seu SLA. ## 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 self‑hosted (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 sanity‑check) ## Zero‑downtime (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 self‑hosted 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 self‑hosted — 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|... ``` 2. 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ós‑deploy (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 self‑hosted) 1. Registrar runner na VPS: - Repo → Settings → Actions → Runners → New self‑hosted → 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 ` - 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 self‑hosted: imagem oficial `ghcr.io/get-convex/convex-backend` ## Bundlers (Next.js) - Em desenvolvimento utilizamos Turbopack (`next dev --turbopack`) pela velocidade incremental e mantemos fallback webpack. - Builds de produção rodam com `next build --turbopack`; use `next build --webpack` apenas se precisar depurar diferenças. - Scripts principais (package.json): - `dev`: `next dev --turbopack` - `build`: `next build --turbopack` - `build:webpack`: `next build --webpack` (fallback) - O workflow de CI executa `bun run build:bun` (roda `next build --turbopack` via Bun) e a stack continua a usar `bun run start:bun` sobre o artefato gerado.