From 29ff0a263797ba08c0402280604a2e07ef6abf71 Mon Sep 17 00:00:00 2001 From: irving Date: Fri, 7 Nov 2025 22:38:47 -0500 Subject: [PATCH] feat: add flyway cli wrapper and staging restore --- README.md | 28 ++ flyway.sh | 92 ++++++ flyway/api-test.conf | 10 + flyway/dev.conf | 10 + flyway/prod.conf | 10 + flyway/staging.conf | 10 + .../main/resources/application-apitest.yml | 3 +- .../V16__align_legacy_tables_collation.sql | 291 ++++++++++++++++++ recreate-staging.sh | 137 +++++++++ 9 files changed, 590 insertions(+), 1 deletion(-) create mode 100755 flyway.sh create mode 100644 flyway/api-test.conf create mode 100644 flyway/dev.conf create mode 100644 flyway/prod.conf create mode 100644 flyway/staging.conf create mode 100644 play-admin/src/main/resources/db/migration/V16__align_legacy_tables_collation.sql create mode 100755 recreate-staging.sh diff --git a/README.md b/README.md index f59620b..939286b 100644 --- a/README.md +++ b/README.md @@ -134,6 +134,34 @@ mvn spotless:apply compile mvn spotless:apply checkstyle:check compile ``` +## 数据库迁移 + +仓库根目录下提供 `flyway/` 配置与 `flyway.sh` 工具脚本,用于在不同环境执行迁移。 + +### Profile 与配置 + +| Profile | 配置文件 | 用途 | +|------------|-----------------------|------------------------------| +| `dev` | `flyway/dev.conf` | 本地 `play-with` 开发库 | +| `staging` | `flyway/staging.conf` | 生产克隆 / 本地 staging 库 | +| `api-test` | `flyway/api-test.conf`| API 集成测试数据库 | +| `prod` | `flyway/prod.conf` | 线上生产库 | + +### 示例 + +```bash +# 校验本地 schema +./flyway.sh validate --profile dev + +# 对 API 测试库执行迁移 +./flyway.sh migrate --profile api-test + +# 修复 staging 库 +./flyway.sh repair --profile staging +``` + +当对 `prod` profile 执行 `migrate` 或 `repair` 时,脚本会连续两次提示“你备份数据库了吗?”以避免误操作,输入 `yes` 才会继续。 + ## API 集成测试指南 在 `play-admin` 模块内提供了基于 `apitest` Profile 的端到端测试套件。为了稳定跑通所有 API 场景,请按以下步骤准备环境: diff --git a/flyway.sh b/flyway.sh new file mode 100755 index 0000000..c29229d --- /dev/null +++ b/flyway.sh @@ -0,0 +1,92 @@ +#!/usr/bin/env bash +set -euo pipefail + +SCRIPT_DIR="$(cd -- "$(dirname -- "${BASH_SOURCE[0]}")" && pwd)" +CONFIG_DIR="$SCRIPT_DIR/flyway" + +usage() { + cat < --profile + +Examples: + ./flyway.sh validate --profile dev + ./flyway.sh migrate --profile api-test +USAGE + exit 1 +} + +if [[ $# -lt 2 ]]; then + usage +fi + +action="$1" +shift +case "$action" in + migrate|validate|repair) ;; + *) echo "[ERROR] Unsupported action: $action" >&2; usage ;; +esac + +profile="" +while [[ $# -gt 0 ]]; do + case "$1" in + --profile|-p) + [[ $# -ge 2 ]] || usage + profile="$2" + shift 2 + ;; + *) + echo "[ERROR] Unknown argument: $1" >&2 + usage + ;; + esac +done + +if [[ -z "$profile" ]]; then + usage +fi + +case "$profile" in + api-test|apitest) + profile="api-test" + config_file="$CONFIG_DIR/api-test.conf" + ;; + dev) + config_file="$CONFIG_DIR/dev.conf" + ;; + staging) + config_file="$CONFIG_DIR/staging.conf" + ;; + prod) + config_file="$CONFIG_DIR/prod.conf" + ;; + *) + echo "[ERROR] Unknown profile: $profile" >&2 + usage + ;; +esac + +if [[ ! -f "$config_file" ]]; then + echo "[ERROR] Config file not found: $config_file" >&2 + exit 1 +fi + +confirm_backup() { + local prompt="$1" + read -r -p "$prompt (yes/no): " reply + if [[ "$reply" != "yes" ]]; then + echo "操作已取消" + exit 1 + fi +} + +if [[ "$profile" == "prod" && ( "$action" == "migrate" || "$action" == "repair" ) ]]; then + confirm_backup "你备份数据库了吗?" + confirm_backup "你真的备份了吗?" +fi + +exec mvn \ + -f "$SCRIPT_DIR/pom.xml" \ + -pl play-admin \ + -DskipTests \ + "flyway:$action" \ + "-Dflyway.configFiles=$config_file" diff --git a/flyway/api-test.conf b/flyway/api-test.conf new file mode 100644 index 0000000..eb32eed --- /dev/null +++ b/flyway/api-test.conf @@ -0,0 +1,10 @@ +flyway.url=jdbc:mysql://127.0.0.1:33306/peipei_apitest?useUnicode=true&characterEncoding=utf8&characterSetResults=utf8 +flyway.user=apitest +flyway.password=apitest +flyway.locations=classpath:db/migration +flyway.table=admin_flyway_schema_history +flyway.baselineOnMigrate=true +flyway.baselineVersion=1 +flyway.cleanDisabled=true +flyway.outOfOrder=false +flyway.validateOnMigrate=false diff --git a/flyway/dev.conf b/flyway/dev.conf new file mode 100644 index 0000000..b2e7de9 --- /dev/null +++ b/flyway/dev.conf @@ -0,0 +1,10 @@ +flyway.url=jdbc:mysql://127.0.0.1:3307/play-with?useUnicode=true&characterEncoding=utf8&characterSetResults=utf8 +flyway.user=root +flyway.password=root +flyway.locations=classpath:db/migration +flyway.table=admin_flyway_schema_history +flyway.baselineOnMigrate=true +flyway.baselineVersion=1 +flyway.cleanDisabled=true +flyway.outOfOrder=false +flyway.validateOnMigrate=false diff --git a/flyway/prod.conf b/flyway/prod.conf new file mode 100644 index 0000000..f28cfd2 --- /dev/null +++ b/flyway/prod.conf @@ -0,0 +1,10 @@ +flyway.url=jdbc:mysql://122.51.20.105:3306/play-with?useUnicode=true&characterEncoding=utf8&zeroDateTimeBehavior=convertToNull&useSSL=true&serverTimezone=GMT%2B8&allowMultiQueries=true&rewriteBatchedStatements=true +flyway.user=root +flyway.password=KdaKRZ2trpdhNePa +flyway.locations=classpath:db/migration +flyway.table=admin_flyway_schema_history +flyway.baselineOnMigrate=true +flyway.baselineVersion=1 +flyway.cleanDisabled=true +flyway.outOfOrder=false +flyway.validateOnMigrate=true diff --git a/flyway/staging.conf b/flyway/staging.conf new file mode 100644 index 0000000..b2e7de9 --- /dev/null +++ b/flyway/staging.conf @@ -0,0 +1,10 @@ +flyway.url=jdbc:mysql://127.0.0.1:3307/play-with?useUnicode=true&characterEncoding=utf8&characterSetResults=utf8 +flyway.user=root +flyway.password=root +flyway.locations=classpath:db/migration +flyway.table=admin_flyway_schema_history +flyway.baselineOnMigrate=true +flyway.baselineVersion=1 +flyway.cleanDisabled=true +flyway.outOfOrder=false +flyway.validateOnMigrate=false diff --git a/play-admin/src/main/resources/application-apitest.yml b/play-admin/src/main/resources/application-apitest.yml index 03cdf38..b22d679 100644 --- a/play-admin/src/main/resources/application-apitest.yml +++ b/play-admin/src/main/resources/application-apitest.yml @@ -13,13 +13,14 @@ spring: datasource: type: com.alibaba.druid.pool.DruidDataSource driverClassName: com.mysql.cj.jdbc.Driver - url: jdbc:mysql://localhost:33306/peipei_apitest?useUnicode=true&characterEncoding=utf8&serverTimezone=Asia/Shanghai&useSSL=false&allowMultiQueries=true&rewriteBatchedStatements=true + url: jdbc:mysql://localhost:33306/peipei_apitest?useUnicode=true&characterEncoding=utf8&serverTimezone=Asia/Shanghai&useSSL=false&allowMultiQueries=true&rewriteBatchedStatements=true&connectionCollation=utf8mb4_general_ci&sessionVariables=collation_connection=utf8mb4_general_ci username: apitest password: apitest druid: enable: true db-type: mysql filters: stat,wall + connection-init-sqls: SET NAMES utf8mb4 COLLATE utf8mb4_general_ci max-active: 20 initial-size: 1 max-wait: 60000 diff --git a/play-admin/src/main/resources/db/migration/V16__align_legacy_tables_collation.sql b/play-admin/src/main/resources/db/migration/V16__align_legacy_tables_collation.sql new file mode 100644 index 0000000..afbc205 --- /dev/null +++ b/play-admin/src/main/resources/db/migration/V16__align_legacy_tables_collation.sql @@ -0,0 +1,291 @@ +-- Align legacy tables with production: ensure core order/earnings tables use utf8mb4_general_ci. +-- Safe to run multiple times; already-general_ci tables will simply rebuild without data changes. + +-- Some historical tables (e.g. sys_tenant_recharge_info) still rely on zero-datetime +-- defaults. MySQL 8 with NO_ZERO_DATE / NO_ZERO_IN_DATE rejects those definitions +-- when the engine rebuilds the table. Temporarily drop those sql_mode flags so the +-- table structure matches production before/after this migration. +SET @OLD_SQL_MODE = @@sql_mode; +SET SESSION sql_mode = REPLACE(REPLACE(@OLD_SQL_MODE, 'NO_ZERO_IN_DATE', ''), 'NO_ZERO_DATE', ''); + +ALTER TABLE `play_order_info` + CONVERT TO CHARACTER SET utf8mb4 + COLLATE utf8mb4_general_ci; + +ALTER TABLE `play_order_log_info` + CONVERT TO CHARACTER SET utf8mb4 + COLLATE utf8mb4_general_ci; + +ALTER TABLE `play_order_random_info` + CONVERT TO CHARACTER SET utf8mb4 + COLLATE utf8mb4_general_ci; + +ALTER TABLE `play_order_refund_info` + CONVERT TO CHARACTER SET utf8mb4 + COLLATE utf8mb4_general_ci; + +ALTER TABLE `play_order_continue_info` + CONVERT TO CHARACTER SET utf8mb4 + COLLATE utf8mb4_general_ci; + +ALTER TABLE `play_order_demand_info` + CONVERT TO CHARACTER SET utf8mb4 + COLLATE utf8mb4_general_ci; + +ALTER TABLE `play_order_evaluate_info` + CONVERT TO CHARACTER SET utf8mb4 + COLLATE utf8mb4_general_ci; + +ALTER TABLE `play_custom_user_info` + CONVERT TO CHARACTER SET utf8mb4 + COLLATE utf8mb4_general_ci; + +ALTER TABLE `play_custom_gift_info` + CONVERT TO CHARACTER SET utf8mb4 + COLLATE utf8mb4_general_ci; + +ALTER TABLE `play_custom_article_info` + CONVERT TO CHARACTER SET utf8mb4 + COLLATE utf8mb4_general_ci; + +ALTER TABLE `play_custom_follow_info` + CONVERT TO CHARACTER SET utf8mb4 + COLLATE utf8mb4_general_ci; + +ALTER TABLE `play_custom_leave_msg` + CONVERT TO CHARACTER SET utf8mb4 + COLLATE utf8mb4_general_ci; + +ALTER TABLE `play_custom_level_info` + CONVERT TO CHARACTER SET utf8mb4 + COLLATE utf8mb4_general_ci; + +ALTER TABLE `play_clerk_user_info` + CONVERT TO CHARACTER SET utf8mb4 + COLLATE utf8mb4_general_ci; + +ALTER TABLE `play_clerk_user_review_info` + CONVERT TO CHARACTER SET utf8mb4 + COLLATE utf8mb4_general_ci; + +ALTER TABLE `play_clerk_level_info` + CONVERT TO CHARACTER SET utf8mb4 + COLLATE utf8mb4_general_ci; + +ALTER TABLE `play_clerk_gift_info` + CONVERT TO CHARACTER SET utf8mb4 + COLLATE utf8mb4_general_ci; + +ALTER TABLE `play_clerk_classification_info` + CONVERT TO CHARACTER SET utf8mb4 + COLLATE utf8mb4_general_ci; + +ALTER TABLE `play_clerk_type_info` + CONVERT TO CHARACTER SET utf8mb4 + COLLATE utf8mb4_general_ci; + +ALTER TABLE `play_clerk_type_user_info` + CONVERT TO CHARACTER SET utf8mb4 + COLLATE utf8mb4_general_ci; + +ALTER TABLE `play_personnel_group_info` + CONVERT TO CHARACTER SET utf8mb4 + COLLATE utf8mb4_general_ci; + +ALTER TABLE `play_personnel_admin_info` + CONVERT TO CHARACTER SET utf8mb4 + COLLATE utf8mb4_general_ci; + +ALTER TABLE `play_personnel_waiter_info` + CONVERT TO CHARACTER SET utf8mb4 + COLLATE utf8mb4_general_ci; + +ALTER TABLE `play_coupon_info` + CONVERT TO CHARACTER SET utf8mb4 + COLLATE utf8mb4_general_ci; + +ALTER TABLE `play_coupon_details` + CONVERT TO CHARACTER SET utf8mb4 + COLLATE utf8mb4_general_ci; + +ALTER TABLE `play_gift_info` + CONVERT TO CHARACTER SET utf8mb4 + COLLATE utf8mb4_general_ci; + +ALTER TABLE `sys_user` + CONVERT TO CHARACTER SET utf8mb4 + COLLATE utf8mb4_general_ci; + +ALTER TABLE `sys_user_role` + CONVERT TO CHARACTER SET utf8mb4 + COLLATE utf8mb4_general_ci; + +ALTER TABLE `sys_role` + CONVERT TO CHARACTER SET utf8mb4 + COLLATE utf8mb4_general_ci; + +ALTER TABLE `sys_role_menu` + CONVERT TO CHARACTER SET utf8mb4 + COLLATE utf8mb4_general_ci; + +ALTER TABLE `sys_role_dept` + CONVERT TO CHARACTER SET utf8mb4 + COLLATE utf8mb4_general_ci; + +ALTER TABLE `sys_dept` + CONVERT TO CHARACTER SET utf8mb4 + COLLATE utf8mb4_general_ci; + +ALTER TABLE `sys_menu` + CONVERT TO CHARACTER SET utf8mb4 + COLLATE utf8mb4_general_ci; + +ALTER TABLE `sys_tenant` + CONVERT TO CHARACTER SET utf8mb4 + COLLATE utf8mb4_general_ci; + +ALTER TABLE `sys_tenant_package` + CONVERT TO CHARACTER SET utf8mb4 + COLLATE utf8mb4_general_ci; + +ALTER TABLE `sys_tenant_recharge_info` + CONVERT TO CHARACTER SET utf8mb4 + COLLATE utf8mb4_general_ci; + +ALTER TABLE `blind_box_config` + CONVERT TO CHARACTER SET utf8mb4 + COLLATE utf8mb4_general_ci; + +ALTER TABLE `blind_box_pool` + CONVERT TO CHARACTER SET utf8mb4 + COLLATE utf8mb4_general_ci; + +ALTER TABLE `blind_box_reward` + CONVERT TO CHARACTER SET utf8mb4 + COLLATE utf8mb4_general_ci; + +ALTER TABLE `commodity_info` + CONVERT TO CHARACTER SET utf8mb4 + COLLATE utf8mb4_general_ci; + +ALTER TABLE `gen_table` + CONVERT TO CHARACTER SET utf8mb4 + COLLATE utf8mb4_general_ci; + +ALTER TABLE `gen_table_column` + CONVERT TO CHARACTER SET utf8mb4 + COLLATE utf8mb4_general_ci; + +ALTER TABLE `order_details_info` + CONVERT TO CHARACTER SET utf8mb4 + COLLATE utf8mb4_general_ci; + +ALTER TABLE `order_log_info` + CONVERT TO CHARACTER SET utf8mb4 + COLLATE utf8mb4_general_ci; + +ALTER TABLE `play_account info` + CONVERT TO CHARACTER SET utf8mb4 + COLLATE utf8mb4_general_ci; + +ALTER TABLE `play_avatar_frame_info` + CONVERT TO CHARACTER SET utf8mb4 + COLLATE utf8mb4_general_ci; + +ALTER TABLE `play_balance_details_info` + CONVERT TO CHARACTER SET utf8mb4 + COLLATE utf8mb4_general_ci; + +ALTER TABLE `play_clerk_article_info` + CONVERT TO CHARACTER SET utf8mb4 + COLLATE utf8mb4_general_ci; + +ALTER TABLE `play_clerk_commodity_info` + CONVERT TO CHARACTER SET utf8mb4 + COLLATE utf8mb4_general_ci; + +ALTER TABLE `play_clerk_data_review_info` + CONVERT TO CHARACTER SET utf8mb4 + COLLATE utf8mb4_general_ci; + +ALTER TABLE `play_clerk_operation_log` + CONVERT TO CHARACTER SET utf8mb4 + COLLATE utf8mb4_general_ci; + +ALTER TABLE `play_clerk_pk` + CONVERT TO CHARACTER SET utf8mb4 + COLLATE utf8mb4_general_ci; + +ALTER TABLE `play_clerk_ranking_info` + CONVERT TO CHARACTER SET utf8mb4 + COLLATE utf8mb4_general_ci; + +ALTER TABLE `play_clerk_resource info` + CONVERT TO CHARACTER SET utf8mb4 + COLLATE utf8mb4_general_ci; + +ALTER TABLE `play_clerk_wages_details_info` + CONVERT TO CHARACTER SET utf8mb4 + COLLATE utf8mb4_general_ci; + +ALTER TABLE `play_clerk_wages_info` + CONVERT TO CHARACTER SET utf8mb4 + COLLATE utf8mb4_general_ci; + +ALTER TABLE `play_clerk_waiter_info` + CONVERT TO CHARACTER SET utf8mb4 + COLLATE utf8mb4_general_ci; + +ALTER TABLE `shop_ui_setting` + CONVERT TO CHARACTER SET utf8mb4 + COLLATE utf8mb4_general_ci; + +ALTER TABLE `sys_administrative_area_dict_info` + CONVERT TO CHARACTER SET utf8mb4 + COLLATE utf8mb4_general_ci; + +ALTER TABLE `sys_dict` + CONVERT TO CHARACTER SET utf8mb4 + COLLATE utf8mb4_general_ci; + +ALTER TABLE `sys_dict_data` + CONVERT TO CHARACTER SET utf8mb4 + COLLATE utf8mb4_general_ci; + +ALTER TABLE `sys_login_log` + CONVERT TO CHARACTER SET utf8mb4 + COLLATE utf8mb4_general_ci; + +ALTER TABLE `sys_operation_log` + CONVERT TO CHARACTER SET utf8mb4 + COLLATE utf8mb4_general_ci; +ALTER TABLE `play_shop_article_info` + CONVERT TO CHARACTER SET utf8mb4 + COLLATE utf8mb4_general_ci; + +ALTER TABLE `play_shop_carousel_info` + CONVERT TO CHARACTER SET utf8mb4 + COLLATE utf8mb4_general_ci; + +ALTER TABLE `play_commodity_info` + CONVERT TO CHARACTER SET utf8mb4 + COLLATE utf8mb4_general_ci; + +ALTER TABLE `play_commodity_and_level_info` + CONVERT TO CHARACTER SET utf8mb4 + COLLATE utf8mb4_general_ci; + +ALTER TABLE `play_custom_amount details` + CONVERT TO CHARACTER SET utf8mb4 + COLLATE utf8mb4_general_ci; + +ALTER TABLE `play_notice_info` + CONVERT TO CHARACTER SET utf8mb4 + COLLATE utf8mb4_general_ci; + +ALTER TABLE `play_order_complaint_info` + CONVERT TO CHARACTER SET utf8mb4 + COLLATE utf8mb4_general_ci; + +-- Restore original sql_mode flags once the rebuilds are done. +SET SESSION sql_mode = @OLD_SQL_MODE; diff --git a/recreate-staging.sh b/recreate-staging.sh new file mode 100755 index 0000000..d425d2c --- /dev/null +++ b/recreate-staging.sh @@ -0,0 +1,137 @@ +#!/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}."