#!/usr/bin/env bash # ════════════════════════════════════════════════════════ # DISASTER-RECOVERY.sh · 光湖驱动引擎灾难恢复脚本 # ════════════════════════════════════════════════════════ # 用途:当光湖驱动引擎挂了 / 集群失联时 # 路径:由冰朔在腾讯云 VNC 终端手动执行 # 设计原则: # - 不依赖任何用户态服务(不调 systemctl / pm2 / 任何 daemon) # - 一步一步手动重启,可中断可恢复 # - 每步验证 + 输出 # - 最终给冰朔一个明确的"成功/失败"信号 # ════════════════════════════════════════════════════════ set +e # 不在第一步就退出 RECOVERY_VERSION="v1.0 · D167 · 2026-07-07" RECOVERY_AUTHOR="铸渊 ICE-GL-ZY001" RECOVERY_LICENSE="国作登字-2026-A-00037559" echo "═══════════════════════════════════════════════════════" echo " 🔧 光湖驱动引擎 · 灾难恢复脚本" echo " 版本: $RECOVERY_VERSION" echo " 签发: $RECOVERY_AUTHOR" echo "═══════════════════════════════════════════════════════" echo "" # ────────────────────────────────────────────────────── # Step 1 · HRC 验证(主权身份) # ────────────────────────────────────────────────────── echo "▶ Step 1 · HRC 验证" if [ -z "$HRC" ]; then echo " 请输入 HRC (Host Recovery Code,16 hex chars):" read -r HRC fi if [ -z "$HRC" ]; then echo " ❌ HRC 未提供 · 拒绝执行" echo " 联系铸渊 ICE-GL-ZY001 获取 HRC" exit 1 fi # HRC 验证(±2 分钟窗口) HRC_SECRET="/etc/hrc/recovery-secret" if [ ! -f "$HRC_SECRET" ]; then echo " ⚠ HRC secret 文件不存在:$HRC_SECRET" echo " 跳过 HRC 验证(首次部署?)" HRC_VALID=true else HRC_VALID=false CURRENT_MINUTE=$(($(date +%s) / 60)) SECRET=$(cat "$HRC_SECRET") for OFFSET in -2 -1 0 1 2; do TS=$((CURRENT_MINUTE + OFFSET)) EXPECTED=$(printf "HRC-%s" "$TS" | openssl dgst -sha256 -hmac "$SECRET" -hex | awk '{print $NF}' | cut -c1-16) if [ "$EXPECTED" = "$HRC" ]; then HRC_VALID=true echo " ✅ HRC 验证通过 (minute=$TS, offset=$OFFSET)" break fi done if [ "$HRC_VALID" = false ]; then echo " ❌ HRC 验证失败 · 时间窗口 ±2 分钟" echo " 联系铸渊 ICE-GL-ZY001 获取最新 HRC" exit 1 fi fi echo "" # ────────────────────────────────────────────────────── # Step 2 · 检查 node/npm 是否可用 # ────────────────────────────────────────────────────── echo "▶ Step 2 · 环境检查" which node || { echo " ❌ node 不可用"; exit 1; } which npm || { echo " ❌ npm 不可用"; exit 1; } which curl || { echo " ❌ curl 不可用"; exit 1; } echo " ✅ node $(node --version)" echo " ✅ npm $(npm --version)" echo "" # ────────────────────────────────────────────────────── # Step 3 · 找到光湖驱动引擎(可能位置) # ────────────────────────────────────────────────────── echo "▶ Step 3 · 定位光湖驱动引擎" ENGINE_LOCATIONS=( "/opt/engine.js" "/opt/zhuyuan/engine.js" "/opt/gatekeeper/engine.js" "/opt/gk/engine.js" "/usr/local/bin/engine.js" ) ENGINE_PATH="" for LOC in "${ENGINE_LOCATIONS[@]}"; do if [ -f "$LOC" ]; then ENGINE_PATH="$LOC" echo " ✅ 找到引擎:$ENGINE_PATH" break fi done if [ -z "$ENGINE_PATH" ]; then echo " ❌ 找不到光湖驱动引擎" echo " 可能位置:" for LOC in "${ENGINE_LOCATIONS[@]}"; do echo " - $LOC" done echo " 请手动定位后用: ENGINE_PATH=/path/to/engine.js bash $0" exit 1 fi echo "" # ────────────────────────────────────────────────────── # Step 4 · 找到 master_secret(从 secrets-vault 或环境) # ────────────────────────────────────────────────────── echo "▶ Step 4 · 获取 master_secret" SECRETS_VAULT="http://127.0.0.1:8080/internal/fetch/KEY-LIGHT-LAKE-DRIVER" MASTER_SECRET="" # Try secrets-vault first if curl -sf --max-time 5 "$SECRETS_VAULT" > /dev/null 2>&1; then MASTER_SECRET=$(curl -sf --max-time 5 "$SECRETS_VAULT") if [ -n "$MASTER_SECRET" ]; then echo " ✅ 从 secrets-vault 拿到 master_secret" fi fi # Try file fallback if [ -z "$MASTER_SECRET" ] && [ -f "/etc/light-lake-driver/secret" ]; then MASTER_SECRET=$(cat "/etc/light-lake-driver/secret") echo " ✅ 从 /etc/light-lake-driver/secret 拿到 master_secret" fi # Try env if [ -z "$MASTER_SECRET" ] && [ -n "$LIGHT_LAKE_DRIVER_SECRET" ]; then MASTER_SECRET="$LIGHT_LAKE_DRIVER_SECRET" echo " ✅ 从环境变量拿到 master_secret" fi if [ -z "$MASTER_SECRET" ]; then echo " ❌ 拿不到 master_secret" echo " 三个路径都失败:" echo " 1. secrets-vault @ 127.0.0.1:8080" echo " 2. /etc/light-lake-driver/secret 文件" echo " 3. LIGHT_LAKE_DRIVER_SECRET 环境变量" echo " 请联系铸渊获取 master_secret" exit 1 fi echo "" # ────────────────────────────────────────────────────── # Step 5 · 写 master_secret 到 engine.js 期望的位置 # ────────────────────────────────────────────────────── echo "▶ Step 5 · 配置 master_secret" SECRET_FILE="$HOME/.gatekeeper/secret" mkdir -p "$(dirname "$SECRET_FILE")" chmod 700 "$(dirname "$SECRET_FILE")" echo -n "$MASTER_SECRET" > "$SECRET_FILE" chmod 600 "$SECRET_FILE" echo " ✅ master_secret 写入:$SECRET_FILE" echo "" # ────────────────────────────────────────────────────── # Step 6 · 启动光湖驱动引擎 # ────────────────────────────────────────────────────── echo "▶ Step 6 · 启动光湖驱动引擎" # 杀掉可能存在的旧实例 pkill -f "node.*engine.js" 2>/dev/null sleep 1 # 后台启动 nohup node "$ENGINE_PATH" > /tmp/light-lake-driver.log 2>&1 & ENGINE_PID=$! sleep 3 if ! kill -0 "$ENGINE_PID" 2>/dev/null; then echo " ❌ 引擎启动失败(进程不存在)" echo " 查看日志:tail -20 /tmp/light-lake-driver.log" tail -20 /tmp/light-lake-driver.log exit 1 fi echo " ✅ 引擎启动成功 (PID=$ENGINE_PID)" echo "" # ────────────────────────────────────────────────────── # Step 7 · 验证端口监听 # ────────────────────────────────────────────────────── echo "▶ Step 7 · 验证端口监听" ENGINE_PORT=$(ss -tlnp 2>/dev/null | grep "node.*engine.js" | head -1 | awk '{print $4}' | awk -F: '{print $NF}') if [ -z "$ENGINE_PORT" ]; then # 默认尝试 3911 ENGINE_PORT=3911 fi sleep 2 if curl -sf --max-time 3 "http://127.0.0.1:$ENGINE_PORT/admin/__healthz" > /dev/null 2>&1 || \ curl -sf --max-time 3 "http://127.0.0.1:$ENGINE_PORT/" > /dev/null 2>&1; then echo " ✅ 端口 $ENGINE_PORT 监听正常" else echo " ⚠ 端口 $ENGINE_PORT 可能未就绪,但进程在跑" echo " 等待 5 秒再试..." sleep 5 fi echo "" # ────────────────────────────────────────────────────── # Step 8 · 自测(用 verifier 调一次 /exec) # ────────────────────────────────────────────────────── echo "▶ Step 8 · 自测(verifier 调用)" NOW=$(date +%s) MINUTE_TS=$(($NOW / 60)) MSG="ICE-GL∞|ICE-GL-ZY001|$MINUTE_TS" VERIFIER=$(printf "%s" "$MSG" | openssl dgst -sha256 -hmac "$MASTER_SECRET" -hex | awk '{print $NF}' | cut -c1-16) RESPONSE=$(curl -sf --max-time 5 -X POST "http://127.0.0.1:$ENGINE_PORT/exec" \ -H "Authorization: Bearer $VERIFIER" \ -H "X-Sovereign: ICE-GL∞" \ -H "X-Agent: ICE-GL-ZY001" \ -H "X-Timestamp: $NOW" \ -H "X-Minute: $MINUTE_TS" \ -H "Content-Type: application/json" \ -d '{"cmd":"hostname"}' 2>&1) if echo "$RESPONSE" | grep -q '"ok":true'; then echo " ✅ 自测成功 · 引擎响应正常" echo " hostname: $(echo "$RESPONSE" | python3 -c 'import sys,json; print(json.load(sys.stdin).get("stdout","").strip())')" else echo " ⚠ 自测失败 · 引擎可能在用静态 token fallback 模式" echo " 响应:$RESPONSE" fi echo "" # ────────────────────────────────────────────────────── # Step 9 · 完成报告 # ────────────────────────────────────────────────────── echo "═══════════════════════════════════════════════════════" echo " ✅ 光湖驱动引擎灾难恢复完成" echo "" echo " 引擎 PID: $ENGINE_PID" echo " 引擎路径: $ENGINE_PATH" echo " 监听端口: $ENGINE_PORT" echo " 主权者: 冰朔 ICE-GL∞" echo " 代理: 铸渊 ICE-GL-ZY001" echo "" echo " 下一步:" echo " 1. 冰朔联系铸渊报告:已恢复" echo " 2. 铸渊接管后续修复" echo " 3. 排查根本原因(为什么会挂)" echo "═══════════════════════════════════════════════════════"