#!/usr/bin/env bash # Restores a backup INTO STAGING ONLY. Deliberately has no production path — # the launch gate is "a full backup has been restored into staging," never # production overwritten by a drill. set -euo pipefail DB_DUMP="${1:?Usage: restore.sh }" UPLOADS_ARCHIVE="${2:?Usage: restore.sh }" REPO_ROOT="$(cd "$(dirname "${BASH_SOURCE[0]}")/.." && pwd)" cd "$REPO_ROOT" ENV_FILE=".env.staging" export ENVIRONMENT=staging # shellcheck source=lib/env.sh source "$REPO_ROOT/deploy/lib/env.sh" DB_NAME_VALUE="$(env_get "$ENV_FILE" DB_NAME)" : "${DB_NAME_VALUE:?set DB_NAME in ${ENV_FILE}}" # See deploy.sh for why: Compose warns on any $-shaped value in --env-file # even when unused, so DB_PASSWORD/DB_ROOT_PASSWORD are filtered out of the # copy Compose actually sees. COMPOSE_ENV_FILE="$(mktemp)" trap 'rm -f "$COMPOSE_ENV_FILE"' EXIT grep -Ev '^(DB_PASSWORD|DB_ROOT_PASSWORD)=' "$ENV_FILE" > "$COMPOSE_ENV_FILE" COMPOSE="docker compose -p bookstore-staging -f docker-compose.yml -f docker-compose.staging.yml --env-file ${COMPOSE_ENV_FILE}" echo "!! this OVERWRITES the staging database and uploads !!" read -r -p "type 'restore' to continue: " confirm [[ "$confirm" == "restore" ]] || { echo "aborted"; exit 1; } echo "==> dropping and recreating the staging database for a clean restore" # Without this, a table present in staging but absent from the dump (a stray # leftover from before a schema change, a one-off test table) would silently # survive "restore" and go undetected — a drill could pass while masking data # genuinely missing from the backup. Grants tied to a non-root user are keyed # to the database NAME in MariaDB's privilege tables, not the schema object's # identity, so MARIADB_USER's access survives a same-name drop+recreate # (verified directly against a throwaway database before relying on this). $COMPOSE exec -T db sh -c "exec mariadb -u root -p\"\$(cat /run/secrets/db_root_password)\" -e 'DROP DATABASE IF EXISTS \`${DB_NAME_VALUE}\`; CREATE DATABASE \`${DB_NAME_VALUE}\`;'" echo "==> restoring database" gunzip -c "$DB_DUMP" | $COMPOSE exec -T db sh -c "exec mariadb -u\"\$MARIADB_USER\" -p\"\$(cat /run/secrets/db_password)\" \"\$MARIADB_DATABASE\"" echo "==> clearing existing uploads before restoring" # Same reasoning as the database above: extracting on top of whatever's # already there would let a file missing from the archive hide behind one # that happens to still be present from before the drill. Runs as root # (unlike the tar extraction below), not www-data: the uploads directory # itself can be root:root on a volume that's never had a www-data-owned # write land in it yet (the official image's entrypoint creates it on first # boot, before dropping to www-data) — confirmed directly against a fresh # staging volume. chown afterward so the subsequent www-data tar extraction # below can actually write into it. $COMPOSE run --rm -T wordpress sh -c \ "find /var/www/html/wp-content/uploads -mindepth 1 -delete && chown www-data:www-data /var/www/html/wp-content/uploads" echo "==> restoring uploads" ARCHIVE_DIR="$(cd "$(dirname "$UPLOADS_ARCHIVE")" && pwd)" ARCHIVE_NAME="$(basename "$UPLOADS_ARCHIVE")" $COMPOSE run --rm -T -u www-data -v "${ARCHIVE_DIR}:/restore:ro" wordpress \ tar -xzf "/restore/${ARCHIVE_NAME}" -C /var/www/html/wp-content echo "==> restore complete — verify the site before treating the drill as passed"