#!/usr/bin/env bash
#
# Claude Code 一键初始化脚本 (Ubuntu / Debian / CentOS / RHEL)
#
# 联网策略(不直连,两代理「交互二选一」+ 自动回退):
#   运行时弹「1/2」菜单选代理; 选中的连不通 / 域名解析到 1.1.1.1(黑洞) 时自动切另一个,
#   两者都不通则秒级清晰报错退出(不再卡 apt 超时)。
#     1) 大陆·域名线路 http.dahe2017.com:8022(默认)   2) 海外·IP线路 155.103.254.112:8022   (同账号)
#   可覆盖:  curl ... | PROXY="http://user:pass@host:port" bash   或   PROXY="host:port:user:pass" bash
#
# 互动菜单:  curl -fsSL https://claude.dahe2016.com/claude.sh | bash          (不带参数=弹菜单:1安装/2升级/3仅改代理)
# 直接安装:  curl -fsSL https://claude.dahe2016.com/claude.sh | bash -s -- install
# 仅升级:    curl -fsSL https://claude.dahe2016.com/claude.sh | bash -s -- upgrade
# 仅改代理:  curl -fsSL https://claude.dahe2016.com/claude.sh | bash -s -- proxy
#            (老代理失效时,只重写 root/claude 的 settings.json 代理,不重装 Node/Claude)
#
# 注意: 本脚本不再负责 SSH 公钥/密钥与 sshd 加固。
#       要配 SSH 证书(给 root/claude 等用户授权公钥或生成密钥对),用独立脚本:
#         curl -fsSL https://claude.dahe2016.com/ssh.sh | bash
#
set -euo pipefail

MODE="${1-}"   # 留空=互动菜单选择; 传 install|upgrade|proxy=直接执行(自动化用)

# ========================== 在这里改成你自己的值 ==========================
# 大陆/海外 二选一(选中的不通则自动切另一个)。同账号 xeawewxqae。
# Claude 走 :8022 线(http.dahe2017.com 与 155.103.254.112 实测都到 Anthropic,出口均 155.103.254.112)。
# 注: Codex 走 OpenAI 需 :8001 线(只有 direct.miyaip.online:8001 通),与此不同,见 codex.sh。
PROXY_1="http://xeawewxqae:ottmopumitymooi@http.dahe2017.com:8022"
PROXY_1_HOST="http.dahe2017.com"          # 大陆·域名前端
PROXY_1_LABEL="大陆·域名线路 http.dahe2017.com:8022"
PROXY_2="http://xeawewxqae:ottmopumitymooi@155.103.254.112:8022"
PROXY_2_HOST="155.103.254.112"            # 海外·纯IP(无 DNS,黑洞检测恒通过)
PROXY_2_LABEL="海外·IP线路 155.103.254.112:8022"
DEFAULT_PROXY_CHOICE="1"                   # 回车 / 无交互终端时用哪个(=大陆域名线)
# 注: 代理同端口一般支持 HTTP/HTTPS/SOCKS5,此处用 http:// 方案;如需走 SOCKS5 把 http:// 改成 socks5h:// 即可。

# 代理覆盖: 环境变量 PROXY 未设置=交互二选一(自动回退); PROXY="" =强制直连;
#   PROXY="http://user:pass@host:port" 或 PROXY="host:port:user:pass" =强制该代理(后者自动规整成 URL)
PROXY="${PROXY-__AUTO__}"

# 探测目标与超时:命中 401/400/200 视为"能真正到 Anthropic"(403=区域封锁/000=超时 判不可用)
PROBE_URL="https://api.anthropic.com/v1/models"
PROBE_TIMEOUT=8

# Claude 认证 (留空=官方账号订阅,装完每台机各自 `claude` 登录)
ANTHROPIC_BASE_URL=""
ANTHROPIC_AUTH_TOKEN=""

EFFORT_LEVEL="xhigh"
SKIP_DANGEROUS_PROMPT="true"
CLAUDE_USER="claude"
NODE_MAJOR="20"
# ========================================================================

if [[ "${EUID}" -ne 0 ]]; then echo "请用 root 运行: sudo bash $0" >&2; exit 1; fi

log()  { printf '\n\033[1;32m==> %s\033[0m\n' "$*"; }
warn() { printf '\033[1;33m[!] %s\033[0m\n' "$*" >&2; }
die()  { printf '\033[1;31m❌ %s\033[0m\n' "$*" >&2; exit 1; }

# 不带参数 => 互动菜单(从 /dev/tty 读,curl|bash 管道运行也能交互);带参数 => 直跑(自动化)
choose_mode() {
  if [[ ! -r /dev/tty ]]; then MODE="install"; warn "无交互终端(/dev/tty 不可用),默认 install 模式"; return; fi
  {
    printf '\n\033[1;36m请选择要执行的操作:\033[0m\n'
    printf '  1) 全量安装   装 Node + Claude + 建 claude 用户 + 免密 sudo + 写代理  (默认)\n'
    printf '  2) 升级       升级 Claude + 重写代理 + 补授免密 sudo(不装 Node)\n'
    printf '  3) 仅改代理   只重写 root/claude 的代理配置,其它都不动\n'
  } >/dev/tty
  local a; read -r -p "输入 1/2/3 [回车=1]: " a </dev/tty || true
  case "${a:-1}" in
    1|install) MODE="install" ;;
    2|upgrade) MODE="upgrade" ;;
    3|proxy)   MODE="proxy" ;;
    *) die "无效选择: ${a}" ;;
  esac
  log "已选择: ${MODE}"
}

[[ -z "${MODE}" ]] && choose_mode
if [[ "${MODE}" != "install" && "${MODE}" != "upgrade" && "${MODE}" != "proxy" ]]; then
  die "未知模式: ${MODE} (可用: install | upgrade | proxy)"; fi

# 主机解析是否正常(空/解析到 1.1.1.1 黑洞 => 异常,返回1)
host_resolves_ok() {
  local host="$1" ips
  ips="$(getent hosts "${host}" 2>/dev/null | awk '{print $1}')"
  [[ -z "${ips}" ]] && return 1
  grep -qx '1.1.1.1' <<<"${ips}" && return 1
  return 0
}

# 该通路能否真正到 Anthropic。$1=代理(空则直连)
probe() {
  local proxy="$1" code
  if [[ -n "${proxy}" ]]; then
    code="$(curl -s -o /dev/null -w '%{http_code}' --max-time "${PROBE_TIMEOUT}" -x "${proxy}" "${PROBE_URL}" 2>/dev/null || true)"
  else
    code="$(curl -s -o /dev/null -w '%{http_code}' --max-time "${PROBE_TIMEOUT}" --noproxy '*' "${PROBE_URL}" 2>/dev/null || true)"
  fi
  # 401(鉴权)/400/200 = 真正到达 Anthropic; 403=区域封锁 000=超时 都判不可用
  [[ "${code}" =~ ^(200|400|401)$ ]]
}

# 把 "host:port:user:pass" 规整成 "http://user:pass@host:port"; 已是 URL(含 ://)或空则原样返回
normalize_proxy() {
  local p="$1"
  [[ -z "${p}" || "${p}" == *"://"* ]] && { printf '%s' "${p}"; return; }
  local h port user pass rest
  IFS=':' read -r h port user pass rest <<<"${p}"
  if [[ -n "${h}" && -n "${port}" && -n "${user}" ]]; then
    printf 'http://%s:%s@%s:%s' "${user}" "${pass}" "${h}" "${port}"
  else
    printf '%s' "${p}"   # 认不出的格式原样交给 curl
  fi
}

# 交互二选一(或无终端时用默认)。结果写入全局 CHOSEN/CHOST/CLABEL/OTHER/OLABEL。
select_proxy_pair() {
  local sel="${DEFAULT_PROXY_CHOICE}"
  if [[ -r /dev/tty ]]; then
    {
      printf '\n\033[1;36m请选择代理:\033[0m\n'
      printf '  1) %s  (默认)\n' "${PROXY_1_LABEL}"
      printf '  2) %s\n' "${PROXY_2_LABEL}"
    } >/dev/tty 2>/dev/null || true
    read -r -p "输入 1/2 [回车=${DEFAULT_PROXY_CHOICE}]: " sel </dev/tty 2>/dev/null || true
    sel="${sel:-${DEFAULT_PROXY_CHOICE}}"
  else
    warn "无交互终端,默认用代理 ${DEFAULT_PROXY_CHOICE}"
  fi
  case "${sel}" in
    2) CHOSEN="${PROXY_2}"; CHOST="${PROXY_2_HOST}"; CLABEL="${PROXY_2_LABEL}"; OTHER="${PROXY_1}"; OLABEL="${PROXY_1_LABEL}" ;;
    1) CHOSEN="${PROXY_1}"; CHOST="${PROXY_1_HOST}"; CLABEL="${PROXY_1_LABEL}"; OTHER="${PROXY_2}"; OLABEL="${PROXY_2_LABEL}" ;;
    *) warn "无效选择「${sel}」,用默认代理 1"; CHOSEN="${PROXY_1}"; CHOST="${PROXY_1_HOST}"; CLABEL="${PROXY_1_LABEL}"; OTHER="${PROXY_2}"; OLABEL="${PROXY_2_LABEL}" ;;
  esac
  log "已选代理: ${CLABEL}"
}

# 决定 PROXY: 交互二选一(默认) 或 强制指定
decide_proxy() {
  if [[ "${PROXY}" != "__AUTO__" ]]; then
    PROXY="$(normalize_proxy "${PROXY}")"
    [[ -z "${PROXY}" ]] && log "按指定: 强制直连(不使用代理)" || log "按指定: 使用代理 ${PROXY}"
  else
    select_proxy_pair
    log "验证选定代理 ${CLABEL} ..."
    if host_resolves_ok "${CHOST}" && probe "${CHOSEN}"; then
      PROXY="${CHOSEN}"; log "✅ 选定代理可用 (${CLABEL})"
    else
      warn "选定代理 ${CLABEL} 不可用(连不通/解析黑洞=本机出口IP多半未加白),自动切另一个: ${OLABEL}"
      if probe "${OTHER}"; then
        PROXY="${OTHER}"; log "✅ 已切到备用 ${OLABEL}"
      else
        die "两个代理都连不上 Anthropic。请确认本机出口IP已在 miyaip 加白,或用 PROXY=\"可用代理\" 覆盖后重试。"
      fi
    fi
  fi
  log "验证选定通路 ..."
  probe "${PROXY}" || { [[ -z "${PROXY}" ]] && die "无法直连 ${PROBE_URL}。" || die "选定代理 ${PROXY} 验证失败。"; }
  log "✅ 通路可用 (${PROXY:-直连})"
}

apply_proxy_env() {
  if [[ -n "${PROXY}" ]]; then
    export http_proxy="${PROXY}"  https_proxy="${PROXY}"
    export HTTP_PROXY="${PROXY}"  HTTPS_PROXY="${PROXY}"
    export no_proxy="localhost,127.0.0.1,::1"  NO_PROXY="localhost,127.0.0.1,::1"
  else
    unset http_proxy https_proxy HTTP_PROXY HTTPS_PROXY ALL_PROXY all_proxy 2>/dev/null || true
  fi
}

if command -v apt-get >/dev/null 2>&1; then PKG="apt"
elif command -v dnf >/dev/null 2>&1; then PKG="dnf"
elif command -v yum >/dev/null 2>&1; then PKG="yum"
else die "不支持的系统:未找到 apt/dnf/yum"; fi

# 等待 apt/dpkg 锁释放(新机常被开机自动更新 unattended-upgrades 占用,否则 apt 报 lock 退出)
wait_apt_lock() {
  [[ "${PKG}" == "apt" ]] || return 0
  command -v fuser >/dev/null 2>&1 || return 0
  local waited=0 max=300
  while fuser /var/lib/dpkg/lock-frontend /var/lib/dpkg/lock /var/cache/apt/archives/lock >/dev/null 2>&1; do
    [[ ${waited} -eq 0 ]] && warn "apt/dpkg 锁被占用(多半是开机自动更新 unattended-upgrades),等待释放(最多 ${max}s)…"
    sleep 5; waited=$((waited+5))
    if [[ ${waited} -ge ${max} ]]; then
      warn "等待 ${max}s 仍被占用,尝试停掉 unattended-upgrades 后继续"
      systemctl stop unattended-upgrades apt-daily.service apt-daily-upgrade.service 2>/dev/null || true
      break
    fi
  done
  if [[ ${waited} -gt 0 ]]; then log "apt 锁已释放(等了 ${waited}s),继续"; fi
  return 0   # 末尾必须显式 return 0:否则锁空闲时上面 [[ ]] 为假会返回1,叠加 set -e 静默退出
}

install_base() {
  case "${PKG}" in
    apt) wait_apt_lock; apt-get update -y; apt-get install -y curl ca-certificates gnupg openssh-server sudo ;;
    dnf|yum) ${PKG} install -y curl ca-certificates openssh-server sudo || true ;;
  esac
}

install_node() {
  if command -v node >/dev/null 2>&1; then log "已存在 Node: $(node -v),跳过安装"; return; fi
  log "通过 NodeSource 安装 Node.js ${NODE_MAJOR}.x"
  case "${PKG}" in
    apt) wait_apt_lock; curl -fsSL "https://deb.nodesource.com/setup_${NODE_MAJOR}.x" | bash -; wait_apt_lock; apt-get install -y nodejs ;;
    dnf|yum) curl -fsSL "https://rpm.nodesource.com/setup_${NODE_MAJOR}.x" | bash -; ${PKG} install -y nodejs ;;
  esac
}

install_claude() {
  command -v npm >/dev/null 2>&1 || die "未找到 npm,无法安装 Claude Code。请先以 install 模式安装 Node。"
  local before; before="$(claude --version 2>/dev/null || true)"
  [[ -n "${before}" ]] && log "检测到已装 Claude Code (${before}),升级到最新版" || log "安装 @anthropic-ai/claude-code 最新版"
  if [[ -n "${PROXY}" ]]; then npm config set proxy "${PROXY}"; npm config set https-proxy "${PROXY}"
  else npm config delete proxy 2>/dev/null || true; npm config delete https-proxy 2>/dev/null || true; fi
  npm install -g @anthropic-ai/claude-code@latest
  local after; after="$(claude --version 2>/dev/null || echo '已安装')"
  [[ -n "${before}" && "${before}" != "${after}" ]] && log "Claude Code 已升级: ${before} -> ${after}" || log "Claude Code 版本: ${after}"
}

create_user() {
  if id "${CLAUDE_USER}" >/dev/null 2>&1; then log "用户 ${CLAUDE_USER} 已存在"
  else log "创建系统用户 ${CLAUDE_USER}"; useradd -m -s /bin/bash "${CLAUDE_USER}"; fi
}

# 给 CLAUDE_USER 授予免密 sudo(写 /etc/sudoers.d/,visudo 校验,失败回滚)
grant_sudo_nopasswd() {
  id "${CLAUDE_USER}" >/dev/null 2>&1 || { warn "用户 ${CLAUDE_USER} 不存在,跳过免密 sudo 授权"; return 0; }
  if ! command -v visudo >/dev/null 2>&1; then
    warn "未找到 sudo/visudo,尝试安装 sudo"
    case "${PKG}" in
      apt) wait_apt_lock; apt-get install -y sudo || true ;;
      dnf|yum) ${PKG} install -y sudo || true ;;
    esac
  fi
  command -v visudo >/dev/null 2>&1 || { warn "仍无 visudo,无法授予免密 sudo(跳过)"; return 0; }
  local f="/etc/sudoers.d/${CLAUDE_USER}" tmp
  tmp="$(mktemp)"
  printf '%s ALL=(ALL) NOPASSWD:ALL\n' "${CLAUDE_USER}" > "${tmp}"
  if visudo -cf "${tmp}" >/dev/null 2>&1; then
    install -m 0440 -o root -g root "${tmp}" "${f}"
    rm -f "${tmp}"
    log "已授予 ${CLAUDE_USER} 免密 sudo (${f})"
  else
    rm -f "${tmp}"
    warn "sudoers 片段校验失败,未授予免密 sudo(已回滚)"
  fi
}

write_settings() {
  local home="$1" owner="$2"
  local dir="${home}/.claude"
  local file="${dir}/settings.json"
  mkdir -p "${dir}"
  [[ -f "${file}" ]] && { cp -a "${file}" "${file}.bak.$(date +%s 2>/dev/null || echo bak)"; log "已备份原 settings.json"; }
  local env_lines=()
  if [[ -n "${PROXY}" ]]; then
    env_lines+=("    \"HTTP_PROXY\": \"${PROXY}\"")
    env_lines+=("    \"HTTPS_PROXY\": \"${PROXY}\"")
    env_lines+=("    \"NO_PROXY\": \"localhost,127.0.0.1,::1\"")
  fi
  [[ -n "${ANTHROPIC_BASE_URL}" ]]   && env_lines+=("    \"ANTHROPIC_BASE_URL\": \"${ANTHROPIC_BASE_URL}\"")
  [[ -n "${ANTHROPIC_AUTH_TOKEN}" ]] && env_lines+=("    \"ANTHROPIC_AUTH_TOKEN\": \"${ANTHROPIC_AUTH_TOKEN}\"")
  local env_body="" i
  for i in "${!env_lines[@]}"; do [[ ${i} -gt 0 ]] && env_body+=$',\n'; env_body+="${env_lines[$i]}"; done
  local top_body=""
  [[ -n "${EFFORT_LEVEL}" ]] && top_body+=$',\n  "effortLevel": "'"${EFFORT_LEVEL}"$'"'
  [[ "${SKIP_DANGEROUS_PROMPT}" == "true" ]] && top_body+=$',\n  "permissions": {\n    "skipDangerousModePermissionPrompt": true\n  }'
  cat > "${file}" <<EOF
{
  "env": {
${env_body}
  }${top_body}
}
EOF
  chmod 600 "${file}"; chown -R "${owner}:$(id -gn "${owner}")" "${dir}"
  log "已写入 ${file} (代理: ${PROXY:-无/直连})"
}

# ================================ 执行 ================================

# 仅改代理模式:老代理失效时,只重写 root/claude 的 settings.json 代理,不重装 Node/Claude。
# 这里不走 decide_proxy 的「探不通就 die」逻辑——目标机当前代理多半已坏,
# 我们强制写入新代理(探测仅作提示,不通也照写),让配置先修好。
if [[ "${MODE}" == "proxy" ]]; then
  log "模式: proxy (仅更新 root/claude 的代理配置)"
  # 交互二选一(或按 PROXY 覆盖); proxy 模式探不通也照写,故这里不做故障转移
  if [[ "${PROXY}" == "__AUTO__" ]]; then select_proxy_pair; PROXY="${CHOSEN}"; else PROXY="$(normalize_proxy "${PROXY}")"; fi
  if [[ -n "${PROXY}" ]]; then
    if probe "${PROXY}"; then log "✅ 新代理可用: ${PROXY}"
    else warn "新代理探测未通过(${PROXY}),仍按要求写入配置;请确认本机出口/网络后再 'claude' 验证"; fi
  else
    log "按指定: 强制直连(不写代理)"
  fi
  apply_proxy_env
  # 同步更新 root 的 npm 代理(便于后续 npm 升级走新代理)
  if command -v npm >/dev/null 2>&1; then
    if [[ -n "${PROXY}" ]]; then npm config set proxy "${PROXY}"; npm config set https-proxy "${PROXY}"
    else npm config delete proxy 2>/dev/null || true; npm config delete https-proxy 2>/dev/null || true; fi
  fi
  write_settings "/root" "root"
  if id "${CLAUDE_USER}" >/dev/null 2>&1; then
    write_settings "$(getent passwd "${CLAUDE_USER}" | cut -d: -f6)" "${CLAUDE_USER}"
  else log "用户 ${CLAUDE_USER} 不存在,跳过其 settings.json"; fi
  log "✅ 代理配置已更新(仅 settings.json,未动 Node/Claude)"
  echo "  - 代理: ${PROXY:-无/直连}"
  exit 0
fi

decide_proxy
apply_proxy_env
log "包管理器: ${PKG}  |  模式: ${MODE}"

if [[ "${MODE}" == "upgrade" ]]; then
  install_claude
  write_settings "/root" "root"
  if id "${CLAUDE_USER}" >/dev/null 2>&1; then
    write_settings "$(getent passwd "${CLAUDE_USER}" | cut -d: -f6)" "${CLAUDE_USER}"
  else log "用户 ${CLAUDE_USER} 不存在,跳过其 settings.json"; fi
  grant_sudo_nopasswd
  log "✅ 升级完成"
  echo "  - Claude Code: $(claude --version 2>/dev/null || echo 已安装)"
  echo "  - 代理:        ${PROXY:-无/直连}"
  echo "  - 免密 sudo:   ${CLAUDE_USER}($([[ -f /etc/sudoers.d/${CLAUDE_USER} ]] && echo 已配置 || echo 未配置))"
else
  install_base; install_node; install_claude; create_user
  grant_sudo_nopasswd
  write_settings "/root" "root"
  CLAUDE_HOME="$(getent passwd "${CLAUDE_USER}" | cut -d: -f6)"
  write_settings "${CLAUDE_HOME}" "${CLAUDE_USER}"
  log "✅ 全部完成"
  echo "  - Node:        $(node -v 2>/dev/null)"
  echo "  - Claude Code: $(claude --version 2>/dev/null || echo 已安装)"
  echo "  - 代理:        ${PROXY:-无/直连}"
  echo "  - 免密 sudo:   ${CLAUDE_USER}($([[ -f /etc/sudoers.d/${CLAUDE_USER} ]] && echo 已配置 || echo 未配置))"
  echo "  - 认证:        官方账号订阅 — 分别运行 'claude'(root) 和 'su - ${CLAUDE_USER} -c claude' 完成登录"
  echo "  - SSH 证书:    本脚本不再配置,需要请跑: curl -fsSL https://claude.dahe2016.com/ssh.sh | bash"
fi
