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

17
.dockerignore Normal file
View File

@@ -0,0 +1,17 @@
.git
.claude
.env
.env.*
!.env.example
node_modules
**/node_modules
dist
**/dist
.next
**/.next
build
**/build
coverage
**/coverage
*.log
docs

View File

@@ -27,6 +27,11 @@ DATABASE_URL=postgresql://b2bcall:CHANGE_ME_STRONG_PASSWORD@postgres:5432/b2bcal
# Schema separado usado pelo Asterisk Realtime (res_odbc)
ASTERISK_DB_SCHEMA=asterisk
# Host que o container do Asterisk (network_mode: host) usa para alcançar o
# Postgres — via loopback do host, já que ele não enxerga a rede Docker
# interna por nome (ver docker-compose.yml e docs/ARCHITECTURE.md).
ASTERISK_DB_HOST=127.0.0.1
# --- Redis -----------------------------------------------------------------
REDIS_HOST=redis
REDIS_PORT=6379

40
TODO.md
View File

@@ -22,13 +22,39 @@ mestre original (`agente.md`, seções 90-93).
- [ ] 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
- [x] Dockerfile Asterisk 22.10.1 LTS compilado do fonte (host network)
Debian trixie não tem mais o pacote `asterisk` nos repos oficiais
- [x] PJSIP (transport UDP:5060; chan_sip nem existe mais a partir do Ast 21)
- [x] AMI habilitado — testado login via 127.0.0.1:5038, bloqueado na LAN via nftables
- [x] ARI habilitado — testado GET /ari/asterisk/info via 127.0.0.1:8088, 401 sem auth
- [x] Realtime ODBC -> Postgres schema `asterisk` (sorcery.conf + extconfig.conf
+ res_odbc.conf) — `pjsip show endpoints` consulta o Postgres sem erro
- [x] CDR via cdr_adaptive_odbc — testado, grava em asterisk.cdr (ANSWERED,
duration/billsec corretos). Nota: coluna `end` renomeada para
`end_time` (palavra reservada no Postgres, cdr_adaptive_odbc não
quota identificadores)
- [x] CEL via cel_odbc — testado, grava em asterisk.cel (CHAN_START/ANSWER/
HANGUP/CHAN_END)
- [x] queue_log habilitado (padrão do app_queue, arquivo em /var/log/asterisk)
- [x] Dialplan inicial de teste (contexto `b2bcall-healthcheck`, exten 600,
Answer/Playback/Echo) — testado via `channel originate ... extension`
- [x] nftables protegendo AMI/ARI/SIP na interface da LAN (ens18), trazido da
Fase 9 para já — não testado a partir de outra máquina na LAN (só
localmente, onde tráfego para o próprio IP vai por `lo` e não
exercita a regra; revisar quando OpenSIPS/outro host estiver disponível)
### Pendências conhecidas da Fase 2 (não bloqueiam, revisitar depois)
- [ ] `res_pjsip_outbound_registration` recusa carregar no boot (mensagem
"no existing documentation" para o tipo 'registration' do sorcery) —
carregamento manual via `module load` funciona mas é desfeito por uma
flag interna de "declined at boot". Só afeta troncos com registration
outbound; endpoints/aors funcionam normalmente. Investigar na Fase 4
ao implementar cadastro de troncos com registration.
- [ ] `res_config_ldap` gera ERROR de log no boot (sem LDAP configurado,
não usamos). Cosmético — considerar `noload` em modules.conf.
- [ ] `cdr_pgsql`/`cel_pgsql` não compilaram (falta libpq-dev na imagem);
usamos `cdr_adaptive_odbc`/`cel_odbc` como alternativa funcional via
ODBC — decisão adequada, não precisa dos módulos pgsql nativos.
## Fase 3 — Backend base
- [ ] apps/api (NestJS + Fastify) bootstrap

View File

@@ -7,6 +7,9 @@ networks:
volumes:
postgres-data:
redis-data:
asterisk-lib:
asterisk-log:
asterisk-spool:
services:
postgres:
@@ -22,7 +25,12 @@ services:
- ./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.
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
@@ -71,3 +79,45 @@ services:
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
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

View File

@@ -84,6 +84,20 @@ O Asterisk nunca terá IP público. OpenSIPS e troncos são acessados via IP pri
AMI/ARI nunca expostos além do host local (bind em 127.0.0.1 ou rede interna Docker
quando possível).
### 3.2.1 Asterisk (host network) ↔ Postgres/Redis (rede Docker interna)
Como o Asterisk usa `network_mode: host`, ele não enxerga a rede Docker
interna (`b2bcall-net`) por nome — DNS de containers só funciona para quem
está anexado à mesma rede definida pelo usuário. Solução adotada: o Postgres
publica a porta 5432 **somente em `127.0.0.1` do host**
(`127.0.0.1:5432:5432`, nunca `0.0.0.0`), e o Asterisk se conecta via
`ASTERISK_DB_HOST=127.0.0.1` (variável própria, distinta de
`POSTGRES_HOST=postgres` usada pelos demais serviços na rede interna). Isso
mantém o Postgres inacessível pela LAN/Internet, alcançável apenas por
processos no próprio host — incluindo um container em host network. Redis
não precisa disso: só a aplicação (bridge network) fala com Redis, nunca o
Asterisk diretamente.
### 3.3 Asterisk Realtime
- PJSIP objects (`ps_endpoints`, `ps_auths`, `ps_aors`, `ps_contacts`,

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

View File

@@ -0,0 +1,33 @@
#!/bin/bash
# Renderiza os templates de configuração (que contêm variáveis ${VAR}) usando
# as variáveis de ambiente do container, para nunca versionar segredos em
# texto puro nos arquivos .conf. /etc/asterisk já vem populado pelo `make
# samples` no build da imagem — aqui só sobrepomos/adicionamos os arquivos
# específicos do B2BCall (o restante dos samples permanece intacto).
set -euo pipefail
CONFIG_SRC=/etc/asterisk-templates
CONFIG_DST=/etc/asterisk
mkdir -p "$CONFIG_DST"
# odbc.ini é configuração do sistema (unixODBC), não do Asterisk.
if [ -e "$CONFIG_SRC/odbc.ini.tpl" ]; then
envsubst < "$CONFIG_SRC/odbc.ini.tpl" > /etc/odbc.ini
fi
for tpl in "$CONFIG_SRC"/*.conf.tpl; do
[ -e "$tpl" ] || continue
name="$(basename "$tpl" .tpl)"
envsubst < "$tpl" > "$CONFIG_DST/$name"
done
for f in "$CONFIG_SRC"/*.conf; do
[ -e "$f" ] || continue
name="$(basename "$f")"
cp "$f" "$CONFIG_DST/$name"
done
chown -R asterisk:asterisk "$CONFIG_DST" /var/lib/asterisk /var/log/asterisk /var/spool/asterisk /var/run/asterisk
exec "$@"

View File

@@ -0,0 +1,70 @@
# Asterisk 22 LTS compilado a partir do fonte oficial.
# Debian não distribui mais o pacote `asterisk` em seus repositórios, então
# compilamos aqui com controle total dos módulos: PJSIP (chan_sip não existe
# mais a partir do Asterisk 21), AMI, ARI, Realtime ODBC (Postgres), CDR, CEL.
FROM debian:trixie-slim
ARG ASTERISK_VERSION=22.10.1
ENV DEBIAN_FRONTEND=noninteractive
RUN apt-get update && apt-get install -y --no-install-recommends \
build-essential wget ca-certificates git subversion patch \
pkg-config autoconf automake libtool \
libxml2-dev libsqlite3-dev uuid-dev libjansson-dev libedit-dev \
unixodbc unixodbc-dev odbc-postgresql \
libsrtp2-dev libssl-dev libncurses-dev libnewt-dev \
libspeex-dev libspeexdsp-dev libogg-dev libvorbis-dev \
libcurl4-openssl-dev libsystemd-dev gettext-base \
odbc-postgresql \
&& rm -rf /var/lib/apt/lists/*
WORKDIR /usr/src
RUN wget -q "https://downloads.asterisk.org/pub/telephony/asterisk/asterisk-${ASTERISK_VERSION}.tar.gz" \
&& tar xzf asterisk-${ASTERISK_VERSION}.tar.gz \
&& rm asterisk-${ASTERISK_VERSION}.tar.gz \
&& mv asterisk-${ASTERISK_VERSION} asterisk
WORKDIR /usr/src/asterisk
# Instala dependências adicionais específicas da distro (detectadas pelo
# próprio script oficial do Asterisk) sem prompts interativos.
RUN contrib/scripts/install_prereq install --yes || true
RUN ./configure --with-jansson-bundled --with-pjproject-bundled --with-crypto --with-ssl=ssl
RUN make menuselect.makeopts \
&& menuselect/menuselect \
--enable res_odbc \
--enable res_config_odbc \
--enable cdr_odbc \
--enable cdr_pgsql \
--enable cel_odbc \
--enable cel_pgsql \
--enable app_queue \
--enable res_pjsip \
--enable res_pjsip_session \
--enable res_ari \
--enable res_ari_applications \
--enable res_ari_channels \
--enable res_ari_bridges \
--enable CORE-SOUNDS-EN-WAV \
--enable MOH-OPSOUND-WAV \
menuselect.makeopts
RUN make -j"$(nproc)"
RUN make install
RUN make samples
RUN ldconfig
RUN groupadd -r asterisk && useradd -r -g asterisk -d /var/lib/asterisk asterisk \
&& mkdir -p /etc/asterisk /var/lib/asterisk /var/log/asterisk /var/spool/asterisk /var/run/asterisk \
&& chown -R asterisk:asterisk /etc/asterisk /var/lib/asterisk /var/log/asterisk /var/spool/asterisk /var/run/asterisk /usr/lib/asterisk
EXPOSE 5060/udp 5060/tcp 5061/tcp 8088/tcp 8089/tcp 5038/tcp
EXPOSE 10000-20000/udp
COPY infrastructure/docker/asterisk-entrypoint.sh /usr/local/bin/asterisk-entrypoint.sh
RUN chmod +x /usr/local/bin/asterisk-entrypoint.sh
ENTRYPOINT ["/usr/local/bin/asterisk-entrypoint.sh"]
CMD ["asterisk", "-f", "-vvv", "-U", "asterisk", "-G", "asterisk"]

View File

@@ -0,0 +1,15 @@
[Unit]
Description=B2BCall custom nftables rules (protects Asterisk admin ports)
After=docker.service network-online.target
Wants=network-online.target
PartOf=docker.service
[Service]
Type=oneshot
RemainAfterExit=yes
ExecStartPre=-/usr/sbin/nft delete table inet b2bcall_fw
ExecStart=/usr/sbin/nft -f /opt/b2bcall/infrastructure/nftables/b2bcall.nft
ExecStop=/usr/sbin/nft delete table inet b2bcall_fw
[Install]
WantedBy=multi-user.target

View File

@@ -0,0 +1,47 @@
#!/usr/sbin/nft -f
# Regras de firewall do B2BCall.
#
# IMPORTANTE: este arquivo NUNCA usa "flush ruleset" — o Docker gerencia suas
# próprias tabelas iptables-nft (visíveis como "table ip nat" / "table ip
# filter" com o aviso "managed by iptables-nft, do not touch"). Uma tabela
# separada (`inet b2bcall_fw`) convive com elas sem conflito: cada tabela
# anexada ao hook "input" é avaliada em ordem de prioridade; um veredito de
# "drop" nesta tabela encerra o processamento do pacote antes mesmo de ele
# chegar às regras do Docker, sem jamais apagar as regras do Docker.
#
# SSH (porta 22) é sempre permitido — nunca deve ficar bloqueado.
#
# Instalado/atualizado via systemd unit b2bcall-nftables.service (ver
# infrastructure/nftables/b2bcall-nftables.service), que roda:
# nft delete table inet b2bcall_fw (ignora erro se não existir)
# nft -f este arquivo
# após o docker.service subir.
table inet b2bcall_fw {
chain input_admin_protect {
type filter hook input priority filter - 1; policy accept;
# Loopback sempre liberado
iifname "lo" accept
# Conexões já estabelecidas / relacionadas sempre liberadas
ct state established,related accept
# Redes internas dos bridges Docker (comunicação entre containers e
# entre containers <-> serviços do Asterisk em host network)
ip saddr 172.16.0.0/12 accept
# SSH nunca é bloqueado
tcp dport 22 accept
# --- Serviços administrativos/telefônicos sensíveis do Asterisk ---
# Nunca devem ser alcançáveis pela LAN (interface ens18). Habilitar
# exceções pontuais aqui somente quando o IP do OpenSIPS/tronco for
# conhecido e explicitamente autorizado (ver docs/OPENSIPS.md, a
# ser criado na Fase 2 de telefonia).
iifname "ens18" tcp dport { 5038, 8088, 8089 } counter drop
iifname "ens18" tcp dport { 5060, 5061 } counter drop
iifname "ens18" udp dport 5060 counter drop
iifname "ens18" udp dport 10000-20000 counter drop
}
}

View File

@@ -0,0 +1,236 @@
-- Tabelas de Realtime do Asterisk (PJSIP via res_config_odbc) + CDR/CEL
-- (cdr_pgsql / cel_pgsql), todas no schema "asterisk" (nunca em "public",
-- que é reservado ao domínio da aplicação — ver docs/ARCHITECTURE.md).
--
-- Colunas seguem o schema oficial documentado pelo Asterisk 22 para
-- res_pjsip realtime (sorcery.conf + extconfig.conf apontam para estas
-- tabelas). Caso uma coluna adicional seja necessária futuramente
-- (ex.: campo específico de um módulo PJSIP menos comum), ela deve ser
-- adicionada via migration, nunca editada manualmente em produção.
SET search_path TO asterisk, public;
-- ---------------------------------------------------------------------
-- ps_endpoints
-- ---------------------------------------------------------------------
CREATE TABLE IF NOT EXISTS asterisk.ps_endpoints (
id VARCHAR(255) PRIMARY KEY,
transport VARCHAR(255),
aors VARCHAR(255),
auth VARCHAR(255),
context VARCHAR(255) DEFAULT 'default',
disallow VARCHAR(255) DEFAULT 'all',
allow VARCHAR(255),
direct_media VARCHAR(3) DEFAULT 'yes',
connected_line_method VARCHAR(255),
direct_media_method VARCHAR(255),
direct_media_glare_mitigation VARCHAR(255),
disable_direct_media_on_nat VARCHAR(3),
dtmf_mode VARCHAR(255) DEFAULT 'rfc4733',
external_media_address VARCHAR(255),
force_rport VARCHAR(3) DEFAULT 'yes',
ice_support VARCHAR(3) DEFAULT 'no',
identify_by VARCHAR(255) DEFAULT 'username,ip',
mailboxes VARCHAR(255),
moh_suggest VARCHAR(255) DEFAULT 'default',
outbound_auth VARCHAR(255),
outbound_proxy VARCHAR(255),
rewrite_contact VARCHAR(3) DEFAULT 'yes',
rtp_symmetric VARCHAR(3) DEFAULT 'yes',
send_diversion VARCHAR(3),
send_pai VARCHAR(3),
send_rpid VARCHAR(3),
timers VARCHAR(255) DEFAULT 'yes',
timers_min_se INTEGER DEFAULT 90,
timers_sess_expires INTEGER DEFAULT 1800,
trust_id_inbound VARCHAR(3) DEFAULT 'no',
trust_id_outbound VARCHAR(3) DEFAULT 'no',
type VARCHAR(255) DEFAULT 'endpoint',
use_avpf VARCHAR(3) DEFAULT 'no',
use_ptime VARCHAR(3) DEFAULT 'no',
callerid VARCHAR(255),
callerid_privacy VARCHAR(255),
callerid_tag VARCHAR(255),
device_state_busy_at INTEGER DEFAULT 0,
language VARCHAR(255) DEFAULT 'en',
max_audio_streams INTEGER DEFAULT 1,
max_video_streams INTEGER DEFAULT 1,
media_encryption VARCHAR(255) DEFAULT 'no',
media_use_received_transport VARCHAR(3) DEFAULT 'no',
one_touch_recording VARCHAR(3) DEFAULT 'no',
record_off_feature VARCHAR(255),
record_on_feature VARCHAR(255),
rtp_engine VARCHAR(255) DEFAULT 'asterisk',
rtp_ipv6 VARCHAR(3) DEFAULT 'no',
rtp_keepalive INTEGER,
rtp_timeout INTEGER,
rtp_timeout_hold INTEGER,
sdp_owner VARCHAR(255),
sdp_session VARCHAR(255),
set_var TEXT,
srtp_tag_32 VARCHAR(3) DEFAULT 'no',
tone_zone VARCHAR(255),
tos_audio VARCHAR(255),
tos_video VARCHAR(255),
cos_audio INTEGER,
cos_video INTEGER,
allow_transfer VARCHAR(3) DEFAULT 'yes',
user_eq_phone VARCHAR(3) DEFAULT 'no',
moh_passthrough VARCHAR(3) DEFAULT 'no',
account_code VARCHAR(255),
notify_early_inuse_ringing VARCHAR(3),
max_contacts INTEGER DEFAULT 1,
remove_existing VARCHAR(3) DEFAULT 'yes',
voicemail_extension VARCHAR(255)
);
CREATE INDEX IF NOT EXISTS idx_ps_endpoints_id ON asterisk.ps_endpoints (id);
-- ---------------------------------------------------------------------
-- ps_auths
-- ---------------------------------------------------------------------
CREATE TABLE IF NOT EXISTS asterisk.ps_auths (
id VARCHAR(255) PRIMARY KEY,
auth_type VARCHAR(255) DEFAULT 'userpass',
nonce_lifetime INTEGER DEFAULT 32,
md5_cred VARCHAR(255),
password VARCHAR(255),
realm VARCHAR(255),
username VARCHAR(255)
);
-- ---------------------------------------------------------------------
-- ps_aors
-- ---------------------------------------------------------------------
CREATE TABLE IF NOT EXISTS asterisk.ps_aors (
id VARCHAR(255) PRIMARY KEY,
contact VARCHAR(255),
default_expiration INTEGER DEFAULT 3600,
mailboxes VARCHAR(255),
max_contacts INTEGER DEFAULT 1,
minimum_expiration INTEGER DEFAULT 60,
remove_existing VARCHAR(3) DEFAULT 'yes',
qualify_frequency INTEGER DEFAULT 0,
authenticate_qualify VARCHAR(3) DEFAULT 'no',
max_expiration INTEGER DEFAULT 7200,
outbound_proxy VARCHAR(255),
support_path VARCHAR(3) DEFAULT 'no',
qualify_timeout NUMERIC(5,3) DEFAULT 3.0,
voicemail_extension VARCHAR(255),
remove_unavailable VARCHAR(3) DEFAULT 'no'
);
-- ---------------------------------------------------------------------
-- ps_contacts (populada dinamicamente pelo registro dos ramais)
-- ---------------------------------------------------------------------
CREATE TABLE IF NOT EXISTS asterisk.ps_contacts (
id VARCHAR(255) PRIMARY KEY,
uri VARCHAR(511),
expiration_time BIGINT,
qualify_frequency INTEGER DEFAULT 0,
outbound_proxy VARCHAR(255),
path TEXT,
user_agent VARCHAR(255),
qualify_timeout NUMERIC(5,3) DEFAULT 3.0,
reg_server VARCHAR(255),
authenticate_qualify VARCHAR(3) DEFAULT 'no',
via_addr VARCHAR(255),
via_port INTEGER,
call_id VARCHAR(255),
endpoint VARCHAR(255),
prune_on_boot VARCHAR(3) DEFAULT 'no'
);
CREATE INDEX IF NOT EXISTS idx_ps_contacts_endpoint ON asterisk.ps_contacts (endpoint);
-- ---------------------------------------------------------------------
-- ps_endpoint_id_ips (identificação de troncos por IP)
-- ---------------------------------------------------------------------
CREATE TABLE IF NOT EXISTS asterisk.ps_endpoint_id_ips (
id VARCHAR(255) PRIMARY KEY,
endpoint VARCHAR(255),
match VARCHAR(80),
srv_lookups VARCHAR(3) DEFAULT 'yes',
match_header VARCHAR(255)
);
CREATE INDEX IF NOT EXISTS idx_ps_endpoint_id_ips_endpoint ON asterisk.ps_endpoint_id_ips (endpoint);
-- ---------------------------------------------------------------------
-- ps_registrations (troncos com registration outbound)
-- ---------------------------------------------------------------------
CREATE TABLE IF NOT EXISTS asterisk.ps_registrations (
id VARCHAR(255) PRIMARY KEY,
auth_rejection_permanent VARCHAR(3) DEFAULT 'yes',
client_uri VARCHAR(255),
contact_user VARCHAR(255),
expiration INTEGER DEFAULT 3600,
max_retries INTEGER DEFAULT 10,
outbound_auth VARCHAR(255),
outbound_proxy VARCHAR(255),
retry_interval INTEGER DEFAULT 60,
forbidden_retry_interval INTEGER DEFAULT 0,
server_uri VARCHAR(255),
transport VARCHAR(255),
support_path VARCHAR(3) DEFAULT 'no',
fatal_retry_interval INTEGER DEFAULT 0,
line VARCHAR(3) DEFAULT 'no',
endpoint VARCHAR(255)
);
-- ---------------------------------------------------------------------
-- CDR (cdr_pgsql) — colunas padrão do CDR nativo do Asterisk
-- ---------------------------------------------------------------------
CREATE TABLE IF NOT EXISTS asterisk.cdr (
id BIGSERIAL PRIMARY KEY,
accountcode VARCHAR(20),
src VARCHAR(80),
dst VARCHAR(80),
dcontext VARCHAR(80),
clid VARCHAR(80),
channel VARCHAR(80),
dstchannel VARCHAR(80),
lastapp VARCHAR(80),
lastdata VARCHAR(80),
start TIMESTAMP,
answer TIMESTAMP,
end_time TIMESTAMP,
duration INTEGER,
billsec INTEGER,
disposition VARCHAR(45),
amaflags VARCHAR(45),
uniqueid VARCHAR(150),
userfield VARCHAR(255),
linkedid VARCHAR(150),
peeraccount VARCHAR(20),
sequence INTEGER
);
CREATE INDEX IF NOT EXISTS idx_asterisk_cdr_uniqueid ON asterisk.cdr (uniqueid);
CREATE INDEX IF NOT EXISTS idx_asterisk_cdr_linkedid ON asterisk.cdr (linkedid);
CREATE INDEX IF NOT EXISTS idx_asterisk_cdr_start ON asterisk.cdr (start);
-- ---------------------------------------------------------------------
-- CEL (cel_pgsql)
-- ---------------------------------------------------------------------
CREATE TABLE IF NOT EXISTS asterisk.cel (
id BIGSERIAL PRIMARY KEY,
eventtype VARCHAR(30),
eventtime TIMESTAMP,
userdeftype VARCHAR(255),
cid_name VARCHAR(80),
cid_num VARCHAR(80),
cid_ani VARCHAR(80),
cid_rdnis VARCHAR(80),
cid_dnid VARCHAR(80),
exten VARCHAR(80),
context VARCHAR(80),
channame VARCHAR(80),
appname VARCHAR(80),
appdata VARCHAR(512),
accountcode VARCHAR(20),
uniqueid VARCHAR(150),
linkedid VARCHAR(150),
peer VARCHAR(80),
userfield VARCHAR(255),
extra TEXT
);
CREATE INDEX IF NOT EXISTS idx_asterisk_cel_uniqueid ON asterisk.cel (uniqueid);
CREATE INDEX IF NOT EXISTS idx_asterisk_cel_linkedid ON asterisk.cel (linkedid);
CREATE INDEX IF NOT EXISTS idx_asterisk_cel_eventtime ON asterisk.cel (eventtime);