Added intelligent user selection that scans /home directory and
presents available users, making it much easier to install the
kiosk client for the correct user account.
Features:
1. Smart User Detection
- Scans /etc/passwd for real users (UID >= 1000)
- Excludes system accounts (nologin, /bin/false)
- Shows username, UID, and home directory
- Presents numbered list of available users
2. Intelligent Defaults
- Auto-suggests SUDO_USER if available
- Falls back to first detected user
- Highlights suggested choice as default
3. Manual Override
- Option to manually enter username
- Validates entered username exists
- Shows confirmation with UID
4. Improved User Experience
- install_client_only(): Uses new select_user function
- configure_local_client(): Shows current user, offers to change
- Clear feedback on user selection
- Proper error handling for invalid selections
5. Verified Ownership
All user configs are properly owned by selected user:
- ~/.baresip/config (chown in configure_baresip:1879)
- ~/.baresip/accounts (chown in configure_baresip:1879)
- ~/.config/systemd/user/* (chown in enable_client_services:1962)
- ~/.config/wireplumber/* (chown in configure_audio_ducking:1067)
- All PipeWire/audio operations run as selected user
Example Flow:
$ sudo ./easy-asterisk-interactive-v1.28.sh
> Install Client Only
Scanning for users...
1) kiosk (UID: 1000, Home: /home/kiosk)
2) admin (UID: 1001, Home: /home/admin)
3) Enter username manually
Select user [1]: 1
✓ Selected user: kiosk (UID: 1000)
This makes it much easier to install on kiosk systems where you
might not remember the exact username, and ensures all configs
are created with proper ownership from the start.
2538 lines
84 KiB
Bash
2538 lines
84 KiB
Bash
#!/bin/bash
|
|
# ================================================================
|
|
# Easy Asterisk - Interactive Installer v1.28
|
|
#
|
|
# UPDATES in v1.28:
|
|
# - FIXED: Device display now correctly shows UDP/5060 for LAN-only installs
|
|
# - FIXED: Device display shows TLS/5061 only when internet/certs configured
|
|
# - FIXED: SRTP shown as "Required" only with TLS, "Not required" for UDP
|
|
# - FIXED: ICE support only enabled for FQDN/internet calling, not LAN-only
|
|
# - FIXED: RTP config - icesupport and STUN only enabled for FQDN setups
|
|
# - ADDED: Informative message after server install before internet setup prompt
|
|
# - ADDED: load_config call in add_device_menu to properly read saved settings
|
|
#
|
|
# This fixes the issue where LAN-only servers incorrectly showed TLS/SRTP
|
|
# requirements when adding devices, preventing proper UDP registration.
|
|
#
|
|
# RETAINED from v1.23:
|
|
# - Router Guide separates VLAN (Allow) vs WAN (NAT)
|
|
# - Caddy Helper generates snippets for both SIP and TURN domains
|
|
# - PTT Mute-default, Audio Ducking, Device Management
|
|
# ================================================================
|
|
|
|
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"
|
|
|
|
# ================================================================
|
|
# 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"
|
|
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 600 "$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 600 "$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
|
|
|
|
# SDES-SRTP for compatibility
|
|
local encryption_block=""
|
|
if [[ "$ENABLE_TLS" == "y" ]]; then
|
|
encryption_block="media_encryption=sdes
|
|
transport=transport-tls"
|
|
fi
|
|
|
|
# ICE support only for FQDN/internet calling
|
|
local ice_block=""
|
|
if [[ -n "$DOMAIN_NAME" ]]; then
|
|
ice_block="ice_support=yes"
|
|
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
|
|
asterisk -rx "pjsip reload" >/dev/null 2>&1
|
|
rebuild_dialplan
|
|
|
|
echo "════════════════════════════════════════════════════════"
|
|
echo " DEVICE ADDED"
|
|
echo "════════════════════════════════════════════════════════"
|
|
echo " Server: ${DOMAIN_NAME:-$(hostname -I | awk '{print $1}')}"
|
|
echo " Extension: $ext"
|
|
echo " Password: $pass"
|
|
if [[ "$ENABLE_TLS" == "y" ]]; then
|
|
echo " Transport: TLS (port 5061)"
|
|
echo " SRTP: Required"
|
|
else
|
|
echo " Transport: UDP (port 5060)"
|
|
echo " SRTP: Not required"
|
|
fi
|
|
if [[ "$USE_COTURN" == "y" ]]; then
|
|
echo " TURN: ${TURN_DOMAIN:-${DOMAIN_NAME:-$CURRENT_PUBLIC_IP}}:${DEFAULT_TURN_PORT}"
|
|
fi
|
|
echo ""
|
|
echo " Config: ENABLE_TLS=$ENABLE_TLS, DOMAIN_NAME=${DOMAIN_NAME:-<not set>}"
|
|
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
|
|
|
|
# Save configuration
|
|
mkdir -p "$(dirname "$PTT_CONFIG_FILE")"
|
|
cat > "$PTT_CONFIG_FILE" << EOF
|
|
PTT_DEVICE="$PTT_DEVICE"
|
|
PTT_KEYCODE="$PTT_KEYCODE"
|
|
PTT_KEYNAME="$PTT_KEYNAME"
|
|
EOF
|
|
chmod 600 "$PTT_CONFIG_FILE"
|
|
|
|
# Also save to main config
|
|
save_config
|
|
|
|
print_success "PTT configured: $PTT_KEYNAME on $(basename $PTT_DEVICE)"
|
|
|
|
# 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}"
|
|
sudo -u "$KIOSK_USER" $user_dbus systemctl --user restart kiosk-ptt 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"
|
|
|
|
# Exit if no PTT device configured - leave audio unmuted for normal kiosk operation
|
|
[[ -z "$PTT_DEVICE" ]] && exit 0
|
|
|
|
# PTT mode: Mute audio source on start, unmute only when button pressed
|
|
pactl set-source-mute @DEFAULT_SOURCE@ 1
|
|
|
|
# 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
|
|
fi
|
|
if [[ "$line" =~ "value 0" ]]; then
|
|
pactl set-source-mute @DEFAULT_SOURCE@ 1
|
|
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: OPNsense, pfSense, or Advanced Routers"
|
|
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
|
|
<sip:${ext}@${server};transport=${transport_str}>;auth_pass=${pass};answermode=${answermode}${media_enc}
|
|
EOF
|
|
chown ${KIOSK_USER}:${KIOSK_USER} "/home/${KIOSK_USER}/.baresip/accounts"
|
|
|
|
local user_dbus="XDG_RUNTIME_DIR=/run/user/${KIOSK_UID}"
|
|
sudo -u "${KIOSK_USER}" $user_dbus systemctl --user restart baresip 2>/dev/null
|
|
|
|
print_success "Client Updated & Restarted"
|
|
}
|
|
|
|
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 "Last 15 lines of log:"
|
|
sudo -u "$t_user" journalctl --user -u baresip -n 15 --no-pager 2>/dev/null || echo " No 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
|
|
|
|
for conf in stasis ari http manager geolocation; do
|
|
cat > "/etc/asterisk/${conf}.conf" << EOF
|
|
[general]
|
|
enabled = no
|
|
EOF
|
|
done
|
|
|
|
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
|
|
<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() {
|
|
local launcher_user="${KIOSK_USER}"
|
|
cat > /usr/local/bin/easy-asterisk-launcher << LAUNCHER
|
|
#!/bin/bash
|
|
CONFIG_FILE="/home/${launcher_user}/.baresip/config"
|
|
TARGETS=("8.8.8.8" "1.1.1.1" "9.9.9.9")
|
|
FOUND_IFACE=""
|
|
|
|
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"
|
|
break 2
|
|
fi
|
|
done
|
|
sleep 5
|
|
done
|
|
|
|
if [[ -f "\$CONFIG_FILE" && -n "\$FOUND_IFACE" ]]; then
|
|
sed -i '/^#*net_interface/d' "\$CONFIG_FILE"
|
|
echo "net_interface \${FOUND_IFACE}" >> "\$CONFIG_FILE"
|
|
fi
|
|
|
|
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
|
|
|
|
# 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
|
|
|
|
[Service]
|
|
Type=simple
|
|
ExecStart=/usr/local/bin/easy-asterisk-launcher
|
|
Restart=always
|
|
RestartSec=5
|
|
Environment=XDG_RUNTIME_DIR=/run/user/${KIOSK_UID}
|
|
|
|
[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
|
|
ConditionPathExists=/etc/easy-asterisk/ptt-device
|
|
|
|
[Service]
|
|
Type=simple
|
|
ExecStartPre=/bin/sleep 3
|
|
ExecStart=/usr/local/bin/kiosk-ptt
|
|
Restart=always
|
|
RestartSec=5
|
|
Environment=XDG_RUNTIME_DIR=/run/user/${KIOSK_UID}
|
|
|
|
[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 > Configure PTT"
|
|
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 (OPNsense/pfSense)"
|
|
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
|
|
}
|
|
|
|
submenu_client() {
|
|
clear
|
|
print_header "Client Settings"
|
|
echo " 1) Configure Local Client"
|
|
echo " 2) Configure PTT Button"
|
|
echo " 3) Run Diagnostics"
|
|
echo " 0) Back"
|
|
read -p " Select: " choice
|
|
case $choice in
|
|
1) configure_local_client ;;
|
|
2) configure_ptt_menu ;;
|
|
3) run_client_diagnostics ;;
|
|
0) return ;;
|
|
esac
|
|
[[ "$choice" != "0" ]] && read -p "Press Enter..."
|
|
[[ "$choice" != "0" ]] && submenu_client
|
|
}
|
|
|
|
submenu_tools() {
|
|
clear
|
|
print_header "Tools"
|
|
echo " 1) Audio Test"
|
|
echo " 2) Verify Audio/Codec Setup"
|
|
echo " 0) Back"
|
|
read -p " Select: " choice
|
|
case $choice in
|
|
1) run_audio_test ;;
|
|
2) verify_audio_setup ;;
|
|
0) return ;;
|
|
esac
|
|
[[ "$choice" != "0" ]] && read -p "Press Enter..."
|
|
[[ "$choice" != "0" ]] && submenu_tools
|
|
}
|
|
|
|
main() {
|
|
check_root
|
|
load_config
|
|
show_main_menu
|
|
}
|
|
|
|
main "$@"
|