feat(infra): apps/api e apps/frontend como services systemd

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 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01BFaBaBSQGhyXGEgtTYZGV8
This commit is contained in:
2026-08-30 00:33:44 -03:00
parent e9dc973aee
commit 4480e9265e
4 changed files with 162 additions and 11 deletions

76
TODO.md
View File

@@ -1396,6 +1396,59 @@ secao 134-139, 168)
ninguém com `users.manage`) — não implementado, mesma classe de ninguém com `users.manage`) — não implementado, mesma classe de
risco documentada em outras ações administrativas desta sessão 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 ## 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 - **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 + 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. 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 - ~~`apps/api` e `apps/frontend` rodam direto no host, sem supervisor, não
docker-compose — só Postgres/Redis/FreeSWITCH/fs-config/fs-events/predictive-dialer/ sobrevivem a reboot~~ **RESOLVIDO na PHASE 38** (2026-08-30): os dois agora são
ai-worker são containers. Depois de qualquer reboot da VM (como o desta sessão, pra services systemd (`b2bcall-api.service`/`b2bcall-frontend.service`,
aplicar a RAM nova) os containers voltam sozinhos (restart policy do compose), mas `infrastructure/systemd/`, `enabled`) — sobrevivem a reboot como os containers
os dois processos de host **não voltam** — precisam ser religados manualmente: Docker já sobreviviam. `API_PORT=3000` (`.env`) e `PORT=3001` (`Environment=` da
`apps/api` precisa de `set -a && source .env && set +a` antes (não lê `.env` unit do frontend, não `.env.local` — não funciona lá, o CLI do Next.js decide a
sozinho, ao contrário dos containers) e deve subir **antes** do frontend, porque os porta antes de aplicar esse arquivo) fixam as duas portas, então a ordem de start
dois usam a porta 3000 por padrão (`API_PORT` não setado, `next dev` sem `-p`) — o não importa mais (achado real: sem isso, os dois competiam pela 3000 por padrão, e
segundo a subir cai sozinho pra 3001. `B2BCALL_API_URL` do frontend quem perdesse crashava com `EADDRINUSE` em vez de cair pra 3001). Detalhes e
(`apps/frontend/.env.local`) assume que a API ficou com a 3000, então a ordem comandos úteis (`systemctl status`/`restart`, `journalctl -f`) em
importa. Sem um processo supervisor (pm2/systemd) isso vai se repetir a cada reboot. `infrastructure/systemd/README.md`. Continuam rodando em modo dev (`pnpm dev`), não
build de produção — decisão deliberada, ver o README.

View File

@@ -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).

View File

@@ -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

View File

@@ -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