Compare commits
11
Commits
de46202ab4
...
d36f99a679
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
d36f99a679 | ||
|
|
8c47694a1a | ||
|
|
3806bef558 | ||
|
|
ad48517b80 | ||
|
|
87a98ec356 | ||
|
|
e1dd727521 | ||
|
|
1b69ddaa98 | ||
|
|
168054f99a | ||
|
|
29d47ec819 | ||
|
|
e6ef37b6bc | ||
|
|
114391fbfd |
+6
-5
@@ -17,7 +17,7 @@
|
||||
# - All SIP clients connect to DOMAIN_NAME:5061 (TLS)
|
||||
# - coturn handles NAT traversal (STUN) and media relay (TURN)
|
||||
# - Works from any network: LAN, cellular, Proton VPN, hotel WiFi
|
||||
# - TURN credentials are auto-generated if TURN_PASSWORD is empty
|
||||
# - Set TURN_PASSWORD below (generate one: openssl rand -base64 18)
|
||||
# ================================================================
|
||||
|
||||
# ── Domain Name (REQUIRED) ────────────────────────────────────
|
||||
@@ -60,11 +60,12 @@ VLAN_SUBNETS=
|
||||
|
||||
# ── TURN/STUN Settings ──────────────────────────────────────
|
||||
# Used by coturn for TURN relay authentication.
|
||||
# Both coturn and Asterisk must use the SAME password.
|
||||
# If empty, both default to "changeme" — set a real password here.
|
||||
# If empty, defaults to "changeme" — set a real password for security.
|
||||
# Generate one with: openssl rand -base64 18
|
||||
#
|
||||
# These credentials are shared between coturn and Asterisk.
|
||||
# SIP clients do NOT need these - only the server uses them.
|
||||
# These credentials are for coturn only. SIP clients that need TURN
|
||||
# relay (behind strict NAT) must configure the same credentials in
|
||||
# their SIP app settings.
|
||||
TURN_USERNAME=easyasterisk
|
||||
TURN_PASSWORD=
|
||||
|
||||
|
||||
+10
-9
@@ -24,7 +24,7 @@ services:
|
||||
network_mode: host
|
||||
depends_on:
|
||||
coturn:
|
||||
condition: service_healthy
|
||||
condition: service_started
|
||||
volumes:
|
||||
- asterisk-config:/etc/asterisk
|
||||
- easy-asterisk-config:/etc/easy-asterisk
|
||||
@@ -85,12 +85,19 @@ services:
|
||||
# The coturn image runs as nobody:nogroup by default, which cannot
|
||||
# create /var/run/turnserver.pid. Run as root to avoid this.
|
||||
user: root
|
||||
# Custom entrypoint bypasses the coturn image's fragile eval-based
|
||||
# entrypoint which causes "Unknown argument:" errors when IP detection
|
||||
# returns empty. Our wrapper handles detection robustly.
|
||||
entrypoint: ["/coturn-entrypoint.sh"]
|
||||
volumes:
|
||||
- ./docker/coturn-entrypoint.sh:/coturn-entrypoint.sh:ro
|
||||
environment:
|
||||
# Image-native external IP detection (adds --external-ip automatically)
|
||||
- DETECT_EXTERNAL_IP=${DETECT_EXTERNAL_IP:-yes}
|
||||
# Passed to our entrypoint for --external-ip. Auto-detected if empty.
|
||||
- PUBLIC_IP=${PUBLIC_IP:-}
|
||||
command:
|
||||
- -n
|
||||
- --listening-port=${TURN_PORT:-3478}
|
||||
- --listening-ip=0.0.0.0
|
||||
- --fingerprint
|
||||
- --lt-cred-mech
|
||||
- --user=${TURN_USERNAME:-easyasterisk}:${TURN_PASSWORD:-changeme}
|
||||
@@ -103,12 +110,6 @@ services:
|
||||
- --no-multicast-peers
|
||||
- --log-file=stdout
|
||||
restart: unless-stopped
|
||||
healthcheck:
|
||||
test: ["CMD-SHELL", "turnutils_stunclient -p ${TURN_PORT:-3478} 127.0.0.1 >/dev/null 2>&1"]
|
||||
interval: 10s
|
||||
timeout: 5s
|
||||
retries: 3
|
||||
start_period: 10s
|
||||
|
||||
volumes:
|
||||
asterisk-config:
|
||||
|
||||
Executable
+26
@@ -0,0 +1,26 @@
|
||||
#!/bin/sh
|
||||
# ================================================================
|
||||
# Robust coturn entrypoint
|
||||
#
|
||||
# The coturn/coturn Docker image's native entrypoint uses:
|
||||
# exec $(eval "echo $@")
|
||||
# which is fragile — if DETECT_EXTERNAL_IP's DNS lookup returns empty,
|
||||
# the eval produces an empty token → "ERROR: CONFIG: Unknown argument:"
|
||||
#
|
||||
# This wrapper reuses the image's detect-external-ip script but avoids
|
||||
# the eval word-splitting issue. If detection fails, we simply omit
|
||||
# --external-ip rather than passing a blank argument.
|
||||
# ================================================================
|
||||
|
||||
# Use explicit PUBLIC_IP if provided, otherwise auto-detect
|
||||
if [ -z "$PUBLIC_IP" ]; then
|
||||
PUBLIC_IP=$(detect-external-ip 2>/dev/null || true)
|
||||
fi
|
||||
|
||||
# Only add --external-ip if we actually have an IP
|
||||
EXTERNAL_IP_ARG=""
|
||||
if [ -n "$PUBLIC_IP" ]; then
|
||||
EXTERNAL_IP_ARG="--external-ip=$PUBLIC_IP"
|
||||
fi
|
||||
|
||||
exec turnserver "$@" $EXTERNAL_IP_ARG
|
||||
+10
-12
@@ -279,24 +279,22 @@ EOF
|
||||
chown asterisk:asterisk /etc/asterisk/pjsip.conf
|
||||
fi
|
||||
|
||||
# ── rtp.conf (always regenerated - includes TURN credentials) ──
|
||||
# Use 127.0.0.1 for stunaddr/turnaddr because coturn runs on the same host
|
||||
# (network_mode: host). Using the FQDN would cause DNS resolution, and if the
|
||||
# DNS TTL is 0 Asterisk cancels recurring resolution — breaking ICE entirely
|
||||
# and adding a ~27-second timeout delay to every call.
|
||||
turn_port="${turn_server##*:}"
|
||||
local_turn="127.0.0.1:${turn_port:-3478}"
|
||||
log_info "Configuring RTP with ICE + STUN + TURN (local: ${local_turn})..."
|
||||
# ── rtp.conf (always regenerated) ──
|
||||
# ICE is enabled so Asterisk participates in ICE negotiation with clients.
|
||||
# stunaddr/turnaddr are NOT set here because:
|
||||
# - Asterisk already knows its public IP via external_media_address in pjsip.conf
|
||||
# - Its RTP ports are port-forwarded, so host candidates are sufficient
|
||||
# - Setting stunaddr/turnaddr causes STUN/TURN gather timeouts (~27s per call)
|
||||
# when the STUN/TURN server is unreachable or misconfigured
|
||||
# coturn is for SIP CLIENTS behind strict NAT — they configure TURN in their
|
||||
# own app settings, independently of Asterisk's rtp.conf.
|
||||
log_info "Configuring RTP with ICE support..."
|
||||
cat > /etc/asterisk/rtp.conf << EOF
|
||||
[general]
|
||||
rtpstart=${RTP_START:-10000}
|
||||
rtpend=${RTP_END:-20000}
|
||||
strictrtp=yes
|
||||
icesupport=yes
|
||||
stunaddr=${local_turn}
|
||||
turnaddr=${local_turn}
|
||||
turnusername=${TURN_USERNAME}
|
||||
turnpassword=${TURN_PASSWORD}
|
||||
EOF
|
||||
chown asterisk:asterisk /etc/asterisk/rtp.conf
|
||||
|
||||
|
||||
+70
-15
@@ -944,6 +944,28 @@ EOF
|
||||
echo " Server Settings → Provisioning Manager → Create Baresip Config"
|
||||
echo ""
|
||||
echo "═══════════════════════════════════════════════════════════════"
|
||||
|
||||
# Show TURN/STUN settings if enabled (for manual SIP app configuration)
|
||||
if [[ "$TURN_ENABLED" == "y" && -n "$TURN_SERVER" ]]; then
|
||||
echo ""
|
||||
echo -e " ${BOLD}STUN/TURN SETTINGS (for NAT traversal)${NC}"
|
||||
echo "═══════════════════════════════════════════════════════════════"
|
||||
echo ""
|
||||
echo " Configure these in your SIP app's Network/ICE settings:"
|
||||
echo " ICE: Enabled"
|
||||
echo " STUN server: ${TURN_SERVER}"
|
||||
echo " TURN server: ${TURN_SERVER}"
|
||||
echo " TURN username: ${TURN_USERNAME}"
|
||||
echo " TURN password: ${TURN_PASSWORD}"
|
||||
echo " TURN transport: UDP"
|
||||
echo ""
|
||||
echo " Linphone: Auto-provisioned via XML (no manual setup needed)"
|
||||
echo " Sipnetic: Settings → Network → ICE/STUN/TURN"
|
||||
echo " Olinuxino: Settings → Network → ICE/STUN/TURN"
|
||||
echo ""
|
||||
echo "═══════════════════════════════════════════════════════════════"
|
||||
fi
|
||||
|
||||
echo ""
|
||||
echo " NOTE: These instructions work for most SIP apps (Zoiper,"
|
||||
echo " sipnetic, etc.) - just use the same credentials."
|
||||
@@ -2015,11 +2037,20 @@ create_linphone_xml() {
|
||||
|
||||
<section name="net">
|
||||
<entry name="mtu">1300</entry>
|
||||
<!-- ICE + STUN/TURN for NAT traversal -->
|
||||
<entry name="firewall_policy">3</entry>
|
||||
<entry name="stun_server">${TURN_SERVER:-${domain}:3478}</entry>
|
||||
</section>
|
||||
|
||||
</config>
|
||||
EOF
|
||||
|
||||
# Add TURN credentials section if TURN is enabled
|
||||
if [[ "$TURN_ENABLED" == "y" && -n "$TURN_SERVER" && -n "$TURN_USERNAME" && -n "$TURN_PASSWORD" ]]; then
|
||||
# Insert TURN credentials into the net section before </config>
|
||||
sed -i "s|<entry name=\"stun_server\">.*</entry>|<entry name=\"stun_server\">${TURN_SERVER}</entry>\n <entry name=\"turn_enable\">1</entry>\n <entry name=\"turn_username\">${TURN_USERNAME}</entry>\n <entry name=\"turn_password\">${TURN_PASSWORD}</entry>|" "$xml_file"
|
||||
fi
|
||||
|
||||
chown asterisk:asterisk "$xml_file"
|
||||
chmod 644 "$xml_file"
|
||||
|
||||
@@ -3032,21 +3063,18 @@ transport=config,pjsip.conf,criteria=type=transport
|
||||
EOF
|
||||
fi
|
||||
|
||||
# ICE / STUN / TURN configuration
|
||||
# Enabled for: FQDN/internet mode OR VPN with ICE enabled
|
||||
# ICE configuration
|
||||
# ICE is enabled so Asterisk participates in ICE negotiation with clients.
|
||||
# stunaddr/turnaddr are NOT set because:
|
||||
# - Asterisk knows its public IP via external_media_address in pjsip.conf
|
||||
# - Its RTP ports are port-forwarded, so host candidates are sufficient
|
||||
# - Setting stunaddr/turnaddr causes STUN/TURN gather timeouts (~27s delay)
|
||||
# coturn (if running) is for SIP clients behind strict NAT — they configure
|
||||
# TURN in their own app settings, independently of Asterisk's rtp.conf.
|
||||
load_config
|
||||
local ice_config=""
|
||||
if [[ -n "$DOMAIN_NAME" ]] || [[ "$VPN_ICE_ENABLED" == "y" ]] || [[ "$TURN_ENABLED" == "y" ]]; then
|
||||
local stun_addr="${TURN_SERVER:-${CUSTOM_STUN_SERVER:-stun.l.google.com:19302}}"
|
||||
ice_config="icesupport=yes
|
||||
stunaddr=${stun_addr}"
|
||||
# Add TURN relay if configured (required for calls through strict NAT/VPN)
|
||||
if [[ "$TURN_ENABLED" == "y" && -n "$TURN_SERVER" && -n "$TURN_USERNAME" && -n "$TURN_PASSWORD" ]]; then
|
||||
ice_config="${ice_config}
|
||||
turnaddr=${TURN_SERVER}
|
||||
turnusername=${TURN_USERNAME}
|
||||
turnpassword=${TURN_PASSWORD}"
|
||||
fi
|
||||
ice_config="icesupport=yes"
|
||||
else
|
||||
ice_config="# icesupport disabled - LAN only mode"
|
||||
fi
|
||||
@@ -4750,20 +4778,33 @@ qualify_frequency=30
|
||||
return True, {'extension': extension, 'password': password, 'name': name}
|
||||
|
||||
def get_server_info():
|
||||
"""Get server configuration info"""
|
||||
"""Get server configuration info including TURN/STUN details"""
|
||||
info = {
|
||||
'domain': '',
|
||||
'tls_enabled': False,
|
||||
'server_ip': ''
|
||||
'server_ip': '',
|
||||
'turn_enabled': False,
|
||||
'turn_server': '',
|
||||
'turn_username': '',
|
||||
'turn_password': ''
|
||||
}
|
||||
|
||||
if os.path.exists(CONFIG_FILE):
|
||||
with open(CONFIG_FILE, 'r') as f:
|
||||
for line in f:
|
||||
line = line.strip()
|
||||
if line.startswith('DOMAIN_NAME='):
|
||||
info['domain'] = line.split('=', 1)[1].strip().strip('"')
|
||||
elif line.startswith('ENABLE_TLS='):
|
||||
info['tls_enabled'] = 'y' in line.lower()
|
||||
elif line.startswith('TURN_ENABLED='):
|
||||
info['turn_enabled'] = 'y' in line.split('=', 1)[1].lower()
|
||||
elif line.startswith('TURN_SERVER='):
|
||||
info['turn_server'] = line.split('=', 1)[1].strip().strip('"')
|
||||
elif line.startswith('TURN_USERNAME='):
|
||||
info['turn_username'] = line.split('=', 1)[1].strip().strip('"')
|
||||
elif line.startswith('TURN_PASSWORD='):
|
||||
info['turn_password'] = line.split('=', 1)[1].strip().strip('"')
|
||||
|
||||
try:
|
||||
result = subprocess.run(['hostname', '-I'], capture_output=True, text=True)
|
||||
@@ -5657,11 +5698,25 @@ HTML_TEMPLATE = '''<!DOCTYPE html>
|
||||
|
||||
if (result.success) {
|
||||
closeModal();
|
||||
document.getElementById('credentials-display').innerHTML = `
|
||||
let credHtml = `
|
||||
<p><strong>Extension:</strong> ${result.data.extension}</p>
|
||||
<p><strong>Password:</strong> ${result.data.password}</p>
|
||||
<p><strong>Name:</strong> ${result.data.name}</p>
|
||||
`;
|
||||
// Fetch server info to show TURN details
|
||||
try {
|
||||
const srvRes = await fetch(API_BASE + '/server');
|
||||
const srv = await srvRes.json();
|
||||
if (srv.turn_enabled && srv.turn_server) {
|
||||
credHtml += `<hr style="margin:12px 0;border-color:#e2e8f0">
|
||||
<p style="font-size:13px;color:#64748b;margin-bottom:6px">STUN/TURN (configure in app Network settings)</p>
|
||||
<p><strong>STUN/TURN server:</strong> ${srv.turn_server}</p>
|
||||
<p><strong>TURN username:</strong> ${srv.turn_username}</p>
|
||||
<p><strong>TURN password:</strong> ${srv.turn_password}</p>
|
||||
`;
|
||||
}
|
||||
} catch(e) {}
|
||||
document.getElementById('credentials-display').innerHTML = credHtml;
|
||||
document.getElementById('credentials-modal').classList.add('active');
|
||||
} else {
|
||||
showAlert(result.error || 'Failed to add device', 'error');
|
||||
|
||||
Reference in New Issue
Block a user