feat(gitea): add capability and backup receipt readbacks
This commit is contained in:
@@ -14,6 +14,73 @@ 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 this receipt performed a production restore drill."
|
||||
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}"
|
||||
@@ -37,6 +104,8 @@ main() {
|
||||
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
|
||||
@@ -44,13 +113,25 @@ main() {
|
||||
|
||||
if [ ! -d "${LOCAL_REPO}/data" ]; then
|
||||
log_info "初始化本地 Restic 倉庫..."
|
||||
restic -r "${LOCAL_REPO}" init --password-file "${RESTIC_PASSWORD_FILE}"
|
||||
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}")
|
||||
restic -r "${LOCAL_REPO}" backup "${DUMP_DIR}" \
|
||||
if ! restic -r "${LOCAL_REPO}" backup "${DUMP_DIR}" \
|
||||
--password-file "${RESTIC_PASSWORD_FILE}" \
|
||||
${tags}
|
||||
${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}"
|
||||
@@ -61,6 +142,7 @@ main() {
|
||||
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}"
|
||||
}
|
||||
|
||||
@@ -0,0 +1,44 @@
|
||||
from __future__ import annotations
|
||||
|
||||
from pathlib import Path
|
||||
|
||||
|
||||
ROOT = Path(__file__).resolve().parents[3]
|
||||
BACKUP_GITEA = ROOT / "scripts" / "backup" / "backup-gitea.sh"
|
||||
|
||||
|
||||
def test_backup_gitea_writes_full_server_component_receipts() -> None:
|
||||
text = BACKUP_GITEA.read_text(encoding="utf-8")
|
||||
|
||||
assert "write_gitea_full_backup_receipt" in text
|
||||
assert "awoooi_gitea_full_backup_component_receipt" in text
|
||||
assert "awoooi_gitea_full_backup_secret_value_collected" in text
|
||||
assert "awoooi_gitea_full_backup_production_restore_performed" in text
|
||||
assert "gitea dump -c /data/gitea/conf/app.ini" in text
|
||||
assert "GITEA_FULL_BACKUP_COMPONENTS" in text
|
||||
|
||||
for component in [
|
||||
"database",
|
||||
"repositories",
|
||||
"app_settings",
|
||||
"lfs_objects",
|
||||
"package_registry",
|
||||
"attachments",
|
||||
"hooks_custom_assets",
|
||||
]:
|
||||
assert component in text
|
||||
|
||||
|
||||
def test_backup_gitea_receipt_contract_stays_no_secret_no_restore() -> None:
|
||||
text = BACKUP_GITEA.read_text(encoding="utf-8")
|
||||
|
||||
assert "awoooi_gitea_full_backup_secret_value_collected" in text
|
||||
assert "awoooi_gitea_full_backup_production_restore_performed" in text
|
||||
assert (
|
||||
'secret_value_collected{host=\\"${host}\\",service=\\"${service_label}\\"} 0'
|
||||
in text
|
||||
)
|
||||
assert (
|
||||
'production_restore_performed{host=\\"${host}\\",service=\\"${service_label}\\"} 0'
|
||||
in text
|
||||
)
|
||||
Reference in New Issue
Block a user