#!/usr/bin/env bash
# ════════════════════════════════════════════════════════════════
# 国内域名机 · 一键部署 · bootstrap.sh
# Sovereign: TCS-0002∞ · ICE-GL∞ · 国作登字-2026-A-00037559
# 守护: 铸渊 · ICE-GL-ZY001
# 模板版本: 0.2.0
# ════════════════════════════════════════════════════════════════
#
# 服务器: GH-CVM-DOMAIN-PROD-01 / ZY-SVR-CN01 / 广州 2C2G 轻量
# 域名:   guanghulab.com (已备案 + Let's Encrypt 续到 2026-08-07)
# 角色:   唯一对外服务器 (Portal + 密钥管理页 + 自托管 git, 后续 PR 填业务)
#
# 这一份 bootstrap 把一台**重装系统后空白的 2C2G Ubuntu 22.04** 准备好:
#   1. 自我感知 (跑 detect-env.sh) + 动态调优 (跑 tune-from-env.sh)
#   2. 切换 APT 镜像源到阿里云 (国内可达, 不卡 Docker mirror)
#   3. 装基础工具链 + nginx + node 20 + pm2 + certbot
#   4. 在 /data/guanghulab/ 下建标准目录, 给后续 PR (portal/secrets-vault/forgejo) 留空位
#   5. enable certbot.timer (LE 证书续期 cron)
#   6. 渲染 nginx server block (不覆盖 SSL, 只追加 guanghulab.com 站点)
#   7. (tiny 档) 建 1G swap 兜底防 OOM
#   8. (失败时) 自动调用 rollback.sh 回到 pre-bootstrap 快照
#
# 设计理念:
#   - cc-003 动态适配: 不假设 2C2G, 探测后决档
#   - cc-004 强制自主: 失败自动回滚, 中文回执 → /data/guanghulab/_logs/deploy-report.md
#   - 不 apt upgrade (2C2G 跑 upgrade 会卡死 30+ 分钟)
#   - 不动 /etc/letsencrypt 已有证书 (LE 配置已在, 续期到 2026-08-07)
#
# 用法 (本地直跑):
#   sudo bash bootstrap.sh
#
# 通常通过 .github/workflows/deploy-domain-server.yml 远程触发,
# 该 workflow 设有"重装广州"误触锁.
#
# 环境变量:
#   DATA_ROOT       默认 /data/guanghulab (持久数据根)
#   DEPLOY_ROOT     默认 /opt/guanghulab (代码/快照/日志根)
#   DOMAIN          默认 guanghulab.com
#   STAGE           默认 bootstrap (合法值: bootstrap / update / rollback)
#   SERVER_ID       默认 GH-CVM-DOMAIN-PROD-01
#   ROLLBACK_TS     可选 (STAGE=rollback 时回到指定快照)
#
# 退出码:
#   0  全部 OK
#   1  bootstrap 失败 (失败前已尝试 autorollback)
#   2  前置条件错 (非 root / DATA_ROOT 缺失等), 不会动机器
# ════════════════════════════════════════════════════════════════

set -euo pipefail

# ─── 全局参数 ─────────────────────────────────────────────────
TEMPLATE_VERSION="0.2.0"
SERVER_ID="${SERVER_ID:-GH-CVM-DOMAIN-PROD-01}"
DATA_ROOT="${DATA_ROOT:-/data/guanghulab}"
DEPLOY_ROOT="${DEPLOY_ROOT:-/opt/guanghulab}"
DOMAIN="${DOMAIN:-guanghulab.com}"
STAGE="${STAGE:-bootstrap}"

export DEBIAN_FRONTEND=noninteractive

SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
SERVER_ENV_JSON="$DEPLOY_ROOT/_logs/server-env.json"

# ─── 校验前置条件 ─────────────────────────────────────────────
if [ "$EUID" -ne 0 ]; then
  echo "❌ 需要 root (请用 sudo bash bootstrap.sh)" >&2
  exit 2
fi

if ! [[ "$SERVER_ID" =~ ^GH-CVM-[A-Z0-9-]+$ ]]; then
  echo "❌ SERVER_ID 格式不合法: $SERVER_ID (应为 GH-CVM-[A-Z0-9-]+)" >&2
  exit 2
fi

# ─── 中文回执 (整个流程持续追加, 任何阶段失败都能给霜砚看) ───
mkdir -p "$DEPLOY_ROOT/_logs"
DEPLOY_REPORT="$DEPLOY_ROOT/_logs/deploy-report.md"
RECEIPT_TS="$(date +%Y%m%d-%H%M%S)"

# 重置当次 report (每次跑 bootstrap 都覆盖, 历史在 _logs/deploy-report-*.md.bak)
if [ -f "$DEPLOY_REPORT" ]; then
  cp -a "$DEPLOY_REPORT" "$DEPLOY_REPORT.$RECEIPT_TS.bak" || true
fi

report() {
  # 中文回执 + 控制台同步打印
  local line="$1"
  echo "$line"
  printf '%s\n' "$line" >> "$DEPLOY_REPORT"
}

cat > "$DEPLOY_REPORT" <<EOF
# 国内域名机 · 部署回执

> 服务器: $SERVER_ID ($DOMAIN)
> 阶段: $STAGE · 模板版本 $TEMPLATE_VERSION
> 开始时间: $(date '+%Y-%m-%d %H:%M:%S %Z')

EOF

report "## ① 自我感知 + 动态调优"

# ─── 阶段路由 ─────────────────────────────────────────────────
case "$STAGE" in
  bootstrap|update) ;;
  rollback)
    report ""
    report "## 阶段=rollback · 委派给 rollback.sh"
    if [ -x "$SCRIPT_DIR/rollback.sh" ]; then
      if [ -n "${ROLLBACK_TS:-}" ]; then
        DATA_ROOT="$DATA_ROOT" DEPLOY_ROOT="$DEPLOY_ROOT" \
          bash "$SCRIPT_DIR/rollback.sh" --to "$ROLLBACK_TS"
      else
        DATA_ROOT="$DATA_ROOT" DEPLOY_ROOT="$DEPLOY_ROOT" \
          bash "$SCRIPT_DIR/rollback.sh"
      fi
      exit $?
    else
      report "❌ rollback.sh 不存在或不可执行"
      exit 1
    fi
    ;;
  *)
    echo "❌ 未知 STAGE: $STAGE (合法: bootstrap / update / rollback)" >&2
    exit 2
    ;;
esac

# ─── 0. 自我感知 ─────────────────────────────────────────────
echo "═══════════════════════════════════════════════════════════"
echo "  国内域名机 bootstrap"
echo "  母编号:    $SERVER_ID"
echo "  数据根:    $DATA_ROOT"
echo "  部署根:    $DEPLOY_ROOT"
echo "  域名:      $DOMAIN"
echo "  阶段:      $STAGE"
echo "  模板版本:  $TEMPLATE_VERSION"
echo "═══════════════════════════════════════════════════════════"

# 必需 jq 给 detect/tune 用
if ! command -v jq >/dev/null 2>&1; then
  apt-get update -qq || true
  apt-get install -y -qq jq || {
    report "❌ jq 装不上, 终止"
    exit 1
  }
fi

if [ -x "$SCRIPT_DIR/detect-env.sh" ]; then
  echo "[0/8] 探测当前服务器实际配置 (detect-env.sh)..."
  DATA_ROOT="$DATA_ROOT" DEPLOY_ROOT="$DEPLOY_ROOT" DOMAIN="$DOMAIN" \
    bash "$SCRIPT_DIR/detect-env.sh" "$SERVER_ENV_JSON" || {
      report "⚠️  detect-env.sh 失败, 退化到默认 tiny 档"
    }
fi

# ─── 0.5 跑前快照 (autorollback 兜底用) ──────────────────────
# 把当前 nginx 配置 + .env.tune + systemd unit 打包, 失败时能回这刻
PRE_TS="$(date +%Y%m%d-%H%M%S)"
SNAP_ROOT="$DATA_ROOT/snapshots"
PRE_SNAP="$SNAP_ROOT/$PRE_TS-pre-$STAGE"
mkdir -p "$PRE_SNAP/nginx" "$PRE_SNAP/systemd"
[ -d /etc/nginx/sites-available ] && cp -a /etc/nginx/sites-available "$PRE_SNAP/nginx/" || true
[ -d /etc/nginx/sites-enabled ]   && cp -a /etc/nginx/sites-enabled   "$PRE_SNAP/nginx/" || true
[ -f "$DATA_ROOT/.env.tune" ]     && cp -a "$DATA_ROOT/.env.tune"     "$PRE_SNAP/" || true
[ -f /etc/systemd/system/guanghulab-portal.service ] && \
  cp -a /etc/systemd/system/guanghulab-portal.service "$PRE_SNAP/systemd/" || true
echo "snapshot before $STAGE @ $(date -u +%Y-%m-%dT%H:%M:%SZ)" > "$PRE_SNAP/NOTE"

# 只保留最近 10 份, 防系统盘塞满
if [ -d "$SNAP_ROOT" ]; then
  ls -1tr "$SNAP_ROOT" 2>/dev/null \
    | head -n -10 \
    | xargs -I{} rm -rf "$SNAP_ROOT/{}" 2>/dev/null || true
fi
report "- 跑前快照: \`$PRE_SNAP\`"

# ─── autorollback 钩子: ERR/EXIT 触发 ────────────────────────
# bootstrap 阶段任何步骤失败 → 自动调 rollback.sh 回到 PRE_SNAP
BOOTSTRAP_OK="false"
autorollback_on_failure() {
  local rc=$?
  if [ "$BOOTSTRAP_OK" = "true" ]; then return 0; fi
  if [ "$rc" -eq 0 ]; then return 0; fi
  echo
  echo "═══════════════════════════════════════════════════════════"
  echo "  ❌ bootstrap 失败 (exit=$rc), 触发自动回滚到 $PRE_TS"
  echo "═══════════════════════════════════════════════════════════"
  report ""
  report "## ❌ bootstrap 失败 (exit=$rc), 触发自动回滚"
  report ""
  report "- 目标快照: \`$PRE_TS-pre-$STAGE\`"
  if [ -x "$SCRIPT_DIR/rollback.sh" ]; then
    DATA_ROOT="$DATA_ROOT" DEPLOY_ROOT="$DEPLOY_ROOT" \
      bash "$SCRIPT_DIR/rollback.sh" --to "$(basename "$PRE_SNAP")" \
      || report "⚠️  自动回滚也失败了, 请人工介入. snapshot=\`$PRE_SNAP\`"
  else
    report "⚠️  rollback.sh 不可执行, 自动回滚跳过"
  fi
  report ""
  report "**给冰朔的人话**: 这次部署没成功. 我已经把机器恢复到部署前的状态."
  report "**Awen 不需要做任何事**, 把这份 deploy-report.md 截图发给冰朔即可."
}
trap autorollback_on_failure EXIT

# ─── 0.6 动态调优 ─────────────────────────────────────────────
if [ -x "$SCRIPT_DIR/tune-from-env.sh" ] && [ -f "$SERVER_ENV_JSON" ]; then
  ENV_FILE="$SERVER_ENV_JSON" DATA_ROOT="$DATA_ROOT" DEPLOY_ROOT="$DEPLOY_ROOT" \
    bash "$SCRIPT_DIR/tune-from-env.sh" || {
      report "⚠️  tune-from-env.sh 失败, 用默认 tiny 档继续"
    }
fi

# 兜底默认值 (tune 失败 / .env.tune 缺失时)
SIZE_TIER="tiny"
PORTAL_INSTANCES="1"
PORTAL_MAX_MEMORY_MB="384"
NGINX_WORKER_CONN="512"
NGINX_WORKER_PROCESSES="1"
FORGEJO_ENABLED="false"
LFS_ENABLED="false"
NEED_SWAP_GB="1"
SYSTEMD_PORTAL_HIGH_MB="500"
SYSTEMD_PORTAL_MAX_MB="700"

if [ -f "$DATA_ROOT/.env.tune" ]; then
  # shellcheck disable=SC1091
  . "$DATA_ROOT/.env.tune"
fi

report "- 档位: **$SIZE_TIER** · portal=${PORTAL_INSTANCES} 进程 · nginx=${NGINX_WORKER_PROCESSES} worker / ${NGINX_WORKER_CONN} conn"
report ""

# ─── 1. APT 镜像源 (阿里云) ──────────────────────────────────
report "## ② 装基础工具链 (跳过 apt upgrade)"
echo "[1/8] 切换 APT 源到阿里云镜像 ..."

CODENAME="$(. /etc/os-release && echo "${VERSION_CODENAME:-jammy}")"
if ! grep -q "mirrors.aliyun.com" /etc/apt/sources.list 2>/dev/null; then
  cp /etc/apt/sources.list "/etc/apt/sources.list.bak.$(date +%s)" || true
  cat > /etc/apt/sources.list <<EOF
deb https://mirrors.aliyun.com/ubuntu/ $CODENAME main restricted universe multiverse
deb https://mirrors.aliyun.com/ubuntu/ $CODENAME-updates main restricted universe multiverse
deb https://mirrors.aliyun.com/ubuntu/ $CODENAME-security main restricted universe multiverse
deb https://mirrors.aliyun.com/ubuntu/ $CODENAME-backports main restricted universe multiverse
EOF
fi
apt-get update -qq

# ─── 2. 基础工具链 ───────────────────────────────────────────
echo "[2/8] 安装基础工具链 (不 apt upgrade, 2C2G 卡死风险) ..."
# 注: 单独装, 不 apt upgrade. nginx 已装则 apt 自动跳过 (不动 SSL 配置)
apt-get install -y -qq \
  curl wget ca-certificates gnupg lsb-release \
  git jq unzip vim htop net-tools \
  nginx certbot python3-certbot-nginx \
  apache2-utils \
  fail2ban ufw \
  rsync openssh-client \
  build-essential

report "- apt 工具链 OK (nginx / certbot / fail2ban / git / htpasswd ...)"

# ─── 3. Node 20 + PM2 (npmmirror 镜像) ───────────────────────
NODE_OK="false"
if command -v node >/dev/null 2>&1; then
  NODE_MAJOR="$(node -v 2>/dev/null | sed 's/v//;s/\..*//')"
  [ "${NODE_MAJOR:-0}" -ge 20 ] && NODE_OK="true"
fi

if [ "$NODE_OK" = "false" ]; then
  echo "[3/8] 安装 Node 20 ..."
  if ! curl -fsSL --max-time 30 https://deb.nodesource.com/setup_20.x | bash - ; then
    echo "    NodeSource 不可达, 改用 nvm 国内镜像..."
    export NVM_NODEJS_ORG_MIRROR=https://npmmirror.com/mirrors/node
    curl -fsSL https://gitee.com/mirrors/nvm/raw/master/install.sh | bash || {
      report "❌ Node 20 装不上"
      exit 1
    }
  fi
  apt-get install -y -qq nodejs || true
fi

if ! command -v pm2 >/dev/null 2>&1; then
  npm config set registry https://registry.npmmirror.com
  npm install -g pm2 --silent
fi
report "- Node $(node -v 2>/dev/null) + PM2 $(pm2 -v 2>/dev/null) OK"

# ─── 4. 标准目录 + 占位 (PR-4/5/6 填) ────────────────────────
report ""
report "## ③ 建标准目录 (留位给后续棒)"
echo "[4/8] 建 $DATA_ROOT 标准目录 + 后续 PR 占位 ..."

mkdir -p \
  "$DATA_ROOT" \
  "$DATA_ROOT/portal" \
  "$DATA_ROOT/secrets-vault" \
  "$DATA_ROOT/forgejo" \
  "$DATA_ROOT/snapshots" \
  "$DATA_ROOT/logs/nginx" \
  "$DATA_ROOT/logs/portal" \
  "$DEPLOY_ROOT/_active" \
  "$DEPLOY_ROOT/_archive" \
  "$DEPLOY_ROOT/_logs" \
  "$DEPLOY_ROOT/_secrets" \
  "$DEPLOY_ROOT/domain-cn"
chmod 700 "$DEPLOY_ROOT/_secrets" "$DATA_ROOT/secrets-vault"

# 占位 README, 后续 PR 来填具体内容
[ ! -f "$DATA_ROOT/portal/README.md" ] && cat > "$DATA_ROOT/portal/README.md" <<'EOF'
# /data/guanghulab/portal/ · 占位

Portal 业务代码留给 PR-4 (光湖门户) 填充。本目录在 PR-2 bootstrap 时建立, 当前为空。
EOF
[ ! -f "$DATA_ROOT/secrets-vault/README.md" ] && cat > "$DATA_ROOT/secrets-vault/README.md" <<'EOF'
# /data/guanghulab/secrets-vault/ · PR-5 落地

光湖国内域名机本地密钥库. AES-256-GCM 加密.

- `.master`    — 32 字节随机主密钥 (chmod 600). **不上 git, 不上 COS, 不传冰朔.**
- `vault.enc`  — 加密后的 secrets JSON (chmod 600).

服务: `pm2 status guanghulab-vault` · 监听 127.0.0.1:8080
访问: 走 nginx 的 /admin/ (basic-auth + 仅本机 IP)
首启凭据: /data/guanghulab/_logs/vault-credentials-FIRST-BOOT.txt (抄完删)
EOF
[ ! -f "$DATA_ROOT/forgejo/README.md" ] && cat > "$DATA_ROOT/forgejo/README.md" <<'EOF'
# /data/guanghulab/forgejo/ · 占位

Forgejo 自托管 git 留给 PR-6 (一台到底搬家方案) 填充。
当前 tune-from-env tiny 档下 FORGEJO_ENABLED=false, bootstrap 不会启动 forgejo。
EOF

report "- 建好 \`portal/\` \`secrets-vault/\` \`forgejo/\` 三个空目录给 PR-4/5/6"

# ─── 5. Swap 兜底 (tiny 档) ──────────────────────────────────
if [ "$NEED_SWAP_GB" = "1" ]; then
  if [ ! -f /swapfile.guanghulab ]; then
    echo "[5/8] 建 1G swap (tiny 档防 OOM) ..."
    fallocate -l 1G /swapfile.guanghulab || dd if=/dev/zero of=/swapfile.guanghulab bs=1M count=1024
    chmod 600 /swapfile.guanghulab
    mkswap /swapfile.guanghulab
    swapon /swapfile.guanghulab
    if ! grep -q '/swapfile.guanghulab' /etc/fstab; then
      echo '/swapfile.guanghulab none swap sw 0 0' >> /etc/fstab
    fi
    report "- 已建 1G swap \`/swapfile.guanghulab\` (tiny 档防 OOM)"
  fi
fi

# ─── 6. Nginx 站点配置 ───────────────────────────────────────
report ""
report "## ④ 配置 Nginx (不动现有 SSL, 只追加站点)"
echo "[6/8] 渲染 nginx 站点配置 (不覆盖 /etc/letsencrypt/) ..."

NGX_TPL="$SCRIPT_DIR/nginx/guanghulab.conf.template"
NGX_OUT="/etc/nginx/sites-available/guanghulab.conf"

if [ ! -f "$NGX_TPL" ]; then
  report "⚠️  nginx 模板缺失: $NGX_TPL, 跳过站点配置"
else
  # 检查 LE 证书是否存在 (重装系统后 /etc/letsencrypt 可能在或不在)
  LE_CERT="/etc/letsencrypt/live/$DOMAIN/fullchain.pem"
  LE_KEY="/etc/letsencrypt/live/$DOMAIN/privkey.pem"
  if [ -f "$LE_CERT" ] && [ -f "$LE_KEY" ]; then
    USE_SSL="1"
    report "- LE 证书在: \`$LE_CERT\` · 启用 HTTPS server block"
  else
    USE_SSL="0"
    report "- LE 证书暂不在 (重装后未恢复), bootstrap 只起 80 端口, 不主动 issue (避免 LE rate-limit)"
    report "  → 冰朔: 把 \`/etc/letsencrypt/\` 整目录从备份恢复后再 \`systemctl reload nginx\` 即可启用 HTTPS"
  fi

  # 渲染模板 (用 awk, 不用 sed, 避开特殊字符)
  DOMAIN_VAL="$DOMAIN" \
  NGINX_WORKER_CONN_VAL="$NGINX_WORKER_CONN" \
  USE_SSL_VAL="$USE_SSL" \
  PORTAL_PORT_VAL="${PORTAL_PORT:-3000}" \
  VAULT_PORT_VAL="${VAULT_PORT:-8080}" \
  awk '
    /^# __SSL_BEGIN__[[:space:]]*$/   { mode="ssl";   next }
    /^# __SSL_END__[[:space:]]*$/     { mode="";      next }
    /^# __NOSSL_BEGIN__[[:space:]]*$/ { mode="nossl"; next }
    /^# __NOSSL_END__[[:space:]]*$/   { mode="";      next }
    {
      # 条件块: ssl 段仅 USE_SSL=1 输出, nossl 段仅 USE_SSL=0 输出
      if (mode == "ssl") {
        if (ENVIRON["USE_SSL_VAL"] != "1") next
        sub(/^# /, "")
        sub(/^#$/, "")
      } else if (mode == "nossl") {
        if (ENVIRON["USE_SSL_VAL"] == "1") next
        sub(/^# /, "")
        sub(/^#$/, "")
      }
      gsub(/__DOMAIN__/, ENVIRON["DOMAIN_VAL"])
      gsub(/__PORTAL_PORT__/, ENVIRON["PORTAL_PORT_VAL"])
      gsub(/__VAULT_PORT__/, ENVIRON["VAULT_PORT_VAL"])
      gsub(/__NGINX_WORKER_CONN__/, ENVIRON["NGINX_WORKER_CONN_VAL"])
      print
    }' "$NGX_TPL" > "$NGX_OUT"

  ln -sf "$NGX_OUT" "/etc/nginx/sites-enabled/guanghulab.conf"
  # 不再删 default (避免误删管理员手配的 default), 只让我们的 server_name 优先
  # 真要清除 default 的, 让冰朔在霜砚指导下 rm /etc/nginx/sites-enabled/default

  # 调 nginx worker_connections (仅当本模板能匹配到默认值)
  if [ -f /etc/nginx/nginx.conf ]; then
    sed -i "s/worker_connections [0-9]\+;/worker_connections $NGINX_WORKER_CONN;/" /etc/nginx/nginx.conf || true
  fi

  if nginx -t 2>&1; then
    systemctl reload nginx || systemctl restart nginx
    report "- nginx 配置语法 OK, 已 reload"
  else
    report "❌ nginx 配置语法错, 拒绝 reload (旧配置仍在跑)"
    exit 1
  fi
fi

# ─── 6.5 生成 /admin/ basic-auth 凭据 (PR-5 secrets-vault) ───
report ""
report "## ④.5 生成密钥管理页 /admin/ basic-auth 凭据"
HTPASSWD_FILE="/etc/nginx/.htpasswd_admin"
VAULT_CRED_FILE="$DEPLOY_ROOT/_logs/vault-credentials-FIRST-BOOT.txt"

if [ ! -f "$HTPASSWD_FILE" ]; then
  if ! command -v htpasswd >/dev/null 2>&1; then
    report "⚠️  htpasswd 不存在 (apache2-utils 没装), /admin/ 走仅 IP 限制"
  else
    # 生成 24 位随机密码 (URL-safe, 不含会让 awk/bash 难处理的字符)
    VAULT_USER="bingshuo"
    VAULT_PASS="$(LC_ALL=C tr -dc 'A-Za-z0-9' </dev/urandom | head -c 24)"
    htpasswd -B -b -c "$HTPASSWD_FILE" "$VAULT_USER" "$VAULT_PASS" >/dev/null 2>&1
    chmod 644 "$HTPASSWD_FILE"

    # 落首启凭据文件 — 冰朔抄完应自行删除
    umask 077
    cat > "$VAULT_CRED_FILE" <<EOF
# 光湖密钥管理页 · 首启凭据 · 抄完后**手动删除本文件**
# 守护: 铸渊 · ICE-GL-ZY001
# 生成时间: $(date -u +%Y-%m-%dT%H:%M:%SZ)
# 域名机: $SERVER_ID ($DOMAIN)
#
# 访问方式 (在你电脑上跑):
#   ssh -L 8443:127.0.0.1:443 ${SUDO_USER:-ubuntu}@<本机公网IP>
#   浏览器打开 https://localhost:8443/admin/ (TLS 警告点继续)
#
# basic-auth:
#   用户名: $VAULT_USER
#   密码:   $VAULT_PASS
#
# 抄完请运行: sudo rm $VAULT_CRED_FILE
EOF
    chmod 600 "$VAULT_CRED_FILE"
    umask 022
    report "- 已生成 /admin/ basic-auth (\`$HTPASSWD_FILE\`)"
    report "- 凭据落在 \`$VAULT_CRED_FILE\` (chmod 600, 抄完 \`sudo rm\`)"
  fi
else
  report "- /admin/ basic-auth (\`$HTPASSWD_FILE\`) 已存在, 不覆盖 (改密码请手动 \`htpasswd -B $HTPASSWD_FILE bingshuo\`)"
fi

# ─── 7. Certbot 自动续期 timer ───────────────────────────────
echo "[7/8] enable certbot.timer (LE 证书自动续期, 续到 2026-08-07) ..."
systemctl enable certbot.timer >/dev/null 2>&1 || true
systemctl start certbot.timer >/dev/null 2>&1 || true
CERTBOT_TIMER_ACTIVE="$(systemctl is-active certbot.timer 2>/dev/null || echo unknown)"
report "- certbot.timer 状态: $CERTBOT_TIMER_ACTIVE"

# ─── 8. 防火墙 + 占位 portal systemd unit ────────────────────
echo "[8/8] 加固防火墙 + 留 portal systemd unit ..."
ufw --force reset >/dev/null
ufw default deny incoming
ufw default allow outgoing
ufw allow 22/tcp
ufw allow 80/tcp
ufw allow 443/tcp
ufw --force enable
systemctl enable fail2ban >/dev/null 2>&1 || true
systemctl restart fail2ban || true

# 留一个 portal systemd unit 占位 (PR-4 真正填业务时, 改 ExecStart 即可)
PORTAL_UNIT="/etc/systemd/system/guanghulab-portal.service"
if [ ! -f "$PORTAL_UNIT" ]; then
  cat > "$PORTAL_UNIT" <<EOF
# Portal · 由 PR-4 接管, 当前是占位
# Generated by domain-cn/bootstrap.sh @ $(date -u +%Y-%m-%dT%H:%M:%SZ)
[Unit]
Description=Guanghulab Portal (placeholder until PR-4)
After=network.target

[Service]
Type=simple
WorkingDirectory=$DATA_ROOT/portal
ExecStart=/bin/sh -c 'echo "[portal placeholder] PR-4 will replace this ExecStart"; sleep 86400'
Restart=on-failure
MemoryHigh=${SYSTEMD_PORTAL_HIGH_MB}M
MemoryMax=${SYSTEMD_PORTAL_MAX_MB}M

[Install]
WantedBy=multi-user.target
EOF
  systemctl daemon-reload
  # 不主动 enable/start, 避免占位脚本占资源. PR-4 自己 enable.
  report "- portal systemd unit 占位写入: \`$PORTAL_UNIT\` (not enabled)"
fi

# ─── 8.5 启动 secrets-vault (PR-5) ───────────────────────────
# 前提: deploy-domain-server.yml 已把以下两个目录 rsync 到位
#   $DEPLOY_ROOT/secrets-vault/      (server/secrets-vault 全部源码)
#   $DEPLOY_ROOT/secrets-vault-frontend/  (frontend/secrets-vault 全部源码)
VAULT_SRC="$DEPLOY_ROOT/secrets-vault"
VAULT_FE="$DEPLOY_ROOT/secrets-vault-frontend"
if [ -d "$VAULT_SRC" ] && [ -f "$VAULT_SRC/server.js" ]; then
  report ""
  report "## ⑤ 启动 secrets-vault (PR-5)"
  echo "[8.5] 启动 secrets-vault ..."

  # 装 vault npm 依赖 (只 express, 体量小)
  if [ ! -d "$VAULT_SRC/node_modules" ]; then
    ( cd "$VAULT_SRC" && npm config set registry https://registry.npmmirror.com && npm install --omit=dev --silent ) || {
      report "❌ secrets-vault npm install 失败"
      exit 1
    }
  fi

  # 用 pm2 启 (跟 portal 同一个 pm2 daemon)
  export VAULT_DIR="${VAULT_DIR:-$DATA_ROOT/secrets-vault}"
  export PORTAL_DATA_DIR="${PORTAL_DATA_DIR:-$DATA_ROOT/portal/data}"
  export VAULT_STATIC_DIR="${VAULT_STATIC_DIR:-$VAULT_FE}"
  export VAULT_PORT="${VAULT_PORT:-8080}"
  export VAULT_HOST="127.0.0.1"
  export VAULT_MANIFEST_PATH="${VAULT_MANIFEST_PATH:-$DEPLOY_ROOT/_active/scripts/preflight/secrets-manifest.json}"
  # 没在 _active 那就退化到 vault 自带的副本 (deploy 时 rsync 整 scripts/preflight)
  if [ ! -f "$VAULT_MANIFEST_PATH" ]; then
    VAULT_MANIFEST_PATH="$VAULT_SRC/secrets-manifest.json"
  fi

  mkdir -p "$DATA_ROOT/_logs"
  if pm2 describe guanghulab-vault >/dev/null 2>&1; then
    pm2 reload guanghulab-vault --update-env >/dev/null 2>&1 \
      && report "- guanghulab-vault: pm2 reload OK" \
      || report "⚠️  guanghulab-vault pm2 reload 失败, 用 restart 兜底" 
    pm2 describe guanghulab-vault >/dev/null 2>&1 || pm2 start "$VAULT_SRC/ecosystem.config.js" --only guanghulab-vault
  else
    pm2 start "$VAULT_SRC/ecosystem.config.js" --only guanghulab-vault \
      && report "- guanghulab-vault: pm2 start OK (127.0.0.1:$VAULT_PORT)" \
      || { report "❌ guanghulab-vault pm2 start 失败"; exit 1; }
  fi
  pm2 save >/dev/null 2>&1 || true
  report "- vault 数据根: \`$VAULT_DIR\` · 主密钥首启自动生成 \`.master\`"
else
  report ""
  report "## ⑤ secrets-vault 未就位 (跳过)"
  report "- \`$VAULT_SRC\` 不存在 — deploy-domain-server.yml 把 server/secrets-vault/ rsync 上来后再跑一次 bootstrap 即可激活"
fi

# ─── 收尾回执 ─────────────────────────────────────────────────
BOOTSTRAP_OK="true"

cat > "$DEPLOY_ROOT/_logs/bootstrap-$RECEIPT_TS.json" <<EOF
{
  "_sovereign": "TCS-0002∞ | $SERVER_ID",
  "_copyright": "国作登字-2026-A-00037559",
  "template_version": "$TEMPLATE_VERSION",
  "stage": "$STAGE",
  "completed_at": "$(date -u +%Y-%m-%dT%H:%M:%SZ)",
  "data_root": "$DATA_ROOT",
  "deploy_root": "$DEPLOY_ROOT",
  "domain": "$DOMAIN",
  "size_tier": "$SIZE_TIER",
  "portal_instances": $PORTAL_INSTANCES,
  "nginx_worker_conn": $NGINX_WORKER_CONN,
  "forgejo_enabled": $FORGEJO_ENABLED,
  "lfs_enabled": $LFS_ENABLED,
  "use_ssl": ${USE_SSL:-0},
  "certbot_timer": "$CERTBOT_TIMER_ACTIVE",
  "pre_snapshot": "$PRE_SNAP",
  "deploy_report": "$DEPLOY_REPORT"
}
EOF

report ""
report "## ✅ 本次 $STAGE 完成"
report ""
report "- 服务器: $SERVER_ID"
report "- 域名: $DOMAIN  (HTTPS=${USE_SSL:-0})"
report "- 档位: $SIZE_TIER"
report "- 部署根: \`$DEPLOY_ROOT\`"
report "- 数据根: \`$DATA_ROOT\`"
report "- 跑前快照: \`$PRE_SNAP\`"
report "- 完成时间: $(date '+%Y-%m-%d %H:%M:%S %Z')"
report ""
report "**给冰朔的人话**: 域名机基础环境已就位. nginx + node 20 + pm2 + certbot 都装好了."
report "下一步: 等 PR-4 填 portal 业务代码 (本仓库后续棒)."

echo
echo "═══════════════════════════════════════════════════════════"
echo "  [OK] $SERVER_ID 域名机 $STAGE 完成"
echo "═══════════════════════════════════════════════════════════"
echo "  域名:         https://$DOMAIN/"
echo "  数据根:       $DATA_ROOT/"
echo "  中文回执:     $DEPLOY_REPORT"
echo "  快照:         $PRE_SNAP"
echo "  下一步:       PR-4 填 portal 业务代码"
echo
