Fold asterisk-digital-ocean into asterisk with droplet auto-detection

services/asterisk-digital-ocean.sh was a near-verbatim copy of
services/asterisk.sh — same vendor refresh, compose template, messaging
dialplan, presence alerts, UFW rules and dashboard/trunk chaining, with the
helper functions renamed _asterisk_do_*. Two copies meant every fix had to
land twice, and several never did.

There is now one `asterisk` service. It reads the DigitalOcean metadata
service and asks either way (so a droplet with metadata blocked, or another
provider's public VM, can still opt in), then gates the genuinely
droplet-specific behaviour on that one answer: swapfile for low-RAM plans,
public-FQDN-only setup with no LAN/VLAN prompts, a Caddy site block pinned
to that FQDN, the remote-Authelia option, and the doctl Cloud Firewall.

Two things that were droplet-only for no real reason now apply everywhere:
the entrypoint patch that writes security-level events to logs/full, and
the logrotate config for that file. Without them the Security Dashboard's
Security Log tab and CrowdSec's Asterisk acquisition were silently empty on
every home/LAN install; crowdsec.sh now detects either install directory.

Existing droplets are left alone: an install at ~/docker/asterisk-digital-ocean
keeps its directory and easy-asterisk-do container names, since its Caddyfile
block, UFW rules, Cloud Firewall, CrowdSec acquisition and PSTN trunk all
name those exactly. New installs use ~/docker/asterisk / easy-asterisk.
`sudo ./setup.sh asterisk-digital-ocean` still works via a new SERVICE_ALIAS
map in setup.sh, without a second menu entry.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01NAddJGE1G6eGaPzmScG5Vh
This commit is contained in:
Claude
2026-07-25 01:06:38 +00:00
parent 15d70356ac
commit 8b843ca1c1
11 changed files with 971 additions and 1772 deletions
File diff suppressed because it is too large Load Diff
+849 -202
View File
File diff suppressed because it is too large Load Diff
+1 -1
View File
@@ -435,7 +435,7 @@ auth.${AUTHELIA_DOMAIN} {
# own incoming request (always auth.${AUTHELIA_DOMAIN} itself) and
# overwrites the value a forward_auth caller (e.g. a remote site's
# "forward_auth https://auth.${AUTHELIA_DOMAIN}" block, see
# services/asterisk-digital-ocean.sh) set for its own domain. Confirmed
# services/asterisk.sh's droplet-mode Caddy block) set for its own domain. Confirmed
# live: every forward-auth check evaluated as if it were for
# auth.${AUTHELIA_DOMAIN} itself (which has policy: bypass in
# access_control.rules so its own login portal isn't gated behind
+1 -1
View File
@@ -268,7 +268,7 @@ services:
labels:
- "io.podman.annotations.label/crowdsec.enable=true"
# Lets Caddyfile blocks reach services that use network_mode: host
# (e.g. asterisk/asterisk-digital-ocean) via "host.docker.internal:PORT" — Caddy
# (e.g. asterisk) via "host.docker.internal:PORT" — Caddy
# itself is on the caddy_net bridge network below, so plain "localhost"
# in a site block resolves to Caddy's own container, not the host.
extra_hosts:
+26 -14
View File
@@ -102,7 +102,7 @@ install_crowdsec() {
echo "[DRY-RUN] Would ensure /var/log/caddy exists for log acquisition"
echo "[DRY-RUN] Would install collections: sshd, linux, caddy, base-http-scenarios"
echo "[DRY-RUN] Would write Caddy acquisition /etc/crowdsec/acquis.d/caddy.yaml"
echo "[DRY-RUN] Would install crowdsecurity/asterisk + write an acquisition if asterisk-digital-ocean is installed"
echo "[DRY-RUN] Would install crowdsecurity/asterisk + write an acquisition if asterisk is installed"
echo "[DRY-RUN] Would optionally wire ntfy ban alerts into the default profile"
echo "[DRY-RUN] Would optionally register with a remote/central LAPI and disable the local one"
echo "[DRY-RUN] Would enable + restart crowdsec and crowdsec-firewall-bouncer"
@@ -194,17 +194,26 @@ labels:
echo " ✓ Caddy acquisition already exists"
fi
# ── 5b. SIP brute-force/enumeration protection, if asterisk-digital-ocean
# is installed (services/asterisk-digital-ocean.sh patches Asterisk to log
# security events — auth failures, registration scanning — to
# $EA_DIR/logs/full. The plain LAN asterisk.sh doesn't emit that file yet,
# so it's intentionally not detected here.)
local ASTERISK_LOG_DIR="$DOCKER_DIR/asterisk-digital-ocean/logs"
if [ -d "$ASTERISK_LOG_DIR" ]; then
echo " Detected asterisk-digital-ocean — installing SIP brute-force/enumeration protection..."
# ── 5b. SIP brute-force/enumeration protection, if Asterisk is installed.
# services/asterisk.sh patches Asterisk to log security events — auth
# failures, registration scanning — to $EA_DIR/logs/full, which is what
# the acquisition below tails. Both directories are probed: a box set up
# before the droplet edition was merged back into `asterisk` still runs
# out of ~/docker/asterisk-digital-ocean. The logging patch used to be
# droplet-only; it now applies to every install, so a home/LAN box gets
# SIP protection here too.
local ASTERISK_LOG_DIR=""
local _ea_candidate
for _ea_candidate in "$DOCKER_DIR/asterisk-digital-ocean" "$DOCKER_DIR/asterisk"; do
[ -d "$_ea_candidate/logs" ] && { ASTERISK_LOG_DIR="$_ea_candidate/logs"; break; }
done
if [ -n "$ASTERISK_LOG_DIR" ]; then
echo " Detected Asterisk at ${ASTERISK_LOG_DIR%/logs} — installing SIP brute-force/enumeration protection..."
sudo cscli collections install crowdsecurity/asterisk 2>/dev/null || \
echo " ⚠ crowdsecurity/asterisk collection may already be installed"
# Filename kept as-is so a droplet that already has this acquisition
# isn't given a second one pointing at the same log.
local ASTERISK_ACQUIS="/etc/crowdsec/acquis.d/asterisk-digital-ocean.yaml"
if [ ! -f "$ASTERISK_ACQUIS" ]; then
local ASTERISK_ACQUIS_CONTENT="filenames:
@@ -543,7 +552,7 @@ install. The real configuration lives under `/etc/crowdsec`.
## What it does
- Detects malicious behaviour (SSH brute force, web scans, SIP brute
force/enumeration if `asterisk-digital-ocean` is installed) by parsing logs.
force/enumeration if `asterisk` is installed) by parsing logs.
- Bans offending IPs via the **firewall bouncer** (iptables/nftables).
- Pulls **community IP reputation** blocklists so known-bad IPs are blocked
before they ever touch your services.
@@ -573,9 +582,12 @@ sudo cscli collections list # installed detection collections
- Log acquisition (what to watch): `/etc/crowdsec/acquis.d/`
- Caddy access logs: `/etc/crowdsec/acquis.d/caddy.yaml`
(`/var/log/caddy/*.log` — Caddy writes JSON access logs there)
- Asterisk SIP auth events (if `asterisk-digital-ocean` is installed):
`/etc/crowdsec/acquis.d/asterisk-digital-ocean.yaml`
(`~/docker/asterisk-digital-ocean/logs/full` — auth failures, registration scans)
- Asterisk SIP auth events (if `asterisk` is installed):
`/etc/crowdsec/acquis.d/asterisk-digital-ocean.yaml` (filename kept from
when the droplet edition was its own service, so existing droplets aren't
given a duplicate acquisition)
(`~/docker/asterisk/logs/full`, or `~/docker/asterisk-digital-ocean/logs/full`
on a pre-merge droplet — auth failures, registration scans)
- Notifications: `/etc/crowdsec/notifications/`
- ntfy ban alerts (if enabled): `/etc/crowdsec/notifications/ntfy.yaml`,
wired into `/etc/crowdsec/profiles.yaml`
@@ -609,7 +621,7 @@ sudo cscli collections list # installed detection collections
list directly in that file, then `sudo systemctl restart crowdsec`. This
can block Let's Encrypt's out-of-region ACME validation checks; if a cert
renewal fails mysteriously, check here first.
- ASN-exempt Asterisk brute-force scenarios (if enabled, asterisk-digital-ocean
- ASN-exempt Asterisk brute-force scenarios (if enabled, Asterisk installs
only): `/etc/crowdsec/scenarios/local-asterisk_bf.yaml` and
`local-asterisk_user_enum.yaml` — local forks of the stock hub scenarios with
specific carrier ASNs excluded from their filter (the hub originals get
+24 -23
View File
@@ -1,6 +1,6 @@
#!/bin/bash
# services/pstn-trunk.sh — SIP PSTN trunk add-on for asterisk-digital-ocean
# (or the home/LAN asterisk install): US-only outbound (NANP dialplan
# services/pstn-trunk.sh — SIP PSTN trunk add-on for services/asterisk.sh:
# US-only outbound (NANP dialplan
# restriction), independent outbound/inbound concurrent-call caps, a 3-tier
# permission model per extension (internal-only / restricted to pre-approved
# numbers / full US calling), a configurable inbound ring-group,
@@ -15,19 +15,19 @@
# works the same way. VoIP.ms and Anveo Direct are both confirmed working;
# see docs/pstn-calling-voipms-plan.md for the design/cost background.
#
# Requires an existing services/asterisk-digital-ocean.sh OR services/asterisk.sh
# install — this adds a PSTN trunk on top of one of them and does not stand
# alone. Permission tiers AND concurrency caps are managed live (no restart
# Requires an existing services/asterisk.sh install (either directory layout —
# ~/docker/asterisk, or ~/docker/asterisk-digital-ocean on a box set up before
# the droplet edition was merged back in) — this adds a PSTN trunk on top and
# does not stand alone. Permission tiers AND concurrency caps are managed live (no restart
# needed) via pstn-permissions.conf / pstn-limits.conf — editable by hand, or
# from services/security-dashboard.sh's "PSTN Trunk" tab if that's installed.
# from services/security-dashboard.sh's Extensions tab if that's installed.
#
# Part of the modular post-install system (sourced by setup.sh).
register_service pstn-trunk homelab "SIP PSTN trunk for asterisk-digital-ocean/asterisk — US-only, per-extension permission tiers, spend/volume alerts (any IP-authenticated provider — VoIP.ms and Anveo Direct both confirmed)"
register_service pstn-trunk homelab "SIP PSTN trunk for asterisk — US-only, per-extension permission tiers, spend/volume alerts (any IP-authenticated provider — VoIP.ms and Anveo Direct both confirmed)"
# ── Surviving Easy Asterisk's regeneration ──────────────────────────────────
# Easy Asterisk (the vendor project asterisk-digital-ocean.sh/asterisk.sh
# build on) fully OVERWRITES both pjsip.conf and extensions.conf from its own
# Easy Asterisk (the vendor project services/asterisk.sh builds on) fully OVERWRITES both pjsip.conf and extensions.conf from its own
# internal state:
# - extensions.conf: rebuilt by rebuild_dialplan() on every container start,
# and whenever a device/room is added or removed via the web admin.
@@ -41,9 +41,9 @@ register_service pstn-trunk homelab "SIP PSTN trunk for asterisk-digital-ocean/a
# the #include itself survive regeneration too, _pstn_patch_vendor_files
# (below) patches it into the vendor's *generator functions* — the same
# technique this repo already uses for the logger.conf security-logging fix
# in _asterisk_do_refresh_vendor_files (see services/asterisk-digital-ocean.sh).
# in _asterisk_refresh_vendor_files (see services/asterisk.sh).
#
# Caveat: if the base asterisk-digital-ocean/asterisk install is later
# Caveat: if the base asterisk install is later
# refreshed ("update in place", which re-copies fresh vendor files)
# independently of this service, the patch is wiped along with it and needs
# reapplying — run this service again (fresh or update mode both reapply it)
@@ -459,7 +459,7 @@ EOF
# zero of the outbound NANP patterns either, and `dialplan show
# from-pstn-trunk` reported the context didn't exist at all, with no
# warning or error anywhere (config log, full log, or the reload command's
# own output) pointing at why. Meanwhile services/asterisk-digital-ocean.sh's
# own output) pointing at why. Meanwhile services/asterisk.sh's
# messaging-dialplan.conf — #include'd via the exact same mechanism, right
# after [intercom] in the same extensions.conf — loaded fine every time.
# The one structural difference: messaging-dialplan.conf's first real line
@@ -1608,7 +1608,7 @@ install_pstn-trunk() {
[[ "$ASTERISK_KIND" == "asterisk-digital-ocean" ]] && CONTAINER_NAME="easy-asterisk-do"
if [ "$DRY_RUN" = true ]; then
echo "[DRY-RUN] Would require an existing asterisk-digital-ocean OR asterisk (LAN) install"
echo "[DRY-RUN] Would require an existing asterisk install (droplet or home/LAN)"
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"
@@ -1640,10 +1640,12 @@ install_pstn-trunk() {
fi
if [[ -z "$EA_DIR" ]]; then
log_error "Neither asterisk-digital-ocean nor asterisk (LAN) is installed — install one first:"
log_error " sudo ./setup.sh asterisk-digital-ocean (recommended — public droplet, static IP)"
log_error " sudo ./setup.sh asterisk (home/LAN — see the static-IP caveat below)"
log_error "This service adds a PSTN trunk on top of one of them; it doesn't stand alone."
log_error "Asterisk is not installed — install it first:"
log_error " sudo ./setup.sh asterisk"
log_error "A public droplet (which that installer detects and tunes for) is the"
log_error "recommended host, since IP authentication wants a static IP — see the"
log_error "caveat below for what that means on a home/LAN box."
log_error "This service adds a PSTN trunk on top of it; it doesn't stand alone."
return 1
fi
@@ -1652,7 +1654,7 @@ install_pstn-trunk() {
log_warning "Using the home/LAN asterisk install. IP authentication needs a STABLE public IP —"
log_warning "if this box is behind a dynamic home IP, your provider's IP allow-list goes stale"
log_warning "whenever your ISP rotates it, breaking calls until you update it there yourself."
log_warning "A static IP from your ISP avoids that; asterisk-digital-ocean sidesteps it entirely."
log_warning "A static IP from your ISP avoids that; a cloud droplet sidesteps it entirely."
fi
log_info "Configuring a SIP PSTN trunk for $ASTERISK_KIND (any IP-authenticated provider —"
@@ -2213,12 +2215,11 @@ Asterisk's native SIP \`MESSAGE\` support (extension-to-extension texting —
no carrier SMS, no PSTN, no cost) is gated by a \`messaging=yes\` flag per
extension in \`pstn-permissions.conf\`, independent of the PSTN calling
tiers above — off by default, same "opt in" posture. Live-editable any
time via the Security Dashboard's "PSTN Trunk" tab, in its own
always-available "Internal SIP messaging" card — no dependency on this
trunk (or any PSTN trunk at all) being installed.
time via the Security Dashboard's Extensions tab, in the Messaging column of
its always-available extensions table — no dependency on this trunk (or any
PSTN trunk at all) being installed.
Actually enforced, not just a flag — \`services/asterisk-digital-ocean.sh\`
(and \`services/asterisk.sh\` for the LAN edition) routes messages through a
Actually enforced, not just a flag — \`services/asterisk.sh\` routes messages through a
dedicated \`[sip-messaging]\` dialplan context (separate from \`[intercom]\`'s
own per-device call routing, so there's no collision risk) and checks this
same flag via \`AST_CONFIG()\` before delivering. One caveat still flagged