#!/usr/bin/env bash # Wait for the post-escrow DR offsite gate to converge. # # 2026-05-20 ogt + Codex: # - 只讀等待人工 credential escrow marker 寫完後,repo scorecard、 # Prometheus recording rule、Alertmanager 可見性與最終 checklist 全部一致。 # - 不讀、不寫、不列印任何 secret;不建立 marker;不觸發 offsite sync。 set -euo pipefail ROOT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")/../.." && pwd)" PROMETHEUS_URL="${PROMETHEUS_URL:-http://192.168.0.110:9090}" ALERTMANAGER_URL="${ALERTMANAGER_URL:-http://192.168.0.110:9093}" TIMEOUT_SECONDS=900 INTERVAL_SECONDS=30 NO_COLOR=0 ONCE=0 usage() { cat <<'USAGE' Usage: bash scripts/reboot-recovery/wait-dr-offsite-ready.sh [--timeout-seconds N] [--interval-seconds N] [--no-color] bash scripts/reboot-recovery/wait-dr-offsite-ready.sh --once [--no-color] Purpose: After the human operator writes the five credential escrow markers on 110, wait until all read-only DR gates converge: 1. full-stack-recovery-scorecard.sh --require-dr 2. Prometheus recovery recording rule with --expect-dr-ready 3. backup-alert-live-visibility-check.py 4. dr-offsite-operator-checklist.sh --require-dr Rules: - This script never writes escrow markers. - This script never uploads or deletes backup data. - This script never prints credential values. - It only waits for scrape/rule/Alertmanager convergence after a real human escrow review. Environment: PROMETHEUS_URL, ALERTMANAGER_URL, REMOTE_110, SSH_BATCH_MODE, SSH_STRICT_HOST_KEY_CHECKING. USAGE } while [ "$#" -gt 0 ]; do case "$1" in --timeout-seconds) TIMEOUT_SECONDS="${2:-}" shift 2 ;; --interval-seconds) INTERVAL_SECONDS="${2:-}" shift 2 ;; --once) ONCE=1 shift ;; --no-color) NO_COLOR=1 shift ;; -h|--help) usage exit 0 ;; *) echo "Unknown argument: $1" >&2 usage >&2 exit 2 ;; esac done if ! [[ "${TIMEOUT_SECONDS}" =~ ^[0-9]+$ ]] || [ "${TIMEOUT_SECONDS}" -le 0 ]; then echo "--timeout-seconds 必須是正整數" >&2 exit 2 fi if ! [[ "${INTERVAL_SECONDS}" =~ ^[0-9]+$ ]] || [ "${INTERVAL_SECONDS}" -le 0 ]; then echo "--interval-seconds 必須是正整數" >&2 exit 2 fi if [ "${NO_COLOR}" = "1" ]; then green="" yellow="" red="" reset="" else green="$(printf '\033[32m')" yellow="$(printf '\033[33m')" red="$(printf '\033[31m')" reset="$(printf '\033[0m')" fi ok() { printf "%sOK%s %s\n" "${green}" "${reset}" "$*" } pending() { printf "%sPENDING%s %s\n" "${yellow}" "${reset}" "$*" } block() { printf "%sBLOCKED%s %s\n" "${red}" "${reset}" "$*" } kv_from_file() { local path="$1" local key="$2" awk -F= -v key="$key" '$1 == key {print $2; found=1; exit} END {if (!found) print ""}' "$path" } run_gate() { local label="$1" local output="$2" shift 2 if "$@" >"${output}" 2>&1; then printf '%s=1\n' "${label}" return 0 fi printf '%s=0\n' "${label}" return 1 } log_root="${TMPDIR:-/tmp}/awoooi-dr-offsite-wait" mkdir -p "${log_root}" run_id="$(date +%Y%m%d-%H%M%S)" log_dir="${log_root}/${run_id}" mkdir -p "${log_dir}" echo "AWOOOI DR offsite convergence wait" date '+%Y-%m-%d %H:%M:%S %Z' echo "PROMETHEUS_URL=${PROMETHEUS_URL}" echo "ALERTMANAGER_URL=${ALERTMANAGER_URL}" echo "LOG_DIR=${log_dir}" echo started_at="$(date +%s)" attempt=0 while :; do attempt=$((attempt + 1)) now="$(date +%s)" elapsed=$((now - started_at)) attempt_dir="${log_dir}/attempt-${attempt}" mkdir -p "${attempt_dir}" scorecard_log="${attempt_dir}/scorecard-require-dr.log" prom_log="${attempt_dir}/prometheus-dr-ready.log" visibility_log="${attempt_dir}/backup-alert-visibility.log" final_log="${attempt_dir}/final-checklist-require-dr.log" scorecard_ok=0 prometheus_ok=0 visibility_ok=0 final_ok=0 if run_gate SCORECARD_READY "${scorecard_log}" \ bash "${ROOT_DIR}/scripts/reboot-recovery/full-stack-recovery-scorecard.sh" --require-dr; then scorecard_ok=1 fi if run_gate PROMETHEUS_READY "${prom_log}" \ python3 "${ROOT_DIR}/scripts/ops/recovery-scorecard-contract-check.py" \ --prometheus-url "${PROMETHEUS_URL}" \ --expect-core-ready \ --expect-dr-ready; then prometheus_ok=1 fi if run_gate BACKUP_VISIBILITY_READY "${visibility_log}" \ python3 "${ROOT_DIR}/scripts/ops/backup-alert-live-visibility-check.py" \ --prometheus-url "${PROMETHEUS_URL}" \ --alertmanager-url "${ALERTMANAGER_URL}"; then visibility_ok=1 fi recovery_state="$(kv_from_file "${scorecard_log}" RECOVERY_STATE)" next_step="$(kv_from_file "${scorecard_log}" NEXT_STEP)" escrow_missing="$(kv_from_file "${scorecard_log}" ESCROW_MISSING_COUNT)" full_marker="$(kv_from_file "${scorecard_log}" OFFSITE_FULL_MARKER_PRESENT)" offsite_configured="$(kv_from_file "${scorecard_log}" OFFSITE_CONFIGURED)" printf 'ATTEMPT=%s ELAPSED_SECONDS=%s SCORECARD_READY=%s PROMETHEUS_READY=%s BACKUP_VISIBILITY_READY=%s OFFSITE_CONFIGURED=%s FULL_MARKER=%s ESCROW_MISSING_COUNT=%s RECOVERY_STATE=%s NEXT_STEP=%s\n' \ "${attempt}" \ "${elapsed}" \ "${scorecard_ok}" \ "${prometheus_ok}" \ "${visibility_ok}" \ "${offsite_configured:-unknown}" \ "${full_marker:-unknown}" \ "${escrow_missing:-unknown}" \ "${recovery_state:-unknown}" \ "${next_step:-unknown}" if [ "${scorecard_ok}" -eq 1 ] && [ "${prometheus_ok}" -eq 1 ] && [ "${visibility_ok}" -eq 1 ]; then if bash "${ROOT_DIR}/scripts/reboot-recovery/dr-offsite-operator-checklist.sh" --require-dr --no-color >"${final_log}" 2>&1; then final_ok=1 fi fi if [ "${final_ok}" -eq 1 ]; then ok "DR offsite final gate converged" echo "FINAL_CHECKLIST_LOG=${final_log}" exit 0 fi if [ "${ONCE}" = "1" ]; then block "DR offsite final gate is not ready yet" echo "LAST_ATTEMPT_DIR=${attempt_dir}" exit 1 fi now="$(date +%s)" elapsed=$((now - started_at)) if [ "${elapsed}" -ge "${TIMEOUT_SECONDS}" ]; then block "timed out waiting for DR offsite final gate" echo "LAST_ATTEMPT_DIR=${attempt_dir}" echo "下一步:如果 ESCROW_MISSING_COUNT 仍大於 0,先由人工在 110 寫入真實非 secret evidence-id;如果已為 0,檢查 Prometheus scrape/rule 與 Alertmanager 收斂。" exit 1 fi pending "waiting ${INTERVAL_SECONDS}s for marker/textfile/Prometheus/Alertmanager convergence" sleep "${INTERVAL_SECONDS}" done