#!/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'