#!/usr/bin/env bash
# =============================================================================
# 一键部署脚本：装工具→加载镜像→解压sys/编译xwatchdog→部署应用→同步配置→启动容器+开机自启
# 适用：Linux（systemd 发行版，如 CentOS/Ubuntu/Debian/Rocky 等）
#
# 用法：
#   chmod +x deploy.sh
#   sudo ./deploy.sh            # 全流程
#   sudo ./deploy.sh load       # 仅加载镜像
#   sudo ./deploy.sh start      # 仅启动容器 + 开机自启
#   sudo ./deploy.sh packages   # 仅安装 nginx + supervisor + uv 并设开机自启
#   sudo ./deploy.sh sys        # 解压 sys.zip→编译xwatchdog→同步etc/opt/var到系统路径并重载服务
#   sudo ./deploy.sh sync       # 仅同步 etc/opt/var 到系统路径并重载服务（需先 sys 解压落地）
#   sudo ./deploy.sh app        # 解压 xglms-master.zip→修权限→docker-build.sh→建 taiji venv
#   sudo ./deploy.sh status     # 查看运行状态
#
# 镜像清单：
#   quay.io/coreos/etcd:v3.5.25            (milvus 元数据)
#   minio/minio:RELEASE.2024-12-18...      (milvus 对象存储)
#   milvusdb/milvus:v2.6.9                 (milvus 向量库 standalone)
#   onlyoffice/documentserver:latest       (在线文档服务)
#   postgres:14-alpine                     (PostgreSQL 14)
# =============================================================================
set -euo pipefail

# ----------------------------- CONFIG -----------------------------------------
# 部署文件所在目录：默认取脚本自身所在目录（与 *.tar/zip 同目录即可直接运行）；
# 也可通过环境变量 TAR_DIR 覆盖（install.sh 引导脚本会设置它）
TAR_DIR="${TAR_DIR:-$(cd "$(dirname "${BASH_SOURCE[0]:-$0}")" 2>/dev/null && pwd || echo /data/doker-tar)}"

# 数据持久化根目录
DATA_ROOT="/data/services"

# docker 网络（milvus 三件套共用，靠容器名互访）
NETWORK_NAME="milvus-net"

# ---- PostgreSQL ----
PG_CONTAINER="xglms-pg14"
PG_PORT="5432"                      # 宿主机端口
PG_USER="postgres"
PG_PASSWORD="ChangeMePlease123!"    # ⚠️ 部署前请改成强密码
PG_DB="xglms"

# ---- OnlyOffice ----
# 注意：OnlyOffice 容器端口必须避开后端 master 进程的 8080（supervisor master.conf + nginx upstream backend）
OO_CONTAINER="onlyoffice-ds"
OO_HTTP_PORT="8088"                 # 宿主机 HTTP 端口 -> 容器 80（避开后端 8080）
OO_HTTPS_PORT="8443"                # 宿主机 HTTPS 端口 -> 容器 443

# ---- Milvus ----
ETCD_CONTAINER="milvus-etcd"
MINIO_CONTAINER="milvus-minio"
MILVUS_CONTAINER="milvus-standalone"
MILVUS_PORT="19530"                 # gRPC
MILVUS_METRICS_PORT="9091"          # metrics / healthz
MINIO_API_PORT="9000"
MINIO_CONSOLE_PORT="9001"
MINIO_ROOT_USER="minioadmin"
MINIO_ROOT_PASSWORD="minioadmin"    # ⚠️ 生产环境请改；改后需同步 milvus 配置

# ---- 系统工具 ----
UV_INSTALL_DIR="/usr/local/bin"     # uv 安装目标目录（加入 PATH）
UV_VERSION=""                       # 留空装最新版；指定版本填如 "0.5.4"
SUPERVISOR_CONF_DIR="/etc/supervisor/conf.d"   # supervisor 程序配置目录（Debian/Ubuntu 风格）
# 覆盖系统文件前的备份目录
SYNC_BACKUP_DIR="/data/services/deploy-backup"

# ---- 应用包 ----
APP_ZIP="xglms-master.zip"          # 应用压缩包文件名（位于 TAR_DIR）
APP_DEST="/opt/xglms-master"        # 解压目标目录
# xwatchdog 源码目录：优先用 sys.zip 解压出的顶层 xwatchdog，回退到应用包内的
XWATCHDOG_SRC="${TAR_DIR}/xwatchdog"
# ---- 系统配置包 ----
SYS_ZIP="sys.zip"                   # 含 etc/opt/var/xwatchdog，解压到 TAR_DIR
# -----------------------------------------------------------------------------

# 镜像 tar -> tag 映射
declare -A IMAGE_TAR=(
  ["milvus-etcd.tar"]="quay.io/coreos/etcd:v3.5.25"
  ["milvus-minio.tar"]="minio/minio:RELEASE.2024-12-18T13-15-44Z"
  ["milvus-standalone-v2.6.9.tar"]="milvusdb/milvus:v2.6.9"
  ["onlyoffice.tar"]="onlyoffice/documentserver:latest"
  ["xglms-pg-v14.tar"]="postgres:14-alpine"
)

# ----------------------------- 日志工具 ---------------------------------------
C_RED='\033[0;31m'; C_GREEN='\033[0;32m'; C_YELLOW='\033[1;33m'; C_BLUE='\033[0;34m'; C_NC='\033[0m'
log()  { echo -e "${C_BLUE}[$(date '+%H:%M:%S')]${C_NC} $*"; }
ok()   { echo -e "${C_GREEN}[$(date '+%H:%M:%S')] ✓${C_NC} $*"; }
warn() { echo -e "${C_YELLOW}[$(date '+%H:%M:%S')] !${C_NC} $*"; }
err()  { echo -e "${C_RED}[$(date '+%H:%M:%S')] ✗${C_NC} $*" >&2; }

# 密码打码：仅显示首尾各 2 字符，中间用 **** 代替（避免日志/终端泄露明文）
mask_pw() {
  local pw="$1"
  local len=${#pw}
  if [ "$len" -le 4 ]; then echo "****"; return; fi
  printf '%s****%s' "${pw:0:2}" "${pw: -2}"
}

# ----------------------------- 前置检查 ---------------------------------------
# 仅这些子命令需要 Docker daemon 在线
NEEDS_DOCKER="load start app status all"

preflight() {
  # 兼容无参数调用（set -u 下 $1 未设置会报错）：用 $1 或默认 all
  local cmd="${1:-all}"
  # 统一探测包管理器，写全局 PKGMGR 供 pkg_install 等复用（子 shell 里的赋值传不回父进程）
  detect_pkgmgr >/dev/null
  [ -n "$PKGMGR" ] || warn "未识别到包管理器（apt/dnf/yum），涉及装包的步骤可能失败"

  # 仅在需要 Docker 的子命令下才检查 daemon
  if [[ " $NEEDS_DOCKER " == *" $cmd "* ]]; then
    command -v docker >/dev/null 2>&1 || { err "未找到 docker，请先安装 Docker (https://docs.docker.com/engine/install/)"; exit 1; }
    if ! docker info >/dev/null 2>&1; then
      err "无法连接 Docker 守护进程。请用 root 运行或将用户加入 docker 组后重试：sudo $0 $cmd"
      exit 1
    fi
  fi
  [ -d "$TAR_DIR" ] || { err "部署文件目录不存在：$TAR_DIR（可用 TAR_DIR=/path/to/dir $0 覆盖）"; exit 1; }
  # 仅在涉及启动容器/镜像加载时才创建数据目录
  if [[ " $NEEDS_DOCKER " == *" $cmd "* ]]; then
    mkdir -p "$DATA_ROOT"/{milvus/{etcd,minio,data},onlyoffice/{logs,data,lib,db},postgres}
  fi
}

# ----------------------------- 加载镜像 ---------------------------------------
load_images() {
  log "开始加载镜像（已存在的会跳过，避免重复读取大文件）..."
  for tar in "${!IMAGE_TAR[@]}"; do
    local tag="${IMAGE_TAR[$tar]}"
    local file="${TAR_DIR}/${tar}"
    if [ ! -f "$file" ]; then
      warn "缺少 tar 文件：$file，跳过"
      continue
    fi
    if docker image inspect "$tag" >/dev/null 2>&1; then
      ok "镜像已存在，跳过加载：$tag"
    else
      log "加载 $tar -> $tag ..."
      docker load -i "$file"
      ok "加载完成：$tag"
    fi
  done
}

# ----------------------------- 网络 -------------------------------------------
ensure_network() {
  if docker network inspect "$NETWORK_NAME" >/dev/null 2>&1; then
    ok "网络已存在：$NETWORK_NAME"
  else
    docker network create "$NETWORK_NAME" >/dev/null
    ok "已创建网络：$NETWORK_NAME"
  fi
}

# ----------------------------- 等待健康 ---------------------------------------
# 轮询容器 healthcheck 状态，最多等 timeout 秒
wait_healthy() {
  local container=$1; local timeout=${2:-120}
  local elapsed=0; local st="starting"
  while [ "$elapsed" -lt "$timeout" ]; do
    st=$(docker inspect --format '{{.State.Health.Status}}' "$container" 2>/dev/null || echo "none")
    [ "$st" = "healthy" ] && { ok "$container 已就绪"; return 0; }
    sleep 3; elapsed=$((elapsed+3))
    printf "."
  done
  echo
  warn "$container 在 ${timeout}s 内未变为 healthy（当前：$st），继续后续步骤（容器可能仍在初始化）"
  return 1
}

# ----------------------------- etcd -------------------------------------------
start_etcd() {
  local dir="${DATA_ROOT}/milvus/etcd"
  mkdir -p "$dir"
  if docker inspect "$ETCD_CONTAINER" >/dev/null 2>&1; then
    log "$ETCD_CONTAINER 已存在，启动并刷新重启策略"
    docker start "$ETCD_CONTAINER" >/dev/null
    docker update --restart unless-stopped "$ETCD_CONTAINER" >/dev/null
  else
    log "创建并启动 $ETCD_CONTAINER ..."
    docker run -d --name "$ETCD_CONTAINER" \
      --restart unless-stopped \
      --network "$NETWORK_NAME" \
      -e ETCDCTL_API=3 \
      -e ETCD_AUTO_COMPACTION_MODE=revision \
      -e ETCD_AUTO_COMPACTION_RETENTION=1000 \
      -e ETCD_QUOTA_BACKEND_BYTES=4294967296 \
      -e ETCD_SNAPSHOT_COUNT=50000 \
      -v "${dir}:/etcd" \
      --health-cmd "etcdctl endpoint health" \
      --health-interval 10s --health-timeout 5s --health-retries 5 \
      quay.io/coreos/etcd:v3.5.25 \
      etcd -advertise-client-urls=http://127.0.0.1:2379 \
           -listen-client-urls=http://0.0.0.0:2379 \
           --data-dir /etcd >/dev/null
    ok "$ETCD_CONTAINER 已创建"
  fi
  wait_healthy "$ETCD_CONTAINER" 60 || true
}

# ----------------------------- minio ------------------------------------------
start_minio() {
  local dir="${DATA_ROOT}/milvus/minio"
  mkdir -p "$dir"
  if docker inspect "$MINIO_CONTAINER" >/dev/null 2>&1; then
    log "$MINIO_CONTAINER 已存在，启动并刷新重启策略"
    docker start "$MINIO_CONTAINER" >/dev/null
    docker update --restart unless-stopped "$MINIO_CONTAINER" >/dev/null
  else
    log "创建并启动 $MINIO_CONTAINER ..."
    docker run -d --name "$MINIO_CONTAINER" \
      --restart unless-stopped \
      --network "$NETWORK_NAME" \
      -e MINIO_ROOT_USER="$MINIO_ROOT_USER" \
      -e MINIO_ROOT_PASSWORD="$MINIO_ROOT_PASSWORD" \
      -p "${MINIO_API_PORT}:9000" \
      -p "${MINIO_CONSOLE_PORT}:9001" \
      -v "${dir}:/minio_data" \
      --health-cmd "wget -q -O /dev/null http://localhost:9000/minio/health/live || exit 1" \
      --health-interval 10s --health-timeout 5s --health-retries 5 \
      minio/minio:RELEASE.2024-12-18T13-15-44Z \
      minio server /minio_data --console-address ":9001" >/dev/null
    ok "$MINIO_CONTAINER 已创建"
  fi
  wait_healthy "$MINIO_CONTAINER" 60 || true
}

# ----------------------------- milvus -----------------------------------------
start_milvus() {
  local dir="${DATA_ROOT}/milvus/data"
  mkdir -p "$dir"
  # milvus 容器以 uid 1000 运行，需把数据目录归属改为 1000，否则无法写入
  if id -u 1000 >/dev/null 2>&1; then
    chown -R 1000:1000 "$dir" 2>/dev/null || warn "chown 1000:1000 $dir 失败（如已正确归属可忽略）"
  else
    chown -R 1000:1000 "$dir" 2>/dev/null || true
  fi
  if docker inspect "$MILVUS_CONTAINER" >/dev/null 2>&1; then
    log "$MILVUS_CONTAINER 已存在，启动并刷新重启策略"
    docker start "$MILVUS_CONTAINER" >/dev/null
    docker update --restart unless-stopped "$MILVUS_CONTAINER" >/dev/null
  else
    log "创建并启动 $MILVUS_CONTAINER ..."
    docker run -d --name "$MILVUS_CONTAINER" \
      --restart unless-stopped \
      --network "$NETWORK_NAME" \
      --security-opt seccomp=unconfined \
      -e ETCD_ENDPOINTS="${ETCD_CONTAINER}:2379" \
      -e MINIO_ADDRESS="${MINIO_CONTAINER}:9000" \
      -p "${MILVUS_PORT}:19530" \
      -p "${MILVUS_METRICS_PORT}:9091" \
      -v "${dir}:/var/lib/milvus" \
      --health-cmd "curl -fsS http://localhost:9091/healthz || exit 1" \
      --health-interval 30s --health-start-period 90s --health-timeout 20s --health-retries 5 \
      milvusdb/milvus:v2.6.9 \
      milvus run standalone >/dev/null
    ok "$MILVUS_CONTAINER 已创建"
  fi
  wait_healthy "$MILVUS_CONTAINER" 180 || true
}

# ----------------------------- postgres ---------------------------------------
start_postgres() {
  if docker inspect "$PG_CONTAINER" >/dev/null 2>&1; then
    log "$PG_CONTAINER 已存在，启动并刷新重启策略"
    docker start "$PG_CONTAINER" >/dev/null
    docker update --restart unless-stopped "$PG_CONTAINER" >/dev/null
  else
    log "创建并启动 $PG_CONTAINER ..."
    # 使用 named volume 规避 alpine postgres 的 uid 权限问题
    docker volume create pgdata-v14 >/dev/null 2>&1 || true
    docker run -d --name "$PG_CONTAINER" \
      --restart unless-stopped \
      -e POSTGRES_USER="$PG_USER" \
      -e POSTGRES_PASSWORD="$PG_PASSWORD" \
      -e POSTGRES_DB="$PG_DB" \
      -p "${PG_PORT}:5432" \
      -v pgdata-v14:/var/lib/postgresql/data \
      --health-cmd "pg_isready -U ${PG_USER} -d ${PG_DB}" \
      --health-interval 10s --health-timeout 5s --health-retries 5 \
      postgres:14-alpine >/dev/null
    ok "$PG_CONTAINER 已创建"
  fi
  wait_healthy "$PG_CONTAINER" 60 || true
}

# ----------------------------- onlyoffice -------------------------------------
start_onlyoffice() {
  local root="${DATA_ROOT}/onlyoffice"
  mkdir -p "$root"/{logs,data,lib,db}
  if docker inspect "$OO_CONTAINER" >/dev/null 2>&1; then
    log "$OO_CONTAINER 已存在，启动并刷新重启策略"
    docker start "$OO_CONTAINER" >/dev/null
    docker update --restart unless-stopped "$OO_CONTAINER" >/dev/null
  else
    log "创建并启动 $OO_CONTAINER ..."
    docker run -d --name "$OO_CONTAINER" \
      --restart unless-stopped \
      --privileged \
      -p "${OO_HTTP_PORT}:80" \
      -p "${OO_HTTPS_PORT}:443" \
      -v "${root}/logs:/var/log/onlyoffice" \
      -v "${root}/data:/var/www/onlyoffice/Data" \
      -v "${root}/lib:/var/lib/onlyoffice" \
      -v "${root}/db:/var/lib/postgresql" \
      onlyoffice/documentserver:latest >/dev/null
    ok "$OO_CONTAINER 已创建"
  fi
  sleep 5
}

# ----------------------------- 包管理器探测 -----------------------------------
# 输出 "apt" / "dnf" / "yum" / "" ，并导出 PKGMGR 供后续复用
detect_pkgmgr() {
  if command -v apt-get >/dev/null 2>&1; then
    PKGMGR="apt"
  elif command -v dnf >/dev/null 2>&1; then
    PKGMGR="dnf"
  elif command -v yum >/dev/null 2>&1; then
    PKGMGR="yum"
  else
    PKGMGR=""
  fi
  echo "$PKGMGR"
}

# apt 系列需安装 supervisor 包；RHEL 系列从 EPEL 装 supervisor
pkg_install() {
  case "$PKGMGR" in
    apt) apt-get install -y "$@" ;;
    dnf) dnf install -y "$@" ;;
    yum) yum install -y "$@" ;;
    *)   err "不支持的包管理器，无法安装 $*"; return 1 ;;
  esac
}

# ----------------------------- 安装 nginx -------------------------------------
install_nginx() {
  if [ -z "$PKGMGR" ]; then warn "未识别到包管理器，跳过 nginx 安装（请手动安装）"; return 1; fi
  if command -v nginx >/dev/null 2>&1; then
    ok "nginx 已安装：$(nginx -v 2>&1)"
  else
    log "安装 nginx ..."
    [ "$PKGMGR" = "apt" ] && apt-get update -y
    pkg_install nginx
    ok "nginx 安装完成"
  fi
  if command -v systemctl >/dev/null 2>&1; then
    if systemctl enable --now nginx >/dev/null 2>&1; then
      ok "nginx 已设为开机自启并启动"
    else
      warn "nginx enable/start 失败，请检查：systemctl status nginx"
    fi
  else
    warn "无 systemd，请手动启动 nginx 并设置开机自启"
  fi
}

# ----------------------------- 安装 supervisor --------------------------------
install_supervisor() {
  if [ -z "$PKGMGR" ]; then warn "未识别到包管理器，跳过 supervisor 安装（请手动安装）"; return 1; fi
  if command -v supervisord >/dev/null 2>&1 || [ -x /usr/bin/supervisord ]; then
    ok "supervisor 已安装：$(supervisord -v 2>/dev/null || echo '已存在')"
  else
    log "安装 supervisor ..."
    [ "$PKGMGR" = "apt" ] && apt-get update -y
    # RHEL 系列的 supervisor 在 EPEL 仓库
    if [ "$PKGMGR" = "dnf" ] || [ "$PKGMGR" = "yum" ]; then
      if ! rpm -q epel-release >/dev/null 2>&1; then
        log "启用 EPEL 仓库 ..."
        pkg_install epel-release || warn "EPEL 安装失败，尝试继续"
      fi
    fi
    pkg_install supervisor
    ok "supervisor 安装完成"
  fi
  # 配置目录与开机自启
  mkdir -p "$SUPERVISOR_CONF_DIR"
  if command -v systemctl >/dev/null 2>&1 && systemctl list-unit-files 2>/dev/null | grep -q '^supervisord\.service\|^supervisor\.service'; then
    # Debian 系服务名为 supervisor，RHEL 系为 supervisord
    local svc
    systemctl list-unit-files | grep -q '^supervisor\.service' && svc="supervisor" || svc="supervisord"
    if systemctl enable --now "$svc" >/dev/null 2>&1; then
      ok "supervisor($svc) 已设为开机自启并启动"
    else
      warn "supervisor($svc) enable/start 失败，请检查：systemctl status $svc"
    fi
  else
    # 没有原生 systemd unit 时，补一个简易 unit（以 RHEL 风格 supervisord 为例）
    local unit="/etc/systemd/system/supervisord.service"
    if [ ! -f "$unit" ]; then
      log "生成 supervisord systemd unit ..."
      cat > "$unit" <<'UNIT'
[Unit]
Description=Process Monitoring and Control Daemon
After=network.target
[Service]
Type=forking
ExecStart=/usr/bin/supervisord -c /etc/supervisor/supervisord.conf
ExecReload=/usr/bin/supervisorctl reload
KillMode=process
Restart=on-failure
RestartSec=5s
[Install]
WantedBy=multi-user.target
UNIT
      # 兼容 Debian/Ubuntu：conf 路径不同，覆盖 ExecStart
      if [ -f /etc/supervisor/supervisord.conf ]; then :; elif [ -f /etc/supervisor/conf.d/supervisord.conf ]; then
        sed -i 's#/etc/supervisor/supervisord.conf#/etc/supervisor/conf.d/supervisord.conf#' "$unit"
      fi
      systemctl daemon-reload
    fi
    if systemctl enable --now supervisord >/dev/null 2>&1; then
      ok "supervisord 已设为开机自启并启动"
    else
      warn "supervisord enable/start 失败，请检查：systemctl status supervisord"
    fi
  fi
}

# ----------------------------- 安装 uv ----------------------------------------
install_uv() {
  if command -v uv >/dev/null 2>&1; then
    ok "uv 已安装：$(uv --version 2>&1)"
    return 0
  fi
  log "安装 uv (Astral Python 包管理器) ..."
  # 优先用官方安装脚本（自带校验、支持版本锁定），失败则回退到 pip
  local tmp_script; tmp_script="$(mktemp)"
  if command -v curl >/dev/null 2>&1; then
    local url="https://astral.sh/uv/install.sh"
    if curl -fsSL "$url" -o "$tmp_script"; then
      if [ -n "$UV_VERSION" ]; then
        UV_INSTALL_DIR="$UV_INSTALL_DIR" sh "$tmp_script" "$UV_VERSION" >/dev/null 2>&1
      else
        UV_INSTALL_DIR="$UV_INSTALL_DIR" sh "$tmp_script" >/dev/null 2>&1
      fi
      rm -f "$tmp_script"
      if [ -x "${UV_INSTALL_DIR}/uv" ]; then
        ok "uv 安装完成：$(${UV_INSTALL_DIR}/uv --version 2>&1)"
        # 确保在 PATH 中（/usr/local/bin 通常已在 PATH）
        grep -q "${UV_INSTALL_DIR}" /etc/profile.d/uv-path.sh 2>/dev/null || \
          echo "export PATH=\"${UV_INSTALL_DIR}:\$PATH\"" > /etc/profile.d/uv-path.sh
        return 0
      fi
      warn "官方安装脚本未成功，回退到 pip 安装"
    else
      warn "无法下载 uv 官方安装脚本，回退到 pip 安装"
      rm -f "$tmp_script"
    fi
  fi
  # 回退：用 pip3 安装
  if command -v pip3 >/dev/null 2>&1; then
    pip3 install --quiet "uv${UV_VERSION:+==${UV_VERSION}}" \
      && ok "uv 已通过 pip3 安装：$(uv --version 2>&1)" \
      || { err "pip3 安装 uv 失败"; return 1; }
  else
    err "既无 curl 也无 pip3，无法安装 uv（请先安装 curl 或 python3-pip）"
    return 1
  fi
}

# ----------------------------- 安装全部系统工具 -------------------------------
install_packages() {
  detect_pkgmgr >/dev/null
  install_nginx    || true
  install_supervisor || true
  install_uv       || true
}

# ----------------------------- 同步 etc/opt/var -------------------------------
# 把脚本同目录下的 etc / opt / var 文件拷到系统对应路径，覆盖前备份旧文件。
# 映射关系：
#   etc/nginx/**            -> /etc/nginx/**
#   etc/supervisord.d/**    -> /etc/supervisor/conf.d/** (Debian) 或 /etc/supervisord.d/** (RHEL)
#   opt/packages/**         -> /opt/xglms-taiji/packages/**       (pypi-server 仓库)
#   var/www/html/website/** -> /var/www/html/website/**           (站点静态文件)
#   var/log/xglms            -> /var/log/xglms                    (仅建目录)
# -----------------------------------------------------------------------------
# 判断 supervisor 程序配置实际应放置的目录（兼容 Debian/RHEL）
resolve_supervisor_dir() {
  # RHEL 系：/etc/supervisord.d（include 默认 *.ini，我们的文件是 .conf 也支持）
  if [ -d /etc/supervisord.d ]; then echo "/etc/supervisord.d"; return; fi
  # Debian 系：/etc/supervisor/conf.d
  if [ -d /etc/supervisor/conf.d ]; then echo "/etc/supervisor/conf.d"; return; fi
  # 都没有：用脚本顶部配置值兜底
  echo "$SUPERVISOR_CONF_DIR"
}

# 关闭 RHEL/Rocky 默认内联在 nginx.conf 的 server 块（监听 80 默认站，与 taiji.conf 冲突）
# 做法：备份 nginx.conf 后，注释掉 http{} 直接子级的 server { ... } 块（RHEL 默认只有一个）
disable_rhel_default_server() {
  local conf=/etc/nginx/nginx.conf
  [ -f "$conf" ] || return 0
  # 仅当配置里确实有内联 server 块（RHEL 风格：nginx.conf 含 listen 80）才处理
  grep -qE 'listen[[:space:]]+.*80' "$conf" 2>/dev/null || return 0
  # 已处理过则跳过（避免重复备份/注释）
  grep -q '# deploy.sh: default server disabled' "$conf" 2>/dev/null && return 0
  backup_existing "$conf"
  # 用 awk 跟踪花括号深度，注释掉第一个位于 http{} 直接子级（depth==1）的 server 块
  # 跳过注释行，避免误匹配 "# server {"
  awk '
    BEGIN { depth=0; in_srv=0; srv_start=0; done=0 }
    {
      # 非 server 块区：维护深度，并检测进入 server 块
      if (!in_srv) {
        # 跳过注释行（原样输出，不计入 server 检测）
        if ($0 ~ /^[[:space:]]*#/) { print; next }
        # 检测 "server {" —— 仅在 depth==1（http{} 直接子级）且尚未处理时进入
        if (!done && depth==1 && $0 ~ /^[[:space:]]*server[[:space:]]*\{/) {
          in_srv=1; srv_start=depth; done=1
          print "# deploy.sh: default server disabled"
          print "# " $0
          depth++; next
        }
        # 维护非 server 区括号深度
        line=$0
        while (match(line, /\{/)) { depth++; line=substr(line, RSTART+1) }
        line=$0
        while (match(line, /\}/)) { depth--; line=substr(line, RSTART+1) }
        print; next
      }
      # server 块内：注释每一行，并维护深度
      print "# " $0
      line=$0
      while (match(line, /\{/)) { depth++; line=substr(line, RSTART+1) }
      line=$0
      while (match(line, /\}/)) { depth--; line=substr(line, RSTART+1) }
      if (depth <= srv_start) in_srv=0
    }
  ' "$conf" > "${conf}.deploy.tmp" && mv "${conf}.deploy.tmp" "$conf"
  ok "已注释 RHEL nginx.conf 内联默认 server 块：$conf"
}

# 带时间戳的安全备份：覆盖 /dest 前先把已有文件拷到本次运行的备份目录
# 依赖全局 BACKUP_RUN_DIR（由 sync_configs 在开始时创建一次）
# 用法：backup_existing /dest/path
backup_existing() {
  local dest="$1"
  [ -e "$dest" ] || return 0
  [ -n "${BACKUP_RUN_DIR:-}" ] || return 0
  # 保留原路径结构以便回滚
  local rel="${dest#/}"
  mkdir -p "${BACKUP_RUN_DIR}/$(dirname "$rel")"
  cp -a "$dest" "${BACKUP_RUN_DIR}/${rel}" 2>/dev/null && log "已备份：$dest -> ${BACKUP_RUN_DIR}/${rel}"
}

# 用法：sync_tree <src_dir> <dest_dir> [owner]
# 将 src 整棵目录拷到 dest，覆盖前逐项备份；可选指定属主
sync_tree() {
  local src="$1" dest="$2" owner="${3:-}"
  [ -d "$src" ] || { warn "源目录不存在，跳过：$src"; return 0; }
  mkdir -p "$dest"
  # 用 cp -a 保留属性；先备份已存在的同名项
  while IFS= read -r -d '' f; do
    local rel="${f#$src/}"
    backup_existing "${dest}/${rel}"
  done < <(find "$src" -type f -print0)
  cp -a "$src/." "$dest/" && ok "已同步：$src -> $dest"
  if [ -n "$owner" ]; then
    chown -R "$owner" "$dest" 2>/dev/null || warn "chown $owner $dest 失败（可忽略）"
  fi
}

sync_configs() {
  local src_root="${TAR_DIR}"
  [ -d "${src_root}/etc" ] || [ -d "${src_root}/opt" ] || [ -d "${src_root}/var" ] || {
    warn "未找到 etc/opt/var 目录（${src_root}），跳过同步"; return 0; }
  mkdir -p "$SYNC_BACKUP_DIR"
  # 本次运行唯一备份目录（用固定计数器避免 date 不可用时回退一致）
  BACKUP_RUN_DIR="${SYNC_BACKUP_DIR}/run-$(date +%Y%m%d_%H%M%S 2>/dev/null || echo unknown)-$$"
  mkdir -p "$BACKUP_RUN_DIR"
  log "本次备份目录：$BACKUP_RUN_DIR"

  # ---------- etc/nginx ----------
  if [ -d "${src_root}/etc/nginx" ]; then
    log "同步 nginx 配置与证书 ..."
    # SSL 证书
    if [ -d "${src_root}/etc/nginx/ssl" ]; then
      mkdir -p /etc/nginx/ssl
      sync_tree "${src_root}/etc/nginx/ssl" /etc/nginx/ssl
      chmod 600 /etc/nginx/ssl/*.key 2>/dev/null || true
      chmod 644 /etc/nginx/ssl/*.crt 2>/dev/null || true
      ok "SSL 证书权限已收紧：*.key=600 *.crt=644"
    fi
    # conf.d 站点配置
    if [ -d "${src_root}/etc/nginx/conf.d" ]; then
      mkdir -p /etc/nginx/conf.d
      # 我们的 taiji.conf 用 server_name _ 占 80/443，需移除系统默认站点避免冲突
      # 1) Debian/Ubuntu：sites-enabled/default 或 conf.d/default.conf
      for d in /etc/nginx/sites-enabled/default /etc/nginx/conf.d/default.conf; do
        if [ -e "$d" ]; then
          backup_existing "$d"
          rm -f "$d" && ok "已移除 nginx 默认站点避免端口冲突：$d"
        fi
      done
      # 2) RHEL/Rocky：默认 server 块内联在 /etc/nginx/nginx.conf 的 http{} 里，
      #    用一个独立 conf 关掉（保留原文件，仅置空默认 server）
      disable_rhel_default_server
      sync_tree "${src_root}/etc/nginx/conf.d" /etc/nginx/conf.d
    fi
  fi

  # ---------- etc/supervisord.d ----------
  if [ -d "${src_root}/etc/supervisord.d" ]; then
    local supdir; supdir="$(resolve_supervisor_dir)"
    log "同步 supervisor 程序配置 -> $supdir ..."
    mkdir -p "$supdir"
    sync_tree "${src_root}/etc/supervisord.d" "$supdir"
    # RHEL 默认 include 是 *.ini；确保 .conf 也能被加载。
    # 策略：若主配置未显式 include *.conf，则追加一个独立的 [include] 段
    # （supervisor 支持多个 [include] 段累加），比 sed 改原文稳。
    local mainconf=""
    [ -f /etc/supervisor/supervisord.conf ] && mainconf="/etc/supervisor/supervisord.conf"
    [ -f /etc/supervisord.conf ] && mainconf="/etc/supervisord.conf"
    if [ -n "$mainconf" ]; then
      if ! grep -qE "files\s*=\s*.*\*\.conf" "$mainconf"; then
        warn "$mainconf 未 include *.conf，追加独立 [include] 段"
        cat >> "$mainconf" <<'EOF'

# 由 deploy.sh 追加：加载 *.conf 程序配置
[include]
files = supervisord.d/*.conf conf.d/*.conf
EOF
        ok "已在 $mainconf 追加 [include] *.conf"
      fi
    fi
  fi

  # ---------- opt/xglms-taiji -> /opt/xglms-taiji（脚本 + pypi 离线包仓库）----------
  # sys.zip 结构为 opt/xglms-taiji/{install.sh,download-server.py,...,packages/}
  if [ -d "${src_root}/opt/xglms-taiji" ]; then
    log "同步 taiji 工具目录 -> /opt/xglms-taiji ..."
    mkdir -p /opt/xglms-taiji
    sync_tree "${src_root}/opt/xglms-taiji" /opt/xglms-taiji
    # 修复脚本可执行权限（zip 不保留可执行位）
    find /opt/xglms-taiji -maxdepth 1 -type f \( -name '*.sh' -o -name '*.py' \) -exec chmod +x {} + 2>/dev/null || true
  elif [ -d "${src_root}/opt/packages" ]; then
    # 兼容旧结构：opt/packages -> /opt/xglms-taiji/packages
    log "同步 pypi 离线包仓库 -> /opt/xglms-taiji/packages ..."
    mkdir -p /opt/xglms-taiji
    sync_tree "${src_root}/opt/packages" /opt/xglms-taiji/packages
  fi

  # ---------- var/www ----------
  if [ -d "${src_root}/var/www" ]; then
    log "同步站点静态文件 -> /var/www/html/website ..."
    mkdir -p /var/www/html/website
    sync_tree "${src_root}/var/www/html/website" /var/www/html/website
  fi

  # ---------- var/log/xglms ----------
  if [ -d "${src_root}/var/log/xglms" ]; then
    mkdir -p /var/log/xglms && ok "已创建日志目录：/var/log/xglms"
  else
    mkdir -p /var/log/xglms && ok "已创建日志目录：/var/log/xglms"
  fi

  # ---------- 重载 nginx / supervisor ----------
  if command -v nginx >/dev/null 2>&1; then
    if nginx -t 2>/dev/null; then
      if systemctl reload nginx 2>/dev/null || nginx -s reload 2>/dev/null; then
        ok "nginx 配置已校验并重载"
      else
        warn "nginx reload 失败，请检查 systemctl status nginx"
      fi
    else
      err "nginx -t 配置校验失败，未重载。请检查 /etc/nginx/conf.d/taiji.conf"
      nginx -t || true
    fi
  else
    warn "nginx 未安装，配置已拷入但未重载（请先执行 packages）"
  fi

  if command -v supervisorctl >/dev/null 2>&1; then
    if supervisorctl reread >/dev/null 2>&1; then
      if supervisorctl update >/dev/null 2>&1; then
        ok "supervisor 已重载并更新（reread + update）"
      else
        warn "supervisorctl update 失败，请检查：supervisorctl status"
      fi
      supervisorctl status 2>/dev/null || true
    else
      warn "supervisorctl reread 失败，请确认 supervisord 已运行：systemctl status supervisord"
    fi
  else
    warn "supervisorctl 未安装，配置已拷入但未重载（请先执行 packages）"
  fi
}

# ----------------------------- 解压系统配置包 ---------------------------------
# 把 sys.zip 解压到 TAR_DIR（当前目录），产生 etc/opt/var/xwatchdog 等顶层目录。
# 这些目录随后供 sync_configs（etc/opt/var -> 系统路径）和 install_xwatchdog 使用。
# 已存在的同名文件会被 unzip -o 覆盖。
unpack_sys() {
  local zipfile="${TAR_DIR}/${SYS_ZIP}"
  [ -f "$zipfile" ] || { warn "系统配置包不存在：$zipfile，跳过解压"; return 1; }
  command -v unzip >/dev/null 2>&1 || {
    log "未安装 unzip，自动安装 ..."
    pkg_install unzip || { err "unzip 安装失败，无法解压系统配置包"; return 1; }
  }

  log "解压 ${SYS_ZIP} -> ${TAR_DIR} ..."
  # sys.zip 顶层直接是 etc/ opt/ var/ xwatchdog/，解压到 TAR_DIR 即得到这些目录
  if unzip -q -o "$zipfile" -d "$TAR_DIR"; then
    ok "系统配置包已解压到 ${TAR_DIR}"
    # 列出落地的顶层目录，便于核对
    local landed=""
    for d in etc opt var xwatchdog; do
      [ -d "${TAR_DIR}/${d}" ] && landed="${landed}${d} "
    done
    [ -n "$landed" ] && log "已落地目录：${landed}"
    # 修复 xwatchdog 下 .sh 与 service 权限（zip 不保留可执行位）
    if [ -d "${TAR_DIR}/xwatchdog" ]; then
      find "${TAR_DIR}/xwatchdog" -type f -name '*.sh' -exec chmod +x {} + 2>/dev/null || true
    fi
  else
    err "解压失败：$zipfile"
    return 1
  fi
}

# ----------------------------- 解压应用包 -------------------------------------
# 把 xglms-master.zip 解压到 /opt/xglms-master（已存在则备份后覆盖）
unpack_app() {
  local zipfile="${TAR_DIR}/${APP_ZIP}"
  [ -f "$zipfile" ] || { warn "应用包不存在：$zipfile，跳过解压"; return 1; }
  command -v unzip >/dev/null 2>&1 || {
    log "未安装 unzip，自动安装 ..."
    pkg_install unzip || { err "unzip 安装失败，无法解压应用包"; return 1; }
  }

  if [ -d "$APP_DEST" ]; then
    # 已存在则备份整个目录后清空，保证干净解压
    local bak="${SYNC_BACKUP_DIR}/app-$(date +%Y%m%d_%H%M%S 2>/dev/null || echo unknown)-$$"
    mkdir -p "$bak"
    cp -a "$APP_DEST" "$bak/" && log "已备份旧应用目录：$APP_DEST -> $bak"
    rm -rf "${APP_DEST:?}/"*
  else
    mkdir -p "$APP_DEST"
  fi

  log "解压 $APP_ZIP -> $APP_DEST ..."
  # zip 内顶层目录为 xglms-master/，解压到 /opt 后会得到 /opt/xglms-master
  if unzip -q -o "$zipfile" -d "$(dirname "$APP_DEST")"; then
    ok "应用已解压到 $APP_DEST"
  else
    err "解压失败：$zipfile"
    return 1
  fi

  # zip 不保留 Unix 可执行权限位，修复所有 .sh 脚本（含 start.sh）
  log "修复脚本可执行权限 ..."
  local fixed=0
  while IFS= read -r -d '' f; do
    chmod +x "$f" && fixed=$((fixed+1))
  done < <(find "$APP_DEST" -type f -name '*.sh' -print0)
  ok "已为 ${fixed} 个 .sh 脚本添加可执行权限（含 start.sh）"

  # 显式确认 start.sh 可执行（supervisor master.conf 依赖 ./start.sh）
  if [ -f "${APP_DEST}/start.sh" ]; then
    chmod +x "${APP_DEST}/start.sh"
    [ -x "${APP_DEST}/start.sh" ] \
      && ok "start.sh 已可执行：${APP_DEST}/start.sh" \
      || warn "start.sh 可执行权限设置失败，请手动：chmod +x ${APP_DEST}/start.sh"
  else
    warn "未找到 start.sh：${APP_DEST}/start.sh"
  fi
}

# ----------------------------- 编译并安装 xwatchdog ----------------------------
# 现场用源码编译（最可靠，匹配当前架构）；编译失败时回退到包内预编译二进制
install_xwatchdog() {
  # 源码目录优先用 sys.zip 解压出的顶层 xwatchdog，不存在则回退到应用包内的
  local wd="${XWATCHDOG_SRC}"
  if [ ! -d "$wd" ] && [ -d "${APP_DEST}/xwatchdog" ]; then
    wd="${APP_DEST}/xwatchdog"
  fi
  [ -d "$wd" ] || { warn "未找到 xwatchdog 源码目录：${XWATCHDOG_SRC} 或 ${APP_DEST}/xwatchdog"; return 1; }
  log "xwatchdog 源码目录：$wd"

  # 1) 安装编译依赖 gcc + openssl 开发库（用全局 PKGMGR，preflight 已探测）
  local need_gcc=0 need_ssl=0
  command -v gcc >/dev/null 2>&1 || need_gcc=1
  # 检查 OpenSSL 开发头文件
  if [ -f /usr/include/openssl/evp.h ] || [ -f /usr/local/include/openssl/evp.h ]; then :; else need_ssl=1; fi

  if [ "$need_gcc" = "1" ] || [ "$need_ssl" = "1" ]; then
    log "安装 xwatchdog 编译依赖（gcc + openssl 开发库）..."
    case "$PKGMGR" in
      apt) apt-get update -y; pkg_install build-essential libssl-dev ;;
      dnf|yum) pkg_install gcc openssl-devel ;;
      *) warn "不支持的包管理器($PKGMGR)，尝试继续（可能编译失败将回退到预编译二进制）" ;;
    esac
  fi

  # 2) 尝试源码编译
  local compiled=0
  if command -v gcc >/dev/null 2>&1 && command -v make >/dev/null 2>&1; then
    log "编译 xwatchdog ..."
    if ( cd "$wd" && make clean >/dev/null 2>&1; make ) >/tmp/xwatchdog_build.log 2>&1; then
      ok "xwatchdog 编译成功"
      compiled=1
    else
      warn "源码编译失败，日志见 /tmp/xwatchdog_build.log，将尝试回退到预编译二进制"
      tail -n 15 /tmp/xwatchdog_build.log 2>/dev/null
    fi
  else
    warn "缺少 gcc/make，跳过编译，将使用预编译二进制"
  fi

  # 3) 选定二进制：优先编译产物，否则按架构选预编译
  local bin=""
  if [ "$compiled" = "1" ] && [ -x "${wd}/xwatchdog" ]; then
    bin="${wd}/xwatchdog"
  else
    local arch; arch="$(uname -m 2>/dev/null || echo x86_64)"
    case "$arch" in
      x86_64|amd64|i?86) bin="${wd}/x86/xwatchdog" ;;
      aarch64|arm64)     bin="${wd}/xwatchdog" ;;  # 顶层预编译为 aarch64
      armv7l|armhf)      bin="${wd}/arm/xwatchdog" ;;
      *) warn "未识别架构 $arch，尝试顶层二进制"; bin="${wd}/xwatchdog" ;;
    esac
    if [ ! -x "$bin" ]; then
      # 顶层可能是 arm，x86 目录有二进制时兜底
      [ -x "${wd}/x86/xwatchdog" ] && bin="${wd}/x86/xwatchdog"
    fi
    if [ ! -x "$bin" ]; then
      err "无可用 xwatchdog 二进制（编译失败且无匹配架构预编译）"
      return 1
    fi
    warn "使用预编译二进制：$bin"
  fi

  # 4) 校验二进制可在本机运行
  if ! "$bin" -h >/dev/null 2>&1; then
    err "二进制无法在本机运行：$bin（架构不匹配或缺少动态库）"
    ldd "$bin" 2>/dev/null | grep -i "not found" && true
    return 1
  fi

  # 5) 安装到 /usr/local/bin
  mkdir -p /usr/local/bin
  backup_existing /usr/local/bin/xwatchdog
  cp "$bin" /usr/local/bin/xwatchdog
  chmod 755 /usr/local/bin/xwatchdog
  ok "xwatchdog 已安装到 /usr/local/bin/xwatchdog"

  # 6) 安装 systemd 服务（Type=oneshot，生成 /etc/.xdevice 设备指纹）
  local unit="/etc/systemd/system/xwatchdog.service"
  if [ -f "${wd}/xwatchdog.service" ]; then
    backup_existing "$unit"
    cp "${wd}/xwatchdog.service" "$unit"
  else
    # 包内缺失则内联生成等价 unit
    cat > "$unit" <<'UNIT'
[Unit]
Description=XGLMS XWatchdog Service
After=network.target

[Service]
Type=oneshot
User=root
ExecStart=/usr/local/bin/xwatchdog -f /etc/.xdevice
StandardOutput=journal
StandardError=journal

[Install]
WantedBy=multi-user.target
UNIT
  fi
  ok "xwatchdog systemd unit 已安装：$unit"

  # 7) 启用并启动（oneshot 服务执行一次即生成设备标识）
  if command -v systemctl >/dev/null 2>&1; then
    systemctl daemon-reload
    # 开机自启：显式处理 enable 成功/失败，避免静默跳过
    if systemctl enable xwatchdog >/dev/null 2>&1; then
      ok "xwatchdog 已设为开机自启 (systemctl enable xwatchdog)"
    else
      warn "xwatchdog 开机自启设置失败，请手动执行：systemctl enable xwatchdog"
    fi
    log "启动 xwatchdog（生成 /etc/.xdevice 设备指纹）..."
    if systemctl start xwatchdog; then
      sleep 1
      if [ -f /etc/.xdevice ]; then
        ok "设备标识已生成：/etc/.xdevice（$(stat -c '%a' /etc/.xdevice 2>/dev/null || echo '?') 权限）"
      else
        warn "/etc/.xdevice 未生成，查看日志：journalctl -u xwatchdog --no-pager -n 30"
        journalctl -u xwatchdog --no-pager -n 20 2>/dev/null || true
      fi
    else
      warn "xwatchdog 启动失败，查看：journalctl -u xwatchdog --no-pager"
    fi
  else
    warn "无 systemd，已安装程序与 unit 文件。请手动启用开机自启并运行："
    warn "  systemctl enable xwatchdog && systemctl start xwatchdog"
    warn "  或直接执行：/usr/local/bin/xwatchdog -f /etc/.xdevice"
  fi
}

# ----------------------------- 运行 taiji-os docker-build ---------------------
# 解压后执行 backend/taiji-os/docker-build.sh
run_taiji_docker_build() {
  local script="${APP_DEST}/backend/taiji-os/docker-build.sh"
  if [ ! -f "$script" ]; then
    warn "未找到 taiji-os 构建脚本：$script，跳过"
    return 1
  fi
  if [ ! -x "$script" ]; then
    chmod +x "$script" 2>/dev/null || true
  fi
  log "执行 taiji-os docker-build.sh ..."
  # 在脚本所在目录运行，避免相对路径问题
  if ( cd "$(dirname "$script")" && bash "$script" ); then
    ok "taiji-os docker-build.sh 执行完成"
  else
    err "taiji-os docker-build.sh 执行失败，请检查：cd $(dirname "$script") && bash docker-build.sh"
    return 1
  fi
}

# ----------------------------- 建立 taiji venv --------------------------------
# 运行 /opt/xglms-taiji/install.sh 建立 .venv（pypi-server / download-server 程序依赖它）
# install.sh 期望在 /opt/xglms-taiji 下运行，使用同目录 packages/ 中的离线 wheel
setup_taiji_venv() {
  local script="/opt/xglms-taiji/install.sh"
  if [ ! -f "$script" ]; then
    warn "未找到 taiji install.sh：$script（pypi/download 程序可能无法启动）"
    return 1
  fi
  chmod +x "$script" 2>/dev/null || true
  # 已存在 .venv 则跳过，避免重复安装
  if [ -x "/opt/xglms-taiji/.venv/bin/python" ]; then
    ok "taiji .venv 已存在，跳过 install.sh"
    return 0
  fi
  log "运行 taiji install.sh 建立 .venv ..."
  if ( cd /opt/xglms-taiji && bash "$script" ); then
    ok "taiji .venv 建立完成：/opt/xglms-taiji/.venv"
  else
    err "taiji install.sh 执行失败，请手动：cd /opt/xglms-taiji && bash install.sh"
    return 1
  fi
}

# ----------------------------- 部署应用 --------------------------------------
# 解压 xglms-master.zip -> /opt/xglms-master，修脚本权限，运行 docker-build.sh，建 taiji venv
deploy_app() {
  unpack_app || return 1
  run_taiji_docker_build || true
  setup_taiji_venv || true
}

# ----------------------------- 系统配置部署（前半：解压+编译，不含同步） -----
# 解压 sys.zip 落地 etc/opt/var/xwatchdog + 编译安装 xwatchdog
# 注意：sync_configs 单独留到 deploy_app 之后执行，因为它会 supervisorctl update
# 立即拉起 master/pypi/download 程序，需先保证 /opt/xglms-master 等应用目录就位。
setup_system_pre() {
  unpack_sys || true
  install_xwatchdog || true
}

# ----------------------------- docker 服务开机自启 -----------------------------
enable_docker_service() {
  if command -v systemctl >/dev/null 2>&1 && systemctl list-unit-files | grep -q '^docker\.service'; then
    if systemctl enable docker >/dev/null 2>&1; then
      ok "已设置 docker 服务开机自启 (systemctl enable docker)"
    else
      warn "docker 开机自启设置失败，请手动执行：systemctl enable docker"
    fi
    systemctl is-active --quiet docker || systemctl start docker
  else
    warn "未检测到 systemd 管理的 docker 服务，请手动确保 docker 开机自启"
  fi
}

# ----------------------------- 状态展示 ---------------------------------------
show_status() {
  echo
  log "================ 容器运行状态 ================"
  docker ps -a --filter "name=${ETCD_CONTAINER}" \
              --filter "name=${MINIO_CONTAINER}" \
              --filter "name=${MILVUS_CONTAINER}" \
              --filter "name=${PG_CONTAINER}" \
              --filter "name=${OO_CONTAINER}" \
              --format "table {{.Names}}\t{{.Status}}\t{{.Ports}}"
  echo
  log "================ 访问信息 ================"
  cat <<EOF
Milvus   :  gRPC ${MILVUS_PORT}  |  健康检查 http://<服务器IP>:${MILVUS_METRICS_PORT}/healthz
MinIO    :  API http://<服务器IP>:${MINIO_API_PORT}  |  控制台 http://<服务器IP>:${MINIO_CONSOLE_PORT}  账号 ${MINIO_ROOT_USER}/$(mask_pw "$MINIO_ROOT_PASSWORD")
PostgreSQL:  <服务器IP>:${PG_PORT}  数据库 ${PG_DB}  用户 ${PG_USER}  密码 $(mask_pw "$PG_PASSWORD")
OnlyOffice:  http://<服务器IP>:${OO_HTTP_PORT}  |  https://<服务器IP>:${OO_HTTPS_PORT}
数据目录 :  ${DATA_ROOT}  (PostgreSQL 数据在 docker volume pgdata-v14)
开机自启 :  docker 服务 + 各容器 restart=unless-stopped
EOF
  echo
  log "================ 系统工具 ================"
  printf "nginx      : %s\n" "$(command -v nginx >/dev/null 2>&1 && nginx -v 2>&1 || echo '未安装')"
  printf "supervisor : %s\n" "$(command -v supervisord >/dev/null 2>&1 && supervisord -v 2>/dev/null || echo '未安装')"
  printf "uv         : %s\n" "$(command -v uv >/dev/null 2>&1 && uv --version 2>&1 || echo '未安装')"
  [ -f /etc/profile.d/uv-path.sh ] && echo "（uv PATH 已写入 /etc/profile.d/uv-path.sh，新终端会话生效）"
  echo
  log "================ 应用 / xwatchdog ================"
  if [ -d /opt/xglms-master ]; then
    printf "应用目录   : %s\n" "/opt/xglms-master（已解压）"
  else
    printf "应用目录   : %s\n" "未解压（请执行 deploy.sh app）"
  fi
  if [ -x /usr/local/bin/xwatchdog ]; then
    # 开机自启状态
    local xwd_autostart="未知"
    if command -v systemctl >/dev/null 2>&1; then
      if systemctl is-enabled xwatchdog >/dev/null 2>&1; then
        xwd_autostart="已启用开机自启"
      else
        xwd_autostart="⚠ 未启用开机自启"
      fi
    fi
    printf "xwatchdog  : 已安装 /usr/local/bin/xwatchdog | %s" "$xwd_autostart"
    if [ -f /etc/.xdevice ]; then
      printf " | 设备标识已生成 /etc/.xdevice\n"
    else
      printf " | /etc/.xdevice 未生成\n"
    fi
  else
    printf "xwatchdog  : 未安装\n"
  fi
  echo
  ok "部署完成。如需查看实时日志：docker logs -f <容器名>"
}

# ----------------------------- 主流程 -----------------------------------------
start_all() {
  ensure_network
  start_etcd
  start_minio
  start_milvus
  start_postgres
  start_onlyoffice
}

main() {
  local cmd="${1:-all}"
  preflight "$cmd"
  case "$cmd" in
    load)     load_images ;;
    start)    start_all; enable_docker_service ;;
    packages) install_packages ;;
    sys)      setup_system_pre; sync_configs || true ;;
    sync)     sync_configs || true ;;
    app)      deploy_app ;;
    status)   show_status ;;
    all|"")   install_packages; load_images; setup_system_pre; deploy_app; sync_configs || true; start_all; enable_docker_service; show_status ;;
    *) err "未知命令：$cmd"; echo "用法：$0 [load|start|packages|sys|sync|app|status|all]"; exit 1 ;;
  esac
}

main "$@"
