#!/bin/sh
# install_hubvpn.sh —— 一行命令把一台 Linux 设备变成「枢纽」（中继端）或「访问端」。
#
# 设计目标（需求 R4：零命令行、零后台）：
#   * 一条命令搞定：下载 → 校验 → 安装 → 开机自启 → 启动；
#   * 不需要用户理解 IP / 网段 / 端口 / tag；
#   * 装完只差"在浏览器里登录一次"，其余全自动。
#
# 用法（复制粘贴即可）：
#   curl -fsSL https://yeeid.com/install.sh | sudo sh -s -- --user 你的账号@example.com --password 你的密码
#   （默认 --mode bridge = 让这台设备当枢纽；--mode client = 只当访问端）
#
# 常用参数：
#   --mode bridge|client     运行角色（默认 bridge）
#   --user/--password        账号密码（给了就装完直接入网；不给则只起控制台，登录后点启动）
#   --tag NAME               枢纽标识（默认取主机名，仅供区分）
#   --server HOST[:PORT]     信令/平台地址（默认 yeeid.com）
#   --wss URL                控制面走 WSS（默认按下载中心地址推导 wss://HOST/hubvpn-ws/）
#   --dir PATH               安装目录（默认 /opt/hubvpn）
#   --file NAME              指定下载文件名（默认按 CPU 架构推断）
#   --sha256 HEX             期望的 sha256（默认从下载中心页面自动读取）
#   --base-url URL           下载中心地址（默认 https://yeeid.com）
#   --allow-unverified       下载中心页面取不到校验值时仍然继续（默认拒绝）
#   --dry-run                只打印将要做什么，不下载、不写盘、不启动
#   --no-start               安装并配置开机自启，但先不启动
#   --no-service             只装二进制，不写 systemd/procd 服务（自己管服务时用）
#
# 兼容性：POSIX sh（dash / busybox ash / OpenWrt 都能跑），只用 curl|wget + sha256sum。
# 安全：sha256 校验不通过一律拒绝安装；服务/init 文件按 0600 写入（可能含账号口令）。
set -eu

BASE_URL="${HUBVPN_BASE_URL:-https://yeeid.com}"
MODE="${HUBVPN_MODE:-bridge}"
SERVER="${HUBVPN_SERVER:-yeeid.com}"
INSTALL_DIR="${HUBVPN_INSTALL_DIR:-/opt/hubvpn}"
SERVICE_NAME="${HUBVPN_SERVICE_NAME:-hubvpn}"
TAG="${HUBVPN_TAG:-}"
ACCOUNT_USER="${HUBVPN_USER:-}"
ACCOUNT_PASS="${HUBVPN_PASSWORD:-}"
WSS="${HUBVPN_WSS:-}"
FILE=""
EXPECTED_SHA256="${HUBVPN_SHA256:-}"
ALLOW_UNVERIFIED=0
DRY_RUN=0
DO_START=1
NO_SERVICE=0

die() { echo "[错误] $*" >&2; exit 1; }
info() { echo "==> $*"; }
warn() { echo "[警告] $*" >&2; }

run() {
  if [ "${DRY_RUN}" -eq 1 ]; then
    echo "    [dry-run] $*"
    return 0
  fi
  "$@"
}

while [ $# -gt 0 ]; do
  case "$1" in
    --mode) MODE="${2:-}"; shift 2 ;;
    --user) ACCOUNT_USER="${2:-}"; shift 2 ;;
    --password|--pass) ACCOUNT_PASS="${2:-}"; shift 2 ;;
    --tag) TAG="${2:-}"; shift 2 ;;
    --server) SERVER="${2:-}"; shift 2 ;;
    --wss) WSS="${2:-}"; shift 2 ;;
    --dir) INSTALL_DIR="${2:-}"; shift 2 ;;
    --file) FILE="${2:-}"; shift 2 ;;
    --sha256) EXPECTED_SHA256="${2:-}"; shift 2 ;;
    --base-url) BASE_URL="${2:-}"; shift 2 ;;
    --allow-unverified) ALLOW_UNVERIFIED=1; shift ;;
    --dry-run) DRY_RUN=1; shift ;;
    --no-start) DO_START=0; shift ;;
    --no-service) NO_SERVICE=1; shift ;;
    -h|--help) sed -n '2,32p' "$0" 2>/dev/null || true; exit 0 ;;
    *) die "未知参数：$1（--help 查看用法）" ;;
  esac
done

case "${MODE}" in
  bridge|client) ;;
  *) die "--mode 只支持 bridge（枢纽）或 client（访问端），收到：${MODE}" ;;
esac
BASE_URL="${BASE_URL%/}"
case "${ACCOUNT_PASS}" in
  *"'"*) die "口令不能包含单引号（会被原样写进服务文件）" ;;
esac

# ---------- ① 架构 ----------
detect_arch() {
  machine="$(uname -m 2>/dev/null || echo unknown)"
  case "${machine}" in
    x86_64|amd64) echo amd64 ;;
    armv7l|armv7|armv6l|arm) echo arm7 ;;
    aarch64|arm64)
      die "平台暂未提供 arm64 版 Linux 安装包。请改用 amd64/armv7 设备，或从源码构建。" ;;
    *) die "无法识别的 CPU 架构：${machine}" ;;
  esac
}
ARCH="$(detect_arch)"
[ -n "${FILE}" ] || FILE="Yeeid_linux_${ARCH}"
ZIP_FILE="${FILE}.zip"

# ---------- ② 下载工具与校验工具 ----------
DOWNLOADER=""
if command -v curl >/dev/null 2>&1; then DOWNLOADER=curl
elif command -v wget >/dev/null 2>&1; then DOWNLOADER=wget
elif [ "${DRY_RUN}" -eq 0 ]; then die "需要 curl 或 wget 才能下载安装包"
fi

http_get() {
  url="$1"; out="${2:-}"
  if [ "${DOWNLOADER}" = curl ]; then
    if [ -n "${out}" ]; then curl -fsSL --max-time 300 -o "${out}" "${url}"
    else curl -fsSL --max-time 30 "${url}"; fi
  else
    if [ -n "${out}" ]; then wget -q -T 300 -O "${out}" "${url}"
    else wget -q -T 30 -O - "${url}"; fi
  fi
}

sha256_of() {
  file="$1"
  if command -v sha256sum >/dev/null 2>&1; then sha256sum "${file}" | awk '{print $1}'
  elif command -v busybox >/dev/null 2>&1; then busybox sha256sum "${file}" | awk '{print $1}'
  elif command -v shasum >/dev/null 2>&1; then shasum -a 256 "${file}" | awk '{print $1}'
  elif command -v openssl >/dev/null 2>&1; then openssl dgst -sha256 "${file}" | awk '{print $NF}'
  elif command -v python3 >/dev/null 2>&1; then
    python3 -c 'import hashlib,sys;print(hashlib.sha256(open(sys.argv[1],"rb").read()).hexdigest())' "${file}"
  else return 1
  fi
}

# 从下载中心页面读取该文件的 sha256（页面本身就展示 sha256，用户/运维可自行核对）。
# 页面结构：<article class="download-card"> … <h4>文件名</h4> … md5 … sha256 … </article>
published_sha256() {
  name="$1"
  page="$(http_get "${BASE_URL}/" 2>/dev/null || true)"
  [ -n "${page}" ] || return 1
  printf '%s\n' "${page}" | tr -d '\r' | awk -v name="${name}" '
    index($0, "<h4>" name "</h4>") { found = 1; next }
    found && /class="download-card"/ { exit }
    found && /checksum-value/ {
      line = $0
      sub(/.*checksum-value">/, "", line)
      sub(/<.*/, "", line)
      gsub(/[^0-9a-fA-F]/, "", line)
      if (length(line) == 64) sha = line
    }
    END { if (sha != "") print sha; else exit 1 }
  '
}

# ---------- ③ 选产物 + 校验值 ----------
# 优先直接下载裸二进制（省掉解压依赖）；下载中心只公开 .zip 时退回压缩包。
ARTIFACT="${FILE}"
if [ "${DRY_RUN}" -eq 0 ]; then
  info "从下载中心读取 ${FILE} 的 sha256（用于校验下载是否被篡改）"
  PAGE_SHA="$(published_sha256 "${FILE}" 2>/dev/null || true)"
  if [ -z "${PAGE_SHA}" ]; then
    PAGE_SHA="$(published_sha256 "${ZIP_FILE}" 2>/dev/null || true)"
    [ -n "${PAGE_SHA}" ] && ARTIFACT="${ZIP_FILE}"
  fi
  [ -n "${EXPECTED_SHA256}" ] || EXPECTED_SHA256="${PAGE_SHA}"
fi
if [ -z "${EXPECTED_SHA256}" ] && [ "${DRY_RUN}" -eq 0 ] && [ "${ALLOW_UNVERIFIED}" -eq 0 ]; then
  cat >&2 <<EOF
[错误] 拿不到 ${FILE} 的 sha256，拒绝在无法校验的情况下安装。
       三个选择：
         1) 在下载中心页面复制该文件的 SHA256，用 --sha256 <值> 重跑；
         2) 确认信任当前网络与下载源，用 --allow-unverified 重跑；
         3) 检查 --base-url 是否正确（当前 ${BASE_URL}）。
EOF
  exit 1
fi

# ---------- ④ 权限与运行参数 ----------
if [ "${DRY_RUN}" -eq 0 ] && [ "${NO_SERVICE}" -eq 0 ] && [ "$(id -u)" -ne 0 ]; then
  die "需要 root 权限安装（请用 sudo；只想装二进制可用 --no-service --dir <可写目录>，或加 --dry-run 先看要做什么）"
fi

case "${SERVER}" in
  *:*) SERVER_ADDR="${SERVER}" ;;
  *) SERVER_ADDR="${SERVER}:${HUBVPN_SERVER_PORT:-9999}" ;;
esac
[ -n "${TAG}" ] || TAG="$(hostname 2>/dev/null | tr -d '\r\n' | sed 's/[^A-Za-z0-9._-]/-/g' | cut -c1-32)"
[ -n "${TAG}" ] || TAG="hub"
if [ -z "${WSS}" ]; then
  case "${BASE_URL}" in
    https://*) WSS="wss://$(printf '%s' "${BASE_URL}" | sed 's|^https://||')/hubvpn-ws/" ;;
    *) WSS="" ;;
  esac
fi

BINARY_PATH="${INSTALL_DIR}/${FILE}"
START_ARGS="-cli -mode ${MODE} -server ${SERVER_ADDR} -tag ${TAG}"
[ -n "${WSS}" ] && START_ARGS="${START_ARGS} -control-wss ${WSS}"
if [ -n "${ACCOUNT_USER}" ] && [ -n "${ACCOUNT_PASS}" ]; then
  START_ARGS="${START_ARGS} -u ${ACCOUNT_USER} -p '${ACCOUNT_PASS}'"
fi

echo "=========================================="
echo " Yeeid 一键安装"
echo "------------------------------------------"
if [ "${MODE}" = bridge ]; then
  echo " 角色      : 枢纽（中继端）——其他设备选它即可连上，其内网自动可用"
else
  echo " 角色      : 访问端"
fi
echo " 平台地址  : ${SERVER_ADDR}"
echo " 安装目录  : ${INSTALL_DIR}"
echo " 架构/产物 : ${ARCH} / ${ARTIFACT}"
if [ "${DRY_RUN}" -eq 1 ]; then
  echo " 校验      : （dry-run：跳过，正式运行时读取下载中心页面上的 sha256 并强校验）"
else
  echo " 校验 sha256: ${EXPECTED_SHA256:-（未取得：需 --allow-unverified）}"
fi
echo " 账号      : ${ACCOUNT_USER:-（未提供：装完在浏览器登录一次即可）}"
echo " 服务名    : ${SERVICE_NAME}"
echo "=========================================="

# ---------- ⑤ 下载 + 强校验 ----------
TMP_DIR=""
cleanup() {
  if [ -n "${TMP_DIR}" ]; then rm -rf "${TMP_DIR}"; fi
}
trap cleanup EXIT INT TERM
if [ "${DRY_RUN}" -eq 0 ]; then
  TMP_DIR="$(mktemp -d 2>/dev/null || mktemp -d -t hubvpn)"
fi

if [ "${DRY_RUN}" -eq 1 ]; then
  info "[dry-run] 下载 ${BASE_URL}/downloads/${ARTIFACT} 并做 sha256 强校验，然后安装到 ${BINARY_PATH}"
  DOWNLOADED=""
else
  info "下载 ${BASE_URL}/downloads/${ARTIFACT}"
  http_get "${BASE_URL}/downloads/${ARTIFACT}" "${TMP_DIR}/${ARTIFACT}" \
    || die "下载失败：${BASE_URL}/downloads/${ARTIFACT}（可用 --file 指定其他文件名）"

  if [ -n "${EXPECTED_SHA256}" ]; then
    ACTUAL_SHA256="$(sha256_of "${TMP_DIR}/${ARTIFACT}")" \
      || die "本机没有可用的 sha256 工具（sha256sum/busybox/shasum/openssl/python3）"
    if [ "${ACTUAL_SHA256}" != "${EXPECTED_SHA256}" ]; then
      cat >&2 <<EOF
[错误] 校验失败，已放弃安装：
         期望 ${EXPECTED_SHA256}
         实际 ${ACTUAL_SHA256}
       可能原因：下载源被篡改、代理缓存了旧版本、或下载不完整。请重试或更换网络。
EOF
      exit 1
    fi
    info "sha256 校验通过：${ACTUAL_SHA256}"
  else
    warn "无法取得官方 sha256，按 --allow-unverified 继续（建议事后核对下载中心页面上的值）"
  fi

  if [ "${ARTIFACT}" = "${ZIP_FILE}" ] || [ "${ARTIFACT##*.}" = "zip" ]; then
    info "解压 ${ARTIFACT}"
    if command -v unzip >/dev/null 2>&1; then
      unzip -q -o "${TMP_DIR}/${ARTIFACT}" -d "${TMP_DIR}/unz" || die "解压失败：${ARTIFACT}"
    elif command -v busybox >/dev/null 2>&1; then
      busybox unzip -o "${TMP_DIR}/${ARTIFACT}" -d "${TMP_DIR}/unz" >/dev/null || die "解压失败：${ARTIFACT}"
    elif command -v python3 >/dev/null 2>&1; then
      python3 -c 'import sys,zipfile;zipfile.ZipFile(sys.argv[1]).extractall(sys.argv[2])' \
        "${TMP_DIR}/${ARTIFACT}" "${TMP_DIR}/unz" || die "解压失败：${ARTIFACT}"
    else
      die "需要 unzip（或 busybox unzip / python3）来解压 ${ARTIFACT}"
    fi
    DOWNLOADED="$(find "${TMP_DIR}/unz" -type f -name "${FILE}*" 2>/dev/null | head -1)"
    [ -n "${DOWNLOADED}" ] || die "压缩包里没找到 ${FILE}"
  else
    DOWNLOADED="${TMP_DIR}/${ARTIFACT}"
  fi
fi

# ---------- ⑥ 安装 ----------
if [ "${DRY_RUN}" -eq 1 ]; then
  info "[dry-run] 创建 ${INSTALL_DIR} 并安装二进制到 ${BINARY_PATH}"
else
  mkdir -p "${INSTALL_DIR}"
  if [ -f "${BINARY_PATH}" ]; then
    cp -p "${BINARY_PATH}" "${BINARY_PATH}.bak.$(date +%Y%m%d%H%M%S)"
    info "已备份旧版本（升级时配置与账号保持不变）"
  fi
  cp -f "${DOWNLOADED}" "${BINARY_PATH}.new"
  chmod 0755 "${BINARY_PATH}.new"
  mv -f "${BINARY_PATH}.new" "${BINARY_PATH}"
fi

# ---------- ⑦ 开机自启 ----------
install_systemd() {
  UNIT="/etc/systemd/system/${SERVICE_NAME}.service"
  info "写入 systemd 服务 ${UNIT}（开机自启 + 崩溃自动重启）"
  if [ "${DRY_RUN}" -eq 1 ]; then
    echo "    [dry-run] ExecStart=${BINARY_PATH} ${START_ARGS}"
    return 0
  fi
  [ -f "${UNIT}" ] && cp -a "${UNIT}" "${UNIT}.bak.$(date +%Y%m%d%H%M%S)"
  umask 077
  cat > "${UNIT}" <<EOF
[Unit]
Description=Yeeid VPN (${MODE})
After=network.target network-online.target
Wants=network-online.target

[Service]
Type=simple
User=root
WorkingDirectory=${INSTALL_DIR}
ExecStart=${BINARY_PATH} ${START_ARGS}
Restart=always
RestartSec=5
LimitNOFILE=65535

[Install]
WantedBy=multi-user.target
EOF
  chmod 0600 "${UNIT}"
  systemctl daemon-reload
  systemctl enable "${SERVICE_NAME}" >/dev/null 2>&1 || true
  if [ "${DO_START}" -eq 1 ]; then systemctl restart "${SERVICE_NAME}"; fi
}

install_procd() {
  INIT="/etc/init.d/${SERVICE_NAME}"
  info "写入 OpenWrt/iStoreOS procd 服务 ${INIT}（开机自启 + 崩溃自动重启）"
  if [ "${DRY_RUN}" -eq 1 ]; then
    echo "    [dry-run] procd command ${BINARY_PATH} ${START_ARGS}"
    return 0
  fi
  [ -f "${INIT}" ] && cp -a "${INIT}" "${INIT}.bak.$(date +%Y%m%d%H%M%S)"
  umask 077
  cat > "${INIT}" <<EOF
#!/bin/sh /etc/rc.common
START=99
STOP=10
USE_PROCD=1

start_service() {
    procd_open_instance ${SERVICE_NAME}
    procd_set_param command ${BINARY_PATH} ${START_ARGS}
    procd_set_param respawn
    procd_set_param stdout 1
    procd_set_param stderr 1
    procd_close_instance
}

stop_service() {
    procd_kill ${SERVICE_NAME} ${SERVICE_NAME}
}
EOF
  chmod 0755 "${INIT}"
  "${INIT}" enable >/dev/null 2>&1 || true
  if [ "${DO_START}" -eq 1 ]; then "${INIT}" restart; fi
}

if [ "${DRY_RUN}" -eq 1 ]; then
  install_systemd
elif [ "${NO_SERVICE}" -eq 1 ]; then
  info "按 --no-service 跳过服务配置；手动启动：${BINARY_PATH} ${START_ARGS}"
elif command -v systemctl >/dev/null 2>&1 && [ -d /etc/systemd/system ]; then
  install_systemd
elif [ -f /etc/rc.common ]; then
  install_procd
else
  warn "没找到 systemd 或 OpenWrt procd：二进制已装好，但需要你自行配置开机自启"
  echo "    手动启动：${BINARY_PATH} ${START_ARGS}"
fi

if [ "${DRY_RUN}" -eq 0 ] && [ "${DO_START}" -eq 1 ] && [ "${NO_SERVICE}" -eq 0 ] && [ -f "/etc/systemd/system/${SERVICE_NAME}.service" ]; then
  sleep 2
  if ! systemctl is-active --quiet "${SERVICE_NAME}"; then
    warn "${SERVICE_NAME} 未处于 active；排查：journalctl -u ${SERVICE_NAME} -n 50 --no-pager"
  fi
fi

# ---------- ⑧ 告诉用户下一步（只有一步） ----------
LAN_IP=""
if command -v ip >/dev/null 2>&1; then
  LAN_IP="$(ip route get 1.1.1.1 2>/dev/null | awk '{for (i = 1; i <= NF; i++) if ($i == "src") { print $(i + 1); exit }}')"
fi
[ -n "${LAN_IP}" ] || LAN_IP="<本机内网IP>"

if [ -n "${ACCOUNT_USER}" ]; then
  NEXT_STEP="用你的账号登录（已用 ${ACCOUNT_USER} 自动入网，登录后即可看到设备）"
else
  NEXT_STEP='用你的账号登录，然后点一下「启动服务」'
fi

echo ""
echo "=========================================="
echo " 安装完成"
echo "=========================================="
if [ "${MODE}" = bridge ]; then
  echo " 这台设备现在是「枢纽」："
  echo "   · 你自己的其他设备登录后，在「在线中继」里选中它即可；"
  echo "   · 它背后的局域网默认就可用，不需要手点「内网路由」。"
else
  echo " 这台设备是「访问端」：登录后选中一台在线中继即可连通。"
fi
echo ""
echo " 下一步（只有一步）："
echo "   浏览器打开   http://${LAN_IP}:16699"
echo "   ${NEXT_STEP}"
echo ""
echo " 常用操作："
if [ "${NO_SERVICE}" -eq 1 ]; then
  echo "   启动       ${BINARY_PATH} ${START_ARGS}"
else
  echo "   查看状态   systemctl status ${SERVICE_NAME}          # systemd 系统"
  echo "   重启       systemctl restart ${SERVICE_NAME}"
fi
echo "   升级      重新执行本脚本（自动备份旧版本，配置与账号保留）"
echo "=========================================="
