#!/usr/bin/env bash set -euo pipefail CONTAINER_NAME=${STAGING_MYSQL_CONTAINER:-peipei-mysql-staging} VOLUME_NAME=${STAGING_MYSQL_VOLUME:-peipei-mysql-staging-data} HOST_PORT=${STAGING_MYSQL_PORT:-3307} DB_NAME=${STAGING_MYSQL_DB:-play-with} ROOT_PASSWORD=${STAGING_MYSQL_ROOT_PASSWORD:-root} MYSQL_IMAGE=${STAGING_MYSQL_IMAGE:-mysql:8.0.32} usage() { cat < Recreates the local staging MySQL container (${CONTAINER_NAME}) and imports the given SQL dump. Environment overrides: STAGING_MYSQL_CONTAINER, STAGING_MYSQL_VOLUME, STAGING_MYSQL_PORT, STAGING_MYSQL_DB, STAGING_MYSQL_ROOT_PASSWORD, STAGING_MYSQL_IMAGE. USAGE exit 1 } if [[ $# -ne 1 ]]; then usage fi backup_path=$1 if [[ ! -f "$backup_path" ]]; then echo "[ERROR] Backup file not found: $backup_path" >&2 exit 1 fi if ! command -v docker >/dev/null 2>&1; then echo "[ERROR] docker command not found" >&2 exit 1 fi port_in_use() { if command -v lsof >/dev/null 2>&1; then lsof -iTCP:"$HOST_PORT" -sTCP:LISTEN -Pn >/dev/null 2>&1 && return 0 fi if command -v netstat >/dev/null 2>&1; then netstat -an | grep -E "\.$HOST_PORT .*LISTEN" >/dev/null 2>&1 && return 0 fi return 1 } confirm_remove() { local prompt="$1" read -r -p "$prompt (yes/no): " reply if [[ "$reply" != "yes" ]]; then echo "操作已取消" exit 1 fi } existing_container_id=$(docker ps -a --filter "name=^${CONTAINER_NAME}$" -q) if [[ -n "$existing_container_id" ]]; then echo "[WARN] Container ${CONTAINER_NAME} already exists." confirm_remove "Remove the existing staging container and recreate it?" docker rm -f "$CONTAINER_NAME" >/dev/null fi echo "[INFO] Removing existing volume (if any): ${VOLUME_NAME}" if docker volume ls -q --filter "name=^${VOLUME_NAME}$" | grep -Fxq "$VOLUME_NAME"; then docker volume rm "$VOLUME_NAME" >/dev/null fi if port_in_use; then containers_on_port=$(docker ps --filter "publish=${HOST_PORT}" --format '{{.ID}}\t{{.Names}}') if [[ -n "$containers_on_port" ]]; then echo "[WARN] Host port ${HOST_PORT} is currently used by the following container(s):" while IFS=$'\t' read -r cid cname; do [[ -z "$cid" ]] && continue echo " - ${cname} (${cid})" done <<<"$containers_on_port" confirm_remove "Stop and remove these container(s)?" while IFS=$'\t' read -r cid cname; do [[ -z "$cid" ]] && continue docker stop "$cid" >/dev/null docker rm "$cid" >/dev/null done <<<"$containers_on_port" else echo "[ERROR] Host port ${HOST_PORT} is already in use. Set STAGING_MYSQL_PORT to an unused port or stop the process using it." >&2 exit 1 fi if port_in_use; then echo "[ERROR] Host port ${HOST_PORT} is still in use after stopping containers. Please free the port manually or change STAGING_MYSQL_PORT." >&2 exit 1 fi fi echo "[INFO] Starting fresh MySQL container ${CONTAINER_NAME}" docker run -d \ --name "$CONTAINER_NAME" \ -p "${HOST_PORT}:3306" \ -e MYSQL_ROOT_PASSWORD="$ROOT_PASSWORD" \ -e MYSQL_DATABASE="$DB_NAME" \ -v "$VOLUME_NAME:/var/lib/mysql" \ "$MYSQL_IMAGE" \ --lower_case_table_names=1 \ --explicit_defaults_for_timestamp=1 \ --character-set-server=utf8mb4 \ --collation-server=utf8mb4_unicode_ci >/dev/null echo -n "[INFO] Waiting for MySQL to accept connections" for attempt in {1..60}; do if docker exec "$CONTAINER_NAME" mysqladmin ping -h127.0.0.1 -uroot -p"$ROOT_PASSWORD" --silent >/dev/null 2>&1; then echo " - ready" break fi sleep 2 echo -n "." if [[ $attempt -eq 60 ]]; then echo "\n[ERROR] MySQL did not become ready in time" >&2 exit 1 fi done case "$backup_path" in *.gz) reader=(gzip -dc -- "$backup_path") ;; *.xz) reader=(xz -dc -- "$backup_path") ;; *.zip) reader=(unzip -p -- "$backup_path") ;; *) reader=(cat -- "$backup_path") ;; esac echo "[INFO] Importing backup $backup_path into ${DB_NAME}" "${reader[@]}" | docker exec -i "$CONTAINER_NAME" mysql -uroot -p"$ROOT_PASSWORD" --default-character-set=utf8mb4 "$DB_NAME" echo "[INFO] Staging database restored. Container ${CONTAINER_NAME} is listening on port ${HOST_PORT}."