feat: bootstrap b2bcall architecture
- docs/ARCHITECTURE.md: decisoes de arquitetura (monorepo, rede Asterisk host mode, realtime PJSIP, camada de telefonia, seguranca desde o design) - TODO.md: checklist vivo de implementacao por fases - estrutura inicial do monorepo (apps/, packages/, infrastructure/, scripts/) - docker-compose.yml: postgres 17 + redis 7 com healthchecks, sem portas publicadas no host, schema 'asterisk' dedicado no Postgres - .env.example + scripts/generate-secrets.sh
This commit is contained in:
73
.env.example
Normal file
73
.env.example
Normal file
@@ -0,0 +1,73 @@
|
|||||||
|
# ============================================================================
|
||||||
|
# B2BCall — variáveis de ambiente (exemplo)
|
||||||
|
# Copie para `.env` e preencha os valores reais. NUNCA commitar o `.env`.
|
||||||
|
# ============================================================================
|
||||||
|
|
||||||
|
# --- Geral -------------------------------------------------------------
|
||||||
|
NODE_ENV=production
|
||||||
|
APP_NAME=B2BCall
|
||||||
|
APP_URL=http://10.10.32.142
|
||||||
|
TZ=America/Sao_Paulo
|
||||||
|
|
||||||
|
# Modo de simulação do discador: quando true, NENHUMA chamada externa real é
|
||||||
|
# originada (usado em dev/testes). Nunca deixar true em produção.
|
||||||
|
DIALER_SIMULATION=true
|
||||||
|
|
||||||
|
# Popular dados de demonstração no seed (nunca true em produção)
|
||||||
|
SEED_DEMO_DATA=false
|
||||||
|
|
||||||
|
# --- PostgreSQL ----------------------------------------------------------
|
||||||
|
POSTGRES_HOST=postgres
|
||||||
|
POSTGRES_PORT=5432
|
||||||
|
POSTGRES_DB=b2bcall
|
||||||
|
POSTGRES_USER=b2bcall
|
||||||
|
POSTGRES_PASSWORD=CHANGE_ME_STRONG_PASSWORD
|
||||||
|
DATABASE_URL=postgresql://b2bcall:CHANGE_ME_STRONG_PASSWORD@postgres:5432/b2bcall?schema=public
|
||||||
|
|
||||||
|
# Schema separado usado pelo Asterisk Realtime (res_odbc)
|
||||||
|
ASTERISK_DB_SCHEMA=asterisk
|
||||||
|
|
||||||
|
# --- Redis -----------------------------------------------------------------
|
||||||
|
REDIS_HOST=redis
|
||||||
|
REDIS_PORT=6379
|
||||||
|
REDIS_PASSWORD=CHANGE_ME_STRONG_PASSWORD
|
||||||
|
REDIS_URL=redis://:CHANGE_ME_STRONG_PASSWORD@redis:6379
|
||||||
|
|
||||||
|
# --- Autenticação / JWT ------------------------------------------------
|
||||||
|
JWT_ACCESS_SECRET=CHANGE_ME_RANDOM_64_HEX
|
||||||
|
JWT_REFRESH_SECRET=CHANGE_ME_RANDOM_64_HEX
|
||||||
|
JWT_ACCESS_TTL=15m
|
||||||
|
JWT_REFRESH_TTL=7d
|
||||||
|
COOKIE_DOMAIN=
|
||||||
|
COOKIE_SECURE=false
|
||||||
|
|
||||||
|
# Master key para criptografia de segredos (credenciais de trunk) — AES-256-GCM
|
||||||
|
SECRETS_MASTER_KEY=CHANGE_ME_RANDOM_32_BYTE_BASE64
|
||||||
|
|
||||||
|
# --- Rate limiting -----------------------------------------------------
|
||||||
|
RATE_LIMIT_LOGIN_MAX=5
|
||||||
|
RATE_LIMIT_LOGIN_WINDOW_SECONDS=60
|
||||||
|
|
||||||
|
# --- Asterisk / AMI / ARI ------------------------------------------------
|
||||||
|
ASTERISK_HOST=127.0.0.1
|
||||||
|
AMI_PORT=5038
|
||||||
|
AMI_USERNAME=b2bcall_ami
|
||||||
|
AMI_SECRET=CHANGE_ME_STRONG_PASSWORD
|
||||||
|
ARI_PORT=8088
|
||||||
|
ARI_USERNAME=b2bcall_ari
|
||||||
|
ARI_SECRET=CHANGE_ME_STRONG_PASSWORD
|
||||||
|
|
||||||
|
# --- SMTP (recuperação de senha, notificações) --------------------------
|
||||||
|
SMTP_HOST=
|
||||||
|
SMTP_PORT=587
|
||||||
|
SMTP_USER=
|
||||||
|
SMTP_PASSWORD=
|
||||||
|
SMTP_FROM=no-reply@b2bcall.local
|
||||||
|
|
||||||
|
# --- Observabilidade -----------------------------------------------------
|
||||||
|
LOG_LEVEL=info
|
||||||
|
METRICS_ENABLED=true
|
||||||
|
|
||||||
|
# --- Portas expostas pelo Nginx (host) -----------------------------------
|
||||||
|
HTTP_PORT=80
|
||||||
|
HTTPS_PORT=443
|
||||||
44
.gitignore
vendored
Normal file
44
.gitignore
vendored
Normal file
@@ -0,0 +1,44 @@
|
|||||||
|
### Env / secrets ###
|
||||||
|
.env
|
||||||
|
.env.*
|
||||||
|
!.env.example
|
||||||
|
*.pem
|
||||||
|
*.key
|
||||||
|
!infrastructure/**/*.example.key
|
||||||
|
FIRST_LOGIN.txt
|
||||||
|
|
||||||
|
### Node ###
|
||||||
|
node_modules/
|
||||||
|
dist/
|
||||||
|
build/
|
||||||
|
.next/
|
||||||
|
out/
|
||||||
|
coverage/
|
||||||
|
*.tsbuildinfo
|
||||||
|
npm-debug.log*
|
||||||
|
pnpm-debug.log*
|
||||||
|
yarn-error.log*
|
||||||
|
|
||||||
|
### Docker / infra runtime data ###
|
||||||
|
infrastructure/postgres/data/
|
||||||
|
infrastructure/asterisk/data/
|
||||||
|
infrastructure/asterisk/sounds/custom/
|
||||||
|
volumes/
|
||||||
|
|
||||||
|
### Logs ###
|
||||||
|
*.log
|
||||||
|
logs/
|
||||||
|
|
||||||
|
### OS / editor ###
|
||||||
|
.DS_Store
|
||||||
|
.vscode/
|
||||||
|
.idea/
|
||||||
|
*.swp
|
||||||
|
|
||||||
|
### Claude Code session data ###
|
||||||
|
.claude/
|
||||||
|
|
||||||
|
### Backups ###
|
||||||
|
backups/*.sql
|
||||||
|
backups/*.sql.gz
|
||||||
|
backups/*.tar.gz
|
||||||
126
TODO.md
Normal file
126
TODO.md
Normal file
@@ -0,0 +1,126 @@
|
|||||||
|
# B2BCall — TODO Operacional
|
||||||
|
|
||||||
|
Checklist vivo. Marcar `[x]` somente após testar. Não deixar item concluído sem
|
||||||
|
validação. Ver critérios de aceite completos em `docs/ARCHITECTURE.md` e no prompt
|
||||||
|
mestre original (`agente.md`, seções 90-93).
|
||||||
|
|
||||||
|
## Fase 0 — Infraestrutura base
|
||||||
|
- [x] Levantamento do servidor (SO, recursos, rede)
|
||||||
|
- [x] docs/ARCHITECTURE.md
|
||||||
|
- [x] TODO.md
|
||||||
|
- [x] Git init + .gitignore + primeiro commit
|
||||||
|
- [x] Instalar Docker Engine + Compose plugin (29.7.2 / Compose v5.5.0)
|
||||||
|
- [x] Estrutura de diretórios do monorepo
|
||||||
|
- [x] .env.example (+ scripts/generate-secrets.sh, .env real gerado localmente)
|
||||||
|
- [ ] nftables (firewall base, default-deny administrativo, sem bloquear SSH) —
|
||||||
|
adiado para a Fase 9, quando houver serviços/portas reais a proteger
|
||||||
|
|
||||||
|
## Fase 1 — Dados e cache
|
||||||
|
- [x] docker-compose: serviço postgres 17-alpine (schemas application/asterisk) — testado, healthy
|
||||||
|
- [x] docker-compose: serviço redis 7-alpine (auth, AOF, maxmemory 100mb) — testado, healthy
|
||||||
|
- [x] Health checks postgres/redis
|
||||||
|
- [ ] packages/database: schema inicial + migrations tool
|
||||||
|
|
||||||
|
## Fase 2 — Asterisk
|
||||||
|
- [ ] Dockerfile Asterisk 22 LTS (host network)
|
||||||
|
- [ ] PJSIP (transports, sem chan_sip)
|
||||||
|
- [ ] AMI habilitado (bind local/rede interna apenas)
|
||||||
|
- [ ] ARI habilitado (idem)
|
||||||
|
- [ ] Realtime ODBC -> Postgres (schema asterisk)
|
||||||
|
- [ ] CDR, CEL, queue_log habilitados
|
||||||
|
- [ ] Dialplan inicial de teste
|
||||||
|
|
||||||
|
## Fase 3 — Backend base
|
||||||
|
- [ ] apps/api (NestJS + Fastify) bootstrap
|
||||||
|
- [ ] Autenticação (Argon2id, access+refresh, cookies HttpOnly)
|
||||||
|
- [ ] Rate limiting (login e endpoints sensíveis)
|
||||||
|
- [ ] RBAC (users/roles/permissions/user_roles/role_permissions) + tela de perfis
|
||||||
|
- [ ] Auditoria (audit_logs) + interceptor genérico
|
||||||
|
- [ ] Health checks /api/health(/live|/ready)
|
||||||
|
- [ ] Swagger/OpenAPI (desabilitável em produção)
|
||||||
|
|
||||||
|
## Fase 4 — Telefonia (camada de aplicação)
|
||||||
|
- [ ] packages/telephony: TelephonyProvider + AsteriskTelephonyProvider
|
||||||
|
- [ ] apps/asterisk-events (AMI listener, normalização, persistência, pub/sub)
|
||||||
|
- [ ] CRUD Troncos (com CPS máximo, ACL, teste de status)
|
||||||
|
- [ ] CRUD Ramais (senha SIP gerada, reset, status tempo real)
|
||||||
|
- [ ] Painel visual de Ramais (WebSocket, prioridade de cores)
|
||||||
|
- [ ] Dialplan estruturado (versionado, modo Advanced, validação+rollback)
|
||||||
|
- [ ] Administração do Asterisk (abas: geral, pjsip, rtp, filas, cdr, cel, logs,
|
||||||
|
ami, ari, modules, diagnóstico com allowlist de comandos)
|
||||||
|
|
||||||
|
## Fase 5 — Call Center
|
||||||
|
- [ ] Agentes (agents, agent_sessions) separados de users
|
||||||
|
- [ ] Máquina de estados do agente (OFFLINE..PAUSED)
|
||||||
|
- [ ] Motivos de pausa (CRUD)
|
||||||
|
- [ ] Filas (CRUD + estratégias documentadas)
|
||||||
|
- [ ] Tela do agente (login dinâmico, pausa/retomada, disposição)
|
||||||
|
- [ ] Disposições de chamada (CRUD + ações: callback, DNC)
|
||||||
|
- [ ] Callback (agendamento + scheduler)
|
||||||
|
- [ ] Monitoramento de Filas (tempo real)
|
||||||
|
|
||||||
|
## Fase 6 — Campanhas e discador preditivo
|
||||||
|
- [ ] CRUD Campanhas (todos os campos da seção 24)
|
||||||
|
- [ ] Import CSV streaming (preview, mapeamento, validação, duplicados, rejeitados)
|
||||||
|
- [ ] Normalização de telefone (serviço dedicado, BR inicialmente)
|
||||||
|
- [ ] Lista de supressão (DNC) + checagem obrigatória pré-originação
|
||||||
|
- [ ] CPS limiter (token bucket, coordenado via Redis, multi-worker)
|
||||||
|
- [ ] Reserva concorrente de leads (FOR UPDATE SKIP LOCKED + timeout)
|
||||||
|
- [ ] Idempotência de originação (attempt_id/call_id/uniqueid/linkedid, state machine)
|
||||||
|
- [ ] PredictiveDialerEngine (EWMA, pacing, previsão de liberação de agentes)
|
||||||
|
- [ ] Controle de abandono (pacing cai / suspende originação)
|
||||||
|
- [ ] AMD opcional por campanha
|
||||||
|
- [ ] Wrap-up time
|
||||||
|
- [ ] Retry engine (regras por causa, limite de tentativas)
|
||||||
|
- [ ] Horário de campanha (timezone, dias/horários, WAITING_SCHEDULE)
|
||||||
|
- [ ] Lock distribuído por campanha (Redis)
|
||||||
|
- [ ] docs/PREDICTIVE_DIALER.md
|
||||||
|
|
||||||
|
## Fase 7 — CDR, métricas e relatórios
|
||||||
|
- [ ] Modelo consolidado de chamadas (CDR+CEL+AMI+queue_log)
|
||||||
|
- [ ] Reconciliação de estados órfãos após restart
|
||||||
|
- [ ] TME / TMA (definições documentadas, cálculo correto)
|
||||||
|
- [ ] Relatório de Chamadas (filtros, paginação server-side, export CSV)
|
||||||
|
- [ ] Relatório de Agentes (tempos, pausas detalhadas)
|
||||||
|
- [ ] Dashboard geral (cards + gráficos reais)
|
||||||
|
- [ ] Dashboard do discador (por campanha, tempo real)
|
||||||
|
- [ ] Compliance de Chamadas (parâmetros configuráveis, alertas, contadores)
|
||||||
|
|
||||||
|
## Fase 8 — Frontend completo
|
||||||
|
- [ ] Bootstrap Next.js + Tailwind + shadcn/ui + TanStack Query + WS client
|
||||||
|
- [ ] Logo processada (b2bcall.png) + tema light/dark
|
||||||
|
- [ ] Menu completo (seção 52)
|
||||||
|
- [ ] Todas as telas do checklist de aceite (seção 90)
|
||||||
|
|
||||||
|
## Fase 9 — Segurança e produção
|
||||||
|
- [ ] Criptografia de segredos de trunk (AES-256-GCM)
|
||||||
|
- [ ] HTTP security headers, CORS, CSRF, Helmet
|
||||||
|
- [ ] nftables final revisado
|
||||||
|
- [ ] Logs estruturados JSON (sem segredos)
|
||||||
|
- [ ] Correlation IDs (request_id/attempt_id/call_id)
|
||||||
|
- [ ] Métricas Prometheus (/metrics)
|
||||||
|
- [ ] Bootstrap super_admin (senha aleatória, FIRST_LOGIN.txt, forçar troca)
|
||||||
|
- [ ] Seed (permissões, perfis, pausas, disposições) sem dados fake em produção
|
||||||
|
- [ ] Backup/restore (postgres, asterisk config, .env seguro)
|
||||||
|
- [ ] scripts/install.sh, update.sh, backup.sh, restore.sh, healthcheck.sh
|
||||||
|
- [ ] Nginx reverse proxy (80/443, WS, HTTPS documentado)
|
||||||
|
|
||||||
|
## Fase 10 — Testes e aceite
|
||||||
|
- [ ] Unit tests (predictive engine, CPS limiter, permissions, phone norm, retry,
|
||||||
|
state machines, TME/TMA, scheduling)
|
||||||
|
- [ ] Integration tests (postgres, redis, repositories, API, AMI mock)
|
||||||
|
- [ ] E2E (login → ... → RBAC, conforme seção 64)
|
||||||
|
- [ ] Modo simulação (DIALER_SIMULATION=true) + testes do predictive engine
|
||||||
|
- [ ] Prova de CPS respeitado / concorrência máxima / sem discagem dupla /
|
||||||
|
pausa efetiva / recuperação após restart / pacing reage a abandono /
|
||||||
|
supressão respeitada / horário respeitado
|
||||||
|
- [ ] Quality gate (lint, typecheck, unit, integration, e2e, compose config,
|
||||||
|
compose ps, health checks) — tudo verde
|
||||||
|
- [ ] Aceite de segurança (seção 92)
|
||||||
|
- [ ] Aceite Asterisk (seção 93, comandos documentados)
|
||||||
|
- [ ] README final completo
|
||||||
|
- [ ] Relatório final da implementação
|
||||||
|
|
||||||
|
---
|
||||||
|
**Nota de ambiente:** VM atual com 1.9 GiB RAM / 2 vCPU — adequada para dev e
|
||||||
|
simulação, não para carga real de produção. Ver docs/ARCHITECTURE.md seção 1.
|
||||||
BIN
b2bcall.png
Normal file
BIN
b2bcall.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 766 KiB |
73
docker-compose.yml
Normal file
73
docker-compose.yml
Normal file
@@ -0,0 +1,73 @@
|
|||||||
|
name: b2bcall
|
||||||
|
|
||||||
|
networks:
|
||||||
|
b2bcall-net:
|
||||||
|
driver: bridge
|
||||||
|
|
||||||
|
volumes:
|
||||||
|
postgres-data:
|
||||||
|
redis-data:
|
||||||
|
|
||||||
|
services:
|
||||||
|
postgres:
|
||||||
|
image: postgres:17-alpine
|
||||||
|
container_name: b2bcall-postgres
|
||||||
|
restart: unless-stopped
|
||||||
|
environment:
|
||||||
|
POSTGRES_DB: ${POSTGRES_DB:-b2bcall}
|
||||||
|
POSTGRES_USER: ${POSTGRES_USER:-b2bcall}
|
||||||
|
POSTGRES_PASSWORD: ${POSTGRES_PASSWORD:?POSTGRES_PASSWORD is required}
|
||||||
|
volumes:
|
||||||
|
- postgres-data:/var/lib/postgresql/data
|
||||||
|
- ./infrastructure/postgres/init:/docker-entrypoint-initdb.d:ro
|
||||||
|
networks:
|
||||||
|
- b2bcall-net
|
||||||
|
# Sem "ports": Postgres não deve ficar acessível fora da rede Docker interna.
|
||||||
|
healthcheck:
|
||||||
|
test: ["CMD-SHELL", "pg_isready -U ${POSTGRES_USER:-b2bcall} -d ${POSTGRES_DB:-b2bcall}"]
|
||||||
|
interval: 10s
|
||||||
|
timeout: 5s
|
||||||
|
retries: 5
|
||||||
|
start_period: 10s
|
||||||
|
logging:
|
||||||
|
driver: json-file
|
||||||
|
options:
|
||||||
|
max-size: "10m"
|
||||||
|
max-file: "3"
|
||||||
|
deploy:
|
||||||
|
resources:
|
||||||
|
limits:
|
||||||
|
memory: 512M
|
||||||
|
|
||||||
|
redis:
|
||||||
|
image: redis:7-alpine
|
||||||
|
container_name: b2bcall-redis
|
||||||
|
restart: unless-stopped
|
||||||
|
environment:
|
||||||
|
REDIS_PASSWORD: ${REDIS_PASSWORD:?REDIS_PASSWORD is required}
|
||||||
|
command: >
|
||||||
|
redis-server
|
||||||
|
--requirepass ${REDIS_PASSWORD:?REDIS_PASSWORD is required}
|
||||||
|
--maxmemory 100mb
|
||||||
|
--maxmemory-policy noeviction
|
||||||
|
--appendonly yes
|
||||||
|
volumes:
|
||||||
|
- redis-data:/data
|
||||||
|
networks:
|
||||||
|
- b2bcall-net
|
||||||
|
# Sem "ports": Redis não deve ficar acessível fora da rede Docker interna.
|
||||||
|
healthcheck:
|
||||||
|
test: ["CMD-SHELL", "redis-cli -a $$REDIS_PASSWORD --no-auth-warning ping | grep -q PONG"]
|
||||||
|
interval: 10s
|
||||||
|
timeout: 5s
|
||||||
|
retries: 5
|
||||||
|
start_period: 5s
|
||||||
|
logging:
|
||||||
|
driver: json-file
|
||||||
|
options:
|
||||||
|
max-size: "10m"
|
||||||
|
max-file: "3"
|
||||||
|
deploy:
|
||||||
|
resources:
|
||||||
|
limits:
|
||||||
|
memory: 160M
|
||||||
142
docs/ARCHITECTURE.md
Normal file
142
docs/ARCHITECTURE.md
Normal file
@@ -0,0 +1,142 @@
|
|||||||
|
# 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-size` limitado) — builds Next.js podem estourar RAM em VMs de
|
||||||
|
2 GiB.
|
||||||
|
- `docker-compose.yml` define `mem_limit`/`deploy.resources.limits` conservadores 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
|
||||||
|
|
||||||
|
```text
|
||||||
|
/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
|
||||||
|
|
||||||
|
```text
|
||||||
|
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.3 Asterisk Realtime
|
||||||
|
|
||||||
|
- PJSIP objects (`ps_endpoints`, `ps_auths`, `ps_aors`, `ps_contacts`,
|
||||||
|
`ps_endpoint_id_ips`, `ps_registrations`) via `res_odbc` + `res_config_odbc`,
|
||||||
|
schema dedicado `asterisk` no 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`: interface `TelephonyProvider` (Originate, Hangup,
|
||||||
|
QueuePause, QueueAdd/Remove, ExtensionState, DeviceState, QueueStatus,
|
||||||
|
PJSIPShowEndpoints/Contacts, reload) + implementação `AsteriskTelephonyProvider`
|
||||||
|
(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-events` mantém conexão persistente ao AMI, normaliza eventos,
|
||||||
|
persiste os relevantes e publica no `apps/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 em
|
||||||
|
`docs/PREDICTIVE_DIALER.md` (a ser criado na fase correspondente).
|
||||||
|
- Reserva de leads via `SELECT ... FOR UPDATE SKIP LOCKED` no Postgres —
|
||||||
|
transição `READY → RESERVED → DIALING` atô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_logs`
|
||||||
|
avaliado 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.
|
||||||
14
infrastructure/postgres/init/001-schemas.sql
Normal file
14
infrastructure/postgres/init/001-schemas.sql
Normal file
@@ -0,0 +1,14 @@
|
|||||||
|
-- Executado automaticamente na primeira inicialização do container Postgres
|
||||||
|
-- (docker-entrypoint-initdb.d). Idempotente por natureza do entrypoint oficial
|
||||||
|
-- (só roda quando o data directory está vazio).
|
||||||
|
|
||||||
|
-- Schema dedicado ao Asterisk Realtime (ps_endpoints, ps_auths, ps_aors, ...).
|
||||||
|
-- O domínio de aplicação (campanhas, leads, agentes, etc.) vive em "public".
|
||||||
|
CREATE SCHEMA IF NOT EXISTS asterisk;
|
||||||
|
|
||||||
|
-- Extensões necessárias para o domínio da aplicação.
|
||||||
|
CREATE EXTENSION IF NOT EXISTS pgcrypto; -- gen_random_uuid(), criptografia auxiliar
|
||||||
|
CREATE EXTENSION IF NOT EXISTS "uuid-ossp";
|
||||||
|
|
||||||
|
GRANT ALL ON SCHEMA asterisk TO CURRENT_USER;
|
||||||
|
GRANT ALL ON SCHEMA public TO CURRENT_USER;
|
||||||
15
scripts/generate-secrets.sh
Executable file
15
scripts/generate-secrets.sh
Executable file
@@ -0,0 +1,15 @@
|
|||||||
|
#!/usr/bin/env bash
|
||||||
|
# Gera valores fortes para os segredos do .env.
|
||||||
|
# Uso: ./scripts/generate-secrets.sh (imprime pares KEY=VALUE para colar no .env)
|
||||||
|
set -euo pipefail
|
||||||
|
|
||||||
|
rand_hex() { openssl rand -hex "${1:-32}"; }
|
||||||
|
rand_b64() { openssl rand -base64 "${1:-32}"; }
|
||||||
|
|
||||||
|
echo "POSTGRES_PASSWORD=$(rand_hex 24)"
|
||||||
|
echo "REDIS_PASSWORD=$(rand_hex 24)"
|
||||||
|
echo "JWT_ACCESS_SECRET=$(rand_hex 64)"
|
||||||
|
echo "JWT_REFRESH_SECRET=$(rand_hex 64)"
|
||||||
|
echo "SECRETS_MASTER_KEY=$(rand_b64 32)"
|
||||||
|
echo "AMI_SECRET=$(rand_hex 24)"
|
||||||
|
echo "ARI_SECRET=$(rand_hex 24)"
|
||||||
Reference in New Issue
Block a user