New packages: - secubox-app-voip: Asterisk PBX in LXC container - luci-app-voip: Dashboard with extensions, trunks, click-to-call VoIP features: - voipctl CLI for container, extensions, trunks, calls, voicemail - OVH Telephony API auto-provisioning for SIP trunks - Click-to-call web interface with quick dial - RPCD backend with 15 methods Jabber VoIP integration: - Jingle VoIP support (STUN/TURN via mod_external_services) - SMS relay via OVH (messages to sms@domain) - Voicemail notifications via Asterisk AMI → XMPP - 9 new RPCD methods for VoIP features Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
478 lines
9.7 KiB
Bash
478 lines
9.7 KiB
Bash
#!/bin/sh
|
|
|
|
# RPCD backend for VoIP LuCI app
|
|
|
|
. /usr/share/libubox/jshn.sh
|
|
|
|
VOIPCTL="/usr/sbin/voipctl"
|
|
|
|
# Helper to get UCI value
|
|
uci_get() {
|
|
local section="$1"
|
|
local option="$2"
|
|
local default="$3"
|
|
local val
|
|
val=$(uci -q get "voip.${section}.${option}")
|
|
echo "${val:-$default}"
|
|
}
|
|
|
|
# Get container status
|
|
get_container_status() {
|
|
local state="not_installed"
|
|
local running="false"
|
|
local lxc_info=""
|
|
|
|
if [ -d "/srv/lxc/voip" ]; then
|
|
state="installed"
|
|
lxc_info=$(lxc-info -n voip 2>/dev/null)
|
|
if echo "$lxc_info" | grep -q "State:.*RUNNING"; then
|
|
running="true"
|
|
fi
|
|
fi
|
|
|
|
echo "$state $running"
|
|
}
|
|
|
|
# Method: status
|
|
method_status() {
|
|
local enabled sip_port
|
|
local container_state running
|
|
local info asterisk_up trunk_registered active_calls extensions
|
|
|
|
enabled=$(uci_get main enabled 0)
|
|
sip_port=$(uci_get asterisk sip_port 5060)
|
|
|
|
info=$(get_container_status)
|
|
container_state=$(echo "$info" | awk '{print $1}')
|
|
running=$(echo "$info" | awk '{print $2}')
|
|
|
|
asterisk_up="false"
|
|
trunk_registered="false"
|
|
active_calls=0
|
|
extensions=0
|
|
|
|
if [ "$running" = "true" ]; then
|
|
lxc-attach -n voip -- pgrep asterisk >/dev/null 2>&1 && asterisk_up="true"
|
|
lxc-attach -n voip -- asterisk -rx "pjsip show registrations" 2>/dev/null | grep -q "Registered" && trunk_registered="true"
|
|
active_calls=$(lxc-attach -n voip -- asterisk -rx "core show channels" 2>/dev/null | grep -oE "^[0-9]+ active" | cut -d' ' -f1 || echo 0)
|
|
extensions=$(lxc-attach -n voip -- asterisk -rx "pjsip show endpoints" 2>/dev/null | grep -c "^[0-9]" || echo 0)
|
|
fi
|
|
|
|
local domain haproxy_enabled
|
|
domain=$(uci_get ssl domain "")
|
|
haproxy_enabled=$(uci_get ssl enabled "0")
|
|
|
|
json_init
|
|
json_add_string "enabled" "$enabled"
|
|
json_add_string "container_state" "$container_state"
|
|
json_add_string "running" "$running"
|
|
json_add_string "asterisk" "$asterisk_up"
|
|
json_add_string "trunk_registered" "$trunk_registered"
|
|
json_add_int "active_calls" "${active_calls:-0}"
|
|
json_add_int "extensions" "${extensions:-0}"
|
|
json_add_string "sip_port" "$sip_port"
|
|
json_add_string "domain" "$domain"
|
|
json_add_string "haproxy" "$haproxy_enabled"
|
|
json_dump
|
|
}
|
|
|
|
# Method: start
|
|
method_start() {
|
|
local output
|
|
output=$($VOIPCTL start 2>&1)
|
|
local rc=$?
|
|
|
|
json_init
|
|
if [ $rc -eq 0 ]; then
|
|
json_add_boolean "success" 1
|
|
json_add_string "message" "VoIP started successfully"
|
|
else
|
|
json_add_boolean "success" 0
|
|
json_add_string "error" "$output"
|
|
fi
|
|
json_dump
|
|
}
|
|
|
|
# Method: stop
|
|
method_stop() {
|
|
local output
|
|
output=$($VOIPCTL stop 2>&1)
|
|
local rc=$?
|
|
|
|
json_init
|
|
if [ $rc -eq 0 ]; then
|
|
json_add_boolean "success" 1
|
|
json_add_string "message" "VoIP stopped successfully"
|
|
else
|
|
json_add_boolean "success" 0
|
|
json_add_string "error" "$output"
|
|
fi
|
|
json_dump
|
|
}
|
|
|
|
# Method: install
|
|
method_install() {
|
|
local output
|
|
output=$($VOIPCTL install 2>&1)
|
|
local rc=$?
|
|
|
|
json_init
|
|
if [ $rc -eq 0 ]; then
|
|
json_add_boolean "success" 1
|
|
json_add_string "message" "VoIP installed successfully"
|
|
else
|
|
json_add_boolean "success" 0
|
|
json_add_string "error" "$output"
|
|
fi
|
|
json_dump
|
|
}
|
|
|
|
# Method: uninstall
|
|
method_uninstall() {
|
|
local output
|
|
output=$($VOIPCTL uninstall 2>&1)
|
|
local rc=$?
|
|
|
|
json_init
|
|
if [ $rc -eq 0 ]; then
|
|
json_add_boolean "success" 1
|
|
json_add_string "message" "VoIP uninstalled successfully"
|
|
else
|
|
json_add_boolean "success" 0
|
|
json_add_string "error" "$output"
|
|
fi
|
|
json_dump
|
|
}
|
|
|
|
# Method: logs
|
|
method_logs() {
|
|
local lines="${1:-50}"
|
|
local output
|
|
|
|
if [ -d "/srv/lxc/voip" ]; then
|
|
output=$($VOIPCTL logs "$lines" 2>&1 | tail -n "$lines")
|
|
else
|
|
output="Container not installed"
|
|
fi
|
|
|
|
json_init
|
|
json_add_string "logs" "$output"
|
|
json_dump
|
|
}
|
|
|
|
# Method: ext_add
|
|
method_ext_add() {
|
|
read -r input
|
|
json_load "$input"
|
|
json_get_var ext ext
|
|
json_get_var name name
|
|
json_get_var password password
|
|
|
|
if [ -z "$ext" ] || [ -z "$name" ]; then
|
|
json_init
|
|
json_add_boolean "success" 0
|
|
json_add_string "error" "Extension and name are required"
|
|
json_dump
|
|
return
|
|
fi
|
|
|
|
local output
|
|
if [ -n "$password" ]; then
|
|
output=$($VOIPCTL ext add "$ext" "$name" "$password" 2>&1)
|
|
else
|
|
output=$($VOIPCTL ext add "$ext" "$name" 2>&1)
|
|
fi
|
|
local rc=$?
|
|
|
|
json_init
|
|
if [ $rc -eq 0 ]; then
|
|
json_add_boolean "success" 1
|
|
json_add_string "message" "Extension $ext created"
|
|
local new_pass=$(echo "$output" | grep -oE 'Password: [^ ]+' | cut -d: -f2 | tr -d ' ')
|
|
json_add_string "password" "$new_pass"
|
|
else
|
|
json_add_boolean "success" 0
|
|
json_add_string "error" "$output"
|
|
fi
|
|
json_dump
|
|
}
|
|
|
|
# Method: ext_del
|
|
method_ext_del() {
|
|
read -r input
|
|
json_load "$input"
|
|
json_get_var ext ext
|
|
|
|
if [ -z "$ext" ]; then
|
|
json_init
|
|
json_add_boolean "success" 0
|
|
json_add_string "error" "Extension is required"
|
|
json_dump
|
|
return
|
|
fi
|
|
|
|
local output
|
|
output=$($VOIPCTL ext del "$ext" 2>&1)
|
|
local rc=$?
|
|
|
|
json_init
|
|
if [ $rc -eq 0 ]; then
|
|
json_add_boolean "success" 1
|
|
json_add_string "message" "Extension $ext deleted"
|
|
else
|
|
json_add_boolean "success" 0
|
|
json_add_string "error" "$output"
|
|
fi
|
|
json_dump
|
|
}
|
|
|
|
# Method: ext_list
|
|
method_ext_list() {
|
|
local extensions=""
|
|
|
|
if lxc-info -n voip 2>/dev/null | grep -q "RUNNING"; then
|
|
# Get extensions from UCI
|
|
extensions=$(uci show voip 2>/dev/null | grep "^voip\.ext_" | grep "\.name=" | while read line; do
|
|
ext=$(echo "$line" | sed "s/voip\.ext_//" | cut -d. -f1)
|
|
name=$(echo "$line" | cut -d= -f2 | tr -d "'")
|
|
echo "${ext}:${name}"
|
|
done | paste -sd,)
|
|
fi
|
|
|
|
json_init
|
|
json_add_string "extensions" "$extensions"
|
|
json_dump
|
|
}
|
|
|
|
# Method: call_originate
|
|
method_call_originate() {
|
|
read -r input
|
|
json_load "$input"
|
|
json_get_var from_ext from_ext
|
|
json_get_var to_number to_number
|
|
|
|
if [ -z "$from_ext" ] || [ -z "$to_number" ]; then
|
|
json_init
|
|
json_add_boolean "success" 0
|
|
json_add_string "error" "From extension and to number are required"
|
|
json_dump
|
|
return
|
|
fi
|
|
|
|
local output
|
|
output=$($VOIPCTL call "$from_ext" "$to_number" 2>&1)
|
|
local rc=$?
|
|
|
|
json_init
|
|
if [ $rc -eq 0 ]; then
|
|
json_add_boolean "success" 1
|
|
json_add_string "message" "Call initiated"
|
|
json_add_string "channel" "$output"
|
|
else
|
|
json_add_boolean "success" 0
|
|
json_add_string "error" "$output"
|
|
fi
|
|
json_dump
|
|
}
|
|
|
|
# Method: calls_list
|
|
method_calls_list() {
|
|
local calls=""
|
|
|
|
if lxc-info -n voip 2>/dev/null | grep -q "RUNNING"; then
|
|
calls=$(lxc-attach -n voip -- asterisk -rx "core show channels concise" 2>/dev/null | head -10)
|
|
fi
|
|
|
|
json_init
|
|
json_add_string "calls" "$calls"
|
|
json_dump
|
|
}
|
|
|
|
# Method: trunk_status
|
|
method_trunk_status() {
|
|
local registered="false"
|
|
local status=""
|
|
|
|
if lxc-info -n voip 2>/dev/null | grep -q "RUNNING"; then
|
|
status=$(lxc-attach -n voip -- asterisk -rx "pjsip show registrations" 2>/dev/null)
|
|
echo "$status" | grep -q "Registered" && registered="true"
|
|
fi
|
|
|
|
json_init
|
|
json_add_string "registered" "$registered"
|
|
json_add_string "status" "$status"
|
|
json_dump
|
|
}
|
|
|
|
# Method: trunk_add_ovh
|
|
method_trunk_add_ovh() {
|
|
local output
|
|
output=$($VOIPCTL trunk add ovh 2>&1)
|
|
local rc=$?
|
|
|
|
json_init
|
|
if [ $rc -eq 0 ]; then
|
|
json_add_boolean "success" 1
|
|
json_add_string "message" "OVH trunk configured"
|
|
else
|
|
json_add_boolean "success" 0
|
|
json_add_string "error" "$output"
|
|
fi
|
|
json_dump
|
|
}
|
|
|
|
# Method: configure_haproxy
|
|
method_configure_haproxy() {
|
|
local output
|
|
output=$($VOIPCTL configure-haproxy 2>&1)
|
|
local rc=$?
|
|
|
|
json_init
|
|
if [ $rc -eq 0 ]; then
|
|
json_add_boolean "success" 1
|
|
json_add_string "message" "HAProxy configured for VoIP"
|
|
else
|
|
json_add_boolean "success" 0
|
|
json_add_string "error" "$output"
|
|
fi
|
|
json_dump
|
|
}
|
|
|
|
# Method: emancipate
|
|
method_emancipate() {
|
|
read -r input
|
|
json_load "$input"
|
|
json_get_var domain domain
|
|
|
|
if [ -z "$domain" ]; then
|
|
json_init
|
|
json_add_boolean "success" 0
|
|
json_add_string "error" "Domain is required"
|
|
json_dump
|
|
return
|
|
fi
|
|
|
|
local output
|
|
output=$($VOIPCTL emancipate "$domain" 2>&1)
|
|
local rc=$?
|
|
|
|
json_init
|
|
if [ $rc -eq 0 ]; then
|
|
json_add_boolean "success" 1
|
|
json_add_string "message" "VoIP emancipated to $domain"
|
|
else
|
|
json_add_boolean "success" 0
|
|
json_add_string "error" "$output"
|
|
fi
|
|
json_dump
|
|
}
|
|
|
|
# List available methods
|
|
list_methods() {
|
|
json_init
|
|
json_add_object "status"
|
|
json_close_object
|
|
json_add_object "start"
|
|
json_close_object
|
|
json_add_object "stop"
|
|
json_close_object
|
|
json_add_object "install"
|
|
json_close_object
|
|
json_add_object "uninstall"
|
|
json_close_object
|
|
json_add_object "logs"
|
|
json_add_int "lines" 50
|
|
json_close_object
|
|
json_add_object "ext_add"
|
|
json_add_string "ext" ""
|
|
json_add_string "name" ""
|
|
json_add_string "password" ""
|
|
json_close_object
|
|
json_add_object "ext_del"
|
|
json_add_string "ext" ""
|
|
json_close_object
|
|
json_add_object "ext_list"
|
|
json_close_object
|
|
json_add_object "call_originate"
|
|
json_add_string "from_ext" ""
|
|
json_add_string "to_number" ""
|
|
json_close_object
|
|
json_add_object "calls_list"
|
|
json_close_object
|
|
json_add_object "trunk_status"
|
|
json_close_object
|
|
json_add_object "trunk_add_ovh"
|
|
json_close_object
|
|
json_add_object "configure_haproxy"
|
|
json_close_object
|
|
json_add_object "emancipate"
|
|
json_add_string "domain" ""
|
|
json_close_object
|
|
json_dump
|
|
}
|
|
|
|
# Main dispatcher
|
|
case "$1" in
|
|
list)
|
|
list_methods
|
|
;;
|
|
call)
|
|
case "$2" in
|
|
status)
|
|
method_status
|
|
;;
|
|
start)
|
|
method_start
|
|
;;
|
|
stop)
|
|
method_stop
|
|
;;
|
|
install)
|
|
method_install
|
|
;;
|
|
uninstall)
|
|
method_uninstall
|
|
;;
|
|
logs)
|
|
read -r input
|
|
json_load "$input"
|
|
json_get_var lines lines
|
|
method_logs "${lines:-50}"
|
|
;;
|
|
ext_add)
|
|
method_ext_add
|
|
;;
|
|
ext_del)
|
|
method_ext_del
|
|
;;
|
|
ext_list)
|
|
method_ext_list
|
|
;;
|
|
call_originate)
|
|
method_call_originate
|
|
;;
|
|
calls_list)
|
|
method_calls_list
|
|
;;
|
|
trunk_status)
|
|
method_trunk_status
|
|
;;
|
|
trunk_add_ovh)
|
|
method_trunk_add_ovh
|
|
;;
|
|
configure_haproxy)
|
|
method_configure_haproxy
|
|
;;
|
|
emancipate)
|
|
method_emancipate
|
|
;;
|
|
*)
|
|
echo '{"error":"Method not found"}'
|
|
;;
|
|
esac
|
|
;;
|
|
*)
|
|
echo '{"error":"Invalid action"}'
|
|
;;
|
|
esac
|