asterisk-do: auto-install Caddy/CrowdSec, fix confusing domain prompt

Self-contained by default now: if Caddy or CrowdSec aren't already on
the box, asterisk-do offers to install them itself (calling their
install_ functions directly — setup.sh sources every services/*.sh up
front, so they're already in-process during a wizard run). Standalone
single-file runs get a manual pointer instead, since those functions
don't exist outside the full repo checkout.

Also fixes the confusing "Configure Caddy reverse proxy for Asterisk
Web Admin" domain prompt: it used to ask for a second, independent
domain, which silently breaks the TLS cert sync if it doesn't match
the SIP FQDN exactly (Caddy only holds a cert for the domain it's
actually serving). It now always reuses the SIP FQDN automatically —
reconstructing configure_caddy_for_service's subdomain default so the
common case (SIP domain is a subdomain of SITE_DOMAIN) needs zero
extra input, with clear wording either way. FQDN prompt, README, and
final summary updated to match.
This commit is contained in:
Claude
2026-07-19 03:48:34 +00:00
parent dfc298f6ab
commit 5eddab9f9a
+93 -27
View File
@@ -205,11 +205,14 @@ install_asterisk-do() {
if [ "$DRY_RUN" = true ]; then if [ "$DRY_RUN" = true ]; then
echo "[DRY-RUN] Would add a swapfile if RAM <= 2048MB and none exists" echo "[DRY-RUN] Would add a swapfile if RAM <= 2048MB and none exists"
echo "[DRY-RUN] Would offer to install Caddy if not already present (full repo only)"
echo "[DRY-RUN] Would create $EA_DIR with Dockerfile, docker-compose.yml, .env" echo "[DRY-RUN] Would create $EA_DIR with Dockerfile, docker-compose.yml, .env"
echo "[DRY-RUN] Would copy/download vendor files from easy-asterisk" echo "[DRY-RUN] Would copy/download vendor files from easy-asterisk"
echo "[DRY-RUN] Would detect droplet public IP via DO metadata service" echo "[DRY-RUN] Would detect droplet public IP via DO metadata service"
echo "[DRY-RUN] Would open UFW ports: 5060, 5061, 8080, 8088, 8089, 3478, 10000-20000, 49152-49252" echo "[DRY-RUN] Would open UFW ports: 5060, 5061, 8080, 8088, 8089, 3478, 10000-20000, 49152-49252"
echo "[DRY-RUN] Would offer to create a DigitalOcean Cloud Firewall via doctl" echo "[DRY-RUN] Would offer to create a DigitalOcean Cloud Firewall via doctl"
echo "[DRY-RUN] Would reverse-proxy the web admin on the SAME FQDN used for SIP (needed for cert sync)"
echo "[DRY-RUN] Would offer to install CrowdSec if not already present (full repo only)"
return 0 return 0
fi fi
@@ -242,6 +245,24 @@ install_asterisk-do() {
fi fi
fi fi
# ── Bring in Caddy automatically, if this is a full repo checkout ─────────
# Caddy is a separate service (services/caddy.sh); asterisk-do only
# *integrates* with it (reused certs, reverse-proxied admin) unless
# offered here. setup.sh sources every services/*.sh file up front, so
# install_caddy already exists in-process when running through the
# wizard — a standalone single-file run doesn't have it, so that case
# gets a manual pointer instead.
if [[ ! -d "$DOCKER_DIR/caddy" ]] && [[ -z "${CADDY_REMOTE_HOST:-}" ]]; then
if declare -F install_caddy &>/dev/null; then
local WANT_CADDY=""
prompt_yn "Caddy not detected — install it now for a trusted TLS cert + reverse proxy? (y/n):" "y" WANT_CADDY
[[ "$WANT_CADDY" =~ ^[Yy]$ ]] && install_caddy
else
log_warning "Caddy not detected, and this looks like a standalone copy of asterisk-do.sh."
log_warning "Grab the full repo to auto-install it, or run services/caddy.sh yourself."
fi
fi
mkdir -p "$EA_DIR" mkdir -p "$EA_DIR"
mkdir -p "$EA_DIR/config/asterisk" "$EA_DIR/config/easy-asterisk" \ mkdir -p "$EA_DIR/config/asterisk" "$EA_DIR/config/easy-asterisk" \
"$EA_DIR/logs" "$EA_DIR/spool" "$EA_DIR/lib" "$EA_DIR/logs" "$EA_DIR/spool" "$EA_DIR/lib"
@@ -312,8 +333,13 @@ install_asterisk-do() {
echo "" echo ""
echo " Point a DNS A record at this droplet before continuing:" echo " Point a DNS A record at this droplet before continuing:"
echo " <subdomain>.${SITE_DOMAIN:-example.com} A ${PUBLIC_IP:-<droplet public IP>}" echo " <subdomain>.${SITE_DOMAIN:-example.com} A ${PUBLIC_IP:-<droplet public IP>}"
echo ""
echo " This one FQDN covers everything below — SIP registration, the web"
echo " admin, and (via Caddy) the TLS cert Asterisk needs for SIP. There's"
echo " no separate \"admin domain\" to pick later — whatever you enter here"
echo " is what your SIP client (e.g. Sipnetic) will register against."
local DOMAIN_NAME="" local DOMAIN_NAME=""
prompt_text "FQDN for this PBX [blank=self-signed cert, IP-only access]:" "" DOMAIN_NAME prompt_text "FQDN for this PBX, e.g. sip.yourdomain.com [blank=self-signed cert, IP-only access]:" "" DOMAIN_NAME
[[ -z "$DOMAIN_NAME" ]] && log_warning "No FQDN entered — using a self-signed cert; phones must trust it manually." [[ -z "$DOMAIN_NAME" ]] && log_warning "No FQDN entered — using a self-signed cert; phones must trust it manually."
# ── Secrets ─────────────────────────────────────────────────────────────── # ── Secrets ───────────────────────────────────────────────────────────────
@@ -482,18 +508,54 @@ ENV
printf ' %s\n' "${DO_FW_RULES[@]}" printf ' %s\n' "${DO_FW_RULES[@]}"
fi fi
# ── Caddy reverse proxy for web admin ───────────────────────────────────── # ── Caddy: reverse-proxy the web admin on the SAME FQDN used for SIP ──────
local EXTRA_BLOCK="" # Caddy only holds a cert for domains it's actively serving. If the web
if [ -d "$DOCKER_DIR/authelia" ]; then # admin were proxied on a different "admin" subdomain, Caddy would obtain
local _use_auth="" # a cert for THAT domain instead — the sync earlier would never find one
prompt_yn "Protect Asterisk web admin with Authelia SSO? (y/n):" "y" _use_auth # matching $DOMAIN_NAME, and SIP TLS would silently stay self-signed. So
if [[ "$_use_auth" =~ ^[Yy]$ ]]; then # there's no separate domain prompt: this always targets $DOMAIN_NAME.
EXTRA_BLOCK=" import authelia" if [[ -z "$DOMAIN_NAME" ]]; then
# Disable built-in auth since Authelia handles it log_info "No FQDN set — web admin stays on http://${PUBLIC_IP:-localhost}:8080 (nothing for Caddy to do)."
sed -i "s/^WEB_ADMIN_AUTH_DISABLED=.*/WEB_ADMIN_AUTH_DISABLED=true/" .env elif [[ ! -d "$DOCKER_DIR/caddy" ]] && [[ -z "${CADDY_REMOTE_HOST:-}" ]]; then
log_info "Caddy not installed — web admin stays on http://${PUBLIC_IP:-localhost}:8080, SIP TLS stays self-signed."
else
local EXTRA_BLOCK=""
if [ -d "$DOCKER_DIR/authelia" ]; then
local _use_auth=""
prompt_yn "Protect Asterisk web admin with Authelia SSO? (y/n):" "y" _use_auth
if [[ "$_use_auth" =~ ^[Yy]$ ]]; then
EXTRA_BLOCK=" import authelia"
# Disable built-in auth since Authelia handles it
sed -i "s/^WEB_ADMIN_AUTH_DISABLED=.*/WEB_ADMIN_AUTH_DISABLED=true/" .env
fi
fi fi
# Reconstruct the subdomain-only fragment so configure_caddy_for_service's
# own "<subdomain>.${SITE_DOMAIN}" default lands exactly on $DOMAIN_NAME —
# pressing Enter at its domain prompt then just works.
local _CADDY_SUBDOMAIN="asterisk"
if [[ -n "${SITE_DOMAIN:-}" ]] && [[ "$DOMAIN_NAME" == *".${SITE_DOMAIN}" ]]; then
_CADDY_SUBDOMAIN="${DOMAIN_NAME%.${SITE_DOMAIN}}"
fi
log_info "Reverse-proxying the web admin at https://${DOMAIN_NAME}/ — this is also what gets"
log_info "Asterisk a trusted TLS cert for SIP instead of a self-signed one."
log_info "When prompted for a domain next, use exactly: ${DOMAIN_NAME}"
configure_caddy_for_service "Asterisk Web Admin" "8080" "$_CADDY_SUBDOMAIN" "$EXTRA_BLOCK"
fi
# ── CrowdSec: SIP brute-force/enumeration protection ──────────────────────
if command -v cscli &>/dev/null; then
log_info "CrowdSec is already installed — rerun it to pick up SIP protection for this install:"
log_info " sudo ./setup.sh crowdsec"
elif declare -F install_crowdsec &>/dev/null; then
local WANT_CS=""
prompt_yn "CrowdSec not detected — install it now for SSH + SIP intrusion prevention? (y/n):" "y" WANT_CS
[[ "$WANT_CS" =~ ^[Yy]$ ]] && install_crowdsec
else
log_warning "CrowdSec not detected, and this looks like a standalone copy of asterisk-do.sh."
log_warning "Grab the full repo to auto-install it, or run services/crowdsec.sh yourself."
fi fi
configure_caddy_for_service "Asterisk Web Admin" "8080" "asterisk" "$EXTRA_BLOCK"
# ── README ──────────────────────────────────────────────────────────────── # ── README ────────────────────────────────────────────────────────────────
write_readme "$EA_DIR" << MD write_readme "$EA_DIR" << MD
@@ -535,11 +597,13 @@ and supported longer (through 2031) if you'd rather track the newer LTS.
Before running this installer, point an A record at the droplet's public IP: Before running this installer, point an A record at the droplet's public IP:
\`\`\` \`\`\`
asterisk.yourdomain.com A <droplet public IP> sip.yourdomain.com A <droplet public IP>
\`\`\` \`\`\`
The installer reads the droplet's public IP itself (via the DigitalOcean The installer reads the droplet's public IP itself (via the DigitalOcean
metadata service) and shows it to you during setup. metadata service) and shows it to you during setup. This one FQDN is used
for SIP, the web admin, and the TLS cert — there's no separate domain to
plan for the admin panel.
## Security ## Security
@@ -622,13 +686,15 @@ users: Linphone or Zoiper cover the same ground.)
## TLS certificate ## TLS certificate
If Caddy is installed and already holds a Let's Encrypt cert for Caddy is what actually talks to Let's Encrypt — Asterisk never does ACME
\`DOMAIN_NAME\` (i.e. there's a Caddyfile site block for that exact hostname), itself. The installer always reverse-proxies the web admin on the exact
the container mounts Caddy's cert store read-only and the entrypoint syncs same FQDN used for SIP (never a separate "admin" domain), specifically
it in automatically on every start — and re-checks every 12h so renewals because that's what makes Caddy hold a cert matching \`DOMAIN_NAME\`. The
get picked up without a restart. No Caddyfile block for the domain, or no container then mounts Caddy's cert store read-only and the entrypoint syncs
Caddy at all, falls back to a self-signed cert (phones must be configured that cert in automatically on every start — and re-checks every 12h so
to accept it). renewals get picked up without a restart. No Caddy on the box, or no FQDN
set at all, falls back to a self-signed cert (phones must be configured to
accept it manually).
## Web admin ## Web admin
@@ -671,13 +737,13 @@ MD
echo " Web admin: http://${PUBLIC_IP:-localhost}:8080" echo " Web admin: http://${PUBLIC_IP:-localhost}:8080"
echo " Manage: docker compose -f $EA_DIR/docker-compose.yml <up|down|logs>" echo " Manage: docker compose -f $EA_DIR/docker-compose.yml <up|down|logs>"
echo " Script: docker exec -it easy-asterisk-do easy-asterisk --help" echo " Script: docker exec -it easy-asterisk-do easy-asterisk --help"
echo "" if [[ -n "$DOMAIN_NAME" ]] && [[ -d "$DOCKER_DIR/caddy" ]]; then
if command -v cscli &>/dev/null; then echo ""
log_info "CrowdSec is already installed — rerun it to add SIP brute-force protection for this install:" log_info "If Caddy was just installed in this same run, it may still be obtaining the"
log_info " sudo ./setup.sh crowdsec" log_info "Let's Encrypt cert for ${DOMAIN_NAME} — Asterisk only checks for it at startup"
else log_info "and then every 12h. If SIP TLS still shows self-signed after a couple of"
log_info "Install CrowdSec (services/crowdsec.sh) for SIP brute-force/enumeration protection —" log_info "minutes, pick it up immediately with:"
log_info " it auto-detects this install and wires up the crowdsecurity/asterisk collection." log_info " docker compose -f $EA_DIR/docker-compose.yml restart asterisk"
fi fi
echo "" echo ""
} }