#!/usr/bin/env bash set -Eeuo pipefail umask 077 SCRIPT_DIR="$(cd -- "$(dirname -- "${BASH_SOURCE[0]}")" && pwd)" PROJECT_DIR="$(cd -- "${SCRIPT_DIR}/.." && pwd)" BACKUP_ROOT="${DATA_BACKUP_ROOT:-${PROJECT_DIR}/backups/mysql}" PASSPHRASE_FILE="${DATA_BACKUP_PASSPHRASE_FILE:-}" if [[ -z "${PASSPHRASE_FILE}" || ! -f "${PASSPHRASE_FILE}" || ! -r "${PASSPHRASE_FILE}" ]]; then echo "DATA_BACKUP_PASSPHRASE_FILE 必须指向可读的独立密钥文件" >&2 exit 2 fi if [[ "$(wc -c < "${PASSPHRASE_FILE}" | tr -d ' ')" -lt 32 ]]; then echo "备份加密口令文件内容不能少于 32 个字符" >&2 exit 2 fi if [[ -z "${BACKUP_ROOT}" || "${BACKUP_ROOT}" == "/" ]]; then echo "拒绝使用不安全的备份目录" >&2 exit 2 fi for command_name in docker gzip openssl; do command -v "${command_name}" >/dev/null 2>&1 || { echo "缺少命令:${command_name}" >&2 exit 2 } done mkdir -p -- "${BACKUP_ROOT}" timestamp="$(date -u +%Y%m%dT%H%M%SZ)" archive_name="mysql-all-${timestamp}.sql.gz.enc" archive_path="${BACKUP_ROOT}/${archive_name}" temporary_path="${BACKUP_ROOT}/.${archive_name}.tmp" checksum_path="${archive_path}.sha256" manifest_path="${archive_path}.json" cleanup() { rm -f -- "${temporary_path}" } trap cleanup EXIT INT TERM cd -- "${PROJECT_DIR}" docker compose exec -T mysql sh -eu -c ' MYSQL_PWD="$MYSQL_ROOT_PASSWORD" mysqldump \ --user=root \ --all-databases \ --single-transaction \ --quick \ --routines \ --events \ --triggers \ --hex-blob \ --set-gtid-purged=OFF ' | gzip -9 | openssl enc -aes-256-cbc -salt -pbkdf2 -iter 200000 \ -pass "file:${PASSPHRASE_FILE}" -out "${temporary_path}" test -s "${temporary_path}" mv -- "${temporary_path}" "${archive_path}" if command -v sha256sum >/dev/null 2>&1; then checksum="$(sha256sum "${archive_path}" | awk '{print $1}')" else checksum="$(shasum -a 256 "${archive_path}" | awk '{print $1}')" fi printf '%s %s\n' "${checksum}" "${archive_name}" > "${checksum_path}" printf '{\n "created_at": "%s",\n "file": "%s",\n "sha256": "%s",\n "encrypted": true,\n "cipher": "AES-256-CBC/PBKDF2",\n "compression": "gzip"\n}\n' \ "${timestamp}" "${archive_name}" "${checksum}" > "${manifest_path}" chmod 600 "${archive_path}" "${checksum_path}" "${manifest_path}" echo "加密备份已生成:${archive_path}" echo "请将 .enc、.sha256 和 .json 同步到独立的异地存储。"