diff --git a/services/asterisk-do.sh b/services/asterisk-do.sh index 04e1c08..ff0e6d4 100755 --- a/services/asterisk-do.sh +++ b/services/asterisk-do.sh @@ -210,7 +210,8 @@ install_asterisk-do() { 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, 8081, 8088, 8089, 3478, 10000-20000, 49152-49252" + 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, , 8088, 8089, 3478, 10000-20000, 49152-49252" echo "[DRY-RUN] Would open 51820/udp (not 51821) if wg-easy was selected" 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)" @@ -487,6 +488,25 @@ EOF sed -i "/CADDY_VOLUME_PLACEHOLDER/d" docker-compose.yml fi + # ── Pick a free port for the web admin ───────────────────────────────────── + # Hardcoding a single number gets fragile fast once several services share + # a host — CrowdSec's own LAPI already collides with 8080 by default (its + # own upstream default, confirmed against its real config.yaml). Scan + # instead: start at 8081 and take the first port nothing is listening on, + # capped so a pathological box can't spin this forever. + local WEB_ADMIN_PORT_VAL=8081 + local _port_scan_limit=$((WEB_ADMIN_PORT_VAL + 100)) + while ss -tlnH "sport = :${WEB_ADMIN_PORT_VAL}" 2>/dev/null | grep -q . \ + && [[ "$WEB_ADMIN_PORT_VAL" -lt "$_port_scan_limit" ]]; do + WEB_ADMIN_PORT_VAL=$((WEB_ADMIN_PORT_VAL + 1)) + done + if [[ "$WEB_ADMIN_PORT_VAL" -ge "$_port_scan_limit" ]]; then + log_warning "No free port found in 8081-${_port_scan_limit} — falling back to 8081 anyway." + WEB_ADMIN_PORT_VAL=8081 + elif [[ "$WEB_ADMIN_PORT_VAL" != 8081 ]]; then + log_info "Port 8081 was already taken — web admin will use ${WEB_ADMIN_PORT_VAL} instead." + fi + # ── .env ────────────────────────────────────────────────────────────────── cat > .env << ENV # ── Domain ──────────────────────────────────────────────────── @@ -512,7 +532,11 @@ HAS_VLANS=n VLAN_SUBNETS= # ── Web admin ───────────────────────────────────────────────── -WEB_ADMIN_PORT=8081 +# Picked automatically at install time (first free port starting at 8081) — +# see WEB_ADMIN_PORT_VAL in services/asterisk-do.sh if this ever needs to +# change again; don't hand-edit without also updating Caddy's Caddyfile and +# both firewall layers to match. +WEB_ADMIN_PORT=${WEB_ADMIN_PORT_VAL} WEB_ADMIN_AUTH_DISABLED=false ENV chmod 600 .env @@ -523,7 +547,7 @@ ENV ufw allow 5060/udp ufw allow 5060/tcp ufw allow 5061/tcp - ufw allow 8081/tcp + ufw allow "${WEB_ADMIN_PORT_VAL}/tcp" ufw allow 8088/tcp ufw allow 8089/tcp ufw allow 3478/udp @@ -555,7 +579,7 @@ ENV "protocol:tcp,ports:5060,address:0.0.0.0/0,address:::/0" "protocol:udp,ports:5060,address:0.0.0.0/0,address:::/0" "protocol:tcp,ports:5061,address:0.0.0.0/0,address:::/0" - "protocol:tcp,ports:8081,address:0.0.0.0/0,address:::/0" + "protocol:tcp,ports:${WEB_ADMIN_PORT_VAL},address:0.0.0.0/0,address:::/0" "protocol:tcp,ports:8088-8089,address:0.0.0.0/0,address:::/0" "protocol:tcp,ports:3478,address:0.0.0.0/0,address:::/0" "protocol:udp,ports:3478,address:0.0.0.0/0,address:::/0" @@ -604,9 +628,9 @@ ENV # 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}:8081 (nothing for Caddy to do)." + log_info "No FQDN set — web admin stays on http://${PUBLIC_IP:-localhost}:${WEB_ADMIN_PORT_VAL} (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}:8081, SIP TLS stays self-signed." + log_info "Caddy not installed — web admin stays on http://${PUBLIC_IP:-localhost}:${WEB_ADMIN_PORT_VAL}, SIP TLS stays self-signed." else local EXTRA_BLOCK="" if [ -d "$DOCKER_DIR/authelia" ]; then @@ -665,7 +689,7 @@ ENV # Asterisk Web Admin ${DOMAIN_NAME} { - reverse_proxy localhost:8081 + reverse_proxy localhost:${WEB_ADMIN_PORT_VAL} header { Strict-Transport-Security "max-age=31536000; includeSubDomains; preload" @@ -847,7 +871,7 @@ plan for the admin panel. | 22 | TCP | SSH (keep this open or you're locked out) | | 5060 | UDP/TCP | SIP signalling (unencrypted) | | 5061 | TCP | SIP over TLS | -| 8081 | TCP | Easy Asterisk web admin | +| ${WEB_ADMIN_PORT_VAL} | TCP | Easy Asterisk web admin (auto-picked — see \`.env\`) | | 8088/8089 | TCP | Asterisk HTTP/WS (ARI/AMI) | | 3478 | UDP/TCP | TURN/STUN (coturn) | | 10000–20000 | UDP | RTP media streams | @@ -881,7 +905,7 @@ be added later by running \`sudo ./setup.sh \` from the repo. the public firewall; the web UI (51821) is deliberately **not** exposed — reach it via SSH tunnel: \`ssh -L 51821:localhost:51821 user@\`, then browse \`http://localhost:51821\`. A natural next step once it's - installed: restrict the web admin (8081) to the VPN subnet only, on both + installed: restrict the web admin (${WEB_ADMIN_PORT_VAL}) to the VPN subnet only, on both firewall layers, so reconfiguring the PBX requires being on the VPN — done manually, not automatically, since a firewall mistake there can lock you out. @@ -965,7 +989,7 @@ accept it manually). ## Web admin -Access the Easy Asterisk web interface at http://:8081 +Access the Easy Asterisk web interface at http://:${WEB_ADMIN_PORT_VAL} or via your configured reverse-proxy domain. ## Data directories (all inside ~/docker/asterisk-do/, included in backup) @@ -1001,7 +1025,7 @@ MD fi echo " Public IP: ${PUBLIC_IP:-unknown}" echo " SIP port: 5061 (TLS) / 5060 (UDP)" - echo " Web admin: http://${PUBLIC_IP:-localhost}:8081" + echo " Web admin: http://${PUBLIC_IP:-localhost}:${WEB_ADMIN_PORT_VAL}" echo " Manage: docker compose -f $EA_DIR/docker-compose.yml " echo " Script: docker exec -it easy-asterisk-do easy-asterisk --help" if [[ -n "$DOMAIN_NAME" ]] && [[ -d "$DOCKER_DIR/caddy" ]]; then diff --git a/services/asterisk.sh b/services/asterisk.sh index 3e06463..4e6514f 100644 --- a/services/asterisk.sh +++ b/services/asterisk.sh @@ -204,7 +204,8 @@ install_asterisk() { if [ "$DRY_RUN" = true ]; then 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 open UFW ports: 5060, 5061, 8081, 8088, 8089, 3478, 10000-20000, 49152-49252" + 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, , 8088, 8089, 3478, 10000-20000, 49152-49252" return 0 fi @@ -359,6 +360,25 @@ EOF sed -i "/CADDY_VOLUME_PLACEHOLDER/d" docker-compose.yml fi + # ── Pick a free port for the web admin ───────────────────────────────────── + # Hardcoding a single number gets fragile fast once several services share + # a host — CrowdSec's own LAPI already collides with 8080 by default (its + # own upstream default, confirmed against its real config.yaml). Scan + # instead: start at 8081 and take the first port nothing is listening on, + # capped so a pathological box can't spin this forever. + local WEB_ADMIN_PORT_VAL=8081 + local _port_scan_limit=$((WEB_ADMIN_PORT_VAL + 100)) + while ss -tlnH "sport = :${WEB_ADMIN_PORT_VAL}" 2>/dev/null | grep -q . \ + && [[ "$WEB_ADMIN_PORT_VAL" -lt "$_port_scan_limit" ]]; do + WEB_ADMIN_PORT_VAL=$((WEB_ADMIN_PORT_VAL + 1)) + done + if [[ "$WEB_ADMIN_PORT_VAL" -ge "$_port_scan_limit" ]]; then + log_warning "No free port found in 8081-${_port_scan_limit} — falling back to 8081 anyway." + WEB_ADMIN_PORT_VAL=8081 + elif [[ "$WEB_ADMIN_PORT_VAL" != 8081 ]]; then + log_info "Port 8081 was already taken — web admin will use ${WEB_ADMIN_PORT_VAL} instead." + fi + # ── .env ────────────────────────────────────────────────────────────────── cat > .env << ENV # ── Domain ──────────────────────────────────────────────────── @@ -383,7 +403,11 @@ HAS_VLANS=${HAS_VLANS_VAL} VLAN_SUBNETS=${VLAN_SUBNETS_VAL} # ── Web admin ───────────────────────────────────────────────── -WEB_ADMIN_PORT=8081 +# Picked automatically at install time (first free port starting at 8081) — +# see WEB_ADMIN_PORT_VAL in services/asterisk.sh if this ever needs to +# change again; don't hand-edit without also updating Caddy's Caddyfile and +# any firewall rules to match. +WEB_ADMIN_PORT=${WEB_ADMIN_PORT_VAL} WEB_ADMIN_AUTH_DISABLED=false ENV chmod 600 .env @@ -394,7 +418,7 @@ ENV ufw allow 5060/udp ufw allow 5060/tcp ufw allow 5061/tcp - ufw allow 8081/tcp + ufw allow "${WEB_ADMIN_PORT_VAL}/tcp" ufw allow 8088/tcp ufw allow 8089/tcp ufw allow 3478/udp @@ -415,7 +439,7 @@ ENV sed -i "s/^WEB_ADMIN_AUTH_DISABLED=.*/WEB_ADMIN_AUTH_DISABLED=true/" .env fi fi - configure_caddy_for_service "Asterisk Web Admin" "8081" "asterisk" "$EXTRA_BLOCK" + configure_caddy_for_service "Asterisk Web Admin" "${WEB_ADMIN_PORT_VAL}" "asterisk" "$EXTRA_BLOCK" # ── README ──────────────────────────────────────────────────────────────── write_readme "$EA_DIR" << 'MD' @@ -484,7 +508,10 @@ to accept it). ## Web admin Access the Easy Asterisk web interface at http://:8081 -or via your configured reverse-proxy domain. +or via your configured reverse-proxy domain. (8081 is the default; if that +port was already taken by something else on this box, the installer picked +the next free one instead — check WEB_ADMIN_PORT in .env for the actual +value.) ## Data directories (all inside ~/docker/asterisk/, included in backup) @@ -502,7 +529,7 @@ or via your configured reverse-proxy domain. |---------------|----------|----------------------------------| | 5060 | UDP/TCP | SIP signalling (unencrypted) | | 5061 | TCP | SIP over TLS | -| 8081 | TCP | Easy Asterisk web admin | +| 8081 | TCP | Easy Asterisk web admin (default — see .env) | | 8088/8089 | TCP | Asterisk HTTP/WS (ARI/AMI) | | 3478 | UDP/TCP | TURN/STUN (coturn) | | 10000–20000 | UDP | RTP media streams | @@ -530,7 +557,7 @@ MD echo " TURN server: (none — LAN/VPN only)" fi echo " SIP port: 5061 (TLS) / 5060 (UDP)" - echo " Web admin: http://$(hostname -I 2>/dev/null | awk '{print $1}' || echo localhost):8081" + echo " Web admin: http://$(hostname -I 2>/dev/null | awk '{print $1}' || echo localhost):${WEB_ADMIN_PORT_VAL}" echo " Manage: docker compose -f $EA_DIR/docker-compose.yml " echo " Script: docker exec -it easy-asterisk easy-asterisk --help" echo ""