Sign inSign up

indianprogrammer/ipbx-agent

By indianprogrammer

•Updated 1 day ago

On-prem AI voice gateway: Asterisk PBX + local STT/LLM/TTS, no cloud

Image
0

75

indianprogrammer/ipbx-agent repository overview

⁠ipbx — Operator & User Manual

Hands-on guide for installing, operating, and tuning the on-prem AI IP-PBX for an ISP. See README.md (overview), PRD.md (product), SRD.md (spec).


⁠1. Architecture in one picture

ServiceContainerRoleHow you reach it
Asterisk PBX (ast)andrius/asteriskCall routing, WebRTC endpoint, ARISIP trunk :5060/u+tcp · WS :8088/ws (thru caddy for the web phone) · RTP 10000–10050/udp
AI agent (agent)ipbx-agentStasis app: answer → record → STT → LLM → TTSAdmin UI/API :8000
LLM (llm)ollama/ollamaLocal LLM (gemma2:2b)internal http://llm:11434⁠
Web phone (webphone)caddy:2-alpineHTTPS portal + SIP-over-WSS proxy + reverse proxy to agenthttps on host :443 (http :80 and :8080 redirect)
ModelsvolumeVAD/ASR/TTS (Silero / Whisper int8 / Piper)read-only /models inside agent
 caller ─▶ SIP trunk (5060) ─▶ Asterisk ── WSS:/ws (via caddy :443) ── browser JSSIP phone 2000
                                  │
                                  └─ ARI ─▶ agent ─▶ llm (ollama)  ─┐
                                                      admin UI/API │

⁠2. Prerequisites

  • Docker Engine + Compose plugin (tested: Docker 29.x, Compose v5.x).
  • Resources: 8 vCPU**, 16 GB RAM recommended, ~6 GB free disk for the stack plus ~350 MB for models on first run. Pure-CPU boxes work — use a smaller LLM on weak hardware (see §7).
  • Ports outbound for first run: Docker Hub, GitHub (models), npm.
  • Free host ports: 443, 8080, 8000, 5060, 8088, and UDP 10000–10050. If Apache/nginx already owns host port 80/443, see §9.2.

Note on port 80: if another web server (e.g. Apache) binds host :80, the web phone container is published as 8080:80/443:443 so nothing conflicts; adjust docker-compose.yml if you prefer a different scheme.

⁠3. First-time install

cd ipbx
cp .env.example .env

Edit .env — at minimum change these three secrets before exposing the box:

VariableExampleMeaning
ARI_PASSWORDagent-secret-change-meAgent↔Asterisk ARI auth
WEBPHONE_PASSWORD1122Password of web extension 2000
ADMIN_TOKENchange-me-admin-tokenBearer token for every admin API call

Then:

make models      # STT/VAD/TTS/espeak data + browser JSSIP bundle (~350 MB)
make up          # builds images, starts stack, pulls the LLM on first boot
make status      # sanity: asterisk channels + pjsip endpoints
docker compose ps

Everything is healthy when all four services report (healthy).

⁠4. Day‑to‑day URLs

WhatURLNotes
Web phone (agent phone)https://localhostRegister ext 2000, dial 1000 to chat with the AI. Accept the self-signed cert once (or install §9.1).
Admin dashboardhttp://localhost:8000/adminPaste ADMIN_TOKEN. Everything else below is the same UI/API.
Admin APIhttp://localhost:8000/api/*Authorization: Bearer $ADMIN_TOKEN
Recordingslisted in admin Recordings tabWAV files also at spool/recording/ (see §6)

Browsers lock the microphone on non-HTTPS origins. Always open the phone via https://localhost⁠ (the caddy TLS). Use hostname or IP as needed — the cert is issued on demand for any name → browser only trusts localhost.

⁠5. Web phone & calls

  1. Open https://localhost⁠ — one-click Register (uses ext 2000).
  2. Dial 1000 ▶ call. The AI answers with a Hindi/English greeting and listens.
  3. Say in Hindi, English, or Hinglish — e.g. "mujhe naya broadband connection chahiye" — the flow is: greeting (TTS) → caller speech (record/VAD) → Whisper → LLM (streamed) → Piper (per-sentence) → spoken back while the next sentences are already queued.
  4. Transfer to a human: press 0 (DTMF) on the phone keypad at any time — the caller (and context) is warm-transferred to extension 2000.
  5. Other extensions (e.g. 2100) can call each other directly; only 1000 is the AI.

⁠6. Admin dashboard tour

  • Calls — live calls with phase (LISTEN/SPEAK/TRANSFER), language, uptime; hangup any call; open a per-call transcript.
  • IVR — edit agent/configs/ivr.yaml in the browser: greeting text/language, transfer number, intents ("operator"/"representative", your ISP FAQs). Saves instantly; new calls use it.
  • Extensions — create SIP/WebRTC users (they're appended to pjsip and the module is hot-reloaded). Required wiring: an endpoint and a type=aor section named exactly like the extension number (e.g. 2100), plus an auth with matching username/password. Asterisk matches the REGISTER's To-username to the AOR name strictly, so a aor2100-style name returns 404. Passwords must match the WEBPHONE_* conventions.
  • Recordings — play/download caller recordings. Files live on the ast-spool volume at spool/recording/ (/media/spool/recording in the agent container, /var/spool/asterisk/recording in .s asterisk container). The dir is created and owned by uid 1000 automatically on agent boot.
  • System / LLM — health of engines+LLM+ARI, and hot-swap the LLM model/base URL (persists in .env after manual edit).

Admin API summary: GET /api/calls · GET /api/calls/{id}/transcript · POST /api/calls/{id}/hangup · GET|PUT /api/ivr · GET|POST /api/extensions · GET /api/recordings · GET|POST /api/config/llm · GET /api/system.

TOKEN=$(grep ADMIN_TOKEN .env | cut -d= -f2)
curl -s -H "Authorization: Bearer $TOKEN" http://localhost:8000/api/system
curl -s -H "Authorization: Bearer $TOKEN" http://localhost:8000/api/calls

⁠7. LLM tuning

  • Models are pulled at container boot; force a pull now: docker compose exec llm ollama pull gemma2:2b
  • Switch model/fastness in .env (LLM_MODEL), then docker compose restart llm agent. gemma2:2b is balanced for CPU; gemma2:9b is better Hindi but slower and needs GPU.
  • Realtime pacing is governed by agent/config.yaml → call.* (record length, silence-stop, playback timeout) and llm.temperature/max_tokens/stream.

⁠8. Backups & maintenance

  • Volumes that hold your data: ast-spool (recordings; path key above), ast-var (Asterisk lib/sounds), and the bind-mounted ./config/asterisk and ./agent/configs/ivr.yaml (edit in the filesystem directly).
  • Backup = copy config/, agent/configs/ivr.yaml, .env, and ast-spool (e.g. docker run --rm -v ipbx_ast-spool:/d -v $PWD:/b alpine cp -a /d /b/spool-backup).
  • make down stops the stack (volumes kept). docker compose down -v wipes recordings/models volumes — only after re-running make models.
  • Logs: make logs (all), make logs-agent (agent only). Agent logs one line per STT/reply; Asterisk ARI/WS activity under docker compose logs asterisk.

⁠9. Networking details (ops)

⁠9.1 TLS

The caddy webphone/Caddyfile uses :443 { tls internal { on_demand } }, so any hostname (localhost, LAN IP, phone.local) gets an internal CA cert. For a public name, edit webphone/Caddyfile: https://phone.yourisp.example { tls ... } and let caddy obtain a real cert, then change the :80 block and docker compose restart webphone.

⁠9.2 Port 80 already used (Apache/nginx)

The web phone is published 8080:80 + 443:443 by default. Visit http://localhost:8080 (redirects to https) or drop a reverse-proxy server block that does proxy_pass https://127.0.0.1:443, proxy_set_header Host $host, plus proxy_http_version 1.1; proxy_set_header Upgrade $http_upgrade; proxy_set_header Connection "upgrade"; for /ws*.

⁠9.3 WebRTC signaling path (for debugging)

Web phone (JSSIP) → wss://localhost/ws → caddy /ws* → asterisk:8088/ws (the PJSIP WebSocket transport listens on Asterisk's HTTP port, not the 5061 declared in pjsip.conf). Verify end-to-end with a raw SIP REGISTER; you should see SIP/2.0 401 Unauthorized (digest challenge) when the password is wrong/absent.

⁠9.4 SIP trunking / DID

Route PSTN DIDs to context inbound in config/asterisk/extensions.conf; any channel entering inbound is picked up by Stasis(ipbx-agent) and handled by the AI. Media for calls is Asterisk 8 kHz → STT auto-resamples to 16 kHz; make sure UDP 10000–10050 is open for RTP.

⁠10. Troubleshooting

SymptomLikely cause → fix
models errors on bootNot downloaded → make models; check paths in agent/config.yaml
Browser TLS warning on the phoneInternal Caddy CA — accept it, or install the cert (§9.1)
Mic blocked in browserYou're on http:// — use https://localhost
Web phone won't registerWrong ext/password, or WS path broke — check .env WEBPHONE_PASSWORD vs pjsip endpoint 2000, Caddyfile /ws* → asterisk:8088, endpoint has webrtc=yes. Raw SIP REGISTER must 401, then 200 with correct digest.
Calls work but no greeting/audioTwo gotchas: (1) the andrius/asterisk image declares VOLUME /var/lib/asterisk/sounds, which shadows the shared volume — compose must mount ast-var:/var/lib/asterisk/sounds and the agent must write TTS to the same volume root (AST_SOUNDS_DIR=/media/sounds = ast-var root). (2) Asterisk's .wav format is 8 kHz only — Piper outputs 22.05 kHz; the TTS engine resamples to 8 k (don't "fix" that). Symptom is Playback failed for sound:tmp/… / Unable to open format wav in docker compose logs asterisk.
Calls work but no outbound media on LANWebRTC SDP must advertise a reachable IP — local_net (the docker net) + external_media_address + endpoint media_address set to the host LAN IP in pjsip.conf; verify answer SDP shows c=IN IP4 <LAN-IP>.
Record → 500/Permission deniedspool/recording must exist & be owned by uid 1000 (agent fixes on boot). Manually: docker compose exec agent chown -R 1000:1000 /media/spool/recording
Empty-STT warnings in logsRecording held no speech (silent caller) — expected; fallback reply plays, loop continues.
LLM slow / no replyCheck docker compose logs llm, make status; pull the model (§7); try gemma2:2b
ARI unreachableari.conf password vs .env ARI_PASSWORD; ports 8088/8089 free
I want to see a real answer in a terminaldocker compose exec -w /app agent python3 -c "from core.llm_client import LLMClient; print(next(LLMClient('http://llm:11434','gemma2:2b').stream_chat([{'role':'user','content':'hi'}])))"

⁠11. Security checklist (ISP deployment)

  • Change all three secrets in .env (see §3) — do not ship defaults.
  • Put the admin API/UI behind a VPN/mTLS; it can hangup calls and edit the IVR.
  • Expose only: :443 (web phone), SIP trunk + RTP 10000–10050 (to trunk peer). :8000, :8088, :8089, :11434 should stay off the public internet.
  • Keep models/Ollama internal (llm isn't published to the host).
  • Run docker compose down -v + change ARI password before a demo, and never re-use the default admin bearer token.

Tag summary

Content type

Image

Digest

sha256:00491801d…

Size

174.9 MB

Last updated

1 day ago

docker pull indianprogrammer/ipbx-agent