feat: add asterisk pjsip integration

- 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.
This commit is contained in:
2026-08-27 10:47:28 -03:00
parent 8d522e93a3
commit eee6d7aece
25 changed files with 675 additions and 8 deletions

View File

@@ -0,0 +1,13 @@
; ARI (Asterisk REST Interface). Usado apenas quando controle fino de
; canais/bridges exigir (ver packages/telephony/AsteriskTelephonyProvider).
; Protegido pelo nftables da mesma forma que o AMI (porta 8088 bloqueada na
; interface da LAN, liberada apenas em loopback/rede interna Docker).
[general]
enabled = yes
pretty = yes
allowed_origins =
[${ARI_USERNAME}]
type = user
read_only = no
password = ${ARI_SECRET}

View File

@@ -0,0 +1,6 @@
[general]
enable = yes
unanswered = yes
congestion = yes
endbeforehexten = no
initiatedseconds = no

View File

@@ -0,0 +1,7 @@
; CDR via Adaptive ODBC: introspecciona as colunas reais da tabela
; asterisk.cdr (start/answer/end/uniqueid/linkedid/...) em vez de usar o
; esquema legado fixo do cdr_odbc (que exige colunas antigas como
; "calldate" e não suporta uniqueid/linkedid).
[asterisk]
connection = asterisk
table = cdr

View File

@@ -0,0 +1,4 @@
[general]
enable = yes
apps = Dial,Queue,Bridge,Park,ParkedCall,AGI,VoiceMail
events = CHAN_START,CHAN_END,ANSWER,HANGUP,APP_START,APP_END,BRIDGE_ENTER,BRIDGE_EXIT,PARK_START,PARK_END,BLINDTRANSFER,ATTENDEDTRANSFER

View File

@@ -0,0 +1,4 @@
; CEL gravado via ODBC, mesma conexão "asterisk" (ver cdr_odbc.conf).
[global]
connection = asterisk
table = cel

View File

@@ -0,0 +1,10 @@
; Mapeia as famílias de configuração realtime para a conexão ODBC "asterisk"
; (definida em res_odbc.conf) e a respectiva tabela no schema "asterisk" do
; Postgres. Usado pelos objetos PJSIP referenciados em sorcery.conf.
[settings]
ps_endpoints => odbc,asterisk,ps_endpoints
ps_auths => odbc,asterisk,ps_auths
ps_aors => odbc,asterisk,ps_aors
ps_contacts => odbc,asterisk,ps_contacts
ps_endpoint_id_ips => odbc,asterisk,ps_endpoint_id_ips
ps_registrations => odbc,asterisk,ps_registrations

View File

@@ -0,0 +1,21 @@
; Dialplan mínimo de validação da Fase 2 (infraestrutura). O editor
; estruturado + versionamento (dialplan_versions) chega na Fase 4 —
; Telefonia, e substituirá este arquivo estático por dialplan gerado pela
; aplicação (AEL/realtime), com backup/validação/rollback antes de ativar.
[general]
static = yes
writeprotect = no
[globals]
; Contexto de teste: confirma que o core do dialplan está funcionando.
[b2bcall-healthcheck]
exten => 600,1,Answer()
same => n,Playback(demo-echotest)
same => n,Echo()
same => n,Hangup()
; Placeholder para o contexto de discagem outbound (Fase 6 — Campanhas).
[outbound]
exten => _X.,1,NoOp(B2BCall outbound placeholder - ${EXTEN})
same => n,Hangup()

View File

@@ -0,0 +1,10 @@
; Servidor HTTP embutido do Asterisk — necessário para ARI (res_ari) e para o
; WebSocket usado por ARI. Mesma política de exposição do AMI/ARI: bind em
; 0.0.0.0 pois o container roda em network_mode: host, mas o nftables bloqueia
; acesso pela interface da LAN (ver infrastructure/nftables/b2bcall.nft).
[general]
enabled = yes
bindaddr = 0.0.0.0
bindport = 8088
prefix =
enablestatic = no

View File

@@ -0,0 +1,7 @@
[general]
dateformat = %F %T
[logfiles]
console => notice,warning,error
messages => notice,warning,error
queue_log => queue_log

View File

@@ -0,0 +1,19 @@
; AMI (Asterisk Manager Interface).
; NUNCA exposto à LAN/Internet: bind em 0.0.0.0 apenas porque o container usa
; network_mode: host, mas o acesso externo é bloqueado pelo nftables
; (infrastructure/nftables/b2bcall.nft) — só loopback e a rede interna dos
; containers Docker (172.16.0.0/12) alcançam a porta 5038.
[general]
enabled = yes
port = 5038
bindaddr = 0.0.0.0
displayconnects = no
webenabled = no
[${AMI_USERNAME}]
secret = ${AMI_SECRET}
deny = 0.0.0.0/0.0.0.0
permit = 127.0.0.1/255.255.255.255
permit = 172.16.0.0/255.240.0.0
read = system,call,agent,user,config,command,dtmf,reporting,cdr,dialplan,originate
write = system,call,agent,user,config,command,dtmf,reporting,originate

View File

@@ -0,0 +1,12 @@
; DSN do sistema usado por res_odbc (conexão nomeada "asterisk" em
; res_odbc.conf). "ConnSettings" ajusta o search_path para o schema dedicado
; "asterisk" sem misturar com o schema de domínio da aplicação ("public").
[asterisk]
Description = B2BCall Asterisk Realtime (PostgreSQL)
Driver = PostgreSQL Unicode
Servername = ${ASTERISK_DB_HOST}
Port = ${POSTGRES_PORT}
Database = ${POSTGRES_DB}
Username = ${POSTGRES_USER}
Password = ${POSTGRES_PASSWORD}
ConnSettings = SET search_path TO asterisk, public;

View File

@@ -0,0 +1,13 @@
; Transporte PJSIP estático (transportes não são gerenciados via realtime).
; Endpoints/auths/aors/contacts SÃO realtime (ver sorcery.conf +
; extconfig.conf) — cadastrados pela aplicação (Fase 4 — Telefonia).
;
; O Asterisk não possui IP público: este transporte escuta na rede interna/
; privada. SIP só deve ser alcançável a partir do OpenSIPS e de troncos
; explicitamente autorizados (ver infrastructure/nftables/b2bcall.nft —
; atualmente bloqueado por padrão na interface da LAN até essas origens
; serem conhecidas).
[transport-udp]
type = transport
protocol = udp
bind = 0.0.0.0:5060

View File

@@ -0,0 +1,8 @@
; Configuração global de filas. As filas em si (Fase 5 — Call Center) serão
; gerenciadas pela aplicação. queue_log é usado por app_queue por padrão
; (grava em /var/log/asterisk/queue_log) e será consumido pelo
; apps/asterisk-events para reconciliação (ver docs/ARCHITECTURE.md).
[general]
persistentmembers = yes
autofill = yes
shared_lastcall = yes

View File

@@ -0,0 +1,9 @@
; Conexão ODBC nomeada "asterisk", usada pelo Realtime (PJSIP, CDR, CEL) via
; res_config_odbc. As credenciais reais ficam no DSN do sistema (/etc/odbc.ini,
; renderizado a partir de infrastructure/asterisk/config/odbc.ini.tpl) — este
; arquivo não contém segredos.
[asterisk]
enabled => yes
dsn => asterisk
pre-connect => yes
sanitysql => select 1

View File

@@ -0,0 +1,11 @@
; Mapeia os objetos PJSIP (sorcery) para o backend realtime/ODBC. As tabelas
; físicas (ps_endpoints, ps_auths, ...) vivem no schema "asterisk" do Postgres
; (ver infrastructure/postgres/init/002-asterisk-realtime.sql) e o mapeamento
; nome-lógico -> conexão ODBC/tabela está em extconfig.conf.
[res_pjsip]
endpoint = realtime,ps_endpoints
auth = realtime,ps_auths
aor = realtime,ps_aors
contact = realtime,ps_contacts
endpoint_id_ip = realtime,ps_endpoint_id_ips
registration = realtime,ps_registrations