Test Asterisk's own coturn config and print softphone setup info
Two follow-ups on the PSTN health check:
- New "coturn (TURN relay for Asterisk)" section reads Asterisk's own
TURN_* values from its .env (not re-derived) and runs a live TURN
allocation against whichever coturn Asterisk is actually configured to
use — the shared instance, or its own embedded per-Asterisk coturn if
that's what this box has (detected via the same "grep -q '^ coturn:'
docker-compose.yml" check CLAUDE.md's migration guidance describes).
Proves what Asterisk itself would use at call time, complementing
tools/coturn-test-check.sh's broader multi-consumer check.
- New "Softphone setup" section parses pjsip.conf directly and prints
per-extension SIP server/username/password/port/transport, plus TURN
credentials for any extension with ice_support=yes — the same values
Sipnetic's "Add Account" screen needs, computed here so a client isn't
installed just to read them out of the Security Dashboard.
Also fixed a bug caught while building a mock test harness to verify both
additions: the extension-registration parser grabbed state via a fixed
field position ($3), silently truncating multi-word states like "Not in
use" down to "Not". Replaced with a regex that captures everything
between the extension and the trailing "N of inf" — verified against both
single- and multi-word states.
And a real syntax bug caught by bash -n before this ever shipped: an
apostrophe inside a ${VAR:-default} expansion ("this box's IP") opens an
unterminated single-quote context even inside double quotes — reworded
to avoid the apostrophe entirely rather than fight bash's parser.
Full mock run (fake docker/curl/systemctl/getent, real pjsip.conf/.env
fixtures matching the actual generated format) confirmed both new
sections and the registration fix all produce correct output end to end.
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01H4k6J1qXXyYxhGEgnJaMvn
This commit is contained in:
+116
-3
@@ -60,6 +60,10 @@ ok "Directory: $EA_DIR"
|
||||
ASTERISK_DIR="$EA_DIR/config/asterisk"
|
||||
LOGS_DIR="$EA_DIR/logs"
|
||||
|
||||
# Fetched once, reused by both the softphone-setup and provider-checklist
|
||||
# sections below.
|
||||
PUBLIC_IP="$(curl -4 -s --max-time 5 ifconfig.me 2>/dev/null || true)"
|
||||
|
||||
# ── Registration ─────────────────────────────────────────────────────────────
|
||||
section "Extension registration"
|
||||
|
||||
@@ -69,9 +73,12 @@ if [ -z "$ENDPOINTS_OUT" ]; then
|
||||
else
|
||||
# Endpoint lines look like " Endpoint: 101/101 Unavailable 0 of inf" —
|
||||
# skip the trunk itself (checked separately below) and the header/legend.
|
||||
# State is captured with a regex, not a fixed field number: it's one or
|
||||
# more words ("Unavailable", but also "Not in use" — a single $3 field
|
||||
# grab truncated that to just "Not").
|
||||
while IFS= read -r line; do
|
||||
ext="$(awk '{print $2}' <<< "$line" | cut -d/ -f1)"
|
||||
state="$(awk '{print $3}' <<< "$line")"
|
||||
state="$(sed -E 's/^ Endpoint:[[:space:]]+[^[:space:]]+[[:space:]]+(.*[^[:space:]])[[:space:]]+[0-9]+ of inf[[:space:]]*$/\1/' <<< "$line")"
|
||||
# Skip the column-header/legend line ("<Endpoint/CID...> <State...>")
|
||||
# printed once at the top of real output — it matches the same
|
||||
# "^ Endpoint:" grep as an actual endpoint row.
|
||||
@@ -139,6 +146,57 @@ else
|
||||
fi
|
||||
fi
|
||||
|
||||
# ── coturn (TURN) — used for remote/NAT'd extensions' media relay, and by
|
||||
# Anveo-style ICE-enabled endpoints. Asterisk caches its OWN TURN_* values
|
||||
# in its .env at the point it was configured — testing with those (not
|
||||
# re-deriving fresh credentials) proves what Asterisk is actually set up
|
||||
# to use, not just that the shared coturn instance works in general (that
|
||||
# broader, multi-consumer check is tools/coturn-test-check.sh's job). ─────────
|
||||
section "coturn (TURN relay for Asterisk)"
|
||||
|
||||
ASTERISK_ENV="$EA_DIR/.env"
|
||||
TURN_SERVER="" TURN_USERNAME="" TURN_PASSWORD="" TURN_PORT=""
|
||||
if [ -f "$ASTERISK_ENV" ]; then
|
||||
set +u
|
||||
# shellcheck disable=SC1090
|
||||
source "$ASTERISK_ENV"
|
||||
set -u
|
||||
TURN_SERVER="${TURN_SERVER:-}"
|
||||
TURN_USERNAME="${TURN_USERNAME:-}"
|
||||
TURN_PASSWORD="${TURN_PASSWORD:-}"
|
||||
TURN_PORT="${TURN_PORT:-}"
|
||||
fi
|
||||
|
||||
if [ -z "$TURN_SERVER" ]; then
|
||||
warn "Asterisk has no TURN configured — fine for LAN-only extensions, but a phone on"
|
||||
warn "mobile data or behind restrictive NAT may get one-way or no audio without it."
|
||||
warn "Add it via: sudo ./setup.sh asterisk (update mode)"
|
||||
else
|
||||
if grep -q '^ coturn:' "$EA_DIR/docker-compose.yml" 2>/dev/null; then
|
||||
COTURN_CONTAINER="easy-asterisk-coturn"
|
||||
[[ "$CONTAINER" == *-do ]] && COTURN_CONTAINER="easy-asterisk-do-coturn"
|
||||
ok "Using an embedded, per-Asterisk coturn ($COTURN_CONTAINER) — not the shared"
|
||||
ok "instance, so tools/coturn-test-check.sh won't see this one; tested separately below."
|
||||
else
|
||||
COTURN_CONTAINER="coturn"
|
||||
ok "Using the shared coturn instance (also covered by tools/coturn-test-check.sh)"
|
||||
fi
|
||||
|
||||
if ! docker ps --format '{{.Names}}' 2>/dev/null | grep -qx "$COTURN_CONTAINER"; then
|
||||
fail "Container '$COTURN_CONTAINER' not running — Asterisk's TURN config points at it but it's down"
|
||||
elif ! docker exec "$COTURN_CONTAINER" which turnutils_uclient &>/dev/null; then
|
||||
warn "turnutils_uclient not found in $COTURN_CONTAINER — skipping live allocation test"
|
||||
else
|
||||
OUT="$(docker exec "$COTURN_CONTAINER" timeout 10 turnutils_uclient -t -T -u "$TURN_USERNAME" -w "$TURN_PASSWORD" 127.0.0.1 -p "${TURN_PORT:-3478}" 2>&1)"
|
||||
if [ $? -eq 0 ]; then
|
||||
ok "Live TURN allocation succeeded with Asterisk's own configured credentials (user '$TURN_USERNAME')"
|
||||
else
|
||||
fail "Live TURN allocation FAILED with Asterisk's configured credentials — raw output:"
|
||||
echo "$OUT" | tail -n 15 | sed 's/^/ /'
|
||||
fi
|
||||
fi
|
||||
fi
|
||||
|
||||
# ── SMS inbound ───────────────────────────────────────────────────────────────
|
||||
section "SMS inbound"
|
||||
if systemctl list-unit-files sms-inbound.service &>/dev/null; then
|
||||
@@ -163,6 +221,63 @@ for log in pstn-trunk-calls.log sip-messages.log; do
|
||||
fi
|
||||
done
|
||||
|
||||
# ── Softphone setup (Sipnetic or any SIP client) ──────────────────────────────
|
||||
# Same data the Security Dashboard's per-extension "info" panel shows
|
||||
# (showEaDeviceDetails in services/security-dashboard.sh), read directly from
|
||||
# pjsip.conf here so this is useful even without the dashboard installed.
|
||||
# Passwords are read from the live config on this box, not regenerated —
|
||||
# printing them is exactly as sensitive as the dashboard's own info panel.
|
||||
section "Softphone setup — one block per extension (password shown, handle accordingly)"
|
||||
|
||||
DOMAIN_NAME=""
|
||||
[ -f "$EA_DIR/.env" ] && DOMAIN_NAME="$(grep -E '^DOMAIN_NAME=' "$EA_DIR/.env" | cut -d= -f2-)"
|
||||
SIP_SERVER="${DOMAIN_NAME:-${PUBLIC_IP:-<could not auto-detect this box IP>}}"
|
||||
|
||||
PJSIP_CONF="$ASTERISK_DIR/pjsip.conf"
|
||||
if [ ! -f "$PJSIP_CONF" ]; then
|
||||
warn "pjsip.conf not found at $PJSIP_CONF — can't print softphone settings"
|
||||
else
|
||||
DEVICE_INFO="$(awk '
|
||||
/^\[[0-9]+\]$/ { ext = substr($0, 2, length($0)-2); cur_type=""; next }
|
||||
/^type=endpoint/ { cur_type="endpoint"; next }
|
||||
/^type=auth/ { cur_type="auth"; next }
|
||||
/^type=aor/ { cur_type="aor"; next }
|
||||
cur_type=="endpoint" && /^transport=/ { split($0,a,"="); transport[ext]=a[2] }
|
||||
cur_type=="endpoint" && /^ice_support=yes/ { ice[ext]="yes" }
|
||||
cur_type=="auth" && /^password=/ { split($0,a,"="); pass[ext]=a[2] }
|
||||
END {
|
||||
for (e in pass) printf "%s|%s|%s|%s\n", e, pass[e], transport[e], (ice[e] ? ice[e] : "no")
|
||||
}
|
||||
' "$PJSIP_CONF" | sort)"
|
||||
|
||||
if [ -z "$DEVICE_INFO" ]; then
|
||||
warn "No devices found in pjsip.conf"
|
||||
else
|
||||
while IFS='|' read -r ext pass transport ice; do
|
||||
[ -z "$ext" ] && continue
|
||||
if [ "$transport" = "transport-tls" ]; then
|
||||
port=5061; proto="tls"
|
||||
else
|
||||
port=5060; proto="udp"
|
||||
fi
|
||||
echo " Extension $ext:"
|
||||
echo " SIP server: $SIP_SERVER"
|
||||
echo " Username: $ext"
|
||||
echo " Password: $pass"
|
||||
echo " Port: $port"
|
||||
echo " Transport: $proto"
|
||||
if [ "$ice" = "yes" ] && [ -n "$TURN_SERVER" ]; then
|
||||
echo " TURN server: $TURN_SERVER"
|
||||
echo " TURN user: $TURN_USERNAME"
|
||||
echo " TURN pass: $TURN_PASSWORD"
|
||||
fi
|
||||
echo ""
|
||||
done <<< "$DEVICE_INFO"
|
||||
ok "Printed setup info for $(wc -l <<< "$DEVICE_INFO") extension(s) — same values Sipnetic's"
|
||||
ok "'Add Account' screen (or the dashboard's QR code / Download settings) needs"
|
||||
fi
|
||||
fi
|
||||
|
||||
# ── Provider portal checklist ─────────────────────────────────────────────────
|
||||
# Everything above is server-side and this script's own checks; the provider
|
||||
# account/portal side (authorized IPs, DID routing, the SMS forward URL) is
|
||||
@@ -183,8 +298,6 @@ if [ -f "$PSTN_ENV" ]; then
|
||||
PROVIDER_NAME="${PROVIDER_NAME:-}"
|
||||
fi
|
||||
|
||||
PUBLIC_IP="$(curl -4 -s --max-time 5 ifconfig.me 2>/dev/null || true)"
|
||||
|
||||
if [ -n "$TRUNK_DID" ]; then
|
||||
echo " This box's DID: $TRUNK_DID"
|
||||
else
|
||||
|
||||
Reference in New Issue
Block a user