#!/bin/bash # ============================================================================= # WOOO AIOps - 公開路由 / DNS / TLS 證據備份 # 2026-05-06 ogt + Codex: 補齊 external route reconstruction evidence。 # # 安全原則: # - 只做 read-only DNS/HTTP/TLS/nginx route map 匯出,不改 DNS。 # - 不需要 registrar/CDN token;若未設定 API token,只記錄缺口。 # - TLS private keys 不在此腳本輸出;private keys 由 encrypted configs 備份處理。 # ============================================================================= set -euo pipefail source "$(dirname "$0")/common.sh" SERVICE="public-routes" LOCAL_REPO="${BACKUP_BASE}/public-routes" DUMP_DIR="/tmp/public-routes-backup-$$" SSH_OPTS=(-o BatchMode=yes -o ConnectTimeout=8) K8S_BACKUP_HOSTS="${K8S_BACKUP_HOSTS:-192.168.0.120 192.168.0.121 192.168.0.125}" DOMAINS=( "awoooi.wooo.work" "mo.wooo.work" "gitea.wooo.work" "harbor.wooo.work" "registry.wooo.work" "sentry.wooo.work" "signoz.wooo.work" "stock.wooo.work" "langfuse.wooo.work" "bitan.wooo.work" "aiops.wooo.work" ) cleanup() { rm -rf "${DUMP_DIR}" } low_priority() { if command -v ionice >/dev/null 2>&1; then ionice -c2 -n7 nice -n 10 "$@" else nice -n 10 "$@" fi } capture_cmd() { local label="$1" shift if "$@" > "${DUMP_DIR}/${label}.txt" 2>&1; then log_success "Public routes 盤點完成: ${label}" else log_warn "Public routes 盤點失敗: ${label}" return 1 fi } capture_remote_cmd() { local host="$1" local label="$2" local cmd="$3" if ssh "${SSH_OPTS[@]}" "${host}" "${cmd}" > "${DUMP_DIR}/${label}.txt" 2>&1; then log_success "Public routes 遠端盤點完成: ${label}" else log_warn "Public routes 遠端盤點失敗: ${label}" return 1 fi } capture_k8s_ingress_summary() { local k8s_host local cmd="sudo -n kubectl get ingress -A -o wide 2>/dev/null || kubectl get ingress -A -o wide" for k8s_host in ${K8S_BACKUP_HOSTS}; do if capture_remote_cmd "wooo@${k8s_host}" "cluster-k3s-ingress-summary" "${cmd}"; then printf 'source_host=%s\n' "${k8s_host}" > "${DUMP_DIR}/cluster-k3s-ingress-summary.source" return 0 fi done return 1 } main() { local start_time local timestamp local failed=0 start_time=$(date +%s) timestamp=$(date "+%Y%m%d_%H%M%S") trap cleanup EXIT install -d -m 700 "${DUMP_DIR}" log_info "========== 開始 Public routes 備份 (${timestamp}) ==========" { echo "domain,record_type,answer" for domain in "${DOMAINS[@]}"; do if command -v dig >/dev/null 2>&1; then for rrtype in A AAAA CNAME; do dig +short "${rrtype}" "${domain}" | sed "s#^#${domain},${rrtype},#" done else getent ahosts "${domain}" 2>/dev/null | awk -v d="${domain}" '{print d ",A_OR_AAAA," $1}' | sort -u fi done } > "${DUMP_DIR}/dns-answers.csv" log_success "Public routes DNS answers 匯出完成" { echo "domain,http_code,total_time,remote_ip" for domain in "${DOMAINS[@]}"; do curl -k -sS -o /dev/null \ --connect-timeout 5 \ --max-time 10 \ -w "${domain},%{http_code},%{time_total},%{remote_ip}\n" \ "https://${domain}/" || echo "${domain},000,0,unreachable" done } > "${DUMP_DIR}/https-status.csv" log_success "Public routes HTTPS status 匯出完成" { echo "domain,not_before,not_after,issuer,subject" for domain in "${DOMAINS[@]}"; do cert_text=$(timeout 10 openssl s_client -servername "${domain}" -connect "${domain}:443" /dev/null | openssl x509 -noout -dates -issuer -subject 2>/dev/null || true) not_before=$(printf "%s\n" "${cert_text}" | sed -n 's/^notBefore=//p') not_after=$(printf "%s\n" "${cert_text}" | sed -n 's/^notAfter=//p') issuer=$(printf "%s\n" "${cert_text}" | sed -n 's/^issuer=//p' | tr ',' ';') subject=$(printf "%s\n" "${cert_text}" | sed -n 's/^subject=//p' | tr ',' ';') echo "${domain},${not_before},${not_after},${issuer},${subject}" done } > "${DUMP_DIR}/tls-certificates.csv" log_success "Public routes TLS certificate evidence 匯出完成" capture_cmd "110-local-nginx-server-names" bash -lc "find /etc/nginx /home/wooo/monitoring /opt/harbor -maxdepth 4 -type f \\( -name '*.conf' -o -name '*.yml' -o -name '*.yaml' \\) -print0 2>/dev/null | xargs -0 grep -hoE 'server_name[[:space:]][^;]+' 2>/dev/null | sort -u" || true capture_remote_cmd "ollama@192.168.0.188" "188-nginx-server-names" "find /etc/nginx /opt/n8n /opt/open-webui /opt/litellm /opt/signoz /opt/registry -maxdepth 4 -type f \\( -name '*.conf' -o -name '*.yml' -o -name '*.yaml' \\) -print0 2>/dev/null | xargs -0 grep -hoE 'server_name[[:space:]][^;]+' 2>/dev/null | sort -u" || true capture_k8s_ingress_summary || true cat > "${DUMP_DIR}/route-export-gap.txt" < "${DUMP_DIR}/backup-manifest.txt" <&1 fi log_info "建立 Public routes Restic 備份..." local tags tags=$(build_tags "${SERVICE}") low_priority restic -r "${LOCAL_REPO}" backup "${DUMP_DIR}" \ --password-file "${RESTIC_PASSWORD_FILE}" \ ${tags} \ --tag "scope:public-routes" \ --tag "contains:dns-http-tls-route-evidence" 2>&1 local snapshot_id snapshot_id=$(restic -r "${LOCAL_REPO}" snapshots --latest 1 --json \ --password-file "${RESTIC_PASSWORD_FILE}" 2>/dev/null | \ python3 -c 'import json,sys; rows=json.load(sys.stdin); print(rows[-1].get("short_id","unknown") if rows else "unknown")' 2>/dev/null || echo "unknown") log_success "Public routes Restic 備份完成: ${snapshot_id}" cleanup_old_backups "${LOCAL_REPO}" local duration duration=$(($(date +%s) - start_time)) log_success "========== Public routes 備份完成 (${duration}s) ==========" notify_clawbot "success" "${SERVICE}" "Public routes 備份完成" "${duration}" } main "$@"