A modern, container-native, SIP-only IP PBX: disposable signaling nodes, explainable routing, API-driven management, and high availability designed into the call architecture. The product spec is hello-pbx-spec.md.
Status: Phase 2 (trunks and routing). Phones register over SIP/UDP
through either SIP node and call each other through a B2BUA. Calls reach the
public network through SIP trunks (registration or IP-authenticated), chosen by
ordered inbound and outbound routes that rewrite numbers and caller ID. Trunks
are health-checked with OPTIONS, fail over to backups, respect concurrency
limits, and every call carries a routing trace in its record. A route tester
explains any number before it goes live. Media flows directly between the
endpoints; anchoring arrives in Phase 5. See docs/phones.md
for phones, docs/provisioning.md for configuring
them automatically, docs/trunks.md for trunks,
docs/voice-prompts.md for generating announcements and
voicemail greetings from text, docs/voicemail.md for the spoken voicemail and *97 menus, docs/ai-access.md for connecting AI agents over MCP and
docs/ai-agent.md for the in-product AI agent (assistant,
findings and proposals). For the IVR / auto-attendant see
docs/ivr.md, and for call queues docs/queues.md. For time and date routing (schedules, holiday calendars, time conditions, the day/night toggle) see
docs/time-conditions.md. For the call events that integrations read (live stream, history, scopes) see docs/integrations.md. To run Hello on Kubernetes (Helm chart, SIP
networking, highly available state, rolling upgrades) see
docs/kubernetes.md. For SIP over TLS and sips: see
docs/tls.md.
| Path | What |
|---|---|
cmd/hello-control |
Management/control-plane service: REST API, migrations |
cmd/hello-sip |
SIP node (Phase 0: ops endpoints and Valkey readiness only) |
internal/ |
config, telemetry (logs, redaction, metrics), ops (health/readiness/drain), api, migrate |
migrations/ |
PostgreSQL migrations (goose), embedded in hello-control |
web/ |
React + TypeScript UI (Vite, pnpm) |
deploy/docker-compose/ |
The development lab |
test/integration/ |
Tests that need PostgreSQL or Docker |
Requires Docker with Compose v2.
docker compose -f deploy/docker-compose/compose.yaml up -d --build --wait
curl localhost:8080/api/v1/version| Service | Host port |
|---|---|
UI (proxies /api) |
http://localhost:8080 |
| hello-control-1 | http://localhost:8081 (/api/v1/version, /api/v1/openapi.json, /readyz, /metrics) |
| kamailio | SIP/UDP 5080: the phone entry point |
| hello-control-1 provisioning | https://localhost:8443 (/p/…; TLS from the lab CA in the provcerts volume) |
| hello-sip-1, hello-sip-2 | http://localhost:8082, http://localhost:8083 (/readyz, /metrics) |
| hello-sip-1, hello-sip-2 | SIP/UDP 5060, 5062 (direct, bypassing Kamailio; for tests) |
| carrier-primary, carrier-backup | 8091, 8092 (simulated carriers) |
| hello-control-2, postgres, Valkey, Sentinel | none |
The lab runs on two networks:
| Network | Members |
|---|---|
edge |
10.89.53.0/24: kamailio (10.89.53.10, also its Hello-facing address), hello-sip-1 (.11), hello-sip-2 (.12), hello-control, the UI, the carriers. Every host port is published here |
state |
PostgreSQL; Valkey (valkey-1 primary at start, valkey-2 replica); sentinel-1..sentinel-3 (master set hello); and the Hello services that use them |
Kamailio's metrics are on port 9090 inside edge only. See docs/ha.md
for the HA topology and operations.
Sign in as admin with the lab-only password hello-lab-admin.
Stop and remove it with docker compose -f deploy/docker-compose/compose.yaml down -v.
go build ./... && go test -race ./...
golangci-lint run ./...
cd web && pnpm install && pnpm typecheck && pnpm lint && pnpm test && pnpm devpnpm dev proxies /api to localhost:8081, so run the lab, or
hello-control serve, alongside it.
Integration tests skip unless they are enabled. The migration test creates
and drops its own scratch database, so the URL needs CREATEDB rights:
HELLO_TEST_DATABASE_URL=postgres://user:pass@localhost:5432/db?sslmode=disable go test ./test/integration/
HELLO_DOCKER=1 go test -timeout 20m ./test/integration/ # images, and SIP flows against the labBoth services read HELLO_* environment variables and exit at startup,
naming the key, if one is missing or malformed.
| Variable | Service | Default |
|---|---|---|
HELLO_NODE_ID |
both | required |
HELLO_HTTP_ADDR |
both | :8081 control, :8082 sip |
HELLO_LOG_LEVEL |
both | info |
HELLO_SHUTDOWN_TIMEOUT |
both | 30s |
HELLO_DRAIN_DELAY |
both | 5s: how long /readyz fails before the listener closes |
HELLO_DATABASE_URL |
both | required; hello-sip reads devices and writes call records |
HELLO_VALKEY_ADDR |
both | required unless Sentinel is set; single Valkey: registrations, active calls, trunk state; needs Valkey 9+ (hash field expiry) |
HELLO_VALKEY_SENTINELS |
both | unset; comma-separated Sentinel host:port list, instead of HELLO_VALKEY_ADDR |
HELLO_VALKEY_MASTER |
both | unset; Sentinel master set name, required with HELLO_VALKEY_SENTINELS |
HELLO_SIP_DOMAIN |
both | required; digest realm and AOR host, identical everywhere |
HELLO_SECRET_KEY |
both | required: 32 bytes, base64; encrypts trunk passwords; identical on every node |
HELLO_BOOTSTRAP_ADMIN_PASSWORD |
control | unset; creates user admin when no user exists |
HELLO_SESSION_TTL |
control | 12h |
HELLO_EVENT_RETENTION_DAYS |
control | 7; how long call events are stored, 1 to 30 (docs/integrations.md) |
HELLO_SIP_BIND_ADDR |
sip | 0.0.0.0:5060 |
HELLO_SIP_ADVERTISED_ADDR |
sip | the bind address, which must then be a specific IP |
HELLO_SIP_NONCE_SECRET |
sip | required, at least 32 bytes, identical on every SIP node |
HELLO_SIP_REGISTER_MIN_EXPIRES / _MAX_EXPIRES |
sip | 60s / 1h |
HELLO_SIP_RING_TIMEOUT |
sip | 30s |
HELLO_SIP_MAX_CALL_DURATION |
sip | 4h; a call with no BYE (phone gone) is cleared after this |
HELLO_SIP_SESSION_EXPIRES_SECONDS |
sip | 1800; RFC 4028 Session-Expires Hello requests and applies on legs to phones; 0 turns session timers off |
HELLO_SIP_SESSION_MIN_SE_SECONDS |
sip | 90; the smallest interval accepted from a peer (422 with Min-SE below it); must not exceed the setting above |
HELLO_MEDIA_DEAD_SECONDS |
sip | 60; an anchored call with no RTP in either direction for this long is ended unless held; 0 turns it off |
HELLO_PAGE_MAX_SECONDS |
sip | 120; a server page ends after this many seconds |
HELLO_PAGE_CONCURRENT |
sip | 5; pages one node runs at once, beyond that the caller hears busy |
HELLO_PAGE_FANOUT |
sip | 200; legs of one page, beyond that the caller hears busy |
HELLO_PAGE_MULTICAST_IFACE |
sip | empty (the system route); the interface a paging group's multicast packets leave on, which must be on the phones' LAN |
HELLO_PAGE_MULTICAST_TTL |
sip | 1; routers a page's multicast packets may cross |
HELLO_SIP_AUTH_FAIL_LIMIT / _WINDOW |
sip | 10 failures per 5m per source IP; set the limit on control too (Diagnostics shows which sources it blocks) |
HELLO_SIP_STATE_TIMEOUT |
sip | 200ms; Valkey calls while handling SIP |
HELLO_SIP_TRUSTED_PROXIES |
sip | unset (trust none); CIDRs of the SIP balancers (Kamailio) whose Path and client address are believed |
HELLO_DRAIN_TIMEOUT |
sip | 2h; a draining node hangs up remaining calls after this |
HELLO_ROLLOUT_WAIT |
sip | 0 (off); on SIGTERM, wait up to this long for the one-at-a-time restart token and a READY peer before draining (docs/ha.md) |
HELLO_MEMBER_HEARTBEAT |
sip | 1s; how often the node refreshes its cluster membership in Valkey (at most 1.33s: a third of the 4s membership TTL) |
HELLO_PROV_PUBLIC_URL |
control | unset (off); the https:// host phones reach; boot and CA URLs use its plain-HTTP form |
HELLO_PROV_ADDR |
control | :8083; the provisioning listener |
HELLO_PROV_SIP_SERVER |
control | HELLO_SIP_ADVERTISED_ADDR; the host:port provisioned phones register with (Kamailio) |
HELLO_PROV_TRUSTED_PROXIES |
control | unset; CIDRs of the reverse proxies whose X-Forwarded-Proto/-For are believed |
HELLO_PROV_CA_CERT |
control | unset; PEM served at /p/ca.crt for phones to trust |
HELLO_PROV_TLS_CERT / _TLS_KEY |
control | unset; TLS on the listener itself, when no proxy terminates it |
HELLO_PROV_BOOT_CIDRS |
control | unset (any); sources allowed the DHCP trust-on-first-use hand-off |
HELLO_PROV_TOKEN_GRACE / _RESYNC |
control | 7d / 24h; old-token grace after a rotation; the phones' re-check interval |
HELLO_PROV_BOOK_REFRESH |
control | 15m; how often phones with a refresh setting re-fetch their address book (at least 1m) |
HELLO_PROV_PUSH_RATE |
sip | 20; check-sync pushes (resync, reboot) claimed per minute across the cluster |
HELLO_PROV_AUTO_RESYNC |
control | false; push a resync to a phone 30 s after a change to what its configuration renders from |
HELLO_DIAL_INTL_PREFIX / _COUNTRY_CODE |
control | 00 / unset; digits standing for +, and the country code a leading 0 belongs to (lookup keys) |
HELLO_PROV_TIMEZONE / _NTP |
control | UTC / pool.ntp.org |
HELLO_VOICEMAIL_LANGUAGE |
both | en; deployment language of the spoken voicemail prompts (en, nl); a box can set its own, docs/voicemail.md |
HELLO_TIMEZONE |
sip | HELLO_PROV_TIMEZONE, else UTC; the zone message times are spoken in |
HELLO_PROV_AUDIT_RETENTION |
control | 90d; how long the fetch audit is kept |
HELLO_PROV_RATE_* |
control | IP_PER_MIN 60, DENIED_PER_10MIN 10, PHONE_PER_HOUR 30 |
HELLO_PROV_SNOM_* etc. |
control | unset; vendor redirect credentials (_SNOM_, _YEALINK_, _YMCS_, _GDMS_), see docs/provisioning.md |
HELLO_PUBLIC_URL |
control | unset; https:// base of Hello for OAuth and /mcp (off when unset), see docs/ai-access.md |
HELLO_OAUTH_ACCESS_TTL |
control | 1h; OAuth access token lifetime |
HELLO_OAUTH_REFRESH_TTL / _REFRESH_MAX |
control | 30 / 90 days; refresh token idle and absolute limits |
HELLO_OAUTH_DCR |
control | false; true allows dynamic client registration |
HELLO_OAUTH_CIMD_ALLOW_PRIVATE |
control | false; true fetches client ID metadata from private addresses |
HELLO_MCP_ALLOWED_ORIGINS |
control | unset; browser origins allowed on /mcp |
The UI container proxies /api/, /mcp, /oauth/ (except the console's /oauth/consent) and /.well-known/oauth- to HELLO_CONTROL_UPSTREAM and re-resolves it through HELLO_DNS_RESOLVER (default 127.0.0.11, Docker's DNS; use your cluster DNS elsewhere).
Every SIP node must share the same HELLO_SIP_NONCE_SECRET. A node with a
different one rejects digest challenges issued by the others.
Commands: hello-control serve | migrate up | migrate status | user add [--role user|viewer|operator|admin] <username> (password on stdin; the role defaults to viewer, the first user is always admin), hello-sip serve. Users, their roles and extension self-service, and voicemail email are described in docs/user-accounts.md; the SMTP_* variables are only the fallback for settings saved in Platform → Email.
Apache-2.0. See LICENSE.