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
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 copy/download vendor files from easy-asterisk"
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 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
fi
@@ -242,6 +245,24 @@ install_asterisk-do() {
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/config/asterisk" "$EA_DIR/config/easy-asterisk" \
"$EA_DIR/logs" "$EA_DIR/spool" "$EA_DIR/lib"
@@ -312,8 +333,13 @@ install_asterisk-do() {
echo ""
echo " Point a DNS A record at this droplet before continuing:"
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=""
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."
# ── Secrets ───────────────────────────────────────────────────────────────
@@ -482,18 +508,54 @@ ENV
printf ' %s\n' "${DO_FW_RULES[@]}"
fi
# ── Caddy reverse proxy for web admin ─────────────────────────────────────
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
# ── Caddy: reverse-proxy the web admin on the SAME FQDN used for SIP ──────
# Caddy only holds a cert for domains it's actively serving. If the web
# admin were proxied on a different "admin" subdomain, Caddy would obtain
# a cert for THAT domain instead — the sync earlier would never find one
# matching $DOMAIN_NAME, and SIP TLS would silently stay self-signed. So
# there's no separate domain prompt: this always targets $DOMAIN_NAME.
if [[ -z "$DOMAIN_NAME" ]]; then
log_info "No FQDN set — web admin stays on http://${PUBLIC_IP:-localhost}:8080 (nothing for Caddy to do)."
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
# 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
configure_caddy_for_service "Asterisk Web Admin" "8080" "asterisk" "$EXTRA_BLOCK"
# ── README ────────────────────────────────────────────────────────────────
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:
\`\`\`
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
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
@@ -622,13 +686,15 @@ users: Linphone or Zoiper cover the same ground.)
## TLS certificate
If Caddy is installed and already holds a Let's Encrypt cert for
\`DOMAIN_NAME\` (i.e. there's a Caddyfile site block for that exact hostname),
the container mounts Caddy's cert store read-only and the entrypoint syncs
it in automatically on every start — and re-checks every 12h so renewals
get picked up without a restart. No Caddyfile block for the domain, or no
Caddy at all, falls back to a self-signed cert (phones must be configured
to accept it).
Caddy is what actually talks to Let's Encrypt — Asterisk never does ACME
itself. The installer always reverse-proxies the web admin on the exact
same FQDN used for SIP (never a separate "admin" domain), specifically
because that's what makes Caddy hold a cert matching \`DOMAIN_NAME\`. The
container then mounts Caddy's cert store read-only and the entrypoint syncs
that cert in automatically on every start — and re-checks every 12h so
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
@@ -671,13 +737,13 @@ MD
echo " Web admin: http://${PUBLIC_IP:-localhost}:8080"
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 ""
if command -v cscli &>/dev/null; then
log_info "CrowdSec is already installed — rerun it to add SIP brute-force protection for this install:"
log_info " sudo ./setup.sh crowdsec"
else
log_info "Install CrowdSec (services/crowdsec.sh) for SIP brute-force/enumeration protection —"
log_info " it auto-detects this install and wires up the crowdsecurity/asterisk collection."
if [[ -n "$DOMAIN_NAME" ]] && [[ -d "$DOCKER_DIR/caddy" ]]; then
echo ""
log_info "If Caddy was just installed in this same run, it may still be obtaining the"
log_info "Let's Encrypt cert for ${DOMAIN_NAME} — Asterisk only checks for it at startup"
log_info "and then every 12h. If SIP TLS still shows self-signed after a couple of"
log_info "minutes, pick it up immediately with:"
log_info " docker compose -f $EA_DIR/docker-compose.yml restart asterisk"
fi
echo ""
}