- infrastructure/docker/asterisk.Dockerfile: Asterisk 22.10.1 LTS compilado do fonte oficial (Debian trixie nao distribui mais o pacote asterisk), com PJSIP, AMI, ARI, res_odbc/res_config_odbc, cdr_adaptive_odbc, cel_odbc, app_queue - infrastructure/asterisk/config: templates renderizados no entrypoint (secrets via envsubst, nunca versionados) + sorcery.conf/extconfig.conf para PJSIP realtime, dialplan de teste (extensao 600) - infrastructure/postgres/init/002-asterisk-realtime.sql: tabelas ps_endpoints/ps_auths/ps_aors/ps_contacts/ps_endpoint_id_ips/ ps_registrations/cdr/cel no schema 'asterisk' - infrastructure/nftables: protege AMI/ARI/SIP contra acesso pela LAN (interface ens18), trazido da Fase 9 do TODO para ja, sem afetar SSH nem a rede interna do Docker - docker-compose.yml: servico asterisk em network_mode: host (RTP); Postgres publicado em 127.0.0.1:5432 (nao 0.0.0.0) para o Asterisk alcanca-lo, ja que host network nao enxerga a rede Docker interna Testado: AMI login, ARI com auth (401 sem auth), pjsip realtime consultando Postgres sem erro, dialplan originate -> CDR e CEL gravados corretamente, healthcheck do compose passando.
7.4 KiB
B2BCall — Arquitetura
1. Contexto do servidor (levantado em 2026-08-27)
| Item | Valor |
|---|---|
| SO | Debian 13 (trixie) |
| Kernel | 6.12.94+deb13-amd64 |
| CPU | 2 vCPU |
| RAM | 1.9 GiB (+ 1.6 GiB swap) |
| Disco | 29 GiB (26 GiB livres) |
| IP | 10.10.32.142/24 (privado, sem IP público) |
| Hostname | Lab-FSCore-API |
Alerta de capacidade: 1.9 GiB de RAM é pouco para rodar simultaneamente Postgres + Redis + Asterisk + API (NestJS) + worker + asterisk-events + scheduler + frontend (Next.js) + Nginx. Decisões desta arquitetura foram tomadas para caber neste ambiente de laboratório:
- Build do frontend fora do container em produção real (ou build multi-stage com
node --max-old-space-sizelimitado) — builds Next.js podem estourar RAM em VMs de 2 GiB. docker-compose.ymldefinemem_limit/deploy.resources.limitsconservadores por serviço.- Antes de operação real de discagem em produção, recomenda-se aumentar a VM para
pelo menos 4 vCPU / 8 GiB RAM. Este ambiente serve para desenvolvimento e testes
funcionais (inclusive
DIALER_SIMULATION=true), não para carga real de discador preditivo com volume.
2. Estrutura do monorepo
/opt/b2bcall
├── apps
│ ├── frontend # Next.js (App Router) + Tailwind + shadcn/ui
│ ├── api # NestJS (Fastify adapter) - REST + WebSocket gateway
│ ├── dialer-worker # PredictiveDialerEngine, CPS limiter, retry engine
│ ├── asterisk-events # Conector AMI dedicado -> normaliza eventos -> Postgres/Redis/WS
│ └── scheduler # Callbacks, horários de campanha, reconciliação, cron jobs
├── packages
│ ├── database # Prisma/Drizzle schema + migrations + client
│ ├── shared # utils comuns (logger, result types, error classes)
│ ├── types # tipos TS compartilhados (DTOs, enums de estado)
│ ├── ui # componentes de UI compartilhados (design system)
│ └── telephony # TelephonyProvider (interface) + AsteriskTelephonyProvider (AMI/ARI)
├── infrastructure
│ ├── asterisk # pjsip.conf, extensions, odbc, realtime, queues
│ ├── nginx # reverse proxy, websocket upgrade, TLS
│ ├── postgres # init scripts, schemas (application / asterisk)
│ └── docker # Dockerfiles por serviço
├── scripts # install.sh, update.sh, backup.sh, restore.sh, healthcheck.sh
├── docs
├── docker-compose.yml
├── .env.example
├── .gitignore
└── README.md
3. Decisões técnicas
3.1 Rede Docker / Asterisk
- Asterisk roda em
network_mode: host— RTP usa uma faixa grande de portas UDP (10000-20000 por padrão); mapear porta a porta em bridge network é inviável em produção. Impacto documentado: o container do Asterisk enxerga a rede do host diretamente (perde isolamento de rede do Docker); todas as portas do Asterisk (SIP 5060/5061, RTP, AMI 5038, ARI 8088) ficam sujeitas apenas ao firewall do host (nftables), não ao Docker networking. Por isso as regras de nftables (seção 8) são a única linha de defesa e devem ser tratadas como obrigatórias, não opcionais. - Todos os demais serviços (api, frontend, worker, asterisk-events, scheduler,
postgres, redis, nginx) ficam em uma rede Docker privada (
b2bcall-net, bridge), sem publicação de portas para o host exceto Nginx (80/443). - Postgres e Redis não publicam portas no host; acessíveis somente dentro de
b2bcall-net.
3.2 Topologia SIP
INTERNET → [OpenSIPS] → rede SIP privada → [Asterisk] → [B2BCall]
Asterisk → rede privada → Troncos SIP (operadoras)
O Asterisk nunca terá IP público. OpenSIPS e troncos são acessados via IP privado. AMI/ARI nunca expostos além do host local (bind em 127.0.0.1 ou rede interna Docker quando possível).
3.2.1 Asterisk (host network) ↔ Postgres/Redis (rede Docker interna)
Como o Asterisk usa network_mode: host, ele não enxerga a rede Docker
interna (b2bcall-net) por nome — DNS de containers só funciona para quem
está anexado à mesma rede definida pelo usuário. Solução adotada: o Postgres
publica a porta 5432 somente em 127.0.0.1 do host
(127.0.0.1:5432:5432, nunca 0.0.0.0), e o Asterisk se conecta via
ASTERISK_DB_HOST=127.0.0.1 (variável própria, distinta de
POSTGRES_HOST=postgres usada pelos demais serviços na rede interna). Isso
mantém o Postgres inacessível pela LAN/Internet, alcançável apenas por
processos no próprio host — incluindo um container em host network. Redis
não precisa disso: só a aplicação (bridge network) fala com Redis, nunca o
Asterisk diretamente.
3.3 Asterisk Realtime
- PJSIP objects (
ps_endpoints,ps_auths,ps_aors,ps_contacts,ps_endpoint_id_ips,ps_registrations) viares_odbc+res_config_odbc, schema dedicadoasteriskno mesmo Postgres. - Domínio de aplicação (campanhas, leads, agentes, filas, etc.) vive no schema
public/application. Nunca misturar as duas responsabilidades na mesma tabela.
3.4 Camada de telefonia
packages/telephony: interfaceTelephonyProvider(Originate, Hangup, QueuePause, QueueAdd/Remove, ExtensionState, DeviceState, QueueStatus, PJSIPShowEndpoints/Contacts, reload) + implementaçãoAsteriskTelephonyProvider(AMI para controle/eventos administrativos, ARI apenas quando controle fino de canais/bridges for necessário). Nenhum comando AMI é chamado diretamente de controllers da API.
3.5 Eventos em tempo real
apps/asterisk-eventsmantém conexão persistente ao AMI, normaliza eventos, persiste os relevantes e publica noapps/api(WebSocket gateway) via Redis pub/sub. Frontend nunca faz polling do Asterisk.
3.6 Motor do discador preditivo
apps/dialer-worker:PredictiveDialerEngine— decide continuamente quantas chamadas originar, usando EWMA de answer rate, TMA, abandono, CPS disponível (token bucket coordenado via Redis) e capacidade de agentes. Detalhado emdocs/PREDICTIVE_DIALER.md(a ser criado na fase correspondente).- Reserva de leads via
SELECT ... FOR UPDATE SKIP LOCKEDno Postgres — transiçãoREADY → RESERVED → DIALINGatômica, com timeout de reservation. - Lock distribuído por campanha via Redis (
dialer:campaign:{id}:lock, TTL + renewal + ownership token) para impedir dois workers controlando a mesma campanha.
3.7 Banco de dados
- PostgreSQL 17.
- Migrations obrigatórias (nenhuma alteração manual de schema).
- Particionamento temporal de
calls,call_events,queue_events,audit_logsavaliado quando o volume justificar — não implementado prematuramente, mas índices e chaves são desenhados para permitir particionamento futuro sem migração destrutiva.
3.8 Segurança desde a arquitetura
- Segredos apenas em
.env(nunca commitado). Credenciais de trunk SIP criptografadas em repouso (AES-256-GCM, master key fora do banco). - RBAC completo (users/roles/permissions/user_roles/role_permissions), reforçado no backend independentemente do frontend.
- nftables com política default-deny para serviços administrativos/telefônicos; SSH nunca bloqueado durante instalação.
4. Status
Ver TODO.md para o checklist vivo de implementação.