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,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);