Customer Onboarding Runbook — FreeSWITCH

This runbook walks an operator through onboarding a new customer on the FreeSWITCH provider — from an empty host to a smoke-tested phone system. It is an ordered checklist with pointers: the detailed reference for every screen, field and failure mode lives in FreeSWITCH Integration Setup and Firewall Service; this page only fixes the order of the steps and the values that must be captured along the way.

Scope

Until multi-tenancy lands, every customer gets a dedicated FreeSWITCH host. The customer's Odoo runs elsewhere (their existing Odoo deployment) and talks to the FreeSWITCH host over the public network. This runbook covers the FreeSWITCH provider only — Twilio, Telnyx, Asterisk and Infobip customers follow the corresponding setup guide instead.

Onboarding at a glance

  1. Collect prerequisites
  2. Provision the host
  3. DNS, Traefik, Let's Encrypt
  4. Compose layout and secrets
  5. Odoo: modules and FreeSWITCH settings
  6. Firewall service
  7. Trunk gateway, DIDs, routes, caller IDs
  8. Users and endpoints
  9. Smoke tests
  10. Handover record

1. Prerequisites

Collect before starting; every item below blocks a later step.

Item Why
Linux host (VM or bare metal) with a public IPv4 and root access SIP/RTP does not survive Docker NAT — all telephony containers run with network_mode: host; the firewall service needs NET_ADMIN against the host kernel.
Docker Engine + compose plugin installed Everything runs as containers.
DNS control for the customer's FreeSWITCH FQDN (e.g. fs.customer.example.com) Let's Encrypt (TLS-ALPN) validates against this name; Verto WSS and XML-RPC use it.
Customer's Odoo: public URL + admin login Modules are installed and all PBX configuration is done there.
SIP trunk credentials from the provider Proxy, username/password (or the provider's IP list for IP-auth trunks), the DID numbers.
A password vault entry for this customer Keep the webhook token, firewall token, ESL password, and firewall dashboard password there. The XML-RPC password is generated and stored internally by Odoo and is not exposed to operators.

Ports that must be reachable on the FreeSWITCH host (open in the cloud security group / host firewall):

Port Proto Purpose
80 TCP Traefik HTTP→HTTPS redirect (ACME uses TLS-ALPN on 443, not this port).
443 TCP Traefik TLS edge: XML-RPC (/RPC2) from Odoo, firewall dashboard/sync (/firewall).
5080 UDP+TCP SIP signaling (sofia external profile) — trunks and SIP phones.
16000–17000 UDP RTP media.
48082 TCP Verto WSS — browser softphone signaling.

Internal-only, never expose: 8080/tcp (mod_xml_rpc plain HTTP, behind Traefik), 8081/tcp (firewall service HTTP, behind Traefik), 8021/tcp (FreeSWITCH ESL, localhost only).

2. Provision the host

  1. Install Docker and the compose plugin (official instructions).
  2. Copy the deploy folder from the addons repo to the host, e.g.:

    scp -r connect_freeswitch/deploy/ root@fs.customer.example.com:/opt/freeswitch

    The folder carries the FreeSWITCH image build sources, the Traefik dynamic config (traefik/) and the .env / compose templates that the next steps customize. The production container uses the bootstrap configuration baked into oduist/freeswitch; the host copy under freeswitch/conf/ is not mounted at runtime. Keep /opt/freeswitch under configuration management or note it in the handover record — it is the only state on the host besides Docker volumes.

3. DNS, Traefik, Let's Encrypt

  1. Create an A record for the FQDN pointing at the host's public IP and wait until it resolves publicly — ACME validation fails otherwise.
  2. Edit /opt/freeswitch/.env:

    COMPOSE_PROJECT_NAME=fs
    # Customer's Odoo, used by FreeSWITCH for xml_curl/CDR/recording callbacks
    ODOO_URL=https://odoo.customer.example.com
    FS_DOMAIN=fs.customer.example.com
    ACME_EMAIL=ops@example.com
    # Keep the staging CA enabled until the whole stack comes up cleanly,
    # then comment it out and remove the acme volume to get the real cert:
    ACME_CASERVER=https://acme-staging-v02.api.letsencrypt.org/directory
  3. Traefik requests the certificate automatically on first start (next step); the FreeSWITCH entrypoint extracts the same certificate from the shared ACME volume for Verto WSS and DTLS-SRTP. Details: TLS/SSL Certificates.

Switch off the staging CA before handover

While ACME_CASERVER points at staging, browsers and Odoo reject the certificate because XML-RPC certificate verification is always enabled. After the stack is verified: comment the line out, docker compose down, docker volume rm fs_traefik-acme, docker compose up -d.

4. Compose layout

The shipped connect_freeswitch/deploy/docker-compose.yml is the production FreeSWITCH host stack. It starts only traefik, fs and firewall, with oduist/freeswitch:2.1.2 and oduist/freeswitch-firewall:2.1.1. The all-in-one local stack that also starts Odoo + Postgres lives in docker-compose.full.yml.

The FreeSWITCH image owns its static bootstrap configuration. Do not mount ./freeswitch/conf over /usr/local/freeswitch/etc/freeswitch: Odoo provides the dynamic users, gateways, ACLs and dialplan over mod_xml_curl, while the image bootstrap is what loads those modules and keeps ESL bound to loopback.

Generate only the host-local secrets here and record them in the vault. Odoo generates FS_WEBHOOK_TOKEN and FIREWALL_AGENT_TOKEN when connect_freeswitch is installed; retrieve them in step 5 instead of creating parallel values:

# ESL password (fs and firewall containers must match)
openssl rand -base64 24 | tr '+/' '-_'

Append them to /opt/freeswitch/.env:

FS_ESL_PASSWORD=<esl password>
FIREWALL_DASHBOARD_PASSWORD=<pick a strong password>

The copied docker-compose.yml and traefik/dynamic.yml already include the firewall service and the /firewall Traefik route. Traefik runs on the host network and proxies to the loopback-only listeners 127.0.0.1:8080 (FreeSWITCH XML-RPC) and 127.0.0.1:8081 (firewall).

Do not start fs or firewall yet. Their required tokens are generated by Odoo in the next step. Starting with made-up or empty values produces expected 401 errors and adds no useful bootstrap capability.

5. Odoo: modules and settings

  1. Install connect and connect_freeswitch in the customer's Odoo — see Installation.
  2. Open Connect → FreeSWITCH → Configuration → Settings and fill in (field-by-field reference: Odoo Configuration):

    Field Value
    WebSocket URL wss://fs.customer.example.com:48082
    Domain fs.customer.example.com
    XML-RPC Host fs.customer.example.com

    XML-RPC always uses verified HTTPS on port 443. The username is fixed to odoo; Odoo generates and stores the password internally. Changing the host rotates that password.

  3. Retrieve the automatically generated protected settings with Oduflow's run_odoo_shell (or an equivalent administrative Odoo shell for a manual deployment):

    settings = env["connect.settings"].sudo()
    print("FS_WEBHOOK_TOKEN=" + (settings.get_param("freeswitch_webhook_token") or ""))
    print("FIREWALL_AGENT_TOKEN=" + (settings.get_param("firewall_service_token") or ""))

    Append those two values to /opt/freeswitch/.env. Do not commit them or repeat them in tickets and deployment summaries. Oduflow service updates must preserve the complete existing environment because env_vars is a full replacement. 4. Start the stack and verify ESL:

    cd /opt/freeswitch
    docker compose up -d
    docker exec freeswitch sh -c 'fs_cli -p "$FS_ESL_PASSWORD" -x status'
  4. Click CHECK STATUS on the settings form — Server Status must show UP — <version>. Anything else: follow the decision table in Checking server status.

    Restart FreeSWITCH after every later XML-RPC host change so mod_xml_rpc loads the rotated password.

6. Firewall

Full reference: Firewall Service. Onboarding order:

  1. In Connect → FreeSWITCH → Configuration → Settings, page Firewall:
    • toggle Firewall Enabled on;
    • set Firewall Service URL to https://fs.customer.example.com (Odoo appends /firewall/sync; Traefik routes the /firewall prefix to the service — the default http://host.docker.internal:8081 only works when Odoo runs on the same host);
    • leave the generated Firewall Service Token unchanged; the value already deployed as AGENT_TOKEN is the same protected Odoo setting;
    • keep the default port lists (5060,5061,5080,5081) and timeouts.
  2. Connect → FreeSWITCH → Firewall → Whitelist: add the trunk provider's signaling IPs and the customer's office NAT exits. Saving syncs to the service immediately.

    Whitelist vs. gateway Inbound IPs

    The firewall whitelist only keeps an IP from being auto-banned at the kernel level. Accepting unauthenticated INVITEs from an IP-auth trunk is separate — that is the Inbound IPs field on the SIP Gateway record (sofia ACL).

  3. Connect → FreeSWITCH → Firewall → Agent Status must show the agent online within one heartbeat interval (60 s). If not: firewall troubleshooting.

7. Trunk gateway, DIDs, routes, caller IDs

All under the Connect → FreeSWITCH menu; saving a gateway restarts the sofia external profile automatically — no manual reload step.

  1. Configuration → SIP Gateways — create the trunk from the provider's credentials (fields: SIP Gateways). For IP-auth trunks fill Inbound IPs. Verify registration:

    docker exec -it freeswitch fs_cli -x "sofia status gateway <name>"
    # State must be REGED (for register=true trunks)
  2. Numbers — one record per DID, each routed to a user, callflow or FIFO. A leading + is matched tolerantly; digits beyond that must match what the trunk actually delivers (see DID format mismatch).

  3. Configuration → Outgoing Routes — at minimum one catch-all route (pattern ^\+\d{7,}$) through the trunk gateway; add national/emergency patterns per customer dial habits (examples).
  4. Outgoing Caller IDs — create the customer's DIDs as caller IDs and flag one as Default; optionally assign per-user numbers on the Connect User form. Resolution order: Outbound Caller ID.

8. Users and endpoints

  1. Create PBX users under Connect → Users — extension, Odoo user link, groups (PBX Users). With several provider modules co-installed, set each user's Originate Provider to FreeSWITCH so click-to-call uses this host.
  2. For every user create at least one endpoint under Connect → FreeSWITCH → Endpoints — the SIP/Verto password is auto-generated; copy it from the form for desk phones (Endpoints).

9. Smoke tests

Run all of these before declaring the customer live. Each line has a detailed fallback reference.

# Test Pass criterion If it fails
1 CHECK STATUS button in FreeSWITCH settings UP — <version>, registrations/gateways listed Status decision table
2 Browser softphone: dial 9196 (echo) You hear yourself echoed back Echo test; no audio → RTP 16000–17000/udp blocked
3 Browser softphone: dial 9664 Hold music plays Testing
4 Outbound PSTN call to a mobile Call connects; the mobile shows the customer's default caller ID sofia status gateway <name>; Outgoing Routes
5 Inbound call to each DID Rings the configured user/callflow DID troubleshooting
6 Click-to-call from a partner form in Odoo User's phone rings, then the destination User's Originate Provider = FreeSWITCH; endpoint registered
7 Call history Calls from tests 4–6 appear under Connect → Calls with recordings (if enabled) CDR webhook / webhook token pairing (XML cURL Integration)
8 Firewall liveness curl -s https://fs.customer.example.com/firewall/healthz returns {"status":"ok","odoo":true,"esl":true}; agent online in Odoo Firewall troubleshooting

10. Handover record

Record these in the customer's operations vault/wiki when the smoke tests pass — the next operator (and the next onboarding) starts from this table:

Item Value to record
FreeSWITCH FQDN + host IP fs.customer.example.com, provider/region of the VM
Deploy path on the host e.g. /opt/freeswitch (compose, .env, Traefik config)
Odoo URL the ODOO_URL the containers point at
Secrets (vault reference) webhook token, firewall token, ESL password, firewall dashboard password
Trunk provider account, proxy, auth mode (register vs IP-auth), support contact
DIDs numbers and their routing (user/callflow)
Image tags deployed oduist/freeswitch:<tag>, oduist/freeswitch-firewall:<tag>
Date of Let's Encrypt switch to production CA to correlate future renewal issues

Routine operations after handover (trunk password rotation, unbanning IPs, monitoring) are covered by the Operations Runbook section and Firewall daily operations.