Add personal/per-extension DID assignment ("their own numbers")
Multiple DIDs can now share one trunk/account, each assigned to a specific extension - additive to the existing shared trunk DID/ring-group, which keeps working unchanged for everyone regardless of what's assigned here. - New pstn-personal-dids.conf (DID -> owner extension), read live by the dialplan for inbound routing: a call to a personal DID routes straight to its owner, checked against the owner's own tier/approved-numbers, no ring-group fallback since it's that extension's own line. - New personal_did= field per extension in pstn-permissions.conf: the outbound Caller-ID override, used by pstn_check_busy (the shared exit point for both domestic and international dialing) instead of the shared trunk DID when the calling extension has one assigned. - Both files kept in sync automatically by one write path - CLI prompt at install/update, or a new "Personal numbers" card in the Security Dashboard's PSTN Trunk tab - rather than requiring hand-editing both consistently. Reassigning a DID or giving an extension a new one cleanly drops the stale side of the old mapping. - Assigning a DID to an internal-tier extension is accepted but warned about (won't ring anyone until also granted full/restricted tier), matching this repo's permissive-with-warnings style rather than blocking the action outright. Also fixed a real bug found while building this: security-dashboard.sh's write_permission() did cp.remove_section(ext) whenever tier was set to "internal", silently discarding any messaging=yes or personal_did= already set on that extension. Now removes only the tier/allowed_numbers keys, dropping the section only once nothing else remains in it. Separately, de-anchored pstn-trunk.sh's install prompts from VoIP.ms as the implicit default (provider name default, server-hostname example wording, rate-prompt wording) now that Anveo Direct is an equally confirmed, tested provider option. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01Ho9mZgAkVpdz7S5wJkg8Nf
This commit is contained in:
+175
-30
@@ -11,9 +11,9 @@
|
||||
# never gated by any of the above, regardless of tier — the trunk is purely
|
||||
# an additional path out to/in from the real phone network.
|
||||
#
|
||||
# Defaults to VoIP.ms (see docs/pstn-calling-voipms-plan.md for the design/
|
||||
# cost background this is built from) but isn't hardcoded to it — any SIP
|
||||
# trunk provider that supports IP authentication works the same way.
|
||||
# Provider-agnostic — any SIP trunk provider that supports IP authentication
|
||||
# works the same way. VoIP.ms and Anveo Direct are both confirmed working;
|
||||
# see docs/pstn-calling-voipms-plan.md for the design/cost background.
|
||||
#
|
||||
# Requires an existing services/asterisk-digital-ocean.sh OR services/asterisk.sh
|
||||
# install — this adds a PSTN trunk on top of one of them and does not stand
|
||||
@@ -23,7 +23,7 @@
|
||||
#
|
||||
# Part of the modular post-install system (sourced by setup.sh).
|
||||
|
||||
register_service pstn-trunk homelab "SIP PSTN trunk for asterisk-digital-ocean/asterisk — US-only, per-extension permission tiers, spend/volume alerts (defaults to VoIP.ms)"
|
||||
register_service pstn-trunk homelab "SIP PSTN trunk for asterisk-digital-ocean/asterisk — US-only, per-extension permission tiers, spend/volume alerts (any IP-authenticated provider — VoIP.ms and Anveo Direct both confirmed)"
|
||||
|
||||
# ── Surviving Easy Asterisk's regeneration ──────────────────────────────────
|
||||
# Easy Asterisk (the vendor project asterisk-digital-ocean.sh/asterisk.sh
|
||||
@@ -306,7 +306,8 @@ exten => pstn_check_busy,1,Set(PSTN_MAX_OUT=${AST_CONFIG(pstn-limits.conf,limits
|
||||
same => n,Set(PSTN_MAX_OUT=${IF($["${PSTN_MAX_OUT}" = ""]?10:${PSTN_MAX_OUT})})
|
||||
same => n,GotoIf($[${GROUP_COUNT(pstn-out)} >= ${PSTN_MAX_OUT}]?pstn_busy,1)
|
||||
same => n,Set(GROUP()=pstn-out)
|
||||
same => n,Set(CALLERID(num)=__PSTN_DID__)
|
||||
same => n,Set(PSTN_PERSONAL_CID=${AST_CONFIG(pstn-permissions.conf,${PSTN_CALLER},personal_did)})
|
||||
same => n,Set(CALLERID(num)=${IF($["${PSTN_PERSONAL_CID}" = ""]?__PSTN_DID__:${PSTN_PERSONAL_CID})})
|
||||
same => n,Set(PSTN_START=${EPOCH})
|
||||
same => n,Dial(PJSIP/${EXTEN}@pstn-trunk,60)
|
||||
same => n,Set(PSTN_DUR=$[${EPOCH} - ${PSTN_START}])
|
||||
@@ -340,9 +341,11 @@ EOF
|
||||
cat >> "$FILE" << 'EOF'
|
||||
|
||||
[from-pstn-trunk]
|
||||
exten => _X.,1,NoOp(Inbound PSTN call from ${CALLERID(num)})
|
||||
exten => _X.,1,NoOp(Inbound PSTN call from ${CALLERID(num)} to ${EXTEN})
|
||||
same => n,Set(PSTN_KILLED=${AST_CONFIG(pstn-trunk-killswitch.conf,state,tripped)})
|
||||
same => n,GotoIf($["${PSTN_KILLED}" = "1"]?pstn_in_killed,1)
|
||||
same => n,Set(PSTN_PERSONAL_OWNER=${AST_CONFIG(pstn-personal-dids.conf,${EXTEN},owner)})
|
||||
same => n,GotoIf($["${PSTN_PERSONAL_OWNER}" != ""]?pstn_personal_inbound,1)
|
||||
same => n,Set(PSTN_RING_LIST=)
|
||||
same => n,Set(PSTN_RING_SEP=)
|
||||
EOF
|
||||
@@ -376,6 +379,29 @@ __ALERT_BUSY_IN_LINE__
|
||||
exten => pstn_in_killed,1,NoOp(PSTN trunk - spend-cap kill-switch is tripped, rejecting inbound call)
|
||||
__ALERT_KILLED_IN_LINE__
|
||||
same => n,Hangup()
|
||||
|
||||
; Personal DID inbound routing — rings ONLY the assigned owner, gated by
|
||||
; that owner's own tier/approved-numbers (same check every ring-group
|
||||
; member gets, just for a single specific target instead of a list, and
|
||||
; with no fallback to the shared ring-group if the owner can't take this
|
||||
; call — it's their own number, not the shared line).
|
||||
exten => pstn_personal_inbound,1,Set(PSTN_OWNER_TIER=${AST_CONFIG(pstn-permissions.conf,${PSTN_PERSONAL_OWNER},tier)})
|
||||
same => n,GotoIf($["${PSTN_OWNER_TIER}" = "full"]?pstn_personal_ring,1)
|
||||
same => n,Set(PSTN_OWNER_ALLOWED=${AST_CONFIG(pstn-permissions.conf,${PSTN_PERSONAL_OWNER},allowed_numbers)})
|
||||
same => n,GotoIf($["${PSTN_OWNER_TIER}" = "restricted" & ${REGEX("^(${PSTN_OWNER_ALLOWED})$" ${CALLERID(num)})}=1]?pstn_personal_ring,1)
|
||||
same => n,NoOp(Denied - personal DID ${EXTEN}'s owner ${PSTN_PERSONAL_OWNER} not authorized for this caller)
|
||||
__ALERT_DENY_PERSONAL_LINE__
|
||||
same => n,Hangup()
|
||||
|
||||
exten => pstn_personal_ring,1,Set(PSTN_MAX_IN=${AST_CONFIG(pstn-limits.conf,limits,max_inbound)})
|
||||
same => n,Set(PSTN_MAX_IN=${IF($["${PSTN_MAX_IN}" = ""]?10:${PSTN_MAX_IN})})
|
||||
same => n,GotoIf($[${GROUP_COUNT(pstn-in)} >= ${PSTN_MAX_IN}]?pstn_in_busy,1)
|
||||
same => n,Set(GROUP()=pstn-in)
|
||||
same => n,Set(PSTN_START=${EPOCH})
|
||||
same => n,Dial(PJSIP/${PSTN_PERSONAL_OWNER},20)
|
||||
same => n,Set(PSTN_DUR=$[${EPOCH} - ${PSTN_START}])
|
||||
same => n,System(printf '%s|in|%s|%s|%s\n' "${PSTN_START}" "${CALLERID(num)}" "${EXTEN}" "${PSTN_DUR}" >> /var/log/asterisk/pstn-trunk-calls.log)
|
||||
same => n,Hangup()
|
||||
EOF
|
||||
|
||||
if [[ -n "$NTFY_URL" ]]; then
|
||||
@@ -383,8 +409,9 @@ EOF
|
||||
sed -i "s#__ALERT_DENY_INBOUND_LINE__# same => n,System(curl -m 5 -s -d 'PSTN trunk: inbound call rejected - caller not approved for any ring target.' '${_esc_url2}' >/dev/null 2>\\&1 \\&)#" "$FILE"
|
||||
sed -i "s#__ALERT_BUSY_IN_LINE__# same => n,System(curl -m 5 -s -d 'PSTN trunk: inbound concurrent-call cap reached - a call was rejected.' '${_esc_url2}' >/dev/null 2>\\&1 \\&)#" "$FILE"
|
||||
sed -i "s#__ALERT_KILLED_IN_LINE__# same => n,System(curl -m 5 -s -H 'Priority: urgent' -d 'PSTN trunk: inbound call rejected - spend-cap kill-switch is tripped.' '${_esc_url2}' >/dev/null 2>\\&1 \\&)#" "$FILE"
|
||||
sed -i "s#__ALERT_DENY_PERSONAL_LINE__# same => n,System(curl -m 5 -s -d 'PSTN trunk: inbound call to a personal DID rejected - owner not authorized for this caller.' '${_esc_url2}' >/dev/null 2>\\&1 \\&)#" "$FILE"
|
||||
else
|
||||
sed -i "/__ALERT_DENY_INBOUND_LINE__/d; /__ALERT_BUSY_IN_LINE__/d; /__ALERT_KILLED_IN_LINE__/d" "$FILE"
|
||||
sed -i "/__ALERT_DENY_INBOUND_LINE__/d; /__ALERT_BUSY_IN_LINE__/d; /__ALERT_KILLED_IN_LINE__/d; /__ALERT_DENY_PERSONAL_LINE__/d" "$FILE"
|
||||
fi
|
||||
}
|
||||
|
||||
@@ -411,18 +438,30 @@ _pstn_write_limits_file() {
|
||||
# ── Shared: initial permission tiers (fresh install / explicit reset only —
|
||||
# "update in place" never calls this, matching how .env/firewall/Caddy config
|
||||
# are protected elsewhere in this repo; see file-level comment above) ──────
|
||||
# Args: FILE, space-separated FULL_EXTS, space-separated MESSAGING_EXTS, then
|
||||
# "ext" "pipe|separated|numbers" pairs for each restricted extension.
|
||||
# Args: FILE, space-separated FULL_EXTS, space-separated MESSAGING_EXTS,
|
||||
# space-separated "ext=did" PERSONAL_DID_ASSIGNMENTS, then "ext"
|
||||
# "pipe|separated|numbers" pairs for each restricted extension.
|
||||
_pstn_write_permissions_file() {
|
||||
local FILE="$1" FULL_EXTS="$2" MESSAGING_EXTS="$3"
|
||||
shift 3
|
||||
local FILE="$1" FULL_EXTS="$2" MESSAGING_EXTS="$3" PERSONAL_DID_ASSIGNMENTS="$4"
|
||||
shift 4
|
||||
local -A _personal_did_map=()
|
||||
local _pd_token
|
||||
for _pd_token in $PERSONAL_DID_ASSIGNMENTS; do
|
||||
_personal_did_map["${_pd_token%%=*}"]="${_pd_token#*=}"
|
||||
done
|
||||
local _written_exts=""
|
||||
{
|
||||
echo "; PSTN permission tiers — internal / restricted / full — PLUS an independent"
|
||||
echo "; 'messaging' flag for Asterisk's native internal SIP MESSAGE texting (no"
|
||||
echo "; carrier SMS, no PSTN, no cost — a separate axis from PSTN calling, since"
|
||||
echo "; the risk profile is different: an extension can be internal-tier for"
|
||||
echo "; calling and still messaging-enabled, or vice versa)."
|
||||
echo "; PSTN permission tiers — internal / restricted / full — PLUS two independent"
|
||||
echo "; axes per extension:"
|
||||
echo "; - 'messaging' for Asterisk's native internal SIP MESSAGE texting (no carrier"
|
||||
echo "; SMS, no PSTN, no cost — a separate axis from PSTN calling, since the risk"
|
||||
echo "; profile is different: an extension can be internal-tier for calling and"
|
||||
echo "; still messaging-enabled, or vice versa)."
|
||||
echo "; - 'personal_did' assigns this extension its own DID (see"
|
||||
echo "; pstn-personal-dids.conf, which the dialplan reads for inbound routing —"
|
||||
echo "; this key here is only the OUTBOUND Caller-ID override). A personal DID"
|
||||
echo "; only actually rings anyone if its owner is also full or restricted tier —"
|
||||
echo "; internal tier means no PSTN either way, personal DID or not."
|
||||
echo "; Read LIVE by the dialplan on every call (AST_CONFIG()) — no Asterisk"
|
||||
echo "; restart needed when this changes. Edit here directly, via the Security"
|
||||
echo "; Dashboard web UI's \"PSTN Trunk\" tab (if installed), or by re-running"
|
||||
@@ -438,6 +477,7 @@ _pstn_write_permissions_file() {
|
||||
echo "[$_ext]"
|
||||
echo "tier=full"
|
||||
[[ " $MESSAGING_EXTS " == *" $_ext "* ]] && echo "messaging=yes"
|
||||
[[ -n "${_personal_did_map[$_ext]:-}" ]] && echo "personal_did=${_personal_did_map[$_ext]}"
|
||||
echo ""
|
||||
_written_exts="$_written_exts $_ext"
|
||||
done
|
||||
@@ -448,6 +488,7 @@ _pstn_write_permissions_file() {
|
||||
echo "tier=restricted"
|
||||
echo "allowed_numbers=${_nums}"
|
||||
[[ " $MESSAGING_EXTS " == *" $_ext "* ]] && echo "messaging=yes"
|
||||
[[ -n "${_personal_did_map[$_ext]:-}" ]] && echo "personal_did=${_personal_did_map[$_ext]}"
|
||||
echo ""
|
||||
_written_exts="$_written_exts $_ext"
|
||||
done
|
||||
@@ -455,6 +496,15 @@ _pstn_write_permissions_file() {
|
||||
if [[ " $_written_exts " != *" $_ext "* ]]; then
|
||||
echo "[$_ext]"
|
||||
echo "messaging=yes"
|
||||
[[ -n "${_personal_did_map[$_ext]:-}" ]] && echo "personal_did=${_personal_did_map[$_ext]}"
|
||||
echo ""
|
||||
_written_exts="$_written_exts $_ext"
|
||||
fi
|
||||
done
|
||||
for _ext in "${!_personal_did_map[@]}"; do
|
||||
if [[ " $_written_exts " != *" $_ext "* ]]; then
|
||||
echo "[$_ext]"
|
||||
echo "personal_did=${_personal_did_map[$_ext]}"
|
||||
echo ""
|
||||
fi
|
||||
done
|
||||
@@ -462,6 +512,36 @@ _pstn_write_permissions_file() {
|
||||
chmod 664 "$FILE"
|
||||
}
|
||||
|
||||
# ── Shared: personal DID -> owner-extension mapping (fresh install / explicit
|
||||
# reset only — same "update never touches it" protection as
|
||||
# pstn-permissions.conf). Args: FILE, then "did" "owner" pairs.
|
||||
_pstn_write_personal_dids_file() {
|
||||
local FILE="$1"
|
||||
shift
|
||||
{
|
||||
echo "; Personal DID -> owner-extension mapping. Read LIVE by the dialplan"
|
||||
echo "; (AST_CONFIG()) on every inbound call — no restart needed. An inbound call"
|
||||
echo "; to a DID listed here routes directly to its owner, checked against the"
|
||||
echo "; owner's OWN tier/approved-numbers in pstn-permissions.conf — no ring-group"
|
||||
echo "; fallback, since this is that extension's own number, not the shared line."
|
||||
echo "; The matching outbound Caller-ID override lives in pstn-permissions.conf"
|
||||
echo "; ('personal_did=' per extension) — kept in sync automatically whenever a"
|
||||
echo "; DID is assigned/removed via the CLI installer or the Security Dashboard's"
|
||||
echo "; PSTN Trunk tab, rather than hand-editing both files separately."
|
||||
echo "; The shared trunk DID keeps working as the main/ring-group line regardless"
|
||||
echo "; of anything assigned here."
|
||||
echo ""
|
||||
while [[ $# -gt 0 ]]; do
|
||||
local _did="$1" _owner="$2"
|
||||
shift 2
|
||||
echo "[$_did]"
|
||||
echo "owner=$_owner"
|
||||
echo ""
|
||||
done
|
||||
} > "$FILE"
|
||||
chmod 664 "$FILE"
|
||||
}
|
||||
|
||||
# ── Shared: kill-switch state (fresh install / explicit reset only — same
|
||||
# "update never touches it" protection as pstn-permissions.conf/
|
||||
# pstn-limits.conf) ─────────────────────────────────────────────────────────
|
||||
@@ -1090,13 +1170,14 @@ install_pstn-trunk() {
|
||||
local PERMISSIONS_FILE="$ASTERISK_DIR/pstn-permissions.conf"
|
||||
local LIMITS_FILE="$ASTERISK_DIR/pstn-limits.conf"
|
||||
local KILLSWITCH_FILE="$ASTERISK_DIR/pstn-trunk-killswitch.conf"
|
||||
local PERSONAL_DIDS_FILE="$ASTERISK_DIR/pstn-personal-dids.conf"
|
||||
local SETTINGS_FILE="$EA_DIR/.pstn-trunk.env"
|
||||
local CONTAINER_NAME="easy-asterisk"
|
||||
[[ "$ASTERISK_KIND" == "asterisk-digital-ocean" ]] && CONTAINER_NAME="easy-asterisk-do"
|
||||
|
||||
if [ "$DRY_RUN" = true ]; then
|
||||
echo "[DRY-RUN] Would require an existing asterisk-digital-ocean OR asterisk (LAN) install"
|
||||
echo "[DRY-RUN] Would prompt for: SIP provider name (default VoIP.ms), server/POP hostname, DID,"
|
||||
echo "[DRY-RUN] Would prompt for: SIP provider name (any IP-authenticated provider), server/POP hostname, DID,"
|
||||
echo "[DRY-RUN] full-PSTN extensions, restricted-PSTN extensions + their approved numbers,"
|
||||
echo "[DRY-RUN] internal SIP messaging extensions (separate from PSTN calling permission),"
|
||||
echo "[DRY-RUN] max concurrent outbound/inbound calls (default 10/10), inbound ring-group extensions,"
|
||||
@@ -1133,15 +1214,17 @@ install_pstn-trunk() {
|
||||
log_warning "A static IP from your ISP avoids that; asterisk-digital-ocean sidesteps it entirely."
|
||||
fi
|
||||
|
||||
log_info "Configuring a SIP PSTN trunk for $ASTERISK_KIND (defaults to VoIP.ms)."
|
||||
log_info "Configuring a SIP PSTN trunk for $ASTERISK_KIND (any IP-authenticated provider —"
|
||||
log_info "VoIP.ms and Anveo Direct are both confirmed working; see docs/pstn-calling-voipms-plan.md)."
|
||||
log_info "US-only outbound (NANP dialplan), a concurrent-call cap, per-extension permission"
|
||||
log_info "tiers, an inbound ring-group, and ntfy alerts on denied/rejected calls plus"
|
||||
log_info "spend/volume checks."
|
||||
echo ""
|
||||
log_warning "Before continuing, on your provider's side you should already have: created an"
|
||||
log_warning "account, funded and set up prepaid billing with auto-recharge OFF (VoIP.ms: Client"
|
||||
log_warning "Area -> Balance Management), ordered a DID with IP authentication pointed at this"
|
||||
log_warning "box's public IP, and picked a server/POP. Also restrict outbound routing to"
|
||||
log_warning "Area -> Balance Management; Anveo Direct: fund the account balance directly),"
|
||||
log_warning "ordered a DID with IP authentication pointed at this box's public IP, and picked"
|
||||
log_warning "a server/POP. Also restrict outbound routing to"
|
||||
log_warning "US/NANP on the provider's own side if it offers that — this dialplan is the second,"
|
||||
log_warning "independent layer, not a substitute for the first."
|
||||
log_warning "See docs/pstn-calling-voipms-plan.md for the full background."
|
||||
@@ -1200,7 +1283,7 @@ install_pstn-trunk() {
|
||||
# ── Prompts — provider account details aren't scriptable, set up manually
|
||||
# on the provider's own site first (see warning above) ───────────────────
|
||||
local PROVIDER_NAME=""
|
||||
prompt_text "SIP trunk provider name (for your reference/docs only):" "VoIP.ms" PROVIDER_NAME
|
||||
prompt_text "SIP trunk provider name (for your reference/docs only — e.g. VoIP.ms, Anveo Direct):" "" PROVIDER_NAME
|
||||
|
||||
local TRUNK_SERVER=""
|
||||
prompt_text "Server/POP hostname (e.g. atlanta2.voip.ms for VoIP.ms — pick the one closest to this box from your provider's server list):" "" TRUNK_SERVER
|
||||
@@ -1317,6 +1400,48 @@ install_pstn-trunk() {
|
||||
local MESSAGING_EXTS=""
|
||||
prompt_text "Extensions allowed to use internal SIP messaging (space-separated, blank = none):" "" MESSAGING_EXTS
|
||||
|
||||
# ── Personal numbers — optional, additive to the shared trunk DID ──────
|
||||
# Multiple DIDs can share this one trunk/account. Assigning one to a
|
||||
# specific extension makes inbound calls to it ring ONLY that extension
|
||||
# (still gated by that extension's own tier/approved-numbers — a
|
||||
# personal DID doesn't bypass PSTN permission, it just narrows routing
|
||||
# from "the shared ring group" to "this one owner"), and makes that
|
||||
# extension's outbound calls show its own DID as Caller-ID instead of
|
||||
# the shared one. The shared DID/ring-group above is unaffected either
|
||||
# way — this is purely additive.
|
||||
echo ""
|
||||
echo " Personal numbers (optional): assign specific DIDs to specific extensions."
|
||||
echo " Inbound calls to that DID ring only its owner; outbound calls from that"
|
||||
echo " extension show its own DID as Caller-ID. Requires the owner to also be"
|
||||
echo " full or restricted tier to actually receive anything on it."
|
||||
local WANT_PERSONAL_DIDS=""
|
||||
prompt_yn "Assign any personal DIDs now? (y/n):" "n" WANT_PERSONAL_DIDS
|
||||
local PERSONAL_DID_PAIRS=() PERSONAL_DID_ASSIGNMENTS=""
|
||||
if [[ "$WANT_PERSONAL_DIDS" =~ ^[Yy]$ ]]; then
|
||||
local _pd_more="y"
|
||||
while [[ "$_pd_more" =~ ^[Yy]$ ]]; do
|
||||
local _pd_did="" _pd_owner=""
|
||||
prompt_text " DID (10-digit US number, digits only):" "" _pd_did
|
||||
if [[ "$_pd_did" =~ ^[0-9]{10}$ ]]; then
|
||||
prompt_text " Owner extension for $_pd_did:" "" _pd_owner
|
||||
if [[ "$_pd_owner" =~ ^[0-9]+$ ]]; then
|
||||
PERSONAL_DID_PAIRS+=("$_pd_did" "$_pd_owner")
|
||||
PERSONAL_DID_ASSIGNMENTS="${PERSONAL_DID_ASSIGNMENTS} ${_pd_owner}=${_pd_did}"
|
||||
if [[ " $FULL_EXTS $RESTRICTED_EXTS " != *" $_pd_owner "* ]]; then
|
||||
log_warning "Extension $_pd_owner isn't full/restricted tier yet — it won't actually"
|
||||
log_warning "receive calls on $_pd_did until you also grant it one of those tiers."
|
||||
fi
|
||||
log_success "Will assign $_pd_did to extension $_pd_owner."
|
||||
else
|
||||
log_warning "Not a valid extension — skipped."
|
||||
fi
|
||||
else
|
||||
log_warning "Not a valid 10-digit DID — skipped."
|
||||
fi
|
||||
prompt_yn " Assign another? (y/n):" "n" _pd_more
|
||||
done
|
||||
fi
|
||||
|
||||
echo ""
|
||||
local WANT_NTFY=""
|
||||
prompt_yn "Send an ntfy alert when a call is denied (permission tier/approved-number check failed) or rejected (concurrency cap hit)? (y/n):" "y" WANT_NTFY
|
||||
@@ -1348,7 +1473,7 @@ install_pstn-trunk() {
|
||||
log_info "Spend/volume alert settings (used only to estimate cost and flag unusual usage —"
|
||||
log_info "not billing-accurate, just a safety net)."
|
||||
local RATE_PER_MIN=""
|
||||
prompt_text " Outbound per-minute rate in USD (VoIP.ms US rate is 0.01):" "0.01" RATE_PER_MIN
|
||||
prompt_text " Outbound per-minute rate in USD (check your provider's published rate — e.g. VoIP.ms US is ~0.01, Anveo Direct US is ~0.001):" "0.01" RATE_PER_MIN
|
||||
local MONTH_THRESHOLD=""
|
||||
prompt_text " Alert once when estimated spend this month reaches (USD):" "10" MONTH_THRESHOLD
|
||||
local BURST_THRESHOLD=""
|
||||
@@ -1383,9 +1508,10 @@ install_pstn-trunk() {
|
||||
"$RING_EXTS" "$NTFY_URL" "$RATE_PER_MIN" \
|
||||
"$MONTH_THRESHOLD" "$BURST_THRESHOLD" "$PROVIDER_NAME" "$MAX_MONTHLY_SPEND" "$CONTAINER_NAME" || return 1
|
||||
|
||||
_pstn_write_permissions_file "$PERMISSIONS_FILE" "$FULL_EXTS" "$MESSAGING_EXTS" "${RESTRICTED_ARGS[@]}"
|
||||
_pstn_write_permissions_file "$PERMISSIONS_FILE" "$FULL_EXTS" "$MESSAGING_EXTS" "$PERSONAL_DID_ASSIGNMENTS" "${RESTRICTED_ARGS[@]}"
|
||||
_pstn_write_limits_file "$LIMITS_FILE" "$MAX_OUTBOUND" "$MAX_INBOUND"
|
||||
_pstn_write_killswitch_file "$KILLSWITCH_FILE"
|
||||
_pstn_write_personal_dids_file "$PERSONAL_DIDS_FILE" "${PERSONAL_DID_PAIRS[@]}"
|
||||
ensure_docker_dir_ownership "$ASTERISK_DIR"
|
||||
|
||||
# No new firewall rules: the base install already opens SIP (5060/5061)
|
||||
@@ -1461,9 +1587,10 @@ a strong safety net, not an absolute guarantee against any overage. See
|
||||
cat > "$DOC_FILE" << MD
|
||||
# SIP PSTN trunk (add-on to $ASTERISK_KIND)
|
||||
|
||||
US-only outbound PSTN calling over a SIP trunk (defaults to VoIP.ms, works
|
||||
with any IP-authenticated provider), per-extension permission tiers, a
|
||||
configurable concurrent-call cap, and an inbound ring-group. See
|
||||
US-only outbound PSTN calling over a SIP trunk (any IP-authenticated
|
||||
provider — VoIP.ms and Anveo Direct both confirmed working), per-extension
|
||||
permission tiers, a configurable concurrent-call cap, and an inbound
|
||||
ring-group. See
|
||||
\`docs/pstn-calling-voipms-plan.md\` in the repo for the full design
|
||||
background, cost estimate, and toll-fraud reasoning.
|
||||
|
||||
@@ -1656,13 +1783,31 @@ call-routing precedence in the same \`[intercom]\` context. Treat the
|
||||
permission flag as ready for a dashboard/CLI-managed allow-list once that
|
||||
routing is confirmed, not as fully wired yet.
|
||||
|
||||
## Personal numbers
|
||||
|
||||
Multiple DIDs can share this one trunk/account — assign one to a specific
|
||||
extension and inbound calls to it route straight to that owner, still
|
||||
gated by the owner's own tier/approved-numbers (no ring-group fallback,
|
||||
since it's that extension's own line, not the shared one), while that
|
||||
extension's outbound calls show its own DID as Caller-ID instead of the
|
||||
shared trunk DID above. Entirely additive: the shared DID/ring-group keeps
|
||||
working for everyone regardless of what's assigned here.
|
||||
|
||||
$([ "${#PERSONAL_DID_PAIRS[@]}" -gt 0 ] && { local _i; for ((_i=0; _i<${#PERSONAL_DID_PAIRS[@]}; _i+=2)); do echo "- \`${PERSONAL_DID_PAIRS[$_i]}\` -> extension ${PERSONAL_DID_PAIRS[$_i+1]}"; done; } || echo "None assigned yet.")
|
||||
|
||||
Stored in \`config/asterisk/pstn-personal-dids.conf\` (DID -> owner, read live
|
||||
by the dialplan for inbound routing) and a \`personal_did=\` field per
|
||||
extension in \`pstn-permissions.conf\` (the outbound Caller-ID override) —
|
||||
both kept in sync automatically by the CLI installer and the Security
|
||||
Dashboard's "PSTN Trunk" tab, live, no restart needed.
|
||||
|
||||
## Managing this from a web UI
|
||||
|
||||
If \`services/security-dashboard.sh\` is installed, its "PSTN Trunk" tab
|
||||
shows both the per-extension permission tiers and the outbound/inbound
|
||||
concurrency caps, all editable live — no restart, no reinstall. Install/
|
||||
update it any time with \`sudo ./setup.sh security-dashboard\`; it
|
||||
auto-detects this install.
|
||||
shows the per-extension permission tiers, the outbound/inbound concurrency
|
||||
caps, and personal-number assignments, all editable live — no restart, no
|
||||
reinstall. Install/update it any time with \`sudo ./setup.sh
|
||||
security-dashboard\`; it auto-detects this install.
|
||||
|
||||
## Manual edits
|
||||
|
||||
|
||||
Reference in New Issue
Block a user