Files
hfb_sys/scripts/backup-common.sh
T
2026-08-16 22:38:31 +08:00

199 lines
5.1 KiB
Bash
Executable File

#!/usr/bin/env bash
# 在线备份脚本共用函数。仅供 scripts/backup-online.sh、archive-binlog.sh 和
# restore-online.sh source,不要单独执行。
BACKUP_ROOT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")/.." && pwd)"
BACKUP_COMPOSE_FILE="${BACKUP_ROOT_DIR}/deploy/docker-compose.prod.yml"
BACKUP_ENV_FILE="${BACKUP_ROOT_DIR}/backend/.env"
BACKUP_XTRABACKUP_IMAGE="${XTRABACKUP_IMAGE:-percona/percona-xtrabackup:8.4}"
backup_log() {
printf '[backup] %s\n' "$*" >&2
}
backup_error() {
printf '[backup] ERROR: %s\n' "$*" >&2
}
backup_require_cmd() {
if ! command -v "$1" >/dev/null 2>&1; then
backup_error "缺少命令:$1"
exit 1
fi
}
backup_env_value() {
local key="$1"
awk -F= -v key="${key}" '$1 == key { sub(/^[^=]*=/, ""); print; exit }' "${BACKUP_ENV_FILE}"
}
backup_require_env() {
local key="$1"
local value
value="$(backup_env_value "${key}")"
if [[ -z "${value}" ]]; then
backup_error "${BACKUP_ENV_FILE} 缺少或未填写 ${key}"
exit 1
fi
printf '%s' "${value}"
}
backup_compose() {
docker compose --env-file "${BACKUP_ENV_FILE}" -f "${BACKUP_COMPOSE_FILE}" "$@"
}
backup_mysql_container_id() {
local cid
cid="$(backup_compose ps -q mysql 2>/dev/null || true)"
if [[ -z "${cid}" ]]; then
backup_error "MySQL 容器未运行"
exit 1
fi
printf '%s' "${cid}"
}
backup_mysql_network() {
local network
network="$(docker inspect -f '{{range $k, $v := .NetworkSettings.Networks}}{{$k}}{{end}}' "$1")"
if [[ -z "${network}" ]]; then
backup_error "无法读取 MySQL Docker 网络"
exit 1
fi
printf '%s' "${network}"
}
backup_mysql_root_query() {
backup_compose exec -T mysql sh -c \
'MYSQL_PWD="$MYSQL_ROOT_PASSWORD" mysql -uroot --protocol=socket -N -B "$MYSQL_DATABASE" -e "$1"' \
sh "$1"
}
backup_validate_dir() {
local dir="$1"
if [[ "${dir}" != /data/* || "${dir}" == "/data" ]]; then
backup_error "备份目录必须是 /data 之下的具体绝对路径,当前值:${dir}"
exit 1
fi
}
backup_lock_or_exit() {
local path="$1"
local wait_seconds="${2:-${BACKUP_LOCK_WAIT_SECONDS:-0}}"
backup_require_cmd flock
mkdir -p "$(dirname "${path}")"
exec 9>"${path}"
if [[ ! "${wait_seconds}" =~ ^[0-9]+$ ]]; then
backup_error "锁等待时间必须是非负整数:${wait_seconds}"
exit 1
fi
if ! flock -w "${wait_seconds}" 9; then
backup_error "已有同类备份任务在执行,拒绝并发运行"
exit 1
fi
}
backup_server_uuid() {
local value
value="$(backup_mysql_root_query 'SELECT @@server_uuid;' 2>/dev/null || true)"
if [[ ! "${value}" =~ ^[0-9A-Fa-f-]{36}$ ]]; then
backup_error "无法读取 MySQL server_uuid"
exit 1
fi
printf '%s' "${value,,}"
}
backup_binlog_index() {
local filename="$1"
if [[ "${filename}" =~ ^mysql-bin\.([0-9]{6,})$ ]]; then
printf '%d' "$((10#${BASH_REMATCH[1]}))"
return
fi
backup_error "不支持的 binlog 文件名:${filename}"
exit 1
}
backup_sha256() {
if command -v sha256sum >/dev/null 2>&1; then
sha256sum "$1" | awk '{print $1}'
return
fi
shasum -a 256 "$1" | awk '{print $1}'
}
backup_file_size() {
if [[ "$(uname)" == 'Darwin' ]]; then
stat -f%z "$1"
else
stat -c%s "$1"
fi
}
backup_encrypt_file() {
local source="$1"
local destination="$2"
local passphrase
passphrase="$(backup_require_env BACKUP_PASSPHRASE)"
BACKUP_PASSPHRASE="${passphrase}" openssl enc -aes-256-cbc -pbkdf2 -iter 600000 -salt \
-pass env:BACKUP_PASSPHRASE \
-in "${source}" \
-out "${destination}"
unset passphrase
}
backup_decrypt_file() {
local source="$1"
local destination="$2"
local passphrase
passphrase="$(backup_require_env BACKUP_PASSPHRASE)"
BACKUP_PASSPHRASE="${passphrase}" openssl enc -d -aes-256-cbc -pbkdf2 -iter 600000 \
-pass env:BACKUP_PASSPHRASE \
-in "${source}" \
-out "${destination}"
unset passphrase
}
backup_oss_upload() {
local source="$1"
local destination="$2"
local kms_key_id
kms_key_id="$(backup_env_value BACKUP_KMS_KEY_ID)"
if [[ -n "${kms_key_id}" ]]; then
ossutil cp "${source}" "${destination}" \
--encryption KMS --kms-master-key-id "${kms_key_id}"
return
fi
# 未配置 KMS 时依赖 Bucket 已启用的 OSS 托管服务器端加密;备份载荷仍会先经过客户端加密。
ossutil cp "${source}" "${destination}"
}
backup_validate_oss_uri() {
local uri="$1"
if [[ ! "${uri}" =~ ^oss://[A-Za-z0-9][A-Za-z0-9._-]*$ ]]; then
backup_error "BACKUP_OSS_URI 必须是 oss://bucket 格式,当前值:${uri}"
exit 1
fi
}
backup_oss_download() {
local source="$1"
local destination="$2"
ossutil cp "${source}" "${destination}"
}
backup_write_complete_marker() {
local path="$1"
local payload_name="$2"
local payload_sha256="$3"
local payload_size="$4"
local server_uuid="$5"
{
printf 'format_version=1\n'
printf 'created_at=%s\n' "$(date -Iseconds)"
printf 'server_uuid=%s\n' "${server_uuid}"
printf 'payload=%s\n' "${payload_name}"
printf 'payload_sha256=%s\n' "${payload_sha256}"
printf 'payload_size_bytes=%s\n' "${payload_size}"
} > "${path}"
}