#!/bin/sh # SecuBox VoIP Manager - LXC Debian container with Asterisk PBX CONFIG="voip" LXC_NAME="voip" LXC_PATH="/srv/lxc" LXC_ROOTFS="$LXC_PATH/$LXC_NAME/rootfs" LXC_CONF="$LXC_PATH/$LXC_NAME/config" DATA_PATH_DEFAULT="/srv/voip" OPKG_UPDATED=0 # Load OVH telephony API OVH_API_LIB="/usr/lib/secubox/voip/ovh-telephony.sh" [ -f "$OVH_API_LIB" ] && . "$OVH_API_LIB" usage() { cat <<'USAGE' Usage: voipctl Installation: install Create LXC container with Asterisk PBX uninstall Remove container (preserves data) update Update Asterisk packages check Run prerequisite checks Service: start Start VoIP server (via init) stop Stop VoIP server restart Restart VoIP server status Show container and service status logs [N] Show last N lines of logs (default: 50) shell Open interactive shell in container cli Open Asterisk CLI Extensions: ext add [password] Create SIP extension ext del Delete extension ext list List all extensions ext passwd [password] Change password Trunks: trunk add ovh Auto-provision OVH SIP trunk trunk add manual Add manual SIP trunk trunk del Remove trunk trunk test Test trunk registration trunk status Show registration status Calls: call Originate call (click-to-call) hangup Hang up active call calls List active calls Voicemail: vm list [ext] List voicemails vm play Play voicemail (outputs path) vm delete Delete voicemail Exposure: configure-haproxy Setup HAProxy for WebRTC emancipate Full exposure (HAProxy + SSL) Internal: service-run Run container via procd service-stop Stop container USAGE } # ---------- helpers ---------- require_root() { [ "$(id -u)" -eq 0 ]; } uci_get() { local key="$1" local section="${2:-main}" uci -q get ${CONFIG}.${section}.$key } uci_set() { local key="$1" local value="$2" local section="${3:-main}" uci set ${CONFIG}.${section}.$key="$value" } log_info() { echo "[INFO] $*"; logger -t voipctl "$*"; } log_warn() { echo "[WARN] $*"; logger -t voipctl -p warning "$*"; } log_error() { echo "[ERROR] $*" >&2; logger -t voipctl -p err "$*"; } ensure_dir() { [ -d "$1" ] || mkdir -p "$1"; } ensure_packages() { for pkg in "$@"; do if ! opkg status "$pkg" 2>/dev/null | grep -q "Status:.*installed"; then if [ "$OPKG_UPDATED" -eq 0 ]; then opkg update || return 1 OPKG_UPDATED=1 fi opkg install "$pkg" || return 1 fi done } defaults() { data_path="$(uci_get data_path || echo $DATA_PATH_DEFAULT)" memory_limit="$(uci_get memory_limit || echo 512)" sip_port="$(uci_get sip_port asterisk || echo 5060)" rtp_start="$(uci_get rtp_start asterisk || echo 10000)" rtp_end="$(uci_get rtp_end asterisk || echo 20000)" ari_port="$(uci_get ari_port asterisk || echo 8089)" ami_port="$(uci_get ami_port asterisk || echo 5038)" } detect_arch() { case "$(uname -m)" in aarch64) echo "aarch64" ;; armv7l) echo "armv7" ;; x86_64) echo "x86_64" ;; *) echo "x86_64" ;; esac } generate_password() { head -c 32 /dev/urandom | base64 | tr -dc 'a-zA-Z0-9' | head -c 16 } # ---------- LXC helpers ---------- lxc_running() { lxc-info -n "$LXC_NAME" -s 2>/dev/null | grep -q "RUNNING" } lxc_exists() { [ -f "$LXC_CONF" ] && [ -d "$LXC_ROOTFS" ] } lxc_exec() { lxc-attach -n "$LXC_NAME" -- "$@" } lxc_stop() { if lxc_running; then lxc-stop -n "$LXC_NAME" -k 2>/dev/null || true sleep 2 fi } # ---------- Asterisk helpers ---------- asterisk_cli() { lxc_exec asterisk -rx "$*" } # ---------- rootfs creation ---------- lxc_create_rootfs() { local arch=$(detect_arch) local debian_arch case "$arch" in aarch64) debian_arch="arm64" ;; armv7) debian_arch="armhf" ;; x86_64) debian_arch="amd64" ;; *) debian_arch="amd64" ;; esac ensure_dir "$LXC_ROOTFS" local rootfs_url="https://images.linuxcontainers.org/images/debian/bookworm/${debian_arch}/default/" log_info "Downloading Debian bookworm rootfs for ${debian_arch}..." local latest_path latest_path=$(wget -q -O - "$rootfs_url" 2>/dev/null | grep -oE '[0-9]{8}_[0-9]{2}:[0-9]{2}' | tail -1) if [ -z "$latest_path" ]; then log_error "Failed to find latest Debian rootfs build" return 1 fi local tarball="/tmp/debian-voip.tar.xz" local tarball_url="${rootfs_url}${latest_path}/rootfs.tar.xz" wget -q -O "$tarball" "$tarball_url" || { log_error "Failed to download Debian rootfs from $tarball_url" return 1 } tar -xJf "$tarball" -C "$LXC_ROOTFS" || { log_error "Failed to extract Debian rootfs" return 1 } rm -f "$tarball" # DNS cp /etc/resolv.conf "$LXC_ROOTFS/etc/resolv.conf" 2>/dev/null || \ echo "nameserver 8.8.8.8" > "$LXC_ROOTFS/etc/resolv.conf" # Create minimal /dev for chroot operations mkdir -p "$LXC_ROOTFS/dev" [ -c "$LXC_ROOTFS/dev/null" ] || mknod -m 666 "$LXC_ROOTFS/dev/null" c 1 3 2>/dev/null [ -c "$LXC_ROOTFS/dev/zero" ] || mknod -m 666 "$LXC_ROOTFS/dev/zero" c 1 5 2>/dev/null [ -c "$LXC_ROOTFS/dev/random" ] || mknod -m 666 "$LXC_ROOTFS/dev/random" c 1 8 2>/dev/null [ -c "$LXC_ROOTFS/dev/urandom" ] || mknod -m 666 "$LXC_ROOTFS/dev/urandom" c 1 9 2>/dev/null # Configure apt sources (include bullseye for asterisk) cat > "$LXC_ROOTFS/etc/apt/sources.list" <<'SOURCES' deb http://deb.debian.org/debian bookworm main contrib non-free non-free-firmware deb http://deb.debian.org/debian bookworm-updates main contrib non-free non-free-firmware deb http://security.debian.org/debian-security bookworm-security main contrib non-free non-free-firmware # Bullseye for Asterisk (not in Bookworm) deb http://deb.debian.org/debian bullseye main SOURCES # Pin bullseye packages to lower priority (only use for asterisk) cat > "$LXC_ROOTFS/etc/apt/preferences.d/bullseye-asterisk" <<'PINS' Package: * Pin: release n=bullseye Pin-Priority: 100 Package: asterisk* Pin: release n=bullseye Pin-Priority: 500 PINS # Install Asterisk PBX log_info "Installing Asterisk PBX..." chroot "$LXC_ROOTFS" /bin/sh -c " export DEBIAN_FRONTEND=noninteractive apt-get update && \ apt-get install -y --no-install-recommends \ asterisk \ asterisk-core-sounds-en \ asterisk-core-sounds-fr \ asterisk-moh-opsound-wav \ asterisk-modules \ ca-certificates \ curl \ procps " || { log_error "Failed to install Asterisk" return 1 } # Create directories mkdir -p "$LXC_ROOTFS/var/spool/asterisk/voicemail" mkdir -p "$LXC_ROOTFS/var/log/asterisk" mkdir -p "$LXC_ROOTFS/etc/asterisk" mkdir -p "$LXC_ROOTFS/srv/voip/sounds" # Create startup script create_startup_script # Clean up apt cache chroot "$LXC_ROOTFS" /bin/sh -c " apt-get clean rm -rf /var/lib/apt/lists/* " log_info "Rootfs created successfully" } create_startup_script() { cat > "$LXC_ROOTFS/opt/start-voip.sh" <<'STARTUP' #!/bin/bash export PATH=/usr/local/bin:/usr/bin:/bin:/usr/sbin:/sbin # Get config from environment SIP_PORT="${VOIP_SIP_PORT:-5060}" RTP_START="${VOIP_RTP_START:-10000}" RTP_END="${VOIP_RTP_END:-20000}" ARI_PORT="${VOIP_ARI_PORT:-8089}" ARI_USER="${VOIP_ARI_USER:-admin}" ARI_PASSWORD="${VOIP_ARI_PASSWORD:-}" AMI_PORT="${VOIP_AMI_PORT:-5038}" AMI_USER="${VOIP_AMI_USER:-admin}" AMI_SECRET="${VOIP_AMI_SECRET:-}" # Generate Asterisk config if not exists if [ ! -f /etc/asterisk/.configured ]; then echo "[VOIP] Generating Asterisk configuration..." # pjsip.conf - SIP stack cat > /etc/asterisk/pjsip.conf < /etc/asterisk/extensions.conf < _1XX,1,NoOp(Dialing extension \${EXTEN}) same => n,Dial(PJSIP/\${EXTEN},30,tT) same => n,VoiceMail(\${EXTEN}@default,u) same => n,Hangup() ; Voicemail access exten => *98,1,VoiceMailMain(\${CALLERID(num)}@default) same => n,Hangup() exten => *97,1,VoiceMailMain(@default) same => n,Hangup() [from-trunk] ; Incoming calls from trunk exten => _X.,1,NoOp(Incoming call to \${EXTEN}) same => n,Goto(internal,100,1) ; Ring extension 100 by default same => n,Hangup() [outbound] ; Outbound calls via trunk exten => _0X.,1,NoOp(Outbound call to \${EXTEN:1}) same => n,Set(CALLERID(num)=\${TRUNK_CALLER_ID}) same => n,Dial(PJSIP/\${EXTEN:1}@ovh-endpoint,60,tT) same => n,Hangup() exten => _+X.,1,NoOp(Outbound call to \${EXTEN}) same => n,Set(CALLERID(num)=\${TRUNK_CALLER_ID}) same => n,Dial(PJSIP/\${EXTEN}@ovh-endpoint,60,tT) same => n,Hangup() DIALPLAN # voicemail.conf cat > /etc/asterisk/voicemail.conf < /etc/asterisk/rtp.conf < /etc/asterisk/http.conf < /etc/asterisk/ari.conf < /etc/asterisk/manager.conf < /etc/asterisk/modules.conf < chan_sip.so noload => res_hep.so noload => res_hep_pjsip.so noload => res_hep_rtcp.so load => res_pjsip.so load => res_pjsip_session.so MODULES touch /etc/asterisk/.configured echo "[VOIP] Configuration generated" fi # Ensure proper permissions chown -R asterisk:asterisk /var/spool/asterisk chown -R asterisk:asterisk /var/log/asterisk chown -R asterisk:asterisk /etc/asterisk chown -R asterisk:asterisk /srv/voip 2>/dev/null echo "[VOIP] Starting Asterisk PBX..." # Run Asterisk in foreground exec /usr/sbin/asterisk -f -U asterisk -G asterisk STARTUP chmod +x "$LXC_ROOTFS/opt/start-voip.sh" } lxc_create_config() { defaults local mem_bytes=$((memory_limit * 1024 * 1024)) ensure_dir "$LXC_PATH/$LXC_NAME" ensure_dir "$data_path" ensure_dir "$data_path/sounds" ensure_dir "$data_path/voicemail" ensure_dir "$data_path/config" # Generate passwords if not set local ari_pass=$(uci_get ari_password asterisk) [ -z "$ari_pass" ] && { ari_pass=$(generate_password) uci_set ari_password "$ari_pass" asterisk } local ami_secret=$(uci_get ami_secret asterisk) [ -z "$ami_secret" ] && { ami_secret=$(generate_password) uci_set ami_secret "$ami_secret" asterisk } uci commit "$CONFIG" cat > "$LXC_CONF" </dev/null /etc/init.d/voip disable 2>/dev/null lxc_stop rm -rf "$LXC_ROOTFS" "$LXC_CONF" uci_set enabled '0' uci commit "$CONFIG" defaults log_info "Container removed. Data preserved in $data_path" } cmd_update() { require_root || { log_error "Must run as root"; return 1; } log_info "Updating Asterisk..." if lxc_running; then lxc_exec apt-get update lxc_exec apt-get upgrade -y asterisk asterisk-modules lxc_exec asterisk -rx "core restart now" log_info "Asterisk updated successfully" else log_error "Container not running" return 1 fi } cmd_check() { echo "VoIP Prerequisites Check" echo "=========================" if command -v lxc-start >/dev/null 2>&1; then echo "[OK] LXC installed" else echo "[FAIL] LXC not installed" fi if lxc_exists; then echo "[OK] Container exists" else echo "[--] Container not created" fi if lxc_running; then echo "[OK] Container running" else echo "[--] Container not running" fi defaults for port in $sip_port $ari_port $ami_port; do if netstat -tln 2>/dev/null | grep -q ":${port} " || \ grep -q ":$(printf '%04X' $port) " /proc/net/tcp 2>/dev/null; then echo "[OK] Port $port listening" else echo "[--] Port $port not listening" fi done if lxc_running; then if lxc_exec pgrep asterisk >/dev/null 2>&1; then echo "[OK] Asterisk process running" else echo "[FAIL] Asterisk process not running" fi fi } cmd_status() { defaults if [ "$1" = "--json" ]; then local running=0 local asterisk_proc=0 local trunk_registered=0 local active_calls=0 local extensions=0 lxc_running && running=1 if [ "$running" = "1" ]; then lxc_exec pgrep asterisk >/dev/null 2>&1 && asterisk_proc=1 asterisk_cli "pjsip show registrations" 2>/dev/null | grep -q "Registered" && trunk_registered=1 active_calls=$(asterisk_cli "core show channels" 2>/dev/null | grep -oE "^[0-9]+ active" | cut -d' ' -f1 || echo 0) extensions=$(asterisk_cli "pjsip show endpoints" 2>/dev/null | grep -c "^[0-9]" || echo 0) fi cat </dev/null 2>&1 && echo " Asterisk: UP" || echo " Asterisk: DOWN" echo "" echo "Trunk Status:" asterisk_cli "pjsip show registrations" 2>/dev/null | grep -E "State|Contact" | head -5 | sed 's/^/ /' echo "" echo "Active Calls:" asterisk_cli "core show channels concise" 2>/dev/null | head -5 | sed 's/^/ /' else echo "State: STOPPED" fi } cmd_logs() { local lines="${1:-50}" if lxc_running; then echo "=== Asterisk logs ===" lxc_exec tail -n "$lines" /var/log/asterisk/messages 2>/dev/null || \ echo "No Asterisk logs found" else echo "Container not running" fi } cmd_shell() { if lxc_running; then lxc_exec /bin/bash || lxc_exec /bin/sh else log_error "Container not running" return 1 fi } cmd_cli() { if lxc_running; then lxc_exec asterisk -rvvv else log_error "Container not running" return 1 fi } cmd_start() { require_root || { log_error "Must run as root"; return 1; } /etc/init.d/voip start } cmd_stop() { require_root || { log_error "Must run as root"; return 1; } /etc/init.d/voip stop } cmd_restart() { require_root || { log_error "Must run as root"; return 1; } /etc/init.d/voip restart } # ---------- extension management ---------- cmd_ext() { local subcmd="$1" shift case "$subcmd" in add) cmd_ext_add "$@" ;; del|delete) cmd_ext_del "$@" ;; passwd|password) cmd_ext_passwd "$@" ;; list) cmd_ext_list ;; *) echo "Usage: voipctl ext " return 1 ;; esac } cmd_ext_add() { local ext="$1" local name="$2" local password="$3" [ -z "$ext" ] || [ -z "$name" ] && { echo "Usage: voipctl ext add [password]" return 1 } lxc_running || { log_error "Container not running"; return 1; } [ -z "$password" ] && password=$(generate_password) # Add to pjsip.conf cat >> "$LXC_ROOTFS/etc/asterisk/pjsip.conf" < [$ext-auth](auth-template) username=$ext password=$password [$ext](aor-template) EOF # Add voicemail echo "$ext => $password,$name,," >> "$LXC_ROOTFS/etc/asterisk/voicemail.conf" # Save to UCI uci set voip.ext_${ext}=extension uci set voip.ext_${ext}.name="$name" uci set voip.ext_${ext}.secret="$password" uci set voip.ext_${ext}.voicemail='1' uci commit voip # Reload Asterisk asterisk_cli "pjsip reload" asterisk_cli "voicemail reload" log_info "Extension created: $ext ($name)" log_info "Password: $password" log_info "" log_info "SIP Settings:" log_info " Server: $(uci -q get network.lan.ipaddr || echo '192.168.255.1')" log_info " Username: $ext" log_info " Password: $password" } cmd_ext_del() { local ext="$1" [ -z "$ext" ] && { echo "Usage: voipctl ext del " return 1 } lxc_running || { log_error "Container not running"; return 1; } # Remove from pjsip.conf (simplified - removes the section) sed -i "/^\[$ext\]/,/^$/d" "$LXC_ROOTFS/etc/asterisk/pjsip.conf" sed -i "/^\[$ext-auth\]/,/^$/d" "$LXC_ROOTFS/etc/asterisk/pjsip.conf" # Remove from voicemail.conf sed -i "/^$ext =>/d" "$LXC_ROOTFS/etc/asterisk/voicemail.conf" # Remove UCI uci delete voip.ext_${ext} 2>/dev/null uci commit voip asterisk_cli "pjsip reload" log_info "Extension deleted: $ext" } cmd_ext_passwd() { local ext="$1" local password="$2" [ -z "$ext" ] && { echo "Usage: voipctl ext passwd [password]" return 1 } lxc_running || { log_error "Container not running"; return 1; } [ -z "$password" ] && password=$(generate_password) # Update in pjsip.conf sed -i "/^\[$ext-auth\]/,/^$/{s/password=.*/password=$password/}" "$LXC_ROOTFS/etc/asterisk/pjsip.conf" # Update UCI uci set voip.ext_${ext}.secret="$password" uci commit voip asterisk_cli "pjsip reload" log_info "Password changed for extension $ext" log_info "New password: $password" } cmd_ext_list() { lxc_running || { log_error "Container not running"; return 1; } echo "Extensions:" echo "===========" asterisk_cli "pjsip show endpoints" 2>/dev/null | grep -E "^[0-9]|Endpoint:" | head -20 } # ---------- trunk management ---------- cmd_trunk() { local subcmd="$1" shift case "$subcmd" in add) cmd_trunk_add "$@" ;; del|delete) cmd_trunk_del ;; test) cmd_trunk_test ;; status) cmd_trunk_status ;; *) echo "Usage: voipctl trunk " return 1 ;; esac } cmd_trunk_add() { local provider="$1" case "$provider" in ovh) cmd_trunk_add_ovh ;; manual) cmd_trunk_add_manual ;; *) echo "Usage: voipctl trunk add " return 1 ;; esac } cmd_trunk_add_ovh() { log_info "Adding OVH SIP trunk..." # Check OVH credentials local app_key=$(uci_get app_key ovh_telephony) local app_secret=$(uci_get app_secret ovh_telephony) local consumer_key=$(uci_get consumer_key ovh_telephony) if [ -z "$app_key" ] || [ -z "$app_secret" ] || [ -z "$consumer_key" ]; then log_error "OVH API credentials not configured" log_info "Configure via: uci set voip.ovh_telephony.app_key=..." log_info "Generate at: https://eu.api.ovh.com/createToken/" return 1 fi # Use OVH API to get SIP credentials if [ -f "$OVH_API_LIB" ]; then ovh_init local accounts=$(ovh_get_billing_accounts) log_info "Available billing accounts: $accounts" # For now, use first account (interactive selection can be added) local billing_account=$(echo "$accounts" | jsonfilter -e '@[0]' 2>/dev/null) [ -z "$billing_account" ] && { log_error "No billing accounts found" return 1 } local lines=$(ovh_get_lines "$billing_account") log_info "Available SIP lines: $lines" local service_name=$(echo "$lines" | jsonfilter -e '@[0]' 2>/dev/null) [ -z "$service_name" ] && { log_error "No SIP lines found" return 1 } local sip_info=$(ovh_get_sip_info "$billing_account" "$service_name") log_info "SIP Info: $sip_info" # Extract credentials and configure # (Implementation depends on OVH API response format) else log_warn "OVH API library not found, using manual configuration" cmd_trunk_add_manual fi } cmd_trunk_add_manual() { log_info "Manual SIP trunk configuration" log_info "Set trunk parameters via UCI:" log_info " uci set voip.sip_trunk.host='sip.provider.com'" log_info " uci set voip.sip_trunk.username='your_username'" log_info " uci set voip.sip_trunk.password='your_password'" log_info " uci commit voip" log_info " voipctl restart" } cmd_trunk_del() { log_info "Removing SIP trunk..." uci set voip.sip_trunk.enabled='0' uci set voip.sip_trunk.username='' uci set voip.sip_trunk.password='' uci commit voip # Remove from pjsip.conf sed -i '/\[ovh-/,/^$/d' "$LXC_ROOTFS/etc/asterisk/pjsip.conf" asterisk_cli "pjsip reload" log_info "Trunk removed" } cmd_trunk_test() { lxc_running || { log_error "Container not running"; return 1; } log_info "Testing trunk registration..." asterisk_cli "pjsip show registrations" asterisk_cli "pjsip qualify ovh-endpoint" } cmd_trunk_status() { lxc_running || { log_error "Container not running"; return 1; } echo "Trunk Registration Status:" echo "==========================" asterisk_cli "pjsip show registrations" } # ---------- call management ---------- cmd_call() { local from="$1" local to="$2" [ -z "$from" ] || [ -z "$to" ] && { echo "Usage: voipctl call " return 1 } lxc_running || { log_error "Container not running"; return 1; } log_info "Originating call: $from -> $to" # Originate call via AMI local result=$(asterisk_cli "channel originate PJSIP/$from extension $to@outbound") echo "$result" } cmd_hangup() { local channel="$1" [ -z "$channel" ] && { echo "Usage: voipctl hangup " return 1 } lxc_running || { log_error "Container not running"; return 1; } asterisk_cli "channel hangup $channel" } cmd_calls() { lxc_running || { log_error "Container not running"; return 1; } echo "Active Calls:" echo "=============" asterisk_cli "core show channels" } # ---------- voicemail management ---------- cmd_vm() { local subcmd="$1" shift case "$subcmd" in list) cmd_vm_list "$@" ;; play) cmd_vm_play "$@" ;; delete) cmd_vm_delete "$@" ;; *) echo "Usage: voipctl vm " return 1 ;; esac } cmd_vm_list() { local ext="$1" lxc_running || { log_error "Container not running"; return 1; } if [ -n "$ext" ]; then echo "Voicemails for extension $ext:" lxc_exec ls -la /var/spool/asterisk/voicemail/default/$ext/INBOX/ 2>/dev/null || echo " None" else echo "All voicemails:" lxc_exec find /var/spool/asterisk/voicemail -name "*.wav" 2>/dev/null | head -20 fi } cmd_vm_play() { local ext="$1" local id="$2" [ -z "$ext" ] || [ -z "$id" ] && { echo "Usage: voipctl vm play " return 1 } local path="/var/spool/asterisk/voicemail/default/$ext/INBOX/msg${id}.wav" echo "$path" } cmd_vm_delete() { local ext="$1" local id="$2" [ -z "$ext" ] || [ -z "$id" ] && { echo "Usage: voipctl vm delete " return 1 } lxc_running || { log_error "Container not running"; return 1; } local path="/var/spool/asterisk/voicemail/default/$ext/INBOX/msg${id}.*" lxc_exec rm -f $path log_info "Voicemail deleted: $ext/$id" } # ---------- HAProxy integration ---------- cmd_configure_haproxy() { require_root || { log_error "Must run as root"; return 1; } defaults local domain=$(uci_get domain ssl) [ -z "$domain" ] && { log_error "Domain not configured. Set: uci set voip.ssl.domain=voip.example.com" return 1 } log_info "Configuring HAProxy for WebRTC..." # Create backend for WebRTC/WSS local backend_name="voip_wss" uci set haproxy.${backend_name}=backend uci set haproxy.${backend_name}.name="$backend_name" uci set haproxy.${backend_name}.mode='http' uci set haproxy.${backend_name}.enabled='1' uci set haproxy.${backend_name}.timeout_server='3600s' uci set haproxy.${backend_name}.timeout_tunnel='3600s' uci set haproxy.${backend_name}.server="voip 127.0.0.1:${ari_port} check" # Create vhost local vhost_name=$(echo "$domain" | tr '.-' '_') uci set haproxy.${vhost_name}=vhost uci set haproxy.${vhost_name}.domain="$domain" uci set haproxy.${vhost_name}.backend="$backend_name" uci set haproxy.${vhost_name}.ssl='1' uci set haproxy.${vhost_name}.ssl_redirect='1' uci set haproxy.${vhost_name}.acme='1' uci set haproxy.${vhost_name}.enabled='1' uci commit haproxy uci_set enabled '1' ssl uci_set domain "$domain" ssl uci commit "$CONFIG" if command -v haproxyctl >/dev/null 2>&1; then haproxyctl generate /etc/init.d/haproxy reload fi log_info "HAProxy configured for $domain" log_info "WebRTC WSS: wss://$domain/ws" } cmd_emancipate() { local domain="$1" [ -z "$domain" ] && { echo "Usage: voipctl emancipate " return 1 } require_root || { log_error "Must run as root"; return 1; } log_info "Emancipating VoIP at $domain..." uci_set domain "$domain" ssl uci commit "$CONFIG" cmd_configure_haproxy log_info "" log_info "==============================================" log_info " VoIP Emancipated!" log_info "==============================================" log_info "" log_info " Domain: $domain" log_info " SIP: $domain:$sip_port" log_info " WebRTC: wss://$domain/ws" log_info "" } # ---------- service management ---------- cmd_service_run() { require_root || exit 1 defaults lxc_exists || { log_error "Container not found. Run: voipctl install"; exit 1; } log_info "Starting VoIP container..." exec lxc-start -n "$LXC_NAME" -F -f "$LXC_CONF" } cmd_service_stop() { log_info "Stopping VoIP container..." lxc_stop } # ---------- main ---------- case "$1" in install) cmd_install ;; uninstall) cmd_uninstall ;; update) cmd_update ;; check) cmd_check ;; start) cmd_start ;; stop) cmd_stop ;; restart) cmd_restart ;; status) shift; cmd_status "$@" ;; logs) shift; cmd_logs "$@" ;; shell) cmd_shell ;; cli) cmd_cli ;; ext) shift; cmd_ext "$@" ;; trunk) shift; cmd_trunk "$@" ;; call) shift; cmd_call "$@" ;; hangup) shift; cmd_hangup "$@" ;; calls) cmd_calls ;; vm) shift; cmd_vm "$@" ;; configure-haproxy) cmd_configure_haproxy ;; emancipate) shift; cmd_emancipate "$@" ;; service-run) cmd_service_run ;; service-stop) cmd_service_stop ;; *) usage; exit 1 ;; esac