CRITICAL FIXES: 1. Function name bug: rebuild_pjsip_config() → generate_pjsip_conf() - Was calling non-existent function in VLAN toggle feature - Would cause errors when enabling/disabling VLAN traversal 2. Uninstall logic: Now adapts to what's installed - "Remove Everything" option now works when only server OR client installed - Menu dynamically shows relevant options - Fixes user-reported issue with uninstall flow IMPROVEMENTS: 3. Better Asterisk failure diagnostics: - Shows 20 recent journal entries (was 10) - Checks certificate file existence - Provides "sudo asterisk -cvvv" debug command - Helps users troubleshoot startup failures 4. Port forwarding text updated: - Changed "OPNsense router" to "router" - Made path hint generic: "(Usually found in: ...)" - Works for all router brands TESTING: - Syntax validated with bash -n - Addresses user-reported issues from installation attempts - No breaking changes to existing functionality Version: 1.37 → 1.37.1 (patch release)
2400 lines
82 KiB
Bash
2400 lines
82 KiB
Bash
#!/bin/bash
|
|
# ================================================================
|
|
# Easy Asterisk - Interactive Installer v1.37.1
|
|
#
|
|
# BUG FIXES in v1.37.1:
|
|
# - FIXED: Function name bug (rebuild_pjsip_config → generate_pjsip_conf)
|
|
# - FIXED: Uninstall menu now adapts based on what's installed
|
|
# - IMPROVED: Better Asterisk failure diagnostics (shows cert status)
|
|
# - IMPROVED: Port forwarding instructions now say "router" not "OPNsense"
|
|
#
|
|
# MAJOR UPDATES in v1.37:
|
|
# - ADDED: VLAN/NAT Traversal toggle (Server Tools menu option 8)
|
|
# - ADDED: Optional VLAN subnet configuration
|
|
# - IMPROVED: Support for devices on VLANs without inter-VLAN routing
|
|
# - COMPATIBLE: Works with flat networks, VLANs, VPNs, and FQDN modes
|
|
#
|
|
# NEW FEATURE - VLAN/NAT Traversal:
|
|
# Enables communication with devices on VLANs without requiring
|
|
# inter-VLAN routing. Uses symmetric RTP and dynamic NAT handling
|
|
# to respond to packets at their source address.
|
|
#
|
|
# RETAINED from v1.30:
|
|
# - Two client types: LAN/VPN (IP) or FQDN (domain)
|
|
# - Per-device connection type selection
|
|
# - FQDN access setup wizard
|
|
# - Kiosk client fixes (DBUS, PipeWire deps)
|
|
# - PTT button configuration
|
|
# ================================================================
|
|
|
|
set +e
|
|
|
|
# Colors
|
|
RED='\033[0;31m'
|
|
GREEN='\033[0;32m'
|
|
YELLOW='\033[1;33m'
|
|
CYAN='\033[0;36m'
|
|
BOLD='\033[1m'
|
|
NC='\033[0m'
|
|
|
|
# Defaults
|
|
DEFAULT_SIP_PORT="5060"
|
|
DEFAULT_SIPS_PORT="5061"
|
|
DEFAULT_TURN_PORT="3478"
|
|
CONFIG_DIR="/etc/easy-asterisk"
|
|
CONFIG_FILE="${CONFIG_DIR}/config"
|
|
PTT_CONFIG_FILE="${CONFIG_DIR}/ptt-device"
|
|
CATEGORIES_FILE="${CONFIG_DIR}/categories.conf"
|
|
ROOMS_FILE="${CONFIG_DIR}/rooms.conf"
|
|
COTURN_CONFIG="/etc/turnserver.conf"
|
|
SCRIPT_VERSION="1.37.1"
|
|
|
|
# ================================================================
|
|
# 1. CORE HELPER FUNCTIONS
|
|
# ================================================================
|
|
|
|
print_header() {
|
|
echo -e "\n${CYAN}╔══════════════════════════════════════════════════════════╗${NC}"
|
|
echo -e "${CYAN} $1${NC}"
|
|
echo -e "${CYAN}╚══════════════════════════════════════════════════════════╝${NC}\n"
|
|
}
|
|
|
|
print_info() { echo -e "${GREEN}[INFO]${NC} $1"; }
|
|
print_warn() { echo -e "${YELLOW}[WARN]${NC} $1"; }
|
|
print_error() { echo -e "${RED}[ERROR]${NC} $1"; }
|
|
print_success() { echo -e "${GREEN}[OK]${NC} $1"; }
|
|
|
|
check_root() {
|
|
if [[ $EUID -ne 0 ]]; then
|
|
print_error "This script must be run as root (use sudo)"
|
|
exit 1
|
|
fi
|
|
}
|
|
|
|
generate_password() {
|
|
tr -dc 'A-Za-z0-9' < /dev/urandom | head -c 16
|
|
}
|
|
|
|
select_server_ip() {
|
|
# Returns selected IP in SELECTED_IP variable
|
|
# Filters out docker, virtual bridges, loopback
|
|
|
|
local -a ips=()
|
|
local -a ifaces=()
|
|
local -a labels=()
|
|
local count=0
|
|
|
|
while IFS= read -r line; do
|
|
local iface=$(echo "$line" | awk '{print $2}' | tr -d ':')
|
|
local ip=$(echo "$line" | awk '{print $4}' | cut -d'/' -f1)
|
|
|
|
# Skip unwanted interfaces
|
|
[[ "$iface" =~ ^(lo|docker|br-|veth|virbr|lxc|lxd) ]] && continue
|
|
[[ "$iface" == "lo" ]] && continue
|
|
[[ -z "$ip" ]] && continue
|
|
|
|
# Determine label
|
|
local label="LAN"
|
|
if [[ "$iface" =~ ^tailscale ]]; then
|
|
label="VPN-Tailscale"
|
|
elif [[ "$iface" =~ ^(wg[0-9]) ]]; then
|
|
label="VPN-WireGuard"
|
|
elif [[ "$iface" =~ ^(wt[0-9]|utun) ]]; then
|
|
label="VPN-Netbird"
|
|
elif [[ "$iface" =~ ^tun ]]; then
|
|
label="VPN-OpenVPN"
|
|
elif [[ "$iface" =~ ^zt ]]; then
|
|
label="VPN-ZeroTier"
|
|
elif [[ "$iface" =~ ^nordlynx ]]; then
|
|
label="VPN-NordVPN"
|
|
elif [[ "$iface" =~ ^proton ]]; then
|
|
label="VPN-Proton"
|
|
elif [[ "$iface" =~ ^mullvad ]]; then
|
|
label="VPN-Mullvad"
|
|
fi
|
|
|
|
((count++))
|
|
ips+=("$ip")
|
|
ifaces+=("$iface")
|
|
labels+=("$label")
|
|
|
|
done < <(ip -o -4 addr show 2>/dev/null)
|
|
|
|
if [[ $count -eq 0 ]]; then
|
|
SELECTED_IP=$(hostname -I | awk '{print $1}')
|
|
return
|
|
fi
|
|
|
|
echo ""
|
|
echo " Available server IPs:"
|
|
for i in "${!ips[@]}"; do
|
|
printf " %d) %-15s (%s - %s)\n" "$((i+1))" "${ips[$i]}" "${ifaces[$i]}" "${labels[$i]}"
|
|
done
|
|
echo " $((count+1))) Enter manually"
|
|
echo ""
|
|
|
|
read -p " Select [1]: " ip_choice
|
|
ip_choice="${ip_choice:-1}"
|
|
|
|
if [[ "$ip_choice" == "$((count+1))" ]]; then
|
|
read -p " Enter IP: " SELECTED_IP
|
|
elif [[ "$ip_choice" -ge 1 && "$ip_choice" -le "$count" ]]; then
|
|
SELECTED_IP="${ips[$((ip_choice-1))]}"
|
|
else
|
|
SELECTED_IP="${ips[0]}"
|
|
fi
|
|
}
|
|
|
|
select_user() {
|
|
local -a users=()
|
|
local -a user_ids=()
|
|
local count=0
|
|
|
|
echo "Scanning for users..."
|
|
echo ""
|
|
|
|
while IFS=: read -r username _ uid _ _ homedir shell; do
|
|
if [[ $uid -ge 1000 && -d "$homedir" && "$shell" != "/usr/sbin/nologin" && "$shell" != "/bin/false" ]]; then
|
|
((count++))
|
|
users+=("$username")
|
|
user_ids+=("$uid")
|
|
echo " ${count}) ${username} (UID: ${uid}, Home: ${homedir})"
|
|
fi
|
|
done < /etc/passwd
|
|
|
|
((count++))
|
|
echo " ${count}) Enter username manually"
|
|
echo ""
|
|
|
|
local default_choice=""
|
|
local default_user="${SUDO_USER:-}"
|
|
if [[ -z "$default_user" ]]; then
|
|
default_user="${users[0]:-}"
|
|
default_choice="1"
|
|
else
|
|
for i in "${!users[@]}"; do
|
|
if [[ "${users[$i]}" == "$default_user" ]]; then
|
|
default_choice=$((i + 1))
|
|
break
|
|
fi
|
|
done
|
|
fi
|
|
|
|
if [[ -n "$default_choice" ]]; then
|
|
read -p "Select user [${default_choice}]: " choice
|
|
choice="${choice:-$default_choice}"
|
|
else
|
|
read -p "Select user: " choice
|
|
fi
|
|
|
|
if [[ "$choice" =~ ^[0-9]+$ && "$choice" -le "${#users[@]}" && "$choice" -gt 0 ]]; then
|
|
local idx=$((choice - 1))
|
|
KIOSK_USER="${users[$idx]}"
|
|
KIOSK_UID="${user_ids[$idx]}"
|
|
echo ""
|
|
print_success "Selected user: $KIOSK_USER (UID: $KIOSK_UID)"
|
|
return 0
|
|
elif [[ "$choice" == "$count" ]]; then
|
|
echo ""
|
|
read -p "Enter username: " KIOSK_USER
|
|
if id "$KIOSK_USER" >/dev/null 2>&1; then
|
|
KIOSK_UID=$(id -u "$KIOSK_USER")
|
|
print_success "Selected user: $KIOSK_USER (UID: $KIOSK_UID)"
|
|
return 0
|
|
else
|
|
print_error "User '$KIOSK_USER' not found"
|
|
return 1
|
|
fi
|
|
else
|
|
print_error "Invalid selection"
|
|
return 1
|
|
fi
|
|
}
|
|
|
|
load_config() {
|
|
if [[ -f "$CONFIG_FILE" ]]; then
|
|
source "$CONFIG_FILE" 2>/dev/null || true
|
|
fi
|
|
INSTALLED_SERVER="${INSTALLED_SERVER:-n}"
|
|
INSTALLED_CLIENT="${INSTALLED_CLIENT:-n}"
|
|
INSTALLED_COTURN="${INSTALLED_COTURN:-n}"
|
|
KIOSK_USER="${KIOSK_USER:-}"
|
|
KIOSK_UID="${KIOSK_UID:-}"
|
|
USE_COTURN="${USE_COTURN:-n}"
|
|
TURN_SECRET="${TURN_SECRET:-}"
|
|
TURN_USER="${TURN_USER:-kioskuser}"
|
|
TURN_PASS="${TURN_PASS:-}"
|
|
TURN_DOMAIN="${TURN_DOMAIN:-}"
|
|
FQDN_ENABLED="${FQDN_ENABLED:-n}"
|
|
VLAN_NAT_TRAVERSAL="${VLAN_NAT_TRAVERSAL:-n}"
|
|
VLAN_SUBNETS="${VLAN_SUBNETS:-}"
|
|
return 0
|
|
}
|
|
|
|
backup_config() {
|
|
local file=$1
|
|
if [[ -f "$file" ]]; then
|
|
cp "$file" "${file}.backup-$(date +%s)"
|
|
ls -tp "${file}.backup-"* 2>/dev/null | tail -n +6 | xargs -I {} rm -- {} 2>/dev/null
|
|
fi
|
|
}
|
|
|
|
save_config() {
|
|
mkdir -p "$CONFIG_DIR"
|
|
chmod 755 "$CONFIG_DIR"
|
|
|
|
cat > "$CONFIG_FILE" << EOF
|
|
# Easy Asterisk Configuration - $(date)
|
|
KIOSK_USER="$KIOSK_USER"
|
|
KIOSK_UID="$KIOSK_UID"
|
|
KIOSK_EXTENSION="$KIOSK_EXTENSION"
|
|
KIOSK_NAME="$KIOSK_NAME"
|
|
SIP_PASSWORD="$SIP_PASSWORD"
|
|
ASTERISK_HOST="$ASTERISK_HOST"
|
|
DOMAIN_NAME="$DOMAIN_NAME"
|
|
TURN_DOMAIN="$TURN_DOMAIN"
|
|
FQDN_ENABLED="$FQDN_ENABLED"
|
|
CERT_PATH="$CERT_PATH"
|
|
KEY_PATH="$KEY_PATH"
|
|
INSTALLED_SERVER="$INSTALLED_SERVER"
|
|
INSTALLED_CLIENT="$INSTALLED_CLIENT"
|
|
INSTALLED_COTURN="$INSTALLED_COTURN"
|
|
USE_COTURN="$USE_COTURN"
|
|
TURN_SECRET="$TURN_SECRET"
|
|
TURN_USER="$TURN_USER"
|
|
TURN_PASS="$TURN_PASS"
|
|
VLAN_NAT_TRAVERSAL="$VLAN_NAT_TRAVERSAL"
|
|
VLAN_SUBNETS="$VLAN_SUBNETS"
|
|
CURRENT_PUBLIC_IP="$CURRENT_PUBLIC_IP"
|
|
PTT_DEVICE="$PTT_DEVICE"
|
|
PTT_KEYCODE="$PTT_KEYCODE"
|
|
LOCAL_CIDR="$LOCAL_CIDR"
|
|
SERVER_LAN_IP="$SERVER_LAN_IP"
|
|
EOF
|
|
chmod 644 "$CONFIG_FILE"
|
|
|
|
if [[ -n "$PTT_DEVICE" ]]; then
|
|
cat > "$PTT_CONFIG_FILE" << EOF
|
|
PTT_DEVICE="$PTT_DEVICE"
|
|
PTT_KEYCODE="$PTT_KEYCODE"
|
|
EOF
|
|
chmod 644 "$PTT_CONFIG_FILE"
|
|
fi
|
|
}
|
|
|
|
open_firewall_ports() {
|
|
print_info "Configuring firewall ports..."
|
|
if command -v ufw &>/dev/null; then
|
|
if ufw status 2>/dev/null | grep -q "Status: active"; then
|
|
ufw allow 5060/udp comment "SIP UDP" 2>/dev/null || true
|
|
ufw allow 5061/tcp comment "SIP TLS" 2>/dev/null || true
|
|
ufw allow 10000:20000/udp comment "RTP Media" 2>/dev/null || true
|
|
if [[ "$USE_COTURN" == "y" ]]; then
|
|
ufw allow ${DEFAULT_TURN_PORT}/udp comment "TURN UDP" 2>/dev/null || true
|
|
ufw allow ${DEFAULT_TURN_PORT}/tcp comment "TURN TCP" 2>/dev/null || true
|
|
ufw allow 49152:65535/udp comment "TURN Relay" 2>/dev/null || true
|
|
fi
|
|
ufw reload 2>/dev/null || true
|
|
print_success "UFW firewall ports opened"
|
|
fi
|
|
fi
|
|
}
|
|
|
|
# ================================================================
|
|
# 2. FQDN ACCESS SETUP
|
|
# ================================================================
|
|
|
|
setup_fqdn_access() {
|
|
print_header "Setup FQDN Access"
|
|
|
|
echo "This configures your server for clients connecting via domain name."
|
|
echo ""
|
|
echo "═══════════════════════════════════════════════════════════════"
|
|
echo " WHO NEEDS FQDN ACCESS?"
|
|
echo "═══════════════════════════════════════════════════════════════"
|
|
echo ""
|
|
echo " • Internet clients (remote locations, mobile phones)"
|
|
echo " • Cross-VLAN clients (different VLAN using hairpin NAT)"
|
|
echo ""
|
|
echo " These clients connect through your router's WAN interface,"
|
|
echo " so they need the same setup: FQDN + port forwarding."
|
|
echo ""
|
|
echo "═══════════════════════════════════════════════════════════════"
|
|
echo ""
|
|
|
|
load_config
|
|
|
|
read -p "Domain name (e.g., sip.example.com): " domain
|
|
if [[ -z "$domain" ]]; then
|
|
print_error "Domain name required"
|
|
return 1
|
|
fi
|
|
DOMAIN_NAME="$domain"
|
|
|
|
# Detect IPs
|
|
local detected_lan_ip=$(hostname -I | awk '{print $1}')
|
|
local detected_public_ip=$(get_public_ip)
|
|
|
|
echo ""
|
|
echo "Detected IPs:"
|
|
echo " LAN IP: ${detected_lan_ip:-not detected}"
|
|
echo " Public IP: ${detected_public_ip:-not detected}"
|
|
echo ""
|
|
|
|
SERVER_LAN_IP="${SERVER_LAN_IP:-$detected_lan_ip}"
|
|
CURRENT_PUBLIC_IP="$detected_public_ip"
|
|
|
|
# CIDR for NAT
|
|
local raw_cidr=$(ip -o -f inet addr show | awk '/scope global/ {print $4}' | head -1)
|
|
local default_cidr="$raw_cidr"
|
|
if [[ "$raw_cidr" =~ \.([0-9]+)/24$ ]]; then
|
|
default_cidr="${raw_cidr%.*}.0/24"
|
|
fi
|
|
read -p "Local network CIDR [$default_cidr]: " local_net
|
|
LOCAL_CIDR="${local_net:-$default_cidr}"
|
|
|
|
FQDN_ENABLED="y"
|
|
save_config
|
|
|
|
echo ""
|
|
echo "═══════════════════════════════════════════════════════════════"
|
|
echo " CERTIFICATE SETUP"
|
|
echo "═══════════════════════════════════════════════════════════════"
|
|
echo ""
|
|
setup_certificates
|
|
|
|
# Regenerate configs
|
|
if [[ "$INSTALLED_SERVER" == "y" ]]; then
|
|
generate_pjsip_conf
|
|
restart_asterisk_safe
|
|
fi
|
|
|
|
echo ""
|
|
echo "═══════════════════════════════════════════════════════════════"
|
|
echo " ROUTER PORT FORWARDING REQUIRED"
|
|
echo "═══════════════════════════════════════════════════════════════"
|
|
echo ""
|
|
echo " On your router, configure these port forwards:"
|
|
echo ""
|
|
echo " ┌──────────────────┬──────────┬─────────────────────────────┐"
|
|
echo " │ WAN Port │ Protocol │ Forward To │"
|
|
echo " ├──────────────────┼──────────┼─────────────────────────────┤"
|
|
echo " │ 5061 │ TCP │ ${SERVER_LAN_IP}:5061 │"
|
|
echo " │ 10000-20000 │ UDP │ ${SERVER_LAN_IP}:10000-20000│"
|
|
echo " └──────────────────┴──────────┴─────────────────────────────┘"
|
|
echo ""
|
|
echo " (Usually found in: Firewall → NAT → Port Forward)"
|
|
echo ""
|
|
echo "═══════════════════════════════════════════════════════════════"
|
|
echo ""
|
|
print_success "FQDN access configured"
|
|
}
|
|
|
|
setup_certificates() {
|
|
echo "Select Certificate Source:"
|
|
echo " 1) Auto-Sync from Caddy"
|
|
echo " 2) Certbot (requires port 80 open)"
|
|
echo " 3) Self-Signed (testing only)"
|
|
echo " 4) Manual path"
|
|
echo " 0) Skip"
|
|
read -p "Select [1]: " cert_opt
|
|
cert_opt="${cert_opt:-1}"
|
|
|
|
[[ "$cert_opt" == "0" ]] && return
|
|
|
|
case "$cert_opt" in
|
|
1) setup_caddy_cert_sync "auto" ;;
|
|
2)
|
|
print_info "Installing Certbot..."
|
|
apt install -y certbot
|
|
certbot certonly --standalone -d "$DOMAIN_NAME" --non-interactive --agree-tos --register-unsafely-without-email
|
|
if [[ -f "/etc/letsencrypt/live/$DOMAIN_NAME/fullchain.pem" ]]; then
|
|
mkdir -p /etc/asterisk/certs
|
|
cat "/etc/letsencrypt/live/$DOMAIN_NAME/fullchain.pem" > /etc/asterisk/certs/server.crt
|
|
cat "/etc/letsencrypt/live/$DOMAIN_NAME/privkey.pem" > /etc/asterisk/certs/server.key
|
|
chown asterisk:asterisk /etc/asterisk/certs/server.* 2>/dev/null
|
|
chmod 644 /etc/asterisk/certs/server.crt
|
|
chmod 600 /etc/asterisk/certs/server.key
|
|
print_success "Certbot success"
|
|
else
|
|
print_error "Certbot failed"
|
|
fi
|
|
;;
|
|
3)
|
|
mkdir -p /etc/asterisk/certs
|
|
openssl req -x509 -nodes -days 3650 -newkey rsa:2048 \
|
|
-keyout /etc/asterisk/certs/server.key \
|
|
-out /etc/asterisk/certs/server.crt \
|
|
-subj "/CN=$DOMAIN_NAME" 2>/dev/null
|
|
chown asterisk:asterisk /etc/asterisk/certs/server.* 2>/dev/null
|
|
chmod 644 /etc/asterisk/certs/server.crt
|
|
chmod 600 /etc/asterisk/certs/server.key
|
|
print_success "Self-signed certificate generated"
|
|
print_warn "Clients must trust this certificate"
|
|
;;
|
|
4)
|
|
read -p "Certificate path: " cp
|
|
read -p "Private key path: " kp
|
|
if [[ -f "$cp" && -f "$kp" ]]; then
|
|
mkdir -p /etc/asterisk/certs
|
|
cat "$cp" > /etc/asterisk/certs/server.crt
|
|
cat "$kp" > /etc/asterisk/certs/server.key
|
|
chown asterisk:asterisk /etc/asterisk/certs/server.* 2>/dev/null
|
|
chmod 644 /etc/asterisk/certs/server.crt
|
|
chmod 600 /etc/asterisk/certs/server.key
|
|
print_success "Certificates installed"
|
|
else
|
|
print_error "Files not found"
|
|
fi
|
|
;;
|
|
esac
|
|
}
|
|
|
|
get_public_ip() {
|
|
curl -s -4 --connect-timeout 5 ifconfig.me 2>/dev/null || \
|
|
curl -s -4 --connect-timeout 5 icanhazip.com 2>/dev/null || echo ""
|
|
}
|
|
|
|
# ================================================================
|
|
# 3. COTURN SETUP
|
|
# ================================================================
|
|
|
|
install_coturn() {
|
|
print_header "Installing COTURN"
|
|
apt update
|
|
apt install -y coturn
|
|
|
|
[[ -z "$TURN_PASS" ]] && TURN_PASS=$(generate_password)
|
|
|
|
local public_ip=$(get_public_ip)
|
|
CURRENT_PUBLIC_IP="$public_ip"
|
|
|
|
if [[ -z "$public_ip" ]]; then
|
|
print_error "Could not detect public IP"
|
|
return 1
|
|
fi
|
|
|
|
print_info "Public IP: $public_ip"
|
|
|
|
sed -i 's/#TURNSERVER_ENABLED=1/TURNSERVER_ENABLED=1/' /etc/default/coturn 2>/dev/null || true
|
|
|
|
backup_config "$COTURN_CONFIG"
|
|
cat > "$COTURN_CONFIG" << EOF
|
|
listening-port=${DEFAULT_TURN_PORT}
|
|
fingerprint
|
|
lt-cred-mech
|
|
realm=${TURN_DOMAIN:-${DOMAIN_NAME:-turn.local}}
|
|
total-quota=100
|
|
stale-nonce=600
|
|
cert=/etc/asterisk/certs/server.crt
|
|
pkey=/etc/asterisk/certs/server.key
|
|
no-tlsv1
|
|
no-tlsv1_1
|
|
cipher-list="ECDHE-RSA-AES256-GCM-SHA512:DHE-RSA-AES256-GCM-SHA512"
|
|
dh2066
|
|
no-stdout-log
|
|
log-file=/var/log/turnserver.log
|
|
simple-log
|
|
external-ip=${public_ip}
|
|
min-port=49152
|
|
max-port=65535
|
|
user-quota=12
|
|
no-multicast-peers
|
|
no-cli
|
|
user=${TURN_USER}:${TURN_PASS}
|
|
EOF
|
|
|
|
chmod 600 "$COTURN_CONFIG"
|
|
|
|
systemctl enable coturn
|
|
systemctl restart coturn
|
|
|
|
if systemctl is-active coturn >/dev/null; then
|
|
print_success "COTURN installed and running"
|
|
INSTALLED_COTURN="y"
|
|
USE_COTURN="y"
|
|
save_config
|
|
return 0
|
|
else
|
|
print_error "COTURN failed to start"
|
|
journalctl -u coturn -n 20 --no-pager
|
|
return 1
|
|
fi
|
|
}
|
|
|
|
toggle_vlan_nat_traversal() {
|
|
print_header "VLAN/NAT Traversal Configuration"
|
|
load_config
|
|
|
|
echo "═══════════════════════════════════════════════════════════════"
|
|
echo " VLAN/NAT TRAVERSAL SETTINGS"
|
|
echo "═══════════════════════════════════════════════════════════════"
|
|
echo ""
|
|
echo " This feature helps devices on VLANs communicate with the"
|
|
echo " Asterisk server without requiring inter-VLAN routing."
|
|
echo ""
|
|
echo " It works by treating VLAN traffic similar to NAT traversal,"
|
|
echo " sending responses back to the source address of incoming packets."
|
|
echo ""
|
|
echo " Current status: $(if [[ "$VLAN_NAT_TRAVERSAL" == "y" ]]; then echo -e "${GREEN}ENABLED${NC}"; else echo -e "${YELLOW}DISABLED${NC}"; fi)"
|
|
echo ""
|
|
|
|
if [[ -n "$VLAN_SUBNETS" ]]; then
|
|
echo " Configured VLAN subnets:"
|
|
IFS=',' read -ra SUBNETS <<< "$VLAN_SUBNETS"
|
|
for subnet in "${SUBNETS[@]}"; do
|
|
echo " - $subnet"
|
|
done
|
|
echo ""
|
|
fi
|
|
|
|
echo "═══════════════════════════════════════════════════════════════"
|
|
echo ""
|
|
echo " 1) Enable VLAN/NAT traversal"
|
|
echo " 2) Disable VLAN/NAT traversal"
|
|
echo " 3) Configure VLAN subnets (optional)"
|
|
echo " 0) Back"
|
|
echo ""
|
|
read -p " Select: " choice
|
|
|
|
case $choice in
|
|
1)
|
|
print_info "Enabling VLAN/NAT traversal..."
|
|
VLAN_NAT_TRAVERSAL="y"
|
|
save_config
|
|
generate_pjsip_conf
|
|
asterisk -rx "pjsip reload" >/dev/null 2>&1
|
|
echo ""
|
|
print_success "VLAN/NAT traversal ENABLED"
|
|
echo ""
|
|
echo " Asterisk will now respond to the source address of incoming"
|
|
echo " packets, allowing VLAN devices to connect properly."
|
|
echo ""
|
|
|
|
read -p " Do you want to configure VLAN subnets now? [y/N]: " config_subnets
|
|
if [[ "$config_subnets" =~ ^[Yy]$ ]]; then
|
|
echo ""
|
|
echo " Enter VLAN subnets (comma-separated, e.g., 192.168.10.0/24,192.168.20.0/24)"
|
|
echo " Leave empty to skip:"
|
|
read -p " > " subnets
|
|
if [[ -n "$subnets" ]]; then
|
|
VLAN_SUBNETS="$subnets"
|
|
save_config
|
|
generate_pjsip_conf
|
|
asterisk -rx "pjsip reload" >/dev/null 2>&1
|
|
print_success "VLAN subnets configured"
|
|
fi
|
|
fi
|
|
;;
|
|
2)
|
|
print_info "Disabling VLAN/NAT traversal..."
|
|
VLAN_NAT_TRAVERSAL="n"
|
|
VLAN_SUBNETS=""
|
|
save_config
|
|
generate_pjsip_conf
|
|
asterisk -rx "pjsip reload" >/dev/null 2>&1
|
|
echo ""
|
|
print_success "VLAN/NAT traversal DISABLED"
|
|
echo ""
|
|
echo " Configuration restored to default (flat network only)."
|
|
;;
|
|
3)
|
|
echo ""
|
|
echo " Current subnets: ${VLAN_SUBNETS:-none}"
|
|
echo ""
|
|
echo " Enter VLAN subnets (comma-separated, e.g., 192.168.10.0/24,192.168.20.0/24)"
|
|
echo " Leave empty to clear:"
|
|
read -p " > " subnets
|
|
VLAN_SUBNETS="$subnets"
|
|
save_config
|
|
|
|
if [[ "$VLAN_NAT_TRAVERSAL" == "y" ]]; then
|
|
generate_pjsip_conf
|
|
asterisk -rx "pjsip reload" >/dev/null 2>&1
|
|
print_success "VLAN subnets updated and applied"
|
|
else
|
|
print_success "VLAN subnets saved (will apply when traversal is enabled)"
|
|
fi
|
|
;;
|
|
0)
|
|
return
|
|
;;
|
|
esac
|
|
|
|
echo ""
|
|
read -p "Press Enter to continue..."
|
|
}
|
|
|
|
configure_coturn_menu() {
|
|
print_header "Configure COTURN"
|
|
|
|
if [[ "$INSTALLED_COTURN" == "y" ]]; then
|
|
echo -e "Status: ${GREEN}Installed${NC}"
|
|
echo "User: ${TURN_USER}"
|
|
echo "Pass: ${TURN_PASS}"
|
|
echo ""
|
|
echo " 1) Update Credentials"
|
|
echo " 2) Reinstall"
|
|
echo " 3) Update IP"
|
|
echo " 4) Show config"
|
|
echo " 5) Uninstall"
|
|
echo " 0) Back"
|
|
read -p "Select: " choice
|
|
case $choice in
|
|
1)
|
|
read -p "Username [${TURN_USER}]: " t_user
|
|
TURN_USER="${t_user:-$TURN_USER}"
|
|
read -p "Password [generate]: " t_pass
|
|
TURN_PASS="${t_pass:-$(generate_password)}"
|
|
sed -i '/^user=/d' "$COTURN_CONFIG"
|
|
echo "user=${TURN_USER}:${TURN_PASS}" >> "$COTURN_CONFIG"
|
|
systemctl restart coturn
|
|
save_config
|
|
print_success "Credentials updated"
|
|
;;
|
|
2) install_coturn ;;
|
|
3)
|
|
local new_ip=$(get_public_ip)
|
|
if [[ -n "$new_ip" ]]; then
|
|
sed -i "s/^external-ip=.*/external-ip=${new_ip}/" "$COTURN_CONFIG"
|
|
systemctl restart coturn
|
|
CURRENT_PUBLIC_IP="$new_ip"
|
|
save_config
|
|
print_success "IP updated to $new_ip"
|
|
fi
|
|
;;
|
|
4)
|
|
echo "Status: $(systemctl is-active coturn)"
|
|
echo "IP: $CURRENT_PUBLIC_IP"
|
|
echo "Credentials: ${TURN_USER}:${TURN_PASS}"
|
|
;;
|
|
5)
|
|
systemctl stop coturn 2>/dev/null
|
|
systemctl disable coturn 2>/dev/null
|
|
apt purge -y coturn 2>/dev/null
|
|
INSTALLED_COTURN="n"
|
|
USE_COTURN="n"
|
|
save_config
|
|
print_success "COTURN uninstalled"
|
|
;;
|
|
esac
|
|
else
|
|
echo "COTURN is not installed."
|
|
read -p "Install now? [Y/n]: " install
|
|
[[ ! "$install" =~ ^[Nn]$ ]] && install_coturn
|
|
fi
|
|
}
|
|
|
|
# ================================================================
|
|
# 4. DEVICE MANAGEMENT
|
|
# ================================================================
|
|
|
|
initialize_default_categories() {
|
|
mkdir -p "$CONFIG_DIR"
|
|
if [[ ! -f "$CATEGORIES_FILE" ]]; then
|
|
cat > "$CATEGORIES_FILE" << 'EOF'
|
|
# Format: id|name|auto_answer(yes/no)|description
|
|
kiosk|Kiosks|yes|Fixed auto-answer intercoms
|
|
mobile|Mobile Devices|no|Phones and mobile devices
|
|
EOF
|
|
chmod 600 "$CATEGORIES_FILE"
|
|
fi
|
|
if [[ ! -f "$ROOMS_FILE" ]]; then
|
|
cat > "$ROOMS_FILE" << 'EOF'
|
|
# Format: ext|name|members|timeout|type(ring/page)
|
|
199|All Kiosks|101,102,103,104,105|60|page
|
|
299|All Mobile|201,202,203,204,205|60|ring
|
|
EOF
|
|
chmod 600 "$ROOMS_FILE"
|
|
fi
|
|
}
|
|
|
|
list_categories() {
|
|
initialize_default_categories
|
|
local index=1
|
|
while IFS='|' read -r cat_id cat_name auto_answer description; do
|
|
[[ "$cat_id" =~ ^# ]] && continue
|
|
[[ -z "$cat_id" ]] && continue
|
|
local auto_text="${RED}Ring${NC}"
|
|
[[ "$auto_answer" == "yes" ]] && auto_text="${GREEN}Auto-answer${NC}"
|
|
echo -e " ${CYAN}$index)${NC} ${BOLD}$cat_name${NC} ($cat_id) - $auto_text"
|
|
((index++))
|
|
done < "$CATEGORIES_FILE"
|
|
}
|
|
|
|
get_category_by_index() {
|
|
local target_index=$1
|
|
local index=1
|
|
while IFS='|' read -r cat_id cat_name auto_answer description; do
|
|
[[ "$cat_id" =~ ^# ]] && continue
|
|
[[ -z "$cat_id" ]] && continue
|
|
if [[ $index -eq $target_index ]]; then
|
|
echo "$cat_id|$cat_name|$auto_answer"
|
|
return 0
|
|
fi
|
|
((index++))
|
|
done < "$CATEGORIES_FILE"
|
|
}
|
|
|
|
manage_categories() {
|
|
print_header "Manage Categories"
|
|
list_categories
|
|
echo ""
|
|
echo " 1) Add Category"
|
|
echo " 2) Delete Category"
|
|
echo " 0) Back"
|
|
read -p "Select: " choice
|
|
case $choice in
|
|
1)
|
|
read -p "ID (lowercase): " cid
|
|
read -p "Display Name: " cname
|
|
read -p "Auto Answer? [y/N]: " ca
|
|
local ans="no"
|
|
[[ "$ca" =~ ^[Yy]$ ]] && ans="yes"
|
|
echo "${cid}|${cname}|${ans}|Custom category" >> "$CATEGORIES_FILE"
|
|
print_success "Category added"
|
|
rebuild_dialplan
|
|
;;
|
|
2)
|
|
read -p "Number to delete: " num
|
|
local data=$(get_category_by_index "$num")
|
|
local cid=$(echo "$data" | cut -d'|' -f1)
|
|
[[ -n "$cid" ]] && sed -i "/^${cid}|/d" "$CATEGORIES_FILE"
|
|
print_success "Deleted"
|
|
rebuild_dialplan
|
|
;;
|
|
esac
|
|
}
|
|
|
|
manage_rooms() {
|
|
print_header "Manage Rooms"
|
|
initialize_default_categories
|
|
echo "Current Rooms:"
|
|
local index=1
|
|
while IFS='|' read -r rext rname rmem rtime rtype; do
|
|
[[ "$rext" =~ ^# ]] && continue
|
|
[[ -z "$rext" ]] && continue
|
|
local type_text="Ring Group"
|
|
[[ "$rtype" == "page" ]] && type_text="PAGE"
|
|
echo -e " ${CYAN}$index)${NC} ${BOLD}$rname${NC} ($rext) - $type_text"
|
|
echo " Members: $rmem"
|
|
((index++))
|
|
done < "$ROOMS_FILE"
|
|
echo ""
|
|
echo " 1) Add Room"
|
|
echo " 2) Edit Room"
|
|
echo " 3) Delete Room"
|
|
echo " 0) Back"
|
|
read -p "Select: " choice
|
|
case $choice in
|
|
1)
|
|
read -p "Extension: " new_ext
|
|
read -p "Name: " new_name
|
|
echo " 1) Ring Group"
|
|
echo " 2) Page/Intercom"
|
|
read -p "Type [1]: " type_sel
|
|
local rtype="ring"
|
|
[[ "$type_sel" == "2" ]] && rtype="page"
|
|
read -p "Members (e.g. 101,102): " members
|
|
echo "${new_ext}|${new_name}|${members}|60|${rtype}" >> "$ROOMS_FILE"
|
|
rebuild_dialplan
|
|
print_success "Room created"
|
|
;;
|
|
2)
|
|
read -p "Room #: " rnum
|
|
local count=0
|
|
while IFS='|' read -r rext rname rmem rtime rtype; do
|
|
[[ "$rext" =~ ^# ]] && continue
|
|
[[ -z "$rext" ]] && continue
|
|
((count++))
|
|
if [[ $count -eq $rnum ]]; then
|
|
echo "Current members: $rmem"
|
|
read -p "New members: " new_mem
|
|
sed -i "/^${rext}|/d" "$ROOMS_FILE"
|
|
echo "${rext}|${rname}|${new_mem}|${rtime}|${rtype}" >> "$ROOMS_FILE"
|
|
rebuild_dialplan
|
|
print_success "Room updated"
|
|
break
|
|
fi
|
|
done < "$ROOMS_FILE"
|
|
;;
|
|
3)
|
|
read -p "Room #: " rnum
|
|
local count=0
|
|
while IFS='|' read -r rext rrest; do
|
|
[[ "$rext" =~ ^# ]] && continue
|
|
[[ -z "$rext" ]] && continue
|
|
((count++))
|
|
if [[ $count -eq $rnum ]]; then
|
|
sed -i "/^${rext}|/d" "$ROOMS_FILE"
|
|
rebuild_dialplan
|
|
print_success "Room deleted"
|
|
break
|
|
fi
|
|
done < "$ROOMS_FILE"
|
|
;;
|
|
esac
|
|
}
|
|
|
|
add_device_menu() {
|
|
print_header "Add Device"
|
|
load_config
|
|
|
|
list_categories
|
|
read -p "Category number: " cat_num
|
|
local cat_data=$(get_category_by_index "$cat_num")
|
|
if [[ -z "$cat_data" ]]; then print_error "Invalid"; return; fi
|
|
local cat_id=$(echo "$cat_data" | cut -d'|' -f1)
|
|
local cat_name=$(echo "$cat_data" | cut -d'|' -f2)
|
|
local auto_answer=$(echo "$cat_data" | cut -d'|' -f3)
|
|
|
|
local start_range=101 end_range=199
|
|
case "$cat_id" in
|
|
kiosk) start_range=101; end_range=199 ;;
|
|
mobile) start_range=201; end_range=299 ;;
|
|
*) start_range=301; end_range=399 ;;
|
|
esac
|
|
|
|
local suggested_ext=""
|
|
for ext in $(seq $start_range $end_range); do
|
|
if ! grep -q "^\[${ext}\]" /etc/asterisk/pjsip.conf 2>/dev/null; then
|
|
suggested_ext=$ext; break
|
|
fi
|
|
done
|
|
|
|
read -p "Extension [$suggested_ext]: " ext
|
|
ext="${ext:-$suggested_ext}"
|
|
|
|
if grep -q "^\[${ext}\]" /etc/asterisk/pjsip.conf 2>/dev/null; then
|
|
print_error "Extension exists!"
|
|
return
|
|
fi
|
|
|
|
read -p "Name: " name
|
|
name="${name:-Device $ext}"
|
|
local pass=$(generate_password)
|
|
|
|
# CONNECTION TYPE SELECTION
|
|
local conn_type="lan"
|
|
local transport_block=""
|
|
local encryption_block=""
|
|
local ice_block=""
|
|
local display_server=""
|
|
local display_port="5060"
|
|
local display_transport="UDP"
|
|
local display_encryption="None"
|
|
|
|
echo ""
|
|
echo "═══════════════════════════════════════════════════════════════"
|
|
echo -e " HOW WILL THIS DEVICE CONNECT?"
|
|
echo "═══════════════════════════════════════════════════════════════"
|
|
echo ""
|
|
echo -e " 1) ${GREEN}LAN/VPN${NC} - Same network or VPN tunnel (UDP)"
|
|
if [[ "$FQDN_ENABLED" == "y" && -n "$DOMAIN_NAME" ]]; then
|
|
echo -e " 2) ${CYAN}FQDN${NC} - Internet or cross-VLAN via ${DOMAIN_NAME} (TLS)"
|
|
else
|
|
echo -e " 2) ${YELLOW}FQDN${NC} - Not configured (run 'Setup FQDN Access' first)"
|
|
fi
|
|
echo ""
|
|
read -p " Select [1]: " conn_choice
|
|
conn_choice="${conn_choice:-1}"
|
|
|
|
if [[ "$conn_choice" == "1" ]]; then
|
|
# LAN/VPN - let user pick which IP
|
|
select_server_ip
|
|
display_server="$SELECTED_IP"
|
|
display_port="5060"
|
|
display_transport="UDP"
|
|
display_encryption="None"
|
|
elif [[ "$conn_choice" == "2" ]]; then
|
|
if [[ "$FQDN_ENABLED" != "y" || -z "$DOMAIN_NAME" ]]; then
|
|
print_error "FQDN access not configured. Run 'Setup FQDN Access' first."
|
|
return
|
|
fi
|
|
conn_type="fqdn"
|
|
transport_block="transport=transport-tls"
|
|
encryption_block="media_encryption=sdes"
|
|
ice_block="ice_support=yes"
|
|
display_server="$DOMAIN_NAME"
|
|
display_port="5061"
|
|
display_transport="TLS"
|
|
display_encryption="SRTP (SDES)"
|
|
fi
|
|
|
|
# Auto-answer override
|
|
local override_tag=""
|
|
if [[ "$auto_answer" == "no" ]]; then
|
|
read -p "Force AUTO-ANSWER? [y/N]: " force_aa
|
|
[[ "$force_aa" =~ ^[Yy]$ ]] && override_tag="[AA:yes]" && auto_answer="yes"
|
|
elif [[ "$auto_answer" == "yes" ]]; then
|
|
read -p "Force RING? [y/N]: " force_ring
|
|
[[ "$force_ring" =~ ^[Yy]$ ]] && override_tag="[AA:no]" && auto_answer="no"
|
|
fi
|
|
|
|
backup_config "/etc/asterisk/pjsip.conf"
|
|
|
|
# Build endpoint config
|
|
local endpoint_extras=""
|
|
if [[ "$conn_type" == "fqdn" ]]; then
|
|
endpoint_extras="${encryption_block}
|
|
${transport_block}
|
|
${ice_block}"
|
|
fi
|
|
|
|
cat >> /etc/asterisk/pjsip.conf << EOF
|
|
|
|
; === Device: $name ($cat_id) [CONN:${conn_type}] $override_tag ===
|
|
[${ext}]
|
|
type=endpoint
|
|
context=intercom
|
|
disallow=all
|
|
allow=opus
|
|
allow=ulaw
|
|
allow=alaw
|
|
allow=g722
|
|
direct_media=no
|
|
rtp_symmetric=yes
|
|
force_rport=yes
|
|
rewrite_contact=yes
|
|
auth=${ext}
|
|
aors=${ext}
|
|
callerid="${name}" <${ext}>
|
|
${endpoint_extras}
|
|
|
|
[${ext}]
|
|
type=auth
|
|
auth_type=userpass
|
|
username=${ext}
|
|
password=${pass}
|
|
|
|
[${ext}]
|
|
type=aor
|
|
max_contacts=5
|
|
remove_existing=yes
|
|
qualify_frequency=60
|
|
EOF
|
|
|
|
chown -R asterisk:asterisk /etc/asterisk
|
|
asterisk -rx "pjsip reload" >/dev/null 2>&1
|
|
rebuild_dialplan
|
|
|
|
echo ""
|
|
echo "═══════════════════════════════════════════════════════════════"
|
|
echo " DEVICE ADDED"
|
|
echo "═══════════════════════════════════════════════════════════════"
|
|
echo ""
|
|
echo " ${BOLD}Client Configuration:${NC}"
|
|
echo " Server: ${display_server}"
|
|
echo " Port: ${display_port}"
|
|
echo " Transport: ${display_transport}"
|
|
echo " Extension: ${ext}"
|
|
echo " Password: ${pass}"
|
|
echo " Encryption: ${display_encryption}"
|
|
echo ""
|
|
echo "═══════════════════════════════════════════════════════════════"
|
|
}
|
|
|
|
remove_device() {
|
|
print_header "Remove Device"
|
|
declare -A REMOVE_MAP
|
|
local count=1
|
|
echo "Select device to remove:"
|
|
echo ""
|
|
local dev_name=""
|
|
while IFS= read -r line; do
|
|
if [[ "$line" == *"; === Device:"* ]]; then
|
|
local temp="${line#*; === Device: }"
|
|
temp="${temp% ===}"
|
|
temp=$(echo "$temp" | sed 's/ \[CONN:[^]]*\]//' | sed 's/ \[AA:[^]]*\]//')
|
|
dev_name="${temp% (*)}"
|
|
fi
|
|
if [[ "$line" =~ ^\[([0-9]+)\] ]]; then
|
|
local ext="${BASH_REMATCH[1]}"
|
|
if [[ -n "$dev_name" ]]; then
|
|
echo " ${count}) Ext ${ext} - ${dev_name}"
|
|
REMOVE_MAP[$count]=$ext
|
|
((count++))
|
|
dev_name=""
|
|
fi
|
|
fi
|
|
done < /etc/asterisk/pjsip.conf
|
|
echo ""
|
|
echo " 98) DELETE ALL"
|
|
echo " 0) Cancel"
|
|
echo ""
|
|
read -p "Select: " choice
|
|
|
|
if [[ "$choice" == "98" ]]; then
|
|
read -p "Type 'DELETE ALL' to confirm: " confirm
|
|
if [[ "$confirm" == "DELETE ALL" ]]; then
|
|
backup_config "/etc/asterisk/pjsip.conf"
|
|
sed -i '/^; === Device:/,/^$/d' /etc/asterisk/pjsip.conf
|
|
sed -i '/^\[[0-9]\{3\}\]/,/^$/d' /etc/asterisk/pjsip.conf
|
|
asterisk -rx "pjsip reload" 2>/dev/null
|
|
rebuild_dialplan
|
|
print_success "All devices deleted"
|
|
fi
|
|
return
|
|
fi
|
|
|
|
[[ "$choice" == "0" || -z "${REMOVE_MAP[$choice]}" ]] && return
|
|
|
|
local ext="${REMOVE_MAP[$choice]}"
|
|
read -p "Remove $ext? [y/N]: " confirm
|
|
if [[ "$confirm" =~ ^[Yy]$ ]]; then
|
|
backup_config "/etc/asterisk/pjsip.conf"
|
|
sed -i "/^; === Device:.*${ext}.*/,/^$/d" /etc/asterisk/pjsip.conf
|
|
sed -i "/^\[${ext}\]/,/^$/d" /etc/asterisk/pjsip.conf
|
|
asterisk -rx "pjsip reload" 2>/dev/null
|
|
rebuild_dialplan
|
|
print_success "Removed"
|
|
fi
|
|
}
|
|
|
|
show_registered_devices() {
|
|
print_header "Device Status"
|
|
load_config
|
|
|
|
printf "${CYAN}%-6s %-18s %-10s %-8s %-10s %-12s${NC}\n" "Ext" "Name" "Category" "Type" "Status" "Password"
|
|
echo "────────────────────────────────────────────────────────────────────────────"
|
|
local dev_name="" dev_cat="" dev_conn=""
|
|
while IFS= read -r line; do
|
|
if [[ "$line" == *"; === Device:"* ]]; then
|
|
local temp="${line#*; === Device: }"
|
|
temp="${temp% ===}"
|
|
# Extract connection type
|
|
if [[ "$temp" == *"[CONN:fqdn]"* ]]; then
|
|
dev_conn="FQDN"
|
|
else
|
|
dev_conn="LAN"
|
|
fi
|
|
temp=$(echo "$temp" | sed 's/ \[CONN:[^]]*\]//' | sed 's/ \[AA:[^]]*\]//')
|
|
dev_cat="${temp##* (}"; dev_cat="${dev_cat%)}"
|
|
dev_name="${temp% (*)}"
|
|
fi
|
|
if [[ "$line" =~ ^\[([0-9]+)\] ]]; then
|
|
local ext="${BASH_REMATCH[1]}"
|
|
if [[ -n "$dev_name" ]]; then
|
|
local status="Offline"
|
|
local status_color="${RED}"
|
|
local avail=$(asterisk -rx "pjsip show endpoint ${ext}" 2>/dev/null | grep -E "Contact:.*(Avail|NonQual)" || true)
|
|
if [[ -n "$avail" ]]; then
|
|
status="Online"
|
|
status_color="${GREEN}"
|
|
fi
|
|
local password=$(grep -A 10 "^\[$ext\]" /etc/asterisk/pjsip.conf | grep "password=" | head -1 | cut -d= -f2)
|
|
local conn_color="${GREEN}"
|
|
[[ "$dev_conn" == "FQDN" ]] && conn_color="${CYAN}"
|
|
printf "%-6s %-18s %-10s ${conn_color}%-8s${NC} ${status_color}%-10s${NC} %-12s\n" "$ext" "${dev_name:0:16}" "$dev_cat" "$dev_conn" "$status" "$password"
|
|
dev_name=""
|
|
fi
|
|
fi
|
|
done < /etc/asterisk/pjsip.conf
|
|
echo ""
|
|
echo "Connection Info:"
|
|
echo " LAN/VPN: Use server IP + port 5060 (UDP)"
|
|
if [[ "$FQDN_ENABLED" == "y" && -n "$DOMAIN_NAME" ]]; then
|
|
echo " FQDN: ${DOMAIN_NAME}:5061 (TLS)"
|
|
fi
|
|
}
|
|
|
|
# ================================================================
|
|
# 5. PTT HANDLER
|
|
# ================================================================
|
|
|
|
configure_ptt_menu() {
|
|
print_header "Configure PTT Button"
|
|
|
|
if ! command -v evtest &>/dev/null; then
|
|
apt install -y evtest >/dev/null 2>&1
|
|
fi
|
|
|
|
[[ -n "$KIOSK_USER" ]] && usermod -aG input "$KIOSK_USER" 2>/dev/null || true
|
|
|
|
print_info "Scanning input devices..."
|
|
echo ""
|
|
|
|
declare -a SUGGESTED_DEVICES SUGGESTED_NAMES OTHER_DEVICES OTHER_NAMES
|
|
|
|
for dev in /dev/input/event*; do
|
|
[[ -e "$dev" ]] || continue
|
|
local name=$(cat "/sys/class/input/$(basename $dev)/device/name" 2>/dev/null || echo "Unknown")
|
|
local lname=$(echo "$name" | tr '[:upper:]' '[:lower:]')
|
|
|
|
# Skip system devices
|
|
if [[ "$lname" =~ (power.button|sleep.button|lid.switch|virtual|video.bus|hdmi|dp,pcm|hotkey|touchpad|touchscreen) ]]; then
|
|
continue
|
|
# Prioritize keyboards, USB HID devices, pedals
|
|
elif [[ "$lname" =~ (keyboard|sayo.*nano$|pedal|foot|^hid|usb) ]]; then
|
|
SUGGESTED_DEVICES+=("$dev")
|
|
SUGGESTED_NAMES+=("$name")
|
|
else
|
|
OTHER_DEVICES+=("$dev")
|
|
OTHER_NAMES+=("$name")
|
|
fi
|
|
done
|
|
|
|
# Display suggested devices first
|
|
if [[ ${#SUGGESTED_DEVICES[@]} -gt 0 ]]; then
|
|
echo -e "${GREEN}Recommended (keyboards/USB buttons):${NC}"
|
|
for i in "${!SUGGESTED_DEVICES[@]}"; do
|
|
printf " ${CYAN}%2d)${NC} %s - %s\n" "$((i+1))" "$(basename ${SUGGESTED_DEVICES[$i]})" "${SUGGESTED_NAMES[$i]}"
|
|
done
|
|
echo ""
|
|
fi
|
|
|
|
# Display other devices
|
|
if [[ ${#OTHER_DEVICES[@]} -gt 0 ]]; then
|
|
echo -e "${YELLOW}Other devices:${NC}"
|
|
local offset=${#SUGGESTED_DEVICES[@]}
|
|
for i in "${!OTHER_DEVICES[@]}"; do
|
|
printf " ${CYAN}%2d)${NC} %s - %s\n" "$((offset+i+1))" "$(basename ${OTHER_DEVICES[$i]})" "${OTHER_NAMES[$i]}"
|
|
done
|
|
echo ""
|
|
fi
|
|
|
|
local ALL_DEVICES=("${SUGGESTED_DEVICES[@]}" "${OTHER_DEVICES[@]}")
|
|
local ALL_NAMES=("${SUGGESTED_NAMES[@]}" "${OTHER_NAMES[@]}")
|
|
local total=${#ALL_DEVICES[@]}
|
|
|
|
if [[ $total -eq 0 ]]; then
|
|
print_error "No input devices found"
|
|
return 1
|
|
fi
|
|
|
|
echo " 0) Back"
|
|
echo ""
|
|
read -p "Select device [1]: " selection
|
|
selection="${selection:-1}"
|
|
|
|
[[ "$selection" == "0" ]] && return 0
|
|
[[ "$selection" -lt 1 || "$selection" -gt "$total" ]] && { print_error "Invalid selection"; return 1; }
|
|
|
|
PTT_DEVICE="${ALL_DEVICES[$((selection-1))]}"
|
|
local dev_name="${ALL_NAMES[$((selection-1))]}"
|
|
echo ""
|
|
print_success "Selected: $dev_name"
|
|
echo " ($PTT_DEVICE)"
|
|
echo ""
|
|
|
|
# Key detection loop
|
|
while true; do
|
|
echo -e "${YELLOW}═══════════════════════════════════════════════════${NC}"
|
|
echo -e "${YELLOW} DO NOT PRESS YET - wait for countdown${NC}"
|
|
echo -e "${YELLOW}═══════════════════════════════════════════════════${NC}"
|
|
|
|
for i in 5 4 3 2 1; do
|
|
echo -ne "\r Waiting... $i "
|
|
sleep 1
|
|
done
|
|
echo ""
|
|
echo ""
|
|
echo -e "${GREEN}>>> NOW PRESS YOUR PTT BUTTON <<<${NC}"
|
|
echo ""
|
|
|
|
local detected_code=$(timeout 10 evtest "$PTT_DEVICE" 2>/dev/null | grep -m1 "value 1$" | grep -oP 'code \K[0-9]+' || echo "")
|
|
|
|
if [[ -n "$detected_code" && "$detected_code" -gt 0 ]]; then
|
|
# Map common key codes to friendly names
|
|
local key_name="Key $detected_code"
|
|
case "$detected_code" in
|
|
1) key_name="Escape" ;;
|
|
28) key_name="Enter" ;;
|
|
57) key_name="Spacebar" ;;
|
|
69) key_name="Num Lock" ;;
|
|
113) key_name="Mute" ;;
|
|
114) key_name="Volume Down" ;;
|
|
115) key_name="Volume Up" ;;
|
|
116) key_name="Power" ;;
|
|
142) key_name="Sleep" ;;
|
|
272) key_name="Left Click" ;;
|
|
273) key_name="Right Click" ;;
|
|
esac
|
|
|
|
print_success "Detected: $key_name (code $detected_code)"
|
|
echo ""
|
|
read -p "Use this key? [Y/n]: " use_key
|
|
|
|
if [[ ! "$use_key" =~ ^[Nn]$ ]]; then
|
|
PTT_KEYCODE="$detected_code"
|
|
PTT_KEYNAME="$key_name"
|
|
break
|
|
fi
|
|
else
|
|
print_warn "No button press detected"
|
|
fi
|
|
|
|
echo ""
|
|
echo " 1) Try again"
|
|
echo " 2) Enter key code manually"
|
|
echo " 3) Cancel"
|
|
read -p "Select [1]: " retry
|
|
|
|
case "${retry:-1}" in
|
|
2)
|
|
read -p "Enter key code: " PTT_KEYCODE
|
|
PTT_KEYNAME="Manual"
|
|
break
|
|
;;
|
|
3)
|
|
return 1
|
|
;;
|
|
esac
|
|
done
|
|
|
|
# Ensure input group
|
|
if [[ -n "$KIOSK_USER" ]]; then
|
|
if ! id -nG "$KIOSK_USER" | grep -qw "input"; then
|
|
print_info "Adding $KIOSK_USER to input group..."
|
|
usermod -aG input "$KIOSK_USER"
|
|
echo ""
|
|
print_warn "User added to 'input' group - LOG OUT AND BACK IN for PTT to work!"
|
|
read -p "Press Enter to acknowledge..."
|
|
fi
|
|
fi
|
|
|
|
save_config
|
|
|
|
print_success "PTT configured: $PTT_KEYNAME on $(basename $PTT_DEVICE)"
|
|
echo ""
|
|
echo "═══════════════════════════════════════════════════════════════"
|
|
echo " PTT Configuration Complete"
|
|
echo "═══════════════════════════════════════════════════════════════"
|
|
echo " Device: $PTT_DEVICE"
|
|
echo " Button: $PTT_KEYNAME (code $PTT_KEYCODE)"
|
|
echo " User: ${KIOSK_USER:-not set}"
|
|
echo ""
|
|
echo " To test: journalctl -t kiosk-ptt -f"
|
|
echo "═══════════════════════════════════════════════════════════════"
|
|
|
|
# Restart PTT service
|
|
if [[ "$INSTALLED_CLIENT" == "y" && -n "$KIOSK_USER" && -n "$KIOSK_UID" ]]; then
|
|
local user_dbus="XDG_RUNTIME_DIR=/run/user/${KIOSK_UID}"
|
|
print_info "Restarting PTT service..."
|
|
sudo -u "$KIOSK_USER" $user_dbus systemctl --user daemon-reload 2>/dev/null
|
|
sudo -u "$KIOSK_USER" $user_dbus systemctl --user enable kiosk-ptt 2>/dev/null
|
|
sudo -u "$KIOSK_USER" $user_dbus systemctl --user restart kiosk-ptt 2>/dev/null || true
|
|
sleep 2
|
|
journalctl -t kiosk-ptt -n 3 --no-pager 2>/dev/null || true
|
|
fi
|
|
|
|
return 0
|
|
}
|
|
|
|
create_ptt_handler() {
|
|
cat > /usr/local/bin/kiosk-ptt << 'PTTSCRIPT'
|
|
#!/bin/bash
|
|
CONFIG="/etc/easy-asterisk/config"
|
|
PTT_CONFIG="/etc/easy-asterisk/ptt-device"
|
|
[[ -f "$CONFIG" ]] && source "$CONFIG"
|
|
[[ -f "$PTT_CONFIG" ]] && source "$PTT_CONFIG"
|
|
|
|
[[ -z "$PTT_DEVICE" ]] && exit 0
|
|
|
|
[[ -z "$XDG_RUNTIME_DIR" ]] && export XDG_RUNTIME_DIR="/run/user/${KIOSK_UID:-$(id -u)}"
|
|
|
|
# Wait for audio
|
|
for i in {1..30}; do
|
|
pactl info >/dev/null 2>&1 && pactl list sources short | grep -q . && break
|
|
sleep 1
|
|
done
|
|
|
|
# Mute on start
|
|
pactl set-source-mute @DEFAULT_SOURCE@ 1 2>/dev/null
|
|
logger -t kiosk-ptt "PTT started, mic muted"
|
|
|
|
evtest --grab "$PTT_DEVICE" 2>/dev/null | while read -r line; do
|
|
if [[ "$line" =~ "value 1" ]]; then
|
|
pactl set-source-mute @DEFAULT_SOURCE@ 0 2>/dev/null
|
|
logger -t kiosk-ptt "PTT pressed - unmuted"
|
|
fi
|
|
if [[ "$line" =~ "value 0" ]]; then
|
|
pactl set-source-mute @DEFAULT_SOURCE@ 1 2>/dev/null
|
|
logger -t kiosk-ptt "PTT released - muted"
|
|
fi
|
|
done
|
|
PTTSCRIPT
|
|
chmod +x /usr/local/bin/kiosk-ptt
|
|
}
|
|
|
|
# ================================================================
|
|
# 6. AUDIO HELPERS
|
|
# ================================================================
|
|
|
|
configure_audio_ducking() {
|
|
[[ -z "$KIOSK_USER" ]] && return
|
|
local wp_dir="/home/${KIOSK_USER}/.config/wireplumber/wireplumber.conf.d"
|
|
mkdir -p "$wp_dir"
|
|
cat > "${wp_dir}/50-intercom-ducking.conf" << 'EOF'
|
|
wireplumber.settings = { linking.allow-moving-streams = true }
|
|
EOF
|
|
chown -R ${KIOSK_USER}:${KIOSK_USER} "/home/${KIOSK_USER}/.config"
|
|
}
|
|
|
|
ensure_audio_unmuted() {
|
|
[[ -z "$KIOSK_USER" || -z "$KIOSK_UID" ]] && return
|
|
[[ -f /etc/easy-asterisk/ptt-device ]] && return # PTT manages muting
|
|
|
|
local user_dbus="XDG_RUNTIME_DIR=/run/user/${KIOSK_UID}"
|
|
sleep 2
|
|
sudo -u "$KIOSK_USER" $user_dbus pactl set-source-mute @DEFAULT_SOURCE@ 0 2>/dev/null || true
|
|
sudo -u "$KIOSK_USER" $user_dbus pactl set-sink-mute @DEFAULT_SINK@ 0 2>/dev/null || true
|
|
}
|
|
|
|
fix_audio_manually() {
|
|
print_header "Fix Audio"
|
|
load_config
|
|
local t_user="${KIOSK_USER:-$SUDO_USER}"
|
|
local t_uid=$(id -u "$t_user" 2>/dev/null)
|
|
local user_dbus="XDG_RUNTIME_DIR=/run/user/$t_uid"
|
|
|
|
echo "Fixing audio for: $t_user"
|
|
sudo -u "$t_user" $user_dbus systemctl --user restart pipewire pipewire-pulse 2>/dev/null || true
|
|
sleep 2
|
|
sudo -u "$t_user" $user_dbus pactl set-source-mute @DEFAULT_SOURCE@ 0 2>/dev/null
|
|
sudo -u "$t_user" $user_dbus pactl set-sink-mute @DEFAULT_SINK@ 0 2>/dev/null
|
|
sudo -u "$t_user" $user_dbus pactl set-source-volume @DEFAULT_SOURCE@ 75% 2>/dev/null
|
|
sudo -u "$t_user" $user_dbus pactl set-sink-volume @DEFAULT_SINK@ 75% 2>/dev/null
|
|
sudo -u "$t_user" $user_dbus systemctl --user restart baresip 2>/dev/null
|
|
print_success "Audio fixed"
|
|
}
|
|
|
|
# ================================================================
|
|
# 7. DIAGNOSTICS
|
|
# ================================================================
|
|
|
|
show_port_requirements() {
|
|
print_header "Port Requirements"
|
|
echo "═══════════════════════════════════════════════════════════════"
|
|
echo " LAN/VPN CLIENTS - No port forwarding needed"
|
|
echo "═══════════════════════════════════════════════════════════════"
|
|
echo " │ 5060/UDP │ SIP Signaling │"
|
|
echo " │ 10000-20000/UDP│ RTP Audio │"
|
|
echo ""
|
|
echo "═══════════════════════════════════════════════════════════════"
|
|
echo " FQDN CLIENTS - Forward these on WAN"
|
|
echo "═══════════════════════════════════════════════════════════════"
|
|
echo " │ 5061/TCP │ SIP-TLS Signaling │"
|
|
echo " │ 10000-20000/UDP│ RTP Audio │"
|
|
echo ""
|
|
}
|
|
|
|
test_sip_connectivity() {
|
|
print_header "SIP Test"
|
|
if systemctl is-active asterisk >/dev/null; then
|
|
print_success "Asterisk running"
|
|
else
|
|
print_error "Asterisk stopped"
|
|
fi
|
|
echo ""
|
|
echo "Listening:"
|
|
ss -ulnp | grep 5060 || echo " 5060/UDP: Not listening"
|
|
ss -tlnp | grep 5061 || echo " 5061/TCP: Not listening"
|
|
}
|
|
|
|
run_client_diagnostics() {
|
|
print_header "Client Diagnostics"
|
|
load_config
|
|
local t_user="${KIOSK_USER:-$SUDO_USER}"
|
|
local t_uid=$(id -u "$t_user" 2>/dev/null)
|
|
local user_dbus="XDG_RUNTIME_DIR=/run/user/$t_uid"
|
|
|
|
echo "User: $t_user"
|
|
echo ""
|
|
|
|
echo "Services:"
|
|
sudo -u "$t_user" $user_dbus systemctl --user is-active baresip >/dev/null 2>&1 && print_success "Baresip running" || print_error "Baresip stopped"
|
|
sudo -u "$t_user" $user_dbus systemctl --user is-active pipewire >/dev/null 2>&1 && print_success "PipeWire running" || print_error "PipeWire stopped"
|
|
|
|
echo ""
|
|
echo "Audio:"
|
|
local src_mute=$(sudo -u "$t_user" $user_dbus pactl get-source-mute @DEFAULT_SOURCE@ 2>/dev/null | awk '{print $2}')
|
|
echo " Microphone muted: ${src_mute:-unknown}"
|
|
|
|
echo ""
|
|
echo "Account:"
|
|
cat "/home/$t_user/.baresip/accounts" 2>/dev/null | sed 's/auth_pass=[^;]*/auth_pass=***/' || echo " Not found"
|
|
}
|
|
|
|
# ================================================================
|
|
# 8. ASTERISK CONFIG
|
|
# ================================================================
|
|
|
|
fix_asterisk_systemd() {
|
|
mkdir -p /etc/systemd/system/asterisk.service.d/
|
|
cat > /etc/systemd/system/asterisk.service.d/override.conf << 'EOF'
|
|
[Unit]
|
|
After=network-online.target
|
|
Wants=network-online.target
|
|
|
|
[Service]
|
|
ExecStart=
|
|
ExecStart=/usr/sbin/asterisk -f -U asterisk -G asterisk
|
|
Restart=always
|
|
RestartSec=10
|
|
EOF
|
|
systemctl daemon-reload
|
|
}
|
|
|
|
repair_core_configs() {
|
|
mkdir -p /etc/asterisk /var/lib/asterisk /var/log/asterisk /var/spool/asterisk /var/run/asterisk
|
|
|
|
if [[ -d "/usr/lib/x86_64-linux-gnu/asterisk/modules" ]]; then
|
|
mkdir -p /usr/lib/asterisk/modules
|
|
cp -rn /usr/lib/x86_64-linux-gnu/asterisk/modules/* /usr/lib/asterisk/modules/ 2>/dev/null || true
|
|
fi
|
|
|
|
cat > /etc/asterisk/asterisk.conf << EOF
|
|
[directories]
|
|
astetcdir => /etc/asterisk
|
|
astmoddir => /usr/lib/asterisk/modules
|
|
astvarlibdir => /var/lib/asterisk
|
|
astdbdir => /var/lib/asterisk
|
|
astkeydir => /var/lib/asterisk
|
|
astdatadir => /var/lib/asterisk
|
|
astagidir => /var/lib/asterisk/agi-bin
|
|
astspooldir => /var/spool/asterisk
|
|
astrundir => /var/run/asterisk
|
|
astlogdir => /var/log/asterisk
|
|
EOF
|
|
|
|
cat > /etc/asterisk/modules.conf << EOF
|
|
[modules]
|
|
autoload=yes
|
|
noload => chan_sip.so
|
|
noload => chan_iax2.so
|
|
load => res_pjsip.so
|
|
load => res_pjsip_session.so
|
|
load => chan_pjsip.so
|
|
load => codec_ulaw.so
|
|
load => codec_alaw.so
|
|
load => codec_g722.so
|
|
load => codec_opus.so
|
|
load => res_rtp_asterisk.so
|
|
load => app_dial.so
|
|
load => app_page.so
|
|
load => pbx_config.so
|
|
EOF
|
|
|
|
for conf in stasis ari http manager geolocation; do
|
|
echo -e "[general]\nenabled = no" > "/etc/asterisk/${conf}.conf"
|
|
done
|
|
|
|
load_config
|
|
local ice_config="# ICE disabled - LAN only"
|
|
if [[ "$FQDN_ENABLED" == "y" ]]; then
|
|
ice_config="icesupport=yes
|
|
stunaddr=stun.l.google.com:19302"
|
|
fi
|
|
|
|
cat > /etc/asterisk/rtp.conf << EOF
|
|
[general]
|
|
rtpstart=10000
|
|
rtpend=20000
|
|
strictrtp=yes
|
|
${ice_config}
|
|
EOF
|
|
|
|
cat > /etc/asterisk/logger.conf << EOF
|
|
[general]
|
|
[logfiles]
|
|
console => notice,warning,error
|
|
EOF
|
|
|
|
chown -R asterisk:asterisk /etc/asterisk /var/lib/asterisk /var/log/asterisk /var/spool/asterisk 2>/dev/null || true
|
|
}
|
|
|
|
generate_pjsip_conf() {
|
|
print_info "Generating PJSIP config..."
|
|
load_config
|
|
local conf_file="/etc/asterisk/pjsip.conf"
|
|
backup_config "$conf_file"
|
|
|
|
# NAT settings for FQDN mode or VLAN/NAT traversal
|
|
local nat_settings=""
|
|
if [[ "$FQDN_ENABLED" == "y" && -n "$CURRENT_PUBLIC_IP" ]]; then
|
|
local local_net="${LOCAL_CIDR:-$(ip -o -f inet addr show | awk '/scope global/ {print $4}' | head -1)}"
|
|
nat_settings="external_media_address=$CURRENT_PUBLIC_IP
|
|
external_signaling_address=$CURRENT_PUBLIC_IP
|
|
local_net=$local_net"
|
|
elif [[ "$VLAN_NAT_TRAVERSAL" == "y" ]]; then
|
|
# VLAN/NAT traversal without FQDN - configure local networks
|
|
local local_net="${LOCAL_CIDR:-$(ip -o -f inet addr show | awk '/scope global/ {print $4}' | head -1)}"
|
|
nat_settings="local_net=$local_net"
|
|
|
|
# Add user-configured VLAN subnets
|
|
if [[ -n "$VLAN_SUBNETS" ]]; then
|
|
IFS=',' read -ra SUBNETS <<< "$VLAN_SUBNETS"
|
|
for subnet in "${SUBNETS[@]}"; do
|
|
subnet=$(echo "$subnet" | xargs) # trim whitespace
|
|
[[ -n "$subnet" ]] && nat_settings="${nat_settings}
|
|
local_net=$subnet"
|
|
done
|
|
fi
|
|
fi
|
|
|
|
cat > "$conf_file" << EOF
|
|
; Easy Asterisk v${SCRIPT_VERSION}
|
|
[global]
|
|
type=global
|
|
user_agent=EasyAsterisk
|
|
|
|
[transport-udp]
|
|
type=transport
|
|
protocol=udp
|
|
bind=0.0.0.0:${DEFAULT_SIP_PORT}
|
|
${nat_settings}
|
|
|
|
[transport-tls]
|
|
type=transport
|
|
protocol=tls
|
|
bind=0.0.0.0:${DEFAULT_SIPS_PORT}
|
|
cert_file=/etc/asterisk/certs/server.crt
|
|
priv_key_file=/etc/asterisk/certs/server.key
|
|
ca_list_file=/etc/ssl/certs/ca-certificates.crt
|
|
method=tlsv1_2
|
|
${nat_settings}
|
|
|
|
EOF
|
|
|
|
# Restore devices from backup
|
|
local backup_file=$(ls -t "${conf_file}.backup-"* 2>/dev/null | head -1)
|
|
if [[ -f "$backup_file" ]]; then
|
|
awk '/^; === Device:/{flag=1} flag' "$backup_file" >> "$conf_file"
|
|
fi
|
|
chown asterisk:asterisk "$conf_file"
|
|
}
|
|
|
|
rebuild_dialplan() {
|
|
local conf_file="/etc/asterisk/extensions.conf"
|
|
backup_config "$conf_file"
|
|
|
|
cat > "$conf_file" << EOF
|
|
[general]
|
|
static=yes
|
|
writeprotect=no
|
|
[default]
|
|
exten => _X.,1,Hangup()
|
|
[intercom]
|
|
EOF
|
|
|
|
local dev_name="" dev_auto=""
|
|
while IFS= read -r line; do
|
|
if [[ "$line" == *"; === Device:"* ]]; then
|
|
local temp="${line#*; === Device: }"
|
|
temp="${temp% ===}"
|
|
local aa_override=""
|
|
[[ "$temp" == *"[AA:yes]"* ]] && aa_override="yes"
|
|
[[ "$temp" == *"[AA:no]"* ]] && aa_override="no"
|
|
temp=$(echo "$temp" | sed 's/ \[CONN:[^]]*\]//' | sed 's/ \[AA:[^]]*\]//')
|
|
local dev_cat="${temp##* (}"; dev_cat="${dev_cat%)}"
|
|
dev_name="${temp% (*)}"
|
|
dev_auto="no"
|
|
local cat_data=$(grep "^${dev_cat}|" "$CATEGORIES_FILE" 2>/dev/null || true)
|
|
[[ -n "$cat_data" && "$(echo "$cat_data" | cut -d'|' -f3)" == "yes" ]] && dev_auto="yes"
|
|
[[ "$aa_override" == "yes" ]] && dev_auto="yes"
|
|
[[ "$aa_override" == "no" ]] && dev_auto="no"
|
|
fi
|
|
if [[ "$line" =~ ^\[([0-9]+)\] && -n "$dev_name" ]]; then
|
|
local ext="${BASH_REMATCH[1]}"
|
|
if [[ "$dev_auto" == "yes" ]]; then
|
|
cat >> "$conf_file" << EOF
|
|
exten => ${ext},1,NoOp(Auto-Answer ${ext})
|
|
same => n,Set(PJSIP_HEADER(add,Call-Info)=\;answer-after=0)
|
|
same => n,Set(PJSIP_HEADER(add,Alert-Info)=auto-answer)
|
|
same => n,Dial(PJSIP/${ext},60)
|
|
same => n,Hangup()
|
|
|
|
EOF
|
|
else
|
|
cat >> "$conf_file" << EOF
|
|
exten => ${ext},1,NoOp(Call ${ext})
|
|
same => n,Dial(PJSIP/${ext},60)
|
|
same => n,Hangup()
|
|
|
|
EOF
|
|
fi
|
|
dev_name=""
|
|
fi
|
|
done < /etc/asterisk/pjsip.conf
|
|
|
|
# Add rooms
|
|
if [[ -f "$ROOMS_FILE" ]]; then
|
|
while IFS='|' read -r rext rname rmem rtime rtype; do
|
|
[[ "$rext" =~ ^# || -z "$rext" ]] && continue
|
|
local dial_list=""
|
|
IFS=',' read -ra EXTS <<< "$rmem"
|
|
for e in "${EXTS[@]}"; do
|
|
e=$(echo "$e" | tr -d ' ')
|
|
[[ -n "$dial_list" ]] && dial_list="${dial_list}&"
|
|
dial_list="${dial_list}PJSIP/${e}"
|
|
done
|
|
if [[ "$rtype" == "page" ]]; then
|
|
cat >> "$conf_file" << EOF
|
|
exten => ${rext},1,NoOp(Page ${rname})
|
|
same => n,Set(PJSIP_HEADER(add,Call-Info)=\;answer-after=0)
|
|
same => n,Page(${dial_list},i,${rtime})
|
|
same => n,Hangup()
|
|
|
|
EOF
|
|
else
|
|
cat >> "$conf_file" << EOF
|
|
exten => ${rext},1,NoOp(Ring ${rname})
|
|
same => n,Dial(${dial_list},${rtime})
|
|
same => n,Hangup()
|
|
|
|
EOF
|
|
fi
|
|
done < "$ROOMS_FILE"
|
|
fi
|
|
|
|
chown -R asterisk:asterisk /etc/asterisk
|
|
asterisk -rx "dialplan reload" &>/dev/null || true
|
|
}
|
|
|
|
configure_asterisk() {
|
|
id asterisk >/dev/null 2>&1 || useradd -r -s /bin/false -d /var/lib/asterisk asterisk 2>/dev/null
|
|
|
|
fix_asterisk_systemd
|
|
initialize_default_categories
|
|
repair_core_configs
|
|
|
|
# Create default cert if none exists
|
|
mkdir -p /etc/asterisk/certs
|
|
if [[ ! -f /etc/asterisk/certs/server.crt ]]; then
|
|
openssl req -x509 -nodes -days 3650 -newkey rsa:2048 \
|
|
-keyout /etc/asterisk/certs/server.key \
|
|
-out /etc/asterisk/certs/server.crt \
|
|
-subj "/CN=asterisk-local" 2>/dev/null
|
|
chown asterisk:asterisk /etc/asterisk/certs/server.*
|
|
chmod 644 /etc/asterisk/certs/server.crt
|
|
chmod 600 /etc/asterisk/certs/server.key
|
|
fi
|
|
|
|
generate_pjsip_conf
|
|
rebuild_dialplan
|
|
restart_asterisk_safe
|
|
systemctl enable asterisk
|
|
}
|
|
|
|
restart_asterisk_safe() {
|
|
print_info "Restarting Asterisk..."
|
|
systemctl stop asterisk 2>/dev/null || true
|
|
sleep 2
|
|
pkill -9 -x asterisk 2>/dev/null || true
|
|
rm -f /var/run/asterisk/asterisk.pid 2>/dev/null
|
|
systemctl start asterisk
|
|
sleep 3
|
|
|
|
if systemctl is-active asterisk >/dev/null; then
|
|
print_success "Asterisk running"
|
|
else
|
|
print_error "Asterisk failed to start"
|
|
echo ""
|
|
echo "Recent log entries:"
|
|
journalctl -u asterisk -n 20 --no-pager
|
|
echo ""
|
|
echo "Checking configuration files..."
|
|
|
|
# Check if pjsip.conf has syntax errors
|
|
if asterisk -rx "pjsip show version" &>/dev/null; then
|
|
echo "PJSIP module OK"
|
|
else
|
|
echo "PJSIP may have configuration errors"
|
|
fi
|
|
|
|
# Check certificate files
|
|
if [[ -f /etc/asterisk/certs/server.crt ]]; then
|
|
echo "Certificate file exists"
|
|
else
|
|
echo "WARNING: Missing /etc/asterisk/certs/server.crt"
|
|
fi
|
|
|
|
if [[ -f /etc/asterisk/certs/server.key ]]; then
|
|
echo "Private key exists"
|
|
else
|
|
echo "WARNING: Missing /etc/asterisk/certs/server.key"
|
|
fi
|
|
|
|
echo ""
|
|
echo "To debug further, run: sudo asterisk -cvvv"
|
|
echo ""
|
|
fi
|
|
}
|
|
|
|
# ================================================================
|
|
# 9. CLIENT CONFIG
|
|
# ================================================================
|
|
|
|
configure_baresip() {
|
|
local baresip_dir="/home/${KIOSK_USER}/.baresip"
|
|
mkdir -p "$baresip_dir"
|
|
|
|
local found_iface=""
|
|
for target in 8.8.8.8 1.1.1.1; do
|
|
found_iface=$(ip route get "$target" 2>/dev/null | awk '{for(i=1;i<=NF;i++) if($i=="dev") print $(i+1)}' | head -1)
|
|
[[ -n "$found_iface" ]] && break
|
|
done
|
|
|
|
cat > "${baresip_dir}/config" << EOF
|
|
poll_method epoll
|
|
audio_player pulse
|
|
audio_source pulse
|
|
audio_alert pulse
|
|
sip_autoanswer yes
|
|
sip_cafile /etc/ssl/certs/ca-certificates.crt
|
|
rtp_timeout 0
|
|
net_af ipv4
|
|
module_path /usr/lib/baresip/modules
|
|
module srtp.so
|
|
module stdio.so
|
|
module pulse.so
|
|
module g711.so
|
|
module opus.so
|
|
module account.so
|
|
module stun.so
|
|
module ice.so
|
|
module turn.so
|
|
EOF
|
|
|
|
[[ -n "$found_iface" ]] && echo "net_interface $found_iface" >> "${baresip_dir}/config"
|
|
|
|
local transport="udp"
|
|
local mediaenc=""
|
|
# Check if connecting to FQDN (contains letters)
|
|
if [[ "$ASTERISK_HOST" =~ [a-zA-Z] ]]; then
|
|
transport="tls"
|
|
mediaenc=";mediaenc=srtp"
|
|
fi
|
|
|
|
local amode="${CLIENT_ANSWERMODE:-auto}"
|
|
|
|
cat > "${baresip_dir}/accounts" << EOF
|
|
<sip:${KIOSK_EXTENSION}@${ASTERISK_HOST};transport=${transport}>;auth_pass=${SIP_PASSWORD};answermode=${amode}${mediaenc}
|
|
EOF
|
|
chown -R ${KIOSK_USER}:${KIOSK_USER} "$baresip_dir"
|
|
chmod 700 "$baresip_dir"
|
|
|
|
configure_audio_ducking
|
|
create_ptt_handler
|
|
create_baresip_launcher
|
|
}
|
|
|
|
create_baresip_launcher() {
|
|
cat > /usr/local/bin/easy-asterisk-launcher << LAUNCHER
|
|
#!/bin/bash
|
|
logger -t baresip-launcher "Starting"
|
|
for i in {1..6}; do
|
|
IFACE=\$(ip route get 8.8.8.8 2>/dev/null | awk '{for(i=1;i<=NF;i++) if(\$i=="dev") print \$(i+1)}' | head -1)
|
|
[[ -n "\$IFACE" ]] && break
|
|
sleep 5
|
|
done
|
|
exec /usr/bin/baresip -f "/home/${KIOSK_USER}/.baresip"
|
|
LAUNCHER
|
|
chmod +x /usr/local/bin/easy-asterisk-launcher
|
|
}
|
|
|
|
enable_client_services() {
|
|
local systemd_dir="/home/${KIOSK_USER}/.config/systemd/user"
|
|
mkdir -p "$systemd_dir"
|
|
|
|
usermod -aG audio "$KIOSK_USER" 2>/dev/null || true
|
|
usermod -aG input "$KIOSK_USER" 2>/dev/null || true
|
|
|
|
cat > "${systemd_dir}/baresip.service" << EOF
|
|
[Unit]
|
|
Description=Baresip SIP Client
|
|
After=pipewire-pulse.service
|
|
Requires=pipewire-pulse.service
|
|
|
|
[Service]
|
|
ExecStartPre=/bin/sleep 5
|
|
ExecStart=/usr/local/bin/easy-asterisk-launcher
|
|
Restart=always
|
|
RestartSec=10
|
|
Environment=XDG_RUNTIME_DIR=/run/user/${KIOSK_UID}
|
|
Environment=DBUS_SESSION_BUS_ADDRESS=unix:path=/run/user/${KIOSK_UID}/bus
|
|
|
|
[Install]
|
|
WantedBy=default.target
|
|
EOF
|
|
|
|
cat > "${systemd_dir}/kiosk-ptt.service" << EOF
|
|
[Unit]
|
|
Description=PTT Handler
|
|
After=baresip.service
|
|
ConditionPathExists=/etc/easy-asterisk/ptt-device
|
|
|
|
[Service]
|
|
ExecStartPre=/bin/sleep 8
|
|
ExecStart=/usr/local/bin/kiosk-ptt
|
|
Restart=always
|
|
RestartSec=10
|
|
Environment=XDG_RUNTIME_DIR=/run/user/${KIOSK_UID}
|
|
Environment=KIOSK_UID=${KIOSK_UID}
|
|
|
|
[Install]
|
|
WantedBy=default.target
|
|
EOF
|
|
|
|
chown -R ${KIOSK_USER}:${KIOSK_USER} "/home/${KIOSK_USER}/.config"
|
|
|
|
loginctl enable-linger $KIOSK_USER 2>/dev/null || true
|
|
local user_dbus="XDG_RUNTIME_DIR=/run/user/${KIOSK_UID}"
|
|
|
|
sudo -u "$KIOSK_USER" $user_dbus systemctl --user daemon-reload
|
|
sudo -u "$KIOSK_USER" $user_dbus systemctl --user enable pipewire pipewire-pulse baresip 2>/dev/null || true
|
|
sudo -u "$KIOSK_USER" $user_dbus systemctl --user restart pipewire pipewire-pulse 2>/dev/null || true
|
|
sleep 2
|
|
sudo -u "$KIOSK_USER" $user_dbus systemctl --user restart baresip 2>/dev/null || true
|
|
|
|
[[ -f /etc/easy-asterisk/ptt-device ]] && sudo -u "$KIOSK_USER" $user_dbus systemctl --user enable kiosk-ptt 2>/dev/null
|
|
|
|
ensure_audio_unmuted
|
|
}
|
|
|
|
configure_local_client() {
|
|
print_header "Configure Local Client"
|
|
load_config
|
|
|
|
if [[ -n "$KIOSK_USER" ]]; then
|
|
echo "Current user: $KIOSK_USER"
|
|
read -p "Change? [y/N]: " change
|
|
[[ "$change" =~ ^[Yy]$ ]] && KIOSK_USER="" && KIOSK_UID=""
|
|
fi
|
|
|
|
if [[ -z "$KIOSK_USER" ]]; then
|
|
select_user || return 1
|
|
else
|
|
KIOSK_UID=$(id -u "$KIOSK_USER" 2>/dev/null)
|
|
fi
|
|
|
|
if [[ ! -d "/home/${KIOSK_USER}/.baresip" ]]; then
|
|
read -p "Baresip not installed. Install now? [Y/n]: " install_it
|
|
if [[ ! "$install_it" =~ ^[Nn]$ ]]; then
|
|
install_baresip_packages
|
|
INSTALLED_CLIENT="y"
|
|
else
|
|
return
|
|
fi
|
|
fi
|
|
|
|
echo ""
|
|
echo " How will this client connect?"
|
|
echo " 1) LAN/VPN (via IP)"
|
|
echo " 2) FQDN (via domain)"
|
|
read -p " Select [1]: " conn_type
|
|
|
|
local server=""
|
|
if [[ "$conn_type" == "2" ]]; then
|
|
read -p " Domain: " server
|
|
else
|
|
read -p " Server IP (LAN or VPN IP of server): " server
|
|
fi
|
|
|
|
read -p "Extension: " ext
|
|
read -p "Password: " pass
|
|
|
|
echo "Answer mode: 1) Manual 2) Auto"
|
|
read -p "Select [2]: " amode
|
|
local answermode="auto"
|
|
[[ "$amode" == "1" ]] && answermode="manual"
|
|
|
|
ASTERISK_HOST="$server"
|
|
KIOSK_EXTENSION="$ext"
|
|
SIP_PASSWORD="$pass"
|
|
CLIENT_ANSWERMODE="$answermode"
|
|
|
|
configure_baresip
|
|
enable_client_services
|
|
save_config
|
|
|
|
print_success "Client configured"
|
|
}
|
|
|
|
# ================================================================
|
|
# 10. CERTIFICATE SYNC
|
|
# ================================================================
|
|
|
|
check_cert_coverage() {
|
|
local cert_file=$1 target_domain=$2 base_domain=$3
|
|
[[ ! -f "$cert_file" ]] && return 1
|
|
local sans=$(openssl x509 -in "$cert_file" -text -noout 2>/dev/null | grep -A1 "Subject Alternative Name" | tail -1)
|
|
echo "$sans" | grep -q "DNS:${target_domain}" && return 0
|
|
echo "$sans" | grep -q "DNS:\*.${base_domain}" && return 0
|
|
return 1
|
|
}
|
|
|
|
setup_caddy_cert_sync() {
|
|
local mode=$1
|
|
load_config
|
|
local domain=${DOMAIN_NAME:-sip.example.com}
|
|
|
|
if [[ "$mode" == "force" ]]; then
|
|
print_header "Caddy Cert Sync"
|
|
read -p "Domain [$domain]: " input_domain
|
|
domain="${input_domain:-$domain}"
|
|
fi
|
|
|
|
local actual_user="${SUDO_USER:-$USER}"
|
|
local actual_home=$(eval echo ~"$actual_user")
|
|
local base_domain=$(echo "$domain" | awk -F. '{print $(NF-1)"."$NF}')
|
|
|
|
local search_paths=(
|
|
"${actual_home}/docker/caddy/ssl"
|
|
"${actual_home}/docker/caddy/caddy_data"
|
|
"${actual_home}/docker/caddy/caddy_data/caddy/certificates/acme-v02.api.letsencrypt.org-directory"
|
|
"/var/lib/caddy"
|
|
"/var/lib/caddy/.local/share/caddy/certificates/acme-v02.api.letsencrypt.org-directory"
|
|
)
|
|
local caddy_cert="" caddy_key=""
|
|
|
|
for base_path in "${search_paths[@]}"; do
|
|
sudo test -d "$base_path" 2>/dev/null || continue
|
|
|
|
local candidates=$(sudo find "$base_path" -maxdepth 5 -type f \( -name "fullchain.pem" -o -name "*.crt" \) 2>/dev/null)
|
|
|
|
for cert in $candidates; do
|
|
sudo cp "$cert" /tmp/cert_check.pem 2>/dev/null || continue
|
|
if check_cert_coverage "/tmp/cert_check.pem" "$domain" "$base_domain"; then
|
|
caddy_cert="$cert"
|
|
local dir=$(dirname "$cert")
|
|
local name=$(basename "$cert")
|
|
[[ "$name" == "fullchain.pem" ]] && caddy_key="${dir}/privkey.pem" || caddy_key=$(echo "$cert" | sed 's/\.crt/\.key/')
|
|
sudo test -f "$caddy_key" && rm -f /tmp/cert_check.pem && break 2
|
|
fi
|
|
rm -f /tmp/cert_check.pem
|
|
done
|
|
done
|
|
|
|
if [[ -n "$caddy_cert" && -n "$caddy_key" ]]; then
|
|
mkdir -p /etc/asterisk/certs
|
|
sudo cat "$caddy_cert" > /etc/asterisk/certs/server.crt
|
|
sudo cat "$caddy_key" > /etc/asterisk/certs/server.key
|
|
chown asterisk:asterisk /etc/asterisk/certs/server.*
|
|
chmod 644 /etc/asterisk/certs/server.crt
|
|
chmod 600 /etc/asterisk/certs/server.key
|
|
|
|
DOMAIN_NAME="$domain"
|
|
FQDN_ENABLED="y"
|
|
save_config
|
|
|
|
[[ "$INSTALLED_SERVER" == "y" ]] && generate_pjsip_conf && restart_asterisk_safe
|
|
|
|
print_success "Certificates installed for $domain"
|
|
return 0
|
|
else
|
|
[[ "$mode" == "force" ]] && print_warn "No matching certificates found"
|
|
return 1
|
|
fi
|
|
}
|
|
|
|
# ================================================================
|
|
# 11. INSTALLATION
|
|
# ================================================================
|
|
|
|
install_full() {
|
|
print_header "Full Installation"
|
|
|
|
local default_user="${SUDO_USER:-$USER}"
|
|
read -p "Client User [$default_user]: " target_user
|
|
KIOSK_USER="${target_user:-$default_user}"
|
|
KIOSK_UID=$(id -u "$KIOSK_USER")
|
|
|
|
SIP_PASSWORD=$(generate_password)
|
|
read -p "Extension [101]: " KIOSK_EXTENSION
|
|
KIOSK_EXTENSION="${KIOSK_EXTENSION:-101}"
|
|
KIOSK_NAME="kiosk-${KIOSK_EXTENSION}"
|
|
|
|
SERVER_LAN_IP=$(hostname -I | awk '{print $1}')
|
|
ASTERISK_HOST="$SERVER_LAN_IP"
|
|
FQDN_ENABLED="n"
|
|
|
|
install_asterisk_packages
|
|
install_baresip_packages
|
|
|
|
INSTALLED_SERVER="y"
|
|
INSTALLED_CLIENT="y"
|
|
|
|
configure_asterisk
|
|
configure_baresip
|
|
enable_client_services
|
|
open_firewall_ports
|
|
save_config
|
|
|
|
echo ""
|
|
echo "═══════════════════════════════════════════════════════════════"
|
|
print_success "Installation complete"
|
|
echo ""
|
|
echo " Server: ${SERVER_LAN_IP}:5060 (UDP)"
|
|
echo " Extension: ${KIOSK_EXTENSION}"
|
|
echo " Password: ${SIP_PASSWORD}"
|
|
echo ""
|
|
echo " LAN/VPN clients can connect now."
|
|
echo "═══════════════════════════════════════════════════════════════"
|
|
echo ""
|
|
read -p "Will any clients connect via FQDN (internet/cross-VLAN)? [y/N]: " setup_fqdn
|
|
[[ "$setup_fqdn" =~ ^[Yy]$ ]] && setup_fqdn_access
|
|
}
|
|
|
|
install_server_only() {
|
|
print_header "Server Installation"
|
|
|
|
SERVER_LAN_IP=$(hostname -I | awk '{print $1}')
|
|
ASTERISK_HOST="$SERVER_LAN_IP"
|
|
FQDN_ENABLED="n"
|
|
|
|
install_asterisk_packages
|
|
configure_asterisk
|
|
open_firewall_ports
|
|
|
|
INSTALLED_SERVER="y"
|
|
save_config
|
|
|
|
echo ""
|
|
print_success "Server installed: ${SERVER_LAN_IP}:5060"
|
|
echo ""
|
|
read -p "Will any clients connect via FQDN? [y/N]: " setup_fqdn
|
|
[[ "$setup_fqdn" =~ ^[Yy]$ ]] && setup_fqdn_access
|
|
}
|
|
|
|
install_client_only() {
|
|
print_header "Client Installation"
|
|
|
|
select_user || return 1
|
|
|
|
echo ""
|
|
echo " How will this client connect?"
|
|
echo " 1) LAN/VPN (via IP)"
|
|
echo " 2) FQDN (via domain)"
|
|
read -p " Select [1]: " conn_type
|
|
|
|
if [[ "$conn_type" == "2" ]]; then
|
|
read -p " Server Domain: " ASTERISK_HOST
|
|
else
|
|
read -p " Server IP (LAN or VPN IP of server): " ASTERISK_HOST
|
|
fi
|
|
|
|
read -p " Extension [101]: " KIOSK_EXTENSION
|
|
KIOSK_EXTENSION="${KIOSK_EXTENSION:-101}"
|
|
read -p " Password: " SIP_PASSWORD
|
|
|
|
echo " Answer mode: 1) Auto 2) Manual"
|
|
read -p " Select [1]: " aa_sel
|
|
CLIENT_ANSWERMODE="auto"
|
|
[[ "$aa_sel" == "2" ]] && CLIENT_ANSWERMODE="manual"
|
|
|
|
install_baresip_packages
|
|
INSTALLED_CLIENT="y"
|
|
configure_baresip
|
|
enable_client_services
|
|
save_config
|
|
|
|
print_success "Client installed"
|
|
}
|
|
|
|
install_asterisk_packages() {
|
|
echo "exit 101" > /usr/sbin/policy-rc.d
|
|
chmod +x /usr/sbin/policy-rc.d
|
|
apt update
|
|
apt install -y asterisk asterisk-core-sounds-en-gsm asterisk-modules openssl curl tcpdump || true
|
|
rm -f /usr/sbin/policy-rc.d
|
|
fix_asterisk_systemd
|
|
}
|
|
|
|
install_baresip_packages() {
|
|
apt update
|
|
apt install -y baresip baresip-core pipewire pipewire-alsa pipewire-pulse wireplumber alsa-utils evtest || true
|
|
}
|
|
|
|
uninstall_menu() {
|
|
print_header "Uninstall"
|
|
load_config
|
|
|
|
# Build menu based on what's installed
|
|
local has_server=$([[ "$INSTALLED_SERVER" == "y" ]] && echo "y" || echo "n")
|
|
local has_client=$([[ "$INSTALLED_CLIENT" == "y" ]] && echo "y" || echo "n")
|
|
|
|
if [[ "$has_server" == "y" && "$has_client" == "y" ]]; then
|
|
echo " 1) Remove Everything"
|
|
echo " 2) Asterisk Only"
|
|
echo " 3) Baresip Only"
|
|
elif [[ "$has_server" == "y" ]]; then
|
|
echo " 1) Remove Asterisk"
|
|
elif [[ "$has_client" == "y" ]]; then
|
|
echo " 1) Remove Baresip"
|
|
else
|
|
print_warn "Nothing installed"
|
|
return
|
|
fi
|
|
|
|
echo " 0) Cancel"
|
|
read -p "Select: " ch
|
|
|
|
case $ch in
|
|
1)
|
|
if [[ "$has_server" == "y" && "$has_client" == "y" ]]; then
|
|
# Remove everything
|
|
systemctl stop asterisk 2>/dev/null
|
|
apt purge -y asterisk* baresip baresip-core 2>/dev/null
|
|
rm -rf /etc/asterisk /var/lib/asterisk /etc/easy-asterisk
|
|
[[ -n "$KIOSK_USER" ]] && rm -rf "/home/${KIOSK_USER}/.baresip"
|
|
systemctl daemon-reload
|
|
print_success "Removed all"
|
|
elif [[ "$has_server" == "y" ]]; then
|
|
# Remove server only
|
|
systemctl stop asterisk 2>/dev/null
|
|
apt purge -y asterisk* 2>/dev/null
|
|
rm -rf /etc/asterisk /var/lib/asterisk /etc/easy-asterisk
|
|
systemctl daemon-reload
|
|
print_success "Removed Asterisk"
|
|
elif [[ "$has_client" == "y" ]]; then
|
|
# Remove client only
|
|
apt purge -y baresip baresip-core 2>/dev/null
|
|
[[ -n "$KIOSK_USER" ]] && rm -rf "/home/${KIOSK_USER}/.baresip"
|
|
rm -rf /etc/easy-asterisk
|
|
print_success "Removed Baresip"
|
|
fi
|
|
;;
|
|
2)
|
|
if [[ "$has_server" == "y" && "$has_client" == "y" ]]; then
|
|
systemctl stop asterisk 2>/dev/null
|
|
apt purge -y asterisk* 2>/dev/null
|
|
rm -rf /etc/asterisk /var/lib/asterisk
|
|
INSTALLED_SERVER="n"
|
|
save_config
|
|
print_success "Removed Asterisk"
|
|
fi
|
|
;;
|
|
3)
|
|
if [[ "$has_server" == "y" && "$has_client" == "y" ]]; then
|
|
apt purge -y baresip baresip-core 2>/dev/null
|
|
[[ -n "$KIOSK_USER" ]] && rm -rf "/home/${KIOSK_USER}/.baresip"
|
|
INSTALLED_CLIENT="n"
|
|
save_config
|
|
print_success "Removed Baresip"
|
|
fi
|
|
;;
|
|
esac
|
|
}
|
|
|
|
# ================================================================
|
|
# 12. MENU SYSTEM
|
|
# ================================================================
|
|
|
|
show_main_menu() {
|
|
clear
|
|
print_header "Easy Asterisk v${SCRIPT_VERSION}"
|
|
|
|
load_config
|
|
echo " Status:"
|
|
if [[ "$INSTALLED_SERVER" == "y" ]]; then
|
|
echo -e " Server: ${GREEN}Installed${NC}"
|
|
echo " LAN/VPN: (use server IP):5060"
|
|
if [[ "$FQDN_ENABLED" == "y" ]]; then
|
|
echo -e " FQDN: ${GREEN}${DOMAIN_NAME}:5061${NC}"
|
|
else
|
|
echo -e " FQDN: ${YELLOW}Not configured${NC}"
|
|
fi
|
|
else
|
|
echo -e " Server: ${YELLOW}Not installed${NC}"
|
|
fi
|
|
if [[ "$INSTALLED_CLIENT" == "y" ]]; then
|
|
echo -e " Client: ${GREEN}Installed${NC}"
|
|
else
|
|
echo -e " Client: ${YELLOW}Not installed${NC}"
|
|
fi
|
|
echo ""
|
|
|
|
local count=1
|
|
declare -A menu_map
|
|
|
|
echo " ${count}) Install"; menu_map[$count]="submenu_install"; ((count++))
|
|
if [[ "$INSTALLED_SERVER" == "y" ]]; then
|
|
echo " ${count}) Setup FQDN Access"; menu_map[$count]="setup_fqdn_access"; ((count++))
|
|
echo " ${count}) Device Management"; menu_map[$count]="submenu_devices"; ((count++))
|
|
echo " ${count}) Server Tools"; menu_map[$count]="submenu_server"; ((count++))
|
|
fi
|
|
if [[ "$INSTALLED_CLIENT" == "y" ]]; then
|
|
echo " ${count}) Client Settings"; menu_map[$count]="submenu_client"; ((count++))
|
|
fi
|
|
echo " 0) Exit"
|
|
echo ""
|
|
|
|
read -p " Select: " choice
|
|
[[ "$choice" == "0" ]] && exit 0
|
|
local action=${menu_map[$choice]}
|
|
[[ -n "$action" ]] && $action
|
|
show_main_menu
|
|
}
|
|
|
|
submenu_install() {
|
|
clear
|
|
print_header "Install"
|
|
echo " 1) Full (server + client)"
|
|
echo " 2) Server only"
|
|
echo " 3) Client only"
|
|
echo " 4) Uninstall"
|
|
echo " 0) Back"
|
|
read -p " Select: " choice
|
|
case $choice in
|
|
1) install_full; read -p "Press Enter..." ;;
|
|
2) install_server_only; read -p "Press Enter..." ;;
|
|
3) install_client_only; read -p "Press Enter..." ;;
|
|
4) uninstall_menu; read -p "Press Enter..." ;;
|
|
esac
|
|
}
|
|
|
|
submenu_devices() {
|
|
clear
|
|
print_header "Device Management"
|
|
echo " 1) Add device"
|
|
echo " 2) Remove device"
|
|
echo " 3) List devices"
|
|
echo " 4) Manage categories"
|
|
echo " 5) Manage rooms"
|
|
echo " 0) Back"
|
|
read -p " Select: " choice
|
|
case $choice in
|
|
1) add_device_menu ;;
|
|
2) remove_device ;;
|
|
3) show_registered_devices ;;
|
|
4) manage_categories ;;
|
|
5) manage_rooms ;;
|
|
0) return ;;
|
|
esac
|
|
[[ "$choice" != "0" ]] && read -p "Press Enter..."
|
|
[[ "$choice" != "0" ]] && submenu_devices
|
|
}
|
|
|
|
submenu_server() {
|
|
clear
|
|
print_header "Server Tools"
|
|
echo " 1) Sync Caddy certs"
|
|
echo " 2) Port requirements"
|
|
echo " 3) Test connectivity"
|
|
echo " 4) Show registrations (live)"
|
|
echo " 5) Configure COTURN"
|
|
echo " 6) Restart Asterisk"
|
|
echo " 7) Watch SIP traffic"
|
|
echo " 8) VLAN/NAT traversal"
|
|
echo " 0) Back"
|
|
read -p " Select: " choice
|
|
case $choice in
|
|
1) setup_caddy_cert_sync "force" ;;
|
|
2) show_port_requirements ;;
|
|
3) test_sip_connectivity ;;
|
|
4) show_registrations ;;
|
|
5) configure_coturn_menu ;;
|
|
6) restart_asterisk_safe ;;
|
|
7) watch_sip_traffic ;;
|
|
8) toggle_vlan_nat_traversal ;;
|
|
0) return ;;
|
|
esac
|
|
[[ "$choice" != "0" ]] && read -p "Press Enter..."
|
|
[[ "$choice" != "0" ]] && submenu_server
|
|
}
|
|
|
|
show_registrations() {
|
|
print_header "SIP Registrations"
|
|
|
|
echo "═══════════════════════════════════════════════════════════════"
|
|
echo " REGISTERED CONTACTS"
|
|
echo "═══════════════════════════════════════════════════════════════"
|
|
asterisk -rx "pjsip show contacts" 2>/dev/null || echo " Unable to query Asterisk"
|
|
echo ""
|
|
|
|
echo "═══════════════════════════════════════════════════════════════"
|
|
echo " ENDPOINT STATUS"
|
|
echo "═══════════════════════════════════════════════════════════════"
|
|
asterisk -rx "pjsip show endpoints" 2>/dev/null | grep -E "^(Endpoint|Contact|[0-9])" || echo " No endpoints"
|
|
echo ""
|
|
|
|
echo "═══════════════════════════════════════════════════════════════"
|
|
echo " SERVER IPs (clients should connect to one of these)"
|
|
echo "═══════════════════════════════════════════════════════════════"
|
|
while IFS= read -r line; do
|
|
local iface=$(echo "$line" | awk '{print $2}' | tr -d ':')
|
|
local ip=$(echo "$line" | awk '{print $4}' | cut -d'/' -f1)
|
|
[[ "$iface" =~ ^(lo|docker|br-|veth|virbr) ]] && continue
|
|
[[ -z "$ip" ]] && continue
|
|
local label="LAN"
|
|
[[ "$iface" =~ ^tailscale ]] && label="VPN-Tailscale"
|
|
[[ "$iface" =~ ^(wg[0-9]) ]] && label="VPN-WireGuard"
|
|
[[ "$iface" =~ ^(wt[0-9]|utun) ]] && label="VPN-Netbird"
|
|
[[ "$iface" =~ ^tun ]] && label="VPN-OpenVPN"
|
|
[[ "$iface" =~ ^zt ]] && label="VPN-ZeroTier"
|
|
printf " %-15s %-18s (%s)\n" "$iface" "$ip" "$label"
|
|
done < <(ip -o -4 addr show 2>/dev/null)
|
|
}
|
|
|
|
watch_sip_traffic() {
|
|
print_header "SIP Traffic Monitor"
|
|
echo "This will show live SIP packets. Press Ctrl+C to stop."
|
|
echo ""
|
|
|
|
if command -v sngrep &>/dev/null; then
|
|
echo "Starting sngrep (visual SIP analyzer)..."
|
|
sngrep -c
|
|
elif command -v tcpdump &>/dev/null; then
|
|
echo "Starting tcpdump on ports 5060/5061..."
|
|
tcpdump -i any -n "port 5060 or port 5061" -v
|
|
else
|
|
print_error "Neither sngrep nor tcpdump installed"
|
|
echo "Install with: apt install sngrep"
|
|
fi
|
|
}
|
|
|
|
submenu_client() {
|
|
clear
|
|
print_header "Client Settings"
|
|
echo " 1) Configure client"
|
|
echo " 2) Configure PTT button"
|
|
echo " 3) Diagnostics (logs, restart, status)"
|
|
echo " 4) Fix audio (unmute & set volume)"
|
|
echo " 5) Restart Baresip now"
|
|
echo " 0) Back"
|
|
read -p " Select: " choice
|
|
case $choice in
|
|
1) configure_local_client ;;
|
|
2) configure_ptt_menu ;;
|
|
3) run_client_diagnostics ;;
|
|
4) fix_audio_manually ;;
|
|
5) restart_baresip_now ;;
|
|
0) return ;;
|
|
esac
|
|
[[ "$choice" != "0" ]] && read -p "Press Enter..."
|
|
[[ "$choice" != "0" ]] && submenu_client
|
|
}
|
|
|
|
restart_baresip_now() {
|
|
load_config
|
|
local t_user="${KIOSK_USER:-$SUDO_USER}"
|
|
t_user="${t_user:-$USER}"
|
|
local t_uid=$(id -u "$t_user" 2>/dev/null)
|
|
local user_dbus="XDG_RUNTIME_DIR=/run/user/$t_uid"
|
|
|
|
print_info "Restarting Baresip for $t_user..."
|
|
sudo -u "$t_user" $user_dbus systemctl --user restart baresip 2>/dev/null
|
|
sleep 3
|
|
|
|
if sudo -u "$t_user" $user_dbus systemctl --user is-active baresip >/dev/null 2>&1; then
|
|
print_success "Baresip running"
|
|
echo ""
|
|
echo "Recent log:"
|
|
sudo -u "$t_user" $user_dbus journalctl --user -u baresip -n 5 --no-pager 2>/dev/null
|
|
else
|
|
print_error "Baresip failed to start"
|
|
echo ""
|
|
echo "Error log:"
|
|
sudo -u "$t_user" $user_dbus journalctl --user -u baresip -n 15 --no-pager 2>/dev/null
|
|
fi
|
|
}
|
|
|
|
main() {
|
|
check_root
|
|
load_config
|
|
show_main_menu
|
|
}
|
|
|
|
main "$@" |