Final review of the restore.sh branch found cmd_full restored every database via `docker exec` before starting the container, which fails immediately in the exact scenario full restore exists for (a freshly rebuilt, stopped container). Reorders to extract -> start container -> restore DBs. Also, while touching cmd_full: - Extract directly into place (cd / && borg extract) instead of staging a full copy under /tmp then cp -a'ing it into $TARGET - halves disk usage and restore time. - Replace `rm -rf "$TARGET"/*` with `find "$TARGET" -mindepth 1 -delete` so dotfiles don't survive a --force wipe. - Add acquire_lock() (shares borg-backup.sh's lockfile so a restore and the nightly backup cron can't run concurrently) and preflight() (passphrase file readable, repo reachable) before any real work in full/db/file. Test isolation: mock borg/docker/mysql/mariadb consistently via a BASH_ENV shim (previously only db-mode's test worked around PATH shadowing by a real docker binary; every mocked test needed it, and a missing `flock` mock broke everything once acquire_lock was added, since flock(1) doesn't exist on macOS). Tests also isolate LOCKFILE and BORG_PASSPHRASE_FILE to throwaway paths. RUNBOOK.md: fix the quarterly drill command (borg extract has no --destination flag, and needs `borg list --short` for a bare archive name), reword the full-restore --force comment which read backwards, and document the MYSQL_ROOT_PASSWORD/RESTORE_LOGDIR env overrides and where restore logs land. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
111 lines
2.9 KiB
Bash
111 lines
2.9 KiB
Bash
#!/bin/bash
|
|
# Builds fake borg/docker/mysql/mariadb executables into $1 so restore.sh
|
|
# can be exercised without real infrastructure. Each mock logs its
|
|
# invocation to $1/mock.log.
|
|
|
|
setup_mock_bin() {
|
|
local dir="$1"
|
|
mkdir -p "$dir"
|
|
local mocklog="$dir/mock.log"
|
|
: > "$mocklog"
|
|
|
|
cat > "$dir/borg" <<EOF
|
|
#!/bin/bash
|
|
echo "borg \$*" >> "$mocklog"
|
|
case "\$1" in
|
|
list)
|
|
echo "host-2026-01-01T00-00-00"
|
|
echo "host-2026-06-01T00-00-00"
|
|
;;
|
|
extract)
|
|
shift
|
|
paths=()
|
|
for a in "\$@"; do
|
|
case "\$a" in
|
|
-*) ;;
|
|
*::*) ;;
|
|
*) paths+=("\$a") ;;
|
|
esac
|
|
done
|
|
for p in "\${paths[@]}"; do
|
|
if [[ "\$p" == *.* ]]; then
|
|
mkdir -p "\$(dirname "\$p")"
|
|
printf 'mock-content\n' > "\$p"
|
|
else
|
|
mkdir -p "\$p"
|
|
touch "\$p/RESTORED_MARKER"
|
|
fi
|
|
done
|
|
;;
|
|
*) ;;
|
|
esac
|
|
exit 0
|
|
EOF
|
|
|
|
cat > "$dir/docker" <<EOF
|
|
#!/bin/bash
|
|
echo "docker \$*" >> "$mocklog"
|
|
case "\$1" in
|
|
inspect)
|
|
if [[ "\$*" == *"State.Running"* ]]; then
|
|
echo true
|
|
else
|
|
echo healthy
|
|
fi
|
|
;;
|
|
start|stop)
|
|
exit 0
|
|
;;
|
|
exec)
|
|
case "\$*" in
|
|
*"command -v mariadb"*) exit 0 ;;
|
|
*) cat >/dev/null 2>&1 || true; exit 0 ;;
|
|
esac
|
|
;;
|
|
*) ;;
|
|
esac
|
|
exit 0
|
|
EOF
|
|
|
|
cat > "$dir/mysql" <<EOF
|
|
#!/bin/bash
|
|
echo "mysql \$*" >> "$mocklog"
|
|
cat >/dev/null 2>&1 || true
|
|
exit 0
|
|
EOF
|
|
cp "$dir/mysql" "$dir/mariadb"
|
|
|
|
# flock(1) is Linux-only (util-linux) and absent on macOS dev machines;
|
|
# restore.sh only ever uses the "flock -n FD" form (never the
|
|
# command-wrapping form), so a no-op success is a faithful stand-in for
|
|
# exclusion testing purposes - no test here exercises actual contention.
|
|
cat > "$dir/flock" <<EOF
|
|
#!/bin/bash
|
|
echo "flock \$*" >> "$mocklog"
|
|
exit 0
|
|
EOF
|
|
|
|
chmod +x "$dir/borg" "$dir/docker" "$dir/mysql" "$dir/mariadb" "$dir/flock"
|
|
}
|
|
|
|
# restore.sh hardens PATH with system dirs (/usr/local/bin etc.) placed
|
|
# ahead of $PATH, so `PATH="$mockdir:$PATH" ...` alone does NOT guarantee the
|
|
# mock is what actually runs: a real borg/docker/mysql/mariadb installed in
|
|
# one of those system dirs would shadow it. BASH_ENV is sourced by bash
|
|
# before running a script and shell functions win over PATH lookup for
|
|
# simple commands regardless of PATH ordering, so this is what actually
|
|
# guarantees isolation on any machine, not just ones that happen to lack
|
|
# those binaries in the hardened prefix.
|
|
mock_bash_env() {
|
|
local dir="$1"
|
|
local bashenv="$dir/bash_env.sh"
|
|
cat > "$bashenv" <<EOF
|
|
borg() { "$dir/borg" "\$@"; }
|
|
docker() { "$dir/docker" "\$@"; }
|
|
mysql() { "$dir/mysql" "\$@"; }
|
|
mariadb() { "$dir/mariadb" "\$@"; }
|
|
flock() { "$dir/flock" "\$@"; }
|
|
EOF
|
|
echo "$bashenv"
|
|
}
|