Introduction
Lyno is a multi-tenant PBX written in Go and shipped as a single binary. The whole phone system — listeners, carrier trunks, tenants, accounts, extensions and dialplans — is described in YAML files that are read once at startup.
Tenants share the SIP listeners and carrier trunks but are otherwise isolated: each has its own phones, extension numbers, dialplans and DIDs. Extension 100 of one tenant has nothing to do with extension 100 of another.
What it is
- Configuration is the source of truth. There is no database. What is in the
files is what the PBX does, which makes a configuration change reviewable in a
diff and a rollback a
git revert. - Multi-tenant from the ground up. Endpoints are attributed to a tenant by the SIP domain they register with; inbound calls by the DID they arrive on. The digest realm is the tenant's own domain, so one tenant's credentials cannot be replayed against another.
- A real dialplan. Twelve step types:
play,dial,moh,hangup,ivr,goto,queue,record,voicemail,voicemail_check,dndandforward, each optionally gated by a time condition. - It writes things down. Call logs, recordings and voicemail go to a separate data directory and never back into the configuration.
- Operated from a terminal. A live dashboard shows calls, registrations, trunks, what the security guard is blocking, and logs. A separate editor TUI walks the entire configuration and validates before it writes.
- Read over HTTP when you want it. A management API serves call logs, recordings, voicemail and status behind scoped bearer tokens. It is off by default.
- Pushed over HTTP when you would rather not poll. Webhooks deliver call, voicemail and registration events to a signed HTTPS endpoint per tenant, from a queue that survives a restart.
- A native app is one more phone. With a LiveKit deployment configured, mobile apps ring alongside the desk phone and get their own device API with revocable tokens.
- One static binary. A distroless container image of roughly 19 MB, or a single binary on a host.
What it is not
- There is no hot reload. Configuration is read at startup; restart the PBX
to apply a change. The single exception is
SIGHUP, which reloads API tokens and nothing else. - There is no transcoding. Both legs of a bridged call must agree on a codec, so Lyno offers the same list (G.711 alaw/ulaw) on both sides.
- There is no SIP TLS and no SRTP that works today. The keys exist and are
validated; a
tlslistener does not start. Run SIP over UDP or TCP behind a network you control. - There is no attended transfer and no message-waiting lamp. Blind
transfer works; a
Replacesheader is stripped. - It is not a session border controller. It rate-limits and bans source addresses and enforces fraud ceilings on outbound calls, but put it behind a firewall you control and treat the trunk source addresses as the perimeter for inbound calls.
Several settings are validated, offered by the editor, and not yet read at run
time — SIP TLS, security.acl, security.requireSrtp, recording.retention,
recording.beep, voicemail.mwi, featureCodes: and queue dynamic: among
them. Each is called out on the page that covers it, and they are listed
together under
the security guard.
How a call is handled
A call enters through exactly one of two doors, and everything follows from which one:
| Door | Trigger | Authorisation | Tenant selected by |
|---|---|---|---|
| Trunk | the source IP matches a configured trunk | none — the source address is the authorisation | the DID that was called |
| Endpoint | anything else | digest auth against the tenant's accounts | the SIP domain in the request |
The LiveKit bridge enters through the trunk door and then branches: an app-originated call is authorised by a one-time grant rather than by its source address.
That fork is the single most important thing to understand about Lyno; it is covered in detail in How a call is routed.