pulse-lets-go/docs/freeswitch.md
Maksim Totmin 12a51a5ebe docs: restructure documentation into docs/ directory
- Create docs/ with 7 focused files: architecture, api, scoring, agent,
  deployment, freeswitch, development
- Rewrite README.md as concise overview with links to docs/
- Remove AGENTS.md (content merged into docs/ and README)
2026-06-25 22:35:19 +07:00

3.0 KiB

FreeSWITCH Integration

pulse-lets-go routes calls through FreeSWITCH via mod_curl → Lua dialplan → /api/route.

Dialplan (route.lua)

FreeSWITCH uses a Lua script in its dialplan. The script calls /api/route via mod_curl and bridges to the returned gateway.

-- contrib/route.lua (simplified)
api = freeswitch.API()
caller_id = session:getVariable("caller_id_number") or ""
dest = session:getVariable("destination_number") or ""
url = "http://localhost:8080/api/route?caller_id=" .. caller_id .. "&dest=" .. dest

raw = api:execute("curl", url)
body = raw:match("\r?\n\r?\n(.+)") or raw

local ok, route = pcall(cjson.decode, body)
if not ok then session:hangup("NORMAL_TEMPORARY_FAILURE") return end

if route.fallback then
  session:bridge(route.sip_gateway)  -- operator
elseif route.sip_gateway then
  session:bridge(route.sip_gateway)  -- best node
else
  session:hangup("NORMAL_TEMPORARY_FAILURE")
end

Dialplan Configuration

<extension name="route_call">
  <condition field="destination_number" expression="^.*$">
    <action application="set" data="balancer_url=http://balancer.lan:8080"/>
    <action application="set" data="ingress_trunk=trk-001"/>
    <action application="lua" data="route.lua"/>
  </condition>
</extension>

Reload dialplan:

sudo fs_cli -x "reloadxml"

ESL (Gateway Management)

When esl.host is configured in config.json, the balancer:

  • Connects to FreeSWITCH ESL (:8021)
  • Synchronizes ingress trunks as Sofia gateways
  • Listens for gateway registration events
  • Streams gateway status to WebSocket frontend

Configuration

{
  "esl": {
    "host": "127.0.0.1",
    "port": 8021,
    "password": "ClueCon",
    "password_env": "",
    "sofia_profile": "external",
    "gateway_prefix": "pulse"
  }
}
  • password_env — alternative: name of environment variable containing password
  • sofia_profile — Sofia profile to manage (default: external)
  • gateway_prefix — prefix for auto-created gateways (default: pulse)

Prerequisites (before deployment)

# Check Command Expected
1 ESL running nc -z 127.0.0.1 8021 && echo OK OK
2 ESL password echo "auth ClueCon" | nc 127.0.0.1 8021 +OK accepted
3 mod_curl loaded fs_cli -x "show modules" | grep mod_curl <load module="mod_curl"/>
4 mod_lua loaded fs_cli -x "show modules" | grep mod_lua <load module="mod_lua"/>
5 ESL ACL ACL in event_socket.conf.xml allows 127.0.0.1 localhost or 127.0.0.1 in ACL

Loading mod_curl Without Restart

fs_cli -x "load mod_curl"
# Add to modules.conf.xml for persistence:
# <load module="mod_curl"/>

Rollback (if route.lua breaks calls)

# 1. Remove pulse_route from dialplan
sudo sed -i '/pulse_route/,/<\/extension>/d' /etc/freeswitch/dialplan/default.xml
sudo fs_cli -x "reloadxml"

# 2. Stop pulse-lets-go
sudo systemctl stop pulse-lets-go

# Calls continue through original dialplan without balancer.