Skip to content

Your switchboard

Your own switchboard

Operate an Asterisk PBX through one Saphira desired-state engine: a scriptable root CLI, a fullscreen terminal interface and an optional bearer-authenticated control channel.

Saphira Linux dragon mascot

What a PBX, SIP, extension and trunk are

A private branch exchange (PBX) is the system that decides what happens when someone dials a number: which local phone should ring, which outside provider should carry a call, which inbound number reaches which destination, and which routes are permitted. It is infrastructure for calls, not a telephone provider by itself.

SIP is a signalling protocol commonly used by phones and providers to register endpoints and set up calls. An extension is a local address and endpoint—often a desk phone, softphone or other SIP client. A trunk is a connection to a provider or another PBX. An inbound DID is a number received from a trunk; an outbound route selects a trunk and rewrites a dialled number when appropriate.

Asterisk is the telephony engine that speaks these protocols and handles the live call path. saphira-pbx is its management and control layer. It does not link against Asterisk libraries; it controls the local Asterisk control socket through asterisk -rx, renders managed configuration, and leaves the telephony engine to do telephony work.

What version 0.2.0 actually provides

The published package is based on the tagged saphira-pbx 0.2.0 interface. It provides provider and trunk records; PJSIP local extensions; DIDs mapped to extensions; outbound routes; explicit emergency-route validation for 999/112; validation, deterministic render, diff, generation-atomic apply with rollback; diagnostics; backup/restore; and token/MCP lifecycle management.

Its v1 scope does not establish a promise of working calls with a particular telephone provider. Do not claim provider interoperability until credentials, NAT, codec policy, routing and an actual call have been tested in your deployment. IVRs, queues, hunt groups and time conditions are not v1 tables. Do not describe them as available. Keep later, uncommitted source work out of operational instructions for this shipped package.

Important operating boundary

The product manages generated configuration; it does not turn unknown provider credentials or an unprepared network into a working telephone service. The first honest result may be a successful configuration validation followed by a failed registration check and automatic rollback. That is useful evidence, not an invitation to weaken security.

How the control layer works

SQLite is the desired-state authority at /var/lib/saphira-pbx/pbx.db. Generated Asterisk configuration beneath /etc/asterisk/saphira/ is derived output, with immutable generations and a current symlink selecting the committed version. Do not hand-edit generated files: the next render or apply will replace derived output from the database.

An apply validates the model, renders a complete generation, performs structural checks, atomically switches the current generation, sends targeted Asterisk reloads, and checks live state. If a failure occurs, it returns to the prior generation and verifies the rollback. A changed live registration credential may still require an explicit Asterisk restart; v1 deliberately does not restart Asterisk silently.

The CLI and TUI call the same engine, so validation, audit logging, locking, rendering and rollback have one path. The state database and recovery material are root-only. The Asterisk identity receives only the generated configuration it needs, not the full state database.

The local control and render path.
Root CLI / TUI

Adds or inspects desired state through the same control engine.

SQLite desired state

Providers, trunks, extensions, DIDs, routes, tokens and audit record.

Generation renderer

Creates deterministic Asterisk files and atomically selects one generation.

Asterisk local socket

Receives targeted reload commands and provides runtime/registration observations.

Install and perform the first health check

# Run as root. saphira-pbx declares Asterisk as a runtime dependency.
apk add saphira-pbx

# Confirm the installed interface and inspect the local integration.
saphira-pbx version
saphira-pbx doctor

The package installs the control binary, its manual, the MCP configuration file and service units. It depends on Asterisk, SQLite, OpenSSL, Jansson, ncurses, tar and diff utilities. Installing it does not enable its network control service automatically.

version should identify the installed product version. doctor is the first operational check: it examines privilege, the Asterisk binary and daemon response, the current-generation symlink, required Asterisk include lines, required modules, database integrity/schema and file modes. Treat every reported failure as a prerequisite to fix, not a harmless warning.

The product verifies required operator-added #include lines but does not edit the main Asterisk configuration for you. If doctor reports missing includes, read the installed product documentation and inspect the existing Asterisk configuration before making a change. Do not guess include paths or overwrite a working Asterisk deployment.

Use the CLI and TUI

# No arguments opens the fullscreen TUI and requires a terminal.
saphira-pbx

# Scriptable inspection commands.
saphira-pbx status
saphira-pbx doctor
saphira-pbx provider list
saphira-pbx trunk list
saphira-pbx extension list
saphira-pbx did list
saphira-pbx route list

The TUI has eight screens: dashboard, providers/trunks, extensions, DIDs, routes, apply, doctor and MCP clients. Use 1–8 to select a screen; arrows, Page Up and Page Down to move; r to refresh; and q to go back or quit. In this release, object editing remains in the CLI; the TUI observes and applies through the same engine.

status displays daemon and registration summary information. It is a runtime observation, not a call-quality test. doctor is the more comprehensive integrity check. Production commands require root; the non-root switches used by the project test harness are not a deployment method.

Build desired state in a safe order

Start with records that can be inspected and rendered before you attempt a provider registration. Use values from the provider’s own credential portal; do not substitute the placeholders below for real settings. Provider secrets must meet the product’s validation policy, and some provider-issued SIP passwords may be too short for it.

# A local extension example. Choose a real strong secret; this writes desired state, not a live phone.
saphira-pbx extension add --number 100 --display "Office test" --secret CHANGEME-AT-LEAST-12-CHARACTERS
saphira-pbx extension list
saphira-pbx validate
saphira-pbx render --dir /root/pbx-render-preview

A successful add returns without an error and the list shows extension 100 without revealing its secret. validate checks the whole model; render --dir writes a reviewable preview to the directory you name instead of applying it to Asterisk. Review the output and correct the model rather than editing generated files.

# Generic provider/trunk shape. Replace every CHANGEME value with facts from the provider.
saphira-pbx provider add --name my-itsp --transport udp \
  --registrar sip.provider.example --username CHANGEME-USER \
  --secret CHANGEME-SECRET
saphira-pbx trunk add --name my-trunk --provider my-itsp \
  --endpoint sip.provider.example --auth-user CHANGEME-USER \
  --secret CHANGEME-SECRET
saphira-pbx provider list
saphira-pbx trunk list
saphira-pbx validate

A provider records identity and registration details; a trunk is the actual ingress/egress leg attached to it. The supported transport values include UDP, TCP, TLS, IAX and IAX2. TLS depends on appropriate provider CA trust; an outbound proxy, codec list and DTMF policy must match the provider rather than this example.

Add DIDs only when they map to a known local extension. Add outbound routes only after a trunk exists. Emergency 999/112 routes must be explicit --emergency routes: ordinary prefix routes are prevented from swallowing these numbers, and an emergency route needs an enabled trunk. This safety rule does not prove that an emergency call will reach a real service; validate that separately with your provider and local obligations.

Apply, verify, recover

# Review differences, then exercise the apply path without changing live configuration.
saphira-pbx diff
saphira-pbx apply --dry-run

# With verified provider facts and a recovery plan, apply and wait for registration verification.
saphira-pbx apply --verify-secs 60
saphira-pbx doctor

diff compares desired output with the live generated configuration. apply --dry-run validates the path without switching the generation. A real apply is generation-atomic: it either commits a verified generation or restores the previous one. With a provider trunk, --verify-secs 60 waits for registration; if it does not reach Registered within the budget, the product rolls the change back.

Success means the command completed, the expected generated generation is current, and doctor reports healthy prerequisites. It is still not proof that every phone, inbound DID, NAT route, codec or emergency path works. Make an approved test call only after those external details are configured and authorised.

# Recovery and retention operations are root-only and act on managed state.
saphira-pbx backup
saphira-pbx rollback
saphira-pbx restore --file ARCHIVE_NAME

Back up before risky changes and keep the archives in a protected recovery location. Restore accepts only an archive name resolved in the product’s own backup location; do not treat it as a general-purpose file-import mechanism. Test a restoration procedure before relying on it for an outage.

Optional MCP control channel

The MCP channel is for a named dashboard, agent or other management client. It uses the same validation, audit, locking, apply and rollback engine as the CLI. It is not an anonymous telephony API and it has no raw shell or arbitrary Asterisk-command operation.

# Inspect the packaged listener configuration before binding anything.
saphira-pbx mcp serve --dry-run
saphira-pbx mcp status

# Issue a least-privilege monitoring credential. Copy its token to a secret store immediately.
saphira-pbx mcp client add --name monitoring --scope pbx.read
saphira-pbx mcp client list
saphira-pbx mcp client scopes

The packaged /etc/saphira-pbx/mcp.conf defaults to loopback-only plain HTTP on port 8443. A client credential is a bearer token: only its digest is retained, while its plaintext is shown at creation time. The default configuration-client scopes are pbx.read, pbx.configure and pbx.apply; pbx.runtime is separately expressible for targeted reload capability.

Non-loopback binding is refused without a valid TLS certificate and private key. Supply and validate TLS material before exposing the listener; saphira-pbx mcp tls-check --cert PATH --key PATH --hostname NAME checks a certificate/key pair using the same validity definition as startup. Do not expose a plain HTTP listener to a network as a shortcut.

# On a systemd host, enable the packaged optional MCP service only after the configuration passes dry-run.
systemctl enable --now saphira-pbx-mcp
systemctl status saphira-pbx-mcp --no-pager
saphira-pbx mcp status

The package also supplies an OpenRC service, and neither service is enabled merely by installation. The daemon initially runs with the privileges needed to bind, read key material and open protected state, then drops its network half to the dedicated saphira-pbx-mcp identity. Every request still re-authenticates its bearer token; the frontend does not receive the authority to bypass the database’s capability checks.

Security, monitoring and troubleshooting

  • Secrets: do not put provider passwords or bearer tokens in shell history, screenshots, tickets or generated-config copies. The product redacts secrets in audit data; operators must preserve that boundary too.
  • Tokens: list, rotate and revoke named MCP clients as roles change. Revocation takes effect on the next authentication; use the smallest scope set that can do the job.
  • Asterisk integration: use saphira-pbx doctor after package, Asterisk or permission changes. It detects missing includes, modules, state modes and daemon response before a call path is attempted.
  • Registration failure: check provider registrar, username, secret, proxy, transport, codec policy and network/NAT facts against the provider portal. A failed apply is designed to roll back; do not repeatedly edit generated Asterisk files.
  • Credential changes: a targeted reload cannot always apply changed live-registration credentials. Read the apply result and perform an explicit service restart only when your maintenance process permits it.
# The first diagnostics for a PBX problem.
saphira-pbx doctor
saphira-pbx status
saphira-pbx validate
saphira-pbx diff
journalctl -u saphira-pbx-mcp -b --no-pager

The MCP journal is relevant only when the optional network service is enabled. Asterisk and provider-call diagnostics remain separate from the MCP transport. Work from the first failed result: state/model validation, generated configuration, Asterisk local control, provider registration, then a permitted call test.

What remains untested or outside this guide

This guide verifies the shipped management interface and its documented behaviours. It does not claim that a particular provider, handset, NAT arrangement, emergency route, inbound DID, TLS provider setup or telephone call works in every environment. Those require actual credentials, network design and call testing.

The product is deliberately separate from the OpenStack roles. It shares the RTFM navigation but is neither an OpenStack component nor a dependency of a cloud deployment.