From 60238814f456888d0c55fdfce3aa18c7ec6590e5 Mon Sep 17 00:00:00 2001 From: Claude Date: Sun, 16 Aug 2026 00:46:09 +0000 Subject: [PATCH] dr_bringup: support restoring from the offsite mirror on a brand-new box MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit This is what's actually needed for a DigitalOcean -> IONOS Asterisk migration (item #1 of the user's original 4-item list): move the whole stack to different hardware entirely, not restore onto the box the offsite mirror already targets. dr_bringup_kopia.sh only ever scanned DEST_NAMES for restorable snapshots — those are always local filesystem Kopia repos (services/backup.sh creates them with `repository create filesystem --path=...`), meaning they only exist on whichever box originally ran the backup. On a genuinely new box, every one of them fails to connect and there's nothing left to restore from — the script's own header comment only covered the case where "the spare box IS the box the primary's mirror targets" (i.e. already holds a copy of the repo data), not a fresh, unrelated box. Fix: also try REMOTE_TYPE/REMOTE_ARGS (the offsite Backblaze/S3 mirror, if configured) as a same-shaped destination named "offsite", reusing DEST_default_PASSWORD since sync-to always mirrors that exact same encrypted repo. Connects once into a fresh local config file scoped to this DR run, then folds into the existing per-destination scan/restore loop unchanged — "offsite" just becomes another entry in _DEST_ARR. Documented the actual migration workflow in the header comment, including the BACKUP_CONF override so copying the old box's backup.conf over doesn't clobber the new box's own freshly-configured one. Verified against the real script (not a reimplementation) with mocked kopia/docker binaries and a crafted backup.conf, covering: local dest unreachable + offsite connects successfully (the actual migration shape) with correct service/path discovery; a real (non---list) restore run confirmed it selects the latest of multiple snapshots by startTime and issues the correct `kopia restore ` call; offsite connect failing (bad REMOTE_ARGS) warns and degrades to "no restorable sources found" instead of crashing; REMOTE_TYPE=none skips the new code path entirely with no behavior change (regression check against the pre-existing local-only case). Co-Authored-By: Claude Sonnet 5 Claude-Session: https://claude.ai/code/session_01H4k6J1qXXyYxhGEgnJaMvn --- extras/dr_bringup_kopia.sh | 52 +++++++++++++++++++++++++++++++++++--- 1 file changed, 48 insertions(+), 4 deletions(-) diff --git a/extras/dr_bringup_kopia.sh b/extras/dr_bringup_kopia.sh index 48ca048..45f781b 100644 --- a/extras/dr_bringup_kopia.sh +++ b/extras/dr_bringup_kopia.sh @@ -15,13 +15,29 @@ # sudo ./dr_bringup.sh --dry-run show what would happen, touch nothing # sudo ./dr_bringup.sh --no-start restore only, skip docker compose up -d # -# Reads backup.conf from the same directory. On the spare box this file -# won't exist yet on its own — copy it over from the primary box first (it -# holds the repository paths/passwords needed to connect): +# Reads backup.conf from the same directory (or wherever $BACKUP_CONF +# points — see below). On the spare box this file won't exist yet on its +# own — copy it over from the primary box first (it holds the repository +# paths/passwords needed to connect): # scp primary:~/docker/backup/backup.conf ~/docker/backup/backup.conf # If the destination repo is a local path shared with the primary (e.g. the # spare box IS the box the primary's REMOTE_TYPE=sftp mirror targets), -# nothing else is needed — the repo data is already there. +# nothing else is needed — the repo data is already there. Otherwise this +# also tries REMOTE_TYPE/REMOTE_ARGS (the offsite Backblaze/S3 mirror, if +# configured) as a restore source — the only one reachable from a genuinely +# new box (e.g. migrating to different hardware/a different provider). +# +# Migrating to a brand-new box that will keep running this repo's own +# backup.sh going forward: don't just scp the old box's backup.conf over +# the new box's own (that would replace its freshly-configured local repo +# with the old box's, which doesn't exist on this box). Instead: +# sudo ./setup.sh backup # sets up this box's own backups, +# # and deploys this script +# scp old-box:~/docker/backup/backup.conf ~/docker/backup/backup-source.conf +# sudo BACKUP_CONF=~/docker/backup/backup-source.conf \ +# ~/docker/backup/dr_bringup.sh --list # preview first +# sudo BACKUP_CONF=~/docker/backup/backup-source.conf \ +# ~/docker/backup/dr_bringup.sh # then actually restore set -uo pipefail HERE="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)" @@ -87,6 +103,34 @@ k_for() { read -ra _DEST_ARR <<< "$DEST_NAMES" declare -a SRC_PATH_LIST=() SRC_DEST_LIST=() SRC_SNAP_LIST=() +# DEST_NAMES entries are always LOCAL filesystem repos (services/backup.sh +# creates them with `repository create filesystem --path=...`) — meaning +# they only exist on whichever box originally ran the backup. On a genuinely +# new box (migrating to different hardware/provider, not restoring onto the +# same DR-spare the offsite mirror already targets), none of them will +# connect, and without this block there would be nothing left to restore +# from at all. REMOTE_TYPE/REMOTE_ARGS (the offsite Backblaze/S3 mirror) is +# reachable from anywhere, so try it too, as a same-shaped destination named +# "offsite" — reusing DEST_default_PASSWORD since sync-to always mirrors +# that exact same encrypted repo, so there's no separate password to ask +# for. Connect once into a fresh local config file scoped to this DR run +# (no pre-existing config for it the way the local DEST_NAMES entries have). +if [ -n "${REMOTE_TYPE:-}" ] && [ "$REMOTE_TYPE" != "none" ] && [ -n "${DEST_default_PASSWORD:-}" ]; then + OFFSITE_CFG="/etc/kopia-backup/dr-offsite.config" + mkdir -p "$(dirname "$OFFSITE_CFG")" /var/cache/kopia-backup + # shellcheck disable=SC2086 + if env KOPIA_PASSWORD="$DEST_default_PASSWORD" "$KOPIA" --config-file="$OFFSITE_CFG" repository status >/dev/null 2>&1 \ + || env KOPIA_PASSWORD="$DEST_default_PASSWORD" "$KOPIA" --config-file="$OFFSITE_CFG" \ + --cache-directory=/var/cache/kopia-backup repository connect $REMOTE_TYPE $REMOTE_ARGS >/dev/null 2>&1; then + DEST_offsite_CONFIG="$OFFSITE_CFG" + DEST_offsite_PASSWORD="$DEST_default_PASSWORD" + _DEST_ARR+=("offsite") + info "Connected to the offsite mirror ($REMOTE_TYPE) as an additional restore source ('offsite')." + else + warn "Could not connect to the offsite mirror ($REMOTE_TYPE) as a restore source — check REMOTE_TYPE/REMOTE_ARGS in $CONF." + fi +fi + for dest in "${_DEST_ARR[@]}"; do if ! k_for "$dest" repository status >/dev/null 2>&1; then warn "Cannot connect to destination '$dest' — skipping."