asterisk-standalone-backup.sh: fix external IP on cross-box restore

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 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01H4k6J1qXXyYxhGEgnJaMvn
This commit is contained in:
Claude
2026-08-16 01:02:28 +00:00
parent 60238814f4
commit 6dc4f0d6c6
+44
View File
@@ -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