#!/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 "$@"