151 lines
7.2 KiB
Bash
Executable File
151 lines
7.2 KiB
Bash
Executable File
#!/bin/bash
|
||
# =============================================================================
|
||
# WOOO AIOps - Gitea 備份腳本
|
||
# 版本: 1.1.0
|
||
# 建立日期: 2026-03-12
|
||
# 2026-05-19 ogt + Codex: 納入 repo/Ansible;離機上傳改由 sync-offsite-backups.sh 統一管控。
|
||
# =============================================================================
|
||
|
||
set -euo pipefail
|
||
|
||
source "$(dirname "$0")/common.sh"
|
||
|
||
SERVICE="gitea"
|
||
GITEA_CONTAINER="gitea"
|
||
LOCAL_REPO="${BACKUP_BASE}/gitea"
|
||
DUMP_DIR="/tmp/gitea-backup-$$"
|
||
TEXTFILE_DIR="${NODE_EXPORTER_TEXTFILE_DIR:-/home/wooo/node_exporter_textfiles}"
|
||
TEXTFILE_PATH="${GITEA_FULL_BACKUP_RECEIPT_TEXTFILE:-${TEXTFILE_DIR}/gitea_full_backup_receipt.prom}"
|
||
HOST_LABEL="${AIOPS_HOST_LABEL:-110}"
|
||
GITEA_FULL_BACKUP_COMPONENTS="database repositories app_settings lfs_objects package_registry attachments hooks_custom_assets"
|
||
|
||
label_escape() {
|
||
printf '%s' "$1" | sed -e 's/\\/\\\\/g' -e 's/"/\\"/g'
|
||
}
|
||
|
||
write_gitea_full_backup_receipt() {
|
||
local status="$1"
|
||
local timestamp="$2"
|
||
local duration="$3"
|
||
local snapshot_id="${4:-unknown}"
|
||
local tmp
|
||
local host
|
||
local service_label
|
||
local status_label
|
||
local component
|
||
local ok=0
|
||
local failed=1
|
||
|
||
if [ "${status}" = "success" ]; then
|
||
ok=1
|
||
failed=0
|
||
fi
|
||
|
||
host="$(label_escape "${HOST_LABEL}")"
|
||
service_label="$(label_escape "${SERVICE}")"
|
||
status_label="$(label_escape "${status}")"
|
||
|
||
install -d -m 755 "${TEXTFILE_DIR}" || return 0
|
||
tmp="$(mktemp "${TEXTFILE_PATH}.tmp.XXXXXX")" || return 0
|
||
{
|
||
echo "# HELP awoooi_gitea_full_backup_last_success_timestamp Unix timestamp of the latest successful Gitea full-server backup receipt."
|
||
echo "# TYPE awoooi_gitea_full_backup_last_success_timestamp gauge"
|
||
echo "# HELP awoooi_gitea_full_backup_last_run_failed Whether the latest Gitea full-server backup run failed."
|
||
echo "# TYPE awoooi_gitea_full_backup_last_run_failed gauge"
|
||
echo "# HELP awoooi_gitea_full_backup_duration_seconds Duration of the latest Gitea full-server backup run."
|
||
echo "# TYPE awoooi_gitea_full_backup_duration_seconds gauge"
|
||
echo "# HELP awoooi_gitea_full_backup_dump_file_present Whether the Gitea dump file was produced before restic backup."
|
||
echo "# TYPE awoooi_gitea_full_backup_dump_file_present gauge"
|
||
echo "# HELP awoooi_gitea_full_backup_restic_snapshot_present Whether a restic snapshot id was recorded for the Gitea dump."
|
||
echo "# TYPE awoooi_gitea_full_backup_restic_snapshot_present gauge"
|
||
echo "# HELP awoooi_gitea_full_backup_component_receipt Whether the Gitea full-server dump includes a redacted component receipt."
|
||
echo "# TYPE awoooi_gitea_full_backup_component_receipt gauge"
|
||
echo "# HELP awoooi_gitea_full_backup_restore_drill_performed Whether a non-production Gitea full-backup restore drill receipt is present."
|
||
echo "# TYPE awoooi_gitea_full_backup_restore_drill_performed gauge"
|
||
echo "# HELP awoooi_gitea_full_backup_secret_value_collected Whether this receipt collected secret values."
|
||
echo "# TYPE awoooi_gitea_full_backup_secret_value_collected gauge"
|
||
echo "# HELP awoooi_gitea_full_backup_production_restore_performed Whether this receipt restored anything into production."
|
||
echo "# TYPE awoooi_gitea_full_backup_production_restore_performed gauge"
|
||
echo "awoooi_gitea_full_backup_last_success_timestamp{host=\"${host}\",service=\"${service_label}\"} $([ "${ok}" = "1" ] && echo "${timestamp}" || echo 0)"
|
||
echo "awoooi_gitea_full_backup_last_run_failed{host=\"${host}\",service=\"${service_label}\",status=\"${status_label}\"} ${failed}"
|
||
echo "awoooi_gitea_full_backup_duration_seconds{host=\"${host}\",service=\"${service_label}\"} ${duration}"
|
||
echo "awoooi_gitea_full_backup_dump_file_present{host=\"${host}\",service=\"${service_label}\"} ${ok}"
|
||
echo "awoooi_gitea_full_backup_restic_snapshot_present{host=\"${host}\",service=\"${service_label}\",snapshot_id=\"$(label_escape "${snapshot_id}")\"} ${ok}"
|
||
for component in ${GITEA_FULL_BACKUP_COMPONENTS}; do
|
||
echo "awoooi_gitea_full_backup_component_receipt{host=\"${host}\",service=\"${service_label}\",component=\"$(label_escape "${component}")\",receipt=\"gitea_dump_restic\"} ${ok}"
|
||
done
|
||
echo "awoooi_gitea_full_backup_restore_drill_performed{host=\"${host}\",service=\"${service_label}\"} 0"
|
||
echo "awoooi_gitea_full_backup_secret_value_collected{host=\"${host}\",service=\"${service_label}\"} 0"
|
||
echo "awoooi_gitea_full_backup_production_restore_performed{host=\"${host}\",service=\"${service_label}\"} 0"
|
||
} >"${tmp}"
|
||
mv "${tmp}" "${TEXTFILE_PATH}" || return 0
|
||
chmod 0644 "${TEXTFILE_PATH}" || true
|
||
}
|
||
|
||
cleanup() {
|
||
rm -rf "${DUMP_DIR}"
|
||
}
|
||
|
||
main() {
|
||
local start_time
|
||
local tags
|
||
local snapshot_id
|
||
local duration
|
||
|
||
start_time=$(date +%s)
|
||
trap cleanup EXIT
|
||
|
||
log_info "========== 開始 Gitea 備份 =========="
|
||
mkdir -p "${DUMP_DIR}"
|
||
|
||
log_info "執行 Gitea dump..."
|
||
if docker exec -u git "${GITEA_CONTAINER}" gitea dump -c /data/gitea/conf/app.ini -f /tmp/gitea-dump.zip 2>&1; then
|
||
docker cp "${GITEA_CONTAINER}:/tmp/gitea-dump.zip" "${DUMP_DIR}/gitea-dump.zip"
|
||
docker exec -u git "${GITEA_CONTAINER}" rm -f /tmp/gitea-dump.zip
|
||
log_success "Gitea dump 完成"
|
||
else
|
||
duration=$(($(date +%s) - start_time))
|
||
write_gitea_full_backup_receipt "dump_failed" "0" "${duration}" "none" || true
|
||
log_error "Gitea dump 失敗"
|
||
notify_clawbot "failed" "${SERVICE}" "Gitea dump 失敗"
|
||
exit 1
|
||
fi
|
||
|
||
if [ ! -d "${LOCAL_REPO}/data" ]; then
|
||
log_info "初始化本地 Restic 倉庫..."
|
||
if ! restic -r "${LOCAL_REPO}" init --password-file "${RESTIC_PASSWORD_FILE}"; then
|
||
duration=$(($(date +%s) - start_time))
|
||
write_gitea_full_backup_receipt "restic_init_failed" "0" "${duration}" "none" || true
|
||
log_error "Restic 初始化失敗"
|
||
notify_clawbot "failed" "${SERVICE}" "Gitea Restic 初始化失敗"
|
||
exit 1
|
||
fi
|
||
fi
|
||
|
||
tags=$(build_tags "${SERVICE}")
|
||
if ! restic -r "${LOCAL_REPO}" backup "${DUMP_DIR}" \
|
||
--password-file "${RESTIC_PASSWORD_FILE}" \
|
||
${tags}; then
|
||
duration=$(($(date +%s) - start_time))
|
||
write_gitea_full_backup_receipt "restic_backup_failed" "0" "${duration}" "none" || true
|
||
log_error "Restic 備份失敗"
|
||
notify_clawbot "failed" "${SERVICE}" "Gitea Restic 備份失敗"
|
||
exit 1
|
||
fi
|
||
|
||
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 || true)
|
||
log_success "Restic 備份完成: ${snapshot_id:-unknown}"
|
||
|
||
log_info "執行 GFS 清理..."
|
||
cleanup_old_backups "${LOCAL_REPO}"
|
||
|
||
log_info "Offsite copy is handled by sync-offsite-backups.sh; no direct rclone sync here."
|
||
|
||
duration=$(($(date +%s) - start_time))
|
||
write_gitea_full_backup_receipt "success" "$(date +%s)" "${duration}" "${snapshot_id:-unknown}" || true
|
||
log_success "========== Gitea 備份完成 (${duration}s) =========="
|
||
notify_clawbot "success" "${SERVICE}" "Gitea 備份完成" "${duration}"
|
||
}
|
||
|
||
main "$@"
|