asterisk-standalone-backup.sh: fix cross-layout restore (DO -> non-DO)

Live blocker: user's actual migration is DigitalOcean droplet
(asterisk-digital-ocean/, container easy-asterisk-do) -> IONOS (plain
asterisk/, container easy-asterisk) -- exactly the case this script
didn't handle. The archive's own top-level directory name and
docker-compose.yml reflect whichever layout produced it
(_asterisk_resolve_layout's two known layouts). The previous restore
extracted straight into $PARENT_DIR, which recreates whatever name is
baked into the archive -- restoring a droplet archive onto a fresh
non-droplet install would land the data at a *second*,
wrongly-named directory (asterisk-digital-ocean) alongside the
freshly-installed one it was meant to replace, with docker-compose.yml
still naming the old project/container(s). Every service that resolves
Asterisk's layout by directory/container name (security-dashboard.sh,
pstn-trunk.sh, CrowdSec's Asterisk acquisition, Caddy) would get
confused by having two candidate layouts on disk, one of them stale
and half-wired.

Fix: extract into a scratch staging directory first. If the archived
docker-compose.yml's container_name differs from this run's own
$CONTAINER (baked in at generation time, so always correct for
whichever layout THIS box's install actually uses), rewrite the
project name, container name, and coturn container name in place
(coturn's is always "$CONTAINER-coturn" on both known layouts, so no
lookup table needed) before the data ever lands at $HERE -- never
lets the archive's own naming leak through. A same-layout restore
(most common case, or two droplet boxes, or two plain boxes) detects
no mismatch and skips the rewrite entirely, unchanged from before.

Verified against the real generated script (extracted from the
heredoc, not reimplemented): a droplet-flavored archive restored onto
a fresh plain-layout box lands at the correct single directory with
no stray second directory, and docker-compose.yml's name/container_name/
coturn container_name all correctly rewritten to the plain layout
(confirmed by diffing the actual restored file, not just checking for
absence of errors); a same-layout restore (droplet archive onto a
droplet box) confirmed to skip the rewrite entirely; the pre-existing
external-IP patch (previous commit) still fires correctly stacked on
top of the layout fix; and the extraction-failure rollback path (a
corrupt/unreadable archive) still restores the pre-restore install
untouched, verified via a marker file surviving the 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 02:34:30 +00:00
parent 6dc4f0d6c6
commit be684e114d
+56 -2
View File
@@ -582,11 +582,54 @@ case "$cmd" in
mv "$HERE" "$ASIDE"
echo "Extracting $ARCHIVE -> $PARENT_DIR ..."
if tar -xzf "$ARCHIVE" -C "$PARENT_DIR"; then
# Extract into a scratch staging dir first rather than straight into
# $PARENT_DIR — an archive's own top-level directory name reflects
# whichever layout produced it (see _asterisk_resolve_layout: plain
# "asterisk"/"easy-asterisk" vs droplet "asterisk-digital-ocean"/
# "easy-asterisk-do"), which can differ from THIS box's layout (e.g.
# restoring a droplet backup onto a fresh non-droplet IONOS install).
# Landing it under the archive's own name instead of $HERE would
# leave two Asterisk directories on disk and confuse every service
# that resolves the layout by directory/container name (security-
# dashboard, pstn-trunk, CrowdSec's Asterisk acquisition, Caddy).
STAGING="$(mktemp -d)"
if tar -xzf "$ARCHIVE" -C "$STAGING"; then
EXTRACTED_DIR="$(find "$STAGING" -mindepth 1 -maxdepth 1 -type d | head -1)"
if [ -z "$EXTRACTED_DIR" ]; then
echo "Archive didn't contain a top-level directory — rolling back."
rm -rf "$STAGING"
mv "$ASIDE" "$HERE"
exit 1
fi
# If the archive came from the other layout, its docker-
# compose.yml still names the OLD project/container(s) — fix
# those to match THIS box's own layout before it lands at $HERE.
# $CONTAINER is this run's own correct value (baked in at
# generation time); everything else derives from it the same
# way _asterisk_resolve_layout's two known layouts do.
ARCHIVED_COMPOSE="$EXTRACTED_DIR/docker-compose.yml"
if [ -f "$ARCHIVED_COMPOSE" ]; then
ARCHIVED_CONTAINER="$(grep -m1 'container_name:' "$ARCHIVED_COMPOSE" | awk '{print $2}')"
if [ -n "$ARCHIVED_CONTAINER" ] && [ "$ARCHIVED_CONTAINER" != "$CONTAINER" ]; then
echo "Archive is from a different Asterisk layout ($ARCHIVED_CONTAINER) than"
echo "this box ($CONTAINER) — updating docker-compose.yml to match this box."
ARCHIVED_COTURN="${ARCHIVED_CONTAINER}-coturn"
NEW_COTURN="${CONTAINER}-coturn"
sed -i "s/name: ${ARCHIVED_CONTAINER#easy-}\$/name: ${CONTAINER#easy-}/; \
s/container_name: ${ARCHIVED_CONTAINER}\$/container_name: ${CONTAINER}/; \
s/container_name: ${ARCHIVED_COTURN}\$/container_name: ${NEW_COTURN}/" \
"$ARCHIVED_COMPOSE"
fi
fi
rm -rf "${HERE:?}"
mv "$EXTRACTED_DIR" "$HERE"
rm -rf "$STAGING"
echo "Extracted."
else
echo "Extraction failed — rolling back to the pre-restore install."
rm -rf "${PARENT_DIR:?}/$BASE_NAME"
rm -rf "$STAGING"
mv "$ASIDE" "$HERE"
exit 1
fi
@@ -1749,6 +1792,17 @@ install uses) and, if they differ, rewrites every occurrence across
and recording audio a text substitution would corrupt. A same-host restore
(rolling back a config mistake, no IP change) leaves everything untouched.
\`restore\` also handles moving between the two layouts this repo supports
(see \`_asterisk_resolve_layout\` — plain \`asterisk\`/\`easy-asterisk\` vs.
DigitalOcean-droplet \`asterisk-digital-ocean\`/\`easy-asterisk-do\`). An
archive's own directory name and \`docker-compose.yml\` reflect whichever
layout produced it; restoring a droplet backup onto a fresh non-droplet
install (or vice versa) rewrites \`docker-compose.yml\`'s project/container/
coturn-container names to match THIS box's layout and lands the data at
this box's own directory — never leaving a second, wrongly-named directory
behind that would confuse every service that resolves Asterisk's layout
(Security Dashboard, PSTN trunk, CrowdSec's Asterisk acquisition, Caddy).
## VLANs / other subnets
\`.env\` → \`HAS_VLANS\`/\`VLAN_SUBNETS\` lists extra networks (space-separated