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.
This commit is contained in:
@@ -257,6 +257,358 @@ _asterisk_refresh_vendor_files() {
|
|||||||
./scripts/vpn-diagnostics.sh ./scripts/dns-whitelist.sh
|
./scripts/vpn-diagnostics.sh ./scripts/dns-whitelist.sh
|
||||||
}
|
}
|
||||||
|
|
||||||
|
# ── Shared: extension presence (online/offline) ntfy alerts ────────────────
|
||||||
|
# Polls PJSIP registration state and alerts only on a CHANGE from the last
|
||||||
|
# check (never on every poll) — same periodic-check shape as pstn-trunk.sh's
|
||||||
|
# usage-alert script, but purely informational, so a looser 2-minute
|
||||||
|
# interval is fine here (nothing enforces/blocks anything off the back of
|
||||||
|
# this one). UNVERIFIED: the `pjsip show contacts` column layout below is
|
||||||
|
# parsed defensively (grep for the Avail/Unavail keyword rather than a fixed
|
||||||
|
# column position) specifically because it hasn't been confirmed against a
|
||||||
|
# live install's actual output yet — run
|
||||||
|
# `docker exec easy-asterisk asterisk -rx "pjsip show contacts"` yourself
|
||||||
|
# after enabling this to confirm extensions/status actually show up as
|
||||||
|
# expected, same as any other not-yet-live-tested piece in this project.
|
||||||
|
_asterisk_write_presence_alert_script() {
|
||||||
|
local FILE="$1" CONTAINER_NAME="$2" NTFY_URL="$3" STATE_FILE="$4"
|
||||||
|
cat > "$FILE" << 'SCRIPT'
|
||||||
|
#!/bin/bash
|
||||||
|
# Auto-generated by services/asterisk.sh — rerun the installer's
|
||||||
|
# presence-alert step to change settings instead of editing this directly.
|
||||||
|
CONTAINER_NAME="__PRESENCE_CONTAINER__"
|
||||||
|
NTFY_URL="__PRESENCE_NTFY_URL__"
|
||||||
|
STATE_FILE="__PRESENCE_STATE_FILE__"
|
||||||
|
|
||||||
|
[[ -z "$NTFY_URL" ]] && exit 0
|
||||||
|
|
||||||
|
send_ntfy() {
|
||||||
|
curl -m 5 -s -d "$1" "$NTFY_URL" >/dev/null 2>&1
|
||||||
|
}
|
||||||
|
|
||||||
|
CURRENT="$(docker exec "$CONTAINER_NAME" asterisk -rx "pjsip show contacts" 2>/dev/null | grep '^ Contact:' | while read -r _ aor rest; do
|
||||||
|
ext="${aor%%/*}"
|
||||||
|
status="Unknown"
|
||||||
|
case "$rest" in
|
||||||
|
*Unavail*) status="Unavail" ;;
|
||||||
|
*Avail*) status="Avail" ;;
|
||||||
|
esac
|
||||||
|
echo "${ext}:${status}"
|
||||||
|
done)"
|
||||||
|
|
||||||
|
[[ -z "$CURRENT" ]] && exit 0
|
||||||
|
|
||||||
|
touch "$STATE_FILE"
|
||||||
|
declare -A OLD_STATE
|
||||||
|
while IFS=: read -r ext status; do
|
||||||
|
[[ -n "$ext" ]] && OLD_STATE["$ext"]="$status"
|
||||||
|
done < "$STATE_FILE"
|
||||||
|
|
||||||
|
: > "${STATE_FILE}.new"
|
||||||
|
while IFS=: read -r ext status; do
|
||||||
|
[[ -z "$ext" ]] && continue
|
||||||
|
echo "${ext}:${status}" >> "${STATE_FILE}.new"
|
||||||
|
old="${OLD_STATE[$ext]:-}"
|
||||||
|
if [[ -n "$old" && "$old" != "$status" && "$status" != "Unknown" ]]; then
|
||||||
|
if [[ "$status" == "Avail" ]]; then
|
||||||
|
send_ntfy "Extension $ext is back online."
|
||||||
|
elif [[ "$old" == "Avail" ]]; then
|
||||||
|
send_ntfy "Extension $ext went offline."
|
||||||
|
fi
|
||||||
|
fi
|
||||||
|
done <<< "$CURRENT"
|
||||||
|
mv "${STATE_FILE}.new" "$STATE_FILE"
|
||||||
|
SCRIPT
|
||||||
|
sed -i "s#__PRESENCE_CONTAINER__#${CONTAINER_NAME}#g; s#__PRESENCE_NTFY_URL__#${NTFY_URL}#g; s#__PRESENCE_STATE_FILE__#${STATE_FILE}#g" "$FILE"
|
||||||
|
chmod 755 "$FILE"
|
||||||
|
}
|
||||||
|
|
||||||
|
_asterisk_install_presence_timer() {
|
||||||
|
local EA_DIR="$1"
|
||||||
|
mkdir -p "$EA_DIR/logs"
|
||||||
|
|
||||||
|
if command -v systemctl >/dev/null 2>&1 && [[ -d /run/systemd/system ]]; then
|
||||||
|
cat > /etc/systemd/system/asterisk-presence-alert.service << SVCEOF
|
||||||
|
[Unit]
|
||||||
|
Description=Asterisk extension presence (online/offline) check
|
||||||
|
|
||||||
|
[Service]
|
||||||
|
Type=oneshot
|
||||||
|
ExecStart=/bin/bash $EA_DIR/asterisk-presence-alert.sh
|
||||||
|
StandardOutput=append:$EA_DIR/logs/asterisk-presence-alert.log
|
||||||
|
StandardError=append:$EA_DIR/logs/asterisk-presence-alert.log
|
||||||
|
SVCEOF
|
||||||
|
|
||||||
|
cat > /etc/systemd/system/asterisk-presence-alert.timer << SVCEOF
|
||||||
|
[Unit]
|
||||||
|
Description=Run the Asterisk presence check every 2 minutes
|
||||||
|
|
||||||
|
[Timer]
|
||||||
|
OnBootSec=2min
|
||||||
|
OnUnitActiveSec=2min
|
||||||
|
AccuracySec=10s
|
||||||
|
|
||||||
|
[Install]
|
||||||
|
WantedBy=timers.target
|
||||||
|
SVCEOF
|
||||||
|
|
||||||
|
systemctl daemon-reload
|
||||||
|
systemctl enable --now asterisk-presence-alert.timer
|
||||||
|
log_success "Presence check installed (systemd timer, every 2 minutes)."
|
||||||
|
elif command -v cron >/dev/null 2>&1 || [[ -d /etc/cron.d ]]; then
|
||||||
|
cat > /etc/cron.d/asterisk-presence-alert << CRON
|
||||||
|
*/2 * * * * root /bin/bash $EA_DIR/asterisk-presence-alert.sh >> $EA_DIR/logs/asterisk-presence-alert.log 2>&1
|
||||||
|
CRON
|
||||||
|
log_success "Presence check installed (cron.d fallback — systemd not detected)."
|
||||||
|
else
|
||||||
|
log_warning "Neither systemd nor cron available — run $EA_DIR/asterisk-presence-alert.sh manually/periodically."
|
||||||
|
fi
|
||||||
|
}
|
||||||
|
|
||||||
|
# ── Shared: internal SIP MESSAGE routing/enforcement ────────────────────────
|
||||||
|
# Confirmed live against a real install's pjsip.conf/extensions.conf
|
||||||
|
# (2026-07-23): every endpoint sets context=intercom and leaves
|
||||||
|
# message_context blank, so PJSIP messaging falls back to context=intercom —
|
||||||
|
# and [intercom] already owns an exact-match `exten => <ext>,1,...` per
|
||||||
|
# device, freshly regenerated by the vendor's own rebuild_dialplan() on
|
||||||
|
# every dialplan rebuild. A competing priority-1 declaration for the same
|
||||||
|
# extension number in a #include'd file would race that (Asterisk doesn't
|
||||||
|
# merge two independent priority-1 declarations for the same context+exten —
|
||||||
|
# one silently wins) and risks breaking normal internal calling entirely.
|
||||||
|
# So this uses its own dedicated [sip-messaging] context instead, reached by
|
||||||
|
# explicitly setting message_context=sip-messaging on every endpoint, so
|
||||||
|
# there is never any overlap with [intercom]'s own per-device call routing.
|
||||||
|
#
|
||||||
|
# The vendor's device-creation code has exactly two independent code paths
|
||||||
|
# that write a fresh endpoint block (confirmed via grep — both contain the
|
||||||
|
# literal line "context=intercom" exactly once): the CLI menu's bash heredoc,
|
||||||
|
# and the web admin's Python add_device(). Patching the vendor's own
|
||||||
|
# generator source (same technique as _pstn_patch_vendor_files) makes every
|
||||||
|
# device added FROM NOW ON pick this up automatically, in either path.
|
||||||
|
# Devices that already existed before this was installed need one one-time
|
||||||
|
# migration pass over the live pjsip.conf (below) since they were written
|
||||||
|
# before the patch existed.
|
||||||
|
_asterisk_patch_messaging_vendor_files() {
|
||||||
|
local EA_DIR="$1"
|
||||||
|
local ENTRYPOINT="$EA_DIR/docker/entrypoint.sh"
|
||||||
|
local EASY1="$EA_DIR/easy-asterisk.sh"
|
||||||
|
local EASY2
|
||||||
|
EASY2="$(find "$EA_DIR" -maxdepth 1 -name 'easy-asterisk-v*.sh' | head -1)"
|
||||||
|
[[ -z "$EASY2" ]] && EASY2="$EA_DIR/easy-asterisk-v0.10.0.sh"
|
||||||
|
local f
|
||||||
|
|
||||||
|
for f in "$EASY1" "$EASY2"; do
|
||||||
|
[[ -f "$f" ]] || { log_error "$f not found — is the base Asterisk install fully set up?"; return 1; }
|
||||||
|
done
|
||||||
|
|
||||||
|
# Device-creation templates: both occurrences of "context=intercom" in
|
||||||
|
# these two files (identical vendor source, copied twice) are the CLI
|
||||||
|
# and web-admin device-creation code paths — a single anchor on the bare
|
||||||
|
# line patches both in one pass.
|
||||||
|
for f in "$EASY1" "$EASY2"; do
|
||||||
|
if ! grep -q '^message_context=sip-messaging$' "$f"; then
|
||||||
|
if grep -q '^context=intercom$' "$f"; then
|
||||||
|
sed -i '/^context=intercom$/a message_context=sip-messaging' "$f"
|
||||||
|
else
|
||||||
|
log_warning "$(basename "$f"): 'context=intercom' anchor not found — vendor template changed upstream."
|
||||||
|
log_warning " Add 'message_context=sip-messaging' manually after every 'context=intercom' line in this file's device-creation code."
|
||||||
|
fi
|
||||||
|
fi
|
||||||
|
done
|
||||||
|
|
||||||
|
# extensions.conf: same [intercom] anchor _pstn_patch_vendor_files uses,
|
||||||
|
# a SEPARATE #include so this coexists whether or not pstn-trunk is
|
||||||
|
# installed — messaging is independent of the PSTN trunk entirely.
|
||||||
|
for f in "$ENTRYPOINT" "$EASY1" "$EASY2"; do
|
||||||
|
[[ -f "$f" ]] || continue
|
||||||
|
if ! grep -q 'messaging-dialplan.conf' "$f"; then
|
||||||
|
if grep -q '^\[intercom\]$' "$f"; then
|
||||||
|
sed -i '/^\[intercom\]$/a #include messaging-dialplan.conf' "$f"
|
||||||
|
else
|
||||||
|
log_warning "$(basename "$f"): '[intercom]' anchor not found — vendor template changed upstream."
|
||||||
|
log_warning " Add '#include messaging-dialplan.conf' manually after [intercom] in this file's extensions.conf heredoc."
|
||||||
|
fi
|
||||||
|
fi
|
||||||
|
done
|
||||||
|
|
||||||
|
log_success "Vendor generator functions patched for internal SIP messaging."
|
||||||
|
}
|
||||||
|
|
||||||
|
# Confirmed live (2026-07-23, via a real pstn-trunk.sh failure that hit this
|
||||||
|
# same mechanism): the vendor-generator patch above only takes effect on a
|
||||||
|
# FUTURE regeneration, and Easy Asterisk's own entrypoint only regenerates
|
||||||
|
# extensions.conf if it doesn't already exist (docker/entrypoint.sh guards
|
||||||
|
# it behind `[[ ! -f ... ]]`) — a box that already has devices configured,
|
||||||
|
# which is the normal case here, never regenerates it on a plain restart.
|
||||||
|
# Patches the LIVE file directly instead, so it takes effect immediately
|
||||||
|
# regardless of whether Easy Asterisk ever regenerates it on its own.
|
||||||
|
_asterisk_ensure_live_messaging_include() {
|
||||||
|
local EA_DIR="$1"
|
||||||
|
local EXT_LIVE="$EA_DIR/config/asterisk/extensions.conf"
|
||||||
|
[[ -f "$EXT_LIVE" ]] || return 0
|
||||||
|
if ! grep -q 'messaging-dialplan.conf' "$EXT_LIVE"; then
|
||||||
|
if grep -q '^\[intercom\]$' "$EXT_LIVE"; then
|
||||||
|
sed -i '/^\[intercom\]$/a #include messaging-dialplan.conf' "$EXT_LIVE"
|
||||||
|
log_success "Patched the messaging #include directly into the live extensions.conf."
|
||||||
|
else
|
||||||
|
log_warning "Couldn't find '[intercom]' in the live extensions.conf — add"
|
||||||
|
log_warning "'#include messaging-dialplan.conf' manually, then: docker exec easy-asterisk asterisk -rx \"dialplan reload\""
|
||||||
|
fi
|
||||||
|
fi
|
||||||
|
docker exec easy-asterisk asterisk -rx "dialplan reload" &>/dev/null || true
|
||||||
|
}
|
||||||
|
|
||||||
|
# One-time migration for devices that already existed before the patch above
|
||||||
|
# — new devices pick up message_context=sip-messaging automatically from now
|
||||||
|
# on, but anything already in pjsip.conf was written before that existed.
|
||||||
|
# Idempotent: buffers the file and only inserts where the very next line
|
||||||
|
# isn't already the exact value, so reruns (every "update") never duplicate it.
|
||||||
|
_asterisk_migrate_existing_devices_message_context() {
|
||||||
|
local PJSIP_FILE="$1"
|
||||||
|
[[ -f "$PJSIP_FILE" ]] || return 0
|
||||||
|
grep -q '^context=intercom$' "$PJSIP_FILE" || return 0
|
||||||
|
|
||||||
|
local TMP_FILE
|
||||||
|
TMP_FILE="$(mktemp)"
|
||||||
|
awk '
|
||||||
|
{ lines[NR] = $0 }
|
||||||
|
END {
|
||||||
|
for (i = 1; i <= NR; i++) {
|
||||||
|
print lines[i]
|
||||||
|
if (lines[i] == "context=intercom" && lines[i+1] != "message_context=sip-messaging") {
|
||||||
|
print "message_context=sip-messaging"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
' "$PJSIP_FILE" > "$TMP_FILE"
|
||||||
|
|
||||||
|
if ! diff -q "$PJSIP_FILE" "$TMP_FILE" >/dev/null 2>&1; then
|
||||||
|
cp "$PJSIP_FILE" "$PJSIP_FILE.backup.$(date +%Y%m%d-%H%M%S)"
|
||||||
|
mv "$TMP_FILE" "$PJSIP_FILE"
|
||||||
|
chown asterisk:asterisk "$PJSIP_FILE" 2>/dev/null || true
|
||||||
|
log_success "Existing devices migrated to message_context=sip-messaging (backup saved alongside pjsip.conf)."
|
||||||
|
else
|
||||||
|
rm -f "$TMP_FILE"
|
||||||
|
fi
|
||||||
|
}
|
||||||
|
|
||||||
|
# The actual enforcement — gated on the SENDER's own "messaging" flag in
|
||||||
|
# pstn-permissions.conf (the exact file/flag the Security Dashboard's
|
||||||
|
# "Internal SIP messaging" checkbox writes, independent of whether the PSTN
|
||||||
|
# trunk is installed), read live via AST_CONFIG() on every message, same
|
||||||
|
# mechanism pstn-trunk.sh's own dialplan already relies on for permission
|
||||||
|
# tiers — no restart needed to take effect. Off by default: an extension
|
||||||
|
# with no entry, or messaging=no, is denied. UNVERIFIED: MESSAGE(from)'s
|
||||||
|
# exact format hasn't been confirmed on a live install — the CUT()-based
|
||||||
|
# extraction below is written to tolerate a display name (e.g. this
|
||||||
|
# project's "name0" <999> callerid format) but if it ever fails to parse,
|
||||||
|
# FROM_EXT ends up empty/wrong and the AST_CONFIG() lookup simply finds no
|
||||||
|
# match, which denies by default (same fail-closed behavior as an
|
||||||
|
# unlisted extension) rather than silently allowing anything through.
|
||||||
|
_asterisk_write_messaging_dialplan() {
|
||||||
|
local FILE="$1"
|
||||||
|
cat > "$FILE" << 'EOF'
|
||||||
|
; Internal SIP MESSAGE routing/enforcement — services/asterisk.sh.
|
||||||
|
; Regenerated on every install/update; edit there, not here directly.
|
||||||
|
;
|
||||||
|
; Reached via each endpoint's message_context=sip-messaging (patched into
|
||||||
|
; Easy Asterisk's own device-creation code — see
|
||||||
|
; _asterisk_patch_messaging_vendor_files) instead of falling back to
|
||||||
|
; [intercom], which already owns an exact-match "exten => <ext>,1,..." per
|
||||||
|
; device for CALLS, regenerated fresh on every dialplan rebuild — a
|
||||||
|
; competing priority-1 declaration for the same extension number here would
|
||||||
|
; race that and risk breaking normal internal calling. This context ONLY
|
||||||
|
; ever receives MESSAGE requests, never calls.
|
||||||
|
[sip-messaging]
|
||||||
|
exten => _X.,1,NoOp(SIP MESSAGE to ${EXTEN})
|
||||||
|
same => n,Set(FROM_URI=${MESSAGE(from)})
|
||||||
|
same => n,Set(FROM_PART=${CUT(FROM_URI,@,1)})
|
||||||
|
same => n,Set(FROM_EXT=${CUT(FROM_PART,:,2)})
|
||||||
|
same => n,Set(SENDER_OK=${AST_CONFIG(pstn-permissions.conf,${FROM_EXT},messaging)})
|
||||||
|
same => n,GotoIf($["${SENDER_OK}" = "yes"]?deliver:deny)
|
||||||
|
same => n(deliver),MessageSend(pjsip:${EXTEN},${FROM_URI})
|
||||||
|
same => n,Hangup()
|
||||||
|
same => n(deny),NoOp(Denied — extension ${FROM_EXT} is not messaging-enabled)
|
||||||
|
same => n,Hangup()
|
||||||
|
EOF
|
||||||
|
}
|
||||||
|
|
||||||
|
_asterisk_remove_presence_timer() {
|
||||||
|
systemctl disable --now asterisk-presence-alert.timer 2>/dev/null || true
|
||||||
|
rm -f /etc/systemd/system/asterisk-presence-alert.timer /etc/systemd/system/asterisk-presence-alert.service
|
||||||
|
rm -f /etc/cron.d/asterisk-presence-alert
|
||||||
|
systemctl daemon-reload 2>/dev/null || true
|
||||||
|
}
|
||||||
|
|
||||||
|
# Interactive step — called from both the fresh-install flow and "update in
|
||||||
|
# place" (always asked either way, same reasoning as pstn-trunk.sh's
|
||||||
|
# international-calling step: this is a live-editable extra, not a
|
||||||
|
# structural setting, so it doesn't belong exclusively to one path).
|
||||||
|
_asterisk_run_presence_step() {
|
||||||
|
local EA_DIR="$1"
|
||||||
|
local SETTINGS_FILE="$EA_DIR/.presence-alert.env"
|
||||||
|
local STATE_FILE="$EA_DIR/.presence-alert.state"
|
||||||
|
|
||||||
|
echo ""
|
||||||
|
local _CUR_ENABLED="n" _CUR_NTFY=""
|
||||||
|
if [[ -f "$SETTINGS_FILE" ]]; then
|
||||||
|
# shellcheck disable=SC1090
|
||||||
|
source "$SETTINGS_FILE"
|
||||||
|
_CUR_ENABLED="${PRESENCE_ENABLED:-n}"
|
||||||
|
_CUR_NTFY="${PRESENCE_NTFY_URL:-}"
|
||||||
|
fi
|
||||||
|
|
||||||
|
if [[ "$_CUR_ENABLED" == "y" ]]; then
|
||||||
|
echo " Extension online/offline ntfy alerts are ON (topic: $_CUR_NTFY)."
|
||||||
|
local _CHANGE=""
|
||||||
|
prompt_yn " Change or disable this? (y/n):" "n" _CHANGE
|
||||||
|
[[ "$_CHANGE" =~ ^[Yy]$ ]] || return 0
|
||||||
|
local _DISABLE=""
|
||||||
|
prompt_yn " Disable presence alerts entirely? (y/n):" "n" _DISABLE
|
||||||
|
if [[ "$_DISABLE" =~ ^[Yy]$ ]]; then
|
||||||
|
_asterisk_remove_presence_timer
|
||||||
|
rm -f "$EA_DIR/asterisk-presence-alert.sh" "$STATE_FILE"
|
||||||
|
cat > "$SETTINGS_FILE" << ENV
|
||||||
|
PRESENCE_ENABLED="n"
|
||||||
|
PRESENCE_NTFY_URL=""
|
||||||
|
ENV
|
||||||
|
log_success "Presence alerts disabled."
|
||||||
|
return 0
|
||||||
|
fi
|
||||||
|
else
|
||||||
|
local _WANT=""
|
||||||
|
prompt_yn "Send an ntfy alert when an extension's SIP registration goes offline / comes back online? (y/n):" "n" _WANT
|
||||||
|
[[ "$_WANT" =~ ^[Yy]$ ]] || return 0
|
||||||
|
fi
|
||||||
|
|
||||||
|
local _ntfy_default="${_CUR_NTFY:-https://ntfy.sh/asterisk-presence}"
|
||||||
|
if [[ -z "$_CUR_NTFY" ]] && [[ -f "$DOCKER_DIR/ntfy/config/server.yml" ]]; then
|
||||||
|
local _local_base_url
|
||||||
|
_local_base_url="$(grep -oP '(?<=base-url: ")[^"]+' "$DOCKER_DIR/ntfy/config/server.yml" 2>/dev/null || true)"
|
||||||
|
if [[ -n "$_local_base_url" ]] && [[ "$_local_base_url" != "https://ntfy.example.com" ]]; then
|
||||||
|
_ntfy_default="${_local_base_url}/asterisk-presence"
|
||||||
|
log_info "Detected a configured local ntfy instance at $_local_base_url — using it as the default."
|
||||||
|
fi
|
||||||
|
fi
|
||||||
|
local PRESENCE_NTFY_URL=""
|
||||||
|
prompt_text " ntfy topic URL:" "$_ntfy_default" PRESENCE_NTFY_URL
|
||||||
|
if [[ -z "$PRESENCE_NTFY_URL" ]]; then
|
||||||
|
log_warning "No topic entered — presence alerts not enabled."
|
||||||
|
return 0
|
||||||
|
fi
|
||||||
|
|
||||||
|
_asterisk_write_presence_alert_script "$EA_DIR/asterisk-presence-alert.sh" "easy-asterisk" "$PRESENCE_NTFY_URL" "$STATE_FILE"
|
||||||
|
_asterisk_install_presence_timer "$EA_DIR"
|
||||||
|
|
||||||
|
cat > "$SETTINGS_FILE" << ENV
|
||||||
|
PRESENCE_ENABLED="y"
|
||||||
|
PRESENCE_NTFY_URL="${PRESENCE_NTFY_URL}"
|
||||||
|
ENV
|
||||||
|
chown "$ACTUAL_USER:$ACTUAL_USER" "$SETTINGS_FILE" 2>/dev/null || true
|
||||||
|
log_success "Presence alerts enabled (checked every 2 minutes) — topic: $PRESENCE_NTFY_URL"
|
||||||
|
log_info "Fires only on a state CHANGE, never every check — the first check after enabling"
|
||||||
|
log_info "never alerts by itself, since there's no prior state to compare against yet."
|
||||||
|
}
|
||||||
|
|
||||||
# ── Shared: docker-compose.yml ─────────────────────────────────────────────
|
# ── Shared: docker-compose.yml ─────────────────────────────────────────────
|
||||||
# Same reasoning as above — one copy of the template used by both fresh
|
# Same reasoning as above — one copy of the template used by both fresh
|
||||||
# installs and updates. Must be called with $PWD already at $EA_DIR.
|
# installs and updates. Must be called with $PWD already at $EA_DIR.
|
||||||
@@ -341,6 +693,13 @@ install_asterisk() {
|
|||||||
echo "[DRY-RUN] Would scan for a free web admin port starting at 8081 (avoids e.g. CrowdSec's 8080)"
|
echo "[DRY-RUN] Would scan for a free web admin port starting at 8081 (avoids e.g. CrowdSec's 8080)"
|
||||||
echo "[DRY-RUN] Would open UFW ports: 5060, 5061, <web admin port>, 8088, 8089, 3478, 10000-20000, 49152-49252"
|
echo "[DRY-RUN] Would open UFW ports: 5060, 5061, <web admin port>, 8088, 8089, 3478, 10000-20000, 49152-49252"
|
||||||
echo "[DRY-RUN] Would offer 'update in place' instead of a fresh install if $EA_DIR already exists"
|
echo "[DRY-RUN] Would offer 'update in place' instead of a fresh install if $EA_DIR already exists"
|
||||||
|
echo "[DRY-RUN] Would patch vendor device-creation code + extensions.conf generator to route"
|
||||||
|
echo "[DRY-RUN] internal SIP MESSAGE through a dedicated [sip-messaging] dialplan context,"
|
||||||
|
echo "[DRY-RUN] gated live on each sender's 'messaging' flag in pstn-permissions.conf — migrates"
|
||||||
|
echo "[DRY-RUN] any already-existing devices too"
|
||||||
|
echo "[DRY-RUN] Would offer optional ntfy alerts on extension registration going offline/online"
|
||||||
|
echo "[DRY-RUN] (checked every 2 minutes via systemd timer, cron.d fallback; always asked,"
|
||||||
|
echo "[DRY-RUN] update mode included)"
|
||||||
return 0
|
return 0
|
||||||
fi
|
fi
|
||||||
|
|
||||||
@@ -363,6 +722,12 @@ install_asterisk() {
|
|||||||
|
|
||||||
_asterisk_refresh_vendor_files
|
_asterisk_refresh_vendor_files
|
||||||
_asterisk_write_compose
|
_asterisk_write_compose
|
||||||
|
_asterisk_patch_messaging_vendor_files "$EA_DIR"
|
||||||
|
_asterisk_write_messaging_dialplan "$EA_DIR/config/asterisk/messaging-dialplan.conf"
|
||||||
|
_asterisk_ensure_live_messaging_include "$EA_DIR"
|
||||||
|
_asterisk_migrate_existing_devices_message_context "$EA_DIR/config/asterisk/pjsip.conf"
|
||||||
|
ensure_docker_dir_ownership "$EA_DIR/config/asterisk"
|
||||||
|
chmod 644 "$EA_DIR/config/asterisk/messaging-dialplan.conf"
|
||||||
|
|
||||||
log_info "Rebuilding and restarting containers..."
|
log_info "Rebuilding and restarting containers..."
|
||||||
if docker compose up -d --build --force-recreate; then
|
if docker compose up -d --build --force-recreate; then
|
||||||
@@ -371,6 +736,8 @@ install_asterisk() {
|
|||||||
log_warning "docker compose up failed — check: docker compose -f $EA_DIR/docker-compose.yml logs"
|
log_warning "docker compose up failed — check: docker compose -f $EA_DIR/docker-compose.yml logs"
|
||||||
fi
|
fi
|
||||||
|
|
||||||
|
_asterisk_run_presence_step "$EA_DIR"
|
||||||
|
|
||||||
local _EXISTING_DOMAIN _EXISTING_PORT
|
local _EXISTING_DOMAIN _EXISTING_PORT
|
||||||
_EXISTING_DOMAIN="$(grep -E '^DOMAIN_NAME=' .env | cut -d= -f2-)"
|
_EXISTING_DOMAIN="$(grep -E '^DOMAIN_NAME=' .env | cut -d= -f2-)"
|
||||||
_EXISTING_PORT="$(grep -E '^WEB_ADMIN_PORT=' .env | cut -d= -f2-)"
|
_EXISTING_PORT="$(grep -E '^WEB_ADMIN_PORT=' .env | cut -d= -f2-)"
|
||||||
@@ -402,6 +769,10 @@ install_asterisk() {
|
|||||||
cd "$EA_DIR" || return 1
|
cd "$EA_DIR" || return 1
|
||||||
|
|
||||||
_asterisk_refresh_vendor_files
|
_asterisk_refresh_vendor_files
|
||||||
|
_asterisk_patch_messaging_vendor_files "$EA_DIR"
|
||||||
|
_asterisk_write_messaging_dialplan "$EA_DIR/config/asterisk/messaging-dialplan.conf"
|
||||||
|
ensure_docker_dir_ownership "$EA_DIR/config/asterisk"
|
||||||
|
chmod 644 "$EA_DIR/config/asterisk/messaging-dialplan.conf"
|
||||||
|
|
||||||
# ── Networking mode ───────────────────────────────────────────────────────
|
# ── Networking mode ───────────────────────────────────────────────────────
|
||||||
echo ""
|
echo ""
|
||||||
@@ -542,6 +913,9 @@ ENV
|
|||||||
log_success "UFW rules added."
|
log_success "UFW rules added."
|
||||||
fi
|
fi
|
||||||
|
|
||||||
|
# ── Extension presence (online/offline) ntfy alerts ────────────────────────
|
||||||
|
_asterisk_run_presence_step "$EA_DIR"
|
||||||
|
|
||||||
# ── README ────────────────────────────────────────────────────────────────
|
# ── README ────────────────────────────────────────────────────────────────
|
||||||
write_readme "$EA_DIR" << 'MD'
|
write_readme "$EA_DIR" << 'MD'
|
||||||
# Easy Asterisk PBX + coturn
|
# Easy Asterisk PBX + coturn
|
||||||
@@ -624,6 +998,26 @@ value.)
|
|||||||
| spool/ | /var/spool/asterisk |
|
| spool/ | /var/spool/asterisk |
|
||||||
| lib/ | /var/lib/asterisk |
|
| lib/ | /var/lib/asterisk |
|
||||||
|
|
||||||
|
## Internal SIP messaging (no PSTN trunk needed)
|
||||||
|
|
||||||
|
Every extension can send/receive Asterisk's native SIP MESSAGE (no carrier
|
||||||
|
SMS, no PSTN, no cost) once its "messaging" flag is set to yes in
|
||||||
|
\`pstn-permissions.conf\` — via the Security Dashboard's "Internal SIP
|
||||||
|
messaging" card, or by hand. This works independent of \`pstn-trunk.sh\`
|
||||||
|
entirely. Under the hood: every device endpoint gets
|
||||||
|
\`message_context=sip-messaging\`, routing messages to a dedicated
|
||||||
|
\`config/asterisk/messaging-dialplan.conf\` context instead of \`[intercom]\`
|
||||||
|
(which already owns per-device call routing) — this install/update patches
|
||||||
|
both the device-creation code (so new extensions pick it up automatically)
|
||||||
|
and any devices that already existed.
|
||||||
|
|
||||||
|
## Extension presence (online/offline) alerts
|
||||||
|
|
||||||
|
Optional ntfy alert when an extension's SIP registration changes state —
|
||||||
|
offered on both fresh install and "update in place". Checked every 2
|
||||||
|
minutes (systemd timer, cron.d fallback); fires only on a change, never on
|
||||||
|
every check.
|
||||||
|
|
||||||
## Ports
|
## Ports
|
||||||
|
|
||||||
| Port | Protocol | Purpose |
|
| Port | Protocol | Purpose |
|
||||||
|
|||||||
Reference in New Issue
Block a user