From af3bee2609b9b3762bf25339eff21915c40274b8 Mon Sep 17 00:00:00 2001 From: Jacky Date: Sat, 8 Aug 2026 22:08:20 +0800 Subject: [PATCH] v0.3.1: Update mysqldump --- .env.production.example | 5 +- docs/BAOTA_UBUNTU_FROM_ZERO.md | 34 +++++++++----- scripts/deploy_production.sh | 85 ++++++++++++++++++++++++---------- 3 files changed, 86 insertions(+), 38 deletions(-) diff --git a/.env.production.example b/.env.production.example index d1549e1..52fd928 100644 --- a/.env.production.example +++ b/.env.production.example @@ -18,5 +18,8 @@ SECURE_HSTS_SECONDS=31536000 # 仅供部署脚本直接访问 127.0.0.1:8000 时设置 Host 头。 DEPLOY_HEALTH_HOST=math.example.com -# 宝塔/Docker MySQL 的 mysqldump 不在 PATH 时取消注释并填写实际路径。 +# Docker MySQL 推荐直接使用容器内的 MySQL 8 客户端。 +MYSQL_CONTAINER=mysql_o0fbdmmf8q + +# 仅非 Docker MySQL 使用。客户端必须是 MySQL 8。 # MYSQLDUMP_BIN=/www/server/mysql/bin/mysqldump diff --git a/docs/BAOTA_UBUNTU_FROM_ZERO.md b/docs/BAOTA_UBUNTU_FROM_ZERO.md index 1d7609d..3cbb9e3 100644 --- a/docs/BAOTA_UBUNTU_FROM_ZERO.md +++ b/docs/BAOTA_UBUNTU_FROM_ZERO.md @@ -100,6 +100,11 @@ ls -l /www/server/pyporject_evn/versions/3.12.13/bin/python* Redis 应返回 `PONG`。 +如果这里显示宿主机 `mysql/mysqldump` 是 5.7,而 Docker 容器是 +MySQL 8.0.35,不需要卸载旧客户端。宿主机 5.7 客户端只用于简单连通性测试; +生产自动备份必须配置 `MYSQL_CONTAINER`,使用容器内部的 MySQL 8 +`mysqldump`,避免跨版本备份兼容问题。 + ## 4. 配置 Docker MySQL 8.0.35 宝塔 `MySQL 多版本管理(Docker 应用)` 中设置: @@ -170,7 +175,8 @@ CREATE DATABASE hulumath CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci; -CREATE USER 'hulumath'@'%' IDENTIFIED BY 'ReplaceWithStrongPassword'; +CREATE USER 'hulumath'@'%' + IDENTIFIED WITH mysql_native_password BY 'ReplaceWithStrongPassword'; GRANT ALL PRIVILEGES ON hulumath.* TO 'hulumath'@'%'; FLUSH PRIVILEGES; @@ -182,6 +188,10 @@ SHOW VARIABLES LIKE 'sql_mode'; 版本必须是 `8.0.35`,字符集应为 `utf8mb4`。 +这里使用 `mysql_native_password` 是为了兼容服务器现有 MySQL 5.7 命令行客户端 +和系统 `mysqlclient` 动态库。数据库端口只绑定 `127.0.0.1:3307`,该账号不会 +暴露公网。 + 从宿主机验证: ```bash @@ -254,10 +264,12 @@ API_USER_RATE=600/minute SECURE_HSTS_SECONDS=31536000 DEPLOY_HEALTH_HOST=math.example.com -MYSQLDUMP_BIN=/usr/bin/mysqldump +MYSQL_CONTAINER=mysql_o0fbdmmf8q ``` -如果 `command -v mysqldump` 返回其他路径,修改 `MYSQLDUMP_BIN`。 +`MYSQL_CONTAINER` 必须填写 `docker ps` 显示的 MySQL 8 容器名。你的服务器 +当前应填写 `mysql_o0fbdmmf8q`。部署脚本会把临时凭据复制进容器,调用容器 +内部的 MySQL 8 `mysqldump`,完成后立即删除。 锁定权限: @@ -478,14 +490,13 @@ hulumath_YYYYMMDD_HHMMSS.sql.gz 保留 14 天。 -人工备份: +人工备份建议同样使用容器内的 MySQL 8 客户端。以下命令会提示输入应用数据库密码: ```bash -mysqldump \ - -h 127.0.0.1 -P 3307 \ +docker exec -i mysql_o0fbdmmf8q mysqldump \ -u hulumath -p \ --single-transaction --quick \ - --routines --triggers --events \ + --triggers --no-tablespaces \ --hex-blob --set-gtid-purged=OFF \ hulumath | gzip > /www/backup/hulumath/manual.sql.gz ``` @@ -546,17 +557,16 @@ ss -ltnp | grep 3307 mysql -h 127.0.0.1 -P 3307 -u hulumath -p hulumath ``` -### 部署找不到 mysqldump +### 部署提示宿主机 mysqldump 版本过低 ```bash -command -v mysqldump -find /www/server/mysql /usr -type f -name mysqldump 2>/dev/null +docker exec mysql_o0fbdmmf8q mysqldump --version ``` -然后设置: +容器内应显示 MySQL 8。然后在 `.env.production` 设置: ```dotenv -MYSQLDUMP_BIN=/实际路径/mysqldump +MYSQL_CONTAINER=mysql_o0fbdmmf8q ``` ### 字符集或 Emoji 写入失败 diff --git a/scripts/deploy_production.sh b/scripts/deploy_production.sh index c6ae6ce..dd8847d 100755 --- a/scripts/deploy_production.sh +++ b/scripts/deploy_production.sh @@ -10,10 +10,12 @@ ENV_FILE="${ENV_FILE:-$PROJECT_DIR/.env.production}" SERVICE_NAME="${SERVICE_NAME:-hulumath-web}" BACKUP_DIR="${BACKUP_DIR:-/www/backup/hulumath}" MYSQLDUMP_BIN="${MYSQLDUMP_BIN:-}" +MYSQL_CONTAINER="${MYSQL_CONTAINER:-}" PREVIOUS_REVISION="${PREVIOUS_REVISION:-}" HEALTH_HOST="${DEPLOY_HEALTH_HOST:-}" ROLLBACK_ENABLED=0 MYSQL_CREDENTIALS_FILE="" +MYSQL_CONTAINER_CREDENTIALS_PATH="" log() { printf '\n[%s] %s\n' "$(date '+%Y-%m-%d %H:%M:%S')" "$*" @@ -39,6 +41,10 @@ DEPLOY_OWNER="$(id -un):$(id -gn)" rollback_application() { local exit_code="${1:-$?}" trap - ERR + if [ -n "$MYSQL_CONTAINER_CREDENTIALS_PATH" ] && [ -n "$MYSQL_CONTAINER" ]; then + docker exec "$MYSQL_CONTAINER" rm -f "$MYSQL_CONTAINER_CREDENTIALS_PATH" \ + >/dev/null 2>&1 || true + fi if [ -n "$MYSQL_CREDENTIALS_FILE" ]; then rm -f "$MYSQL_CREDENTIALS_FILE" fi @@ -124,20 +130,32 @@ $SUDO chown -R "$DEPLOY_OWNER" "$VENV_DIR" "$VENV_DIR/bin/pip" install --disable-pip-version-check -r requirements.txt log "备份 MySQL" -if [ -z "$MYSQLDUMP_BIN" ]; then - MYSQLDUMP_BIN="$(command -v mysqldump || true)" +if [ -n "$MYSQL_CONTAINER" ]; then + command -v docker >/dev/null 2>&1 || fail "配置了 MYSQL_CONTAINER,但服务器没有 docker 命令" + docker inspect "$MYSQL_CONTAINER" >/dev/null 2>&1 \ + || fail "MySQL 容器不存在: $MYSQL_CONTAINER" + docker exec "$MYSQL_CONTAINER" mysqldump --version \ + | grep -Eq 'Distrib 8\.' \ + || fail "容器内 mysqldump 不是 MySQL 8: $MYSQL_CONTAINER" +else + if [ -z "$MYSQLDUMP_BIN" ]; then + MYSQLDUMP_BIN="$(command -v mysqldump || true)" + fi + if [ -z "$MYSQLDUMP_BIN" ]; then + for candidate in \ + /www/server/mysql/bin/mysqldump \ + /usr/bin/mysqldump; do + if [ -x "$candidate" ]; then + MYSQLDUMP_BIN="$candidate" + break + fi + done + fi + [ -x "$MYSQLDUMP_BIN" ] \ + || fail "未找到 mysqldump;Docker MySQL 请设置 MYSQL_CONTAINER" + "$MYSQLDUMP_BIN" --version | grep -Eq 'Distrib 8\.' \ + || fail "宿主机 mysqldump 不是 MySQL 8;请设置 MYSQL_CONTAINER 使用容器内客户端" fi -if [ -z "$MYSQLDUMP_BIN" ]; then - for candidate in \ - /www/server/mysql/bin/mysqldump \ - /usr/bin/mysqldump; do - if [ -x "$candidate" ]; then - MYSQLDUMP_BIN="$candidate" - break - fi - done -fi -[ -x "$MYSQLDUMP_BIN" ] || fail "未找到 mysqldump;可通过 MYSQLDUMP_BIN 指定客户端路径" $SUDO mkdir -p "$BACKUP_DIR" $SUDO chown "$(id -un):$(id -gn)" "$BACKUP_DIR" BACKUP_FILE="$BACKUP_DIR/hulumath_$(date '+%Y%m%d_%H%M%S').sql.gz" @@ -167,24 +185,41 @@ def quote(value): with open(sys.argv[1], "w", encoding="utf-8") as target: target.write("[client]\n") - target.write(f"host={quote(values['host'])}\n") - target.write(f"port={values['port']}\n") + if not os.environ.get("MYSQL_CONTAINER"): + target.write(f"host={quote(values['host'])}\n") + target.write(f"port={values['port']}\n") target.write(f"user={quote(values['user'])}\n") target.write(f"password={quote(values['password'])}\n") target.write("default-character-set=utf8mb4\n") print(database) PY )" -"$MYSQLDUMP_BIN" \ - --defaults-extra-file="$MYSQL_CREDENTIALS_FILE" \ - --single-transaction \ - --quick \ - --routines \ - --triggers \ - --events \ - --hex-blob \ - --set-gtid-purged=OFF \ - "$MYSQL_DATABASE_NAME" | gzip -c > "$BACKUP_FILE" +if [ -n "$MYSQL_CONTAINER" ]; then + MYSQL_CONTAINER_CREDENTIALS_PATH="/tmp/hulumath-dump-$$.cnf" + docker cp "$MYSQL_CREDENTIALS_FILE" \ + "$MYSQL_CONTAINER:$MYSQL_CONTAINER_CREDENTIALS_PATH" + docker exec "$MYSQL_CONTAINER" mysqldump \ + --defaults-extra-file="$MYSQL_CONTAINER_CREDENTIALS_PATH" \ + --single-transaction \ + --quick \ + --triggers \ + --hex-blob \ + --no-tablespaces \ + --set-gtid-purged=OFF \ + "$MYSQL_DATABASE_NAME" | gzip -c > "$BACKUP_FILE" + docker exec "$MYSQL_CONTAINER" rm -f "$MYSQL_CONTAINER_CREDENTIALS_PATH" + MYSQL_CONTAINER_CREDENTIALS_PATH="" +else + "$MYSQLDUMP_BIN" \ + --defaults-extra-file="$MYSQL_CREDENTIALS_FILE" \ + --single-transaction \ + --quick \ + --triggers \ + --hex-blob \ + --no-tablespaces \ + --set-gtid-purged=OFF \ + "$MYSQL_DATABASE_NAME" | gzip -c > "$BACKUP_FILE" +fi rm -f "$MYSQL_CREDENTIALS_FILE" MYSQL_CREDENTIALS_FILE="" test -s "$BACKUP_FILE" || fail "数据库备份为空: $BACKUP_FILE"