重写在线备份链路
This commit is contained in:
Executable
+193
@@ -0,0 +1,193 @@
|
||||
#!/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_kms() {
|
||||
local source="$1"
|
||||
local destination="$2"
|
||||
local kms_key_id
|
||||
kms_key_id="$(backup_require_env BACKUP_KMS_KEY_ID)"
|
||||
ossutil cp "${source}" "${destination}" \
|
||||
--encryption KMS --kms-master-key-id "${kms_key_id}"
|
||||
}
|
||||
|
||||
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}"
|
||||
}
|
||||
Reference in New Issue
Block a user