Add update-in-place mode to asterisk and asterisk-do installers
Re-running either installer on an existing install used to re-ask every prompt (domain, extras, firewall, Authelia) just to pick up a script fix like the exports mount. Both now detect an existing docker-compose.yml + .env and offer to update in place instead: only vendor files and docker-compose.yml are refreshed and the stack is rebuilt, leaving .env, firewall rules, and Caddy/Authelia config untouched. The vendor-copy and docker-compose.yml generation blocks (previously inline and duplicated between what would have been two near-identical code paths) are factored into per-file helper functions (_asterisk_do_refresh_vendor_files/_asterisk_do_write_compose and _asterisk_refresh_vendor_files/_asterisk_write_compose) so fresh installs and updates share one copy of the logic instead of drifting apart — the same problem that caused the /root export path and the vpn-diagnostics.sh COPY bug to slip through unevenly between the two services in the first place. Names are per-file since setup.sh sources every services/*.sh into one process.
This commit is contained in:
+163
-112
@@ -203,6 +203,123 @@ fi
|
||||
|
||||
register_service asterisk-do homelab "Easy Asterisk PBX + coturn, tuned for a public DigitalOcean droplet" 5061
|
||||
|
||||
# ── Shared: vendor file refresh ────────────────────────────────────────────
|
||||
# Called from both a fresh install and an "update in place" run, so a single
|
||||
# copy of this logic stays current for both instead of drifting apart. Must
|
||||
# be called with $PWD already at $EA_DIR.
|
||||
_asterisk_do_refresh_vendor_files() {
|
||||
mkdir -p docker scripts
|
||||
|
||||
local _SELF_DIR_LOCAL
|
||||
_SELF_DIR_LOCAL="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
|
||||
local VENDOR_DIR="$_SELF_DIR_LOCAL/../vendor/easy-asterisk"
|
||||
|
||||
if [[ -d "$VENDOR_DIR" ]]; then
|
||||
log_info "Copying vendor files from $VENDOR_DIR ..."
|
||||
cp "$VENDOR_DIR/Dockerfile" ./Dockerfile
|
||||
cp "$VENDOR_DIR/docker/entrypoint.sh" ./docker/entrypoint.sh
|
||||
cp "$VENDOR_DIR/docker/coturn-entrypoint.sh" ./docker/coturn-entrypoint.sh
|
||||
cp "$VENDOR_DIR/easy-asterisk-v0.10.0.sh" ./easy-asterisk.sh
|
||||
cp "$VENDOR_DIR/easy-asterisk-v0.10.0.sh" ./easy-asterisk-v0.10.0.sh
|
||||
cp "$VENDOR_DIR/scripts/vpn-diagnostics.sh" ./scripts/vpn-diagnostics.sh
|
||||
cp "$VENDOR_DIR/scripts/dns-whitelist.sh" ./scripts/dns-whitelist.sh
|
||||
else
|
||||
log_info "Vendor directory not found — downloading from GitHub ..."
|
||||
local GH_RAW="https://raw.githubusercontent.com/DeadDork/easy-asterisk/main"
|
||||
curl -fsSL "$GH_RAW/Dockerfile" -o ./Dockerfile
|
||||
curl -fsSL "$GH_RAW/docker/entrypoint.sh" -o ./docker/entrypoint.sh
|
||||
curl -fsSL "$GH_RAW/docker/coturn-entrypoint.sh" -o ./docker/coturn-entrypoint.sh
|
||||
curl -fsSL "$GH_RAW/easy-asterisk-v0.10.0.sh" -o ./easy-asterisk.sh
|
||||
curl -fsSL "$GH_RAW/scripts/vpn-diagnostics.sh" -o ./scripts/vpn-diagnostics.sh
|
||||
curl -fsSL "$GH_RAW/scripts/dns-whitelist.sh" -o ./scripts/dns-whitelist.sh
|
||||
cp ./easy-asterisk.sh ./easy-asterisk-v0.10.0.sh
|
||||
fi
|
||||
|
||||
chmod 755 ./easy-asterisk.sh ./easy-asterisk-v0.10.0.sh \
|
||||
./docker/entrypoint.sh ./docker/coturn-entrypoint.sh \
|
||||
./scripts/vpn-diagnostics.sh ./scripts/dns-whitelist.sh
|
||||
|
||||
# Persist security-level logging to a file — vendor's logger.conf only
|
||||
# sends the "security" level (auth failures, SIP brute-force attempts) to
|
||||
# the console (Docker stdout), not a file CrowdSec/fail2ban can tail.
|
||||
if grep -q '^console => notice,warning,error,security$' ./docker/entrypoint.sh; then
|
||||
sed -i '/^console => notice,warning,error,security$/a full => notice,warning,error,security' \
|
||||
./docker/entrypoint.sh
|
||||
else
|
||||
log_warning "entrypoint.sh logger.conf template changed upstream — security events won't be logged to a file. Update the sed patch in this installer."
|
||||
fi
|
||||
}
|
||||
|
||||
# ── Shared: docker-compose.yml ─────────────────────────────────────────────
|
||||
# Same reasoning as above — one copy of the template used by both fresh
|
||||
# installs and updates. Must be called with $PWD already at $EA_DIR.
|
||||
_asterisk_do_write_compose() {
|
||||
cat > docker-compose.yml << 'EOF'
|
||||
name: asterisk-do
|
||||
|
||||
services:
|
||||
asterisk:
|
||||
build: .
|
||||
container_name: easy-asterisk-do
|
||||
network_mode: host
|
||||
depends_on:
|
||||
coturn:
|
||||
condition: service_started
|
||||
volumes:
|
||||
- ./config/asterisk:/etc/asterisk
|
||||
- ./config/easy-asterisk:/etc/easy-asterisk
|
||||
- ./logs:/var/log/asterisk
|
||||
- ./spool:/var/spool/asterisk
|
||||
- ./lib:/var/lib/asterisk
|
||||
- ./easy-asterisk.sh:/usr/local/bin/easy-asterisk:ro
|
||||
- ./exports:/root
|
||||
CADDY_VOLUME_PLACEHOLDER
|
||||
env_file: .env
|
||||
restart: unless-stopped
|
||||
healthcheck:
|
||||
test: ["CMD", "asterisk", "-rx", "core show version"]
|
||||
interval: 30s
|
||||
timeout: 5s
|
||||
retries: 3
|
||||
|
||||
coturn:
|
||||
image: coturn/coturn:latest
|
||||
container_name: easy-asterisk-do-coturn
|
||||
network_mode: host
|
||||
user: root
|
||||
entrypoint: ["/coturn-entrypoint.sh"]
|
||||
volumes:
|
||||
- ./docker/coturn-entrypoint.sh:/coturn-entrypoint.sh:ro
|
||||
env_file: .env
|
||||
command:
|
||||
- -n
|
||||
- --listening-port=${TURN_PORT:-3478}
|
||||
- --listening-ip=0.0.0.0
|
||||
- --fingerprint
|
||||
- --lt-cred-mech
|
||||
- --user=${TURN_USERNAME:-easyasterisk}:${TURN_PASSWORD}
|
||||
- --realm=${DOMAIN_NAME:-localhost}
|
||||
- --min-port=49152
|
||||
- --max-port=49252
|
||||
- --no-tls
|
||||
- --no-dtls
|
||||
- --no-cli
|
||||
- --no-multicast-peers
|
||||
- --log-file=stdout
|
||||
restart: unless-stopped
|
||||
|
||||
EOF
|
||||
|
||||
# Share Caddy's cert store (read-only) so the entrypoint can auto-sync a
|
||||
# real Let's Encrypt cert for DOMAIN_NAME instead of falling back to
|
||||
# self-signed. No-op if Caddy isn't installed on this box.
|
||||
if [[ -d "$DOCKER_DIR/caddy/data" ]]; then
|
||||
sed -i "s#CADDY_VOLUME_PLACEHOLDER# - ${DOCKER_DIR}/caddy/data:/caddy-data:ro#" docker-compose.yml
|
||||
else
|
||||
sed -i "/CADDY_VOLUME_PLACEHOLDER/d" docker-compose.yml
|
||||
fi
|
||||
}
|
||||
|
||||
install_asterisk-do() {
|
||||
require_docker || return 1
|
||||
log_info "Installing Easy Asterisk PBX + coturn (DigitalOcean droplet edition)..."
|
||||
@@ -224,9 +341,53 @@ install_asterisk-do() {
|
||||
echo "[DRY-RUN] Would offer local OR remote Authelia to protect the web admin"
|
||||
echo "[DRY-RUN] Would offer to install CrowdSec if not already present (full repo only)"
|
||||
echo "[DRY-RUN] Would offer to run base setup first if not already done (full repo only)"
|
||||
echo "[DRY-RUN] Would offer 'update in place' instead of a fresh install if $EA_DIR already exists"
|
||||
return 0
|
||||
fi
|
||||
|
||||
# ── Existing install? Offer update-in-place instead of a full reinstall ───
|
||||
# A fresh install re-runs every prompt (domain, extras, DO firewall,
|
||||
# Authelia). An update only refreshes vendor files + docker-compose.yml —
|
||||
# picking up fixes like this one — and rebuilds, without touching .env,
|
||||
# UFW, the Cloud Firewall, or the Caddy/Authelia config already in place.
|
||||
if [[ -f "$EA_DIR/docker-compose.yml" && -f "$EA_DIR/.env" ]]; then
|
||||
echo ""
|
||||
log_info "Existing install found at $EA_DIR."
|
||||
local UPDATE_INPLACE=""
|
||||
prompt_yn "Update in place (refresh vendor files + docker-compose.yml, keep your existing domain/firewall/Authelia settings) instead of a full fresh reinstall? (y/n):" "y" UPDATE_INPLACE
|
||||
if [[ "$UPDATE_INPLACE" =~ ^[Yy]$ ]]; then
|
||||
mkdir -p "$EA_DIR/config/asterisk" "$EA_DIR/config/easy-asterisk" \
|
||||
"$EA_DIR/logs" "$EA_DIR/spool" "$EA_DIR/lib" "$EA_DIR/exports"
|
||||
ensure_docker_dir_ownership "$EA_DIR"
|
||||
cd "$EA_DIR" || return 1
|
||||
|
||||
_asterisk_do_refresh_vendor_files
|
||||
_asterisk_do_write_compose
|
||||
|
||||
log_info "Rebuilding and restarting containers..."
|
||||
if docker compose up -d --build --force-recreate; then
|
||||
log_success "Update complete — vendor files and docker-compose.yml refreshed."
|
||||
else
|
||||
log_warning "docker compose up failed — check: docker compose -f $EA_DIR/docker-compose.yml logs"
|
||||
fi
|
||||
|
||||
local _EXISTING_DOMAIN _EXISTING_PORT
|
||||
_EXISTING_DOMAIN="$(grep -E '^DOMAIN_NAME=' .env | cut -d= -f2-)"
|
||||
_EXISTING_PORT="$(grep -E '^WEB_ADMIN_PORT=' .env | cut -d= -f2-)"
|
||||
echo ""
|
||||
log_success "Existing .env, UFW rules, Cloud Firewall, and Caddy/Authelia config were left untouched."
|
||||
if [[ -n "$_EXISTING_DOMAIN" ]]; then
|
||||
echo " Web admin: https://${_EXISTING_DOMAIN}/"
|
||||
else
|
||||
echo " Web admin: http://<droplet-ip>:${_EXISTING_PORT:-8081}"
|
||||
fi
|
||||
echo " Logs: docker compose -f $EA_DIR/docker-compose.yml logs -f"
|
||||
echo ""
|
||||
return 0
|
||||
fi
|
||||
log_info "Proceeding with a full fresh reinstall — existing config will be reused where prompts match, everything else re-asked."
|
||||
fi
|
||||
|
||||
# ── Bring in base first, if this is a genuinely fresh box ─────────────────
|
||||
# Naming a service directly (sudo ./setup.sh asterisk-do) skips setup.sh's
|
||||
# own first-run base step — essential packages, SSH key import, disabling
|
||||
@@ -341,53 +502,7 @@ install_asterisk-do() {
|
||||
ensure_docker_dir_ownership "$EA_DIR"
|
||||
cd "$EA_DIR" || return 1
|
||||
|
||||
mkdir -p docker
|
||||
|
||||
# ── Vendor files (shared with services/asterisk.sh — no duplication) ──────
|
||||
local _SELF_DIR_LOCAL
|
||||
_SELF_DIR_LOCAL="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
|
||||
local VENDOR_DIR="$_SELF_DIR_LOCAL/../vendor/easy-asterisk"
|
||||
|
||||
mkdir -p scripts
|
||||
|
||||
if [[ -d "$VENDOR_DIR" ]]; then
|
||||
log_info "Copying vendor files from $VENDOR_DIR ..."
|
||||
cp "$VENDOR_DIR/Dockerfile" ./Dockerfile
|
||||
cp "$VENDOR_DIR/docker/entrypoint.sh" ./docker/entrypoint.sh
|
||||
cp "$VENDOR_DIR/docker/coturn-entrypoint.sh" ./docker/coturn-entrypoint.sh
|
||||
cp "$VENDOR_DIR/easy-asterisk-v0.10.0.sh" ./easy-asterisk.sh
|
||||
cp "$VENDOR_DIR/easy-asterisk-v0.10.0.sh" ./easy-asterisk-v0.10.0.sh
|
||||
cp "$VENDOR_DIR/scripts/vpn-diagnostics.sh" ./scripts/vpn-diagnostics.sh
|
||||
cp "$VENDOR_DIR/scripts/dns-whitelist.sh" ./scripts/dns-whitelist.sh
|
||||
else
|
||||
log_info "Vendor directory not found — downloading from GitHub ..."
|
||||
local GH_RAW="https://raw.githubusercontent.com/DeadDork/easy-asterisk/main"
|
||||
curl -fsSL "$GH_RAW/Dockerfile" -o ./Dockerfile
|
||||
curl -fsSL "$GH_RAW/docker/entrypoint.sh" -o ./docker/entrypoint.sh
|
||||
curl -fsSL "$GH_RAW/docker/coturn-entrypoint.sh" -o ./docker/coturn-entrypoint.sh
|
||||
curl -fsSL "$GH_RAW/easy-asterisk-v0.10.0.sh" -o ./easy-asterisk.sh
|
||||
curl -fsSL "$GH_RAW/scripts/vpn-diagnostics.sh" -o ./scripts/vpn-diagnostics.sh
|
||||
curl -fsSL "$GH_RAW/scripts/dns-whitelist.sh" -o ./scripts/dns-whitelist.sh
|
||||
cp ./easy-asterisk.sh ./easy-asterisk-v0.10.0.sh
|
||||
fi
|
||||
|
||||
chmod 755 ./easy-asterisk.sh ./easy-asterisk-v0.10.0.sh \
|
||||
./docker/entrypoint.sh ./docker/coturn-entrypoint.sh \
|
||||
./scripts/vpn-diagnostics.sh ./scripts/dns-whitelist.sh
|
||||
|
||||
# ── Persist security-level logging to a file ──────────────────────────────
|
||||
# Vendor's logger.conf only sends the "security" level (auth failures, SIP
|
||||
# brute-force attempts) to the console — that's Docker's stdout, not a file
|
||||
# CrowdSec/fail2ban can tail. Patch our copy of entrypoint.sh (not the
|
||||
# shared vendor/ source) so it also writes those events to
|
||||
# /var/log/asterisk/full, which is bind-mounted to $EA_DIR/logs/full — a
|
||||
# host path services/crowdsec.sh can point its Asterisk acquisition at.
|
||||
if grep -q '^console => notice,warning,error,security$' ./docker/entrypoint.sh; then
|
||||
sed -i '/^console => notice,warning,error,security$/a full => notice,warning,error,security' \
|
||||
./docker/entrypoint.sh
|
||||
else
|
||||
log_warning "entrypoint.sh logger.conf template changed upstream — security events won't be logged to a file. Update the sed patch in this installer."
|
||||
fi
|
||||
_asterisk_do_refresh_vendor_files
|
||||
|
||||
# ── DigitalOcean droplet detection ────────────────────────────────────────
|
||||
# A droplet's own public IP/ID are readable, unauthenticated, from the
|
||||
@@ -429,71 +544,7 @@ install_asterisk-do() {
|
||||
# a usable address (the FQDN if set, otherwise the droplet's public IP).
|
||||
local TURN_SERVER_VAL="${DOMAIN_NAME:-$PUBLIC_IP}:3478"
|
||||
|
||||
# ── docker-compose.yml ────────────────────────────────────────────────────
|
||||
cat > docker-compose.yml << 'EOF'
|
||||
name: asterisk-do
|
||||
|
||||
services:
|
||||
asterisk:
|
||||
build: .
|
||||
container_name: easy-asterisk-do
|
||||
network_mode: host
|
||||
depends_on:
|
||||
coturn:
|
||||
condition: service_started
|
||||
volumes:
|
||||
- ./config/asterisk:/etc/asterisk
|
||||
- ./config/easy-asterisk:/etc/easy-asterisk
|
||||
- ./logs:/var/log/asterisk
|
||||
- ./spool:/var/spool/asterisk
|
||||
- ./lib:/var/lib/asterisk
|
||||
- ./easy-asterisk.sh:/usr/local/bin/easy-asterisk:ro
|
||||
- ./exports:/root
|
||||
CADDY_VOLUME_PLACEHOLDER
|
||||
env_file: .env
|
||||
restart: unless-stopped
|
||||
healthcheck:
|
||||
test: ["CMD", "asterisk", "-rx", "core show version"]
|
||||
interval: 30s
|
||||
timeout: 5s
|
||||
retries: 3
|
||||
|
||||
coturn:
|
||||
image: coturn/coturn:latest
|
||||
container_name: easy-asterisk-do-coturn
|
||||
network_mode: host
|
||||
user: root
|
||||
entrypoint: ["/coturn-entrypoint.sh"]
|
||||
volumes:
|
||||
- ./docker/coturn-entrypoint.sh:/coturn-entrypoint.sh:ro
|
||||
env_file: .env
|
||||
command:
|
||||
- -n
|
||||
- --listening-port=${TURN_PORT:-3478}
|
||||
- --listening-ip=0.0.0.0
|
||||
- --fingerprint
|
||||
- --lt-cred-mech
|
||||
- --user=${TURN_USERNAME:-easyasterisk}:${TURN_PASSWORD}
|
||||
- --realm=${DOMAIN_NAME:-localhost}
|
||||
- --min-port=49152
|
||||
- --max-port=49252
|
||||
- --no-tls
|
||||
- --no-dtls
|
||||
- --no-cli
|
||||
- --no-multicast-peers
|
||||
- --log-file=stdout
|
||||
restart: unless-stopped
|
||||
|
||||
EOF
|
||||
|
||||
# Share Caddy's cert store (read-only) so the entrypoint can auto-sync a
|
||||
# real Let's Encrypt cert for DOMAIN_NAME instead of falling back to
|
||||
# self-signed. No-op if Caddy isn't installed on this box.
|
||||
if [[ -d "$DOCKER_DIR/caddy/data" ]]; then
|
||||
sed -i "s#CADDY_VOLUME_PLACEHOLDER# - ${DOCKER_DIR}/caddy/data:/caddy-data:ro#" docker-compose.yml
|
||||
else
|
||||
sed -i "/CADDY_VOLUME_PLACEHOLDER/d" docker-compose.yml
|
||||
fi
|
||||
_asterisk_do_write_compose
|
||||
|
||||
# ── Pick a free port for the web admin ─────────────────────────────────────
|
||||
# Hardcoding a single number gets fragile fast once several services share
|
||||
|
||||
+128
-71
@@ -201,35 +201,17 @@ fi
|
||||
|
||||
register_service asterisk homelab "Easy Asterisk PBX + coturn TURN server (home intercom/VoIP)" 5061
|
||||
|
||||
install_asterisk() {
|
||||
require_docker || return 1
|
||||
log_info "Installing Easy Asterisk PBX + coturn..."
|
||||
# ── Shared: vendor file refresh ────────────────────────────────────────────
|
||||
# Called from both a fresh install and an "update in place" run, so a single
|
||||
# copy of this logic stays current for both instead of drifting apart. Must
|
||||
# be called with $PWD already at $EA_DIR.
|
||||
_asterisk_refresh_vendor_files() {
|
||||
mkdir -p docker scripts
|
||||
|
||||
local EA_DIR="$DOCKER_DIR/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 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, <web admin port>, 8088, 8089, 3478, 10000-20000, 49152-49252"
|
||||
return 0
|
||||
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" "$EA_DIR/exports"
|
||||
ensure_docker_dir_ownership "$EA_DIR"
|
||||
cd "$EA_DIR" || return 1
|
||||
|
||||
mkdir -p docker
|
||||
|
||||
# ── Vendor files ──────────────────────────────────────────────────────────
|
||||
local _SELF_DIR_LOCAL
|
||||
_SELF_DIR_LOCAL="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
|
||||
local VENDOR_DIR="$_SELF_DIR_LOCAL/../vendor/easy-asterisk"
|
||||
|
||||
mkdir -p scripts
|
||||
|
||||
if [[ -d "$VENDOR_DIR" ]]; then
|
||||
log_info "Copying vendor files from $VENDOR_DIR ..."
|
||||
cp "$VENDOR_DIR/Dockerfile" ./Dockerfile
|
||||
@@ -254,54 +236,14 @@ install_asterisk() {
|
||||
chmod 755 ./easy-asterisk.sh ./easy-asterisk-v0.10.0.sh \
|
||||
./docker/entrypoint.sh ./docker/coturn-entrypoint.sh \
|
||||
./scripts/vpn-diagnostics.sh ./scripts/dns-whitelist.sh
|
||||
}
|
||||
|
||||
# ── Networking mode ───────────────────────────────────────────────────────
|
||||
echo ""
|
||||
echo " Networking mode:"
|
||||
echo " 1) FQDN (recommended) — TLS + TURN relay, every phone connects the"
|
||||
echo " same way regardless of LAN/VLAN/remote"
|
||||
echo " 2) LAN-only — no domain, self-signed cert, local network/VPN only"
|
||||
local HA_NETMODE=""
|
||||
prompt_text "Choose [1]:" "1" HA_NETMODE
|
||||
|
||||
local DOMAIN_NAME=""
|
||||
if [[ "$HA_NETMODE" != "2" ]]; then
|
||||
prompt_text "FQDN (e.g. asterisk.${SITE_DOMAIN:-example.com}) [blank=fall back to LAN-only]:" "" DOMAIN_NAME
|
||||
[[ -z "$DOMAIN_NAME" ]] && log_warning "No FQDN entered — proceeding in LAN-only mode."
|
||||
fi
|
||||
|
||||
# ── Local networks / VLANs ────────────────────────────────────────────────
|
||||
# Feeds HAS_VLANS/VLAN_SUBNETS into .env, which the entrypoint reads to add
|
||||
# extra local_net= entries in pjsip.conf so phones on those subnets get
|
||||
# correct NAT/SDP handling (this is what fixes the "no sound" symptom for
|
||||
# devices on a VLAN the server isn't itself attached to).
|
||||
echo ""
|
||||
echo " Detecting networks this host can see..."
|
||||
local DETECTED_NETS=""
|
||||
DETECTED_NETS="$(ip -o -f inet addr show scope global 2>/dev/null \
|
||||
| awk '{print $2, $4}' \
|
||||
| grep -Ev '^(docker|br-|veth|tun|tap|wg)' \
|
||||
| awk '{ split($2,a,"/"); split(a[1],o,"."); print o[1]"."o[2]"."o[3]".0/"a[2] }' \
|
||||
| sort -u)"
|
||||
if [[ -n "$DETECTED_NETS" ]]; then
|
||||
echo " This host is directly attached to:"
|
||||
echo "$DETECTED_NETS" | sed 's/^/ /'
|
||||
fi
|
||||
echo " Phones on OTHER VLANs (this server usually can't see those directly)"
|
||||
echo " still need to be listed here so their media is treated as local/trusted."
|
||||
local VLAN_SUBNETS_VAL=""
|
||||
prompt_text "VLAN/VPN subnets, space-separated CIDRs [blank=none]:" "" VLAN_SUBNETS_VAL
|
||||
local HAS_VLANS_VAL="n"
|
||||
[[ -n "$VLAN_SUBNETS_VAL" ]] && HAS_VLANS_VAL="y"
|
||||
|
||||
# ── Secrets ───────────────────────────────────────────────────────────────
|
||||
local TURN_PASSWORD
|
||||
TURN_PASSWORD="$(generate_password 24)"
|
||||
|
||||
local TURN_SERVER_VAL=""
|
||||
[[ -n "$DOMAIN_NAME" ]] && TURN_SERVER_VAL="${DOMAIN_NAME}:3478"
|
||||
|
||||
# ── docker-compose.yml ────────────────────────────────────────────────────
|
||||
# ── Shared: docker-compose.yml ─────────────────────────────────────────────
|
||||
# Same reasoning as above — one copy of the template used by both fresh
|
||||
# installs and updates. Must be called with $PWD already at $EA_DIR.
|
||||
# HAS_VLANS_VAL/VLAN_SUBNETS_VAL aren't referenced here — they live only in
|
||||
# .env, which the entrypoint reads at container start.
|
||||
_asterisk_write_compose() {
|
||||
cat > docker-compose.yml << 'EOF'
|
||||
name: asterisk
|
||||
|
||||
@@ -366,6 +308,121 @@ EOF
|
||||
else
|
||||
sed -i "/CADDY_VOLUME_PLACEHOLDER/d" docker-compose.yml
|
||||
fi
|
||||
}
|
||||
|
||||
install_asterisk() {
|
||||
require_docker || return 1
|
||||
log_info "Installing Easy Asterisk PBX + coturn..."
|
||||
|
||||
local EA_DIR="$DOCKER_DIR/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 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, <web admin port>, 8088, 8089, 3478, 10000-20000, 49152-49252"
|
||||
echo "[DRY-RUN] Would offer 'update in place' instead of a fresh install if $EA_DIR already exists"
|
||||
return 0
|
||||
fi
|
||||
|
||||
# ── Existing install? Offer update-in-place instead of a full reinstall ───
|
||||
# A fresh install re-runs every prompt (networking mode, domain, VLANs,
|
||||
# Authelia). An update only refreshes vendor files + docker-compose.yml —
|
||||
# picking up fixes like this one — and rebuilds, without touching .env,
|
||||
# UFW, or the Caddy/Authelia config already in place.
|
||||
if [[ -f "$EA_DIR/docker-compose.yml" && -f "$EA_DIR/.env" ]]; then
|
||||
echo ""
|
||||
log_info "Existing install found at $EA_DIR."
|
||||
local UPDATE_INPLACE=""
|
||||
prompt_yn "Update in place (refresh vendor files + docker-compose.yml, keep your existing domain/VLAN/Authelia settings) instead of a full fresh reinstall? (y/n):" "y" UPDATE_INPLACE
|
||||
if [[ "$UPDATE_INPLACE" =~ ^[Yy]$ ]]; then
|
||||
mkdir -p "$EA_DIR/config/asterisk" "$EA_DIR/config/easy-asterisk" \
|
||||
"$EA_DIR/logs" "$EA_DIR/spool" "$EA_DIR/lib" "$EA_DIR/exports"
|
||||
ensure_docker_dir_ownership "$EA_DIR"
|
||||
cd "$EA_DIR" || return 1
|
||||
|
||||
_asterisk_refresh_vendor_files
|
||||
_asterisk_write_compose
|
||||
|
||||
log_info "Rebuilding and restarting containers..."
|
||||
if docker compose up -d --build --force-recreate; then
|
||||
log_success "Update complete — vendor files and docker-compose.yml refreshed."
|
||||
else
|
||||
log_warning "docker compose up failed — check: docker compose -f $EA_DIR/docker-compose.yml logs"
|
||||
fi
|
||||
|
||||
local _EXISTING_DOMAIN _EXISTING_PORT
|
||||
_EXISTING_DOMAIN="$(grep -E '^DOMAIN_NAME=' .env | cut -d= -f2-)"
|
||||
_EXISTING_PORT="$(grep -E '^WEB_ADMIN_PORT=' .env | cut -d= -f2-)"
|
||||
echo ""
|
||||
log_success "Existing .env, UFW rules, and Caddy/Authelia config were left untouched."
|
||||
if [[ -n "$_EXISTING_DOMAIN" ]]; then
|
||||
echo " Web admin: https://${_EXISTING_DOMAIN}/"
|
||||
else
|
||||
echo " Web admin: http://$(hostname -I 2>/dev/null | awk '{print $1}' || echo localhost):${_EXISTING_PORT:-8081}"
|
||||
fi
|
||||
echo " Logs: docker compose -f $EA_DIR/docker-compose.yml logs -f"
|
||||
echo ""
|
||||
return 0
|
||||
fi
|
||||
log_info "Proceeding with a full fresh reinstall — existing config will be reused where prompts match, everything else re-asked."
|
||||
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" "$EA_DIR/exports"
|
||||
ensure_docker_dir_ownership "$EA_DIR"
|
||||
cd "$EA_DIR" || return 1
|
||||
|
||||
_asterisk_refresh_vendor_files
|
||||
|
||||
# ── Networking mode ───────────────────────────────────────────────────────
|
||||
echo ""
|
||||
echo " Networking mode:"
|
||||
echo " 1) FQDN (recommended) — TLS + TURN relay, every phone connects the"
|
||||
echo " same way regardless of LAN/VLAN/remote"
|
||||
echo " 2) LAN-only — no domain, self-signed cert, local network/VPN only"
|
||||
local HA_NETMODE=""
|
||||
prompt_text "Choose [1]:" "1" HA_NETMODE
|
||||
|
||||
local DOMAIN_NAME=""
|
||||
if [[ "$HA_NETMODE" != "2" ]]; then
|
||||
prompt_text "FQDN (e.g. asterisk.${SITE_DOMAIN:-example.com}) [blank=fall back to LAN-only]:" "" DOMAIN_NAME
|
||||
[[ -z "$DOMAIN_NAME" ]] && log_warning "No FQDN entered — proceeding in LAN-only mode."
|
||||
fi
|
||||
|
||||
# ── Local networks / VLANs ────────────────────────────────────────────────
|
||||
# Feeds HAS_VLANS/VLAN_SUBNETS into .env, which the entrypoint reads to add
|
||||
# extra local_net= entries in pjsip.conf so phones on those subnets get
|
||||
# correct NAT/SDP handling (this is what fixes the "no sound" symptom for
|
||||
# devices on a VLAN the server isn't itself attached to).
|
||||
echo ""
|
||||
echo " Detecting networks this host can see..."
|
||||
local DETECTED_NETS=""
|
||||
DETECTED_NETS="$(ip -o -f inet addr show scope global 2>/dev/null \
|
||||
| awk '{print $2, $4}' \
|
||||
| grep -Ev '^(docker|br-|veth|tun|tap|wg)' \
|
||||
| awk '{ split($2,a,"/"); split(a[1],o,"."); print o[1]"."o[2]"."o[3]".0/"a[2] }' \
|
||||
| sort -u)"
|
||||
if [[ -n "$DETECTED_NETS" ]]; then
|
||||
echo " This host is directly attached to:"
|
||||
echo "$DETECTED_NETS" | sed 's/^/ /'
|
||||
fi
|
||||
echo " Phones on OTHER VLANs (this server usually can't see those directly)"
|
||||
echo " still need to be listed here so their media is treated as local/trusted."
|
||||
local VLAN_SUBNETS_VAL=""
|
||||
prompt_text "VLAN/VPN subnets, space-separated CIDRs [blank=none]:" "" VLAN_SUBNETS_VAL
|
||||
local HAS_VLANS_VAL="n"
|
||||
[[ -n "$VLAN_SUBNETS_VAL" ]] && HAS_VLANS_VAL="y"
|
||||
|
||||
# ── Secrets ───────────────────────────────────────────────────────────────
|
||||
local TURN_PASSWORD
|
||||
TURN_PASSWORD="$(generate_password 24)"
|
||||
|
||||
local TURN_SERVER_VAL=""
|
||||
[[ -n "$DOMAIN_NAME" ]] && TURN_SERVER_VAL="${DOMAIN_NAME}:3478"
|
||||
|
||||
_asterisk_write_compose
|
||||
|
||||
# ── Pick a free port for the web admin ─────────────────────────────────────
|
||||
# Hardcoding a single number gets fragile fast once several services share
|
||||
|
||||
Reference in New Issue
Block a user