- apps/frontend: Next.js 15 (App Router) + Tailwind v4 + componentes estilo shadcn/ui sobre Radix UI + TanStack Query. Tema light/dark, logo processada. Menu completo (secao 52) com gating por permissao real. Todas as telas do checklist de aceite (secao 90) conectadas a endpoints reais (nao mockup): login, usuarios, perfis/permissoes, ramais/troncos, dialplan, filas/agentes, console do agente, campanhas (CPS/CSV/ iniciar/pausar), monitoramento ao vivo (polling, nao WebSocket real), TME/TMA, busca/export de chamadas, administracao do Asterisk, auditoria - infrastructure/nginx: reverse proxy colocando frontend+API na mesma origem (porta 80), antecipado da Fase 9 pois a API nao publica porta propria - apps/api: GET /api/monitoring/agents (estado corrente real via agent_state_events em aberto) e filtro queueId em GET /api/reports/calls Pendencia registrada: tela de Callbacks nao implementada (schema existe desde a Fase 6, mas nunca houve controller/service — construir a tela sem API real seria mockup). Verificacao visual em navegador nao foi possivel neste ambiente headless; validado via tsc/eslint/next build limpos + curl reproduzindo as chamadas do navegador (middleware de auth, 24 paginas protegidas via Nginx, endpoints de dados com cookie de sessao).
303 lines
8.4 KiB
YAML
303 lines
8.4 KiB
YAML
name: b2bcall
|
|
|
|
networks:
|
|
b2bcall-net:
|
|
driver: bridge
|
|
|
|
volumes:
|
|
postgres-data:
|
|
redis-data:
|
|
asterisk-lib:
|
|
asterisk-log:
|
|
asterisk-spool:
|
|
# Compartilhado entre api e asterisk: a API gera o dialplan estruturado
|
|
# aqui (agente.md seção 21) e o Asterisk inclui via extensions.conf.
|
|
dialplan-generated:
|
|
|
|
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
|
|
ports:
|
|
# Publicado SOMENTE em loopback do host: o container do Asterisk usa
|
|
# network_mode: host (necessário para RTP) e por isso não enxerga a
|
|
# rede Docker interna por nome — precisa alcançar o Postgres via
|
|
# 127.0.0.1. Isso nunca fica acessível pela LAN/Internet (não é 0.0.0.0).
|
|
- "127.0.0.1:${POSTGRES_PORT:-5432}:5432"
|
|
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
|
|
|
|
asterisk:
|
|
build:
|
|
context: .
|
|
dockerfile: infrastructure/docker/asterisk.Dockerfile
|
|
image: b2bcall-asterisk:22.10.1
|
|
container_name: b2bcall-asterisk
|
|
restart: unless-stopped
|
|
network_mode: host
|
|
# RTP exige uma faixa larga de portas UDP — inviável de mapear uma a uma
|
|
# em bridge network, por isso host mode (ver docs/ARCHITECTURE.md 3.1).
|
|
# AMI (5038), ARI (8088) e SIP (5060) ficam protegidos pelo nftables
|
|
# (infrastructure/nftables/b2bcall.nft), que bloqueia acesso pela LAN.
|
|
depends_on:
|
|
postgres:
|
|
condition: service_healthy
|
|
env_file:
|
|
- .env
|
|
environment:
|
|
# O Asterisk (host network) alcança o Postgres via loopback do host,
|
|
# não pelo nome do container (ver publicação de porta do serviço postgres).
|
|
ASTERISK_DB_HOST: ${ASTERISK_DB_HOST:-127.0.0.1}
|
|
volumes:
|
|
- ./infrastructure/asterisk/config:/etc/asterisk-templates:ro
|
|
- asterisk-lib:/var/lib/asterisk
|
|
- asterisk-log:/var/log/asterisk
|
|
- asterisk-spool:/var/spool/asterisk
|
|
- dialplan-generated:/etc/asterisk-generated
|
|
healthcheck:
|
|
test: ["CMD-SHELL", "asterisk -rx 'core show uptime' | grep -q 'System uptime'"]
|
|
interval: 15s
|
|
timeout: 5s
|
|
retries: 5
|
|
start_period: 20s
|
|
logging:
|
|
driver: json-file
|
|
options:
|
|
max-size: "10m"
|
|
max-file: "3"
|
|
deploy:
|
|
resources:
|
|
limits:
|
|
memory: 400M
|
|
|
|
api:
|
|
build:
|
|
context: .
|
|
dockerfile: infrastructure/docker/api.Dockerfile
|
|
image: b2bcall-api:0.1.0
|
|
container_name: b2bcall-api
|
|
restart: unless-stopped
|
|
networks:
|
|
- b2bcall-net
|
|
# Permite alcançar o Asterisk (network_mode: host) via AMI/ARI a partir
|
|
# da rede bridge — ver docs/ARCHITECTURE.md 3.2.1.
|
|
extra_hosts:
|
|
- "host.docker.internal:host-gateway"
|
|
depends_on:
|
|
postgres:
|
|
condition: service_healthy
|
|
redis:
|
|
condition: service_healthy
|
|
env_file:
|
|
- .env
|
|
environment:
|
|
POSTGRES_HOST: postgres
|
|
REDIS_URL: redis://:${REDIS_PASSWORD}@redis:6379
|
|
volumes:
|
|
- dialplan-generated:/etc/asterisk-generated
|
|
# Sem "ports": só o Nginx (Fase 9) expõe HTTP ao mundo externo. Em dev,
|
|
# acessar via `docker compose exec` ou publicar temporariamente.
|
|
healthcheck:
|
|
test: ["CMD", "node", "-e", "require('http').get('http://127.0.0.1:3000/api/health', r => process.exit(r.statusCode === 200 ? 0 : 1)).on('error', () => process.exit(1))"]
|
|
interval: 15s
|
|
timeout: 5s
|
|
retries: 5
|
|
start_period: 15s
|
|
logging:
|
|
driver: json-file
|
|
options:
|
|
max-size: "10m"
|
|
max-file: "3"
|
|
deploy:
|
|
resources:
|
|
limits:
|
|
memory: 300M
|
|
|
|
asterisk-events:
|
|
build:
|
|
context: .
|
|
dockerfile: infrastructure/docker/asterisk-events.Dockerfile
|
|
image: b2bcall-asterisk-events:0.1.0
|
|
container_name: b2bcall-asterisk-events
|
|
restart: unless-stopped
|
|
networks:
|
|
- b2bcall-net
|
|
extra_hosts:
|
|
- "host.docker.internal:host-gateway"
|
|
depends_on:
|
|
postgres:
|
|
condition: service_healthy
|
|
redis:
|
|
condition: service_healthy
|
|
asterisk:
|
|
condition: service_healthy
|
|
env_file:
|
|
- .env
|
|
environment:
|
|
ASTERISK_HOST: host.docker.internal
|
|
POSTGRES_HOST: postgres
|
|
REDIS_URL: redis://:${REDIS_PASSWORD}@redis:6379
|
|
logging:
|
|
driver: json-file
|
|
options:
|
|
max-size: "10m"
|
|
max-file: "3"
|
|
deploy:
|
|
resources:
|
|
limits:
|
|
memory: 150M
|
|
|
|
frontend:
|
|
build:
|
|
context: .
|
|
dockerfile: infrastructure/docker/frontend.Dockerfile
|
|
image: b2bcall-frontend:0.1.0
|
|
container_name: b2bcall-frontend
|
|
restart: unless-stopped
|
|
networks:
|
|
- b2bcall-net
|
|
depends_on:
|
|
api:
|
|
condition: service_healthy
|
|
# Sem "ports": só o Nginx expõe HTTP ao mundo externo (agente.md seção
|
|
# 87) — o browser fala só com o Nginx, nunca direto com este container.
|
|
healthcheck:
|
|
test: ["CMD", "node", "-e", "require('http').get('http://127.0.0.1:3000/login', r => process.exit(r.statusCode < 500 ? 0 : 1)).on('error', () => process.exit(1))"]
|
|
interval: 15s
|
|
timeout: 5s
|
|
retries: 5
|
|
start_period: 15s
|
|
logging:
|
|
driver: json-file
|
|
options:
|
|
max-size: "10m"
|
|
max-file: "3"
|
|
deploy:
|
|
resources:
|
|
limits:
|
|
memory: 200M
|
|
|
|
nginx:
|
|
image: nginx:1.27-alpine
|
|
container_name: b2bcall-nginx
|
|
restart: unless-stopped
|
|
networks:
|
|
- b2bcall-net
|
|
depends_on:
|
|
frontend:
|
|
condition: service_healthy
|
|
api:
|
|
condition: service_healthy
|
|
volumes:
|
|
- ./infrastructure/nginx/nginx.conf:/etc/nginx/nginx.conf:ro
|
|
ports:
|
|
# Único serviço exposto à LAN (agente.md seção 87) — todo o resto
|
|
# (api, frontend, postgres, redis, asterisk AMI/ARI) fica só na rede
|
|
# interna do Docker ou protegido por nftables.
|
|
- "80:80"
|
|
healthcheck:
|
|
test: ["CMD", "wget", "-qO-", "http://127.0.0.1:80/login"]
|
|
interval: 15s
|
|
timeout: 5s
|
|
retries: 5
|
|
start_period: 10s
|
|
logging:
|
|
driver: json-file
|
|
options:
|
|
max-size: "10m"
|
|
max-file: "3"
|
|
deploy:
|
|
resources:
|
|
limits:
|
|
memory: 64M
|
|
|
|
dialer-worker:
|
|
build:
|
|
context: .
|
|
dockerfile: infrastructure/docker/dialer-worker.Dockerfile
|
|
image: b2bcall-dialer-worker:0.1.0
|
|
container_name: b2bcall-dialer-worker
|
|
restart: unless-stopped
|
|
networks:
|
|
- b2bcall-net
|
|
extra_hosts:
|
|
- "host.docker.internal:host-gateway"
|
|
depends_on:
|
|
postgres:
|
|
condition: service_healthy
|
|
redis:
|
|
condition: service_healthy
|
|
asterisk:
|
|
condition: service_healthy
|
|
env_file:
|
|
- .env
|
|
environment:
|
|
ASTERISK_HOST: host.docker.internal
|
|
POSTGRES_HOST: postgres
|
|
REDIS_URL: redis://:${REDIS_PASSWORD}@redis:6379
|
|
logging:
|
|
driver: json-file
|
|
options:
|
|
max-size: "10m"
|
|
max-file: "3"
|
|
deploy:
|
|
resources:
|
|
limits:
|
|
memory: 150M
|