Files
B2BCall-dialer/docs/FREESWITCH.md
Matheus b3b0aaacb3 feat: add FreeSWITCH service (SignalWire packages, not compiled from source)
- infrastructure/freeswitch/Dockerfile: debian:trixie-slim + SignalWire
  packaged freeswitch-meta-vanilla, avoiding a C/C++ build on a 1.9GB RAM VM
- FREESWITCH_PAT used only via Docker BuildKit secret, apt credentials file
  created and deleted within the same RUN — verified absent from the final
  image with docker history
- minimal module set (agente.md secao 15): sofia, event_socket, commands,
  dptools, callcenter, avmd, curl, local_stream, etc. mod_xml_curl installed
  but disabled — it refuses to load without a configured gateway-url, which
  will exist once b2bcall-fs-config is built
- entrypoint.sh rotates the Event Socket password away from the 'ClueCon'
  default at container runtime (never baked into the image); fails loudly if
  ESL_PASSWORD is unset
- port 8021 not published to the host; only reachable from other containers
  on the compose network
- found and fixed: freeswitch-conf-vanilla is a Recommends (not a Depends)
  of freeswitch-meta-vanilla, so --no-install-recommends silently produced
  an empty /etc/freeswitch and a crash loop
- verified end-to-end: fs_cli status via ESL with the custom password,
  default password rejected, expected modules loaded, healthcheck green,
  ~44MB RAM usage
- docs/FREESWITCH.md, docs/NETWORK_ARCHITECTURE.md (network_mode decision
  deferred until a real SIP trunk exists)
2026-08-28 06:30:25 -03:00

4.5 KiB

FreeSWITCH

Imagem própria em infrastructure/freeswitch/ (agente.md secao 19), rodando como serviço Docker freeswitch (container b2bcall-freeswitch).

Por que pacotes prontos, e não compilar da fonte

O servidor de desenvolvimento tem só ~1.9GB de RAM. Compilar FreeSWITCH (C/C++, dezenas de módulos) arriscaria OOM e levaria muito tempo. Em vez disso, a imagem usa os pacotes .deb pré-compilados do repositório oficial do SignalWire (freeswitch.signalwire.com), autenticado com FREESWITCH_PAT — exatamente o uso previsto para essa credencial (agente.md secao 11). O repositório do SignalWire tem pacotes para o codename trixie, então a imagem usa debian:trixie-slim como base (mesma versão do host, mas isso é coincidência — o container é isolado e poderia usar qualquer Debian suportado pelo repo).

Manuseio do PAT (agente.md secao 11)

FREESWITCH_PAT entra no build via Docker BuildKit secret (--mount=type=secret,id=freeswitch_pat), nunca como ARG/ENV. O arquivo de credenciais do apt (/etc/apt/auth.conf.d/freeswitch.conf, que contém o PAT em texto puro) é criado e apagado dentro do mesmo RUN, então nunca aparece em nenhuma camada da imagem final — verificado com docker history --no-trunc.

docker-compose.yml declara o secret assim:

secrets:
  freeswitch_pat:
    environment: FREESWITCH_PAT

e o serviço freeswitch referencia secrets: [freeswitch_pat] em build:.

Pacotes instalados

freeswitch-meta-vanilla       # core + config de referência (a mesma usada em
                               # praticamente todo tutorial/livro de FreeSWITCH)
freeswitch-conf-vanilla       # ⚠ Recommends de meta-vanilla, não Depends —
                               # precisa ser listado explicitamente com
                               # --no-install-recommends (foi um bug real
                               # durante o setup: sem isso /etc/freeswitch
                               # fica vazio e o container entra em crash loop)
freeswitch-mod-callcenter     # ACD (agente.md secao 37)
freeswitch-mod-avmd           # detecção de caixa postal/beep (secao 87)
freeswitch-mod-curl           # chamadas HTTP a partir do dialplan

mod_xml_curl está instalado mas desativado em overrides/autoload_configs/modules.conf.xml — o módulo se recusa a carregar sem pelo menos um binding com gateway-url configurada ("Binding has no url!"), e essa URL só existirá quando o b2bcall-fs-config for criado (fase "XML Curl", logo em seguida). Reativar lá.

mod_signalwire foi removido da lista de módulos: é específico da nuvem do SignalWire, que não usamos (só o repositório de pacotes).

Event Socket (agente.md secao 22)

Senha alterada da padrão (ClueCon) para ${ESL_PASSWORD} (gerado com openssl rand, vive só em .env) via entrypoint.sh, que faz um sed no event_socket.conf.xml em runtime — a senha real nunca é copiada para a imagem, só injetada via variável de ambiente do container. O entrypoint falha alto (set -eu + ${ESL_PASSWORD:?...}) se a variável não estiver definida — nunca sobe com a senha padrão por engano.

Porta 8021 não é publicada no host (ports: ausente no compose) — só alcançável por outros containers na rede interna do Docker Compose (b2bcall_default), pelo nome de serviço freeswitch. Isso satisfaz "NÃO pública... acesso somente pela aplicação autorizada" sem precisar de ACL adicional por enquanto (ACL fica como hardening futuro, ver TODO).

O que NÃO foi feito nesta fase (fica para as próximas, por design)

Seguindo a ordem do próprio agente.md (secao 232): esta fase só cobre "ter o FreeSWITCH rodando e alcançável". As próximas fases constroem em cima:

  • Event Socket: b2bcall-fs-events, conexão ESL permanente (secao 21).
  • XML Curl: b2bcall-fs-config, reativa mod_xml_curl apontando pra esse serviço (secao 26).
  • Extensions/Trunks/Dialplan: hoje o directory/dialplan estático da config vanilla continua com os 20 ramais de teste (1000-1019, senhas fracas — README do próprio pacote avisa isso). Como as portas SIP não estão publicadas no host, isso fica contido, mas precisa ser substituído por mod_xml_curl dinâmico antes de qualquer tronco/ramal real existir.
  • mod_odbc_cdr: não instalado ainda — só faz sentido junto da fase de CDR.

Verificação manual

source <(grep '^ESL_PASSWORD=' .env)
docker exec b2bcall-freeswitch fs_cli -p "$ESL_PASSWORD" -x "status"
docker exec b2bcall-freeswitch fs_cli -p "$ESL_PASSWORD" -x "module_exists mod_sofia"