Skip to main content

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, dnd and forward, 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 tls listener 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 Replaces header 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.
Some keys are accepted and do nothing

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:

DoorTriggerAuthorisationTenant selected by
Trunkthe source IP matches a configured trunknone — the source address is the authorisationthe DID that was called
Endpointanything elsedigest auth against the tenant's accountsthe 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.

Where to go next