Commit Graph
778 Commits
Author SHA1 Message Date
Claude dd25782ef1 pstn-trunk: drop redundant container restart from update-in-place path
install_pstn-trunk's "update" branch unconditionally restarted the
asterisk container, then immediately called _pstn_ensure_live_includes,
which already patches the live pjsip.conf/extensions.conf #include lines
and reloads res_pjsip/dialplan — the restart accomplished nothing the
reload doesn't, while dropping any calls already in progress. Confirmed
live as a real double-restart when this runs chained from
asterisk-digital-ocean.sh, which had just rebuilt/restarted the same
container moments earlier.

Fresh installs keep their existing (still restart-first) apply step
unchanged — only "update in place" loses the redundant restart.
2026-07-24 14:24:45 +00:00
Outis 261ed3dc74 Merge pull request #225 from outis1one/claude/sip-voip-integration-atsins
Fix group-ring script's INI parser silently returning empty on every …
2026-07-24 09:47:35 -04:00
Claude 228add0937 Fix group-ring script's INI parser silently returning empty on every lookup
_ini_get() in _pstn_write_personal_group_ring_script split on '=' and
compared $1 == key without trimming whitespace, but configparser.write()
(used to write pstn-groups.conf and pstn-permissions.conf) pads '=' with
spaces by default. Every lookup — including the group's own members= line —
silently returned empty, so group-owned personal DID ring-groups never
actually rang anyone regardless of member tier/whitelist config. Trim
whitespace around both the extracted key and value before comparing.

Also switch the script's final echo to printf to remove any ambiguity in
the dialplan's SHELL()-empty-string check.
2026-07-24 13:44:06 +00:00
Outis 0254f6e51e Merge pull request #224 from outis1one/claude/sip-voip-integration-atsins
Fix secdash access to Asterisk config: use ACLs, not chown-fragile gr…
2026-07-24 09:28:11 -04:00
Claude dd8d20cbc8 Fix secdash access to Asterisk config: use ACLs, not chown-fragile groups
Confirmed live on a real droplet: secdash could read pjsip.conf fine but
/api/pstn-status kept returning false even though pstn-trunk-dialplan.conf
genuinely existed. Root cause: the Asterisk container's own entrypoint runs
`chown -R asterisk:asterisk /etc/asterisk` on every container start, and
the numeric UID/GID it resolves to inside the container coincidentally
collided with unrelated host system accounts (config/asterisk ended up
owned by messagebus:uuidd on this box) - silently reverting whatever group
membership _secdash_grant_asterisk_access had granted secdash at install
time. This wasn't a one-time misconfiguration; it would have silently
broken again on every future container restart.

Switched the grant mechanism from chmod + usermod group membership to
POSIX ACLs (setfacl) - chown doesn't touch ACL entries (only chmod
recalculates the ACL mask, and nothing in this flow chmods after install),
so the grant survives the container's own maintenance chown. Default ACLs
(-d) also make newly-created files (a regenerated dialplan, a fresh
personal-DID entry) inherit the same access automatically.

Also added _secdash_grant_ancestor_traversal, which grants execute-only ACL
traversal up the directory tree - needed on any box where DOCKER_DIR sits
under a restrictive parent (e.g. /root on some cloud images defaults to
700, blocking a non-root secdash from ever reaching deeper directories no
matter what those directories themselves grant). Falls back to the old
chmod/group approach with a warning if the 'acl' package is somehow
unavailable (it's installed automatically otherwise).

Verified with a real non-root system test user against a fixture
reproducing the exact failure (a leaf directory with no "other" access,
owned by an unrelated user/group): confirmed blocked before the fix,
confirmed read+write access after, and confirmed access survives a
simulated `chown -R` (the container restart scenario) plus correct
inheritance onto a freshly-created file afterward - all without re-running
the grant.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Ho9mZgAkVpdz7S5wJkg8Nf
2026-07-24 13:26:35 +00:00
Outis af9dcfe47f Merge pull request #223 from outis1one/claude/sip-voip-integration-atsins
Claude/sip voip integration atsins
2026-07-24 08:53:08 -04:00
Claude ff61e8f733 Chain Security Dashboard + PSTN trunk setup into the Asterisk install flow
asterisk.sh and asterisk-digital-ocean.sh now offer, at the end of both
their fresh-install and update-mode paths, to also set up the Security
Dashboard and configure a real PSTN trunk in the same run - one script
walks through the whole stack instead of needing to separately remember
and run `sudo ./setup.sh security-dashboard` / `sudo ./setup.sh pstn-trunk`
afterward.

Both target services keep their own register_service call and stay fully
independently invocable - this is purely an additive convenience layer
(_asterisk_offer_dashboard_and_trunk / _asterisk_do_offer_dashboard_and_trunk),
not a replacement. An already-installed piece is silently refreshed
(install_security-dashboard/install_pstn-trunk each already have their own
update/fresh/cancel reinstall-mode gate, so calling them again just does
the right thing); a not-yet-installed piece gets one y/n instead of every
detailed prompt firing.

Guarded with declare -F so a standalone `sudo bash asterisk.sh` copy (no
sibling services/*.sh files sourced) skips both cleanly with an
explanatory message instead of erroring on an undefined function.

Verified: full sourcing simulation resolves all four install_* functions
correctly, `setup.sh --dry-run --unattended asterisk` and
`asterisk-digital-ocean` both complete cleanly end-to-end, and
`setup.sh --list` still shows all four services as independently
selectable.

Documented the pattern in CLAUDE.md under a new "Chaining into another
service from within your own" section for future contributors.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Ho9mZgAkVpdz7S5wJkg8Nf
2026-07-24 12:38:45 +00:00
Claude 6f5ed30469 Replace Caddy path-proxy with a fully native Asterisk Admin tab
Supersedes the previous commit's reverse-proxy approach entirely: instead
of Caddy routing to Easy Asterisk's own separate vendored web admin
process, the dashboard now reimplements that admin's functionality
natively - one process, one page, real tab-switching, no separate app to
proxy, patch, or embed. Reverts asterisk.sh/asterisk-digital-ocean.sh's
WEBADMIN_BASE_PATH vendor patching and Caddy-skip logic back to their
pre-proxy state (confirmed identical via diff) since neither is needed
anymore.

security-dashboard.sh additions:
- ea_* functions covering full device/category/room parity with
  vendor/easy-asterisk/easy-asterisk-v0.10.0.sh's own web admin: list/add/
  delete/rename/change-category for devices, list/create/delete/rename for
  categories, list/create/delete/rename/add-member/remove-member for
  rooms, plus live registered/unregistered status. Reads go straight
  through the host-side bind-mounted config files (same as the existing
  list_extensions() already does for pjsip.conf); writes go through
  `docker exec -i <container> tee <path>` instead of a direct host-side
  write, since Easy Asterisk's container writes these files as its own
  internal user and a host-side write would just be fighting that
  ownership again on the next container restart.
- Found and fixed a real bug (inherited from the vendored admin's own
  template, not introduced here): a plain non-mobile LAN device leaves
  both the keepalive and ice template lines empty, producing two
  consecutive blank lines inside the endpoint's pjsip.conf stanza instead
  of one - which broke the delete/rename/category-change parsers' "blank
  line ends this device's block" boundary detection, leaving an orphaned
  tail of config behind on delete. Fixed by building the endpoint block
  from a filtered line list instead of positional template blanks.
  Confirmed via a full synthetic add/rename/category-change/delete cycle
  against realistic pjsip.conf/categories.conf/rooms.conf fixtures (with
  docker exec mocked to a local file) - round-trips back to the original
  fixture correctly.
- New plumbing: _secdash_grant_asterisk_access grants read-only access to
  categories.conf/rooms.conf's directory (separate from pjsip.conf's,
  confirmed against the vendored source - /etc/easy-asterisk/*, not
  /etc/asterisk/*); _secdash_write_sudoers adds six exact (no wildcards)
  docker-exec sudoers entries scoped to the one Asterisk container
  actually installed, validated live with visudo -c; _secdash_write_systemd_unit
  passes the new ASTERISK_EA_CONFIG_DIR/ASTERISK_EA_CONTAINER env vars and
  adds the config dir to ReadOnlyPaths, validated live with
  systemd-analyze verify.
- New UI: Asterisk Admin tab with Devices/Categories/Rooms cards, sortable
  tables matching the existing style, inline category-reassignment
  dropdowns, and per-room member chips with an inline add-member picker.
  Nav button visibility now checks live container reachability
  (/api/ea-status) instead of just Asterisk-install detection.

Still unverified: the actual `docker exec` calls (module reload, dialplan
rebuild, live status) against a real running Easy Asterisk container -
only the file-parsing/transformation logic itself has been exercised, via
mocked writes, not the real container plumbing.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Ho9mZgAkVpdz7S5wJkg8Nf
2026-07-24 12:14:27 +00:00
Claude d449586dde Replace the Asterisk Admin iframe with a native Caddy path-proxy
No more cross-origin iframe: the Security Dashboard now reverse-proxies
the real Asterisk web admin natively at /asterisk-admin/ on its own
domain via Caddy's handle_path, instead of embedding a separate site in
a frame. One domain, one login wall, for both.

- services/asterisk.sh / services/asterisk-digital-ocean.sh: patch the
  vendored web admin's one hardcoded absolute API path
  (`const API_BASE = '/api'`, confirmed via the real vendored source to
  be the only absolute-path reference anywhere in its HTML/JS - no other
  hrefs, no login-page redirect, plain HTTP Basic Auth instead) so it
  resolves correctly when mounted under a sub-path, via a new
  WEBADMIN_BASE_PATH env var threaded through entrypoint.sh. Verified
  against the real vendored file: patched output is
  '/asterisk-admin/api' with the env var set, unchanged '/api' without
  it. Skip each service's own dedicated admin Caddy domain when the
  Security Dashboard is already installed, since it fronts the admin
  instead.
- services/security-dashboard.sh: _secdash_configure_caddy now accepts
  the admin's port and Asterisk's own directory/domain, path-routes
  /asterisk-admin/* alongside the dashboard's own handle{} block, and
  writes WEB_ADMIN_BASE_PATH into Asterisk's .env + restarts that
  container once proxying is confirmed live. Defaults the dashboard's
  own domain prompt to the droplet's DOMAIN_NAME when detected, since
  Caddy's SIP-TLS cert sync already depends on serving that exact
  domain. Removed the old CSP frame-ancestors patching and the iframe
  itself; the nav is now a plain link, shown only once the proxy is
  confirmed wired up.
- Fixed _secdash_remove_caddy_block's marker match to tolerate the
  dashboard's reverse_proxy line now living one indent level deeper
  (inside its own handle{} block) - verified against a synthetic
  Caddyfile that it still finds and removes exactly the right block.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Ho9mZgAkVpdz7S5wJkg8Nf
2026-07-24 11:45:11 +00:00
Claude 4f102b12c1 One dashboard to manage Asterisk: core tabs always on, optional tabs self-hide
Restructure the nav so it reflects what's actually installed on this box,
letting one dashboard URL cover everything from a bare LAN Asterisk box up
to a full droplet with a trunk and CrowdSec:

- Security Log and a new Extensions tab (Groups + Internal SIP messaging,
  split out of the old "PSTN Trunk" tab) are always available - they only
  need Asterisk itself, not a trunk or CrowdSec.
- Asterisk Admin, PSTN Trunk, and CrowdSec each check their own live
  install state on every page load and hide their own nav button entirely
  when not present, instead of showing an empty/placeholder tab.
- Add crowdsec_installed() (checks for /usr/bin/cscli) and a
  /api/crowdsec-status endpoint, mirroring the existing pstn_installed()/
  /api/pstn-status pattern.

This fixes the earlier design where messaging/groups management lived
inside the PSTN Trunk tab even though both work with plain Asterisk and no
trunk at all - hiding that tab would have taken them down with it.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Ho9mZgAkVpdz7S5wJkg8Nf
2026-07-24 05:56:54 +00:00
Outis 6faa60f832 Merge pull request #222 from outis1one/claude/sip-voip-integration-atsins
Dashboard UI cleanup: column sorting, iframe fix confirmed, messaging…
2026-07-23 23:48:50 -04:00
Claude 17a21c076f Dashboard UI cleanup: column sorting, iframe fix confirmed, messaging chips
- Security Log tab: replace the per-column text filter row with clickable
  sortable column headers (same pattern as the CrowdSec bans table).
- PSTN Trunk tab: add sortable headers to the permissions, personal-DID
  (numeric on DID), and groups tables.
- Move the "Internal SIP messaging" card to the bottom of the PSTN tab and
  replace its per-row table+Save-button layout with a checkbox chip row
  and a single Save-changes button.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Ho9mZgAkVpdz7S5wJkg8Nf
2026-07-24 03:45:13 +00:00
Outis 20e7fafd6a Merge pull request #221 from outis1one/claude/sip-voip-integration-atsins
Claude/sip voip integration atsins
2026-07-23 23:11:17 -04:00
Claude c893751859 Document group-owned personal number assignment
Update the dashboard's own Personal Numbers card description and the
Anveo Direct setup guide to mention assigning a DID to a group instead
of a single extension.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Ho9mZgAkVpdz7S5wJkg8Nf
2026-07-24 03:03:55 +00:00
Claude dd3ea131ac Allow assigning a personal number to a group, not just a single extension
The dashboard's Personal Numbers card now accepts a group (stored as
"@GroupName", unambiguous against a same-named numeric extension) as well
as a plain extension. A group-owned DID rings every CURRENT member whose
own tier/approved-numbers authorize the caller, computed fresh on every
call by a generated pstn-personal-group-ring.sh (invoked via the
dialplan's SHELL() function) rather than unrolled at install time, since
group membership can change any time via the dashboard with no reinstall
- unlike the shared ring-group, which is fixed at install/update time.

Applies the identical per-member permission check the shared ring-group
already bakes into the dialplan, just computed in a plain shell loop
against the same two config files - group ownership doesn't bypass the
tier/approved-numbers model. Tested standalone against mock config data
(full/restricted/internal mix, matching/non-matching caller, empty and
nonexistent groups) - all four cases behaved correctly. The dialplan's own
SHELL() invocation is still unverified against a real call.

Group ownership never touches pstn-permissions.conf's personal_did
(outbound Caller-ID override) field, since there's no single extension to
hang that on for a group.
2026-07-24 03:00:13 +00:00
Outis 3e729f0559 Merge pull request #220 from outis1one/claude/sip-voip-integration-atsins
Claude/sip voip integration atsins
2026-07-23 21:42:11 -04:00
Claude f5e93ec35b Build the interactive Anveo Direct account-setup walkthrough
Replaces the static wall of warnings with a real 5-step guided walkthrough
(account/funding, DID ordering, both trunk objects, confirmed rate),
pausing for confirmation between each step and showing this box's actual
public IP inline for direct copy-paste into Anveo's Authorized IP
Addresses and SIP Trunk forwarding fields. Built directly from
docs/anveo-direct-setup-guide.md now that the whole flow is confirmed
working end-to-end, so a fresh run shouldn't need the trial-and-error
session that guide was written from.
2026-07-24 01:29:06 +00:00
Claude 9c9a299479 Add a confirmed-working, step-by-step Anveo Direct + Easy Asterisk setup guide
Documents the actual end-to-end sequence that got a real DID working
(account setup, DID ordering, both trunk objects, droplet configuration,
permission setup, testing), including the real confirmed values (the
$0.00388/min rate, the port-5060 SIP URI forwarding format, the two
separate trunk-object types) and a summary of every real bug hit and
fixed along the way. Linked from the existing planning-notes doc.
2026-07-24 01:10:35 +00:00
Outis 8128eea0d8 Merge pull request #219 from outis1one/claude/sip-voip-integration-atsins
Fix personal-DID inbound lookup: 10-digit storage vs 11-digit EXTEN m…
2026-07-23 20:46:14 -04:00
Claude 3ee3a45367 Fix personal-DID inbound lookup: 10-digit storage vs 11-digit EXTEN mismatch
Confirmed live: PSTN_PERSONAL_OWNER came back empty despite a real
assignment existing, because the Security Dashboard stores personal DIDs
as 10 digits (PERSONAL_DID_RE = ^\d{10}$) while Anveo's inbound INVITE
delivers the called number as 11-digit E.164 (with leading 1) in ${EXTEN}
- confirmed via PSTN_DID_CALLED=15557776655 in the live test. AST_CONFIG()
looking up an 11-digit key never found the 10-digit section.

Normalizes to 10 digits before the lookup (strips a leading digit only
when the string is actually 11 characters, so this doesn't misfire against
a provider that already sends 10). Also fixed the dashboard's own DID
input placeholder, which contradicted its own 10-digit validation error.
2026-07-23 22:08:24 +00:00
Outis 9ebe23bb9f Merge pull request #218 from outis1one/claude/sip-voip-integration-atsins
Dedupe RING_EXTS before generating the inbound dialplan
2026-07-23 17:06:30 -04:00
Claude b5b0edef62 Dedupe RING_EXTS before generating the inbound dialplan
Confirmed live: a ring-group list with a repeated extension ("201 301 313
312 313") generated duplicate ring<ext>/skip<ext> priority labels in the
same [from-pstn-trunk] extension. Asterisk doesn't error on this - it
silently resolves Goto() to the wrong occurrence and loops between the two
blocks forever, so the call never reaches the actual Dial() and nothing
ever rings, with no error to point at the cause. Dedupes (first-occurrence
order preserved) regardless of how the duplicate got in there.
2026-07-23 20:54:43 +00:00
Outis 31e8ec410a Merge pull request #217 from outis1one/claude/sip-voip-integration-atsins
Fix EXTEN corruption across every Goto to a named dialplan extension
2026-07-23 16:24:53 -04:00
Claude a797c9845f Fix EXTEN corruption across every Goto to a named dialplan extension
Confirmed live: Dial() actually placed the call to "PJSIP/pstn_check_busy@
pstn-trunk" instead of the dialed number. Root cause is systemic, not one
line — Goto(label,1) to a *different named extension* (as opposed to a
same-extension priority label, which _pstn_ring_member_block already knew
to avoid) resets ${EXTEN} to that label's own name. Every helper extension
downstream of a Goto (pstn_check_allow_out, pstn_check_busy,
pstn_intl_check_country, pstn_intl_country_denied, pstn_personal_inbound)
was reading ${EXTEN} expecting the originally-dialed number and getting
its own extension name instead.

This wasn't just cosmetic: pstn_check_allow_out's REGEX approved-number
check compared against the label name too, meaning restricted-tier
extensions could never successfully place a call regardless of whether
the number was actually approved.

Fixed by capturing the real dialed number into PSTN_DIALED (outbound) /
PSTN_DID_CALLED (inbound) at each entry point, before any Goto, and using
that instead of ${EXTEN} in every downstream extension.
2026-07-23 20:24:02 +00:00
Outis 7b9cfc0008 Merge pull request #216 from outis1one/claude/sip-voip-integration-atsins
Fix PSTN_CALLER extraction — CHANNEL(peername) isn't valid on this As…
2026-07-23 16:17:12 -04:00
Claude 4b080dd845 Fix PSTN_CALLER extraction — CHANNEL(peername) isn't valid on this Asterisk build
Confirmed live: "Unknown or unavailable item requested: 'peername'" left
PSTN_CALLER empty, which made AST_CONFIG() error ("requires a category")
and PSTN_TIER come back empty too — every call fell through to "no PSTN
permission" and got busy-signaled regardless of the extension's actual
tier. Replaced with CUT(CHANNEL,/,2) then CUT(...,-,1), extracting the
endpoint name from the channel name itself (e.g. "PJSIP/201-00000006" ->
"201") — a plain string operation with no dependency on which channel
function items this particular Asterisk build supports.
2026-07-23 20:14:03 +00:00
Outis 062db1e8bd Merge pull request #215 from outis1one/claude/sip-voip-integration-atsins
Fix off-by-one in the NANP outbound dial patterns
2026-07-23 16:01:33 -04:00
Claude 4457b471d9 Fix off-by-one in the NANP outbound dial patterns
_1NXXNXXXXX / _NXXNXXXXX were one digit short (10/9 characters instead of
the correct 11/10 for a full NANP number: 1 + area code(3) + exchange(3) +
subscriber(4)). Asterisk's exact-length pattern matching never matched a
real dialed number as a result — confirmed live via "extension not found
in context 'intercom'" on every outbound test call, traced by dumping
`dialplan show intercom` and counting characters against the actual
pattern. This was the real root cause underneath everything else fixed
along the way (stale settings file, the #include not reaching the live
config) — those were real bugs too, but this one was still there
underneath all of them.
2026-07-23 19:57:15 +00:00
Outis 7827849973 Merge pull request #214 from outis1one/claude/sip-voip-integration-atsins
Don't let a malformed .pstn-trunk.env crash the whole install under s…
2026-07-23 13:48:12 -04:00
Claude 09113f4e41 Don't let a malformed .pstn-trunk.env crash the whole install under set -u
setup.sh runs with set -uo pipefail, and source-ing a settings file with
any stray unexpanded/unset variable reference (confirmed live on an older
deployed copy) was fatal to the entire script, not just that one prompt.
Wraps the source in set +u/-u and falls back to sane defaults for anything
missing, but still hard-errors with a clear message if TRUNK_SERVER/
TRUNK_DID themselves are missing, rather than silently writing a broken
trunk config.
2026-07-23 17:46:16 +00:00
Outis 243dc03474 Merge pull request #213 from outis1one/claude/sip-voip-integration-atsins
Claude/sip voip integration atsins
2026-07-23 13:43:29 -04:00
Claude 02bf2fe3a2 Mirror SIP messaging + presence alerts to asterisk.sh (LAN edition)
Ports the internal SIP MESSAGE dialplan enforcement, extension presence
(online/offline) ntfy alerts, and the live-config-include patching from
asterisk-digital-ocean.sh, for feature parity between the two Asterisk
flavors. The pstn-trunk.sh live-include fix already applied to both, since
that's shared code parametrized by which flavor is installed.
2026-07-23 17:19:31 +00:00
Claude a33b8e7b23 Simplify pstn-trunk.sh: point to the dashboard instead of CLI permission prompts
Removed the full/restricted-extension, approved-numbers pool/whiptail,
messaging-extensions, and personal-DID-assignment prompts from the
installer. All four are already live-editable, no-restart-needed settings
the Security Dashboard's PSTN Trunk tab manages end to end — the CLI wall
of prompts (that then needed a full reinstall to change) just duplicated
that with more friction. Every extension now starts at internal tier (no
PSTN, no messaging) until granted via the dashboard. Kept: provider/DID
setup, concurrency caps, and the inbound ring-group (basic trunk wiring,
not a permission). Also updated the generated README and CLI summary to
match, and refreshed the stale "known gap" messaging section that predated
the dialplan enforcement built earlier this session.
2026-07-23 17:16:42 +00:00
Claude 3f033da635 Fix trunk/messaging #include lines never reaching an existing install's live config
Confirmed live via a real failed test call ("extension not found in context
'intercom'"): Easy Asterisk's entrypoint only regenerates pjsip.conf/
extensions.conf if they don't already exist. Patching the vendor generator
functions (as both pstn-trunk.sh and the earlier SIP MESSAGE work did) only
takes effect on a future full regeneration — a box that already has devices
configured, the normal case, never triggers one on a plain restart, so the
#include lines never actually reached the live files despite the generator
patch succeeding.

Both installers now also patch the live pjsip.conf/extensions.conf directly
(same anchors, idempotent) and force a reload, so the trunk/messaging
dialplan actually takes effect immediately regardless of whether Easy
Asterisk ever regenerates its config on its own.
2026-07-23 17:10:13 +00:00
Outis 06813bb07a Merge pull request #212 from outis1one/claude/sip-voip-integration-atsins
Claude/sip voip integration atsins
2026-07-23 12:57:18 -04:00
Claude 67ded2eb38 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.
2026-07-23 16:55:22 +00:00
Claude fdd2aaa1d3 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).
2026-07-23 16:49:19 +00:00
Claude 16da6c1372 Clarify PSTN tier prompts — enter extension numbers, not tier names
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').
2026-07-23 16:44:35 +00:00
Claude 526cb4a623 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.
2026-07-23 16:14:19 +00:00
Claude 4c0326ca5f 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.
2026-07-23 16:05:52 +00:00
Outis 73b6a7ab20 Merge pull request #211 from outis1one/claude/sip-voip-integration-atsins
Claude/sip voip integration atsins
2026-07-23 11:05:17 -04:00
Claude 3661c8fbca Wire up internal SIP MESSAGE enforcement using a dedicated dialplan context
Confirmed against a live install's pjsip.conf/extensions.conf that every
endpoint falls back to context=intercom for messaging (message_context
blank), and that [intercom] owns one exact-match per-device dial pattern
regenerated on every dialplan rebuild. Rather than risk racing that, every
endpoint now gets message_context=sip-messaging (patched into both of Easy
Asterisk's device-creation code paths, plus a one-time migration for
existing devices), routing messages to their own [sip-messaging] context
gated on the existing pstn-permissions.conf messaging flag.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Ho9mZgAkVpdz7S5wJkg8Nf
2026-07-23 15:03:03 +00:00
Claude b10a626b22 Fix ntfy deny-all default, add crowdsec update mode, presence alerts, embedded Asterisk admin tab
- ntfy.sh: auth-default-access was deny-all, silently blocking both publish
  and anonymous subscribe on every topic once an auth-file is present.
  Default to read-write (same model as public ntfy.sh) so alerts from
  crowdsec.sh/pstn-trunk.sh actually get delivered.
- crowdsec.sh: add update/fresh/cancel reinstall gating — reruns previously
  re-asked every optional prompt (ASN exempt, geo-allowlist, ntfy, remote
  LAPI) unconditionally with no way to just refresh in place.
- asterisk-digital-ocean.sh: add optional ntfy alerts on extension
  registration going offline/online, checked every 2 minutes via systemd
  timer (cron.d fallback), offered on both fresh install and update.
- security-dashboard.sh: replace the outbound-only Asterisk Web Admin link
  with an embedded, lazy-loaded iframe tab, with a best-effort Caddy
  frame-ancestors patch and an always-available "open in new tab" fallback.
2026-07-23 14:15:17 +00:00
Outis 542b793b9a Merge pull request #210 from outis1one/claude/sip-voip-integration-atsins
Add named extension groups and Security Log column filtering
2026-07-23 08:33:35 -04:00
Claude 3359898b4e Add named extension groups and Security Log column filtering
Groups: a new always-available "Groups" card in the PSTN tab (same place
as Internal SIP messaging, no dependency on a PSTN trunk being installed)
lets you name a set of extensions and bulk-enable/disable messaging for
all of them at once. Purely a management-layer convenience - pstn-groups.conf
is never read by the dialplan, which only ever looks at per-extension keys
in pstn-permissions.conf. Applying a group action just calls the same
write_messaging() each individual checkbox uses, once per current member.
Editing membership never retroactively changes anything already applied,
and deleting a group never touches members' own settings - confirmed with
tests covering create/apply/edit-membership/re-apply/delete.

Security Log: added a per-column filter row (Time/Event/Account/Remote/
Severity), live as you type, filtering client-side against the
already-fetched events rather than re-querying - persists correctly across
the existing 30-second auto-refresh.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Ho9mZgAkVpdz7S5wJkg8Nf
2026-07-23 06:05:37 +00:00
Outis e6bc51061d Merge pull request #209 from outis1one/claude/sip-voip-integration-atsins
Claude/sip voip integration atsins
2026-07-23 01:49:41 -04:00
Claude 7851e2befd Decouple internal SIP messaging management from PSTN trunk installation
Messaging has no dependency on a PSTN trunk existing (no cost, no
carrier, no DID), but the whole PSTN Trunk tab - including the messaging
checkbox added last commit - was hidden behind pstn_installed(), which
only becomes true once services/pstn-trunk.sh's dialplan is actually
wired in. That meant enabling messaging required going through full SIP
trunk/provider setup first for no real reason.

Added a standalone "Internal SIP messaging" card that's always visible in
the tab regardless of trunk status, backed by a new write_messaging()
that only touches the messaging key (leaving tier/allowed_numbers/
personal_did untouched) and creates pstn-permissions.conf from scratch if
it doesn't exist yet. Confirmed the systemd unit's ReadWritePaths and the
group/chmod grants already covered this - both are set up whenever a base
Asterisk install is detected, independent of pstn-trunk - so no
permission-layer changes were needed, only the dashboard's own artificial
UI gate.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Ho9mZgAkVpdz7S5wJkg8Nf
2026-07-23 05:38:36 +00:00
Claude 8291eba55e Make the messaging permission flag live-editable via the dashboard
get_all_permissions()/write_permission() now handle messaging alongside
tier/allowed_numbers as one save action, and the Security Dashboard's PSTN
Trunk table gets a Messaging checkbox column - no more needing to re-run
pstn-trunk.sh's CLI just to change who can use internal SIP texting,
matching how tier/allowed_numbers/personal_did already worked.

Tested that messaging correctly survives tier changes and personal-DID
assignment/removal on the same extension (independent axes, as intended).

Still explicitly not done, and said so in both READMEs rather than
implying otherwise now that there's a nice UI for it: the actual SIP
MESSAGE dialplan wiring that would make Asterisk enforce this flag. That
gap hasn't changed - only the permission storage/UI layer around it has.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Ho9mZgAkVpdz7S5wJkg8Nf
2026-07-23 05:27:58 +00:00
Claude 3705fb5fcc Add Anveo Direct/VoIP.ms known-provider quick-pick to pstn-trunk.sh
Prompts now start with a 1/2/3 choice (Anveo Direct / VoIP.ms / manual)
that pre-fills already-known values - Anveo Direct's sbc.anveo.com
hostname and its 4 published signaling IPs, so they don't need retyping
from memory - plus provider-specific account-setup reminders. Every
value stays editable at each subsequent prompt; this only changes
defaults, not behavior, so existing manual-entry users see no change.

Flagged rather than silently trusted: Anveo Direct's own Outbound Trunks
page documents dialing as [PREFIX]PHONENUMBER@sbc.anveo.com, which
contradicts an earlier no-prefix-needed finding from their FAQ. The
dialplan still dials the bare number (matching the older finding) - the
quick-pick prints a loud warning to verify the Prefix field can be left
blank before relying on this, rather than guessing and hardcoding prefix
handling without live confirmation either way.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Ho9mZgAkVpdz7S5wJkg8Nf
2026-07-23 05:08:30 +00:00
Claude fcb01a4d93 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
2026-07-23 05:03:13 +00:00