From ca239a38865b88ef9fffffd6347425df94e62a44 Mon Sep 17 00:00:00 2001 From: Claude Date: Thu, 13 Aug 2026 02:12:32 +0000 Subject: [PATCH] =?UTF-8?q?Retire=20the=20shared=20coturn=20service=20?= =?UTF-8?q?=E2=80=94=20every=20WebRTC/SIP=20service=20now=20runs=20its=20o?= =?UTF-8?q?wn?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Shared coturn (services/coturn.sh, ensure_coturn_user in lib/common.sh) is no longer an installable or usable option anywhere in this repo. It's moved to attic/coturn.sh (with tools/coturn-test-check.sh alongside it), which is outside setup.sh's services/*.sh glob, so it never registers, never appears in the menu, and `sudo ./setup.sh coturn` now fails with "unknown service". Asterisk and Mattermost each already had an opt-out to run their own dedicated coturn instead of the shared one; that opt-out is now the only behavior — the shared-coturn preference, the opt-out prompt, and every ensure_coturn_user() call site are gone. find_free_coturn_range() (lib/common.sh) is what makes unconditional dedicated coturn safe: it scans every coturn-owning service's own .env on the box for already-claimed relay ranges and picks one that can't collide, so Asterisk + any number of Mattermost instances can each run their own coturn on one box without the relay-port collisions this repo's coturn history warns about. Existing installs still pointed at a shared coturn container are left running as-is on `update` (no silent migration attempt against a service that no longer exists to heal against) — a full/fresh reinstall is the migration path, which generates a new dedicated coturn with fresh credentials and says so. Also updates CLAUDE.md's coturn guidance for future service authors, attic/README.md with the retirement rationale, and stale services/coturn.sh path references in services/asterisk.sh, tools/pstn-test-check.sh, README.md, and docs/vps-sizing-recommendations.md. Co-Authored-By: Claude Sonnet 5 Claude-Session: https://claude.ai/code/session_01Crt4ymNEHEbWqscB1qvZgC --- CLAUDE.md | 115 ++++++++------- README.md | 3 +- attic/README.md | 30 ++++ {tools => attic}/coturn-test-check.sh | 6 + {services => attic}/coturn.sh | 30 +++- docs/vps-sizing-recommendations.md | 16 ++- lib/common.sh | 116 --------------- services/asterisk.sh | 200 +++++++++----------------- services/mattermost.sh | 77 ++++------ tools/pstn-test-check.sh | 16 ++- 10 files changed, 242 insertions(+), 367 deletions(-) rename {tools => attic}/coturn-test-check.sh (97%) rename {services => attic}/coturn.sh (91%) diff --git a/CLAUDE.md b/CLAUDE.md index e07aee3..4e2d9dd 100644 --- a/CLAUDE.md +++ b/CLAUDE.md @@ -674,7 +674,7 @@ interpolates `${WEB_PORT}` directly. A quoted `<< 'MD'` heredoc doesn't, and converting it means escaping *every* backtick used for inline-code formatting (`` \`...\` ``) — miss one and bash tries to execute it as a command substitution the next time the heredoc is read, the same class of -bug the coturn.sh backtick incident was (see `services/coturn.sh`'s +bug the coturn.sh backtick incident was (see `attic/coturn.sh`'s `write_readme` call). For a README with only one or two backticks, escaping them is fine. For one with many (`services/iopaint.sh`'s model reference table), it's safer to leave the heredoc quoted and patch the @@ -778,63 +778,72 @@ so that hostname resolves; `configure_caddy_for_service`'s bare-port upstream case already does this for you — don't hand-roll `localhost:PORT` in a Caddy site block. -## Shared coturn (TURN/STUN) relay +## coturn (TURN/STUN) relay — dedicated per service, not shared -Any service that needs a TURN server for WebRTC/SIP NAT traversal shares -**one** coturn instance (`services/coturn.sh`) instead of running its own. -This exists because it didn't always: `asterisk` and `mattermost` used to -each embed a dedicated coturn container (`network_mode: host`, each with its -own relay port range) — confirmed live, their default ranges overlapped by -~100 UDP ports, so running both on one box meant a coin-flip over which -service's active call lost its media relay. One shared instance with one -port range removes the collision instead of just moving it around. +Any service that needs a TURN server for WebRTC/SIP NAT traversal runs its +**own dedicated** coturn container. There is no shared coturn service to +install or point at — `services/coturn.sh` was tried and retired; it's +parked at `attic/coturn.sh` (outside `services/*.sh`'s glob, so it never +registers or appears in the menu — see `attic/README.md`). Sharing one +instance saved a container per consumer (~40MB) but was a single point of +failure every consumer depended on, and needing a dedicated per-consumer +long-term-credential user added real setup complexity for a small RAM win. +Don't reintroduce it — give every new WebRTC/SIP-capable service its own +coturn, following the pattern below. -**Use `ensure_coturn_user` (`lib/common.sh`), not your own coturn container:** +**The collision this pattern has to avoid:** `asterisk` and `mattermost` +each embed a dedicated coturn container (`network_mode: host`, each with +its own relay port range) — confirmed live, two independent coturns' +default ranges used to overlap by ~100 UDP ports, so running both on one +box meant a coin-flip over which service's active call lost its media +relay. Static default ranges alone don't solve this; something has to pick +non-overlapping ranges per box. + +**Use `find_free_coturn_range` (`lib/common.sh`) to size the range, not a +hardcoded default:** ```bash -ensure_coturn_user "my-service" -if [ -n "$COTURN_HOST" ]; then - # Out-params (not `local` — read them after the call returns, same - # convention as configure_caddy_for_service's CADDY_SERVICE_*): - # COTURN_HOST COTURN_PORT COTURN_USERNAME COTURN_PASSWORD -else - # coturn unavailable (not installed and services/coturn.sh isn't loaded - # to chain-install it — e.g. this file run fully standalone) — degrade - # gracefully. Don't block the rest of your install on this. -fi +local MY_COTURN_MIN_PORT=49152 MY_COTURN_MAX_PORT=49252 +find_free_coturn_range MY_COTURN_MIN_PORT MY_COTURN_MAX_PORT 100 49152 +[[ "$MY_COTURN_MIN_PORT" != 49152 ]] && \ + log_info "Dedicated coturn relay range shifted to ${MY_COTURN_MIN_PORT}-${MY_COTURN_MAX_PORT} to stay clear of another coturn already on this box." ``` -`ensure_coturn_user` chain-installs `services/coturn.sh` the first time -*any* service needs one (guarded with `declare -F install_coturn`, same -pattern as the asterisk → security-dashboard chaining below), then -registers a dedicated long-term-credential username/password for your -consumer name. The credential is cached in -`~/docker/coturn/users/.env`, so calling this again on a rerun -reuses the same credential instead of minting a new one and silently -orphaning whatever client already has the old one configured. +Unlike a single fixed host port (`find_free_port`'s job — coturn's relay +range isn't a statically bound listening socket you can detect with a live +`ss`/socket scan), `find_free_coturn_range` scans every `$DOCKER_DIR/*/.env` +for a `TURN_MAX_PORT=` line and starts the new range 50 ports past the +highest one found — so it works across every coturn-owning service on the +box (Asterisk, each Mattermost instance, yours), regardless of install +order. Persist the chosen range as `TURN_MIN_PORT=`/`TURN_MAX_PORT=` in your +own `.env` so later installs' scans see it, and on an `update` rerun read +those same keys back from the existing `.env` instead of re-scanning — a +live coturn container must never silently move to a different port range +(breaks in-flight/repeat sessions on whatever client already has the old +range's ports allowed through its own firewall/NAT). See +`services/asterisk.sh`'s and `services/mattermost.sh`'s `EMBEDDED_COTURN_MIN_PORT`/ +`MM_COTURN_MIN_PORT` handling for the reference pattern, including the +`MODE != "update"` gate that scans only on a fresh install. -**Why long-term credentials (`--lt-cred-mech`), not the REST-API/HMAC mode -(`--use-auth-secret`) some WebRTC apps default to:** coturn does not support -running both auth mechanisms on one instance at once — enabling -`--use-auth-secret` silently overrides `--lt-cred-mech` server-wide, which -would break every static-credential consumer. `--lt-cred-mech` supports any -number of named users out of the box, which is the actual shape a -shared-multi-consumer coturn needs. If the service you're adding only -exposes an HMAC-secret TURN setting in its own UI (no plain -username/password option), check its docs for an alternative field first — -Mattermost's Calls plugin looked HMAC-only at a glance but also accepts a -fixed username/credential pair via its "ICE Servers Configurations" JSON -field (see `services/mattermost.sh` for the exact format). Don't fall back -to a second coturn instance just because the first field you found expects -a shared secret. +**Auth mode — long-term credentials (`--lt-cred-mech`) or HMAC +(`--use-auth-secret`), your choice per instance:** coturn doesn't support +running both on one instance at once, but since each service now owns its +instance outright, this is a free per-service choice — no shared-instance +constraint forcing one mode across every consumer. `services/asterisk.sh` +uses `--lt-cred-mech` (fixed username/password, simplest to bake into a SIP +device's config); `services/mattermost.sh` uses `--use-auth-secret` (HMAC), +matching the Calls plugin's own "TURN Static Auth Secret" field. Check the +consuming app's own TURN settings UI for which fields it actually exposes +before picking. -**Migrating an existing service from its own embedded coturn:** don't do it -silently. An `update` rerun must keep whatever coturn shape a service -already has — detect the existing embedded container (e.g. `grep -q '^ -coturn:' docker-compose.yml` before regenerating it) and preserve it -exactly, the same non-destructive rule as every other `update` path in this -file. Only switch to the shared coturn on an explicit `fresh` reinstall, and -warn before doing it — the TURN username/password changes, and any -already-configured client (a SIP phone, a browser session) keeps the old -credentials until it's reconfigured. See `services/asterisk.sh`'s -`USE_EMBEDDED_COTURN` handling for the reference pattern. +**Legacy installs still on the old shared coturn:** an `update` rerun on an +install that predates this repo's dedicated-coturn-only model (no `coturn:` +block in its `docker-compose.yml`) must not try to silently migrate or +"heal" it — there's no shared coturn service left in this repo to heal it +against. Leave it running exactly as-is (an `update` never touches `.env` +anyway) and point at a full/fresh reinstall as the migration path, which +generates a new dedicated coturn container with fresh credentials. See the +`_HAD_EMBEDDED_COTURN` handling in `services/asterisk.sh` and +`services/mattermost.sh` for the reference pattern — detect via `grep -q +'^ coturn:' docker-compose.yml` before regenerating it, same as any other +non-destructive `update` path in this file. diff --git a/README.md b/README.md index 7911739..f426677 100644 --- a/README.md +++ b/README.md @@ -185,7 +185,7 @@ a ready-to-copy Caddy config snippet to `~/docker/caddy-snippets/`. | Group | Services | |-------|---------| | `base` | `net-tools`, `ncdu`, `git`, `curl`, `wget`, `htop`, `tree`, `zip`/`unzip`, `ca-certificates`, `gnupg`, `jq`, `rsync`; `glow` (terminal markdown reader, Charm apt repo); Docker CE + Compose plugin; `openssh-server` with GitHub/Launchpad SSH key import, optional password-auth lockdown, and SSH Host aliases; optional NetBird overlay network | -| `homelab` | `caddy`, `crowdsec`, `authelia`, `coturn` (shared TURN/STUN relay — Asterisk, Mattermost Calls, and future WebRTC-capable services all register a dedicated credential against one instance instead of each running its own), `homeassistant`, `asterisk`, `pstn-trunk`, `sms-inbound`, `security-dashboard`, `sunshine`, `vpn-data-mount` (mount existing SMB shares from a NetBird-connected home box — SSH trust bootstrap, then read-only discovery of shares already configured there; never writes to the home box's Samba config; repeatable, pick from any number of a home box's shares in one pass; optional per-share [gocryptfs decrypt layer](#client-side-encryption-for-vpn-data-mount) so the VPS only ever handles ciphertext) | +| `homelab` | `caddy`, `crowdsec`, `authelia`, `homeassistant`, `asterisk` (own dedicated coturn for TURN/STUN — see `mattermost` below for the other coturn-owning service), `pstn-trunk`, `sms-inbound`, `security-dashboard`, `sunshine`, `vpn-data-mount` (mount existing SMB shares from a NetBird-connected home box — SSH trust bootstrap, then read-only discovery of shares already configured there; never writes to the home box's Samba config; repeatable, pick from any number of a home box's shares in one pass; optional per-share [gocryptfs decrypt layer](#client-side-encryption-for-vpn-data-mount) so the VPS only ever handles ciphertext) | | `utilities` | `actualbudget`, `ai-gpu`, `ai-stack`, `archivebox`, `beszel` (lightweight server + Docker monitoring — CPU/RAM/disk/network, auto-discovers running containers via the Docker socket; complements Gatus rather than replacing it — Gatus is a black-box HTTP check, Beszel is white-box host/process monitoring), `beszel-agent` (agent-only Beszel install for a remote/homelab box reporting to a hub elsewhere — connects outbound over HTTPS, no VPN/port-forwarding/FQDN needed on that box), `changedetection`, `ddclient`, `filebrowser`, `fmd`, `gatus`, `homebox`, `iopaint`, `joplin`, `koha`, `magicmirror`, `mail-archiver`, `mattermost`, `mealie`, `meshcentral`, `n8n`, `nextcloud`, `ntfy`, `onlyoffice`, `paintplus`, `portainer`, `rustdesk`, `stirling-pdf`, `syncthing`, `traccar`, `unifi`, `uptimekuma`, `vaultwarden`, `watchyourlan`, `watchtower`, `wg-easy`, `wordpress` (multi-site, dedicated MariaDB per site — blogs, business sites, e-commerce via WooCommerce) | | `media` | `arm`, `audiobookshelf`, `calibre-web`, `emby`, `immich`, `jellyfin`, `lyrion` | | `cameras` | `frigate`, `frigate-audio`, `frigate-notify`, `sky-cam` | @@ -207,7 +207,6 @@ homelab caddy crowdsec authelia - coturn homeassistant asterisk pstn-trunk diff --git a/attic/README.md b/attic/README.md index f57b0e5..1a85a95 100644 --- a/attic/README.md +++ b/attic/README.md @@ -34,3 +34,33 @@ rather than `fresh` at the reinstall prompt, and having a snapshot. Two copies of the same logic is the exact problem the merge existed to fix, and this one will drift the moment `services/asterisk.sh` gets a fix that isn't backported here — which it deliberately won't be. + +## `coturn.sh` / `coturn-test-check.sh` + +The shared-coturn service (`services/coturn.sh`, moved here unchanged from +`services/`) and its standalone health-check tool (`tools/coturn-test-check.sh`, +moved from `tools/`). This model — every WebRTC/SIP-capable service +(`asterisk`, `mattermost`) sharing one coturn instance via `ensure_coturn_user` +— is no longer offered anywhere in this repo. Every service now runs its own +dedicated coturn instead, with `lib/common.sh`'s `find_free_coturn_range()` +avoiding the relay-port collisions a shared instance used to prevent by +scanning every coturn-owning service's own `.env` on the box. See +`CLAUDE.md`'s "coturn (TURN/STUN) relay" section for the current pattern. + +Sharing one instance only ever saved ~40MB RAM per additional consumer +beyond the first — real, but small — against being a single point of +failure every consumer depended on. Parked here, not deleted, since the +code is still correct and someone could resurrect it if a future need for +it shows up. Both files still run standalone if invoked directly: + +```bash +sudo bash attic/coturn.sh +sudo bash attic/coturn-test-check.sh +``` + +Nothing in this repo calls `ensure_coturn_user()` anymore (the function +itself was removed from `lib/common.sh`), so resurrecting this only makes +sense if you're deliberately reintroducing the shared-coturn pattern +yourself — a new consumer service would need its own call to whatever +takes `ensure_coturn_user`'s place, since that helper no longer exists to +call. diff --git a/tools/coturn-test-check.sh b/attic/coturn-test-check.sh similarity index 97% rename from tools/coturn-test-check.sh rename to attic/coturn-test-check.sh index dfcb7ac..2791443 100755 --- a/tools/coturn-test-check.sh +++ b/attic/coturn-test-check.sh @@ -1,4 +1,10 @@ #!/usr/bin/env bash +# attic/coturn-test-check.sh — RETIRED along with attic/coturn.sh (formerly +# services/coturn.sh). This tool only makes sense against a shared coturn +# instance, which this repo no longer offers — see attic/coturn.sh's header +# for what replaced it (each service gets its own dedicated coturn now). +# Kept for reference alongside it, not actively maintained. +# # tools/coturn-test-check.sh — Health-check for the shared coturn (TURN/STUN) # instance services/coturn.sh sets up, and every consumer registered against # it (Asterisk, one or more Mattermost instances, anything else added via diff --git a/services/coturn.sh b/attic/coturn.sh similarity index 91% rename from services/coturn.sh rename to attic/coturn.sh index 08dbb94..cd33c13 100644 --- a/services/coturn.sh +++ b/attic/coturn.sh @@ -1,10 +1,30 @@ #!/bin/bash -# services/coturn.sh — Shared TURN/STUN relay (coturn) for WebRTC-capable services. -# Part of the modular post-install system (sourced by setup.sh). +# attic/coturn.sh — RETIRED. Formerly services/coturn.sh. # -# Can also be run standalone on any machine: -# sudo bash coturn.sh -# (Docker must already be installed when run standalone) +# The shared-coturn model this file implements is no longer offered by this +# repo at all: services/asterisk.sh and services/mattermost.sh each now run +# their own dedicated coturn unconditionally, with lib/common.sh's +# find_free_coturn_range() making that safe (it scans every coturn-owning +# service's own .env on the box for already-claimed relay ranges and picks +# a block that can't collide with any of them, dedicated or shared). Sharing +# one instance only ever saved ~40MB RAM per additional consumer beyond the +# first — real, but small — and it was a single point of failure every +# consumer depended on. Parked here, not deleted, since the code is still +# correct and someone could resurrect it if a future need for it shows up; +# living in attic/ (outside services/*.sh's glob) means it never +# self-registers, never appears in the menu, and `sudo ./setup.sh coturn` +# now correctly fails with "unknown service" instead of silently offering +# a coturn shape nothing else in this repo will register a user against. +# +# ── Everything below this point is the file exactly as it ran before +# retirement, kept for reference/rollback, not actively maintained. ──────── +# +# Can still be run standalone on any machine, same as before: +# sudo bash attic/coturn.sh +# (Docker must already be installed when run standalone) — but nothing in +# this repo will call ensure_coturn_user() to register with it anymore, so +# doing this only makes sense if you're deliberately reintroducing the +# shared-coturn pattern yourself. # # One coturn instance, shared by every service that needs TURN (Asterisk, # Mattermost, and anything added later) instead of each service running its diff --git a/docs/vps-sizing-recommendations.md b/docs/vps-sizing-recommendations.md index d0c130b..b0ed2fb 100644 --- a/docs/vps-sizing-recommendations.md +++ b/docs/vps-sizing-recommendations.md @@ -31,10 +31,16 @@ Rules of thumb: `ensure_swapfile()` unconditionally, which offers a 2GB swapfile any time RAM is ≤4096MB and none exists yet (`services/asterisk.sh` also calls it directly for the standalone-run case, so it's covered either way). -- Sharing one `coturn` instance (`services/coturn.sh`) instead of letting - each WebRTC-capable service (Asterisk, Mattermost) embed its own saves a - container per consumer and — more importantly — avoids relay-port - collisions between them. +- **Historical note, no longer applicable:** this doc's Tier 2/3 plans below + were sized around one shared `coturn` instance instead of each WebRTC- + capable service (Asterisk, Mattermost) embedding its own — it saved a + container per consumer and avoided relay-port collisions between them. + That shared-coturn service has since been retired from this repo (see + `attic/coturn.sh`); every service now runs its own dedicated coturn, and + `lib/common.sh`'s `find_free_coturn_range()` avoids the same relay-port + collisions by scanning each coturn-owning service's `.env` instead. Budget + a coturn container per WebRTC-capable service/instance, not one shared + ~40MB line, when re-planning a box from scratch. ## Tier 1 — ~1 vCPU / 1GB RAM / 25GB SSD @@ -43,7 +49,7 @@ Example: DigitalOcean Basic, $6/mo. This is tight enough that Docker's own daemon overhead is already a meaningful fraction of the box. **Pick one purpose, not a stack:** -- **Option A — Asterisk only.** Asterisk + the shared coturn service fits +- **Option A — Asterisk only.** Asterisk + its own dedicated coturn fits comfortably per this repo's own droplet-sizing notes (`services/asterisk.sh` README section) — a swapfile is added automatically (RAM ≤4GB, see above), and this plan is "fine for a couple of extensions and light personal use." diff --git a/lib/common.sh b/lib/common.sh index bd9c4ff..4c54ad7 100644 --- a/lib/common.sh +++ b/lib/common.sh @@ -1038,119 +1038,3 @@ CADDY_BLOCK echo "" } -# ── Shared coturn (TURN/STUN) wiring ────────────────────────────────────────── -# Usage: ensure_coturn_user "" -# -# Installs the shared coturn service (services/coturn.sh) if this is the -# first service on the box that needs TURN, then registers (or reuses) a -# dedicated long-term-credential user for the caller — one coturn instance, -# one relay port range, shared by every consumer instead of each service -# running its own and fighting over host ports (see services/coturn.sh's -# header for why that used to be a real, confirmed-live problem). -# -# Out-params (not `local` — read them after the call returns), same -# convention as configure_caddy_for_service's CADDY_SERVICE_* above: -# COTURN_HOST host/IP TURN clients should connect to -# COTURN_PORT coturn's listening port -# COTURN_USERNAME this consumer's long-term-credential username -# COTURN_PASSWORD this consumer's long-term-credential password -# COTURN_HOST is left empty if coturn couldn't be installed or reached — -# callers should treat that as "no TURN available" and degrade gracefully, -# same as checking CADDY_SERVICE_CONFIGURED after configure_caddy_for_service. -# -# Credentials are cached per-consumer in coturn's own users/.env so a -# service re-running its own installer reuses the same one instead of -# minting a new credential and orphaning the old one (which would silently -# break already-configured clients still holding it). -ensure_coturn_user() { - local _consumer="$1" - COTURN_HOST="" COTURN_PORT="" COTURN_USERNAME="" COTURN_PASSWORD="" - - if [ ! -d "$DOCKER_DIR/coturn" ]; then - if declare -F install_coturn >/dev/null 2>&1; then - log_info "No shared coturn (TURN/STUN) server yet — setting one up for $_consumer..." - # install_coturn cd's into $DOCKER_DIR/coturn and never cd's back — - # the caller (e.g. asterisk.sh, already cd'd into its own install - # directory) would otherwise return here with the wrong cwd and go - # on to write ITS docker-compose.yml/.env into coturn's directory - # instead of its own. Confirmed live: this clobbered coturn's - # compose file and left the consumer's own directory without one, - # so its later `docker compose up --build` failed with "Dockerfile: - # no such file or directory" (no Dockerfile in coturn's directory). - local _caller_pwd - _caller_pwd="$(pwd)" - install_coturn - local _coturn_rc=$? - cd "$_caller_pwd" || true - [ "$_coturn_rc" -ne 0 ] && { log_warning "coturn setup failed — $_consumer will run without TURN."; return 1; } - else - log_warning "services/coturn.sh not loaded — $_consumer will run without TURN." - log_warning "Run: sudo ./setup.sh coturn" - return 1 - fi - fi - - if [ "$DRY_RUN" = true ]; then - echo "[DRY-RUN] Would register coturn user '$_consumer'" - return 0 - fi - - local _env="$DOCKER_DIR/coturn/.env" - [ -f "$_env" ] || { log_warning "coturn installed but $_env missing — cannot register '$_consumer'."; return 1; } - local _realm _host _port - _realm="$(grep '^COTURN_REALM=' "$_env" | cut -d= -f2-)" - _host="$(grep '^COTURN_HOST=' "$_env" | cut -d= -f2-)" - _port="$(grep '^COTURN_PORT=' "$_env" | cut -d= -f2-)"; _port="${_port:-3478}" - - local _userdir="$DOCKER_DIR/coturn/users" - local _userfile="$_userdir/${_consumer}.env" - mkdir -p "$_userdir" - - if [ -f "$_userfile" ]; then - local _u _p - _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)" - if docker exec coturn turnadmin -a -u "$COTURN_USERNAME" -p "$COTURN_PASSWORD" \ - -r "$_realm" -b /var/lib/coturn/turndb >/dev/null 2>&1; then - { echo "COTURN_USER=$COTURN_USERNAME"; echo "COTURN_PASS=$COTURN_PASSWORD"; } > "$_userfile" - chmod 600 "$_userfile" - log_success "Registered coturn user '$COTURN_USERNAME' for $_consumer" - else - log_warning "Could not register a coturn user for $_consumer — is the coturn container running?" - COTURN_USERNAME="" COTURN_PASSWORD="" - return 1 - fi - fi - - COTURN_HOST="$_host" - COTURN_PORT="$_port" -} diff --git a/services/asterisk.sh b/services/asterisk.sh index 69747c4..3922847 100644 --- a/services/asterisk.sh +++ b/services/asterisk.sh @@ -280,7 +280,7 @@ CBLOCK fi # ───────────────────────────────────────────────────────────────────────────── -register_service asterisk homelab "Easy Asterisk PBX (intercom/VoIP; auto-tunes for a DigitalOcean droplet); TURN via the shared coturn service" 5061 +register_service asterisk homelab "Easy Asterisk PBX (intercom/VoIP; auto-tunes for a DigitalOcean droplet); own dedicated coturn for TURN" 5061 # ── Install layout: directory + container names ──────────────────────────── # Sets ASTERISK_DIR / ASTERISK_CONTAINER / ASTERISK_COTURN / ASTERISK_PROJECT. @@ -1024,14 +1024,14 @@ _asterisk_offer_dashboard_and_trunk() { # and container names are therefore substituted afterwards, same placeholder # trick the Caddy volume line already uses below. # -# USE_EMBEDDED_COTURN controls whether this install runs its own dedicated -# coturn container (legacy shape) or relies on the shared coturn service -# (services/coturn.sh) instead. This is NOT a free choice at every call site -# — an install that already has its own embedded coturn must keep getting -# one on every "update" regeneration of this file, or the next `docker -# compose up` silently drops the container its own .env TURN_PASSWORD still -# points at, breaking every already-configured phone with no warning. See -# the two call sites below for how each decides. +# Every install now runs its own dedicated coturn — there's no shared coturn +# service left in this repo to opt into (see attic/coturn.sh for why it was +# retired). USE_EMBEDDED_COTURN still exists as a parameter purely for +# backward compatibility with pre-retirement installs that were pointed at +# the old shared coturn service instead: an "update" on one of those must +# keep NOT writing a coturn: block (there's no .env TURN_PASSWORD for it to +# use), so it stays exactly as it was rather than silently gaining or losing +# a container. See the two call sites below for how each decides. _asterisk_write_compose() { local PROJECT="$1" CONTAINER="$2" COTURN_CONTAINER="$3" USE_EMBEDDED_COTURN="${4:-true}" local COTURN_MIN_PORT_VAL="${5:-49152}" COTURN_MAX_PORT_VAL="${6:-49252}" @@ -1106,9 +1106,9 @@ EOF # # This is entirely about Asterisk's OWN SIP transport-tls cert (port # 5061) -- it has nothing to do with coturn's separate, unrelated TURNS - # (TLS-wrapped TURN) capability, which the shared coturn service indeed - # doesn't support (see services/coturn.sh's README). A previous version - # of this check gated the mount on USE_EMBEDDED_COTURN == true, conflating + # (TLS-wrapped TURN) capability, which the (since-retired) shared coturn + # service indeed didn't support (see attic/coturn.sh's README). A + # previous version of this check gated the mount on USE_EMBEDDED_COTURN == true, conflating # the two. Confirmed live: on a shared-coturn install with a real Caddy # cert already sitting on disk for DOMAIN_NAME, Asterisk silently kept # generating (and re-generating) a self-signed cert forever, because @@ -1380,8 +1380,8 @@ _asterisk_configure_do_cloud_firewall() { # silently dropped everything else before it ever reached the box. UFW being # wide open proves nothing about a layer in front of it that UFW can't see. _asterisk_remind_non_do_firewall() { - local WEB_ADMIN_PORT_VAL="$1" WEB_ADMIN_PUBLIC_ACCESS_NEEDED="$2" USE_EMBEDDED_COTURN_VAL="${3:-true}" - local COTURN_MIN_PORT_VAL="${4:-49152}" COTURN_MAX_PORT_VAL="${5:-49252}" + local WEB_ADMIN_PORT_VAL="$1" WEB_ADMIN_PUBLIC_ACCESS_NEEDED="$2" + local COTURN_MIN_PORT_VAL="${3:-49152}" COTURN_MAX_PORT_VAL="${4:-49252}" echo "" log_warning "This box is reachable via FQDN but wasn't set up as a DigitalOcean droplet," log_warning "so no automatic network-edge firewall was configured (that step only exists" @@ -1397,15 +1397,8 @@ _asterisk_remind_non_do_firewall() { [[ "$WEB_ADMIN_PUBLIC_ACCESS_NEEDED" == true ]] && echo " TCP ${WEB_ADMIN_PORT_VAL} (web admin)" echo " TCP 8088, 8089 (Asterisk HTTP/HTTPS)" echo " UDP 10000-20000 (RTP media)" - if [[ "$USE_EMBEDDED_COTURN_VAL" == true ]]; then - echo " UDP/TCP 3478 (TURN/STUN)" - echo " UDP ${COTURN_MIN_PORT_VAL}-${COTURN_MAX_PORT_VAL} (TURN relay)" - else - echo " UDP/TCP 3478 too, if the shared coturn instance (services/coturn.sh) lives" - echo " on this same box — its exact TURN relay range is in its own README" - echo " (~/docker/coturn/README.md), not repeated here since it's independently" - echo " configurable and this install doesn't own it." - fi + echo " UDP/TCP 3478 (TURN/STUN)" + echo " UDP ${COTURN_MIN_PORT_VAL}-${COTURN_MAX_PORT_VAL} (TURN relay)" } # ── Shared: README ───────────────────────────────────────────────────────── @@ -1461,9 +1454,7 @@ connecting a phone. The Security Dashboard's Extensions tab | TURN username | ${TURN_USERNAME_VAL} | | TURN password | see \`.env\` → \`TURN_PASSWORD\` | -$( [[ "$USE_EMBEDDED_COTURN" == true ]] \ - && echo "This install runs its own dedicated coturn container (the \`coturn:\` service in docker-compose.yml)." \ - || echo "TURN is served by the box's shared coturn service, not a container in this compose file — see \`~/docker/coturn/README.md\`. Every service on the box that needs TURN (Mattermost Calls, etc.) shares this same relay, each with its own dedicated username." ) +This install runs its own dedicated coturn container (the \`coturn:\` service in docker-compose.yml). Recommended softphones: Linphone, Zoiper, Bria, Grandstream Wave, and [Sipnetic](https://www.sipnetic.com/) on Android (free, TLS/SRTP + @@ -1682,11 +1673,10 @@ install_asterisk() { echo "[DRY-RUN] - offer local OR remote Authelia to protect the web admin" echo "[DRY-RUN] - offer to create a DigitalOcean Cloud Firewall via doctl" echo "[DRY-RUN] Would scan for a free web admin port starting at 8081 (avoids e.g. CrowdSec's 8080)" - echo "[DRY-RUN] Would register a TURN user with the shared coturn service (chain-installing it" - echo "[DRY-RUN] if this is the first service on the box that needs one), falling back to" - echo "[DRY-RUN] Asterisk's own dedicated coturn if the shared service is unavailable" + echo "[DRY-RUN] Would run its own dedicated coturn container for TURN, with a relay port" + echo "[DRY-RUN] range picked to avoid colliding with any other coturn already on the box" echo "[DRY-RUN] Would open UFW ports: 5060, 5061, , 8088, 8089, 10000-20000," - echo "[DRY-RUN] plus 3478 + 49152-49252 only if falling back to a dedicated coturn" + echo "[DRY-RUN] plus 3478 + the dedicated coturn's relay port range" echo "[DRY-RUN] Would offer 'update in place' instead of a fresh install if $EA_DIR already exists" echo "[DRY-RUN] Would patch vendor device-creation code + extensions.conf generator to route" echo "[DRY-RUN] internal SIP MESSAGE through a dedicated [sip-messaging] dialplan context," @@ -1754,19 +1744,16 @@ 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. + # A pre-existing install with no embedded coturn block predates + # this repo's dedicated-coturn-only model — it's still pointed + # at a shared coturn container this repo no longer installs or + # manages (attic/coturn.sh). Leave it running as-is; update + # never touches .env or firewall rules anyway. Point at a + # fresh reinstall as the migration path instead of silently + # trying to heal a registration against a service that no + # longer exists here. if [[ "$_HAD_EMBEDDED_COTURN" != true ]]; then - ensure_coturn_user "asterisk" + log_info "This install still points at a shared coturn service, which this repo no longer installs or manages. It will keep working as long as that coturn container keeps running. Run a full reinstall (not update) to migrate to a dedicated coturn." fi _asterisk_run_presence_step "$EA_DIR" "$CONTAINER" @@ -1794,8 +1781,9 @@ install_asterisk() { 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." + log_warning "Authelia), including generating a fresh dedicated coturn container" + log_warning "with new TURN credentials — any already-configured phone's TURN" + log_warning "settings will need to be updated afterward (re-scan its QR code)." local _WIPE_PBX_DATA="" prompt_yn " Also delete stored PBX data (extensions, voicemail, recordings, spool)? (y/n):" "n" _WIPE_PBX_DATA @@ -1899,83 +1887,41 @@ install_asterisk() { fi # ── Secrets / TURN ─────────────────────────────────────────────────────── - # Prefer the shared coturn service (services/coturn.sh) — one TURN server - # for every service on the box instead of Asterisk running its own and - # fighting other consumers (Mattermost, etc.) over relay ports. Falls - # back to Asterisk's own dedicated coturn if the shared service isn't - # available (e.g. this file run standalone with no sibling services/*.sh - # sourced) or registration fails for any reason — Asterisk should never - # end up with no TURN at all just because the shared path had a problem. + # Asterisk always runs its own dedicated coturn — there is no shared + # coturn service in this repo anymore (see attic/coturn.sh for why it + # was retired). find_free_coturn_range (below) is what makes running a + # dedicated coturn per service safe: it checks every coturn-owning + # service's .env on the box and picks a relay range that can't collide + # with any of them. local USE_EMBEDDED_COTURN=true local TURN_USERNAME TURN_PASSWORD TURN_PORT_VAL TURN_SERVER_VAL - local FORCE_EMBEDDED_COTURN="" - # Only reachable here via an explicit "fresh" choice above — "update" - # is handled separately and always preserves whatever coturn shape - # already exists, never silently switches it. - if [[ -f "$EA_DIR/docker-compose.yml" ]] && grep -q '^ coturn:' "$EA_DIR/docker-compose.yml" 2>/dev/null; then - echo "" - log_warning "This box's existing Asterisk install has its own dedicated coturn." - log_warning "Continuing may switch it to the new shared coturn service — any" - log_warning "phone/softphone configured with the OLD TURN username/password will" - log_warning "need updating once this completes." + TURN_USERNAME="easyasterisk" + TURN_PASSWORD="$(generate_password 24)" + TURN_PORT_VAL="3478" + # A public box always has a usable TURN address (the FQDN if set, else its + # public IP). A LAN box with no FQDN has none — coturn is only reachable + # over the local network, so clients use the server's LAN address directly. + TURN_SERVER_VAL="" + if [[ "$IS_DO" == true ]]; then + TURN_SERVER_VAL="${DOMAIN_NAME:-$PUBLIC_IP}:3478" + elif [[ -n "$DOMAIN_NAME" ]]; then + TURN_SERVER_VAL="${DOMAIN_NAME}:3478" fi - # Opt-out of the shared coturn preference below, for the rare case where - # you specifically want Asterisk isolated on its own TURN relay again - # (e.g. reproducing an older install's exact shape to rule out anything - # coturn-sharing-specific during troubleshooting). Only offered when a - # shared instance actually exists — no meaningful choice otherwise. - if [[ -d "$DOCKER_DIR/coturn" ]]; then - local _USE_SHARED_COTURN="" - prompt_yn "Use the shared coturn service for TURN? (n = run Asterisk's own dedicated coturn instead) (y/n):" "y" _USE_SHARED_COTURN - [[ "$_USE_SHARED_COTURN" =~ ^[Nn]$ ]] && FORCE_EMBEDDED_COTURN=true - fi - - [[ "$FORCE_EMBEDDED_COTURN" != true ]] && ensure_coturn_user "asterisk" - if [[ "$FORCE_EMBEDDED_COTURN" != true && -n "${COTURN_HOST:-}" ]]; then - USE_EMBEDDED_COTURN=false - TURN_USERNAME="$COTURN_USERNAME" - TURN_PASSWORD="$COTURN_PASSWORD" - TURN_PORT_VAL="$COTURN_PORT" - TURN_SERVER_VAL="${COTURN_HOST}:${COTURN_PORT}" - log_success "Using the shared coturn service — TURN username '$COTURN_USERNAME'." - else - TURN_USERNAME="easyasterisk" - TURN_PASSWORD="$(generate_password 24)" - TURN_PORT_VAL="3478" - # A public box always has a usable TURN address (the FQDN if set, else its - # public IP). A LAN box with no FQDN has none — coturn is only reachable - # over the local network, so clients use the server's LAN address directly. - TURN_SERVER_VAL="" - if [[ "$IS_DO" == true ]]; then - TURN_SERVER_VAL="${DOMAIN_NAME:-$PUBLIC_IP}:3478" - elif [[ -n "$DOMAIN_NAME" ]]; then - TURN_SERVER_VAL="${DOMAIN_NAME}:3478" - fi - if [[ "$FORCE_EMBEDDED_COTURN" == true ]]; then - log_info "Running Asterisk's own dedicated coturn, as requested." - else - log_info "Shared coturn unavailable — Asterisk will run its own dedicated coturn." - fi - fi - - # A dedicated embedded coturn running ALONGSIDE any other coturn on the - # same box (the shared instance, Asterisk's own on a prior install, - # any Mattermost instance's own) is exactly the pre-merge collision bug - # this repo's coturn history warns about if two of them claim overlapping - # relay ports — confirmed live, two independent coturns' default ranges - # used to overlap by ~100 UDP ports. find_free_coturn_range (lib/common.sh) - # checks every coturn-owning service's .env on the box, not just the - # shared instance's, and picks a range starting safely past whatever's - # already claimed. No other coturn on the box at all leaves it at the - # historical 49152-49252 default — nothing to collide with yet. + # A dedicated coturn here running alongside Asterisk's own on a prior + # install, or any Mattermost instance's own, is exactly the pre-merge + # collision bug this repo's coturn history warns about if two of them + # claim overlapping relay ports — confirmed live, two independent + # coturns' default ranges used to overlap by ~100 UDP ports. + # find_free_coturn_range (lib/common.sh) checks every coturn-owning + # service's .env on the box and picks a range starting safely past + # whatever's already claimed. No other coturn on the box at all leaves + # it at the historical 49152-49252 default — nothing to collide with yet. local EMBEDDED_COTURN_MIN_PORT=49152 EMBEDDED_COTURN_MAX_PORT=49252 - if [[ "$USE_EMBEDDED_COTURN" == true ]]; then - find_free_coturn_range EMBEDDED_COTURN_MIN_PORT EMBEDDED_COTURN_MAX_PORT 100 49152 - [[ "$EMBEDDED_COTURN_MIN_PORT" != 49152 ]] && \ - log_info "Dedicated coturn relay range shifted to ${EMBEDDED_COTURN_MIN_PORT}-${EMBEDDED_COTURN_MAX_PORT} to stay clear of another coturn already on this box." - fi + find_free_coturn_range EMBEDDED_COTURN_MIN_PORT EMBEDDED_COTURN_MAX_PORT 100 49152 + [[ "$EMBEDDED_COTURN_MIN_PORT" != 49152 ]] && \ + log_info "Dedicated coturn relay range shifted to ${EMBEDDED_COTURN_MIN_PORT}-${EMBEDDED_COTURN_MAX_PORT} to stay clear of another coturn already on this box." _asterisk_write_compose "$ASTERISK_PROJECT" "$CONTAINER" "$ASTERISK_COTURN" "$USE_EMBEDDED_COTURN" \ "$EMBEDDED_COTURN_MIN_PORT" "$EMBEDDED_COTURN_MAX_PORT" @@ -2017,18 +1963,16 @@ install_asterisk() { DOMAIN_NAME=${DOMAIN_NAME} # ── TURN/STUN ───────────────────────────────────────────────── -# $( [[ "$USE_EMBEDDED_COTURN" == true ]] && echo "This install runs its own dedicated coturn (see the coturn: service in docker-compose.yml)." || echo "Using the shared coturn service — see ~/docker/coturn/README.md." ) +# This install runs its own dedicated coturn (see the coturn: service in docker-compose.yml). TURN_USERNAME=${TURN_USERNAME} TURN_PASSWORD=${TURN_PASSWORD} TURN_PORT=${TURN_PORT_VAL} # Empty when there's no publicly resolvable address (LAN-only, no FQDN). TURN_SERVER=${TURN_SERVER_VAL} -# This install's OWN coturn relay range -- only set when USE_EMBEDDED_COTURN -# is true above. Left blank when using the shared coturn service, so other -# services' find_free_coturn_range (lib/common.sh) scan correctly skips this -# file instead of treating a range this install doesn't actually own as claimed. -TURN_MIN_PORT=$( [[ "$USE_EMBEDDED_COTURN" == true ]] && echo "$EMBEDDED_COTURN_MIN_PORT" ) -TURN_MAX_PORT=$( [[ "$USE_EMBEDDED_COTURN" == true ]] && echo "$EMBEDDED_COTURN_MAX_PORT" ) +# This install's own coturn relay range — other services' find_free_coturn_range +# (lib/common.sh) scans this file to avoid claiming an overlapping range. +TURN_MIN_PORT=${EMBEDDED_COTURN_MIN_PORT} +TURN_MAX_PORT=${EMBEDDED_COTURN_MAX_PORT} # ── RTP port range ──────────────────────────────────────────── RTP_START=10000 @@ -2092,13 +2036,9 @@ ENV ufw allow 8088/tcp ufw allow 8089/tcp ufw allow 10000:20000/udp - if [[ "$USE_EMBEDDED_COTURN" == true ]]; then - ufw allow 3478/udp - ufw allow 3478/tcp - ufw allow "${EMBEDDED_COTURN_MIN_PORT}:${EMBEDDED_COTURN_MAX_PORT}/udp" - fi - # Shared coturn opens its own ports once, at its own install time - # (services/coturn.sh) — nothing to open here when using it. + ufw allow 3478/udp + ufw allow 3478/tcp + ufw allow "${EMBEDDED_COTURN_MIN_PORT}:${EMBEDDED_COTURN_MAX_PORT}/udp" ensure_ufw_enabled log_success "UFW rules added." fi @@ -2108,7 +2048,7 @@ ENV _asterisk_configure_do_cloud_firewall "$DROPLET_ID" "$WEB_ADMIN_PORT_VAL" "$WEB_ADMIN_PUBLIC_ACCESS_NEEDED" \ "$EMBEDDED_COTURN_MIN_PORT" "$EMBEDDED_COTURN_MAX_PORT" elif [[ -n "$DOMAIN_NAME" ]]; then - _asterisk_remind_non_do_firewall "$WEB_ADMIN_PORT_VAL" "$WEB_ADMIN_PUBLIC_ACCESS_NEEDED" "$USE_EMBEDDED_COTURN" \ + _asterisk_remind_non_do_firewall "$WEB_ADMIN_PORT_VAL" "$WEB_ADMIN_PUBLIC_ACCESS_NEEDED" \ "$EMBEDDED_COTURN_MIN_PORT" "$EMBEDDED_COTURN_MAX_PORT" fi diff --git a/services/mattermost.sh b/services/mattermost.sh index c59a896..2c0833f 100644 --- a/services/mattermost.sh +++ b/services/mattermost.sh @@ -238,7 +238,7 @@ CBLOCK fi # ───────────────────────────────────────────────────────────────────────────── -register_service mattermost utilities "Team messaging with voice/video calls (Mattermost; TURN via the shared coturn service); supports multiple isolated instances" 8065 +register_service mattermost utilities "Team messaging with voice/video calls (Mattermost; own dedicated coturn for TURN); supports multiple isolated instances" 8065 install_mattermost() { require_docker || return 1 @@ -251,7 +251,6 @@ install_mattermost() { local INSTANCE_SUFFIX="" PROJECT="mattermost" local MM_CONTAINER="mattermost" DB_CONTAINER="mattermost-db" local WEB_PORT="8065" CALLS_UDP_PORT="8443" - local COTURN_CONSUMER="mattermost" if [ -d "$DIR" ]; then echo "" @@ -280,7 +279,6 @@ install_mattermost() { PROJECT="mattermost-$_suffix" MM_CONTAINER="mattermost-$_suffix" DB_CONTAINER="mattermost-$_suffix-db" - COTURN_CONSUMER="mattermost-$_suffix" log_info "New instance: $DIR" fi fi @@ -305,8 +303,8 @@ install_mattermost() { echo "[DRY-RUN] Would create $DIR with docker-compose.yml" echo "[DRY-RUN] Would write .env with DB and Mattermost secrets" echo "[DRY-RUN] Would create data/ logs/ config/ plugins/ db/ subdirectories" - echo "[DRY-RUN] Would register a TURN user with the shared coturn service for '$COTURN_CONSUMER'" - echo "[DRY-RUN] (falling back to a dedicated coturn if the shared service is unavailable)" + echo "[DRY-RUN] Would run this instance's own dedicated coturn container for TURN, with a relay" + echo "[DRY-RUN] port range picked to avoid colliding with any other coturn already on the box" echo "[DRY-RUN] Would open UFW ports ${WEB_PORT}/tcp, ${CALLS_UDP_PORT}/udp" return 0 fi @@ -323,16 +321,11 @@ install_mattermost() { return 0 ;; fresh) - if [ "$_HAD_EMBEDDED_COTURN" = true ]; then - echo "" - log_warning "This install has its own dedicated coturn. Continuing may switch it to" - 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." + log_warning "below from scratch, including generating a fresh dedicated coturn" + log_warning "container with new TURN credentials — the Calls plugin's TURN config in" + log_warning "System Console will need updating afterward (see below)." local _WIPE_MM_DATA="" prompt_yn " Also delete stored data (Postgres database, uploaded files, config, plugins)? (y/n):" "n" _WIPE_MM_DATA @@ -408,47 +401,31 @@ networks: " fi - # ── TURN: shared coturn preferred, dedicated coturn as fallback ───────── - # See services/coturn.sh's header for why one shared TURN server beats - # every service (Asterisk, each Mattermost instance, ...) running its - # own and fighting over host relay ports. + # ── TURN: always this instance's own dedicated coturn ─────────────────── + # There is no shared coturn service in this repo anymore (see + # attic/coturn.sh for why it was retired) — every instance runs its own. + # find_free_coturn_range (below) is what makes that safe: it checks + # every coturn-owning service's .env on the box and picks a relay range + # that can't collide with any of them. local USE_EMBEDDED_COTURN=true local TURN_HOST_VAL="" TURN_PORT_VAL="" TURN_USERNAME_VAL="" TURN_PASSWORD_VAL="" - local FORCE_EMBEDDED_COTURN="" - # Opt-out of the shared coturn preference, same as services/asterisk.sh — - # only offered on a genuinely fresh install (never re-asked on update, - # matching every other coturn-shape decision in this file) and only when - # a shared instance actually exists to opt out of. - if [ "$MODE" = "fresh" ] && [ -d "$DOCKER_DIR/coturn" ]; then - local _USE_SHARED_COTURN="" - prompt_yn "Use the shared coturn service for TURN? (n = run this instance's own dedicated coturn instead) (y/n):" "y" _USE_SHARED_COTURN - [[ "$_USE_SHARED_COTURN" =~ ^[Nn]$ ]] && FORCE_EMBEDDED_COTURN=true - fi - - if [ "$MODE" = "update" ] && [ "$_HAD_EMBEDDED_COTURN" = true ]; then - USE_EMBEDDED_COTURN=true # preserve exactly — never switch on update - elif [ "$FORCE_EMBEDDED_COTURN" = true ]; then - USE_EMBEDDED_COTURN=true - log_info "Running this instance's own dedicated coturn, as requested." - else - ensure_coturn_user "$COTURN_CONSUMER" - if [ -n "${COTURN_HOST:-}" ]; then - USE_EMBEDDED_COTURN=false - TURN_HOST_VAL="$COTURN_HOST"; TURN_PORT_VAL="$COTURN_PORT" - TURN_USERNAME_VAL="$COTURN_USERNAME"; TURN_PASSWORD_VAL="$COTURN_PASSWORD" - log_success "Using the shared coturn service — TURN username '$COTURN_USERNAME'." - else - log_info "Shared coturn unavailable — this instance will run its own dedicated coturn." - fi + # A pre-existing instance with no embedded coturn block predates this + # repo's dedicated-coturn-only model — it's still pointed at a shared + # coturn container this repo no longer installs or manages. Leave it + # running as-is (update never touches .env anyway) rather than trying + # to heal a registration against a service that no longer exists here. + if [ "$MODE" = "update" ] && [ "$_HAD_EMBEDDED_COTURN" != true ]; then + USE_EMBEDDED_COTURN=false + log_info "This instance still points at a shared coturn service, which this repo no longer installs or manages. It will keep working as long as that coturn container keeps running. Run a full reinstall (not update) to migrate to a dedicated coturn." fi [ -n "$MM_SECRET" ] || MM_SECRET=$(generate_password 48) - # A dedicated coturn here running alongside the shared instance, Asterisk's - # own, or a sibling Mattermost instance's own is the same pre-merge relay- - # port collision this repo's coturn history warns about (confirmed live: - # two independent coturns' default ranges used to overlap by ~100 UDP - # ports). find_free_coturn_range (lib/common.sh) checks every coturn- + # A dedicated coturn here running alongside Asterisk's own, a sibling + # Mattermost instance's own, or a legacy shared instance still running is + # the same pre-merge relay-port collision this repo's coturn history + # warns about (confirmed live: two independent coturns' default ranges + # used to overlap by ~100 UDP ports). find_free_coturn_range (lib/common.sh) checks every coturn- # owning service's .env on the box and picks a range starting safely past # whatever's already claimed; the historical 49153-49352 default only # survives when nothing else on the box claims a range at all. @@ -593,7 +570,9 @@ EOF ufw allow 3479/udp; ufw allow 3479/tcp ufw allow "${MM_COTURN_MIN_PORT}:${MM_COTURN_MAX_PORT}/udp" comment "Mattermost coturn relay" fi - # Shared coturn opens its own ports once, at its own install time. + # A legacy instance still on a shared coturn (USE_EMBEDDED_COTURN=false + # above) has nothing to open here — that coturn's ports were opened + # once, at its own install time, whenever that was. fi echo "" diff --git a/tools/pstn-test-check.sh b/tools/pstn-test-check.sh index 5a75919..eb46388 100755 --- a/tools/pstn-test-check.sh +++ b/tools/pstn-test-check.sh @@ -172,8 +172,9 @@ fi # Anveo-style ICE-enabled endpoints. Asterisk caches its OWN TURN_* values # in its .env at the point it was configured — testing with those (not # re-deriving fresh credentials) proves what Asterisk is actually set up -# to use, not just that the shared coturn instance works in general (that -# broader, multi-consumer check is tools/coturn-test-check.sh's job). ───────── +# to use. Every current install runs its own dedicated coturn; a box that +# still points at a legacy shared coturn instance predates that (see +# attic/coturn.sh) and can be spot-checked with attic/coturn-test-check.sh. ───────── section "coturn (TURN relay for Asterisk)" ASTERISK_ENV="$EA_DIR/.env" @@ -197,11 +198,11 @@ else if grep -q '^ coturn:' "$EA_DIR/docker-compose.yml" 2>/dev/null; then COTURN_CONTAINER="easy-asterisk-coturn" [[ "$CONTAINER" == *-do ]] && COTURN_CONTAINER="easy-asterisk-do-coturn" - ok "Using an embedded, per-Asterisk coturn ($COTURN_CONTAINER) — not the shared" - ok "instance, so tools/coturn-test-check.sh won't see this one; tested separately below." + ok "Using an embedded, per-Asterisk coturn ($COTURN_CONTAINER); tested separately below." else COTURN_CONTAINER="coturn" - ok "Using the shared coturn instance (also covered by tools/coturn-test-check.sh)" + ok "Using a legacy shared coturn instance (this repo no longer installs this shape —" + ok "see attic/coturn.sh; also covered by attic/coturn-test-check.sh)" fi if ! docker ps --format '{{.Names}}' 2>/dev/null | grep -qx "$COTURN_CONTAINER"; then @@ -210,7 +211,8 @@ else warn "turnutils_uclient not found in $COTURN_CONTAINER — skipping live allocation test" else # Plain UDP only — no -t/-T (TCP/TLS) flags. coturn is started with - # --no-tls --no-dtls (services/coturn.sh), so requesting an + # --no-tls --no-dtls (services/asterisk.sh's embedded coturn, and + # attic/coturn.sh's legacy shared one — same flags either way), so requesting an # encrypted/TCP transport here just fails the allocation outright # against a server that never offered one, misreporting a config # problem that doesn't exist. Confirmed live: this was the actual @@ -220,7 +222,7 @@ else # turnutils_uclient also refuses to run at all without either -e # or -y ("Either -e peer_address or -y must be specified", # confirmed live). -e needs an actual reachable, non-loopback peer - # to relay through — services/coturn.sh never sets + # to relay through — this repo's coturn containers never set # --allow-loopback-peers, so -e 127.0.0.1 gets rejected with # "channel bind: error 403 (Forbidden IP)" (also confirmed live, # against a real local coturn instance built to test this exact