Files
awoooi/scripts/backup/backup-awoooi.sh
ogt 5f4f1ab865
Some checks failed
CD Pipeline / workflow-shape (push) Successful in 1s
CD Pipeline / cancel-stale-cd (push) Has been skipped
CD Pipeline / build-and-deploy (push) Has been cancelled
CD Pipeline / post-deploy-checks (push) Has been cancelled
CD Pipeline / tests (push) Has been cancelled
fix(agent): bound host110 monitoring repair
2026-07-14 21:31:54 +08:00

175 lines
5.9 KiB
Bash
Executable File
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
#!/bin/bash
# =============================================================================
# WOOO AIOps - AWOOOI 資料庫備份腳本
# 2026-04-05 Claude Code: 首席架構師備份審計 — awoooi_prod/dev/k3s_datastore
# 部署位置: /backup/scripts/backup-awoooi.sh (on 192.168.0.110)
# 整合進 backup-all.sh (步驟 4/4)
# =============================================================================
set -euo pipefail
# 載入共用函式
source "$(dirname "$0")/common.sh"
# 配置
SERVICE="awoooi"
AWOOOI_HOST="192.168.0.188"
AWOOOI_DB_USER="awoooi"
AWOOOI_DB_PASS="${AWOOOI_DB_PASS:-}"
AWOOOI_DB_HOST="localhost"
AWOOOI_DB_PORT="5432"
K3S_DB_USER="postgres"
LOCAL_REPO="${BACKUP_BASE}/awoooi"
DUMP_DIR="/tmp/awoooi-backup-$$"
quote_remote() {
printf "%q" "$1"
}
pgpass_escape() {
local value="$1"
value="${value//\\/\\\\}"
value="${value//:/\\:}"
printf '%s' "${value}"
}
pgpass_line() {
local database="$1"
printf '%s:%s:%s:%s:%s\n' \
"$(pgpass_escape "${AWOOOI_DB_HOST}")" \
"$(pgpass_escape "${AWOOOI_DB_PORT}")" \
"$(pgpass_escape "${database}")" \
"$(pgpass_escape "${AWOOOI_DB_USER}")" \
"$(pgpass_escape "${AWOOOI_DB_PASS}")"
}
remote_pgpass_wrapper() {
local command="$1"
printf 'umask 077; pgpass=$(mktemp "${TMPDIR:-/tmp}/awoooi-pgpass.XXXXXX") || exit 1; cleanup() { rm -f "$pgpass"; }; trap cleanup EXIT HUP INT TERM; cat > "$pgpass"; PGPASSFILE="$pgpass" %s' "${command}"
}
run_remote_pg_dump() {
local database="$1"
local output_file="$2"
local stderr_file="${output_file}.stderr"
local command
command="pg_dump --no-password -U $(quote_remote "${AWOOOI_DB_USER}") -h $(quote_remote "${AWOOOI_DB_HOST}") -p $(quote_remote "${AWOOOI_DB_PORT}") $(quote_remote "${database}")"
if pgpass_line "${database}" \
| ssh "ollama@${AWOOOI_HOST}" "$(remote_pgpass_wrapper "${command}")" \
>"${output_file}" 2>"${stderr_file}"; then
rm -f "${stderr_file}"
return 0
fi
tail -40 "${stderr_file}" | sed -E 's/(password=)[^ ]+/\1REDACTED/g' >&2 || true
rm -f "${stderr_file}"
return 1
}
# 保留策略覆寫(比其他服務更長)
KEEP_DAILY=14 # 14 天每日
KEEP_WEEKLY=8 # 8 週每週
KEEP_MONTHLY=12 # 12 個月每月
main() {
local start_time=$(date +%s)
local failed=0
if [ -z "${AWOOOI_DB_PASS}" ]; then
log_error "AWOOOI_DB_PASS is unavailable; refusing an unauthenticated or hardcoded fallback"
notify_clawbot "failed" "${SERVICE}" "AWOOOI DB backup credential unavailable"
exit 1
fi
log_info "========== 開始 AWOOOI 資料庫備份 =========="
mkdir -p "${DUMP_DIR}"
# Step 1: awoooi_prod dump核心資料庫KB/事故/AutoRepair/Drift
log_info "Dump awoooi_prod..."
local timestamp=$(date "+%Y%m%d_%H%M%S")
if run_remote_pg_dump \
"awoooi_prod" "${DUMP_DIR}/awoooi_prod_${timestamp}.sql"; then
local size=$(du -h "${DUMP_DIR}/awoooi_prod_${timestamp}.sql" | cut -f1)
log_success "awoooi_prod dump 完成 (${size})"
else
log_error "awoooi_prod dump 失敗"
((failed++))
fi
# Step 2: awoooi_dev dump
log_info "Dump awoooi_dev..."
if run_remote_pg_dump \
"awoooi_dev" "${DUMP_DIR}/awoooi_dev_${timestamp}.sql"; then
local size=$(du -h "${DUMP_DIR}/awoooi_dev_${timestamp}.sql" | cut -f1)
log_success "awoooi_dev dump 完成 (${size})"
else
log_warn "awoooi_dev dump 跳過(可能不存在)"
fi
# Step 3: k3s_datastore dumpKine 後端)
log_info "Dump k3s_datastore..."
if run_remote_pg_dump \
"k3s_datastore" "${DUMP_DIR}/k3s_datastore_${timestamp}.sql"; then
local size=$(du -h "${DUMP_DIR}/k3s_datastore_${timestamp}.sql" | cut -f1)
log_success "k3s_datastore dump 完成 (${size})"
else
log_warn "k3s_datastore dump 跳過"
fi
# 若核心 DB 失敗,中止
if [ $failed -gt 0 ]; then
log_error "核心 DB awoooi_prod 備份失敗,中止"
notify_clawbot "failed" "${SERVICE}" "AWOOOI 核心 DB 備份失敗"
rm -rf "${DUMP_DIR}"
exit 1
fi
# Step 4: Restic 備份
log_info "建立 Restic 備份..."
local tags=$(build_tags "${SERVICE}")
if [ ! -d "${LOCAL_REPO}/data" ]; then
log_info "初始化 Restic 倉庫 ${LOCAL_REPO}..."
restic -r "${LOCAL_REPO}" init --password-file "${RESTIC_PASSWORD_FILE}" 2>&1
fi
restic -r "${LOCAL_REPO}" backup "${DUMP_DIR}" \
--password-file "${RESTIC_PASSWORD_FILE}" \
${tags} 2>&1
local snapshot_id=$(restic -r "${LOCAL_REPO}" snapshots --latest 1 --json \
--password-file "${RESTIC_PASSWORD_FILE}" 2>/dev/null | \
grep -oP '"short_id":"\K[^"]+' | head -1)
log_success "Restic 備份完成: ${snapshot_id}"
# Step 5: GFS 清理(延長保留)
log_info "執行 GFS 清理 (daily=${KEEP_DAILY} weekly=${KEEP_WEEKLY} monthly=${KEEP_MONTHLY})..."
restic -r "${LOCAL_REPO}" forget --prune \
--password-file "${RESTIC_PASSWORD_FILE}" \
--keep-daily ${KEEP_DAILY} \
--keep-weekly ${KEEP_WEEKLY} \
--keep-monthly ${KEEP_MONTHLY} 2>&1
log_success "GFS 清理完成"
# Step 6: Offsite 同步交由集中控制器
#
# 2026-06-18 Codex: daily service backup must not run a direct offsite
# sync. `backup-all.sh` and the scheduled offsite controller own the gated
# rclone sync / verification flow so failed local backups cannot partially
# publish stale repositories.
if check_b2_config; then
log_info "偵測到 legacy B2 設定;略過 service-level 直同步,請使用 sync-offsite-backups.sh"
fi
rm -rf "${DUMP_DIR}"
local end_time=$(date +%s)
local duration=$((end_time - start_time))
log_success "========== AWOOOI 備份完成 (${duration}s) =========="
notify_clawbot "success" "${SERVICE}" "AWOOOI DB 備份完成 (awoooi_prod/dev + k3s)" "${duration}"
}
main "$@"