#!/usr/bin/env bash # # Claude Code 一键初始化脚本 (Ubuntu / Debian / CentOS / RHEL) # # 联网策略(不直连,主备代理故障转移): # 主代理 PRIMARY_PROXY(direct.miyaip.online:8001) 优先; # 当它「连不通」或域名「解析到 1.1.1.1(黑洞)」时,自动切到 FALLBACK_PROXY(155.103.254.112:8022)。 # 两者都不通则秒级清晰报错退出(不再卡 apt 超时)。 # 可覆盖: curl ... | PROXY="http://user:pass@host:port" bash (强制指定代理) # # 一行安装: curl -fsSL https://claude.dahe2016.com/i.sh | bash # 仅升级: curl -fsSL https://claude.dahe2016.com/i.sh | bash -s -- upgrade # set -euo pipefail MODE="${1:-install}" # ========================== 在这里改成你自己的值 ========================== # 主代理(优先) + 备用代理(主不通/解析黑洞时切换) PRIMARY_PROXY="http://xeawewxqae:ottmopumitymooi@direct.miyaip.online:8001" FALLBACK_PROXY="http://xeawewxqae:ottmopumitymooi@155.103.254.112:8022" PRIMARY_HOST="direct.miyaip.online" # 用于解析黑洞检测的主机名 # 代理覆盖: 环境变量 PROXY 未设置=按上面主备自动选; PROXY=http://...=强制该代理 PROXY="${PROXY-__AUTO__}" # 探测目标与超时:命中 401/400/200 视为"能真正到 Anthropic"(403=区域封锁/000=超时 判不可用) PROBE_URL="https://api.anthropic.com/v1/models" PROBE_TIMEOUT=8 # 你的 SSH 公钥 (留空则不配置免密登录) SSH_PUB_KEY="" # Claude 认证 (留空=官方账号订阅,装完每台机各自 `claude` 登录) ANTHROPIC_BASE_URL="" ANTHROPIC_AUTH_TOKEN="" EFFORT_LEVEL="xhigh" SKIP_DANGEROUS_PROMPT="true" CLAUDE_USER="claude" NODE_MAJOR="20" HARDEN_ROOT_SSH="false" # ======================================================================== if [[ "${EUID}" -ne 0 ]]; then echo "请用 root 运行: sudo bash $0" >&2; exit 1; fi if [[ "${MODE}" != "install" && "${MODE}" != "upgrade" ]]; then echo "❌ 未知模式: ${MODE} (可用: install | upgrade)" >&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; } # 主机解析是否正常(空/解析到 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)$ ]] } # 决定 PROXY: 主备故障转移(默认) 或 强制指定 decide_proxy() { if [[ "${PROXY}" != "__AUTO__" ]]; then [[ -z "${PROXY}" ]] && log "按指定: 强制直连(不使用代理)" || log "按指定: 使用代理 ${PROXY}" else log "选择代理: 优先主 ${PRIMARY_HOST}:8001,失败回退备用 155.103.254.112:8022" if host_resolves_ok "${PRIMARY_HOST}"; then if probe "${PRIMARY_PROXY}"; then PROXY="${PRIMARY_PROXY}"; log "✅ 主代理可用" else warn "主代理 ${PRIMARY_HOST}:8001 连不通,切备用" fi else warn "主代理域名 ${PRIMARY_HOST} 解析异常(空/1.1.1.1 黑洞,本机IP多半未加白),切备用" fi if [[ "${PROXY}" == "__AUTO__" ]]; then if probe "${FALLBACK_PROXY}"; then PROXY="${FALLBACK_PROXY}"; log "✅ 备用代理 155.103.254.112:8022 可用" 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 install_base() { case "${PKG}" in apt) apt-get update -y; apt-get install -y curl ca-certificates gnupg openssh-server ;; dnf|yum) ${PKG} install -y curl ca-certificates openssh-server || 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) curl -fsSL "https://deb.nodesource.com/setup_${NODE_MAJOR}.x" | bash -; 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 } 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}" </dev/null; then printf '%s\n' "${SSH_PUB_KEY}" >> "${auth}"; log "已添加 SSH 公钥" else log "SSH 公钥已存在,跳过"; fi chmod 600 "${auth}"; chown -R "${owner}:$(id -gn "${owner}")" "${ssh_dir}" } harden_sshd() { [[ "${HARDEN_ROOT_SSH}" == "true" ]] || return 0 local cfg="/etc/ssh/sshd_config"; [[ -f "${cfg}" ]] || return 0 log "加固 sshd: root 仅密钥登录"; cp -a "${cfg}" "${cfg}.bak" sed -i -e 's/^#\?PubkeyAuthentication.*/PubkeyAuthentication yes/' \ -e 's/^#\?PermitRootLogin.*/PermitRootLogin prohibit-password/' "${cfg}" grep -q '^PubkeyAuthentication' "${cfg}" || echo 'PubkeyAuthentication yes' >> "${cfg}" grep -q '^PermitRootLogin' "${cfg}" || echo 'PermitRootLogin prohibit-password' >> "${cfg}" systemctl restart sshd 2>/dev/null || systemctl restart ssh 2>/dev/null || true } # ================================ 执行 ================================ 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 log "✅ 升级完成" echo " - Claude Code: $(claude --version 2>/dev/null || echo 已安装)" echo " - 代理: ${PROXY:-无/直连}" else install_base; install_node; install_claude; create_user write_settings "/root" "root"; add_ssh_key "/root" "root" CLAUDE_HOME="$(getent passwd "${CLAUDE_USER}" | cut -d: -f6)" write_settings "${CLAUDE_HOME}" "${CLAUDE_USER}"; add_ssh_key "${CLAUDE_HOME}" "${CLAUDE_USER}" harden_sshd log "✅ 全部完成" echo " - Node: $(node -v 2>/dev/null)" echo " - Claude Code: $(claude --version 2>/dev/null || echo 已安装)" echo " - 代理: ${PROXY:-无/直连}" echo " - 认证: 官方账号订阅 — 分别运行 'claude'(root) 和 'su - ${CLAUDE_USER} -c claude' 完成登录" fi