Merge pull request #220 from outis1one/claude/sip-voip-integration-atsins

Claude/sip voip integration atsins
This commit is contained in:
Outis
2026-07-23 21:42:11 -04:00
committed by GitHub
3 changed files with 327 additions and 23 deletions
+220
View File
@@ -0,0 +1,220 @@
# Anveo Direct + Easy Asterisk — confirmed working setup guide
This is the exact sequence that got a real Anveo Direct DID working end to
end (both outbound and inbound) with `asterisk-digital-ocean.sh` +
`pstn-trunk.sh`, confirmed live on a real droplet. Follow it in order for
each additional number — steps 12 are one-time account setup; steps
37 repeat per DID.
## 0. Prerequisites
- `asterisk-digital-ocean.sh` (or `asterisk.sh` for a LAN box) already
installed and running, with at least one extension configured.
- This box's public IP address (`curl -4 ifconfig.me`).
## 1. Anveo Direct account (one-time)
1. Create an account at `https://www.anveo.com/account.asp?account_type=direct`
— this is what makes it a **Direct** account (trunk/DID product), not
the hosted-PBX product.
2. Fund the account balance directly (not a subscription plan). Bank
transfers can take several hours to clear.
3. **Known account-level caps as of this writing**, both separate from
anything this installer enforces:
- **Trial account limits**: 2 concurrent outbound calls (CPS: 2),
$2/30-day spend limit, $2 minimum balance — shown on the Outbound
Trunks page under "Account Limits." A "request higher limits" link
sits next to the CallerID Policies section for asking Anveo to lift
these once funded.
- **Phone Numbers cap**: "You can manage up to 2 phone numbers. Please
contact customer support to increase the limit" — a **separate** cap
from the concurrent-call limit above, shown on the Phone Numbers
summary page. Anveo indicated (not independently confirmed) that
removing this requires providing a tax ID (EIN for a business, SSN
for an individual) — decide for yourself whether that trade-off is
worth it before planning on more than 2 DIDs.
4. CallerID policy: USA/Canada/international destinations all require
CallerID **from a verified/Anveo-owned number** — any DID ordered
through Anveo satisfies this automatically.
## 2. Order a DID
1. Go to the DID ordering tool ("Phone Numbers around the World" /
similar), pick a number, and select the **Per Minute** rate plan — not
Prime.
- **Per Minute**: $0.004/min incoming, 10 dedicated incoming channels
bundled in, no separate trunk product needed for inbound.
- **Prime**: free incoming, but requires a separately-purchased "Anveo
Trunk" (a *different* product — flat monthly fee, $6.50$17/mo by
zone, confirmed via the "Anveo Direct Trunk Price List" page). Per
Minute avoids this fee entirely, which is why it's the one to use.
2. Confirm: $0.25 one-time setup, $0.15/month recurring (billed the 15th),
3-month minimum term, pre-paid from account balance.
## 3. Create the Outbound Service (Call Termination) Trunk
This only needs to be done **once** — the same trunk carries outbound
calls for every DID/extension. Skip this step for DIDs 2+.
1. Outbound Trunks → **Add a new Call Termination Trunk**.
2. Fill in:
- **Title**: any label (e.g. `asterisk-do`)
- **Dialing Prefix**: leave **blank** — confirmed not required (no red
"required" asterisk on this field, unlike Authorized IP Addresses and
Call Routing Method). The dialplan here dials the bare number, no
prefix.
- **Authorized IP Addresses**: this box's public IP, one per line. This
is the IP-authentication Anveo uses instead of a SIP password.
- **Rate Cap**: optional safety ceiling — e.g. `Yes, $1/min` filters out
any route pricier than that. Domestic US termination runs far below
this, so it doesn't interfere with normal calls, just blocks getting
routed onto an absurdly expensive carrier by accident.
- **Concurrent Calls Limit**: whatever you want as an Anveo-side cap
(e.g. `Yes, 6`) — note the account's Trial "Outbound Channels: 2"
limit (see step 1) overrides this until lifted; whichever number is
lower wins.
- **Call Routing Method**: leave the default **Custom Least Cost
Routing Model** and all of its sub-fields (All Prime Routes / cost
lowest-to-highest / 5 routes / cost-ordered failover / 10 sec
timeout) exactly as shown — sensible defaults, nothing to tune here.
3. Save.
### Finding the real outbound rate
The trunk's LCR pulls from **"All Prime Routes"** by default — meaning
your real per-minute cost is whatever's in the **Anveo Direct Prime** rate
card (Outbound Services/Call Termination page → "Download rates here"
under the **Prime** column, not Value/Standard/All). Confirmed from that
CSV: standard US-to-US domestic is a flat **`$0.00388/min`**, billed
per-second (not rounded to the minute) — this is the generic `USA.`
catch-all entry (prefix `1`); a few specific area codes have their own
slightly different override rates, and Virgin Islands is billed
separately/higher (~$0.02/min) despite looking like a normal US number.
## 4. Create the SIP Trunk (inbound forwarding) — once per DID
Anveo has **two unrelated "trunk" concepts** — don't confuse them:
- The **Outbound Service Trunk** from step 3 handles calls *out*.
- A separate **"SIP Trunk"** object (Account Options → wherever inbound
routing objects live) handles calls *in* — this is what actually
populates a DID's "Destination SIP Trunk" dropdown.
For **each DID**, create one of these:
1. **Trunk Name**: any label (e.g. `asterisk-do-inbound`) — can reuse the
same one for multiple DIDs if you want them all forwarding the same
way, or make one per DID for clarity.
2. **Primary**: type **SIP URI**, value:
```
$[E164]$@<this box's public IP>:5060
```
(port 5060 — Easy Asterisk's default SIP port, confirmed from its own
vendor source `DEFAULT_SIP_PORT="5060"`). The `$[E164]$` placeholder is
replaced by Anveo with the DID's own number, digits only, country code
included, no `+` — e.g. DID `+15551234567` becomes `15551234567@IP:5060`
in the actual INVITE Anveo sends.
3. **Failover**: leave blank.
4. Save.
5. Go to the DID's own **Call Options** tab → set **Destination SIP
Trunk** to this new SIP Trunk object (not the Outbound Service Trunk)
→ Save.
6. **Optional, recommended once you're adding more DIDs**: Account
Options → Service Defaults → set **Default Destination Trunk** to this
SIP Trunk, so future DIDs auto-route here without repeating step 5.
## 5. Configure the droplet
```
sudo ./setup.sh pstn-trunk
```
- Existing install → choose **update** (`r`) if you're just changing the
DID/server, or the CLI will walk fresh prompts if none exists yet.
- Provider quick-pick: **1) Anveo Direct** — pre-fills `sbc.anveo.com` and
Anveo's 4 published signaling IPs (only one of which the hostname
itself resolves to at any given moment; the others are entered as
"additional known source IPs" so inbound is recognized regardless of
which one a call actually arrives from).
- **DID**: 10 digits, no leading `1` (e.g. `5551234567`).
- **Outbound per-minute rate**: `0.00388` (see step 3's rate-lookup note)
— pre-filled automatically when Anveo Direct is the selected provider.
- Concurrent-call caps, ring-group extensions, ntfy topic, spend-cap
kill-switch thresholds: set to taste (all live-editable later without
reinstalling).
- Who can call/be called (permission tiers, approved numbers), internal
SIP messaging, and personal-number assignment are **not** prompted here
— set them all in the Security Dashboard's **PSTN Trunk** tab instead
(install it first if you haven't: `sudo ./setup.sh security-dashboard`).
Every extension starts at `internal` (no PSTN access) until granted
there.
## 6. Grant permissions and (optionally) a personal number
In the Security Dashboard's PSTN Trunk tab:
1. Set the tier for each extension that should get PSTN access (`full` or
`restricted` — `restricted` also needs at least one approved number).
2. **Personal numbers** card: assign this DID to a specific extension if
you want inbound calls to it to ring *only* that extension (instead of
the whole shared ring-group) and its outbound Caller-ID to show this
DID instead of the shared trunk DID. Enter the DID as **10 digits, no
leading 1** (matches the same convention as the trunk DID above) —
the dashboard's own input validates against exactly that format.
## 7. Test
- **Outbound**: from a full/restricted-tier extension in Sipnetic, dial
`1` + area code + number (11 digits total — the dialplan matches the
full NANP pattern, not a bare 10-digit number without the leading `1`).
- **Inbound**: from any outside phone, dial the DID's real number. If a
personal number was assigned, only that extension should ring; if not,
the whole configured ring-group rings simultaneously until someone
answers or 20 seconds pass.
- Watch the live console while testing either direction:
```
docker exec -it easy-asterisk-do asterisk -rvvv
```
## Bugs hit and fixed along the way (informational — already fixed)
These were all real, confirmed-live bugs in earlier versions of this
installer, not configuration mistakes — listed here for context on what
"just works" now that didn't before:
- **NANP dial pattern was one digit short** (`_1NXXNXXXXX` / `_NXXNXXXXX`,
10/9 characters instead of the correct 11/10) — Asterisk's exact-length
pattern matching never matched a real number at all.
- **`${CHANNEL(peername)}`** isn't a valid channel-function item on this
Asterisk build — broke the caller-extension lookup used for every
permission check.
- **`${EXTEN}` corruption after `Goto` to a named extension** — every
`Goto(somelabel,1)` to a *different* extension (not a same-extension
priority label) resets `${EXTEN}` to that label's own name, silently
breaking the approved-numbers check, the international country check,
and the final `Dial()` itself. Fixed by capturing the real dialed
number into its own variable before any such jump.
- **Duplicate ring-group extensions** (the same extension listed twice in
`RING_EXTS`) generated duplicate dialplan priority labels, causing an
infinite loop that never reached the ring step at all. Now deduped
automatically.
- **Personal-DID lookup format mismatch** — DIDs are stored 10-digit, but
Anveo delivers the called number as 11-digit E.164 in `${EXTEN}`; the
lookup now normalizes to 10 digits first.
- **Easy Asterisk only regenerates `pjsip.conf`/`extensions.conf` if they
don't already exist** — a box that already has devices configured (the
normal case) never picks up a vendor-script `#include` patch on a plain
restart. Both the PSTN trunk and internal-messaging installers now also
patch the live config files directly, not just the generator source.
- **A stale/malformed `.pstn-trunk.env`** could crash the entire installer
under `setup.sh`'s `set -u` on `source`. Now guarded and falls back to
safe defaults, erroring clearly only if genuinely required fields are
missing.
## Still open
- The 2-phone-number account cap (see step 1) blocks getting all 4
planned numbers until either Anveo lifts it or a tax ID is provided —
tabled for now.
- The interactive CLI walkthrough (`pstn-trunk.sh` actually prompting
through account setup step by step, not just this static doc) hasn't
been built yet — this guide is the reference for building that once
there's appetite for it.
+6
View File
@@ -3,6 +3,12 @@
Research and decisions from a design discussion, saved here so the work can
be picked up in a fresh chat without re-deriving the background.
**For the confirmed-working, step-by-step Anveo Direct account + droplet
setup (real values, real bugs hit and fixed), see
`docs/anveo-direct-setup-guide.md` instead of re-deriving it from this
file** — this doc is the design/decision log; that one is the clean
how-to.
**Implemented** — see `services/pstn-trunk.sh` (run `sudo ./setup.sh
pstn-trunk` after `asterisk-digital-ocean` **or** `asterisk` (home/LAN) is
installed — both are supported, see the file for the static-IP caveat on the
+101 -23
View File
@@ -1211,6 +1211,101 @@ ENV
_pstn_install_periodic_timer "$EA_DIR"
}
# Simple "press Enter to continue" pause — the portal steps below happen in
# a browser, not this terminal, so there's nothing to validate; this just
# gates pacing. Auto-skips under UNATTENDED (prompt_text's own behavior).
_pstn_wait_continue() {
local _msg="$1" _ignored=""
prompt_text " $_msg" "" _ignored
}
# ── Anveo Direct walkthrough ────────────────────────────────────────────────
# Every value/field below is confirmed working end-to-end against a real
# account and a real call (both directions) — see
# docs/anveo-direct-setup-guide.md for the full narrative this was built
# from, including every bug that was hit and fixed along the way to get
# here. Nothing here is automatable (it's a real account behind a browser),
# so this walks the account-side steps with a pause between each, then lets
# the rest of this installer's existing prompts (DID, ring group, etc.)
# handle the Asterisk side as normal.
_pstn_anveo_walkthrough() {
local _pub_ip=""
_pub_ip="$(curl -fsS -4 --max-time 3 ifconfig.me 2>/dev/null || true)"
[[ -z "$_pub_ip" ]] && _pub_ip="<this box's public IP — run: curl -4 ifconfig.me>"
echo ""
log_info "Anveo Direct walkthrough — each step below happens in Anveo's own portal,"
log_info "not this terminal. Press Enter after each one to move to the next. Every"
log_info "value given here is confirmed to work, not a guess."
echo ""
echo " Step 1/5 — Account"
echo " https://www.anveo.com/account.asp?account_type=direct"
echo " Create/verify your account, then fund the balance directly (NOT a"
echo " subscription plan) — \$25 is plenty to start. Bank transfers can take a"
echo " few hours to clear. Two account-level caps to know about, both separate"
echo " from anything this installer enforces:"
echo " - Trial limits: 2 concurrent outbound calls, \$2/30-day spend cap, \$2"
echo " minimum balance — a 'request higher limits' link sits next to"
echo " CallerID Policies once you're funded."
echo " - Phone-numbers cap: most new accounts can manage up to 2 DIDs;"
echo " raising it means contacting Anveo support (they may ask for a tax"
echo " ID — your call whether that trade-off is worth it)."
echo " CallerID policy: outbound Caller-ID must be a verified/Anveo-owned"
echo " number — any DID you order through them satisfies this automatically."
_pstn_wait_continue "Press Enter once your account is created and funded:"
echo ""
echo " Step 2/5 — Order a DID"
echo " Order a phone number choosing the 'Per Minute' rate plan — NOT Prime."
echo " Per Minute bundles 10 dedicated incoming channels at \$0.004/min with no"
echo " separate trunk fee; Prime is free incoming but requires a separate,"
echo " flat monthly-fee trunk product you don't need."
_pstn_wait_continue "Press Enter once you've ordered a DID:"
echo ""
echo " Step 3/5 — Outbound Service (Call Termination) Trunk"
echo " Skip this step if you already have one from a previous number — one"
echo " trunk serves every DID on the account, this only needs doing once."
echo " Outbound Trunks -> Add a new Call Termination Trunk:"
echo " Title: any label"
echo " Dialing Prefix: leave BLANK (confirmed not required)"
echo " Authorized IP Addresses: ${_pub_ip}"
echo " Rate Cap: optional, e.g. \$1/min (a safety ceiling only —"
echo " domestic calls run far below this)"
echo " Concurrent Calls Limit: your choice (the Trial account's own 2-call"
echo " cap from Step 1 overrides this until lifted)"
echo " Call Routing Method: leave 'Custom Least Cost Routing Model' and"
echo " all its sub-fields exactly as defaulted"
_pstn_wait_continue "Press Enter once the trunk is created:"
echo ""
echo " Step 4/5 — SIP Trunk (inbound forwarding) — one per DID"
echo " A SEPARATE object from the Outbound trunk above — don't confuse them;"
echo " this is what actually makes inbound calls to THIS DID ring anywhere."
echo " Trunk Name: any label"
echo " Primary: SIP URI -> \$[E164]\$@${_pub_ip}:5060"
echo " Failover: leave blank"
echo " Then open this DID's own Call Options tab -> Destination SIP Trunk ->"
echo " select the SIP Trunk you just created -> Save."
_pstn_wait_continue "Press Enter once done:"
echo ""
echo " Step 5/5 — Confirmed rate"
echo " Standard US-to-US domestic on the Prime rate card (the route set this"
echo " trunk's Custom LCR pulls from by default) is \$0.00388/min, billed"
echo " per-second — already the default a few prompts from now, no need to"
echo " look it up yourself unless you want to double-check it."
_pstn_wait_continue "Press Enter to continue:"
echo ""
log_success "Portal setup done. The rest of this installer configures the Asterisk side."
log_info "One more thing for after this finishes: grant PSTN access (and, if you"
log_info "want, this DID as a personal number) to the right extension via the"
log_info "Security Dashboard's PSTN Trunk tab — every extension starts at 'internal'"
log_info "(no PSTN access) until you do."
}
install_pstn-trunk() {
require_docker || return 1
@@ -1236,7 +1331,9 @@ install_pstn-trunk() {
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: known-provider quick-pick (Anveo Direct/VoIP.ms pre-fill known"
echo "[DRY-RUN] Would prompt for: known-provider quick-pick (Anveo Direct runs a full 5-step"
echo "[DRY-RUN] interactive portal walkthrough — account/funding, DID ordering, both trunk"
echo "[DRY-RUN] objects, confirmed rate — pausing for Enter between each; VoIP.ms pre-fills known"
echo "[DRY-RUN] server/signaling-IP values; still editable) or manual entry, SIP provider name, DID,"
echo "[DRY-RUN] max concurrent outbound/inbound calls (default 10/10), inbound ring-group extensions,"
echo "[DRY-RUN] ntfy alert topic (optional), international-calling allow-list (CLI-only,"
@@ -1279,7 +1376,8 @@ install_pstn-trunk() {
fi
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 "VoIP.ms and Anveo Direct are both confirmed working; see docs/pstn-calling-voipms-plan.md"
log_info "and, for Anveo Direct specifically, docs/anveo-direct-setup-guide.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."
@@ -1380,27 +1478,7 @@ install_pstn-trunk() {
_default_provider_name="Anveo Direct"
_default_server="sbc.anveo.com"
_default_extra_ips="169.48.232.158 204.216.109.55 176.9.39.206 72.9.149.25"
echo ""
log_info "Anveo Direct known values pre-filled below (hostname, 4 published signaling IPs)."
log_warning "Before continuing: create/verify your account at"
log_warning " https://www.anveo.com/account.asp?account_type=direct"
log_warning "fund it, order a DID, and configure at least one outbound Call"
log_warning "Termination trunk in Anveo's own portal (Outbound Trunks page). A Trial"
log_warning "account shows its own \$2/30-day spend limit and \$2 minimum balance —"
log_warning "separate from anything this installer enforces; confirm with Anveo"
log_warning "support whether those change once verified/funded. Their CallerID policy"
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_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]\$@<this box's public IP>:5060."
log_warning "Create both in Anveo's portal; this installer only handles the Asterisk side."
_pstn_anveo_walkthrough
;;
2)
_default_provider_name="VoIP.ms"