diff --git a/docs/anveo-direct-setup-guide.md b/docs/anveo-direct-setup-guide.md index f219370..45657e4 100644 --- a/docs/anveo-direct-setup-guide.md +++ b/docs/anveo-direct-setup-guide.md @@ -143,7 +143,7 @@ account default in step 6 and skip even that: sudo ./setup.sh pstn-trunk ``` -- Existing install → choose **update** (`r`) if you're just changing the +- Existing install → choose **update** (`u`) if you're just changing the DID/server, or the CLI will walk fresh prompts if none exists yet. - Provider quick-pick: **1) Anveo Direct** — pre-fills `sbc.anveo.com` and Anveo's 4 published signaling IPs (only one of which the hostname diff --git a/lib/common.sh b/lib/common.sh index 421701e..016aaf5 100644 --- a/lib/common.sh +++ b/lib/common.sh @@ -718,12 +718,12 @@ prompt_reinstall_mode() { return fi echo " Existing install detected. Choose:" - echo " r) Reinstall in place — refresh vendor files/config, keep existing settings" - echo " f) Full install — re-run every prompt from scratch" + echo " u) Update — refresh vendor files/config, keep existing settings" + echo " f) Full reinstall — re-run every prompt from scratch" echo " c) Cancel — leave everything as-is [default]" - read -p " Choice [r/f/c, Enter=cancel]: " response + read -p " Choice [u/f/c, Enter=cancel]: " response case "${response,,}" in - r) eval "$varname='update'" ;; + u) eval "$varname='update'" ;; f) eval "$varname='fresh'" ;; *) eval "$varname='cancel'" ;; esac @@ -1076,6 +1076,31 @@ ensure_coturn_user() { _u="$(grep '^COTURN_USER=' "$_userfile" | cut -d= -f2-)" _p="$(grep '^COTURN_PASS=' "$_userfile" | cut -d= -f2-)" COTURN_USERNAME="$_u" COTURN_PASSWORD="$_p" + + # The cache file surviving doesn't mean the username still exists in + # coturn's own live database — confirmed live: a coturn + # container/volume recreated without preserving ./db wipes the + # database while this file (a separate directory) survives + # untouched, silently orphaning every consumer's credentials until + # something re-registers them. Without this check, re-running the + # consumer's installer (fresh or update) never re-registers anything + # since it only ever hits the else branch below on a MISSING cache + # file — a stale-but-present one looked identical to a healthy one. + # A real "user[realm]" line never contains a space; turnadmin -l's + # own startup log lines do (confirmed live, at least one coturn + # build writes them to stdout, not stderr), so filtering on that + # keeps this robust across builds without needing to match a + # specific log format. + local _db_users + _db_users="$(docker exec coturn turnadmin -l -b /var/lib/coturn/turndb 2>/dev/null | grep -v ' ' | sed -E 's/\[.*//' | awk 'NF')" + if ! grep -qx "$_u" <<< "$_db_users"; then + log_warning "coturn user '$_u' ($_consumer) has cached credentials but isn't in coturn's live database — re-registering with the same password." + if docker exec coturn turnadmin -a -u "$_u" -p "$_p" -r "$_realm" -b /var/lib/coturn/turndb >/dev/null 2>&1; then + log_success "Re-registered coturn user '$_u' for $_consumer" + else + log_warning "Could not re-register coturn user '$_u' for $_consumer — is the coturn container running?" + fi + fi else COTURN_USERNAME="$_consumer" COTURN_PASSWORD="$(generate_password 24)" diff --git a/services/asterisk.sh b/services/asterisk.sh index bc71888..0cfa883 100644 --- a/services/asterisk.sh +++ b/services/asterisk.sh @@ -82,12 +82,12 @@ if [[ "${BASH_SOURCE[0]}" == "${0}" ]]; then return fi echo " Existing install detected. Choose:" - echo " r) Reinstall in place — refresh vendor files/config, keep existing settings" - echo " f) Full install — re-run every prompt from scratch" + echo " u) Update — refresh vendor files/config, keep existing settings" + echo " f) Full reinstall — re-run every prompt from scratch" echo " c) Cancel — leave everything as-is [default]" - read -r -p " Choice [r/f/c, Enter=cancel]: " _r + read -r -p " Choice [u/f/c, Enter=cancel]: " _r case "${_r,,}" in - r) eval "$_var='update'" ;; + u) eval "$_var='update'" ;; f) eval "$_var='fresh'" ;; *) eval "$_var='cancel'" ;; esac @@ -1655,6 +1655,21 @@ install_asterisk() { log_warning "docker compose up failed — check: docker compose -f $EA_DIR/docker-compose.yml logs" fi + # Self-heal a stale/orphaned shared-coturn registration on + # every update, not just a full reinstall — the check inside + # ensure_coturn_user() is what actually re-registers a + # missing user, this just needs to reach it. Gated on NOT + # having an embedded coturn: an install with its own + # dedicated coturn deliberately never touches the shared one + # on update (see the warning above and CLAUDE.md's coturn + # migration guidance) — calling this unconditionally would + # silently chain-install services/coturn.sh for a box that + # was never using it, the exact "don't migrate silently on + # update" mistake that guidance warns against. + if [[ "$_HAD_EMBEDDED_COTURN" != true ]]; then + ensure_coturn_user "asterisk" + fi + _asterisk_run_presence_step "$EA_DIR" "$CONTAINER" _asterisk_offer_dashboard_and_trunk "$EA_DIR" @@ -1677,7 +1692,26 @@ install_asterisk() { return 0 ;; fresh) - log_info "Proceeding with a full fresh reinstall — every prompt below runs from scratch." + echo "" + log_warning "Full reinstall stops the existing containers and re-runs every" + log_warning "prompt below from scratch (domain, networking, firewall, Caddy/" + log_warning "Authelia). The TURN credential registered with the shared coturn" + log_warning "service is reused as-is — no need to touch coturn for this." + local _WIPE_PBX_DATA="" + prompt_yn " Also delete stored PBX data (extensions, voicemail, recordings, spool)? (y/n):" "n" _WIPE_PBX_DATA + + log_info "Stopping the existing containers..." + (cd "$EA_DIR" && docker compose down 2>/dev/null) + + if [[ "$_WIPE_PBX_DATA" =~ ^[Yy]$ ]]; then + rm -rf "$EA_DIR/config/asterisk" "$EA_DIR/spool" "$EA_DIR/logs" "$EA_DIR/lib" + log_warning "Deleted config/asterisk, spool, logs, and lib — extensions," + log_warning "voicemail, and call recordings are gone." + if declare -F install_pstn-trunk >/dev/null 2>&1 || [ -f "$EA_DIR/pstn-trunk-usage-alert.sh" ]; then + log_warning "PSTN trunk patches config/asterisk's dialplan — re-run" + log_warning "'sudo ./setup.sh pstn-trunk' afterward to restore it." + fi + fi ;; esac fi diff --git a/services/coturn.sh b/services/coturn.sh index 19b3fdc..08dbb94 100644 --- a/services/coturn.sh +++ b/services/coturn.sh @@ -87,12 +87,12 @@ if [[ "${BASH_SOURCE[0]}" == "${0}" ]]; then local _var="$1" _r if [[ "${UNATTENDED:-false}" == "true" ]]; then eval "$_var='cancel'"; return; fi echo " Existing install detected. Choose:" - echo " r) Reinstall in place — refresh vendor files, keep existing settings" - echo " f) Full install — re-run every prompt from scratch" + echo " u) Update — refresh vendor files, keep existing settings" + echo " f) Full reinstall — re-run every prompt from scratch" echo " c) Cancel — leave everything as-is [default]" - read -r -p " Choice [r/f/c, Enter=cancel]: " _r + read -r -p " Choice [u/f/c, Enter=cancel]: " _r case "${_r,,}" in - r) eval "$_var='update'" ;; + u) eval "$_var='update'" ;; f) eval "$_var='fresh'" ;; *) eval "$_var='cancel'" ;; esac @@ -186,6 +186,27 @@ install_coturn() { log_warning "change the host/port/realm below, every already-registered consumer" log_warning "(Asterisk, Mattermost, ...) keeps pointing at the OLD values in its own" log_warning ".env until you re-run that service's installer too." + + local _consumers="" + [ -d "$DIR/users" ] && _consumers="$(find "$DIR/users" -maxdepth 1 -name '*.env' -printf '%f\n' 2>/dev/null | sed 's/\.env$//' | tr '\n' ' ')" + if [ -n "$_consumers" ]; then + echo "" + log_info "Registered consumers: $_consumers" + local _WIPE_USERS="" + prompt_yn " Also delete all TURN user credentials and the user database (forces every consumer above to re-register)? (y/n):" "n" _WIPE_USERS + if [[ "$_WIPE_USERS" =~ ^[Yy]$ ]]; then + rm -rf "$DIR/users" "$DIR/db" + mkdir -p "$DIR/db" "$DIR/users" + # The running container (if any) still holds the old, + # now-deleted turndb file open — new turnadmin writes + # to the fresh file at that path go unseen until the + # server process restarts and reopens it. + docker restart coturn >/dev/null 2>&1 + log_warning "Deleted TURN credentials and the user database." + log_warning "Re-run each consumer's installer in Update mode afterward —" + log_warning "ensure_coturn_user() auto-recovers a fresh credential for it." + fi + fi ;; esac fi diff --git a/services/mattermost.sh b/services/mattermost.sh index 67363fd..7ccb3a6 100644 --- a/services/mattermost.sh +++ b/services/mattermost.sh @@ -314,6 +314,20 @@ install_mattermost() { log_warning "the shared coturn service — the Calls plugin's TURN config in System" log_warning "Console will need updating to the new credentials afterward (see below)." fi + echo "" + log_warning "Full reinstall stops the existing containers and re-runs every prompt" + log_warning "below from scratch. The TURN credential registered with the shared" + log_warning "coturn service is reused as-is — no need to touch coturn for this." + local _WIPE_MM_DATA="" + prompt_yn " Also delete stored data (Postgres database, uploaded files, config, plugins)? (y/n):" "n" _WIPE_MM_DATA + + log_info "Stopping the existing containers..." + (cd "$DIR" && docker compose down 2>/dev/null) + + if [[ "$_WIPE_MM_DATA" =~ ^[Yy]$ ]]; then + rm -rf "$DIR/db" "$DIR/data" "$DIR/logs" "$DIR/config" "$DIR/plugins" + log_warning "Deleted the Postgres database, uploaded files, config, and plugins." + fi ;; esac fi diff --git a/services/security-dashboard.sh b/services/security-dashboard.sh index 789f3ca..40bd0ff 100644 --- a/services/security-dashboard.sh +++ b/services/security-dashboard.sh @@ -184,7 +184,24 @@ install_security-dashboard() { log_info "Leaving the existing install as-is." return 0 ;; - fresh) ;; + fresh) + echo "" + log_warning "Full reinstall stops the dashboard and removes its systemd unit," + log_warning "sudoers grant, Caddy block, service user, and app files, then sets" + log_warning "it up again from scratch — every prompt below runs as if this were" + log_warning "a brand new install." + local _WIPE_ADMINS="" + prompt_yn " Also delete dashboard-admins.conf (per-admin Calls/Texts/Voicemail scoping)? (y/n):" "n" _WIPE_ADMINS + + local _ADMINS_BACKUP="" + if [[ ! "$_WIPE_ADMINS" =~ ^[Yy]$ ]] && [ -f "$APP_DIR/dashboard-admins.conf" ]; then + _ADMINS_BACKUP="$(mktemp)" + cp "$APP_DIR/dashboard-admins.conf" "$_ADMINS_BACKUP" + log_info "Preserving dashboard-admins.conf across the reinstall." + fi + + _secdash_teardown "$APP_DIR" "$SVC_USER" "$DASHBOARD_PORT" + ;; esac fi @@ -197,6 +214,11 @@ install_security-dashboard() { _secdash_grant_asterisk_access "$SVC_USER" "$ASTERISK_LOG_DIR" "$ASTERISK_CONFIG_DIR" "$ASTERISK_EA_CONFIG_DIR" "$ASTERISK_SPOOL_DIR" mkdir -p "$APP_DIR" + if [ -n "${_ADMINS_BACKUP:-}" ]; then + cp "$_ADMINS_BACKUP" "$APP_DIR/dashboard-admins.conf" + rm -f "$_ADMINS_BACKUP" + log_success "Restored dashboard-admins.conf" + fi _secdash_write_app "$APP_DIR" chown -R "$SVC_USER:$SVC_USER" "$APP_DIR" _secdash_write_asn_helper "$APP_DIR" @@ -974,6 +996,35 @@ _secdash_remove_caddy_block() { log_info "Removed the existing dashboard Caddy block (regenerating it fresh)." } +# Full teardown for "Full reinstall" — stops the service and removes +# everything a fresh install recreates: systemd unit, sudoers grant, Caddy +# site block, the secdash system user, and the app directory. Non-Docker +# service (systemd + /opt, not a container), so lib/common.sh's +# remove_service() (Docker-only, $DOCKER_DIR/) doesn't apply here — +# this is the equivalent for this one service. Callers are responsible for +# backing up/restoring anything under $_app_dir they want to survive (see +# the dashboard-admins.conf handling around the "fresh" case in +# install_security-dashboard() — this function does not know which files, +# if any, the caller wants to keep). +_secdash_teardown() { + local _app_dir="$1" _svc_user="$2" _port="$3" + + systemctl stop security-dashboard 2>/dev/null || true + systemctl disable security-dashboard 2>/dev/null || true + rm -f /etc/systemd/system/security-dashboard.service + systemctl daemon-reload + + rm -f /etc/sudoers.d/security-dashboard + + _secdash_remove_caddy_block "$_port" + + id "$_svc_user" &>/dev/null && userdel "$_svc_user" 2>/dev/null + + rm -rf "$_app_dir" + + log_success "Removed security-dashboard's systemd unit, sudoers grant, Caddy block, user, and app files." +} + # Root-owned helper for editing CrowdSec's Asterisk-scenario YAMLs — the # secdash service user (--shell /usr/sbin/nologin, no special file grants) # cannot write /etc/crowdsec/scenarios/*.yaml directly (root:root, mode diff --git a/services/wordpress.sh b/services/wordpress.sh index 85b092b..7438e0f 100644 --- a/services/wordpress.sh +++ b/services/wordpress.sh @@ -95,12 +95,12 @@ if [[ "${BASH_SOURCE[0]}" == "${0}" ]]; then local _var="$1" _r if [[ "${UNATTENDED:-false}" == "true" ]]; then eval "$_var='cancel'"; return; fi echo " Existing install detected. Choose:" - echo " r) Reinstall in place — refresh image/compose, keep database and settings" - echo " f) Full install — re-run every prompt from scratch" + echo " u) Update — refresh image/compose, keep database and settings" + echo " f) Full reinstall — re-run every prompt from scratch" echo " c) Cancel — leave everything as-is [default]" - read -r -p " Choice [r/f/c, Enter=cancel]: " _r + read -r -p " Choice [u/f/c, Enter=cancel]: " _r case "${_r,,}" in - r) eval "$_var='update'" ;; + u) eval "$_var='update'" ;; f) eval "$_var='fresh'" ;; *) eval "$_var='cancel'" ;; esac diff --git a/tools/coturn-test-check.sh b/tools/coturn-test-check.sh index d1a7f66..dfcb7ac 100755 --- a/tools/coturn-test-check.sh +++ b/tools/coturn-test-check.sh @@ -76,7 +76,14 @@ ok "Relay port range: ${COTURN_MIN_PORT}-${COTURN_MAX_PORT}" # ── Registered consumers ────────────────────────────────────────────────────── section "Registered consumers" -DB_USERS="$(docker exec coturn turnadmin -l -b /var/lib/coturn/turndb 2>/dev/null | sed -E 's/\[.*//' | awk 'NF' | sort -u)" +# turnadmin -l writes its own startup log lines ("INFO SQLite connection +# was closed.", "INFO log file opened: ...") to STDOUT on at least some +# coturn builds, not stderr — confirmed live, `2>/dev/null` alone let them +# through and got misparsed as usernames. A real "user[realm]" line never +# contains a space; every log line here does, so filtering those out is a +# safe, simple way to keep only genuine entries regardless of which coturn +# build's log-noise happens to leak onto stdout. +DB_USERS="$(docker exec coturn turnadmin -l -b /var/lib/coturn/turndb 2>/dev/null | grep -v ' ' | sed -E 's/\[.*//' | awk 'NF' | sort -u)" if [ -z "$DB_USERS" ]; then warn "No users found in coturn's own database — nothing has actually registered yet, or turnadmin -l's output format changed. Raw:" docker exec coturn turnadmin -l -b /var/lib/coturn/turndb 2>&1 | sed 's/^/ /'