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
Three follow-ups from live testing on this session's actual VPS:
- tools/pstn-test-check.sh's SMS section printed the Forward-to-URL value
to configure but never said what to do next — add the "text this DID,
then watch journalctl -u sms-inbound -f" step right after it.
- docs/pstn-sms-test-checklist.md: the "which box actually handled this"
question has a simple answer (a DID's inbound routing targets exactly
one IP:port, so there's no ambiguity to resolve, only a portal setting
to confirm) — written up so it doesn't need re-deriving. Also fixed the
--list example to cd into the repo first; ./setup.sh is a relative path
and silently fails with "command not found" from any other directory,
confirmed live in this session.
- New tools/coturn-test-check.sh: health-checks the shared coturn instance
(services/coturn.sh) and every consumer registered against it (Asterisk,
any number of Mattermost instances) — container/identity, each cached
consumer credential cross-checked against coturn's own live user
database (catches the container/volume-recreated-without-db drift case),
UFW rules for both the TURN port and the relay range, a capacity
explanation reasoned from the actual port-range math instead of a guess,
and a real TURN allocation test per consumer via turnutils_uclient —
the only way to prove credentials + port range + firewall all actually
work together, not just that each looks right in isolation. Deliberately
does not attempt a concurrent load test, since that would consume real
relay ports other services may be actively using.
Verified the turnadmin -l output parsing, UFW rule matching, and the
empty-array-under-set–u loop pattern against mock data before shipping.
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01H4k6J1qXXyYxhGEgnJaMvn
Server-side config was fully verifiable already; what wasn't is the
provider-account side (Anveo's authorized-IP list, DID routing, SMS
forward-URL) since that lives entirely outside this box. Rather than
leave "go check the portal" as a vague pointer, compute and print the
exact values each portal field needs to match: this box's public IP,
the trunk DID (from .pstn-trunk.env), and the SMS forward URL read
straight from /opt/sms-inbound/settings.env (SMS_FORWARD_URL) instead of
making the user reconstruct or hunt for a value the installer already
generated and stored. Anveo-specific field-by-field checklist when
PROVIDER_NAME matches; generic fallback otherwise.
Verified the .pstn-trunk.env / settings.env sourcing against mock files
matching the real generated format, including the literal $[from]$-style
Anveo placeholders in SMS_FORWARD_URL, which must survive `source` under
`set -u` without triggering bash's legacy $[...] arithmetic expansion —
same guard pattern services/pstn-trunk.sh's own update path already uses.
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01H4k6J1qXXyYxhGEgnJaMvn
docs/pstn-sms-test-checklist.md's manual steps (registration, trunk
reachability, dialplan contexts, kill-switch state, usage-alert timer
health, recent call/message activity) are all things a script can check
directly instead of re-typed by hand each time — and re-typing them is
exactly what led to the container-name mistake in the prior commit.
tools/pstn-test-check.sh auto-detects the container/directory the same
way the checklist doc now does, runs every automatable check, and prints
PASS/WARN/FAIL per item plus a summary. What it can't cover — actually
placing a call or sending a text — still needs the checklist doc.
Caught during testing against real command output pasted in this
session: the endpoint-parsing loop matched pjsip's own column-header
line ("<Endpoint/CID...> <State...>") as if it were a real endpoint row,
producing a bogus result. Fixed by skipping any row whose parsed
extension starts with "<".
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01H4k6J1qXXyYxhGEgnJaMvn