fix: start mariadb before restoring DBs in cmd_full, add lock/preflight

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>
This commit is contained in:
kbe
2026-07-25 19:42:56 +02:00
co-authored by Claude Sonnet 5
parent 8d7a603615
commit 592ef45bab
4 changed files with 138 additions and 46 deletions
+32 -1
View File
@@ -75,5 +75,36 @@ exit 0
EOF
cp "$dir/mysql" "$dir/mariadb"
chmod +x "$dir/borg" "$dir/docker" "$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"
}