From 6dc4f0d6c6dc45c18311877f079bfac7a3479b29 Mon Sep 17 00:00:00 2001 From: Claude Date: Sun, 16 Aug 2026 01:02:28 +0000 Subject: [PATCH] asterisk-standalone-backup.sh: fix external IP on cross-box restore MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit User confirmed via a live pjsip.conf on their actual DO box: both external_media_address and external_signaling_address are literal IPs, written by easy-asterisk at first container start (not something this repo's install script controls directly). A straight restore of a backup archive from a DIFFERENT box onto the new IONOS box would leave the OLD DigitalOcean IP baked into pjsip.conf — dialplan and PJSIP device credentials would come back fine, but RTP media (and likely SIP signaling/registration) would stay broken, silently, since nothing in the restore path previously touched these values. Fix: after extracting the archive, `restore` reads the archive's own external_signaling_address as "old IP", detects this host's actual current public IP (same DO-metadata -> ifconfig.me -> hostname -I fallback chain services/asterisk.sh's own install already uses), and if they differ, rewrites every occurrence across config/ and .env (fixed-string match, not a regex, so the IP's dots can't be misinterpreted). Deliberately does NOT touch spool/, logs/, or lib/ — those hold voicemail messages and call recordings, and a blind text substitution across binary audio would corrupt it. A restore onto the same host (e.g. rolling back a bad config change, no IP change) leaves every file untouched — the check only fires on an actual mismatch. Verified against the real generated script (extracted verbatim from the heredoc, not a reimplementation) with a full mock backup/restore cycle: built a fixture archive with pjsip.conf's three transport blocks (udp/tcp/tls) and .env's TURN_SERVER all hardcoded to a fake "old box" IP, plus a fake binary voicemail file; restored it onto a mocked "new box" with a different detected IP via a stubbed curl. Confirmed every occurrence in both pjsip.conf and .env was correctly rewritten to the new IP, and confirmed via byte-for-byte comparison that the binary voicemail file was completely untouched. Separately verified the same-IP case (mocked curl returning the archive's own IP) makes no changes at all, matching a same-host config rollback. Co-Authored-By: Claude Sonnet 5 Claude-Session: https://claude.ai/code/session_01H4k6J1qXXyYxhGEgnJaMvn --- services/asterisk.sh | 44 ++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 44 insertions(+) diff --git a/services/asterisk.sh b/services/asterisk.sh index 701f83f..50248be 100644 --- a/services/asterisk.sh +++ b/services/asterisk.sh @@ -591,6 +591,37 @@ case "$cmd" in exit 1 fi + # pjsip.conf bakes external_media_address/external_signaling_address + # in as literal IPs (easy-asterisk writes them at first container + # start, not this repo) — restoring an archive from a DIFFERENT box + # verbatim leaves the OLD box's IP in place, breaking RTP media (and + # likely SIP signaling) on THIS box even though the dialplan itself + # comes back fine. Detect the mismatch and patch it automatically — + # this is the whole reason "restore" onto a new host exists, so a + # stale IP left behind would defeat the point every single time. + PJSIP_CONF="$HERE/config/asterisk/pjsip.conf" + if [ -f "$PJSIP_CONF" ]; then + OLD_EXT_IP="$(grep -m1 '^external_signaling_address=' "$PJSIP_CONF" | cut -d= -f2)" + NEW_EXT_IP="$(curl -fsS --max-time 2 http://169.254.169.254/metadata/v1/interfaces/public/0/ipv4/address 2>/dev/null || true)" + [ -z "$NEW_EXT_IP" ] && NEW_EXT_IP="$(curl -fsS --max-time 3 https://ifconfig.me 2>/dev/null || true)" + [ -z "$NEW_EXT_IP" ] && NEW_EXT_IP="$(hostname -I 2>/dev/null | awk '{print $1}')" + + if [ -n "$OLD_EXT_IP" ] && [ -n "$NEW_EXT_IP" ] && [ "$OLD_EXT_IP" != "$NEW_EXT_IP" ]; then + echo "This archive's SIP config was for a different box's public IP" + echo "($OLD_EXT_IP) — this box's is $NEW_EXT_IP. Updating" + echo "external_media_address/external_signaling_address so RTP/SIP work here." + # Fixed-string match/replace, restricted to config + .env — + # never spool/logs/lib, which hold voicemail/recording audio + # that a text substitution would corrupt. + ESC_OLD_IP="${OLD_EXT_IP//./\\.}" + grep -rlF "$OLD_EXT_IP" "$HERE/config" "$HERE/.env" 2>/dev/null | while read -r _f; do + sed -i "s/$ESC_OLD_IP/$NEW_EXT_IP/g" "$_f" + done + echo "Updated. If this PBX uses VLANs/extra subnets, re-check them:" + echo " docker exec -it $CONTAINER easy-asterisk # Server Settings -> Configure VLAN/VPN Subnets" + fi + fi + chown -R "$ACTUAL_USER:$ACTUAL_USER" "$HERE" if [ "$WAS_RUNNING" = true ]; then @@ -1705,6 +1736,19 @@ To migrate to a new host: run \`backup\` on the old one, copy the archive over, then run \`restore\` after a fresh \`sudo ./setup.sh asterisk\` install (or directly into an empty \`${EA_DIR}\`) on the new host. +\`restore\` also detects and fixes the one thing that doesn't travel between +boxes on its own: \`pjsip.conf\`'s \`external_media_address\`/ +\`external_signaling_address\` are literal IPs, baked in by easy-asterisk at +first container start — restoring an archive from a different box verbatim +would otherwise leave the OLD box's IP in place, breaking RTP media (and +likely SIP registration) even though the dialplan itself comes back fine. +\`restore\` compares the archive's IP against this host's own (same +DO-metadata → ifconfig.me → \`hostname -I\` detection this script's own +install uses) and, if they differ, rewrites every occurrence across +\`config/\` and \`.env\` — never \`spool/\`/\`logs/\`/\`lib/\`, which hold voicemail +and recording audio a text substitution would corrupt. A same-host restore +(rolling back a config mistake, no IP change) leaves everything untouched. + ## VLANs / other subnets \`.env\` → \`HAS_VLANS\`/\`VLAN_SUBNETS\` lists extra networks (space-separated