- Investigated the real callcenter_config command surface via
'help callcenter_config' on the running FreeSWITCH before writing any
code: queues only have load/unload/reload (static XML + reload, no
'queue add' exists), while agents and tiers are fully dynamic via ESL
commands (agent add, tier add) -- no file involved. This shapes the next
phase (Agents/Tiers) differently from this one.
- queues table (tenant-scoped, RLS): strategy, moh/announce, wait times,
tier rules, discard/abandoned handling, skip-agents-with-external-calls,
recording_enabled
- packages/telephony: buildQueueXml()
- infrastructure/freeswitch: our own callcenter.conf.xml override (empties
the vanilla static agents/tiers -- those become fully dynamic in the next
phase) that includes callcenter_queues.conf.d/*.xml via X-PRE-PROCESS,
same pattern as the Sofia gateway directory
- apps/api/src/queues: CRUD (POST/GET/GET:id/DELETE) using the queues.view/
.manage permissions already in the seed
- b2bcall-fs-config (queue-sync.ts): one XML file per queue on a shared
volume, synced via Redis pub/sub (b2bcall:queues:sync) on create/delete
and once at boot -- same shape as trunk-sync.ts
- confirmed manually against the real FreeSWITCH, before coding the sync
logic: 'queue load <name>' fails ('Invalid Queue not found!') for a file
added after boot -- needs 'reloadxml' first to repopulate the in-memory
XML tree from disk; after that, 'queue reload <name>' alone handles both
create and update, no need to distinguish load vs reload
- verified end-to-end: created a queue (ROUND_ROBIN, maxWaitTime=120,
discardAbandonedAfter=90), 'callcenter_config queue list' showed the
correct values on the FreeSWITCH side; deleted it, list went back empty
docs/QUEUES.md
129 lines
4.3 KiB
YAML
129 lines
4.3 KiB
YAML
services:
|
|
postgres:
|
|
image: postgres:18-alpine
|
|
container_name: b2bcall-postgres
|
|
restart: unless-stopped
|
|
environment:
|
|
POSTGRES_USER: ${POSTGRES_USER}
|
|
POSTGRES_PASSWORD: ${POSTGRES_PASSWORD}
|
|
POSTGRES_DB: ${POSTGRES_DB}
|
|
ports:
|
|
- "127.0.0.1:5432:5432"
|
|
volumes:
|
|
- postgres_data:/var/lib/postgresql
|
|
healthcheck:
|
|
test: ["CMD-SHELL", "pg_isready -U ${POSTGRES_USER} -d ${POSTGRES_DB}"]
|
|
interval: 5s
|
|
timeout: 5s
|
|
retries: 10
|
|
|
|
redis:
|
|
image: redis:7-alpine
|
|
container_name: b2bcall-redis
|
|
restart: unless-stopped
|
|
command: ["redis-server", "--requirepass", "${REDIS_PASSWORD}"]
|
|
ports:
|
|
- "127.0.0.1:6379:6379"
|
|
volumes:
|
|
- redis_data:/data
|
|
healthcheck:
|
|
test: ["CMD-SHELL", "redis-cli -a ${REDIS_PASSWORD} ping | grep -q PONG"]
|
|
interval: 5s
|
|
timeout: 5s
|
|
retries: 10
|
|
|
|
fs-config:
|
|
build:
|
|
context: .
|
|
dockerfile: apps/freeswitch-config/Dockerfile
|
|
container_name: b2bcall-fs-config
|
|
restart: unless-stopped
|
|
depends_on:
|
|
- postgres
|
|
- redis
|
|
environment:
|
|
# Hostname interno do compose (postgres/redis/freeswitch), nao
|
|
# localhost — ver docs/NETWORK_ARCHITECTURE.md.
|
|
APP_DATABASE_URL: postgresql://${POSTGRES_APP_USER}:${POSTGRES_APP_PASSWORD}@postgres:5432/${POSTGRES_DB}?schema=public
|
|
ENCRYPTION_KEY: ${ENCRYPTION_KEY}
|
|
FS_CONFIG_USER: ${FS_CONFIG_USER}
|
|
FS_CONFIG_PASSWORD: ${FS_CONFIG_PASSWORD}
|
|
REDIS_URL: redis://:${REDIS_PASSWORD}@redis:6379
|
|
ESL_HOST: freeswitch
|
|
ESL_PORT: "8021"
|
|
ESL_PASSWORD: ${ESL_PASSWORD}
|
|
SOFIA_EXTERNAL_GATEWAYS_DIR: /gateways
|
|
CALLCENTER_QUEUES_DIR: /callcenter-queues
|
|
volumes:
|
|
# Compartilhado com o FreeSWITCH (agente.md secao 41-42): fs-config
|
|
# escreve os XML de gateway aqui, o profile "external" ja inclui
|
|
# sip_profiles/external/*.xml automaticamente (config vanilla).
|
|
- freeswitch_external_gateways:/gateways
|
|
# Compartilhado com o FreeSWITCH (agente.md secao 50-51): fs-config
|
|
# escreve um XML de fila por arquivo aqui, incluido via
|
|
# X-PRE-PROCESS no nosso callcenter.conf.xml (mesmo padrao acima).
|
|
- freeswitch_callcenter_queues:/callcenter-queues
|
|
# Sem porta publicada: so o FreeSWITCH (mesma rede do compose) chama isto.
|
|
healthcheck:
|
|
test: ["CMD", "node", "-e", "fetch('http://localhost:8080/health').then(r => process.exit(r.ok ? 0 : 1)).catch(() => process.exit(1))"]
|
|
interval: 10s
|
|
timeout: 5s
|
|
retries: 10
|
|
start_period: 10s
|
|
|
|
freeswitch:
|
|
build:
|
|
context: ./infrastructure/freeswitch
|
|
secrets:
|
|
- freeswitch_pat
|
|
container_name: b2bcall-freeswitch
|
|
restart: unless-stopped
|
|
depends_on:
|
|
- fs-config
|
|
environment:
|
|
ESL_PASSWORD: ${ESL_PASSWORD}
|
|
FS_CONFIG_USER: ${FS_CONFIG_USER}
|
|
FS_CONFIG_PASSWORD: ${FS_CONFIG_PASSWORD}
|
|
volumes:
|
|
- freeswitch_external_gateways:/etc/freeswitch/sip_profiles/external
|
|
- freeswitch_callcenter_queues:/etc/freeswitch/autoload_configs/callcenter_queues.conf.d
|
|
# Nenhuma porta publicada no host: SIP/RTP ainda não têm troncos reais
|
|
# configurados, e o Event Socket (8021) só deve ser alcançável por outros
|
|
# containers na rede interna do compose (agente.md secao 22).
|
|
healthcheck:
|
|
test: ["CMD-SHELL", "fs_cli -p \"$$ESL_PASSWORD\" -x status | grep -q 'is ready'"]
|
|
interval: 10s
|
|
timeout: 5s
|
|
retries: 10
|
|
start_period: 20s
|
|
|
|
fs-events:
|
|
build:
|
|
context: .
|
|
dockerfile: apps/freeswitch-events/Dockerfile
|
|
container_name: b2bcall-fs-events
|
|
restart: unless-stopped
|
|
depends_on:
|
|
- freeswitch
|
|
- redis
|
|
- postgres
|
|
environment:
|
|
ESL_HOST: freeswitch
|
|
ESL_PORT: "8021"
|
|
ESL_PASSWORD: ${ESL_PASSWORD}
|
|
APP_DATABASE_URL: postgresql://${POSTGRES_APP_USER}:${POSTGRES_APP_PASSWORD}@postgres:5432/${POSTGRES_DB}?schema=public
|
|
# Hostnames internos do compose (freeswitch/redis), diferente do
|
|
# REDIS_URL do .env que aponta pra localhost (uso pelo apps/api, que
|
|
# ainda roda no host) — ver docs/NETWORK_ARCHITECTURE.md.
|
|
REDIS_URL: redis://:${REDIS_PASSWORD}@redis:6379
|
|
|
|
secrets:
|
|
freeswitch_pat:
|
|
environment: FREESWITCH_PAT
|
|
|
|
volumes:
|
|
postgres_data:
|
|
redis_data:
|
|
freeswitch_external_gateways:
|
|
freeswitch_callcenter_queues:
|