From 4c0326ca5f7b4885f8f5216a73390d9a5307736c Mon Sep 17 00:00:00 2001 From: Claude Date: Thu, 23 Jul 2026 16:05:52 +0000 Subject: [PATCH 1/5] Update Anveo Direct dial-prefix warning with live-confirmed findings The dialing-prefix ambiguity is resolved: Anveo's own trunk edit screen has no required-field marker on Dialing Prefix, confirming it's optional. Also documents the separate Outbound Service Trunk vs. inbound SIP Trunk distinction discovered while walking through a real account setup. --- services/pstn-trunk.sh | 15 +++++++++------ 1 file changed, 9 insertions(+), 6 deletions(-) diff --git a/services/pstn-trunk.sh b/services/pstn-trunk.sh index cb1c23b..14caff3 100644 --- a/services/pstn-trunk.sh +++ b/services/pstn-trunk.sh @@ -1315,12 +1315,15 @@ install_pstn-trunk() { log_warning "requires a verified/Anveo-owned number as Caller-ID — any DID you order" log_warning "through them satisfies that automatically (see this service's personal-" log_warning "number feature for assigning a specific one per extension)." - log_warning "UNVERIFIED: Anveo's own outbound-trunk page documents dialing as" - log_warning "[PREFIX]PHONENUMBER@sbc.anveo.com (a per-trunk prefix) — this contradicts" - log_warning "an earlier no-prefix-needed finding from their FAQ. This dialplan dials" - log_warning "the bare number, no prefix. Check whether your configured trunk's Prefix" - log_warning "field can be left blank before relying on this — if it can't, outbound" - log_warning "calls through that trunk won't match and will fail." + log_info "Confirmed live (2026-07-23): the trunk's own Dialing Prefix field has no" + log_info "required-field marker on Anveo's Edit Call Termination Trunk screen — it's" + log_info "optional. Leave it blank; this dialplan dials the bare number, no prefix." + log_warning "Anveo Direct also separates two different 'trunk' concepts — don't confuse them:" + log_warning "an Outbound Service (Call Termination) Trunk handles calls OUT (IP-authenticated," + log_warning "what this installer configures), while inbound routing for a DID is a SEPARATE" + log_warning "'SIP Trunk' object (Account -> the DID's Call Options tab -> Destination SIP" + log_warning "Trunk) that forwards to a SIP URI — e.g. \$[E164]\$@:5060." + log_warning "Create both in Anveo's portal; this installer only handles the Asterisk side." ;; 2) _default_provider_name="VoIP.ms" From 526cb4a6232485260c9ff07341080d9945cd326a Mon Sep 17 00:00:00 2001 From: Claude Date: Thu, 23 Jul 2026 16:14:19 +0000 Subject: [PATCH 2/5] Fix prompt_yn silently discarding its default on empty input Confirmed live: pressing Enter on any "(y/n): y"-style prompt set the variable to an empty string instead of the stated default, since prompt_yn had no ${response:-$default} fallback (prompt_text already had one). Every downstream [[ "$VAR" =~ ^[Yy]$ ]] check treated "just press Enter" as no. Also shows the default value in the prompt text itself for both helpers, since neither displayed it before. --- lib/common.sh | 17 ++++++++++++----- 1 file changed, 12 insertions(+), 5 deletions(-) diff --git a/lib/common.sh b/lib/common.sh index eca7cf0..349af3f 100644 --- a/lib/common.sh +++ b/lib/common.sh @@ -413,21 +413,28 @@ validate_password() { # Prompt yes/no, honoring unattended. prompt_yn "Question?" "default" VARNAME prompt_yn() { - local question="$1" default="$2" varname="$3" response + local question="$1" default="$2" varname="$3" response hint="" if [ "$UNATTENDED" = true ]; then eval "$varname='$default'"; echo "$question [auto: $default]"; return fi - read -p "$question " response - eval "$varname='$response'" + # Confirmed live: this used to have no fallback to $default at all here — + # pressing Enter on a stated "(y/n): y" default silently set the variable + # to an EMPTY string, not "y", so every downstream `[[ "$VAR" =~ ^[Yy]$ ]]` + # check treated "just press Enter to accept the default" as a no. Every + # prompt_yn call in every service was affected. + [ -n "$default" ] && hint=" [$default]" + read -p "${question}${hint} " response + eval "$varname='${response:-$default}'" } # Prompt text, honoring unattended. prompt_text "Question?" "default" VARNAME prompt_text() { - local question="$1" default="$2" varname="$3" response + local question="$1" default="$2" varname="$3" response hint="" if [ "$UNATTENDED" = true ]; then eval "$varname='$default'"; echo "$question [auto: $default]"; return fi - read -p "$question " response + [ -n "$default" ] && hint=" [$default]" + read -p "${question}${hint} " response eval "$varname='${response:-$default}'" } From 16da6c13726f781506083b2bee321a898f2cefdf Mon Sep 17 00:00:00 2001 From: Claude Date: Thu, 23 Jul 2026 16:44:35 +0000 Subject: [PATCH 3/5] =?UTF-8?q?Clarify=20PSTN=20tier=20prompts=20=E2=80=94?= =?UTF-8?q?=20enter=20extension=20numbers,=20not=20tier=20names?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit User feedback: reading the tier list (internal/restricted/full) right before "Full-PSTN extensions:" read as ambiguous about whether to type the tier word or extension numbers. Reworded both prompts to say "extension NUMBERS" explicitly and added inline examples (e.g. '999 213'). --- services/pstn-trunk.sh | 16 ++++++++-------- 1 file changed, 8 insertions(+), 8 deletions(-) diff --git a/services/pstn-trunk.sh b/services/pstn-trunk.sh index 14caff3..57981bc 100644 --- a/services/pstn-trunk.sh +++ b/services/pstn-trunk.sh @@ -1380,20 +1380,20 @@ install_pstn-trunk() { # ── Permission tiers ─────────────────────────────────────────────────── echo "" - echo " Three tiers, per extension:" + echo " Three PSTN permission tiers. Below, you'll enter EXTENSION NUMBERS at each" + echo " prompt (e.g. 999, 213) — never the tier name itself:" echo " internal — call/receive other Asterisk extensions + internal ring" - echo " groups only. No PSTN at all. Default for anything not" - echo " listed below." + echo " groups only. No PSTN at all. The default for any extension" + echo " not entered at either prompt below — nothing to type for it." echo " restricted — internal, PLUS call/receive ONLY pre-approved US numbers." echo " full — internal, PLUS call/receive ANY US number." - echo " These are managed LIVE after install (pstn-permissions.conf) — via the" - echo " Security Dashboard web UI if installed, or by hand — with no restart or" - echo " reinstall needed to change them later." + echo " Live-editable after install (pstn-permissions.conf) — via the Security" + echo " Dashboard web UI if installed, or by hand — no restart/reinstall needed." local FULL_EXTS="" - prompt_text "Full-PSTN extensions (space-separated, blank = none):" "" FULL_EXTS + prompt_text "Extension NUMBERS to grant FULL PSTN access (space-separated, e.g. '999 213', blank = none):" "" FULL_EXTS local RESTRICTED_EXTS="" - prompt_text "Restricted-PSTN extensions (space-separated, blank = none):" "" RESTRICTED_EXTS + prompt_text "Extension NUMBERS to grant RESTRICTED PSTN access (space-separated, e.g. '301', blank = none):" "" RESTRICTED_EXTS local RESTRICTED_ARGS=() if [[ -n "$RESTRICTED_EXTS" ]]; then From fdd2aaa1d3a2f14b61f294414dbcca378f8f1076 Mon Sep 17 00:00:00 2001 From: Claude Date: Thu, 23 Jul 2026 16:49:19 +0000 Subject: [PATCH 4/5] Add shared approved-numbers pool + whiptail multi-select per extension Restricted extensions previously required retyping the full 11-digit number list from scratch at each extension's own prompt. Now offers entering a pool once, then a whiptail checklist per extension to pick a subset (falls back to typing numbers directly, or 'all' for the whole pool, when whiptail isn't available or the run is unattended). --- services/pstn-trunk.sh | 45 ++++++++++++++++++++++++++++++++++++++++-- 1 file changed, 43 insertions(+), 2 deletions(-) diff --git a/services/pstn-trunk.sh b/services/pstn-trunk.sh index 57981bc..3662eb8 100644 --- a/services/pstn-trunk.sh +++ b/services/pstn-trunk.sh @@ -1397,10 +1397,51 @@ install_pstn-trunk() { local RESTRICTED_ARGS=() if [[ -n "$RESTRICTED_EXTS" ]]; then + # Shared pool, entered once — faster than retyping the same numbers + # per extension when several extensions overlap. Picking per + # extension then uses a whiptail checklist (multi-select, toggle + # with space) against this pool if whiptail is available and this + # isn't an unattended run; otherwise falls back to typing numbers + # directly (or "all" for the whole pool) per extension, same as + # before this existed. + echo "" + echo " Optional: enter a shared pool of approved numbers ONCE below, then pick" + echo " which ones apply to each restricted extension next — instead of retyping" + echo " the same numbers for every extension that shares them." + local MASTER_NUMS_RAW="" MASTER_NUMS=() + prompt_text " Approved-numbers pool (comma/space-separated, 11-digit US numbers, e.g. '15551234567 15559876543', blank = enter per-extension instead):" "" MASTER_NUMS_RAW + if [[ -n "$MASTER_NUMS_RAW" ]]; then + local _pool_n + while IFS= read -r _pool_n; do + [[ -n "$_pool_n" ]] && MASTER_NUMS+=("$_pool_n") + done < <(echo "$MASTER_NUMS_RAW" | tr ', ' '\n\n' | grep -E '^[0-9]{11}$' | sort -u) + if [[ ${#MASTER_NUMS[@]} -eq 0 ]]; then + log_warning "No valid 11-digit numbers found in that pool — falling back to per-extension entry." + else + log_success "Pool: ${#MASTER_NUMS[@]} number(s) — ${MASTER_NUMS[*]}" + fi + fi + local _ext _raw_nums _clean_nums for _ext in $RESTRICTED_EXTS; do - prompt_text " Approved numbers for extension $_ext (comma/space-separated, 11-digit US numbers, e.g. 15551234567):" "" _raw_nums - _clean_nums="$(echo "$_raw_nums" | tr ', ' '\n\n' | grep -E '^[0-9]{11}$' | paste -sd'|' - 2>/dev/null)" + _clean_nums="" + if [[ ${#MASTER_NUMS[@]} -gt 0 ]] && command -v whiptail >/dev/null 2>&1 && [[ "$UNATTENDED" != true ]]; then + local _wt_args=() _wt_n _selected + for _wt_n in "${MASTER_NUMS[@]}"; do + _wt_args+=("$_wt_n" "" "off") + done + _selected="$(whiptail --title "Extension $_ext" --checklist \ + "Approved numbers for extension $_ext (space to toggle, Enter to confirm):" \ + 20 70 10 "${_wt_args[@]}" 3>&1 1>&2 2>&3)" + [[ -n "$_selected" ]] && _clean_nums="$(echo "$_selected" | tr -d '"' | tr ' ' '\n' | paste -sd'|' -)" + else + prompt_text " Approved numbers for extension $_ext (comma/space-separated, 11-digit US numbers, e.g. 15551234567, or 'all' for the whole pool above):" "" _raw_nums + if [[ "$_raw_nums" == "all" && ${#MASTER_NUMS[@]} -gt 0 ]]; then + _clean_nums="$(printf '%s\n' "${MASTER_NUMS[@]}" | paste -sd'|' -)" + else + _clean_nums="$(echo "$_raw_nums" | tr ', ' '\n\n' | grep -E '^[0-9]{11}$' | paste -sd'|' - 2>/dev/null)" + fi + fi if [[ -z "$_clean_nums" ]]; then log_warning "No valid 11-digit numbers entered for $_ext — it will be restricted with an EMPTY" log_warning "approved list, meaning no PSTN number can currently reach/be reached by it until" From 67ded2eb387dd571fcea892d526164e5672eac3a Mon Sep 17 00:00:00 2001 From: Claude Date: Thu, 23 Jul 2026 16:55:22 +0000 Subject: [PATCH 5/5] Use the confirmed Anveo Direct Prime rate as the RATE_PER_MIN default 0.00388/min for standard US-to-US domestic, confirmed against Anveo's own Prime rate card CSV (the route set the outbound trunk's LCR config actually pulls from) rather than the earlier ~0.001 ballpark guess. Defaults to it automatically when the Anveo Direct quick-pick was chosen. --- services/pstn-trunk.sh | 10 +++++++++- 1 file changed, 9 insertions(+), 1 deletion(-) diff --git a/services/pstn-trunk.sh b/services/pstn-trunk.sh index 3662eb8..a3298d1 100644 --- a/services/pstn-trunk.sh +++ b/services/pstn-trunk.sh @@ -1561,8 +1561,16 @@ install_pstn-trunk() { echo "" log_info "Spend/volume alert settings (used only to estimate cost and flag unusual usage —" log_info "not billing-accurate, just a safety net)." + # Confirmed live (2026-07-23) against Anveo Direct's own "Prime" rate card + # (the route set selected on the outbound trunk's Custom LCR config, + # "Get Routes/Carriers from: All Prime Routes"): standard US-to-US + # domestic is $0.00388/min, billed per-second — that CSV is the actual + # rate an Anveo Direct Prime trunk pays, not an estimate. VoIP.ms's own + # rate is still an unconfirmed ballpark. + local _default_rate="0.01" + [[ "$_provider_choice" == "1" ]] && _default_rate="0.00388" local 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 + 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 Prime rate is 0.00388 confirmed):" "$_default_rate" RATE_PER_MIN local MONTH_THRESHOLD="" prompt_text " Alert once when estimated spend this month reaches (USD):" "10" MONTH_THRESHOLD local BURST_THRESHOLD=""