A relayed call costs 0 units. Opus against G.711 costs 2, G.729 or iLBC
against Opus costs 4, G.726 against G.711 costs 1, and a virtual agent adds 3 — so the transcode figure is what a node
actually runs out of, and a node can be comfortable on channels and out of
budget. The Overview shows each as in use / limit.
The audio-streaming module this project builds is free for ten concurrent
streams, and a call past the module’s own limit is not refused by it — it is
answered and silent. That is why the node refuses first, and why the
default is 8: live assist draws on the same ten.
When the machine itself is loaded: the load brake
A counted limit can only see calls. On a box shared with anything else — a database, a mail server, other sites — a neighbour taking a core is not a channel, and every gauge still reads fine while audio breaks on every call at once. So the node also watches the machine:
This is the load brake. Calls this platform chose to make — campaigns,
API-placed AI calls — are shed first; a person who clicked to call is refused
only when everything is. The overview shows a badge
while it is happening, with what the machine was doing.
An API call refused this way answers
503 node_busy with Retry-After: 30:
“try again shortly”, as distinct from 422, “this request is wrong”.
Which node an organisation lives on
Every node card says who lives here: how many organisations are placed on it, the first few by name, and a link to the list of them. Placement is set on the organisation — Organisations → the tenant → Node — and the fleet is where you see the result. One node carries a base badge. It is the node the control plane shares a machine with, and it is the only node where virtual agents, voicemail, IVR prompts, hold music and call recording run: a virtual agent’s audio is a named pipe both the daemon and the switch have to open, and a recording is written where the call is. So an organisation that uses any of those can be placed on base and nowhere else, and the server refuses a placement they cannot follow rather than letting it fail on the next call. The refusal names what stopped it. The same refusal meets an attempt to switch one of those on for an organisation already placed on another node. An organisation placed on a node does not fall back when that node is down. That is the point of placing: one organisation’s node failing stays that organisation’s problem, instead of spilling its calls onto other tenants’ switches. An organisation on any node goes to whichever is healthy.Calls for an organisation on another node
Carriers connect to base and nowhere else — a plain-call node holds no gateway and no carrier password. So a call for an organisation placed on another node is carried by two switches: the carrier’s leg on base, and the phone’s leg on the organisation’s own node, joined by one hop over the mesh.- Inbound: base takes the carrier’s call, works out the organisation, sees it lives elsewhere and passes the call to that node, which rings the phone.
- Outbound: a phone on the node dials; the node passes the call to base, and base sends it out through the trunk as if the phone were its own.
Adding a node
Base registers itself whenfiretoned starts beside a switch. Every other node
is added in Fleet → New node: a name, its mesh address (WireGuard or
Tailscale — never a public one), and two credentials the panel generates — the
node’s own ESL password and directory secret. Saving hands back one
command to run on the box:
join.sh refuses a public
control-plane address, writes the node’s env, builds FreeSWITCH if it is not
there, installs the configuration pointed at base, installs the agent with the
bundled key, installs the firewall when told the interface, and ends by proving
each path from the node’s side. Join token on a node’s card issues a fresh
one when the first was spent or the box is being rebuilt.
Base must listen on the mesh for this: FIRETONE_ESL_LISTEN,
FIRETONE_PROVISION_LISTEN and FIRETONE_API_LISTEN on its mesh address
(deploy/control-plane/make-env.sh asks for the address). While they are on
loopback no token can be issued, the dialog says which variable, and the two
secrets are shown for carrying by hand.
The daemon re-reads the fleet every half minute, so a node added here is
connected within that — no restart. A node deleted here is disconnected the
same way.
Verify node
Each node card shows three dots — ESL, Directory, Agent — one per path between the node and base, refreshed with the card. Verify asks each path at its real endpoint and says what to fix:
The verdict is the worst status. A node whose card is green on ESL alone and
red on Directory is a node whose phones cannot register, and this is where it
says so.
What a node holds
A node is the most exposed machine in the system, so what is on it reaches only that node:- Its own ESL password, set on the node in Fleet. The control plane opens each node’s socket with that node’s password and no other.
- Its own directory secret (
provision_secret, set on the node). When the node’s switch asks the control plane for a phone’s credentials, it is held to that secret, judged by the mesh address the request came from. A node with none set is held to the sharedFIRETONE_PROVISION_SECRET, which is what a single box uses. A secret lifted from one node opens no other’s directory. - An agent key bound to the node.
firetonectl node-agent-keymints a key that can report metrics and sync the firewall for that one node, and is refused for every other node — whoever owns it. - No carrier credential. Trunks are on base. A node that is not base gets a gateway file with nothing in it and a carriers ACL that admits the fleet and no carrier. There is nothing on it worth stealing.
Keeping transcode low
Both legs agreeing on a codec lets the switch relay rather than transcode. That is the difference between near-zero CPU and the most expensive thing the system does — see extensions.Which codecs a node speaks
Opus and G.711 always. G.729, iLBC and G.726 are separate modules, built with the switch (deploy/node/build-g729.sh, deploy/node/build-codecs.sh)
and loaded only where they are installed. A node without one still takes the
call: every codec profile and every sensible trunk list keeps G.711 behind its
first choice, so the call connects in G.711 instead. verify.sh names any
module that is installed and not loaded.
On a trunk they are ticked, not typed — see
a trunk’s codecs. Through the API the names
are G729, iLBC and G726-32: the rate is part of the name, and a bare
G726 is refused because the switch would drop it from the offer without saying
so. When a trunk is pinned to a node, its editor says so if a ticked codec’s
module is not loaded there.
Admission control
A node refuses new calls beyond its configured session limit rather than degrading everything already in progress. The switch’s own limit is per second as well as total, and it is shared with everything arriving inbound — which is why a wide outbound campaign competes with your tenants’ incoming calls. The same is true of a carrier: a trunk’s channel limit counts both directions, because that is what the carrier contracted. Where a dialling burst must not be able to fill it, set the trunk’s outbound channel limit as well — it caps the outgoing share and reserves the rest for calls arriving. See trunks.The name desk phones use
A node’s Handset server name (e.g.sip.firet.one) is what every
extension’s handset settings name as the server. It is display only — nothing
is written to the switch — but a phone using TLS on 5061 needs it: the phone
checks the certificate against the server it was given, and a certificate names
a host, not an address. Leave it empty and extensions show the advertised
address, which is fine for UDP and TCP on 5060.
The switch listens for SIP on IPv4 only. A name with an IPv6 (AAAA) record
sends a phone that prefers IPv6 to an address nothing answers on.
An organisation’s handsets are shown the node chosen on the organisation, or the
only node there is. With several nodes and none chosen, the handset settings say
so instead of guessing.