pstn-trunk: split inbound dialplan into its own #include'd file
Root cause found via live debugging: pstn-trunk-dialplan.conf mixed two contexts in one file - outbound patterns continuing [intercom], then a [from-pstn-trunk] header appearing partway through the same file. On the reporting box this caused the ENTIRE file to silently fail to load: `dialplan show intercom` showed zero of the outbound NANP patterns, and `dialplan show from-pstn-trunk` reported the context didn't exist, with no warning anywhere (config log, full log, or the reload command's own output). messaging-dialplan.conf, #include'd the same way right after [intercom], loaded fine - the working structural difference is that its first real line IS its own context header, never trying to continue [intercom] first. Split pstn-trunk-dialplan.conf into that same working shape: - pstn-trunk-dialplan.conf keeps only the outbound content (continues [intercom], unchanged from before). - pstn-trunk-inbound-dialplan.conf is new, and starts with [from-pstn-trunk] as its first non-comment line - nothing before it. Both _pstn_patch_vendor_files (generator patch) and _pstn_ensure_live_includes (direct live-file patch) now add a second #include line for the new file. _pstn_ensure_live_includes also now verifies after reload that 'from-pstn-trunk' actually has a matching extension, and warns loudly if not, instead of unconditionally reporting success regardless of whether the reload actually worked.
This commit is contained in:
+137
-40
@@ -106,6 +106,24 @@ _pstn_patch_vendor_files() {
|
||||
fi
|
||||
done
|
||||
|
||||
# Inbound dialplan lives in its OWN #include'd file, separate from
|
||||
# pstn-trunk-dialplan.conf above — see _pstn_write_inbound_dialplan_include's
|
||||
# comment for why. Anchored on the outbound include (added just above,
|
||||
# so it's already present) rather than re-anchoring on '[intercom]',
|
||||
# purely so the two land next to each other in the generated file.
|
||||
for f in "$ENTRYPOINT" "$EASY1" "$EASY2"; do
|
||||
if ! grep -q 'pstn-trunk-inbound-dialplan.conf' "$f"; then
|
||||
if grep -q '^#include pstn-trunk-dialplan.conf$' "$f"; then
|
||||
sed -i '/^#include pstn-trunk-dialplan.conf$/a #include pstn-trunk-inbound-dialplan.conf' "$f"
|
||||
elif grep -q '^\[intercom\]$' "$f"; then
|
||||
sed -i '/^\[intercom\]$/a #include pstn-trunk-inbound-dialplan.conf' "$f"
|
||||
else
|
||||
log_warning "$(basename "$f"): neither the outbound include nor '[intercom]' anchor found — vendor template changed upstream."
|
||||
log_warning " Add '#include pstn-trunk-inbound-dialplan.conf' manually after [intercom] in this file's extensions.conf heredoc."
|
||||
fi
|
||||
fi
|
||||
done
|
||||
|
||||
log_success "Vendor generator functions patched to include the PSTN trunk config."
|
||||
}
|
||||
|
||||
@@ -150,8 +168,40 @@ _pstn_ensure_live_includes() {
|
||||
fi
|
||||
fi
|
||||
|
||||
# Inbound lives in its OWN #include'd file — see
|
||||
# _pstn_write_inbound_dialplan_include's comment for why it can't share
|
||||
# pstn-trunk-dialplan.conf. Anchor on the outbound include so the two
|
||||
# sit together; fall back to '[intercom]' if that's somehow not there.
|
||||
if [[ -f "$EXT_LIVE" ]] && ! grep -q 'pstn-trunk-inbound-dialplan.conf' "$EXT_LIVE"; then
|
||||
if grep -q '^#include pstn-trunk-dialplan.conf$' "$EXT_LIVE"; then
|
||||
sed -i '/^#include pstn-trunk-dialplan.conf$/a #include pstn-trunk-inbound-dialplan.conf' "$EXT_LIVE"
|
||||
log_success "Patched the inbound trunk's #include directly into the live extensions.conf."
|
||||
elif grep -q '^\[intercom\]$' "$EXT_LIVE"; then
|
||||
sed -i '/^\[intercom\]$/a #include pstn-trunk-inbound-dialplan.conf' "$EXT_LIVE"
|
||||
log_success "Patched the inbound trunk's #include directly into the live extensions.conf."
|
||||
else
|
||||
log_warning "Couldn't find an anchor for the inbound include in the live extensions.conf — add"
|
||||
log_warning "'#include pstn-trunk-inbound-dialplan.conf' manually, then reload."
|
||||
fi
|
||||
fi
|
||||
|
||||
docker exec "$CONTAINER_NAME" asterisk -rx "module reload res_pjsip.so" &>/dev/null || true
|
||||
docker exec "$CONTAINER_NAME" asterisk -rx "dialplan reload" &>/dev/null || true
|
||||
|
||||
# Confirmed live (2026-07-24): the two commands above were run with all
|
||||
# output suppressed, so a previous version of this function reported
|
||||
# "[OK] Patched..." unconditionally even when the reload silently failed
|
||||
# to actually create the from-pstn-trunk context (root cause turned out
|
||||
# to be the single-file structure fixed above, but there was no way to
|
||||
# tell from the install log alone — only manual `dialplan show
|
||||
# from-pstn-trunk` on the box revealed it). Verify the reload actually
|
||||
# produced the context and say so plainly if it didn't, instead of
|
||||
# silently trusting the reload command's own exit status.
|
||||
if ! docker exec "$CONTAINER_NAME" asterisk -rx "dialplan show from-pstn-trunk" 2>/dev/null | grep -q "'_X\.'"; then
|
||||
log_warning "Reloaded the dialplan, but 'from-pstn-trunk' still has no matching"
|
||||
log_warning "extension — inbound PSTN calls will get an immediate SIP rejection."
|
||||
log_warning "Check: docker exec $CONTAINER_NAME asterisk -rx \"dialplan show from-pstn-trunk\""
|
||||
fi
|
||||
}
|
||||
|
||||
# ── Shared: pjsip trunk config (aor/identify/endpoint, IP-authenticated) ───
|
||||
@@ -251,25 +301,21 @@ MEMBER
|
||||
# CDR CSV's comma-quoting entirely (our own pipe-delimited format has no
|
||||
# embedded-delimiter risk since every field here is digits/hostnames).
|
||||
_pstn_write_dialplan_include() {
|
||||
local FILE="$1" DID="$2" RING_EXTS="$3" NTFY_URL="$4"
|
||||
# Dedupe: a repeated extension (typo, copy-paste, or a settings file
|
||||
# edited by hand) would otherwise generate two identical ring<ext>/
|
||||
# skip<ext> priority labels in the same [from-pstn-trunk] extension.
|
||||
# Confirmed live: Asterisk doesn't error on the duplicate labels, it
|
||||
# silently resolves Goto() to the wrong one and loops between the two
|
||||
# blocks forever — the call never reaches the actual Dial(), and no
|
||||
# ring/notification ever happens, with no error anywhere to point at it.
|
||||
RING_EXTS="$(echo "$RING_EXTS" | xargs -n1 | awk '!seen[$0]++' | xargs)"
|
||||
local FILE="$1" DID="$2" NTFY_URL="$3"
|
||||
cat > "$FILE" << 'EOF'
|
||||
; PSTN outbound/inbound — US-only (NANP). Concurrent-call caps (both
|
||||
; directions) AND tiered permissions (internal / restricted / full) are read
|
||||
; LIVE from pstn-limits.conf / pstn-permissions.conf via AST_CONFIG() — edit
|
||||
; PSTN outbound — US-only (NANP). Concurrent-call caps AND tiered
|
||||
; permissions (internal / restricted / full) are read LIVE from
|
||||
; pstn-limits.conf / pstn-permissions.conf via AST_CONFIG() — edit
|
||||
; either there, or via the Security Dashboard web UI, with no restart
|
||||
; needed. Regenerated by services/pstn-trunk.sh — edit there, not here
|
||||
; directly, or a reinstall/update will overwrite this file (pstn-limits.conf
|
||||
; and pstn-permissions.conf are NOT touched by "update", only by a "fresh"
|
||||
; reinstall or the web UI).
|
||||
;
|
||||
; Inbound routing lives in pstn-trunk-inbound-dialplan.conf, a SEPARATE
|
||||
; #include'd file — see _pstn_write_inbound_dialplan_include's comment for
|
||||
; why it can't live in this same file.
|
||||
;
|
||||
; No catch-all pattern here on purpose: only these two NANP patterns route
|
||||
; to the trunk, so an unauthorized or compromised extension can't reach
|
||||
; anything else even if the trunk itself would technically allow more. See
|
||||
@@ -390,25 +436,68 @@ EOF
|
||||
else
|
||||
sed -i "/__ALERT_DENY_TIER_LINE__/d; /__ALERT_DENY_INTL_LINE__/d; /__ALERT_DENY_NUMBER_LINE__/d; /__ALERT_BUSY_LINE__/d; /__ALERT_KILLED_LINE__/d; /__ALERT_DENY_INTL_TIER_LINE__/d; /__ALERT_DENY_INTL_COUNTRY_LINE__/d" "$FILE"
|
||||
fi
|
||||
}
|
||||
|
||||
# ── Inbound: [from-pstn-trunk], one unrolled block per ring-group member.
|
||||
# Permission check (is anyone in the ring group authorized for this
|
||||
# caller) happens before the concurrency check, mirroring outbound's
|
||||
# own ordering (permission gate, then busy gate).
|
||||
#
|
||||
# PSTN_CALLERID_NORM below mirrors what outbound's _NXXNXXXXXX pattern
|
||||
# already does (Goto 1${EXTEN} to add a leading "1" before any tier/
|
||||
# allowed_numbers check) but for the inbound direction: allowed_numbers
|
||||
# is always stored 11-digit (dashboard's NUMBER_RE requires exactly 11
|
||||
# digits), but a provider's inbound Caller-ID isn't guaranteed to include
|
||||
# the leading "1" — confirmed live: Anveo delivered a bare 10-digit
|
||||
# CALLERID(num), so every restricted-tier check against it failed on
|
||||
# a plain digit-count mismatch (11-digit pattern vs. 10-digit string),
|
||||
# regardless of whether the number itself was genuinely on the list.
|
||||
# Every restricted-tier comparison below (ring-group members, personal-
|
||||
# DID owner, group-owned personal DID) uses this normalized value
|
||||
# instead of raw ${CALLERID(num)}.
|
||||
cat >> "$FILE" << 'EOF'
|
||||
# ── Shared: inbound dialplan ([from-pstn-trunk]) — its OWN #include'd file ──
|
||||
# This MUST be a separate file from pstn-trunk-dialplan.conf (outbound,
|
||||
# above), not a second section appended to it. Confirmed live (2026-07-24):
|
||||
# when both lived in one file — outbound content continuing [intercom]
|
||||
# first, then a "[from-pstn-trunk]" header later in the SAME file — NONE of
|
||||
# it loaded. Not just the inbound section: `dialplan show intercom` showed
|
||||
# zero of the outbound NANP patterns either, and `dialplan show
|
||||
# from-pstn-trunk` reported the context didn't exist at all, with no
|
||||
# warning or error anywhere (config log, full log, or the reload command's
|
||||
# own output) pointing at why. Meanwhile services/asterisk-digital-ocean.sh's
|
||||
# messaging-dialplan.conf — #include'd via the exact same mechanism, right
|
||||
# after [intercom] in the same extensions.conf — loaded fine every time.
|
||||
# The one structural difference: messaging-dialplan.conf's first real line
|
||||
# IS its own context header ([sip-messaging]); it never tries to continue
|
||||
# [intercom] first. Splitting this file the same way (nothing before
|
||||
# [from-pstn-trunk] except comments) fixed it. Root cause inside Asterisk's
|
||||
# config loader not fully understood — but this file must keep starting
|
||||
# with [from-pstn-trunk] and nothing that continues an ambient context
|
||||
# before it, or the failure comes back.
|
||||
#
|
||||
# One unrolled block per ring-group member. Permission check (is anyone in
|
||||
# the ring group authorized for this caller) happens before the concurrency
|
||||
# check, mirroring outbound's own ordering (permission gate, then busy
|
||||
# gate).
|
||||
#
|
||||
# PSTN_CALLERID_NORM below mirrors what outbound's _NXXNXXXXXX pattern
|
||||
# already does (Goto 1${EXTEN} to add a leading "1" before any tier/
|
||||
# allowed_numbers check) but for the inbound direction: allowed_numbers
|
||||
# is always stored 11-digit (dashboard's NUMBER_RE requires exactly 11
|
||||
# digits), but a provider's inbound Caller-ID isn't guaranteed to include
|
||||
# the leading "1" — confirmed live: Anveo delivered a bare 10-digit
|
||||
# CALLERID(num), so every restricted-tier check against it failed on
|
||||
# a plain digit-count mismatch (11-digit pattern vs. 10-digit string),
|
||||
# regardless of whether the number itself was genuinely on the list.
|
||||
# Every restricted-tier comparison below (ring-group members, personal-
|
||||
# DID owner, group-owned personal DID) uses this normalized value
|
||||
# instead of raw ${CALLERID(num)}.
|
||||
_pstn_write_inbound_dialplan_include() {
|
||||
local FILE="$1" RING_EXTS="$2" NTFY_URL="$3"
|
||||
# Dedupe: a repeated extension (typo, copy-paste, or a settings file
|
||||
# edited by hand) would otherwise generate two identical ring<ext>/
|
||||
# skip<ext> priority labels in the same [from-pstn-trunk] extension.
|
||||
# Confirmed live: Asterisk doesn't error on the duplicate labels, it
|
||||
# silently resolves Goto() to the wrong one and loops between the two
|
||||
# blocks forever — the call never reaches the actual Dial(), and no
|
||||
# ring/notification ever happens, with no error anywhere to point at it.
|
||||
RING_EXTS="$(echo "$RING_EXTS" | xargs -n1 | awk '!seen[$0]++' | xargs)"
|
||||
cat > "$FILE" << 'EOF'
|
||||
; PSTN inbound routing ([from-pstn-trunk]) — kept in its OWN file, separate
|
||||
; from pstn-trunk-dialplan.conf's outbound content. See
|
||||
; _pstn_write_inbound_dialplan_include's comment in services/pstn-trunk.sh
|
||||
; for why: a combined file silently failed to load ANY content (outbound
|
||||
; included) the one time this was tried. Regenerated by
|
||||
; services/pstn-trunk.sh — edit there, not here directly, or a
|
||||
; reinstall/update will overwrite this file.
|
||||
;
|
||||
; Concurrent-call caps AND tiered permissions (internal / restricted /
|
||||
; full) are read LIVE from pstn-limits.conf / pstn-permissions.conf via
|
||||
; AST_CONFIG() — edit either there, or via the Security Dashboard web UI,
|
||||
; with no restart needed.
|
||||
|
||||
[from-pstn-trunk]
|
||||
exten => _X.,1,NoOp(Inbound PSTN call from ${CALLERID(num)} to ${EXTEN})
|
||||
@@ -1333,12 +1422,14 @@ _pstn_apply_settings() {
|
||||
|
||||
mkdir -p "$ASTERISK_DIR"
|
||||
_pstn_write_pjsip_include "$ASTERISK_DIR/pstn-trunk-pjsip.conf" "$SERVER" "$SERVER_IPS" "$DID"
|
||||
_pstn_write_dialplan_include "$ASTERISK_DIR/pstn-trunk-dialplan.conf" "$DID" "$RING_EXTS" "$NTFY_URL"
|
||||
_pstn_write_dialplan_include "$ASTERISK_DIR/pstn-trunk-dialplan.conf" "$DID" "$NTFY_URL"
|
||||
_pstn_write_inbound_dialplan_include "$ASTERISK_DIR/pstn-trunk-inbound-dialplan.conf" "$RING_EXTS" "$NTFY_URL"
|
||||
_pstn_write_personal_group_ring_script "$ASTERISK_DIR/pstn-personal-group-ring.sh"
|
||||
_pstn_write_usage_alert_script "$EA_DIR/pstn-trunk-usage-alert.sh" "$EA_DIR" "$ASTERISK_DIR" \
|
||||
"$RATE" "$MONTH_THRESHOLD" "$BURST_THRESHOLD" "$MAX_MONTHLY_SPEND" "$NTFY_URL" "$CONTAINER_NAME"
|
||||
ensure_docker_dir_ownership "$ASTERISK_DIR"
|
||||
chmod 644 "$ASTERISK_DIR/pstn-trunk-pjsip.conf" "$ASTERISK_DIR/pstn-trunk-dialplan.conf"
|
||||
chmod 644 "$ASTERISK_DIR/pstn-trunk-pjsip.conf" "$ASTERISK_DIR/pstn-trunk-dialplan.conf" \
|
||||
"$ASTERISK_DIR/pstn-trunk-inbound-dialplan.conf"
|
||||
chmod 755 "$ASTERISK_DIR/pstn-personal-group-ring.sh"
|
||||
|
||||
# Values are double-quoted: this file is `source`d back in on "update"
|
||||
@@ -1994,11 +2085,17 @@ hand-edited files. Trunk/dialplan config here lives in files of its own,
|
||||
|
||||
- \`config/asterisk/pstn-trunk-pjsip.conf\` — the trunk's \`aor\`/\`identify\`/
|
||||
\`endpoint\` sections (IP-authenticated, no password stored).
|
||||
- \`config/asterisk/pstn-trunk-dialplan.conf\` — NANP-only outbound routing,
|
||||
ntfy alert hooks, and the \`[from-pstn-trunk]\` inbound context. Reads
|
||||
permission tiers from \`pstn-permissions.conf\` and concurrency caps from
|
||||
\`pstn-limits.conf\` (both above) live, rather than baking either in,
|
||||
specifically so they can change without touching this file.
|
||||
- \`config/asterisk/pstn-trunk-dialplan.conf\` — NANP-only outbound routing
|
||||
and ntfy alert hooks.
|
||||
- \`config/asterisk/pstn-trunk-inbound-dialplan.conf\` — the
|
||||
\`[from-pstn-trunk]\` inbound context (ring group + personal DID routing),
|
||||
kept in its own file rather than appended to the outbound one above — a
|
||||
combined file silently failed to load any of its content at all (see the
|
||||
comment on \`_pstn_write_inbound_dialplan_include\` in
|
||||
\`services/pstn-trunk.sh\`). Both dialplan files read permission tiers from
|
||||
\`pstn-permissions.conf\` and concurrency caps from \`pstn-limits.conf\`
|
||||
(both above) live, rather than baking either in, specifically so they can
|
||||
change without touching these files.
|
||||
|
||||
The \`#include\` lines themselves are patched into Easy Asterisk's *generator
|
||||
functions* (\`docker/entrypoint.sh\`, \`easy-asterisk.sh\`, and its versioned
|
||||
@@ -2125,9 +2222,9 @@ security-dashboard\`; it auto-detects this install.
|
||||
## Manual edits
|
||||
|
||||
Don't hand-edit \`pstn-trunk-pjsip.conf\` / \`pstn-trunk-dialplan.conf\` /
|
||||
\`pstn-trunk-usage-alert.sh\` directly if you plan to re-run this installer
|
||||
later — it overwrites all three unconditionally from \`.pstn-trunk.env\` on
|
||||
both fresh and update. \`pstn-permissions.conf\` and \`pstn-limits.conf\` are
|
||||
\`pstn-trunk-inbound-dialplan.conf\` / \`pstn-trunk-usage-alert.sh\` directly
|
||||
if you plan to re-run this installer later — it overwrites all four
|
||||
unconditionally from \`.pstn-trunk.env\` on both fresh and update. \`pstn-permissions.conf\` and \`pstn-limits.conf\` are
|
||||
different — see "Permission tiers" / "Concurrent-call caps" above, both are
|
||||
safe to hand-edit any time. For one-off testing, restart the container
|
||||
instead of running the installer:
|
||||
|
||||
Reference in New Issue
Block a user