# 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: ```yaml 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 **nunca publicada pra rede** — só em loopback do próprio host (`"127.0.0.1:8021:8021"` no `docker-compose.yml`, desde a PHASE 65, ver seção abaixo). Isso satisfaz "NÃO pública... acesso somente pela aplicação autorizada" (agente.md secao 22) sem abrir a porta pra ninguém além do próprio host. ## Achados na sessão de PHASE 65 (Platform > Infraestrutura) Duas telas de `platform/infraestrutura` (FreeSWITCH, SIP Profiles, Nodes) sempre mostravam "Timeout conectando no ESL do FreeSWITCH" nesta VM. A suposição registrada até então (agora corrigida) era que isso seria uma limitação permanente: "`apps/api` roda fora do Docker, a porta nunca é publicada, então este endpoint sempre falha aqui". Isso estava incompleto. **Causa real**: `ESL_HOST=freeswitch` no `.env` — um nome DNS que só resolve dentro da rede interna do Docker Compose (embedded DNS), nunca a partir do resolver do próprio host (confirmado: `getent hosts freeswitch` falha no host com exit code 2). Não era uma questão de porta publicada — testado e confirmado que o **host sempre alcança o IP de qualquer container na rede bridge do Docker diretamente** (`docker inspect` + `/dev/tcp` bateram), mesmo sem nenhuma porta publicada. Só outras *máquinas* são bloqueadas sem `ports:` — o host nunca foi. **Fix**: publicar 8021 só em loopback (`127.0.0.1:8021:8021`) e trocar `ESL_HOST` pra `127.0.0.1` no `.env` — resolve o DNS sem abrir a porta pra rede nenhuma (confirmado via `ss -tlnp`: `docker-proxy` escutando em `127.0.0.1`, não `0.0.0.0`). Os serviços que rodam DENTRO do Docker (`fs-events`, `fs-config`, `predictive-dialer`) continuam com `ESL_HOST=freeswitch` fixo no próprio `docker-compose.yml` — não dependem do `.env` pra isso, então a troca não afeta eles. **Segundo bug, achado na mesma investigação**: a tela "Nodes" continuava mostrando `error: ""` (string vazia) mesmo depois do fix acima. `getGateways()` (`packages/telephony/src/freeswitch-provider.ts`) rodava `show gateways as json` — **não é um sub-comando válido** de `show` nesta versão do FreeSWITCH (`fs_cli -x "show gateways as json"` devolve `-USAGE: codec|endpoint|application|...`). O pacote `esl` trata qualquer reply começando com `-` como erro de protocolo e rejeita a promise com um `FreeSwitchError` cujo `.message` pode vir vazio — daí o `""` na tela em vez de uma mensagem útil. Fix: `getGateways()` agora roda `sofia status` (mesmo comando já usado pela tela de SIP Profiles) — gateways aparecem como linhas `type=gateway` no texto puro, sem variante JSON dedicada nesta versão. ## 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 ```bash 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" ```