> ## Documentation Index
> Fetch the complete documentation index at: https://docs.firetone.com.au/llms.txt
> Use this file to discover all available pages before exploring further.

# Installing FireTone

> What runs where, and the order to bring it up in.

FireTone is three static binaries and a FreeSWITCH node.

|                      |                                                      |
| -------------------- | ---------------------------------------------------- |
| `firetoned`          | The control plane: API, call routing, media sessions |
| `firetonectl`        | Migrations and administration                        |
| `firetone-nodeagent` | Reports a node's health back to the control plane    |

All are built with `CGO_ENABLED=0`, so they copy onto a machine and run.

## Prerequisites

* PostgreSQL 14 or later
* Redis, on loopback
* FreeSWITCH, on the node
* `ffmpeg`, for media conversion

## Bringing it up

<Steps>
  <Step title="Database">
    Create the role and database, then set `FIRETONE_DATABASE_URL`.

    ```bash theme={null}
    export FIRETONE_DATABASE_URL="postgres://firetone:...@127.0.0.1:5432/firetone?sslmode=disable"
    ```
  </Step>

  <Step title="Build">
    ```bash theme={null}
    make build
    ```
  </Step>

  <Step title="Migrate">
    ```bash theme={null}
    ./bin/firetonectl migrate status
    ./bin/firetonectl migrate up
    ```

    <Warning>
      Migrations are compiled into `firetonectl`. If you change one, **rebuild
      the binary** or it will keep applying the old text — including the error
      it printed last time.
    </Warning>
  </Step>

  <Step title="Run">
    ```bash theme={null}
    ./bin/firetoned
    ```

    Health is on the API listener. Systemd units for both the daemon and the
    node agent are in `deploy/`.
  </Step>

  <Step title="The node">
    Build and configure FreeSWITCH from `deploy/node/`, then point it at the
    control plane's ESL listener.
  </Step>
</Steps>

## The daemon refuses to start if

These are deliberate refusals, not crashes:

* `FIRETONE_DATABASE_URL`, `FIRETONE_JWT_SECRET` or `FIRETONE_PROVISION_SECRET`
  is missing.
* The ESL, provisioning or media-stream listener is bound to `0.0.0.0`. Those
  three serve SIP passwords and raw call audio; a wildcard bind is not a
  configuration choice it will accept.

## Three that disable a feature rather than stopping the daemon

* Without **`FIRETONE_SECRET_KEY`**, provider credentials cannot be stored and
  virtual agents fall back to an echo provider.
* Without **`FIRETONE_PIN_PEPPER`**, agent PIN sign-in is **disabled entirely**
  rather than hashing without a pepper.
* Without **`FIRETONE_FS_CONF_DIR`**, the daemon provisions no switch: a
  register-mode trunk never registers and the carriers ACL is never written, so
  adding an address under a trunk changes the database and nothing else. The
  trunk's page says so; nothing else will.

<Note>
  The unit must also be able to write there. `install-service.sh` reads that
  variable and adds the generated directories to `ReadWritePaths` —
  `ProtectSystem=strict` makes everything else read-only whatever the ownership
  says, and the symptom is a "read-only file system" error that looks like a
  disk fault.
</Note>

## SIP-TLS for desk phones

A phone outside the office should register over **TLS on 5061**; on 5060 its
password exchange and audio cross the internet in the clear. The switch config
step turns TLS on by itself whenever it finds a certificate a phone could
trust, and decides again on every run, so an update cannot switch it off:

* `sip-tls.pem` in the secrets directory — key, certificate and chain in one
  file; or
* a real certificate for the host installed as `wss.pem` (the self-signed one a
  proxied box generates for itself does not count).

The certificate must name the host phones are given — the node's *Handset
server name* — and that name must resolve to the node itself, not to a web
proxy in front of the site. Where the site's domain is behind Cloudflare, issue
a separate certificate for an unproxied name such as `sip.<domain>` and install
it as `sip-tls.pem`. SIP and media are **IPv4 only**.

`FT_SIP_TLS=1` insists and refuses to continue without one; `FT_SIP_TLS=0` keeps
it off. `SIP_TLS_VERSION` defaults to `tlsv1.2,tlsv1.3`. Every refusal happens
before the running configuration is touched. Open **5061/tcp** in the cloud
security group; the host firewall rule is installed for you.

<Warning>
  With TLS switched on and no certificate, FreeSWITCH does not refuse — it
  generates a self-signed certificate and listens anyway, which looks like
  success from outside. That is why the installer writes the certificate
  itself, and `verify.sh` checks that the one 5061 serves is the installed one.
</Warning>

## Where the switch reaches the control plane

The switch connects to firetoned for every call (ESL) and every registration
(the directory). Both addresses are read from firetoned's own
`FIRETONE_ESL_LISTEN` and `FIRETONE_PROVISION_LISTEN` when the switch config
step runs, so the two ends agree. For a switch on another machine set
`FIRETONE_CONTROL_HOST` to the control plane's mesh address — ESL is plaintext,
so never a public one.

## Behind a reverse proxy

Set `FIRETONE_TRUSTED_PROXIES`. Without it every request appears to come from
the proxy, and per-IP rate limits collapse onto one address — which means they
protect nothing.

## Two commands worth knowing

The environment file is `0600` and owned by the service user, so the obvious
hand-typed forms fail in ways that blame the wrong thing. Both are shipped:

```bash theme={null}
sudo ./deploy/control-plane/ftctl doctor          # firetonectl, with the daemon's environment
sudo ./deploy/node/fscli -x 'sofia status'        # fs_cli, with this node's ESL password
```

<Note>
  The staging installer (`deploy/staging/install-ubuntu.sh`) is run regularly
  and its gaps are fixed as they are found; a first run on a genuinely clean
  machine is still the less-travelled path. Expect to read the scripts as well
  as run them — each explains what it refuses to do and why.
</Note>
