diff --git a/easy-asterisk-v0.9.5.sh b/easy-asterisk-v0.9.5.sh deleted file mode 100644 index 557a4e0..0000000 --- a/easy-asterisk-v0.9.5.sh +++ /dev/null @@ -1,3034 +0,0 @@ -#!/bin/bash -# ================================================================ -# Easy Asterisk - Interactive Installer v0.9.5 -# -# UPDATES in v0.9.5: -# - FIXED: Stasis disabled causing startup failure (now only disables optional modules) -# - ADDED: Automatic VLAN/NAT traversal (works with flat, VPN, and VLAN networks) -# - ADDED: Per-device connection type selection (LAN/VPN vs FQDN) -# - IMPROVED: Transport-level NAT configuration for better VLAN support -# - IMPROVED: Generic router references (not brand-specific) -# - COMPATIBLE: Works with flat networks, VLANs, VPNs, and FQDN modes -# -# RETAINED from v1.29: -# - Kiosk client fixes (DBUS, PipeWire deps) -# - PTT button fixes -# - Smart user detection -# - Comprehensive logging -# - Working uninstall logic -# ================================================================ - -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="0.9.5" - -# ================================================================ -# 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_user() { - # Scan /home for real users (exclude system accounts) - local -a users=() - local -a user_ids=() - local count=0 - - echo "Scanning for users..." - echo "" - - # Get users from /home with valid shells - while IFS=: read -r username _ uid _ _ homedir shell; do - # Only include users with UID >= 1000 and valid shell - 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 - - # Add option to manually enter username - ((count++)) - echo " ${count}) Enter username manually" - echo "" - - # Suggest default based on SUDO_USER or first user found - local default_choice="" - local default_user="${SUDO_USER:-}" - if [[ -z "$default_user" ]]; then - default_user="${users[0]:-}" - default_choice="1" - else - # Find index of SUDO_USER - 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 - - # Validate choice - 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 - # Manual entry - 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:-}" - 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" -ENABLE_TLS="$ENABLE_TLS" -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" -CURRENT_PUBLIC_IP="$CURRENT_PUBLIC_IP" -PTT_DEVICE="$PTT_DEVICE" -PTT_KEYCODE="$PTT_KEYCODE" -LOCAL_CIDR="$LOCAL_CIDR" -EOF - chmod 644 "$CONFIG_FILE" - - # Save PTT config separately - 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 - # Allow relay range for TURN - 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. COTURN SETUP & DYNAMIC IP -# ================================================================ - -get_public_ip() { - local ip=$(curl -s -4 --connect-timeout 5 ifconfig.me 2>/dev/null || curl -s -4 --connect-timeout 5 icanhazip.com 2>/dev/null || echo "") - echo "$ip" -} - -install_coturn() { - print_header "Installing COTURN" - apt update - apt install -y coturn - - if [[ -z "$TURN_PASS" ]]; then - TURN_PASS=$(generate_password) - fi - - 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" - - # Enable coturn - sed -i 's/#TURNSERVER_ENABLED=1/TURNSERVER_ENABLED=1/' /etc/default/coturn 2>/dev/null || true - - # Configure coturn - backup_config "$COTURN_CONFIG" - cat > "$COTURN_CONFIG" << EOF -# Easy Asterisk COTURN Configuration -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:ECDHE-RSA-AES256-GCM-SHA384:DHE-RSA-AES256-GCM-SHA384:ECDHE-RSA-AES256-SHA384" -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 -} - -configure_coturn_credentials() { - print_header "Configure TURN Credentials" - echo "Current User: ${TURN_USER}" - echo "Current Pass: ${TURN_PASS}" - echo "" - read -p "Enter Username [${TURN_USER}]: " t_user - t_user="${t_user:-$TURN_USER}" - read -p "Enter Password [generate]: " t_pass - t_pass="${t_pass:-$(generate_password)}" - - TURN_USER="$t_user" - TURN_PASS="$t_pass" - - if [[ -f "$COTURN_CONFIG" ]]; then - # Remove old user lines and add new one - sed -i '/^user=/d' "$COTURN_CONFIG" - echo "user=${TURN_USER}:${TURN_PASS}" >> "$COTURN_CONFIG" - systemctl restart coturn - print_success "Credentials updated and service restarted" - else - print_error "COTURN not installed. Run install first." - fi - save_config -} - -update_coturn_ip() { - local new_ip=$(get_public_ip) - - if [[ -z "$new_ip" ]]; then - print_warn "Could not detect public IP" - return 1 - fi - - if [[ "$new_ip" == "$CURRENT_PUBLIC_IP" ]]; then - print_info "IP unchanged: $new_ip" - return 0 - fi - - print_info "IP changed: $CURRENT_PUBLIC_IP -> $new_ip" - - # Update coturn config - if [[ -f "$COTURN_CONFIG" ]]; then - sed -i "s/^external-ip=.*/external-ip=${new_ip}/" "$COTURN_CONFIG" - systemctl restart coturn - print_success "COTURN updated" - fi - - # Update pjsip config - if [[ -f /etc/asterisk/pjsip.conf ]]; then - sed -i "s/^external_media_address=.*/external_media_address=${new_ip}/" /etc/asterisk/pjsip.conf - sed -i "s/^external_signaling_address=.*/external_signaling_address=${new_ip}/" /etc/asterisk/pjsip.conf - asterisk -rx "pjsip reload" 2>/dev/null - print_success "Asterisk updated" - fi - - CURRENT_PUBLIC_IP="$new_ip" - save_config - return 0 -} - -create_ip_update_script() { - cat > /usr/local/bin/easy-asterisk-update-ip << 'IPSCRIPT' -#!/bin/bash -CONFIG_FILE="/etc/easy-asterisk/config" -[[ -f "$CONFIG_FILE" ]] && source "$CONFIG_FILE" - -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 -} - -NEW_IP=$(get_public_ip) -[[ -z "$NEW_IP" ]] && exit 0 -[[ "$NEW_IP" == "$CURRENT_PUBLIC_IP" ]] && exit 0 - -# Update COTURN -if [[ -f /etc/turnserver.conf ]]; then - sed -i "s/^external-ip=.*/external-ip=${NEW_IP}/" /etc/turnserver.conf - systemctl restart coturn -fi - -# Update Asterisk -if [[ -f /etc/asterisk/pjsip.conf ]]; then - sed -i "s/^external_media_address=.*/external_media_address=${NEW_IP}/" /etc/asterisk/pjsip.conf - sed -i "s/^external_signaling_address=.*/external_signaling_address=${NEW_IP}/" /etc/asterisk/pjsip.conf - asterisk -rx "pjsip reload" 2>/dev/null -fi - -# Update config file -sed -i "s/^CURRENT_PUBLIC_IP=.*/CURRENT_PUBLIC_IP=\"${NEW_IP}\"/" "$CONFIG_FILE" - -logger "Easy Asterisk: Updated IP to ${NEW_IP}" -IPSCRIPT - chmod +x /usr/local/bin/easy-asterisk-update-ip - - # Create systemd timer - cat > /etc/systemd/system/easy-asterisk-ip-update.service << 'EOF' -[Unit] -Description=Easy Asterisk IP Update -After=network-online.target - -[Service] -Type=oneshot -ExecStart=/usr/local/bin/easy-asterisk-update-ip -EOF - - cat > /etc/systemd/system/easy-asterisk-ip-update.timer << 'EOF' -[Unit] -Description=Easy Asterisk IP Update Timer - -[Timer] -OnBootSec=2min -OnUnitActiveSec=10min - -[Install] -WantedBy=timers.target -EOF - - systemctl daemon-reload - systemctl enable easy-asterisk-ip-update.timer - systemctl start easy-asterisk-ip-update.timer - - print_success "IP update timer installed (checks every 10 minutes)" -} - -configure_coturn_menu() { - print_header "Configure COTURN (TURN Server)" - - if [[ "$INSTALLED_COTURN" == "y" ]]; then - echo -e "Status: ${GREEN}Installed${NC}" - echo "User: ${TURN_USER}" - echo "Pass: ${TURN_PASS}" - echo "" - echo " 1) Update Credentials (User/Pass)" - echo " 2) Reinstall/Reconfigure" - echo " 3) Update IP manually" - echo " 4) Show configuration" - echo " 5) Uninstall" - echo " 0) Back" - read -p "Select: " choice - case $choice in - 1) configure_coturn_credentials ;; - 2) install_coturn ;; - 3) update_coturn_ip ;; - 4) show_coturn_config ;; - 5) uninstall_coturn ;; - esac - else - echo "COTURN is not installed." - read -p "Install now? [Y/n]: " install - if [[ ! "$install" =~ ^[Nn]$ ]]; then - install_coturn - if [[ "$INSTALLED_COTURN" == "y" ]]; then - create_ip_update_script - fi - fi - fi -} - -show_coturn_config() { - print_header "COTURN Configuration" - echo "Status: $(systemctl is-active coturn)" - echo "Public IP: $CURRENT_PUBLIC_IP" - echo "Port: ${DEFAULT_TURN_PORT}" - echo "Credentials: ${TURN_USER} : ${TURN_PASS}" - echo "" - echo "Client Config String:" - echo "turn:${TURN_USER}:${TURN_PASS}@${TURN_DOMAIN:-${DOMAIN_NAME:-$CURRENT_PUBLIC_IP}}:${DEFAULT_TURN_PORT}" - echo "" - echo "Logs:" - tail -n 20 /var/log/turnserver.log 2>/dev/null || echo " No logs found" -} - -uninstall_coturn() { - systemctl stop coturn 2>/dev/null || true - systemctl disable coturn 2>/dev/null || true - apt purge -y coturn 2>/dev/null || true - rm -f /etc/turnserver.conf - rm -f /usr/local/bin/easy-asterisk-update-ip - systemctl stop easy-asterisk-ip-update.timer 2>/dev/null || true - systemctl disable easy-asterisk-ip-update.timer 2>/dev/null || true - rm -f /etc/systemd/system/easy-asterisk-ip-update.* - systemctl daemon-reload - INSTALLED_COTURN="n" - USE_COTURN="n" - save_config - print_success "COTURN uninstalled" -} - -# ================================================================ -# 3. 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="${GREEN}PAGE/INTERCOM${NC}" - echo -e " ${CYAN}$index)${NC} ${BOLD}$rname${NC} ($rext) - $type_text" - echo -e " Members: $rmem" - ((index++)) - done < "$ROOMS_FILE" - echo "" - echo " 1) Add Room" - echo " 2) Edit Room Members" - echo " 3) Delete Room" - echo " 0) Back" - read -p "Select: " choice - case $choice in - 1) - read -p "Room Extension: " new_ext - read -p "Room Name: " new_name - echo " 1) Ring Group (Phones ring)" - echo " 2) Page/Intercom (Auto-answer)" - read -p "Select [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 "Select Room #: " rnum - local target_line="" - local count=0 - while IFS= read -r line; do - if [[ ! "$line" =~ ^# ]] && [[ -n "$line" ]]; then - ((count++)) - if [[ $count -eq $rnum ]]; then target_line="$line"; break; fi - fi - done < "$ROOMS_FILE" - if [[ -n "$target_line" ]]; then - IFS='|' read -r rext rname rmem rtime rtype <<< "$target_line" - 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" - fi - ;; - 3) - read -p "Select Room #: " rnum - local count=0 - local target_ext="" - while IFS='|' read -r rext rrest; do - if [[ ! "$rext" =~ ^# ]] && [[ -n "$rext" ]]; then - ((count++)) - if [[ $count -eq $rnum ]]; then target_ext="$rext"; break; fi - fi - done < "$ROOMS_FILE" - if [[ -n "$target_ext" ]]; then - sed -i "/^${target_ext}|/d" "$ROOMS_FILE" - rebuild_dialplan - print_success "Room Deleted" - fi - ;; - esac -} - -add_device_menu() { - print_header "Add Device" - load_config # Load saved configuration to check ENABLE_TLS, DOMAIN_NAME, etc. - 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) - - 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 - - # 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 [[ "$ENABLE_TLS" == "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 Internet Access' first)" - fi - echo "" - read -p " Select [1]: " conn_choice - conn_choice="${conn_choice:-1}" - - if [[ "$conn_choice" == "1" ]]; then - # LAN/VPN - UDP, no encryption - display_server="$(hostname -I | awk '{print $1}')" - display_port="5060" - display_transport="UDP" - display_encryption="None" - elif [[ "$conn_choice" == "2" ]]; then - if [[ "$ENABLE_TLS" != "y" || -z "$DOMAIN_NAME" ]]; then - print_error "FQDN access not configured. Run 'Setup Internet 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 - - backup_config "/etc/asterisk/pjsip.conf" - - cat >> /etc/asterisk/pjsip.conf << EOF - -; === Device: $name ($cat_id) $override_tag === -[${ext}] -type=endpoint -context=intercom -disallow=all -allow=opus -allow=ulaw -allow=alaw -allow=g722 -${encryption_block} -direct_media=no -rtp_symmetric=yes -force_rport=yes -rewrite_contact=yes -${ice_block} -auth=${ext} -aors=${ext} -callerid="${name}" <${ext}> - -[${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 2>/dev/null || true - 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 "" - while IFS= read -r line; do - if [[ "$line" == *"; === Device:"* ]]; then - local temp="${line#*; === Device: }" - temp="${temp% ===}" - temp="${temp% \[AA:*\]}" - local name="${temp% (*)}" - fi - if [[ "$line" =~ ^\[([0-9]+)\] ]]; then - local ext="${BASH_REMATCH[1]}" - if [[ -n "$name" ]]; then - echo " ${count}) Ext ${ext} - ${name}" - REMOVE_MAP[$count]=$ext - ((count++)) - name="" - fi - fi - done < /etc/asterisk/pjsip.conf - echo "" - echo " 98) DELETE ALL DEVICES" - echo " 0) Cancel" - echo "" - read -p "Select: " choice - - if [[ "$choice" == "98" ]]; then - echo "" - print_warn "This will DELETE ALL DEVICES!" - read -p "Type 'DELETE ALL' to confirm: " confirm - if [[ "$confirm" == "DELETE ALL" ]]; then - backup_config "/etc/asterisk/pjsip.conf" - # Remove all device sections - 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" - else - print_error "Cancelled" - fi - return - fi - - [[ "$choice" == "0" || -z "${REMOVE_MAP[$choice]}" ]] && return - - local ext="${REMOVE_MAP[$choice]}" - read -p "Confirm removal of $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" - printf "${CYAN}%-6s %-20s %-15s %-15s %-15s${NC}\n" "Ext" "Name" "Category" "Status" "Password" - echo "------------------------------------------------------------------------------" - local dev_name="" dev_cat="" - while IFS= read -r line; do - if [[ "$line" == *"; === Device:"* ]]; then - local temp="${line#*; === Device: }" - temp="${temp% ===}" - [[ "$temp" == *"[AA:"* ]] && temp="${temp% \[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="${RED}Offline${NC}" - local avail=$(asterisk -rx "pjsip show endpoint ${ext}" 2>/dev/null | grep -E "Contact:.*(Avail|NonQual)" || true) - [[ -n "$avail" ]] && status="${GREEN}Online${NC}" - local password=$(grep -A 10 "^\[$ext\]" /etc/asterisk/pjsip.conf | grep "password=" | head -1 | cut -d= -f2) - printf "%-6s %-20s %-15s %b %-15s\n" "$ext" "${dev_name:0:18}" "$dev_cat" "$status" "$password" - dev_name="" - fi - fi - done < /etc/asterisk/pjsip.conf - echo "" - echo "Connection Details:" - echo " Domain: ${DOMAIN_NAME:-$(hostname -I | awk '{print $1}')}" - echo " Port: ${DEFAULT_SIP_PORT}/udp (LAN) or ${DEFAULT_SIPS_PORT}/tcp (TLS)" -} - -# ================================================================ -# 4. PTT WIZARD (Fixed: Mute by default) -# ================================================================ - -configure_ptt_menu() { - print_header "Configure PTT Button" - detect_ptt_button -} - -detect_ptt_button() { - # Ensure evtest is installed - if ! command -v evtest &>/dev/null; then - apt install -y evtest >/dev/null 2>&1 - fi - - # Add user to input group - [[ -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:]') - - # Filter out system devices that aren't PTT candidates - if [[ "$lname" =~ (power.button|sleep.button|lid.switch|virtual|video.bus|hdmi|dp,pcm|hotkey|touchpad|touchscreen) ]]; then - OTHER_DEVICES+=("$dev") - OTHER_NAMES+=("$name") - # Prioritize keyboards, USB HID devices, pedals - elif [[ "$lname" =~ (keyboard|sayo.*nano$|pedal|foot|^hid) ]]; 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}Keyboards and 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 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=$(cat "/sys/class/input/$(basename $PTT_DEVICE)/device/name" 2>/dev/null || echo "Unknown") - 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 user is in input group (critical for PTT device access) - 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_error "IMPORTANT: User added to 'input' group" - echo " User must log out and log back in (or reboot) for group change to take effect." - echo " PTT will NOT work until then!" - echo "" - read -p "Press Enter to acknowledge..." - fi - fi - - # Save configuration via save_config (will set proper permissions) - 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" - echo " User: ${KIOSK_USER:-not set}" - echo "" - echo " Testing PTT:" - echo " 1. Check logs: journalctl -t kiosk-ptt -f" - echo " 2. Press PTT button" - echo " 3. You should see: 'PTT pressed - mic unmuted'" - echo "" - echo " If you see 'Permission denied' errors:" - echo " - User needs to be in 'input' group (already added above)" - echo " - Log out and log back in, or reboot" - echo "═══════════════════════════════════════════════════════" - - # Restart PTT service if client is installed - if [[ "$INSTALLED_CLIENT" == "y" && -n "$KIOSK_USER" ]]; then - local user_dbus="XDG_RUNTIME_DIR=/run/user/${KIOSK_UID}" - echo "" - 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 restart kiosk-ptt 2>/dev/null || true - sleep 2 - echo "" - echo "Checking PTT status..." - journalctl -t kiosk-ptt -n 5 --no-pager 2>/dev/null || echo " No logs yet (check after logging out/in if needed)" - 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" - -# Exit if no PTT device configured - leave audio unmuted for normal kiosk operation -[[ -z "$PTT_DEVICE" ]] && exit 0 - -# Ensure we have the user's runtime directory -if [[ -z "$XDG_RUNTIME_DIR" ]]; then - # If running as systemd service, this should already be set - # But if not, try to detect it - if [[ -n "$KIOSK_UID" ]]; then - export XDG_RUNTIME_DIR="/run/user/${KIOSK_UID}" - else - # Fall back to current user - export XDG_RUNTIME_DIR="/run/user/$(id -u)" - fi -fi - -# Wait for PipeWire/PulseAudio to be ready -for i in {1..10}; do - if pactl info >/dev/null 2>&1; then - break - fi - sleep 1 -done - -# PTT mode: Mute audio source on start, unmute only when button pressed -pactl set-source-mute @DEFAULT_SOURCE@ 1 2>/dev/null || { - logger -t kiosk-ptt "ERROR: Failed to mute audio source" - exit 1 -} - -logger -t kiosk-ptt "PTT handler started, microphone muted, listening on $PTT_DEVICE" - -# Unmute on press, mute on release -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 - mic unmuted" - fi - if [[ "$line" =~ "value 0" ]]; then - pactl set-source-mute @DEFAULT_SOURCE@ 1 2>/dev/null - logger -t kiosk-ptt "PTT released - mic muted" - fi -done -PTTSCRIPT - chmod +x /usr/local/bin/kiosk-ptt -} - -# ================================================================ -# 5. AUDIO DUCKING -# ================================================================ - -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" ]] && return - [[ -z "$KIOSK_UID" ]] && return - - # Only unmute if PTT is not configured - if [[ ! -f /etc/easy-asterisk/ptt-device ]]; then - local user_dbus="XDG_RUNTIME_DIR=/run/user/${KIOSK_UID}" - - # Wait a moment for PipeWire to initialize - sleep 2 - - # Unmute all sources and sinks - 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 - - # Set reasonable volume levels if they're at 0 - local source_vol=$(sudo -u "$KIOSK_USER" $user_dbus pactl get-source-volume @DEFAULT_SOURCE@ 2>/dev/null | grep -oP '\d+%' | head -1 | tr -d '%') - local sink_vol=$(sudo -u "$KIOSK_USER" $user_dbus pactl get-sink-volume @DEFAULT_SINK@ 2>/dev/null | grep -oP '\d+%' | head -1 | tr -d '%') - - [[ -n "$source_vol" && "$source_vol" -lt 50 ]] && sudo -u "$KIOSK_USER" $user_dbus pactl set-source-volume @DEFAULT_SOURCE@ 75% 2>/dev/null || true - [[ -n "$sink_vol" && "$sink_vol" -lt 50 ]] && sudo -u "$KIOSK_USER" $user_dbus pactl set-sink-volume @DEFAULT_SINK@ 75% 2>/dev/null || true - fi -} - -# ================================================================ -# 6. DIAGNOSTICS & FIREWALL -# ================================================================ - -show_port_requirements() { - print_header "Port / Firewall Requirements" - echo "This server needs traffic to pass from your Clients (Kiosks/Phones)." - echo "" - echo "Does your Asterisk server have a PUBLIC IP (VPS/Cloud)?" - echo " -> YES: You must use 'Forwarding' (DNAT) rules on your router." - echo " -> NO: You must use 'Allow/Pass' rules on your VLAN interfaces." - echo "" - echo "Required Ports:" - echo "┌──────────────────┬──────────┬───────────────────────────────┐" - echo "│ Port │ Protocol │ Purpose │" - echo "├──────────────────┼──────────┼───────────────────────────────┤" - echo "│ 5060 │ UDP │ SIP Signaling (Registration) │" - echo "│ 5061 │ TCP │ SIP-TLS Signaling (Secure) │" - echo "│ 10000-20000 │ UDP │ RTP Media (Audio/Video) │" - if [[ "$USE_COTURN" == "y" ]]; then - echo "│ ${DEFAULT_TURN_PORT} │ UDP/TCP │ TURN Signaling (Handshake) │" - echo "│ 49152-65535 │ UDP │ TURN Relay (Actual Media Path)│" - fi - echo "└──────────────────┴──────────┴───────────────────────────────┘" - echo "" - echo "NOTE: VPN Users" - echo "If ALL clients and server are on a VPN (Tailscale/Wireguard), you DO NOT" - echo "need port forwarding or COTURN. Just bind Asterisk to the VPN IP." -} - -show_firewall_guide() { - print_header "Interactive Firewall Guide (Hand-holding Mode)" - echo "For: Routers with VLAN support" - echo "" - echo "=== SCENARIO A: INTERNAL ONLY (VLAN to VLAN) ===" - echo "Example: Kiosks on VLAN 10, Server on VLAN 20" - echo "GOAL: Allow Kiosks to talk to Server." - echo "" - echo "STEP 1: Log in to Router. Go to Firewall > Rules > VLAN 10 Interface." - echo " (Do NOT use 'Port Forwarding' for internal VLANs!)" - echo "" - echo "STEP 2: Create Rule 1 (Signaling)" - echo " - Action: Pass (Allow)" - echo " - Protocol: UDP/TCP" - echo " - Source: VLAN 10 Net" - echo " - Dest: ${CURRENT_PUBLIC_IP:-Server_IP}" - echo " - Port: 3478" - echo "" - echo "STEP 3: Create Rule 2 (The Relay Range - CRITICAL)" - echo " - Action: Pass (Allow)" - echo " - Protocol: UDP" - echo " - Source: VLAN 10 Net" - echo " - Dest: ${CURRENT_PUBLIC_IP:-Server_IP}" - echo " - Port Range:" - echo " From: 49152" - echo " To: 65535" - echo " (Note: Type these numbers in the Start/End boxes)" - echo "" - echo "================================================" - echo "" - echo "=== SCENARIO B: EXTERNAL ACCESS (Internet to LAN) ===" - echo "Example: Remote phone connecting from a hotel." - echo "GOAL: Forward traffic from Internet to Server." - echo "" - echo "STEP 1: Go to Firewall > NAT > Port Forwarding." - echo "STEP 2: Create Rule." - echo " - Interface: WAN" - echo " - Protocol: UDP" - echo " - Dest. Port: 3478 (and 49152-65535)" - echo " - Redirect IP: ${CURRENT_PUBLIC_IP:-Server_IP}" - echo "" - read -p "Press Enter to return..." -} - -show_preflight_check() { - print_header "Pre-Flight Requirements Check" - echo "Modern browsers (Chrome, Safari, Kiosk Mode) have strict security settings." - echo "" - echo "1. HTTPS / SSL Certificate (Required for Camera/Mic)" - echo " - Browsers block Mic/Cam on 'Insecure Origins' (HTTP)." - echo " - Exception: http://localhost is allowed." - echo " - Solution: You NEED a domain (FQDN) and SSL Cert (LetsEncrypt)." - echo " - Workaround: Use the 'Caddy Cert Sync' option in this script." - echo "" - echo "2. Static vs Dynamic IP" - echo " - If your Public IP changes, COTURN will break." - echo " - Solution: Use the 'Update IP manually' or auto-script in the menu." - echo "" - echo "3. VPN Alternative" - echo " - A VPN (Tailscale) negates the need for COTURN and Port Forwarding." - echo " - It treats all devices as if they are on the same flat network." - echo "" - read -p "Press Enter to return..." -} - -test_sip_connectivity() { - print_header "SIP Connectivity Test" - if systemctl is-active asterisk >/dev/null; then - print_success "Asterisk Running" - else - print_error "Asterisk Down" - fi - echo "" - echo "Listening ports:" - ss -ulnp | grep 5060 || echo " UDP 5060: Not listening" - ss -tlnp | grep 5061 || echo " TCP 5061: Not listening" - if [[ -n "$DOMAIN_NAME" ]]; then - echo "" - echo "TLS Certificate check:" - timeout 5 openssl s_client -connect localhost:5061 -servername "$DOMAIN_NAME" 2>/dev/null | grep "Verify return code" || echo " TLS test failed" - fi -} - -verify_cidr_config() { - print_header "CIDR Configuration" - local my_ip=$(hostname -I | cut -d' ' -f1) - echo "Server IP: $my_ip" - echo "" - echo "Current NAT settings in pjsip.conf:" - grep -E "external_|local_net" /etc/asterisk/pjsip.conf 2>/dev/null || echo " No NAT settings found" -} - -watch_live_logs() { - print_header "Live Debugging" - echo "Enabling PJSIP Logger..." - asterisk -rx "module load res_pjsip_logger.so" 2>/dev/null || true - asterisk -rx "pjsip set logger on" 2>/dev/null - echo "" - echo "Options:" - echo " 1) Asterisk Console (verbose)" - echo " 2) Packet Capture (tcpdump)" - read -p "Select [1]: " pcap - if [[ "$pcap" == "2" ]]; then - echo "Starting tcpdump. Press CTRL+C to stop." - tcpdump -i any port 5060 or port 5061 -nn -v - else - echo "Starting Console. Press CTRL+C to exit." - asterisk -rvvv - fi - asterisk -rx "pjsip set logger off" 2>/dev/null -} - -router_doctor() { - print_header "Router Traffic Doctor" - if ! systemctl is-active asterisk >/dev/null; then - print_error "Asterisk is NOT RUNNING" - restart_asterisk_safe - return - fi - - print_success "Asterisk is UP" - echo "" - echo "Server Listening IPs:" - ip -o -4 addr show | awk '{print " " $2 ": " $4}' - echo "" - echo "Instructions:" - echo " 1. Take out your phone/laptop" - echo " 2. Attempt to REGISTER or CALL" - echo " 3. I will listen for 15 seconds" - echo "" - read -p "Press Enter to start listening..." - - if timeout 15 tcpdump -i any -c 1 "port 5060 or port 5061" 2>/dev/null; then - echo "" - print_success "PACKET RECEIVED! Router forwarding is working." - else - echo "" - print_error "NO PACKETS RECEIVED." - echo "Your router or firewall is blocking the connection." - fi -} - -configure_local_client() { - print_header "Configure Local Client" - load_config - - # If KIOSK_USER already set from config, show and ask if want to change - if [[ -n "$KIOSK_USER" ]]; then - echo "Current configured user: $KIOSK_USER" - read -p "Change user? [y/N]: " change_user - if [[ "$change_user" =~ ^[Yy]$ ]]; then - KIOSK_USER="" - KIOSK_UID="" - fi - fi - - # If still no user, select one - if [[ -z "$KIOSK_USER" ]]; then - echo "" - echo "Select the user to configure:" - echo "" - if ! select_user; then - print_error "User selection failed" - return 1 - fi - else - # Ensure KIOSK_UID is set - KIOSK_UID=$(id -u "$KIOSK_USER" 2>/dev/null) - fi - - echo "" - - if [[ ! -d "/home/${KIOSK_USER}/.baresip" ]]; then - print_error "Baresip not installed for $KIOSK_USER" - echo "" - read -p "Install Baresip client now? [Y/n]: " install_it - if [[ ! "$install_it" =~ ^[Nn]$ ]]; then - install_baresip_packages - configure_baresip - enable_client_services - INSTALLED_CLIENT="y" - save_config - print_success "Baresip installed" - echo "" - echo "Audio configured for $KIOSK_USER" - echo "If audio doesn't work, log out and back in or reboot." - echo "" - else - return - fi - fi - - read -p "Extension: " ext - read -p "Password: " pass - read -p "Server Domain/IP: " server - - local transport_str="udp" - local media_enc="" - - if [[ "$server" =~ [a-zA-Z] ]]; then - print_info "Domain detected. Using TLS." - transport_str="tls" - media_enc=";mediaenc=srtp" - fi - - echo "" - echo "Answer Mode:" - echo " 1) Manual (ring on incoming)" - echo " 2) Auto (auto-answer)" - read -p "Select [1]: " amode - local answermode="manual" - [[ "$amode" == "2" ]] && answermode="auto" - - echo "" - echo "Enable TURN? (Required if behind NAT/VLAN without VPN)" - read -p "Use TURN server? [y/N]: " use_turn - local turn_config="" - if [[ "$use_turn" =~ ^[Yy]$ ]]; then - read -p "TURN User [${TURN_USER}]: " t_user - t_user="${t_user:-$TURN_USER}" - read -p "TURN Pass [${TURN_PASS}]: " t_pass - t_pass="${t_pass:-$TURN_PASS}" - local turn_host="${server}" - if [[ ! "$turn_host" =~ [a-zA-Z] ]]; then - # If server is IP, ask if TURN host is different - read -p "TURN Host [${server}]: " th - turn_host="${th:-$server}" - fi - turn_config="turn_server turn:${t_user}:${t_pass}@${turn_host}:3478" - fi - - # Update config file for TURN - local conf_file="/home/${KIOSK_USER}/.baresip/config" - if [[ -f "$conf_file" ]]; then - sed -i '/^turn_server/d' "$conf_file" - if [[ -n "$turn_config" ]]; then - echo "$turn_config" >> "$conf_file" - print_success "TURN configuration added" - fi - fi - - cat > "/home/${KIOSK_USER}/.baresip/accounts" << EOF -;auth_pass=${pass};answermode=${answermode}${media_enc} -EOF - chown ${KIOSK_USER}:${KIOSK_USER} "/home/${KIOSK_USER}/.baresip/accounts" - chown ${KIOSK_USER}:${KIOSK_USER} "/home/${KIOSK_USER}/.baresip/config" - - # Update main config - ASTERISK_HOST="$server" - KIOSK_EXTENSION="$ext" - CLIENT_ANSWERMODE="$answermode" - save_config - - local user_dbus="XDG_RUNTIME_DIR=/run/user/${KIOSK_UID}" - - # Reload systemd daemon in case services changed - sudo -u "${KIOSK_USER}" $user_dbus systemctl --user daemon-reload 2>/dev/null - - # Restart audio and client services - print_info "Restarting services..." - 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 - - # Ensure audio is unmuted if not in PTT mode - if [[ ! -f /etc/easy-asterisk/ptt-device ]]; then - sleep 1 - ensure_audio_unmuted - fi - - print_success "Client Reconfigured & Services Restarted" - echo "" - echo "Run Diagnostics to verify connection status." -} - -run_client_diagnostics() { - print_header "Client Diagnostics" - load_config - local t_user="${KIOSK_USER:-$SUDO_USER}" - t_user="${t_user:-$USER}" - local t_uid=$(id -u "$t_user" 2>/dev/null) - - echo -e "User: ${BOLD}$t_user${NC}" - echo "---------------------------------------------------" - if sudo -u "$t_user" XDG_RUNTIME_DIR=/run/user/$t_uid systemctl --user is-active baresip >/dev/null 2>&1; then - print_success "Baresip RUNNING" - else - print_error "Baresip STOPPED/FAILED" - fi - echo "---------------------------------------------------" - - echo "Audio Services:" - local user_dbus="XDG_RUNTIME_DIR=/run/user/$t_uid" - if sudo -u "$t_user" $user_dbus systemctl --user is-active pipewire >/dev/null 2>&1; then - print_success "PipeWire RUNNING" - else - print_error "PipeWire STOPPED" - fi - if sudo -u "$t_user" $user_dbus systemctl --user is-active pipewire-pulse >/dev/null 2>&1; then - print_success "PipeWire-Pulse RUNNING" - else - print_error "PipeWire-Pulse STOPPED" - fi - - echo "" - echo "Audio Status:" - local src_mute=$(sudo -u "$t_user" $user_dbus pactl get-source-mute @DEFAULT_SOURCE@ 2>/dev/null | awk '{print $2}') - local sink_mute=$(sudo -u "$t_user" $user_dbus pactl get-sink-mute @DEFAULT_SINK@ 2>/dev/null | awk '{print $2}') - local src_vol=$(sudo -u "$t_user" $user_dbus pactl get-source-volume @DEFAULT_SOURCE@ 2>/dev/null | grep -oP '\d+%' | head -1) - local sink_vol=$(sudo -u "$t_user" $user_dbus pactl get-sink-volume @DEFAULT_SINK@ 2>/dev/null | grep -oP '\d+%' | head -1) - - echo " Microphone: ${src_mute:-unknown} (Volume: ${src_vol:-unknown})" - echo " Speaker: ${sink_mute:-unknown} (Volume: ${sink_vol:-unknown})" - - if [[ "$src_mute" == "yes" ]]; then - echo "" - print_error "MICROPHONE IS MUTED - No audio will be sent!" - echo " To fix: pactl set-source-mute @DEFAULT_SOURCE@ 0" - fi - - echo "" - echo "PTT Configuration:" - if [[ -f /etc/easy-asterisk/ptt-device ]]; then - echo " PTT Mode: ENABLED" - source /etc/easy-asterisk/ptt-device 2>/dev/null - echo " Device: ${PTT_DEVICE:-not set}" - else - echo " PTT Mode: DISABLED (normal intercom mode)" - fi - - echo "---------------------------------------------------" - - echo "Network Interface:" - grep "^net_interface" "/home/$t_user/.baresip/config" 2>/dev/null || echo " Not set" - - echo "---------------------------------------------------" - echo "Account Config:" - cat "/home/$t_user/.baresip/accounts" 2>/dev/null | sed 's/auth_pass=[^;]*/auth_pass=***/' || echo " Not found" - - echo "---------------------------------------------------" - if [[ -n "$ASTERISK_HOST" ]]; then - echo -n "Server ($ASTERISK_HOST): " - if ping -c 1 -W 2 "$ASTERISK_HOST" >/dev/null 2>&1; then - print_success "Reachable" - else - print_error "Unreachable" - fi - fi - echo "---------------------------------------------------" - echo "System Logs (launcher):" - journalctl -t baresip-launcher -n 10 --no-pager 2>/dev/null | tail -10 || echo " No launcher logs" - echo "" - echo "System Logs (PTT):" - journalctl -t kiosk-ptt -n 5 --no-pager 2>/dev/null | tail -5 || echo " No PTT logs" - echo "" - echo "Baresip Service Log:" - sudo -u "$t_user" journalctl --user -u baresip -n 10 --no-pager 2>/dev/null || echo " No logs" - echo "---------------------------------------------------" - echo "" - echo "To see live logs, run:" - echo " journalctl -t baresip-launcher -f # Launcher logs" - echo " journalctl -t kiosk-ptt -f # PTT logs" - echo " sudo -u $t_user journalctl --user -u baresip -f # Baresip logs" - echo "---------------------------------------------------" -} - -run_audio_test() { - print_header "Audio Test" - echo "Playing test tone..." - speaker-test -t sine -f 440 -c 2 -l 1 >/dev/null 2>&1 - echo "" - read -p "Did you hear audio? [y/N]: " res - if [[ "$res" =~ ^[Yy]$ ]]; then - print_success "Audio OK" - else - print_error "Check volume/connections" - fi -} - -verify_audio_setup() { - print_header "Audio Verification" - echo "=== Codecs ===" - asterisk -rx "core show codecs" 2>/dev/null | grep -E "(opus|ulaw|alaw|g722)" || echo " N/A" - echo "" - echo "=== PJSIP Modules ===" - asterisk -rx "module show like pjsip" 2>/dev/null | head -10 || echo " N/A" - echo "" - echo "=== Certificate ===" - if [[ -f /etc/asterisk/certs/server.crt ]]; then - openssl x509 -in /etc/asterisk/certs/server.crt -noout -subject -dates 2>/dev/null - else - echo " None" - fi -} - -# ================================================================ -# 7. ASTERISK CONFIG -# ================================================================ - -fix_asterisk_systemd() { - print_info "Configuring systemd..." - mkdir -p /etc/systemd/system/asterisk.service.d/ - cat > /etc/systemd/system/asterisk.service.d/override.conf << 'SVCEOF' -[Unit] -Wants=network-online.target -After=network-online.target - -[Service] -ExecStart= -ExecStart=/usr/sbin/asterisk -f -U asterisk -G asterisk -RuntimeDirectory=asterisk -RuntimeDirectoryMode=0750 -MemoryMax=infinity -TasksMax=infinity -KillMode=mixed -KillSignal=SIGTERM -TimeoutStartSec=60 -TimeoutStopSec=30 -SendSIGKILL=no -Restart=always -RestartSec=10 -Type=simple -SVCEOF - systemctl daemon-reload -} - -recover_xml_docs() { - mkdir -p /var/lib/asterisk/documentation/thirdparty - chown -R asterisk:asterisk /var/lib/asterisk/documentation 2>/dev/null -} - -repair_core_configs() { - print_info "Repairing configs..." - - # Copy modules (not symlink - AppArmor blocks symlinks) - 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 - - mkdir -p /etc/asterisk /var/lib/asterisk /var/log/asterisk /var/spool/asterisk /var/run/asterisk - recover_xml_docs - - if [[ ! -f /etc/asterisk/asterisk.conf ]]; then - 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 - fi - - 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 => res_pjsip_logger.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 - - # Disable optional modules (NOT stasis - required in Asterisk 20.x) - for conf in ari http manager geolocation; do - cat > "/etc/asterisk/${conf}.conf" << EOF -[general] -enabled = no -EOF - done - - # Configure Stasis properly (required core module) - cat > /etc/asterisk/stasis.conf << EOF -[general] -; Stasis is required for Asterisk 20.x core functionality -EOF - - if [[ ! -f /etc/asterisk/sorcery.conf ]]; then - cat > /etc/asterisk/sorcery.conf << EOF -[res_pjsip] -endpoint=config,pjsip.conf,criteria=type=endpoint -auth=config,pjsip.conf,criteria=type=auth -aor=config,pjsip.conf,criteria=type=aor -transport=config,pjsip.conf,criteria=type=transport -EOF - fi - - # ICE and STUN only for FQDN/internet calling - load_config - local ice_stun_config="" - if [[ -n "$DOMAIN_NAME" ]]; then - ice_stun_config="icesupport=yes -stunaddr=stun.l.google.com:19302" - else - ice_stun_config="# icesupport disabled - LAN only mode" - fi - - cat > /etc/asterisk/rtp.conf << EOF -[general] -rtpstart=10000 -rtpend=20000 -strictrtp=yes -${ice_stun_config} -EOF - - cat > /etc/asterisk/logger.conf << EOF -[general] -[logfiles] -console => notice,warning,error -EOF - - rm -f /var/lib/asterisk/.asterisk_history - chown -R asterisk:asterisk /etc/asterisk /var/lib/asterisk /var/log/asterisk /var/spool/asterisk 2>/dev/null || true - chown -R asterisk:asterisk /usr/lib/asterisk/modules 2>/dev/null || true -} - -generate_pjsip_conf() { - print_info "Generating PJSIP..." - load_config - local conf_file="/etc/asterisk/pjsip.conf" - backup_config "$conf_file" - - # Prioritize CURRENT_PUBLIC_IP from coturn/updater if available, else detect - local public_ip="${CURRENT_PUBLIC_IP}" - if [[ -z "$public_ip" ]]; then - public_ip=$(curl -s -4 --connect-timeout 5 ifconfig.me 2>/dev/null || echo "") - fi - - 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 - - # Use stored CIDR if available - local local_net="${LOCAL_CIDR:-$default_cidr}" - - local nat_settings="" - if [[ -n "$public_ip" && -n "$DOMAIN_NAME" ]]; then - nat_settings="external_media_address=$public_ip -external_signaling_address=$public_ip -local_net=$local_net" - print_info "NAT: Public IP=$public_ip, Local=$local_net" - fi - - cat > "$conf_file" << EOF -; Easy Asterisk v1.23 -[global] -type=global -user_agent=EasyAsterisk - -[transport-udp] -type=transport -protocol=udp -bind=0.0.0.0:${DEFAULT_SIP_PORT} -${nat_settings} - -[transport-tcp] -type=transport -protocol=tcp -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 - - 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" - print_success "Restored devices from backup" - fi - chown asterisk:asterisk "$conf_file" -} - -rebuild_dialplan() { - local quiet=$1 - [[ "$quiet" != "quiet" ]] && print_info "Rebuilding 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_cat="" dev_auto="" dev_aa_override="" - while IFS= read -r line; do - if [[ "$line" == *"; === Device:"* ]]; then - dev_aa_override="" - local temp="${line#*; === Device: }" - temp="${temp% ===}" - if [[ "$temp" == *"[AA:yes]"* ]]; then - dev_aa_override="yes"; temp="${temp% [AA:yes]}" - elif [[ "$temp" == *"[AA:no]"* ]]; then - dev_aa_override="no"; temp="${temp% [AA:no]}" - fi - 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) - if [[ -n "$cat_data" ]]; then - local is_auto=$(echo "$cat_data" | cut -d'|' -f3) - [[ "$is_auto" == "yes" ]] && dev_auto="yes" - fi - [[ "$dev_aa_override" == "yes" ]] && dev_auto="yes" - [[ "$dev_aa_override" == "no" ]] && dev_auto="no" - fi - if [[ "$line" =~ ^\[([0-9]+)\] ]]; then - local ext="${BASH_REMATCH[1]}" - if [[ -n "$dev_name" ]]; then - 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 - fi - done < /etc/asterisk/pjsip.conf - - # Add rooms - if [[ -f "$ROOMS_FILE" ]]; then - while IFS='|' read -r rext rname rmem rtime rtype; do - [[ "$rext" =~ ^# ]] && continue - [[ -z "$rext" ]] && continue - local dial_list="" - IFS=',' read -ra EXTS <<< "$rmem" - for ext in "${EXTS[@]}"; do - ext=$(echo "$ext" | tr -d ' ') - [[ -n "$dial_list" ]] && dial_list="${dial_list}&" - dial_list="${dial_list}PJSIP/${ext}" - done - if [[ "$rtype" == "page" ]]; then - cat >> "$conf_file" << EOF -; Room: ${rname} (Page) -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 -; Room: ${rname} (Ring) -exten => ${rext},1,NoOp(Call ${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() { - if ! id asterisk >/dev/null 2>&1; then - useradd -r -s /bin/false -d /var/lib/asterisk asterisk 2>/dev/null || true - fi - - print_info "Configuring Asterisk..." - fix_asterisk_systemd - initialize_default_categories - repair_core_configs - - 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 - fi - - chown asterisk:asterisk /etc/asterisk/certs/server.* 2>/dev/null || true - chmod 644 /etc/asterisk/certs/server.crt 2>/dev/null || true - chmod 600 /etc/asterisk/certs/server.key 2>/dev/null || true - - generate_pjsip_conf - rebuild_dialplan "quiet" - - restart_asterisk_safe - systemctl enable asterisk -} - -restart_asterisk_safe() { - print_info "Restarting Asterisk..." - systemctl stop asterisk 2>/dev/null || true - sleep 2 - # Use -x for exact match to avoid killing this script - pkill -9 -x asterisk 2>/dev/null || true - rm -f /var/run/asterisk/asterisk.pid 2>/dev/null || true - rm -f /var/lib/asterisk/.asterisk_history 2>/dev/null || true - systemctl start asterisk - sleep 3 - - if systemctl is-active asterisk >/dev/null; then - print_success "Asterisk running" - else - print_error "Asterisk failed to start" - journalctl -u asterisk -n 15 --no-pager - fi -} - -# ================================================================ -# 8. CLIENT CONFIG -# ================================================================ - -configure_baresip() { - local baresip_dir="/home/${KIOSK_USER}/.baresip" - mkdir -p "$baresip_dir" - - # Detect network interface - local found_iface="" - for target in 8.8.8.8 1.1.1.1 9.9.9.9; do - local iface=$(ip route get "$target" 2>/dev/null | awk '{for(i=1;i<=NF;i++) if($i=="dev") print $(i+1)}' | head -1) - if [[ -n "$iface" ]]; then - found_iface="$iface" - print_success "Network interface: $found_iface" - break - fi - 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="" - if [[ "$ENABLE_TLS" == "y" ]]; then - transport="tls" - mediaenc=";mediaenc=srtp" - fi - - local amode="${CLIENT_ANSWERMODE:-auto}" - - cat > "${baresip_dir}/accounts" << EOF -;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() { - local launcher_user="${KIOSK_USER}" - cat > /usr/local/bin/easy-asterisk-launcher << LAUNCHER -#!/bin/bash -CONFIG_FILE="/home/${launcher_user}/.baresip/config" -ACCOUNTS_FILE="/home/${launcher_user}/.baresip/accounts" -TARGETS=("8.8.8.8" "1.1.1.1" "9.9.9.9") -FOUND_IFACE="" - -logger -t baresip-launcher "Starting Baresip launcher for user ${launcher_user}" - -# Wait for network -for i in {1..6}; do - for target in "\${TARGETS[@]}"; do - IFACE=\$(ip route get "\$target" 2>/dev/null | awk '{for(i=1;i<=NF;i++) if(\$i=="dev") print \$(i+1)}' | head -1) - if [[ -n "\$IFACE" ]]; then - FOUND_IFACE="\$IFACE" - logger -t baresip-launcher "Network found on interface: \$IFACE" - break 2 - fi - done - logger -t baresip-launcher "Waiting for network... (attempt \$i/6)" - sleep 5 -done - -if [[ -z "\$FOUND_IFACE" ]]; then - logger -t baresip-launcher "ERROR: No network interface found after 30 seconds" -fi - -# Update network interface in config -if [[ -f "\$CONFIG_FILE" && -n "\$FOUND_IFACE" ]]; then - sed -i '/^#*net_interface/d' "\$CONFIG_FILE" - echo "net_interface \${FOUND_IFACE}" >> "\$CONFIG_FILE" - logger -t baresip-launcher "Updated config with interface: \$FOUND_IFACE" -fi - -# Verify config files exist -if [[ ! -f "\$CONFIG_FILE" ]]; then - logger -t baresip-launcher "ERROR: Config file not found: \$CONFIG_FILE" - exit 1 -fi - -if [[ ! -f "\$ACCOUNTS_FILE" ]]; then - logger -t baresip-launcher "ERROR: Accounts file not found: \$ACCOUNTS_FILE" - exit 1 -fi - -logger -t baresip-launcher "Starting Baresip client..." -exec /usr/bin/baresip -f "/home/${launcher_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" - - # Ensure audio group membership - if ! id -nG "$KIOSK_USER" | grep -qw "audio"; then - usermod -aG audio "$KIOSK_USER" - fi - - # Ensure input group membership (for PTT device access) - if ! id -nG "$KIOSK_USER" | grep -qw "input"; then - usermod -aG input "$KIOSK_USER" - fi - - # Baresip service - cat > "${systemd_dir}/baresip.service" << EOF -[Unit] -Description=Baresip SIP Client -After=pipewire.service pipewire-pulse.service network-online.target -Wants=network-online.target pipewire.service pipewire-pulse.service -Requires=pipewire-pulse.service - -[Service] -Type=simple -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 -StandardOutput=journal -StandardError=journal - -[Install] -WantedBy=default.target -EOF - - # PTT service - only create if PTT is configured - cat > "${systemd_dir}/kiosk-ptt.service" << EOF -[Unit] -Description=PTT Button Handler -After=pipewire.service pipewire-pulse.service baresip.service -Requires=pipewire-pulse.service -ConditionPathExists=/etc/easy-asterisk/ptt-device - -[Service] -Type=simple -ExecStartPre=/bin/sleep 8 -ExecStart=/usr/local/bin/kiosk-ptt -Restart=always -RestartSec=10 -Environment=XDG_RUNTIME_DIR=/run/user/${KIOSK_UID} -Environment=DBUS_SESSION_BUS_ADDRESS=unix:path=/run/user/${KIOSK_UID}/bus -Environment=KIOSK_UID=${KIOSK_UID} -StandardOutput=journal -StandardError=journal - -[Install] -WantedBy=default.target -EOF - - chown -R ${KIOSK_USER}:${KIOSK_USER} "/home/${KIOSK_USER}/.config" - - if [[ -n "$KIOSK_USER" ]]; then - loginctl enable-linger $KIOSK_USER 2>/dev/null || true - local user_dbus="XDG_RUNTIME_DIR=/run/user/${KIOSK_UID}" - - # Enable and start PipeWire services for the user - sudo -u "$KIOSK_USER" $user_dbus systemctl --user daemon-reload - sudo -u "$KIOSK_USER" $user_dbus systemctl --user enable pipewire pipewire-pulse 2>/dev/null || true - sudo -u "$KIOSK_USER" $user_dbus systemctl --user restart pipewire pipewire-pulse 2>/dev/null || true - - # Enable baresip - sudo -u "$KIOSK_USER" $user_dbus systemctl --user enable baresip - - # Only enable PTT if configured - if [[ -f /etc/easy-asterisk/ptt-device ]]; then - sudo -u "$KIOSK_USER" $user_dbus systemctl --user enable kiosk-ptt - sudo -u "$KIOSK_USER" $user_dbus systemctl --user restart baresip kiosk-ptt - else - sudo -u "$KIOSK_USER" $user_dbus systemctl --user restart baresip - # Ensure audio is unmuted for normal kiosk operation - ensure_audio_unmuted - fi - fi -} - -# ================================================================ -# 9. CERTIFICATE HANDLING -# ================================================================ - -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 - [[ "$mode" == "force" ]] && print_header "Caddy Cert Sync" - - load_config - local domain=${DOMAIN_NAME:-sip.example.com} - if [[ "$mode" == "force" ]]; then - 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" - "/data/caddy" - "/root/.local/share/caddy/certificates" - ) - local caddy_cert="" caddy_key="" - - [[ "$mode" == "force" ]] && echo "Searching for certificates..." - - for base_path in "${search_paths[@]}"; do - if ! sudo test -d "$base_path" 2>/dev/null; then continue; fi - [[ "$mode" == "force" ]] && echo " Checking: $base_path" - - 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 - [[ "$mode" == "force" ]] && print_success "Found matching cert: $cert" - caddy_cert="$cert" - local dir=$(dirname "$cert") - local name=$(basename "$cert") - if [[ "$name" == "fullchain.pem" ]]; then - caddy_key="${dir}/privkey.pem" - else - caddy_key=$(echo "$cert" | sed 's/\.crt/\.key/') - fi - if sudo test -f "$caddy_key"; then - rm -f /tmp/cert_check.pem - break 2 - fi - 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" - ENABLE_TLS="y" - ASTERISK_HOST="$domain" - save_config - - generate_pjsip_conf - restart_asterisk_safe - - [[ "$mode" == "force" ]] && print_success "Certificates installed for $domain" - return 0 - else - [[ "$mode" == "force" ]] && print_warn "No matching certificates found" - return 1 - fi -} - -setup_internet_access() { - print_header "Setup Internet Access" - - echo "Select Certificate Source:" - echo " 1) Auto-Sync from Caddy (Docker/Native)" - echo " 2) Standalone Certbot (Requires Port 80 open)" - echo " 3) Self-Signed (Internal testing only)" - echo " 4) Manual Path" - echo " 0) Cancel" - read -p "Select: " cert_opt - - [[ "$cert_opt" == "0" ]] && return - - # Show port requirements - show_preflight_check - show_port_requirements - echo "" - read -p "Continue? [Y/n]: " cont - [[ "$cont" =~ ^[Nn]$ ]] && return - - load_config - read -p "FQDN [${DOMAIN_NAME:-sip.example.com}]: " fqdn - DOMAIN_NAME="${fqdn:-${DOMAIN_NAME:-sip.example.com}}" - ASTERISK_HOST="$DOMAIN_NAME" - - echo "" - echo "Do you have a separate domain for TURN? (e.g., turn.example.com)" - read -p "Enter TURN domain (leave empty to use $DOMAIN_NAME): " t_dom - TURN_DOMAIN="${t_dom:-$DOMAIN_NAME}" - - # CIDR Prompt - echo "" - print_header "Local Network CIDR" - 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 - echo "This helps Asterisk distinguish local vs external traffic." - read -p "Local network CIDR [$default_cidr]: " local_net - LOCAL_CIDR="${local_net:-$default_cidr}" - - save_config - - case "$cert_opt" in - 1) # Caddy - # Show Caddy Helper text - echo "---------------------------------------------------------" - echo "CADDY HELPER: Ensure these are in your Caddyfile to get certs:" - echo "" - echo "${DOMAIN_NAME} {" - echo " respond \"Asterisk Cert Placeholder\" 200" - echo "}" - if [[ "$TURN_DOMAIN" != "$DOMAIN_NAME" ]]; then - echo "" - echo "${TURN_DOMAIN} {" - echo " respond \"TURN Cert Placeholder\" 200" - echo "}" - fi - echo "" - echo "Restart Caddy, wait 30s, then press Enter." - echo "---------------------------------------------------------" - read -p "Press Enter to sync..." - if setup_caddy_cert_sync "auto"; then - print_success "Setup complete using Caddy certificates!" - else - print_error "Caddy sync failed. Ensure Caddy is running." - return - fi - ;; - 2) # Certbot - 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.* - print_success "Certbot Success" - else - print_error "Certbot failed" - return - fi - ;; - 3) # Self-Signed - 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.* - chmod 644 /etc/asterisk/certs/server.crt - chmod 600 /etc/asterisk/certs/server.key - print_success "Self-signed certificate generated" - print_warn "Clients will need to trust this certificate" - ;; - 4) # Manual - 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.* - print_success "Certificates installed" - else - print_error "Files not found!" - return - fi - ;; - esac - - ENABLE_TLS="y" - save_config - generate_pjsip_conf - restart_asterisk_safe - print_success "Internet access configuration complete" -} - -# ================================================================ -# 10. 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") - - if ! collect_common_config; then return; fi - collect_client_config - install_dependencies - INSTALLED_SERVER="y" - INSTALLED_CLIENT="y" - ENABLE_TLS="n" # LAN-only by default, set to "y" only if internet/certs setup is run - configure_asterisk - configure_baresip - enable_client_services - open_firewall_ports - save_config - - echo "" - echo "════════════════════════════════════════════════════════" - print_success "Local network install complete" - echo "" - echo "Server and devices are reachable over internal LAN network only." - echo "To add internet calling capability, continue with the setup below." - echo "════════════════════════════════════════════════════════" - echo "" - read -p "Run Internet/Certificate Setup wizard now? [Y/n]: " run_setup - [[ ! "$run_setup" =~ ^[Nn]$ ]] && setup_internet_access - - print_success "Installation complete" -} - -install_server_only() { - print_header "Server Installation" - ASTERISK_HOST="127.0.0.1" - ENABLE_TLS="n" # LAN-only by default, set to "y" only if internet/certs setup is run - install_asterisk_packages - configure_asterisk - open_firewall_ports - INSTALLED_SERVER="y" - save_config - - echo "" - echo "════════════════════════════════════════════════════════" - print_success "Local network install complete" - echo "" - echo "Server and devices are reachable over internal LAN network only." - echo "To add internet calling capability, continue with the setup below." - echo "════════════════════════════════════════════════════════" - echo "" - read -p "Run Internet/Certificate Setup wizard now? [Y/n]: " run_setup - [[ ! "$run_setup" =~ ^[Nn]$ ]] && setup_internet_access - - print_success "Server installed" -} - -install_client_only() { - print_header "Client Installation" - echo "Select the user to install the kiosk client for:" - echo "" - - if ! select_user; then - print_error "User selection failed" - return 1 - fi - - echo "" - read -p "Server (IP or domain): " ASTERISK_HOST - read -p "SIP Password: " SIP_PASSWORD - - if [[ "$ASTERISK_HOST" =~ [a-zA-Z] ]]; then - ENABLE_TLS="y" - else - ENABLE_TLS="n" - fi - - echo "" - echo "Answer Mode:" - echo " 1) Auto (auto-answer incoming calls)" - echo " 2) Manual (ring on incoming)" - read -p "Select [1]: " aa_sel - CLIENT_ANSWERMODE="auto" - [[ "$aa_sel" == "2" ]] && CLIENT_ANSWERMODE="manual" - - collect_client_config - install_baresip_packages - INSTALLED_CLIENT="y" - configure_baresip - enable_client_services - save_config - - print_success "Client installed" - echo "" - echo "════════════════════════════════════════════════════════" - echo " IMPORTANT: Audio Configuration" - echo "════════════════════════════════════════════════════════" - echo " User: $KIOSK_USER" - echo " - Audio group: Added" - echo " - PipeWire services: Enabled" - echo " - Microphone: Unmuted (for intercom mode)" - echo "" - echo " If audio doesn't work immediately:" - echo " 1. Log out and log back in as '$KIOSK_USER'" - echo " 2. Or reboot the system" - echo " 3. Check audio with: pactl list sources short" - echo "" - echo " PTT Mode: Not configured (normal intercom operation)" - echo " To configure PTT: Main Menu > Client Management > Configure PTT Button" - echo "════════════════════════════════════════════════════════" -} - -collect_common_config() { - SIP_PASSWORD="${SIP_PASSWORD:-$(generate_password)}" - ASTERISK_HOST="127.0.0.1" - return 0 -} - -collect_client_config() { - read -p "Extension [101]: " KIOSK_EXTENSION - KIOSK_EXTENSION="${KIOSK_EXTENSION:-101}" - KIOSK_NAME="kiosk-${KIOSK_EXTENSION}" -} - -install_dependencies() { - install_asterisk_packages - install_baresip_packages -} - -install_asterisk_packages() { - echo "exit 101" > /usr/sbin/policy-rc.d - chmod +x /usr/sbin/policy-rc.d - apt update - # asterisk-opus removed (included in asterisk-modules on Ubuntu 24.04+) - apt install -y asterisk asterisk-core-sounds-en-gsm asterisk-modules openssl curl tcpdump sngrep || true - mkdir -p /var/lib/asterisk /var/log/asterisk /var/spool/asterisk /var/run/asterisk - ldconfig - update-ca-certificates 2>/dev/null || 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" - echo " 1) Remove Everything" - echo " 2) Asterisk Only" - echo " 3) Baresip Only" - echo " 0) Cancel" - read -p "Select: " ch - case $ch in - 1) - systemctl stop asterisk 2>/dev/null || true - apt purge -y asterisk* baresip baresip-core 2>/dev/null || true - rm -rf /etc/asterisk /var/lib/asterisk /var/log/asterisk /var/spool/asterisk /usr/lib/asterisk - rm -rf /etc/systemd/system/asterisk.service.d /etc/easy-asterisk - [[ -n "$KIOSK_USER" ]] && rm -rf "/home/${KIOSK_USER}/.baresip" - systemctl daemon-reload - INSTALLED_SERVER="n" - INSTALLED_CLIENT="n" - rm -f "$CONFIG_FILE" - print_success "Removed all" - ;; - 2) - systemctl stop asterisk 2>/dev/null || true - apt purge -y asterisk* 2>/dev/null || true - rm -rf /etc/asterisk /var/lib/asterisk - INSTALLED_SERVER="n" - save_config - print_success "Removed Asterisk" - ;; - 3) - apt purge -y baresip baresip-core 2>/dev/null || true - [[ -n "$KIOSK_USER" ]] && rm -rf "/home/${KIOSK_USER}/.baresip" - INSTALLED_CLIENT="n" - save_config - print_success "Removed Baresip" - ;; - esac -} - -# ================================================================ -# 11. MENU SYSTEM (Reordered: Server #2, Devices #3) -# ================================================================ - -show_main_menu() { - clear - print_header "Easy Asterisk v1.23" - - load_config - echo " Status:" - if [[ -f "$CONFIG_FILE" ]]; then - [[ "$INSTALLED_SERVER" == "y" ]] && echo -e " Server: ${GREEN}Installed${NC}" || echo -e " Server: ${YELLOW}Not installed${NC}" - [[ "$INSTALLED_CLIENT" == "y" ]] && echo -e " Client: ${GREEN}Installed${NC}" || echo -e " Client: ${YELLOW}Not installed${NC}" - [[ "$INSTALLED_COTURN" == "y" ]] && echo -e " COTURN: ${GREEN}Installed${NC}" || echo -e " COTURN: ${YELLOW}Not installed${NC}" - [[ -n "$DOMAIN_NAME" ]] && echo -e " Domain: ${DOMAIN_NAME}" - else - echo -e " ${YELLOW}Not configured${NC}" - fi - echo "" - - declare -A menu_map - local count=1 - - echo " ${count}) Install/Configure"; menu_map[$count]="submenu_install"; ((count++)) - if [[ "$INSTALLED_SERVER" == "y" ]]; then - echo " ${count}) Server Settings"; menu_map[$count]="submenu_server"; ((count++)) - echo " ${count}) Device Management"; menu_map[$count]="submenu_devices"; ((count++)) - fi - echo " ${count}) Client Settings"; menu_map[$count]="submenu_client"; ((count++)) - echo " ${count}) Tools"; menu_map[$count]="submenu_tools"; ((count++)) - 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_server() { - clear - print_header "Server Settings" - echo " 1) Setup Internet Access (TLS/Certs/NAT)" - echo " 2) Force re-sync Caddy certs" - echo " 3) Show port/firewall requirements" - echo " 4) Interactive Firewall Guide" - echo " 5) Test SIP connectivity" - echo " 6) Verify CIDR/NAT config" - echo " 7) Watch Live Logs" - echo " 8) Router Doctor" - echo " 9) Configure TURN Server (COTURN)" - echo " 0) Back" - read -p " Select: " choice - case $choice in - 1) setup_internet_access ;; - 2) setup_caddy_cert_sync "force" ;; - 3) show_port_requirements ;; - 4) show_firewall_guide ;; - 5) test_sip_connectivity ;; - 6) verify_cidr_config ;; - 7) watch_live_logs ;; - 8) router_doctor ;; - 9) configure_coturn_menu ;; - 0) return ;; - esac - [[ "$choice" != "0" ]] && read -p "Press Enter..." - [[ "$choice" != "0" ]] && submenu_server -} - -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 -} - -export_clients() { - print_header "Export Client Configurations" - load_config - initialize_default_categories - - # Create export directory - local timestamp=$(date +%Y%m%d_%H%M%S) - local export_dir="/tmp/asterisk_export_${timestamp}" - local export_file="/root/asterisk-clients-${timestamp}.tar.gz" - - mkdir -p "$export_dir" - - # Check if there are any devices to export - if ! grep -q "^; === Device:" /etc/asterisk/pjsip.conf 2>/dev/null; then - print_error "No client devices found to export" - rm -rf "$export_dir" - return - fi - - # Export devices from pjsip.conf (everything after transport definitions) - echo "Extracting client devices..." - awk '/^; === Device:/{flag=1} flag' /etc/asterisk/pjsip.conf > "$export_dir/devices.conf" - - # Count devices - local device_count=$(grep -c "^; === Device:" "$export_dir/devices.conf") - - # Export categories - if [[ -f "$CATEGORIES_FILE" ]]; then - echo "Exporting categories..." - cp "$CATEGORIES_FILE" "$export_dir/categories.conf" - fi - - # Export rooms - if [[ -f "$ROOMS_FILE" ]]; then - echo "Exporting rooms..." - cp "$ROOMS_FILE" "$export_dir/rooms.conf" - fi - - # Create metadata file - cat > "$export_dir/export_info.txt" << EOF -Easy Asterisk Client Export -Export Date: $(date) -Device Count: $device_count -Domain: ${DOMAIN_NAME:-Not configured} -TLS Enabled: ${ENABLE_TLS:-no} -Exported by: $(whoami) -Hostname: $(hostname) -EOF - - # Create tar.gz archive - echo "Creating archive..." - tar -czf "$export_file" -C /tmp "asterisk_export_${timestamp}" 2>/dev/null - - # Cleanup temp directory - rm -rf "$export_dir" - - if [[ -f "$export_file" ]]; then - print_success "Export completed successfully!" - echo "" - echo " Exported: $device_count devices" - echo " File: $export_file" - echo " Size: $(du -h "$export_file" | cut -f1)" - echo "" - echo " To import on another system:" - echo " 1) Copy file to the target server" - echo " 2) Run Easy Asterisk" - echo " 3) Select 'Client Settings' -> 'Import Clients'" - else - print_error "Export failed" - fi -} - -import_clients() { - print_header "Import Client Configurations" - load_config - initialize_default_categories - - echo "Available export files in /root:" - local files=($(ls -t /root/asterisk-clients-*.tar.gz 2>/dev/null)) - - if [[ ${#files[@]} -eq 0 ]]; then - echo "" - read -p "Enter full path to export file: " import_file - else - echo "" - local i=1 - for f in "${files[@]}"; do - echo " $i) $(basename "$f") - $(du -h "$f" | cut -f1) - $(date -r "$f" '+%Y-%m-%d %H:%M')" - ((i++)) - done - echo " 0) Enter custom path" - echo "" - read -p "Select file [1]: " file_choice - file_choice="${file_choice:-1}" - - if [[ "$file_choice" == "0" ]]; then - read -p "Enter full path to export file: " import_file - elif [[ "$file_choice" -ge 1 && "$file_choice" -le ${#files[@]} ]]; then - import_file="${files[$((file_choice-1))]}" - else - print_error "Invalid selection" - return - fi - fi - - if [[ ! -f "$import_file" ]]; then - print_error "File not found: $import_file" - return - fi - - # Extract to temp directory - local timestamp=$(date +%Y%m%d_%H%M%S) - local import_dir="/tmp/asterisk_import_${timestamp}" - mkdir -p "$import_dir" - - echo "Extracting archive..." - tar -xzf "$import_file" -C "$import_dir" 2>/dev/null - - # Find the extracted directory - local extract_dir=$(find "$import_dir" -type d -name "asterisk_export_*" | head -1) - if [[ ! -d "$extract_dir" ]]; then - print_error "Invalid export file format" - rm -rf "$import_dir" - return - fi - - # Show export info - if [[ -f "$extract_dir/export_info.txt" ]]; then - echo "" - echo "═══════════════════════════════════════════════════════════════" - cat "$extract_dir/export_info.txt" - echo "═══════════════════════════════════════════════════════════════" - echo "" - fi - - # Count devices to import - local device_count=0 - if [[ -f "$extract_dir/devices.conf" ]]; then - device_count=$(grep -c "^; === Device:" "$extract_dir/devices.conf") - fi - - if [[ $device_count -eq 0 ]]; then - print_error "No devices found in export file" - rm -rf "$import_dir" - return - fi - - echo "This will import $device_count device(s)." - echo "" - read -p "Import mode [1=Merge, 2=Replace All]: " import_mode - import_mode="${import_mode:-1}" - - if [[ "$import_mode" == "2" ]]; then - echo "" - echo "${RED}WARNING: This will DELETE ALL existing devices!${NC}" - read -p "Type 'DELETE ALL' to confirm: " confirm - if [[ "$confirm" != "DELETE ALL" ]]; then - print_error "Import cancelled" - rm -rf "$import_dir" - return - fi - fi - - # Backup existing configurations - echo "Backing up current configuration..." - backup_config "/etc/asterisk/pjsip.conf" - backup_config "$CATEGORIES_FILE" - backup_config "$ROOMS_FILE" - - # Import devices - if [[ "$import_mode" == "2" ]]; then - # Replace mode - remove all existing devices - echo "Removing existing devices..." - local temp_pjsip="/tmp/pjsip_base_${timestamp}.conf" - awk '/^; === Device:/{exit} {print}' /etc/asterisk/pjsip.conf > "$temp_pjsip" - cat "$temp_pjsip" "$extract_dir/devices.conf" > /etc/asterisk/pjsip.conf - rm -f "$temp_pjsip" - print_success "Replaced all devices with imported devices" - else - # Merge mode - check for conflicts - echo "Checking for extension conflicts..." - local conflicts=0 - local conflict_list="" - - while IFS= read -r line; do - if [[ "$line" =~ ^\[([0-9]+)\]$ ]]; then - local ext="${BASH_REMATCH[1]}" - if grep -q "^\[${ext}\]" /etc/asterisk/pjsip.conf 2>/dev/null; then - conflicts=$((conflicts + 1)) - conflict_list="${conflict_list}${ext} " - fi - fi - done < "$extract_dir/devices.conf" - - if [[ $conflicts -gt 0 ]]; then - echo "" - echo "${YELLOW}Warning: Found $conflicts conflicting extension(s): $conflict_list${NC}" - read -p "Skip conflicting devices? [Y/n]: " skip_conflicts - skip_conflicts="${skip_conflicts:-Y}" - - if [[ ! "$skip_conflicts" =~ ^[Yy]$ ]]; then - print_error "Import cancelled" - rm -rf "$import_dir" - return - fi - - # Import only non-conflicting devices - echo "Importing non-conflicting devices..." - local temp_import="/tmp/import_filtered_${timestamp}.conf" - local skip_device=0 - - while IFS= read -r line; do - if [[ "$line" == "; === Device:"* ]]; then - skip_device=0 - echo "$line" >> "$temp_import" - elif [[ "$line" =~ ^\[([0-9]+)\]$ ]]; then - local ext="${BASH_REMATCH[1]}" - if grep -q "^\[${ext}\]" /etc/asterisk/pjsip.conf 2>/dev/null; then - skip_device=1 - echo " Skipping extension $ext (already exists)" - else - echo "$line" >> "$temp_import" - fi - elif [[ $skip_device -eq 0 ]]; then - echo "$line" >> "$temp_import" - fi - done < "$extract_dir/devices.conf" - - cat "$temp_import" >> /etc/asterisk/pjsip.conf - rm -f "$temp_import" - else - # No conflicts, import all - echo "No conflicts found, importing all devices..." - cat "$extract_dir/devices.conf" >> /etc/asterisk/pjsip.conf - fi - - print_success "Devices imported successfully" - fi - - # Import categories (merge, skip duplicates) - if [[ -f "$extract_dir/categories.conf" ]]; then - echo "Importing categories..." - while IFS='|' read -r cat_id cat_name auto_answer description; do - [[ "$cat_id" =~ ^# ]] && continue - [[ -z "$cat_id" ]] && continue - - # Skip if already exists - if grep -q "^${cat_id}|" "$CATEGORIES_FILE" 2>/dev/null; then - echo " Skipping category '$cat_id' (already exists)" - else - echo "${cat_id}|${cat_name}|${auto_answer}|${description}" >> "$CATEGORIES_FILE" - echo " Imported category: $cat_name" - fi - done < "$extract_dir/categories.conf" - fi - - # Import rooms (merge, skip duplicates) - if [[ -f "$extract_dir/rooms.conf" ]]; then - echo "Importing rooms..." - while IFS='|' read -r ext name members timeout type; do - [[ "$ext" =~ ^# ]] && continue - [[ -z "$ext" ]] && continue - - # Skip if already exists - if grep -q "^${ext}|" "$ROOMS_FILE" 2>/dev/null; then - echo " Skipping room '$name' (extension $ext already exists)" - else - echo "${ext}|${name}|${members}|${timeout}|${type}" >> "$ROOMS_FILE" - echo " Imported room: $name (ext $ext)" - fi - done < "$extract_dir/rooms.conf" - fi - - # Cleanup - rm -rf "$import_dir" - - # Reload Asterisk - echo "" - echo "Reloading Asterisk configuration..." - asterisk -rx "pjsip reload" >/dev/null 2>&1 - rebuild_dialplan quiet - - print_success "Import completed successfully!" - echo "" - echo " Run 'List devices' to verify imported clients" -} - -submenu_client() { - clear - print_header "Client Settings" - echo " 1) Configure Local Client" - echo " 2) Configure PTT Button" - echo " 3) Run Diagnostics" - echo " 4) Export Clients" - echo " 5) Import Clients" - echo " 0) Back" - read -p " Select: " choice - case $choice in - 1) configure_local_client ;; - 2) configure_ptt_menu ;; - 3) run_client_diagnostics ;; - 4) export_clients ;; - 5) import_clients ;; - 0) return ;; - esac - [[ "$choice" != "0" ]] && read -p "Press Enter..." - [[ "$choice" != "0" ]] && submenu_client -} - -fix_audio_manually() { - print_header "Manual Audio Fix" - 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" - - echo "Fixing audio for user: $t_user" - echo "" - - # Restart PipeWire services - echo "Restarting PipeWire services..." - sudo -u "$t_user" $user_dbus systemctl --user restart pipewire pipewire-pulse 2>/dev/null || true - sleep 2 - - # Unmute audio - echo "Unmuting audio sources and sinks..." - sudo -u "$t_user" $user_dbus pactl set-source-mute @DEFAULT_SOURCE@ 0 2>/dev/null && echo " ✓ Microphone unmuted" || echo " ✗ Failed to unmute microphone" - sudo -u "$t_user" $user_dbus pactl set-sink-mute @DEFAULT_SINK@ 0 2>/dev/null && echo " ✓ Speaker unmuted" || echo " ✗ Failed to unmute speaker" - - # Set volume - echo "Setting volume levels to 75%..." - sudo -u "$t_user" $user_dbus pactl set-source-volume @DEFAULT_SOURCE@ 75% 2>/dev/null && echo " ✓ Microphone volume set" || echo " ✗ Failed to set microphone volume" - sudo -u "$t_user" $user_dbus pactl set-sink-volume @DEFAULT_SINK@ 75% 2>/dev/null && echo " ✓ Speaker volume set" || echo " ✗ Failed to set speaker volume" - - echo "" - echo "Current audio status:" - local src_mute=$(sudo -u "$t_user" $user_dbus pactl get-source-mute @DEFAULT_SOURCE@ 2>/dev/null | awk '{print $2}') - local sink_mute=$(sudo -u "$t_user" $user_dbus pactl get-sink-mute @DEFAULT_SINK@ 2>/dev/null | awk '{print $2}') - echo " Microphone: ${src_mute:-unknown}" - echo " Speaker: ${sink_mute:-unknown}" - - echo "" - echo "Restarting Baresip..." - sudo -u "$t_user" $user_dbus systemctl --user restart baresip 2>/dev/null && echo " ✓ Baresip restarted" || echo " ✗ Failed to restart Baresip" -} - -submenu_tools() { - clear - print_header "Tools" - echo " 1) Audio Test" - echo " 2) Verify Audio/Codec Setup" - echo " 3) Fix Audio (Unmute & Restart)" - echo " 0) Back" - read -p " Select: " choice - case $choice in - 1) run_audio_test ;; - 2) verify_audio_setup ;; - 3) fix_audio_manually ;; - 0) return ;; - esac - [[ "$choice" != "0" ]] && read -p "Press Enter..." - [[ "$choice" != "0" ]] && submenu_tools -} - -main() { - check_root - load_config - show_main_menu -} - -main "$@"