#!/bin/sh
# ============================================================
# migrate-to-root.sh — 把普通用户安装的 zqagent 节点迁到 root 身份运行
#
#   curl -fsSL https://agent.cd-zq.com/migrate-to-root.sh | sudo sh
#   curl -fsSL https://agent.cd-zq.com/migrate-to-root.sh | sudo sh -s -- --dry-run
#
# 适用：Linux + systemd，且当初用 install.sh 以普通用户身份安装。
# 迁移后仍是**同一个节点**：node.json（稳定 nodeId）与 fleet.json（节点凭据）
# 随数据目录一起搬，Hub 上无需重新 link。
#
# 做的事情：
#   1. 预检并下载上游 install.sh（失败则不动现有服务）
#   2. 停服务，备份 systemd unit
#   3. 复制数据目录到 root 家目录，chown root，旧目录保留
#   4. 修正 updates/state.json 里的绝对路径（回滚/暂存依赖它）
#   5. 确保可执行文件落在 /usr/local/bin
#   6. 用上游 install.sh 以 root 身份重写 unit（沿用端口/Host/公钥/通道）
#   7. 健康检查；任一步失败自动恢复 unit 并重启旧配置
#
# 关于 --home：
#   preserve（默认）在 unit 里写死 Environment=HOME=<原用户家目录>。进程 UID 已是
#   root，但 HOME 不变，claude/codex 等 agent CLI 的登录态、~/.config/zqagent 里的
#   LLM 连接配置继续可用 —— 这是「行为完全不变」的默认选择。
#   target 则让 HOME=/root，需要自己重新登录各类 CLI；脚本会把
#   ~/.config/zqagent 与 ~/.local/state/zqagent 复制过去，其余登录态不搬。
#
# 参数：
#   --service NAME    systemd unit 名，默认 zqagent
#   --data-dir DIR    目标数据目录，默认 <root 家目录>/.zqagent_data
#   --bin-dir DIR     目标安装目录；默认「旧二进制在家目录时用 /usr/local/bin，
#                     否则沿用旧目录」
#   --home MODE       preserve（默认）| target
#   --installer SRC   上游 install.sh 的本地路径或 URL；默认取原更新源同域的
#                     install.sh（内网自建镜像时自动跟随）
#   --purge-old       迁移成功后删除旧数据目录（默认保留，供回滚）
#   --force           目标数据目录已存在时合并复制（默认中止）
#   --dry-run         只打印将执行的命令，不动系统
#   --rollback        用最近一次备份的 unit 回滚并重启
#   -h, --help        显示本帮助
#
# 回滚只恢复服务定义，不回搬数据。若已用 --purge-old 删除旧数据目录，
# 请先把它恢复回来再回滚，否则节点会以空数据启动。
# ============================================================
set -eu

SELF_URL="https://agent.cd-zq.com/migrate-to-root.sh"
DEFAULT_INSTALLER="https://agent.cd-zq.com/install.sh"

SERVICE="zqagent"
TARGET_DATA=""
TARGET_BIN_DIR=""
BIN_DIR_SET=0
HOME_MODE="preserve"
INSTALLER=""
PURGE_OLD=0
FORCE=0
DRY_RUN=0
ROLLBACK=0
TMP_INSTALLER=""
BAK=""
MIGRATION_DONE=0
TARGET_DATA_CREATED=0

say() { printf '%s\n' "$*"; }
warn() { printf '! %s\n' "$*" >&2; }
die() {
  printf '错误：%s\n' "$*" >&2
  exit 1
}

# 从 stdin 执行（curl | sh）时 $0 是解释器本身，读不到脚本源码
# 文本，用法只能手写兜底。
usage() {
  if [ -r "$0" ] && [ "$(head -c 2 "$0" 2>/dev/null || true)" = "#!" ]; then
    awk '/^# =====/{if (++n == 2) exit; next} n == 1 {sub(/^# ?/, ""); print}' "$0"
    return 0
  fi
  cat <<EOF
用法：
  curl -fsSL ${SELF_URL} | sudo sh              # 迁移（默认保留 HOME）
  curl -fsSL ${SELF_URL} | sudo sh -s -- --dry-run

常用参数（可任意组合，写在 sh -s -- 之后）：
  --dry-run         只打印将执行的命令，不动系统
  --home target     HOME 一并切到 /root（agent CLI 需重新登录）
  --data-dir DIR    目标数据目录，默认 /root/.zqagent_data
  --bin-dir DIR     目标安装目录
  --installer SRC   上游 install.sh 的本地路径或 URL（内网镜像用）
  --purge-old       迁移成功后删除旧数据目录
  --force           目标数据目录非空时合并复制
  --rollback        回滚到迁移前的服务定义
  --service NAME    systemd unit 名，默认 zqagent
  -h, --help        显示本帮助

完整说明见脚本头注释：curl -fsSL ${SELF_URL} -o /tmp/m.sh && less /tmp/m.sh
EOF
}

while [ $# -gt 0 ]; do
  case "$1" in
    --service) SERVICE="${2:-}"; shift 2 ;;
    --data-dir) TARGET_DATA="${2:-}"; shift 2 ;;
    --bin-dir) TARGET_BIN_DIR="${2:-}"; BIN_DIR_SET=1; shift 2 ;;
    --home) HOME_MODE="${2:-}"; shift 2 ;;
    --installer) INSTALLER="${2:-}"; shift 2 ;;
    --purge-old) PURGE_OLD=1; shift ;;
    --force) FORCE=1; shift ;;
    --dry-run) DRY_RUN=1; shift ;;
    --rollback) ROLLBACK=1; shift ;;
    -h|--help) usage; exit 0 ;;
    *) die "未知参数：$1（用 --help 查看用法）" ;;
  esac
done

case "$HOME_MODE" in
  preserve|target) ;;
  *) die "--home 只接受 preserve 或 target，收到：${HOME_MODE}" ;;
esac

UNIT="/etc/systemd/system/${SERVICE}.service"

# dry-run 下只回显；真正执行时才调用外部命令
do_cmd() {
  if [ "$DRY_RUN" = "1" ]; then
    printf '  [dry-run] %s\n' "$*"
    return 0
  fi
  "$@"
}

# 失败时自动恢复 unit 并重启旧配置。成功路径把 MIGRATION_DONE 置 1。
on_exit() {
  if [ "$MIGRATION_DONE" = "1" ] || [ "$DRY_RUN" = "1" ]; then
    return 0
  fi
  [ -n "$BAK" ] || return 0
  [ -f "$BAK" ] || return 0
  warn "迁移未完成，恢复 unit 并重启旧配置"
  cp -f "$BAK" "$UNIT" || true
  systemctl daemon-reload || true
  systemctl restart "$SERVICE" || true
  if [ "$TARGET_DATA_CREATED" = "1" ]; then
    warn "目标数据目录已保留（未删除）：${TARGET_DATA}"
  fi
  warn "旧数据目录仍在：${OLD_DATA}"
}

# 供 sed 替换用的正则转义（路径里的 . + [ 等不能当通配符）
re_escape() { printf '%s' "$1" | sed 's/[][\\.^$*+?(){}|]/\\&/g'; }
# 替换串里只需转义 & 与分隔符 |
sub_escape() { printf '%s' "$1" | sed 's/[&|]/\\&/g'; }

# ---------- 回滚 ----------
if [ "$ROLLBACK" = "1" ]; then
  [ "$(id -u)" -eq 0 ] || die "需要 root：sudo sh $0 --rollback"
  latest="$(ls -1t "${UNIT}".bak.* 2>/dev/null | head -1 || true)"
  [ -n "$latest" ] || die "找不到 unit 备份（${UNIT}.bak.*），无法回滚"
  say "→ 用备份恢复：${latest}"
  say "  注意：只恢复服务定义，数据目录不回搬。旧数据目录若已被 --purge-old 删除，"
  say "        请先把它恢复回来再重启，否则节点会以空数据启动。"
  do_cmd cp -f "$latest" "$UNIT"
  do_cmd systemctl daemon-reload
  do_cmd systemctl restart "$SERVICE"
  say "✅ 已回滚并重启 ${SERVICE}（当前定义：$(grep -E '^(User|ExecStart)=' "$UNIT" | tr '\n' ' ')）"
  exit 0
fi

# ---------- 预检 ----------
[ "$(id -u)" -eq 0 ] || die "需要 root：curl -fsSL ${SELF_URL} | sudo sh"
command -v systemctl >/dev/null 2>&1 || die "找不到 systemctl（仅支持 systemd）"
[ -d /run/systemd/system ] || die "当前环境没有运行中的 systemd（容器内常见）"
[ -f "$UNIT" ] || die "找不到服务定义 ${UNIT}；先用 install.sh 装一遍，或用 --service 指定名称"

# 从 unit 里取简单键值与 ExecStart
unit_value() { sed -n "s/^$1=//p" "$UNIT" | head -1; }

EXEC_LINE="$(sed -n 's/^ExecStart=//p' "$UNIT" | head -1)"
[ -n "$EXEC_LINE" ] || die "${UNIT} 里没有 ExecStart"
EXEC_BIN="${EXEC_LINE%% *}"
EXEC_BIN="${EXEC_BIN%\"}"
EXEC_BIN="${EXEC_BIN#\"}"

# exec_arg <flag> <默认值>：从 ExecStart 里取某个参数
exec_arg() {
  _ea_v="$(printf '%s\n' "$EXEC_LINE" \
    | sed -n "s#.*[[:space:]]$1[[:space:]]\{1,\}\([^[:space:]]*\).*#\1#p" | head -1)"
  printf '%s' "${_ea_v:-$2}"
}

OLD_USER="$(unit_value User)"
OLD_USER="${OLD_USER:-root}"
OLD_HOME="$(getent passwd "$OLD_USER" 2>/dev/null | cut -d: -f6 || true)"
OLD_DATA="$(exec_arg --data-dir "${OLD_HOME:-}/.zqagent_data")"
# 老版本 unit 可能没写 --port（端口由 server-port 记录，进程会写回实际监听端口）
OLD_PORT="$(exec_arg --port "")"
if [ -z "$OLD_PORT" ] && [ -f "${OLD_DATA}/server-port" ]; then
  OLD_PORT="$(tr -dc '0-9' < "${OLD_DATA}/server-port" | head -c 5)"
fi
OLD_PORT="${OLD_PORT:-8787}"
OLD_HOST="$(exec_arg --host 127.0.0.1)"
OLD_LOG="$(sed -n 's/^StandardOutput=append://p' "$UNIT" | head -1)"
UPDATE_PUBKEY="$(unit_value Environment=ZQAGENT_UPDATE_PUBKEY)"
CHANNEL="$(unit_value Environment=ZQAGENT_UPDATE_CHANNEL)"
UPDATE_URL="$(unit_value Environment=ZQAGENT_UPDATE_URL)"

TARGET_HOME="$(getent passwd root 2>/dev/null | cut -d: -f6 || true)"
TARGET_HOME="${TARGET_HOME:-/root}"
TARGET_DATA="${TARGET_DATA:-${TARGET_HOME}/.zqagent_data}"

# 二进制：旧路径在原用户家目录下时搬到 /usr/local/bin，否则原地不动
if [ "$BIN_DIR_SET" = "0" ]; then
  if [ -n "$OLD_HOME" ]; then
    case "$EXEC_BIN" in
      "$OLD_HOME"/*) TARGET_BIN_DIR="/usr/local/bin" ;;
      *) TARGET_BIN_DIR="$(dirname "$EXEC_BIN")" ;;
    esac
  else
    TARGET_BIN_DIR="$(dirname "$EXEC_BIN")"
  fi
fi
TARGET_BIN="${TARGET_BIN_DIR}/zqagent"

if [ "$OLD_USER" = "root" ]; then
  say "✓ 服务 ${SERVICE} 已以 root 运行（${EXEC_LINE}）"
  say "  无需迁移。若只想改数据目录，直接重跑 install.sh 并带 --data-dir。"
  exit 0
fi
[ -n "$OLD_HOME" ] || die "取不到原用户 ${OLD_USER} 的家目录（用户已删除？）"
[ -d "$OLD_DATA" ] || die "旧数据目录不存在：${OLD_DATA}"

# npm 形态的 unit 是 `node <...>/dist/cli/index.js serve`，照抄会把 node 复制成
# zqagent；这种安装本来也不支持自更新，直接让用户用 install.sh 重装。
# 放在「文件是否存在」之前判断，否则报错会变成「/usr/bin/node 不存在」。
case "$(basename "$EXEC_BIN")" in
  node|nodejs)
    die "服务是用 node 启动的（npm 形态）：
  ${EXEC_LINE}
  迁移脚本只处理单文件（SEA）安装。请先用 install.sh 重装成单文件版本。"
    ;;
esac
[ -e "$EXEC_BIN" ] || die "旧可执行文件不存在：${EXEC_BIN}"
case "$EXEC_LINE" in
  *" serve --"*|*" serve"*) ;;
  *) die "无法识别的 ExecStart（缺少 serve 子命令）：${EXEC_LINE}" ;;
esac

if [ -e "$TARGET_DATA" ] && [ -n "$(ls -A "$TARGET_DATA" 2>/dev/null || true)" ] && [ "$FORCE" = "0" ]; then
  die "目标数据目录已存在且非空：${TARGET_DATA}
  确认可以合并复制再加 --force，或先用 --data-dir 指定别的目录。"
fi

# ---------- 计划 ----------
say ""
say "迁移计划"
say "  服务        ${SERVICE}"
say "  身份        ${OLD_USER} → root"
say "  数据目录    ${OLD_DATA} → ${TARGET_DATA}"
say "  可执行文件  ${EXEC_BIN} → ${TARGET_BIN}"
say "  端口/地址   ${OLD_PORT} / ${OLD_HOST}"
say "  日志        ${OLD_LOG:-未知} → ${TARGET_DATA}/node.log"
if [ "$HOME_MODE" = "preserve" ]; then
  say "  HOME        ${OLD_HOME}（保持，agent CLI 登录态不丢）"
else
  say "  HOME        ${TARGET_HOME}"
fi
if [ -L "$EXEC_BIN" ]; then
  warn "可执行文件是符号链接（npm 形态）：迁移后仍指向原用户目录，建议改用单文件安装包。"
fi
if [ "$HOME_MODE" = "preserve" ]; then
  say "  说明：root 进程会以 ${OLD_HOME} 作为 HOME 写入缓存/工具产物，"
  say "        这些新文件属主是 root，原用户之后再以 ${OLD_USER} 身份运行时可能读不到。"
fi
say ""

if [ "$DRY_RUN" = "1" ]; then
  say "（--dry-run：以下命令不会执行）"
fi

# ---------- 准备上游安装脚本（必须在停服务之前）----------
say "→ 准备上游 install.sh"
if [ -z "$INSTALLER" ]; then
  base="$(printf '%s' "$UPDATE_URL" | sed -n 's#^\(https\?://[^/]*\)/.*#\1#p')"
  INSTALLER="${base:-https://agent.cd-zq.com}/install.sh"
fi
if [ -f "$INSTALLER" ]; then
  INSTALLER_PATH="$INSTALLER"
  say "  使用本地文件 ${INSTALLER_PATH}"
  [ "$DRY_RUN" = "1" ] || head -n 1 "$INSTALLER_PATH" | grep -q '^#!' \
    || die "${INSTALLER_PATH} 不像是 shell 脚本"
else
  command -v curl >/dev/null 2>&1 || die "需要 curl 下载 install.sh，或用 --installer 指定本地文件"
  TMP_INSTALLER="$(mktemp "${TMPDIR:-/tmp}/zqagent-install.XXXXXX")"
  say "  下载 ${INSTALLER}"
  if [ "$DRY_RUN" = "1" ]; then
    printf '  [dry-run] curl -fsSL --max-time 30 -o %s %s\n' "$TMP_INSTALLER" "$INSTALLER"
  else
    curl -fsSL --max-time 30 -o "$TMP_INSTALLER" "$INSTALLER" \
      || die "下载 install.sh 失败：${INSTALLER}（网络受限时用 --installer 指定本地文件）"
    head -n 1 "$TMP_INSTALLER" | grep -q '^#!' \
      || die "下载到的内容不是 shell 脚本（可能被网关拦截）：${INSTALLER}"
  fi
  INSTALLER_PATH="$TMP_INSTALLER"
fi

# 从这里开始会改动系统，失败时由 on_exit 恢复
trap 'on_exit' EXIT

# ---------- 1. 停服务 + 备份 unit ----------
say "→ 停止服务并备份 unit"
if systemctl is-active --quiet "$SERVICE"; then
  do_cmd systemctl stop "$SERVICE"
else
  say "  （服务当前未运行，跳过 stop）"
fi
BAK="${UNIT}.bak.$(date +%Y%m%d%H%M%S)"
do_cmd cp -a "$UNIT" "$BAK"

# ---------- 2. 复制数据目录 ----------
if [ ! -e "$TARGET_DATA" ]; then
  TARGET_DATA_CREATED=1
fi
old_real="$(readlink -f "$OLD_DATA")"
new_real="$(readlink -f "$TARGET_DATA" 2>/dev/null || printf '%s' "$TARGET_DATA")"
if [ "$old_real" != "$new_real" ]; then
  say "→ 复制数据目录（旧目录保留，供回滚）"
  do_cmd mkdir -p "$TARGET_DATA"
  do_cmd cp -a "${OLD_DATA}/." "${TARGET_DATA}/"
  do_cmd chown -R root:root "$TARGET_DATA"
  if [ "$DRY_RUN" = "0" ]; then
    src_n="$(find "$OLD_DATA" | wc -l)"
    dst_n="$(find "$TARGET_DATA" | wc -l)"
    [ "$dst_n" -ge "$src_n" ] || die "复制不完整（源 ${src_n} 项，目标 ${dst_n} 项），已中止"
    say "  已复制 ${dst_n} 项"
  fi
else
  say "→ 数据目录已是目标位置，跳过复制"
  do_cmd chown -R root:root "$TARGET_DATA"
fi

# ---------- 3. 修正更新状态里的绝对路径 ----------
STATE="${TARGET_DATA}/updates/state.json"
if [ -f "$STATE" ] && [ "$DRY_RUN" = "0" ]; then
  say "→ 修正 updates/state.json 中的绝对路径"
  old_re="$(re_escape "$OLD_DATA")"
  new_esc="$(sub_escape "$TARGET_DATA")"
  sed -i "s|${old_re}|${new_esc}|g" "$STATE"
  if [ "$EXEC_BIN" != "$TARGET_BIN" ]; then
    old_bin_re="$(re_escape "$EXEC_BIN")"
    new_bin_esc="$(sub_escape "$TARGET_BIN")"
    sed -i "s|${old_bin_re}|${new_bin_esc}|g" "$STATE"
  fi
fi

# ---------- 4. 确保二进制就位 ----------
if [ "$EXEC_BIN" != "$TARGET_BIN" ]; then
  say "→ 放置可执行文件"
  if [ -e "$TARGET_BIN" ]; then
    say "  ${TARGET_BIN} 已存在，跳过复制"
  else
    do_cmd mkdir -p "$TARGET_BIN_DIR"
    # 复制而非软链：原用户目录将来可能被清理
    do_cmd cp -a "$EXEC_BIN" "$TARGET_BIN"
    do_cmd chmod 755 "$TARGET_BIN"
  fi
fi

# ---------- 5. 用上游 install.sh 以 root 重写 unit ----------
say "→ 以 root 身份重写 systemd unit"

# 从旧更新地址反推 --base-url，保证新 unit 的清单地址与原来一致
BASE_URL=""
if [ -n "$UPDATE_URL" ] && [ -n "$CHANNEL" ]; then
  BASE_URL="${UPDATE_URL%/"${CHANNEL}".json}"
fi

# POSIX sh 没有数组：用位置参数构造子命令参数
set -- install --bin-dir "$TARGET_BIN_DIR" --data-dir "$TARGET_DATA" --service-name "$SERVICE"
if [ -n "$OLD_PORT" ]; then set -- "$@" --port "$OLD_PORT"; fi
if [ -n "$OLD_HOST" ]; then set -- "$@" --host "$OLD_HOST"; fi
if [ -n "$UPDATE_PUBKEY" ]; then set -- "$@" --pubkey "$UPDATE_PUBKEY"; fi
if [ -n "$CHANNEL" ]; then set -- "$@" --channel "$CHANNEL"; fi
if [ -n "$BASE_URL" ]; then set -- "$@" --base-url "$BASE_URL"; fi

# env -u SUDO_USER 是关键：install.sh 用 SUDO_USER 推导服务运行用户，
# 通过 sudo 执行时会得到 ubuntu，unit 又会写成普通用户。
# stdin 重定向到 /dev/null：curl | sh 场景下 stdin 是脚本本身，不能让
# 子进程误读剩余内容。
if [ "$DRY_RUN" = "1" ]; then
  printf '  [dry-run] env -u SUDO_USER bash %s %s\n' "$INSTALLER_PATH" "$*"
else
  env -u SUDO_USER -u SUDO_UID -u SUDO_GID bash "$INSTALLER_PATH" "$@" < /dev/null
fi

# ---------- 6. HOME 处理 ----------
if [ "$HOME_MODE" = "preserve" ]; then
  say "→ 固定 HOME=${OLD_HOME}（保持 agent CLI 登录态）"
  if [ "$DRY_RUN" = "0" ]; then
    if grep -q '^Environment=HOME=' "$UNIT"; then
      sed -i "s|^Environment=HOME=.*|Environment=HOME=${OLD_HOME}|" "$UNIT"
    else
      tmp="$(mktemp)"
      awk -v h="$OLD_HOME" '{print} /^\[Service\]$/ {print "Environment=HOME=" h}' "$UNIT" > "$tmp"
      mv "$tmp" "$UNIT"
    fi
    systemctl daemon-reload
    systemctl restart "$SERVICE" || true
  fi
else
  say "→ HOME 切到 ${TARGET_HOME}"
  for d in ".config/zqagent" ".local/state/zqagent"; do
    src="${OLD_HOME}/${d}"
    dst="${TARGET_HOME}/${d}"
    [ -d "$src" ] || continue
    if [ -e "$dst" ]; then
      say "  ${dst} 已存在，跳过"
      continue
    fi
    do_cmd mkdir -p "$(dirname "$dst")"
    do_cmd cp -a "$src" "$dst"
    do_cmd chown -R root:root "$dst"
  done
  warn "agent CLI（claude/codex 等）的登录态没有搬，首次使用需重新登录。"
fi

# ---------- 7. 验证 ----------
if [ "$DRY_RUN" = "1" ]; then
  MIGRATION_DONE=1
  say ""
  if [ "$PURGE_OLD" = "1" ]; then
    say "（--purge-old：健康检查通过后会删除 ${OLD_DATA}）"
  fi
  say "✅ dry-run 结束，未做任何修改。"
  exit 0
fi

say "→ 等待服务就绪"
# install.sh 在端口被占用时会自动递增并把新端口写进 unit，所以以 unit 为准
PROBE_PORT="$(sed -n 's/^ExecStart=.*--port[ =]\([0-9][0-9]*\).*/\1/p' "$UNIT" | head -1)"
PROBE_PORT="${PROBE_PORT:-$OLD_PORT}"
if [ "$PROBE_PORT" != "$OLD_PORT" ]; then
  warn "端口已从 ${OLD_PORT} 变为 ${PROBE_PORT}（安装时原端口不可用）"
fi
probe_host="$OLD_HOST"
case "$probe_host" in 0.0.0.0|"") probe_host="127.0.0.1" ;; esac

ok=0
if command -v curl >/dev/null 2>&1; then
  i=0
  while [ "$i" -lt 30 ]; do
    if curl -fsS -m 2 "http://${probe_host}:${PROBE_PORT}/healthz" >/dev/null 2>&1; then
      ok=1
      break
    fi
    i=$((i + 1))
    sleep 1
  done
else
  # 没有 curl 时退化为「进程在跑」：至少确认 systemd 没进入 restart 循环
  sleep 3
  if systemctl is-active --quiet "$SERVICE"; then ok=1; fi
  warn "未安装 curl，跳过 HTTP 健康检查，仅确认服务处于 active"
fi

main_pid="$(systemctl show -p MainPID "$SERVICE" 2>/dev/null | sed -n 's/^MainPID=//p')"
run_user="$(ps -o user= -p "${main_pid:-0}" 2>/dev/null | tr -d ' ' || true)"

if [ "$ok" != "1" ]; then
  warn "健康检查未通过（${probe_host}:${PROBE_PORT}/healthz）"
  warn "排查：systemctl status ${SERVICE} --no-pager；日志 ${TARGET_DATA}/node.log"
  warn "确认无误前可回滚：curl -fsSL ${SELF_URL} | sudo sh -s -- --rollback"
  exit 1
fi

MIGRATION_DONE=1

# grep 在管道里失败会被 sed 的退出码掩盖，用 sed -n p + 参数兜底
home_shown="$(sed -n 's/^Environment=HOME=//p' "$UNIT" | head -1)"
say ""
say "✅ 迁移完成"
say "  运行用户  ${run_user:-未知}（PID ${main_pid:-?}）"
say "  数据目录  ${TARGET_DATA}"
say "  日志      ${TARGET_DATA}/node.log"
say "  服务定义  ${UNIT}（备份 ${BAK}）"
say "  HOME      ${home_shown:-${TARGET_HOME}}"
say ""
say "  节点身份未变（node.json / fleet.json 已随数据迁移），Hub 上无需重新接入。"
if [ "$PURGE_OLD" = "1" ]; then
  say "→ 删除旧数据目录 ${OLD_DATA}"
  rm -rf "$OLD_DATA"
else
  say "  旧数据目录仍保留：${OLD_DATA}"
  say "  确认运行正常后可删除：sudo rm -rf ${OLD_DATA}"
fi
say "  回滚：curl -fsSL ${SELF_URL} | sudo sh -s -- --rollback"
