- move the relational data center to MySQL and a standalone workbench\n- add Interface Center API credentials, policies, logs, and DBeaver SSH guidance\n- harden authentication and deployment while retiring unused management surfaces
74 lines
2.4 KiB
Bash
Executable File
74 lines
2.4 KiB
Bash
Executable File
#!/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 同步到独立的异地存储。"
|