feat: implement Trunks with real FreeSWITCH gateway sync
- trunks table (tenant-scoped, RLS): host/proxy/realm, register, username/password_enc (AES-256-GCM), dtmf_mode, ping, transport, and a status/status_updated_at pair meant to be driven by FreeSWITCH events - apps/api/src/trunks: CRUD (POST/GET/GET:id/DELETE), same RBAC/tenant pattern as Extensions, password never exposed in any GET - packages/telephony: buildGatewayXml() generates a Sofia gateway XML file - b2bcall-fs-config now writes sip_profiles/external/<trunk_id>.xml (shared Docker volume with FreeSWITCH -- the vanilla external profile already includes external/*.xml) and runs 'sofia profile external rescan' over ESL; syncs on boot and on demand via Redis pub/sub (b2bcall:trunks:sync), since apps/api runs on the host and fs-config has no port published to reach directly - added FreeSwitchTelephonyProvider.waitUntilConnected() to fix a startup race: the first sync ran before the ESL connection had settled, logging a harmless but noisy error - verified end-to-end with a fake host: create trunk -> gateway file written -> FreeSWITCH shows the real gateway (FAIL_WAIT, expected) -> delete -> file removed (cleanup also correctly swept the stale 'example.com' gateway that had been copied into the volume from the vanilla image) - apps/freeswitch-events/src/trunk-status.ts: written to update Trunk.status from sofia::gateway_state events, using the same normalizeEslEvent path already proven for CHANNEL_* events - KNOWN GAP, documented rather than glossed over: monitored fs-events for ~90s while the gateway visibly transitioned states in FreeSWITCH (FAIL_WAIT/DOWN) and no sofia::gateway_state event was observed arriving. CUSTOM/sofia::* events have not actually been proven working end-to-end in this session -- only CHANNEL_* events have been. Needs verification against a real SIP target before the status auto-update can be trusted in production. See docs/TRUNKS.md and TODO.md. - docs/TRUNKS.md
This commit is contained in:
@@ -40,13 +40,24 @@ services:
|
||||
restart: unless-stopped
|
||||
depends_on:
|
||||
- postgres
|
||||
- redis
|
||||
environment:
|
||||
# Hostname interno do compose (postgres), nao localhost — ver
|
||||
# docs/NETWORK_ARCHITECTURE.md.
|
||||
# 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
|
||||
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
|
||||
# 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))"]
|
||||
@@ -68,6 +79,8 @@ services:
|
||||
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
|
||||
# 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).
|
||||
@@ -87,10 +100,12 @@ services:
|
||||
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.
|
||||
@@ -103,3 +118,4 @@ secrets:
|
||||
volumes:
|
||||
postgres_data:
|
||||
redis_data:
|
||||
freeswitch_external_gateways:
|
||||
|
||||
Reference in New Issue
Block a user