secubox-openwrt/package/secubox/secubox-app-voip/files/usr/sbin/voipctl
CyberMind-FR c137296dcd fix(voipctl): Fix JSON output in status command
- Fix active_calls and extensions count to output clean integers
- Remove tr -cd which was causing duplicate values in JSON
- Use simpler variable assignment with fallback to 0
- Prevents malformed JSON output from cmd_status()

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
2026-02-25 14:37:33 +01:00

1064 lines
24 KiB
Bash
Executable File

#!/bin/sh
# voipctl - SecuBox VoIP PBX Control Script
# Manages Asterisk PBX in LXC container with OVH SIP trunk
set -e
CONTAINER_NAME="voip"
CONTAINER_PATH="/srv/lxc/$CONTAINER_NAME"
DATA_PATH="/srv/voip"
LIB_PATH="/usr/lib/secubox/voip"
# Source helper libraries
[ -f "$LIB_PATH/ovh-telephony.sh" ] && . "$LIB_PATH/ovh-telephony.sh"
[ -f "$LIB_PATH/asterisk-config.sh" ] && . "$LIB_PATH/asterisk-config.sh"
# Colors for output
RED='\033[0;31m'
GREEN='\033[0;32m'
YELLOW='\033[1;33m'
BLUE='\033[0;34m'
NC='\033[0m'
log_info() { echo -e "${BLUE}[INFO]${NC} $*"; }
log_ok() { echo -e "${GREEN}[OK]${NC} $*"; }
log_warn() { echo -e "${YELLOW}[WARN]${NC} $*"; }
log_err() { echo -e "${RED}[ERROR]${NC} $*" >&2; }
usage() {
cat <<EOF
Usage: voipctl <command> [options]
Container Management:
install Create LXC container and install Asterisk
uninstall Remove container and all data
start Start VoIP services
stop Stop VoIP services
restart Restart VoIP services
status Show status (JSON)
logs [-f] View Asterisk logs
shell Open shell in container
Extension Management:
ext add <num> <name> [password] Add extension
ext del <num> Delete extension
ext list List extensions
ext passwd <num> [password] Set extension password
Trunk Management:
trunk add ovh Auto-provision OVH SIP trunk
trunk add manual Configure trunk manually
trunk test Test trunk connectivity
trunk status Show registration status
Call Operations:
call <from> <to> Originate call (click-to-call)
hangup <channel> Hang up active call
calls List active calls
Voicemail:
vm list [ext] List voicemails
vm play <ext> <id> Play voicemail
vm delete <ext> <id> Delete voicemail
Call Recording:
rec enable Enable call recording
rec disable Disable call recording
rec status Show recording status
rec list [date] List recordings (YYYYMMDD)
rec play <file> Play recording
rec download <file> Get download path
rec delete <file> Delete recording
rec cleanup [days] Delete recordings older than N days
Configuration:
configure-haproxy Setup WebRTC proxy in HAProxy
emancipate <domain> Full exposure with SSL
reload Reload Asterisk configuration
EOF
exit 1
}
# Check if container exists
container_exists() {
[ -d "$CONTAINER_PATH/rootfs" ]
}
# Check if container is running
container_running() {
lxc-info -n "$CONTAINER_NAME" -s 2>/dev/null | grep -q "RUNNING"
}
# Execute command in container
container_exec() {
if ! container_running; then
log_err "Container not running"
return 1
fi
lxc-attach -n "$CONTAINER_NAME" -- "$@"
}
# Generate random password
gen_password() {
head -c 16 /dev/urandom | base64 | tr -dc 'a-zA-Z0-9' | head -c 16
}
#
# Container Installation
#
cmd_install() {
if container_exists; then
log_warn "Container already exists. Use 'uninstall' first to reinstall."
return 1
fi
log_info "Creating VoIP container..."
mkdir -p "$CONTAINER_PATH" "$DATA_PATH"
# Create LXC config
cat > "$CONTAINER_PATH/config" <<EOF
lxc.uts.name = $CONTAINER_NAME
lxc.rootfs.path = dir:$CONTAINER_PATH/rootfs
lxc.include = /usr/share/lxc/config/common.conf
# Use host network for SIP/RTP
lxc.net.0.type = none
# Mount data directory
lxc.mount.entry = $DATA_PATH srv/voip none bind,create=dir 0 0
# Init with tini
lxc.init.cmd = /usr/bin/tini -- /start-voip.sh
# Capabilities for audio
lxc.cap.keep = sys_nice
EOF
log_info "Downloading Debian rootfs..."
local arch="arm64"
local release="bookworm"
local mirror="http://deb.debian.org/debian"
# Use debootstrap if available, otherwise download tarball
if command -v debootstrap >/dev/null 2>&1; then
debootstrap --arch="$arch" --variant=minbase "$release" "$CONTAINER_PATH/rootfs" "$mirror"
else
# Download pre-built rootfs
local rootfs_url="https://images.linuxcontainers.org/images/debian/$release/$arch/default/"
local latest=$(wget -qO- "$rootfs_url" | grep -oE '[0-9]{8}_[0-9]{2}:[0-9]{2}' | tail -1)
wget -O /tmp/rootfs.tar.xz "${rootfs_url}${latest}/rootfs.tar.xz"
mkdir -p "$CONTAINER_PATH/rootfs"
tar -xJf /tmp/rootfs.tar.xz -C "$CONTAINER_PATH/rootfs"
rm -f /tmp/rootfs.tar.xz
fi
log_info "Installing Asterisk packages..."
# Setup resolv.conf
cp /etc/resolv.conf "$CONTAINER_PATH/rootfs/etc/resolv.conf"
# Install packages
cat > "$CONTAINER_PATH/rootfs/tmp/setup.sh" <<'SETUP'
#!/bin/bash
export DEBIAN_FRONTEND=noninteractive
apt-get update
apt-get install -y --no-install-recommends \
asterisk \
asterisk-core-sounds-en \
asterisk-moh-opsound-wav \
asterisk-modules \
tini \
curl \
jq \
ca-certificates
# Clean up
apt-get clean
rm -rf /var/lib/apt/lists/*
# Create voip data directory
mkdir -p /srv/voip/{sounds,recordings,voicemail,logs}
SETUP
chmod +x "$CONTAINER_PATH/rootfs/tmp/setup.sh"
# Start container temporarily to run setup
lxc-start -n "$CONTAINER_NAME" -d -F -- /bin/bash /tmp/setup.sh
# Wait for setup to complete
local timeout=300
while [ $timeout -gt 0 ] && lxc-info -n "$CONTAINER_NAME" -s 2>/dev/null | grep -q "RUNNING"; do
sleep 5
timeout=$((timeout - 5))
done
# Create startup script
cat > "$CONTAINER_PATH/rootfs/start-voip.sh" <<'STARTUP'
#!/bin/bash
# VoIP container startup script
# Clean stale PID files
rm -f /var/run/asterisk/asterisk.pid 2>/dev/null
# Start Asterisk
/usr/sbin/asterisk -f -vvvg -c
STARTUP
chmod +x "$CONTAINER_PATH/rootfs/start-voip.sh"
# Generate initial Asterisk config
generate_asterisk_config
log_ok "VoIP container installed successfully"
log_info "Run 'voipctl start' to start services"
}
cmd_uninstall() {
log_warn "This will remove the VoIP container and all data!"
echo -n "Continue? [y/N] "
read -r confirm
[ "$confirm" = "y" ] || [ "$confirm" = "Y" ] || return 1
if container_running; then
log_info "Stopping container..."
lxc-stop -n "$CONTAINER_NAME" -k 2>/dev/null || true
fi
log_info "Removing container..."
rm -rf "$CONTAINER_PATH"
log_ok "Container removed"
log_info "Data directory preserved at $DATA_PATH"
}
cmd_start() {
if ! container_exists; then
log_err "Container not installed. Run 'voipctl install' first."
return 1
fi
if container_running; then
log_warn "Container already running"
return 0
fi
log_info "Starting VoIP container..."
lxc-start -n "$CONTAINER_NAME" -d
# Wait for Asterisk to be ready
local timeout=30
while [ $timeout -gt 0 ]; do
if container_exec asterisk -rx "core show version" >/dev/null 2>&1; then
log_ok "Asterisk started"
return 0
fi
sleep 1
timeout=$((timeout - 1))
done
log_err "Asterisk failed to start in time"
return 1
}
cmd_stop() {
if ! container_running; then
log_warn "Container not running"
return 0
fi
log_info "Stopping VoIP container..."
lxc-stop -n "$CONTAINER_NAME"
log_ok "Container stopped"
}
cmd_restart() {
cmd_stop
sleep 2
cmd_start
}
cmd_status() {
local running=0
local registered=0
local active_calls=0
local extensions=0
if container_running; then
running=1
# Check trunk registration
if container_exec asterisk -rx "pjsip show registrations" 2>/dev/null | grep -q "Registered"; then
registered=1
fi
# Count active calls
local calls_output
calls_output=$(container_exec asterisk -rx "core show channels" 2>/dev/null | grep -oE "^[0-9]+ active" | head -1 | cut -d' ' -f1) || true
active_calls=${calls_output:-0}
# Count extensions
local ext_output
ext_output=$(container_exec asterisk -rx "pjsip show endpoints" 2>/dev/null | wc -l) || true
extensions=${ext_output:-0}
fi
cat <<EOF
{
"running": $running,
"trunk_registered": $registered,
"active_calls": ${active_calls:-0},
"extensions": ${extensions:-0},
"container": "$CONTAINER_NAME",
"data_path": "$DATA_PATH"
}
EOF
}
cmd_logs() {
local follow=""
[ "$1" = "-f" ] && follow="-f"
if [ -n "$follow" ]; then
container_exec tail -f /var/log/asterisk/messages
else
container_exec tail -100 /var/log/asterisk/messages
fi
}
cmd_shell() {
container_exec /bin/bash
}
#
# Extension Management
#
cmd_ext_add() {
local num="$1"
local name="$2"
local secret="${3:-$(gen_password)}"
[ -z "$num" ] || [ -z "$name" ] && {
log_err "Usage: voipctl ext add <number> <name> [password]"
return 1
}
# Validate extension number
if ! echo "$num" | grep -qE '^[0-9]{3,6}$'; then
log_err "Extension must be 3-6 digits"
return 1
fi
# Add to UCI
local section="ext_$num"
uci set voip.$section=extension
uci set voip.$section.name="$name"
uci set voip.$section.secret="$secret"
uci set voip.$section.context="internal"
uci set voip.$section.voicemail="1"
uci commit voip
# Regenerate Asterisk config
generate_pjsip_extensions
log_ok "Extension $num created for $name"
echo "Password: $secret"
}
cmd_ext_del() {
local num="$1"
[ -z "$num" ] && {
log_err "Usage: voipctl ext del <number>"
return 1
}
uci delete "voip.ext_$num" 2>/dev/null || {
log_err "Extension $num not found"
return 1
}
uci commit voip
generate_pjsip_extensions
log_ok "Extension $num deleted"
}
cmd_ext_list() {
echo "Extensions:"
echo "----------"
uci show voip 2>/dev/null | grep "=extension" | while read -r line; do
local section=$(echo "$line" | cut -d'.' -f2 | cut -d'=' -f1)
local num=$(echo "$section" | sed 's/ext_//')
local name=$(uci -q get "voip.$section.name")
echo " $num: $name"
done
}
cmd_ext_passwd() {
local num="$1"
local secret="${2:-$(gen_password)}"
[ -z "$num" ] && {
log_err "Usage: voipctl ext passwd <number> [password]"
return 1
}
uci set "voip.ext_$num.secret=$secret" 2>/dev/null || {
log_err "Extension $num not found"
return 1
}
uci commit voip
generate_pjsip_extensions
log_ok "Password updated for extension $num"
echo "New password: $secret"
}
#
# Trunk Management
#
cmd_trunk_add_ovh() {
log_info "Provisioning OVH SIP trunk..."
# Check OVH credentials
local app_key=$(uci -q get voip.ovh_telephony.app_key)
local app_secret=$(uci -q get voip.ovh_telephony.app_secret)
local consumer_key=$(uci -q get voip.ovh_telephony.consumer_key)
if [ -z "$app_key" ] || [ -z "$app_secret" ] || [ -z "$consumer_key" ]; then
log_err "OVH API credentials not configured"
log_info "Set credentials in /etc/config/voip under ovh_telephony section"
return 1
fi
# Fetch billing accounts
log_info "Fetching OVH telephony accounts..."
local accounts=$(ovh_api_get "/telephony")
if [ -z "$accounts" ] || [ "$accounts" = "[]" ]; then
log_err "No telephony accounts found"
return 1
fi
echo "Available billing accounts:"
echo "$accounts" | jsonfilter -e '@[*]' | nl -w2 -s'. '
echo -n "Select account number: "
read -r account_num
local billing_account=$(echo "$accounts" | jsonfilter -e "@[$((account_num-1))]")
# Fetch SIP lines
log_info "Fetching SIP lines..."
local lines=$(ovh_api_get "/telephony/$billing_account/line")
echo "Available SIP lines:"
echo "$lines" | jsonfilter -e '@[*]' | nl -w2 -s'. '
echo -n "Select line number: "
read -r line_num
local service_name=$(echo "$lines" | jsonfilter -e "@[$((line_num-1))]")
# Get SIP credentials
log_info "Fetching SIP credentials..."
local sip_accounts=$(ovh_api_get "/telephony/$billing_account/line/$service_name/sipAccounts")
local sip_username=$(echo "$sip_accounts" | jsonfilter -e '@[0]')
# Save to UCI
uci set voip.ovh_telephony.billing_account="$billing_account"
uci set voip.ovh_telephony.service_name="$service_name"
uci set voip.sip_trunk.enabled="1"
uci set voip.sip_trunk.provider="ovh"
uci set voip.sip_trunk.host="sip.ovh.net"
uci set voip.sip_trunk.username="$sip_username"
uci commit voip
log_info "Enter SIP password (from OVH manager):"
read -rs sip_password
uci set voip.sip_trunk.password="$sip_password"
uci commit voip
# Generate PJSIP config
generate_pjsip_trunk
log_ok "OVH trunk configured: $sip_username"
log_info "Run 'voipctl restart' to apply changes"
}
cmd_trunk_test() {
if ! container_running; then
log_err "Container not running"
return 1
fi
log_info "Testing trunk registration..."
container_exec asterisk -rx "pjsip show registrations"
}
cmd_trunk_status() {
if ! container_running; then
log_err "Container not running"
return 1
fi
container_exec asterisk -rx "pjsip show registrations"
echo
container_exec asterisk -rx "pjsip show endpoints" | head -20
}
#
# Call Operations
#
cmd_call() {
local from="$1"
local to="$2"
[ -z "$from" ] || [ -z "$to" ] && {
log_err "Usage: voipctl call <from_ext> <to_number>"
return 1
}
if ! container_running; then
log_err "Container not running"
return 1
fi
log_info "Originating call: $from -> $to"
# First ring the extension, then connect to destination
local channel=$(container_exec asterisk -rx "channel originate PJSIP/$from application Dial PJSIP/$to@ovh-trunk" 2>&1)
if echo "$channel" | grep -qi "error"; then
log_err "Failed to originate call: $channel"
return 1
fi
log_ok "Call initiated"
echo "$channel"
}
cmd_hangup() {
local channel="$1"
[ -z "$channel" ] && {
log_err "Usage: voipctl hangup <channel>"
return 1
}
container_exec asterisk -rx "channel request hangup $channel"
log_ok "Hangup requested"
}
cmd_calls() {
if ! container_running; then
log_err "Container not running"
return 1
fi
container_exec asterisk -rx "core show channels"
}
#
# Voicemail
#
cmd_vm_list() {
local ext="$1"
if [ -n "$ext" ]; then
find "$DATA_PATH/voicemail/default/$ext" -name "msg*.wav" 2>/dev/null | while read -r msg; do
local id=$(basename "$msg" .wav)
local info=$(cat "${msg%.wav}.txt" 2>/dev/null | grep -E "^(callerid|origdate)=" | tr '\n' ' ')
echo "$id: $info"
done
else
find "$DATA_PATH/voicemail/default" -type d -name "[0-9]*" 2>/dev/null | while read -r dir; do
local ext=$(basename "$dir")
local count=$(find "$dir" -name "msg*.wav" 2>/dev/null | wc -l)
echo "$ext: $count messages"
done
fi
}
cmd_vm_play() {
local ext="$1"
local id="$2"
[ -z "$ext" ] || [ -z "$id" ] && {
log_err "Usage: voipctl vm play <extension> <msg_id>"
return 1
}
local file="$DATA_PATH/voicemail/default/$ext/$id.wav"
if [ -f "$file" ]; then
# Play via aplay or just output path
if command -v aplay >/dev/null; then
aplay "$file"
else
echo "Voicemail file: $file"
fi
else
log_err "Message not found: $id"
return 1
fi
}
cmd_vm_delete() {
local ext="$1"
local id="$2"
[ -z "$ext" ] || [ -z "$id" ] && {
log_err "Usage: voipctl vm delete <extension> <msg_id>"
return 1
}
rm -f "$DATA_PATH/voicemail/default/$ext/$id".*
log_ok "Message deleted"
}
#
# Call Recording
#
RECORDINGS_PATH="$DATA_PATH/recordings"
cmd_rec_enable() {
log_info "Enabling call recording..."
uci set voip.recording=recording
uci set voip.recording.enabled="1"
uci set voip.recording.format="wav"
uci set voip.recording.retention_days="30"
uci commit voip
# Create recordings directory
mkdir -p "$RECORDINGS_PATH"
# Regenerate dialplan with recording enabled
generate_dialplan
if container_running; then
container_exec asterisk -rx "dialplan reload"
fi
log_ok "Call recording enabled"
log_info "Recordings will be saved to: $RECORDINGS_PATH"
}
cmd_rec_disable() {
log_info "Disabling call recording..."
uci set voip.recording.enabled="0"
uci commit voip
generate_dialplan
if container_running; then
container_exec asterisk -rx "dialplan reload"
fi
log_ok "Call recording disabled"
}
cmd_rec_status() {
local enabled=$(uci -q get voip.recording.enabled)
local format=$(uci -q get voip.recording.format || echo "wav")
local retention=$(uci -q get voip.recording.retention_days || echo "30")
local total_count=0
local total_size=0
local today_count=0
if [ -d "$RECORDINGS_PATH" ]; then
total_count=$(find "$RECORDINGS_PATH" -type f -name "*.$format" 2>/dev/null | wc -l)
total_size=$(du -sh "$RECORDINGS_PATH" 2>/dev/null | cut -f1 || echo "0")
local today=$(date +%Y%m%d)
if [ -d "$RECORDINGS_PATH/$today" ]; then
today_count=$(find "$RECORDINGS_PATH/$today" -type f -name "*.$format" 2>/dev/null | wc -l)
fi
fi
cat <<EOF
{
"enabled": ${enabled:-0},
"format": "$format",
"retention_days": $retention,
"path": "$RECORDINGS_PATH",
"total_recordings": $total_count,
"total_size": "$total_size",
"today_recordings": $today_count
}
EOF
}
cmd_rec_list() {
local date_filter="$1"
local format=$(uci -q get voip.recording.format || echo "wav")
if [ -n "$date_filter" ]; then
# List recordings for specific date
local dir="$RECORDINGS_PATH/$date_filter"
if [ -d "$dir" ]; then
echo "Recordings for $date_filter:"
echo "-------------------------"
find "$dir" -type f -name "*.$format" 2>/dev/null | while read -r file; do
local name=$(basename "$file")
local size=$(du -h "$file" 2>/dev/null | cut -f1)
local time=$(echo "$name" | cut -d'-' -f1)
local caller=$(echo "$name" | cut -d'-' -f2)
local dest=$(echo "$name" | cut -d'-' -f3 | sed "s/\.$format//")
printf " %s %s -> %s (%s)\n" "$time" "$caller" "$dest" "$size"
done
else
log_warn "No recordings found for $date_filter"
fi
else
# List all recording dates with counts
echo "Recording dates:"
echo "---------------"
find "$RECORDINGS_PATH" -mindepth 1 -maxdepth 1 -type d 2>/dev/null | sort -r | while read -r dir; do
local date=$(basename "$dir")
local count=$(find "$dir" -type f -name "*.$format" 2>/dev/null | wc -l)
local size=$(du -sh "$dir" 2>/dev/null | cut -f1)
printf " %s: %d recordings (%s)\n" "$date" "$count" "$size"
done
fi
}
cmd_rec_play() {
local file="$1"
[ -z "$file" ] && {
log_err "Usage: voipctl rec play <filename>"
return 1
}
# Find the file (could be full path or just filename)
local fullpath
if [ -f "$file" ]; then
fullpath="$file"
else
fullpath=$(find "$RECORDINGS_PATH" -name "$file" -type f 2>/dev/null | head -1)
fi
if [ -z "$fullpath" ] || [ ! -f "$fullpath" ]; then
log_err "Recording not found: $file"
return 1
fi
# Try to play the file
if command -v aplay >/dev/null 2>&1; then
log_info "Playing: $fullpath"
aplay "$fullpath"
elif command -v ffplay >/dev/null 2>&1; then
ffplay -nodisp -autoexit "$fullpath"
else
echo "File: $fullpath"
log_info "No audio player available. Download the file to play."
fi
}
cmd_rec_download() {
local file="$1"
[ -z "$file" ] && {
log_err "Usage: voipctl rec download <filename>"
return 1
}
local fullpath
if [ -f "$file" ]; then
fullpath="$file"
else
fullpath=$(find "$RECORDINGS_PATH" -name "$file" -type f 2>/dev/null | head -1)
fi
if [ -z "$fullpath" ] || [ ! -f "$fullpath" ]; then
log_err "Recording not found: $file"
return 1
fi
echo "$fullpath"
}
cmd_rec_delete() {
local file="$1"
[ -z "$file" ] && {
log_err "Usage: voipctl rec delete <filename>"
return 1
}
local fullpath
if [ -f "$file" ]; then
fullpath="$file"
else
fullpath=$(find "$RECORDINGS_PATH" -name "$file" -type f 2>/dev/null | head -1)
fi
if [ -z "$fullpath" ] || [ ! -f "$fullpath" ]; then
log_err "Recording not found: $file"
return 1
fi
rm -f "$fullpath"
log_ok "Deleted: $(basename "$fullpath")"
# Clean up empty directories
find "$RECORDINGS_PATH" -type d -empty -delete 2>/dev/null
}
cmd_rec_cleanup() {
local days="${1:-30}"
log_info "Cleaning up recordings older than $days days..."
local count=0
local freed=0
if [ -d "$RECORDINGS_PATH" ]; then
# Calculate size before
local before=$(du -s "$RECORDINGS_PATH" 2>/dev/null | cut -f1 || echo 0)
# Delete old recordings
count=$(find "$RECORDINGS_PATH" -type f -mtime +$days 2>/dev/null | wc -l)
find "$RECORDINGS_PATH" -type f -mtime +$days -delete 2>/dev/null
# Remove empty directories
find "$RECORDINGS_PATH" -type d -empty -delete 2>/dev/null
# Calculate freed space
local after=$(du -s "$RECORDINGS_PATH" 2>/dev/null | cut -f1 || echo 0)
freed=$(( (before - after) / 1024 ))
fi
log_ok "Deleted $count recordings, freed ${freed}MB"
}
cmd_rec_list_json() {
local date_filter="$1"
local format=$(uci -q get voip.recording.format || echo "wav")
echo "["
local first=1
if [ -n "$date_filter" ]; then
local dir="$RECORDINGS_PATH/$date_filter"
if [ -d "$dir" ]; then
find "$dir" -type f -name "*.$format" 2>/dev/null | sort -r | while read -r file; do
local name=$(basename "$file")
local size=$(stat -c%s "$file" 2>/dev/null || echo 0)
local mtime=$(stat -c%Y "$file" 2>/dev/null || echo 0)
local time=$(echo "$name" | cut -d'-' -f1)
local caller=$(echo "$name" | cut -d'-' -f2)
local dest=$(echo "$name" | cut -d'-' -f3 | sed "s/\.$format//")
[ $first -eq 0 ] && echo ","
first=0
cat <<ENTRY
{
"filename": "$name",
"path": "$file",
"date": "$date_filter",
"time": "$time",
"caller": "$caller",
"destination": "$dest",
"size": $size,
"timestamp": $mtime
}
ENTRY
done
fi
else
# List recent recordings (last 7 days)
find "$RECORDINGS_PATH" -type f -name "*.$format" -mtime -7 2>/dev/null | sort -r | head -50 | while read -r file; do
local name=$(basename "$file")
local dir=$(dirname "$file")
local date=$(basename "$dir")
local size=$(stat -c%s "$file" 2>/dev/null || echo 0)
local mtime=$(stat -c%Y "$file" 2>/dev/null || echo 0)
local time=$(echo "$name" | cut -d'-' -f1)
local caller=$(echo "$name" | cut -d'-' -f2)
local dest=$(echo "$name" | cut -d'-' -f3 | sed "s/\.$format//")
[ $first -eq 0 ] && echo ","
first=0
cat <<ENTRY
{
"filename": "$name",
"path": "$file",
"date": "$date",
"time": "$time",
"caller": "$caller",
"destination": "$dest",
"size": $size,
"timestamp": $mtime
}
ENTRY
done
fi
echo "]"
}
#
# Configuration
#
cmd_configure_haproxy() {
log_info "Configuring HAProxy for WebRTC..."
local domain=$(uci -q get voip.ssl.domain)
[ -z "$domain" ] && {
log_err "Domain not configured. Set voip.ssl.domain first."
return 1
}
# Add HAProxy backend for WebRTC
# This assumes secubox-app-haproxy is installed
if [ -f /usr/sbin/haproxyctl ]; then
# Generate HAProxy config snippet
cat >> /etc/haproxy/conf.d/voip.cfg <<EOF
# VoIP WebRTC Backend
backend voip_websocket
mode http
option forwardfor
server asterisk 127.0.0.1:8088 check
# Route for /ws WebSocket
acl is_voip_ws path_beg /ws
use_backend voip_websocket if is_voip_ws
EOF
haproxyctl reload
log_ok "HAProxy configured for WebRTC"
else
log_warn "HAProxy not found. Install secubox-app-haproxy first."
fi
}
cmd_emancipate() {
local domain="$1"
[ -z "$domain" ] && {
log_err "Usage: voipctl emancipate <domain>"
return 1
}
log_info "Emancipating VoIP at $domain..."
# Configure domain
uci set voip.ssl.enabled="1"
uci set voip.ssl.domain="$domain"
uci commit voip
# Configure HAProxy
cmd_configure_haproxy
# Request SSL certificate
if [ -f /usr/sbin/acmectl ]; then
acmectl issue "$domain"
fi
log_ok "VoIP exposed at https://$domain"
}
cmd_reload() {
if ! container_running; then
log_err "Container not running"
return 1
fi
log_info "Reloading Asterisk configuration..."
generate_asterisk_config
container_exec asterisk -rx "core reload"
log_ok "Configuration reloaded"
}
#
# Main
#
case "$1" in
install)
cmd_install
;;
uninstall)
cmd_uninstall
;;
start)
cmd_start
;;
stop)
cmd_stop
;;
restart)
cmd_restart
;;
status)
cmd_status
;;
logs)
shift
cmd_logs "$@"
;;
shell)
cmd_shell
;;
ext)
case "$2" in
add) shift 2; cmd_ext_add "$@" ;;
del) shift 2; cmd_ext_del "$@" ;;
list) cmd_ext_list ;;
passwd) shift 2; cmd_ext_passwd "$@" ;;
*) usage ;;
esac
;;
trunk)
case "$2" in
add)
case "$3" in
ovh) cmd_trunk_add_ovh ;;
manual) log_info "Edit /etc/config/voip sip_trunk section" ;;
*) usage ;;
esac
;;
test) cmd_trunk_test ;;
status) cmd_trunk_status ;;
*) usage ;;
esac
;;
call)
shift
cmd_call "$@"
;;
hangup)
shift
cmd_hangup "$@"
;;
calls)
cmd_calls
;;
vm)
case "$2" in
list) shift 2; cmd_vm_list "$@" ;;
play) shift 2; cmd_vm_play "$@" ;;
delete) shift 2; cmd_vm_delete "$@" ;;
*) usage ;;
esac
;;
rec)
case "$2" in
enable) cmd_rec_enable ;;
disable) cmd_rec_disable ;;
status) cmd_rec_status ;;
list) shift 2; cmd_rec_list "$@" ;;
list-json) shift 2; cmd_rec_list_json "$@" ;;
play) shift 2; cmd_rec_play "$@" ;;
download) shift 2; cmd_rec_download "$@" ;;
delete) shift 2; cmd_rec_delete "$@" ;;
cleanup) shift 2; cmd_rec_cleanup "$@" ;;
*) usage ;;
esac
;;
configure-haproxy)
cmd_configure_haproxy
;;
emancipate)
shift
cmd_emancipate "$@"
;;
reload)
cmd_reload
;;
*)
usage
;;
esac