340 lines
16 KiB
Markdown
340 lines
16 KiB
Markdown
# 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 `pnpm 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.
|
||
|
||
## 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
|
||
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>
|
||
|
||
# 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`.
|
||
|
||
## 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: `pnpm -C 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' \
|
||
node:20-bullseye bash -lc "corepack enable && corepack prepare pnpm@10.20.0 --activate && pnpm install --frozen-lockfile --prod=false && pnpm exec 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' \
|
||
node:20-bullseye bash -lc "set -euo pipefail; corepack enable && corepack prepare pnpm@10.20.0 --activate && pnpm i --frozen-lockfile --prod=false; \
|
||
unset CONVEX_DEPLOYMENT; \
|
||
pnpm exec convex env set MACHINE_PROVISIONING_SECRET 'seu-hex' -y; \
|
||
pnpm exec 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 && pnpm 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" \
|
||
pnpm auth:seed'
|
||
```
|
||
- Filas padrão: `docker exec -it "$CONTAINER" bash -lc 'cd /app && pnpm 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 `node:20` com `pnpm exec 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 `node:20-bullseye` com envs `CONVEX_SELF_HOSTED_URL` e `CONVEX_SELF_HOSTED_ADMIN_KEY` (secrets do GitHub)
|
||
- Rodar `pnpm exec 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`.
|
||
- `pnpm` reclama de workspace:
|
||
- O `pnpm-workspace.yaml` inclui `apps/desktop`. No deploy do web isso não impacta pois usamos filtros/paths do projeto. Se preferir isolar, ajuste para apenas `.`.
|
||
- Portainer erro de bind relativo:
|
||
- Usar caminho absoluto `/srv/apps/sistema:/app` no stack (feito).
|
||
- Prisma CLI “not found”:
|
||
- Instalar devDependencies no build (`NPM_CONFIG_PRODUCTION=false` e `pnpm install --prod=false`).
|
||
- 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 self‑hosted: imagem oficial `ghcr.io/get-convex/convex-backend`
|
||
|
||
## Turbopack (Next.js)
|
||
- O projeto usa Turbopack em dev e build.
|
||
- Scripts (package.json):
|
||
- `dev`: `next dev --turbopack`
|
||
- `build`: `next build --turbopack`
|
||
- O workflow de CI executa `pnpm build` (que já chama Turbopack via script), e a stack utiliza `pnpm start` sobre o artefato gerado.
|