From 5eddab9f9a1aa901c44419b7f60679e455c463da Mon Sep 17 00:00:00 2001 From: Claude Date: Sun, 19 Jul 2026 03:48:34 +0000 Subject: [PATCH] asterisk-do: auto-install Caddy/CrowdSec, fix confusing domain prompt MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 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. --- services/asterisk-do.sh | 120 +++++++++++++++++++++++++++++++--------- 1 file changed, 93 insertions(+), 27 deletions(-) diff --git a/services/asterisk-do.sh b/services/asterisk-do.sh index cdf0670..c6061c5 100755 --- a/services/asterisk-do.sh +++ b/services/asterisk-do.sh @@ -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 " .${SITE_DOMAIN:-example.com} A ${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 ".${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 +sip.yourdomain.com A \`\`\` 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 " 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 "" }