From 4480e9265e41a9fa7a42f3233c20d97efccee206 Mon Sep 17 00:00:00 2001 From: Matheus Date: Sun, 30 Aug 2026 00:33:44 -0300 Subject: [PATCH] feat(infra): apps/api e apps/frontend como services systemd MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Fecha um risco documentado desde a PHASE 01: os dois processos de host (fora do Docker) não sobreviviam a um reboot da VM, precisando ser religados manualmente toda vez. Agora são b2bcall-api.service e b2bcall-frontend.service (infrastructure/systemd/), enabled, sobrevivem a reboot como os containers Docker já sobreviviam via restart: unless-stopped. Achado real resolvido antes de instalar: sem porta fixa, os dois disputavam a 3000 por padrão — quem perdesse crashava com EADDRINUSE em vez de cair pra 3001. Fixado API_PORT=3000 (.env) e PORT=3001 (Environment= direto na unit do frontend — confirmado que colocar em apps/frontend/.env.local não funciona, o CLI do Next.js decide a porta antes de aplicar esse arquivo). A ordem de start deixa de importar. Rodam em modo dev (pnpm dev), não build de produção — decisão deliberada documentada no README, virar produção é escopo maior. Testado ponta a ponta: units instaladas e habilitadas, subiram nas portas certas sem fallback nem crash, journalctl com logs limpos, smoke test de regressão nas 19 telas do tenant + platform via os processos supervisionados, todas 200. Achado incidental: as rotas do Next.js dev levam 10-15s pra compilar na primeira visita — explica os TimeoutError intermitentes do Puppeteer vistos ao longo da sessão, não é um bug. Co-Authored-By: Claude Sonnet 5 Claude-Session: https://claude.ai/code/session_01BFaBaBSQGhyXGEgtTYZGV8 --- TODO.md | 76 ++++++++++++++++--- infrastructure/systemd/README.md | 57 ++++++++++++++ infrastructure/systemd/b2bcall-api.service | 20 +++++ .../systemd/b2bcall-frontend.service | 20 +++++ 4 files changed, 162 insertions(+), 11 deletions(-) create mode 100644 infrastructure/systemd/README.md create mode 100644 infrastructure/systemd/b2bcall-api.service create mode 100644 infrastructure/systemd/b2bcall-frontend.service diff --git a/TODO.md b/TODO.md index f8fbe90..4c4fd13 100644 --- a/TODO.md +++ b/TODO.md @@ -1396,6 +1396,59 @@ secao 134-139, 168) ninguém com `users.manage`) — não implementado, mesma classe de risco documentada em outras ações administrativas desta sessão +## PHASE 38 — Infraestrutura: `apps/api`/`apps/frontend` como services +systemd (fecha um risco documentado desde a PHASE 01) +- [x] `infrastructure/systemd/b2bcall-api.service` e + `b2bcall-frontend.service` (+ `README.md` com instalação/comandos + úteis) — os dois processos de host (fora do Docker) agora + `enabled`, sobrevivem a reboot como os containers já sobreviviam + via `restart: unless-stopped`. +- [x] **Achado real, resolvido antes de instalar as units**: sem porta + fixa, `apps/api` e `apps/frontend` disputavam a 3000 por padrão — + quem perdesse a corrida crashava (`EADDRINUSE`, Nest/Fastify sem + fallback) em vez de cair pra 3001 como o Next.js faz sozinho. Fixado + `API_PORT=3000` em `.env` e `PORT=3001` via `Environment=` direto na + unit do frontend — a ordem de start deixa de importar, cada um já + nasce na porta certa. +- [x] **Achado real, descoberto testando**: `PORT=3001` em + `apps/frontend/.env.local` **não funciona** — o CLI do `next dev` + decide a porta antes desse arquivo ser aplicado (confirmado: com + `.env.local` sozinho, ainda aparecia "Port 3000 is in use, using + 3001" mesmo com `.env.local` correto). Só `Environment=`/uma + variável de ambiente real do processo funciona. Documentado no + README pra não recair no mesmo engano depois. +- [x] Rodam em modo dev (`pnpm dev`), não build de produção — decisão + deliberada (`README.md` explica: mudar pra `next build`/`tsc && + node dist` é escopo de "ir pra produção", não de "sobreviver a + reboot"), documentada explicitamente em vez de fingir que já é + produção. +- [x] Testado ponta a ponta: parado os processos manuais, instalado e + habilitado as duas units, `systemctl start` na ordem api→frontend + (a ordem já não importa mais, mas testado assim mesmo) — os dois + subiram na porta certa sem nenhum fallback nem crash, `journalctl + -u` confirma logs limpos com `SyslogIdentifier`, smoke test de + regressão nas 19 telas do tenant + platform via os processos + supervisionados por systemd, todas 200. +- [x] **Achado incidental, explica uma flakiness recorrente do próprio + processo de teste desta sessão inteira**: `journalctl` confirmou + rotas do Next.js dev levando 10-15s pra compilar na primeira visita + (`Compiled /platform/billing/tarifas in 11.9s`) — a causa real dos + vários `TimeoutError` de navegação do Puppeteer vistos ao longo de + várias fases anteriores, sempre contornados com um retry simples. + Não é um bug, é o modo dev do Next.js funcionando como esperado; + registrado na memória do projeto pra não investigar de novo à toa + numa sessão futura. +- [ ] Não testado com um reboot de verdade da VM (deliberadamente evitado + — reboot é uma ação disruptiva demais pra essa sessão confirmar + sozinha); `systemctl is-enabled` confirma que as units estão + registradas pra iniciar no boot, o que é a garantia que o systemd + oferece, mas o cenário completo (Docker + Postgres/Redis prontos + + as duas units subindo depois) nunca foi observado de ponta a ponta + numa reinicialização real +- [ ] `apps/api`/`apps/frontend` continuam sem HTTPS/domínio próprio + (nginx, pendência original da PHASE 01) — o acesso de teste + continua direto na porta 3001 da VM + --- ## Riscos conhecidos @@ -1408,14 +1461,15 @@ secao 134-139, 168) - **RAM da VM aumentada pra 8GB em 2026-08-29** — resolve a preocupação acima, mas ainda não tem swap generoso nem monitoramento de pico durante um build de frontend + todos os workers rodando junto; reavaliar se o Next.js dev server crescer muito. -- **`apps/api` e `apps/frontend` rodam direto no host** (`pnpm dev`), fora do - docker-compose — só Postgres/Redis/FreeSWITCH/fs-config/fs-events/predictive-dialer/ - ai-worker são containers. Depois de qualquer reboot da VM (como o desta sessão, pra - aplicar a RAM nova) os containers voltam sozinhos (restart policy do compose), mas - os dois processos de host **não voltam** — precisam ser religados manualmente: - `apps/api` precisa de `set -a && source .env && set +a` antes (não lê `.env` - sozinho, ao contrário dos containers) e deve subir **antes** do frontend, porque os - dois usam a porta 3000 por padrão (`API_PORT` não setado, `next dev` sem `-p`) — o - segundo a subir cai sozinho pra 3001. `B2BCALL_API_URL` do frontend - (`apps/frontend/.env.local`) assume que a API ficou com a 3000, então a ordem - importa. Sem um processo supervisor (pm2/systemd) isso vai se repetir a cada reboot. +- ~~`apps/api` e `apps/frontend` rodam direto no host, sem supervisor, não + sobrevivem a reboot~~ **RESOLVIDO na PHASE 38** (2026-08-30): os dois agora são + services systemd (`b2bcall-api.service`/`b2bcall-frontend.service`, + `infrastructure/systemd/`, `enabled`) — sobrevivem a reboot como os containers + Docker já sobreviviam. `API_PORT=3000` (`.env`) e `PORT=3001` (`Environment=` da + unit do frontend, não `.env.local` — não funciona lá, o CLI do Next.js decide a + porta antes de aplicar esse arquivo) fixam as duas portas, então a ordem de start + não importa mais (achado real: sem isso, os dois competiam pela 3000 por padrão, e + quem perdesse crashava com `EADDRINUSE` em vez de cair pra 3001). Detalhes e + comandos úteis (`systemctl status`/`restart`, `journalctl -f`) em + `infrastructure/systemd/README.md`. Continuam rodando em modo dev (`pnpm dev`), não + build de produção — decisão deliberada, ver o README. diff --git a/infrastructure/systemd/README.md b/infrastructure/systemd/README.md new file mode 100644 index 0000000..d78c496 --- /dev/null +++ b/infrastructure/systemd/README.md @@ -0,0 +1,57 @@ +# systemd — apps/api e apps/frontend + +`apps/api` e `apps/frontend` rodam direto no host (fora do Docker — só +Postgres/Redis/FreeSWITCH/fs-config/fs-events/predictive-dialer/ai-worker são +containers, e esses já voltam sozinhos via `restart: unless-stopped` do +`docker-compose.yml` + `docker.service` habilitado). Até a PHASE 38 os dois +processos de host tinham que ser religados manualmente depois de qualquer +reboot — estas units resolvem isso. + +## Por que os dois rodam em `pnpm dev`, não build de produção + +Decisão deliberada: o ambiente inteiro (`ACESSO_TESTE.md`) já é descrito como +desenvolvimento/teste, não produção. Migrar `apps/api` pra `pnpm build && node +dist/main.js` e `apps/frontend` pra `next build && next start` é uma mudança +maior (variar comportamento de erro, HMR, etc.) fora do escopo de "sobreviver a +um reboot" — fica pra quando o projeto realmente for pra produção. + +## Instalação + +```bash +sudo cp infrastructure/systemd/b2bcall-api.service /etc/systemd/system/ +sudo cp infrastructure/systemd/b2bcall-frontend.service /etc/systemd/system/ +sudo systemctl daemon-reload +sudo systemctl enable --now b2bcall-api.service +sudo systemctl enable --now b2bcall-frontend.service +``` + +## Por que a ordem nunca mais importa + +`API_PORT=3000` fixo em `.env` (lido via `EnvironmentFile=` na unit do +`b2bcall-api`) e `PORT=3001` fixo via `Environment=` na unit do +`b2bcall-frontend` — achado real desta sessão: sem os dois explícitos, os dois +processos disputavam a porta 3000 por padrão, e quem perdesse a corrida caía +pra 3001 (Next.js) ou **crashava direto** (`EADDRINUSE`, Nest/Fastify não tem +fallback automático). Com as duas portas fixas, a ordem de start deixa de +importar — cada um já nasce na porta certa, nunca compete pela mesma. + +Note-se que `PORT=3001` só funciona quando setado no **ambiente do processo** +(`Environment=` na unit, ou `PORT=3001 pnpm dev` manual) — colocar em +`apps/frontend/.env.local` sozinho não basta: o CLI do Next.js decide a porta +antes de aplicar esse arquivo, então só o fallback automático (achar 3000 +ocupada, tentar 3001) resolveria, e só por sorte de ordem. + +## Comandos úteis + +```bash +sudo systemctl status b2bcall-api.service b2bcall-frontend.service +journalctl -u b2bcall-api.service -f +journalctl -u b2bcall-frontend.service -f +sudo systemctl restart b2bcall-api.service # depois de mudar código do backend +sudo systemctl restart b2bcall-frontend.service # depois de mudar dependencias do frontend +``` + +`apps/api` recompila (`tsc`) toda vez que a unit inicia/reinicia — leva uns +15-20s. `apps/frontend` (`next dev`) é bem mais rápido pra subir, mas cada +rota ainda compila na primeira vez que é visitada (comportamento normal do +modo dev do Next.js, não um problema desta unit). diff --git a/infrastructure/systemd/b2bcall-api.service b/infrastructure/systemd/b2bcall-api.service new file mode 100644 index 0000000..7790d6f --- /dev/null +++ b/infrastructure/systemd/b2bcall-api.service @@ -0,0 +1,20 @@ +[Unit] +Description=B2BCall API (NestJS/Fastify) +After=network-online.target docker.service +Wants=network-online.target +StartLimitIntervalSec=0 + +[Service] +Type=simple +WorkingDirectory=/opt/b2bcall/apps/api +EnvironmentFile=/opt/b2bcall/.env +ExecStart=/usr/bin/pnpm dev +Restart=on-failure +RestartSec=5 +User=root +StandardOutput=journal +StandardError=journal +SyslogIdentifier=b2bcall-api + +[Install] +WantedBy=multi-user.target diff --git a/infrastructure/systemd/b2bcall-frontend.service b/infrastructure/systemd/b2bcall-frontend.service new file mode 100644 index 0000000..be14171 --- /dev/null +++ b/infrastructure/systemd/b2bcall-frontend.service @@ -0,0 +1,20 @@ +[Unit] +Description=B2BCall Frontend (Next.js dev server) +After=network-online.target b2bcall-api.service +Wants=network-online.target +StartLimitIntervalSec=0 + +[Service] +Type=simple +WorkingDirectory=/opt/b2bcall/apps/frontend +Environment=PORT=3001 +ExecStart=/usr/bin/pnpm dev +Restart=on-failure +RestartSec=5 +User=root +StandardOutput=journal +StandardError=journal +SyslogIdentifier=b2bcall-frontend + +[Install] +WantedBy=multi-user.target