Files
app-backup/lib/prepare-restore-test.sh

65 lines
3.4 KiB
Bash

#!/usr/bin/env bash
# Root-only installer preparation. No production SQL writes or credentials in Docker.
set -Eeuo pipefail
LIB_DIR=$(cd -- "$(dirname -- "${BASH_SOURCE[0]}")" && pwd)
# shellcheck source=lib/common.sh
source "$LIB_DIR/common.sh"
CONFIG_FILE=${1:?Configuration required}
load_config
[[ $ENABLE_RESTORE_TEST == true ]] || exit 0
guard_workspaces
# shellcheck source=lib/locking.sh
source "$LIB_DIR/locking.sh"
acquire_backup_lock 1
export APP_BACKUP_INHERIT_LOCK=1
build_plan
(( ${#DB_NAMES[@]} )) || exit 0
need docker; need timeout
docker_cmd=(docker --host=unix:///var/run/docker.sock)
if ! "${docker_cmd[@]}" info --format '{{.DockerRootDir}}' >/dev/null 2>&1; then
systemctl start docker.service
fi
docker_root=$("${docker_cmd[@]}" info --format '{{.DockerRootDir}}')
[[ $docker_root == /* ]] || die 'Cannot determine local Docker storage'
version=''
for i in "${!DB_NAMES[@]}"; do
secure_file "${DB_CNFS[i]}"
server=$("$DB_CLIENT_BIN" --defaults-extra-file="${DB_CNFS[i]}" --database="${DB_NAMES[i]}" --batch --skip-column-names --execute='SELECT VERSION()')
[[ $server =~ ^([0-9]+\.[0-9]+)\.[0-9]+.*MariaDB ]] || die 'Automatic restore-test preparation requires MariaDB; no production server changes made'
found=${BASH_REMATCH[1]}
[[ -z $version || $version == "$found" ]] || die 'Different production MariaDB series require separate restore-test configurations'
version=$found
done
image=${RESTORE_TEST_DB_IMAGE:-mariadb:$version}
[[ $image =~ ^[a-zA-Z0-9][a-zA-Z0-9._/:@-]*$ ]] || die 'Invalid restore-test image reference'
if ! "${docker_cmd[@]}" image inspect "$image" >/dev/null 2>&1; then
# Image layers are stored on Docker's filesystem, not necessarily WORKDIR.
[[ -d $docker_root ]] || die 'Docker storage is not accessible for the space check'
# shellcheck source=lib/space.sh
source "$LIB_DIR/space.sh"
ensure_space "$docker_root" 2147483648
echo "-- Preparing isolated MariaDB image: $image"
timeout --kill-after=30s 1800 "${docker_cmd[@]}" pull "$image"
fi
# Pin the probe to an immutable local image ID; never pull during monthly jobs.
image_id=$("${docker_cmd[@]}" image inspect --format '{{.Id}}' "$image")
"${docker_cmd[@]}" image inspect "$image_id" | python3 -c 'import json,sys; info=json.load(sys.stdin)[0]; assert not set(info.get("Config",{}).get("Volumes") or {}) - {"/var/lib/mysql"}, "Unexpected image volumes"'
probe=$(timeout --kill-after=10s 60 "${docker_cmd[@]}" run --rm --pull=never --network=none --read-only \
--cap-drop=ALL --security-opt=no-new-privileges --memory=128m --pids-limit=64 \
--entrypoint=mariadbd "$image_id" --no-defaults --version)
[[ $probe == *" $version."* && $probe == *MariaDB* ]] || die 'Restore-test image does not match production MariaDB series'
echo "-- MariaDB $version image verified; testing isolated initialization/import/cleanup"
# Use a private temporary config; publish the image selection only after success.
prepared=$(mktemp "${CONFIG_FILE}.prepare-XXXXXX")
trap 'rm -f -- "$prepared"' EXIT
cp -- "$CONFIG_FILE" "$prepared"
printf '\nRESTORE_TEST_DB_IMAGE=%q\n' "$image" >> "$prepared"
script="$LIB_DIR/../usr-local-sbin/app-restore-test.sh"
[[ -f $script ]] || script=/usr/local/sbin/app-restore-test.sh
bash "$script" --config "$prepared" --prepare-db
if [[ $RESTORE_TEST_DB_IMAGE != "$image" ]]; then
chmod 600 "$prepared"
mv -fT -- "$prepared" "$CONFIG_FILE"
fi
echo '-- MariaDB restore-test preparation passed; temporary database removed'