sidetone

Start

Installation

One file and one data directory. The first start creates the database and prints an administrator's token once; the console is on the same port as the API.

Three ways to run it

The container

The image is ghcr.io/enetideas/sidetone, built for amd64 and arm64. Host networking, because audio needs a wide range of UDP ports; a named volume for the data.

$ docker run -d --name sidetone --network host -v sidetone-data:/data ghcr.io/enetideas/sidetone
$ docker logs sidetone

The token is in the log, once. The console is at http://127.0.0.1:8080/ of the host.

Docker Compose

The same image with a restart policy, so it comes back after a reboot, and the container locked down: read-only root filesystem, no capabilities. Save this as docker-compose.yml and run docker compose up -d.

name: sidetone
services:
  sidetone:
    image: ghcr.io/enetideas/sidetone:latest
    network_mode: host
    restart: unless-stopped
    read_only: true
    tmpfs: [ /tmp ]
    cap_drop: [ ALL ]
    environment:
      TZ: Europe/London
    volumes:
      - data:/data
volumes:
  data:

Set TZ to where the office is: desk phones take their clocks from the server. The full file in the repository has every setting explained.

The binary

A static executable: copy it to a machine and run it. Releases are published on GitHub once there is a tagged version; until then, build it, which needs Go and nothing else.

$ git clone https://github.com/eNetIdeas/sidetone
$ cd sidetone && make build
$ ./bin/sidetone -data-dir /var/lib/sidetone

For a service that survives reboots, run it under systemd as its own user with the data directory writable and nothing else. The binary needs no capabilities unless it is to bind port 80 for certificates (see Encryption).

Start-up settings

These are the only settings that need a restart: where it listens, where the data goes, and the first token. Everything else is changed while it runs. Each flag has an environment variable, which is what the container uses.

FlagEnvironmentDefault
-data-dirSIDETONE_DATA_DIR./dataThe database, backups, recordings, certificates.
-api-addrSIDETONE_API_ADDR127.0.0.1:8080The API and the console. Keep it on localhost or behind a reverse proxy with TLS.
-api-tls-addrSIDETONE_API_TLS_ADDRnoneA second listener for the API and the console over HTTPS, with the server's own certificate. The browser phone needs it anywhere but on localhost.
-sip-listenSIDETONE_SIP_LISTENudp://0.0.0.0:5060,tcp://0.0.0.0:5060Any of udp tcp tls ws wss, comma-separated.
-external-ipSIDETONE_EXTERNAL_IPlearnedThe public address, behind NAT. Learned from the trunk registration when empty; set it if there is no trunk and phones come in from outside.
-sip-realmSIDETONE_SIP_REALMsidetoneThe digest realm. Changing it invalidates every SIP password.
-phones-addrSIDETONE_PHONES_ADDRnoneA listener for what desk phones fetch, provisioning and the phonebook, and nothing else.
-trusted-proxySIDETONE_TRUSTED_PROXYnoneThe address or network of your reverse proxy, whose X-Forwarded-For is then believed.
-acme-domainSIDETONE_ACME_DOMAINnoneObtain and renew the TLS certificate from Let's Encrypt for this name.
-log-levelSIDETONE_LOG_LEVELinfodebug says why calls are refused.
-no-uiSIDETONE_NO_UIoffServe the API without the console.
—SIDETONE_ADMIN_TOKENgeneratedSupply your own admin token, 32 characters or more, instead of having one printed.

Firewall and NAT

Open the SIP ports you listen on, and the whole audio range over UDP: 10000 to 20000 unless you narrow it under Settings → Network (rtp_port_min, rtp_port_max), which needs no restart. All audio flows through the server, so phones never need to reach each other.

Behind a home or office router the server works without port forwards in the common case, the way a phone does. Its registration keeps a hole open for the provider's signalling, and its audio goes out first, which opens one for the audio coming back. Each party is told the address it can reach: a phone on the LAN gets the server's address there, the provider gets the public one, learned from what the registration was seen to come from (the log says public address learned). A change after the first is only believed when two answers in a row agree; -external-ip pins the address instead.

What a strict router will still drop is audio the provider sends before the server has sent any, such as ringing tones from far away. Forwarding UDP 5060 and the audio range to this machine cures that for good.

In Docker, use host networking. Publishing ten thousand UDP ports does not work well, and the API stays on the host's loopback that way. With bridge networking instead, opt in explicitly: -e SIDETONE_API_ADDR=0.0.0.0:8080 -p 127.0.0.1:8080:8080.

The first day

  • The token is printed once at the first start. Sign in to the console with it, or send it as Authorization: Bearer. The server keeps only a hash: if you lose it, make another from the Security page while you can, or set SIDETONE_ADMIN_TOKEN and start again.
  • Test mode. A new server starts in test mode: the emergency numbers are refused, every attempt is logged, and the console says so above every page. Switch it off under Settings → Calls when the telephones are in service. See Test mode.
  • Phones. Any SIP phone or softphone registers with a user's extension and password. Yealink desk phones can be given one address and fetch everything else for themselves. See Users and phones.
  • The outside world is a trunk, a route and an inbound number. See Calling out and in.

Updating

The container: pull the new image and start it again; the data is in the volume. The binary: replace the file and restart. Database migrations run on start; take a backup first (POST /v1/backups) if you want one to go back to. Calls in progress end with a restart, so do it when the phones are quiet, or wait for GET /v1/calls to be empty.