#!/usr/bin/env bash # 衍水工资系统 —— MySQL 全量备份(⑨ 备份恢复与运维检查) # # 思路:在跑中的容器内执行 mysqldump,免暴露 3306 端口给宿主机工具, # 也避免宿主机装 MySQL 客户端。凭据从 docker/.env 读取(不入脚本、不入版本库)。 # # 用法:在项目根目录执行 bash scripts/backup_full.sh # 产物:db/backups/payroll_YYYYMMDD_HHMMSS.sql.gz,保留最近 30 天。 set -euo pipefail ROOT="$(cd "$(dirname "${BASH_SOURCE[0]}")/.." && pwd)" cd "$ROOT" if [[ -f docker/.env ]]; then # shellcheck disable=SC1091 source docker/.env else echo "ERROR: docker/.env 不存在(请先创建 docker/.env 并填入 MySQL 凭据)" >&2 exit 1 fi : "${MYSQL_DATABASE:?docker/.env 缺 MYSQL_DATABASE}" : "${MYSQL_ROOT_PASSWORD:?docker/.env 缺 MYSQL_ROOT_PASSWORD}" BACKUP_DIR="$ROOT/db/backups" mkdir -p "$BACKUP_DIR" STAMP="$(date +%Y%m%d_%H%M%S)" OUT="$BACKUP_DIR/payroll_${STAMP}.sql.gz" # 容器名固定(compose 显式 container_name),与 docker/docker-compose.dev.yml 保持一致 CONTAINER="ys-mysql-dev" if ! docker inspect "$CONTAINER" >/dev/null 2>&1; then echo "ERROR: 容器 $CONTAINER 未运行(先 docker compose up -d)" >&2 exit 1 fi echo "开始备份 $MYSQL_DATABASE → $(basename "$OUT")" docker exec "$CONTAINER" sh -c \ "mysqldump -uroot -p\"\$MYSQL_ROOT_PASSWORD\" --single-transaction --routines --triggers \"$MYSQL_DATABASE\"" \ | gzip > "$OUT" if [[ ! -s "$OUT" ]]; then echo "ERROR: 备份文件为空(数据库名或凭据可能不对)" >&2 rm -f "$OUT" exit 1 fi SIZE="$(du -h "$OUT" | cut -f1)" echo "备份完成:$(basename "$OUT") ($SIZE)" # 记录 binlog 位点:增量备份(backup_incremental.sh)与恢复重放的对齐锚点—— # 从「此位点之后」的 binlog 开始重放即为完整增量链 docker exec "$CONTAINER" sh -c \ 'mysql -uroot -p"$MYSQL_ROOT_PASSWORD" -N -e "SHOW MASTER STATUS"' > "${OUT}.pos" 2>/dev/null if [[ -s "${OUT}.pos" ]]; then echo "binlog 位点:$(awk '{print $1, "pos="$2}' "${OUT}.pos") → $(basename "$OUT").pos" fi # 保留 30 天 find "$BACKUP_DIR" -maxdepth 1 -name 'payroll_*.sql.gz' -mtime +30 -print -delete find "$BACKUP_DIR" -maxdepth 1 -name 'payroll_*.pos' -mtime +30 -print -delete echo "已清理 30 天前的旧备份" echo "" echo "最新备份:" ls -lh "$BACKUP_DIR"/*.sql.gz 2>/dev/null | tail -3