Files
awoooi/scripts/backup/backup-public-routes.sh
Your Name cfb866d055
Some checks failed
Ansible Lint / lint (push) Successful in 35s
CD Pipeline / tests (push) Failing after 13s
CD Pipeline / build-and-deploy (push) Has been skipped
CD Pipeline / post-deploy-checks (push) Has been skipped
Code Review / ai-code-review (push) Failing after 11s
feat(governance): add agent market automation surfaces
2026-06-04 21:50:55 +08:00

183 lines
6.8 KiB
Bash
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
#!/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 2>/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" <<EOF
timestamp=${timestamp}
cloud_dns_zone_export=not_configured_without_provider_token
registrar_export=manual_escrow_required
cdn_or_tunnel_export=manual_escrow_required
private_keys_policy=not_exported_here_covered_by_encrypted_configs_backup
EOF
cat > "${DUMP_DIR}/backup-manifest.txt" <<EOF
service=public-routes
timestamp=${timestamp}
contains=dns_answers,https_status,tls_certificate_metadata,nginx_server_names,k8s_ingress_summary,route_export_gap
secret_policy=no_provider_tokens_no_tls_private_keys
failed_components=${failed}
EOF
if [ ! -d "${LOCAL_REPO}/data" ]; then
log_info "初始化 Restic 倉庫: ${LOCAL_REPO}"
low_priority restic -r "${LOCAL_REPO}" init --password-file "${RESTIC_PASSWORD_FILE}" 2>&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 "$@"