#!/usr/bin/env bash # ============================================================= # WSL2 内部环境检测 & 安装脚本 # 可单独在 WSL2 Ubuntu 中运行:bash wsl-setup.sh # 也会被 deploy.ps1 自动调用(通过 Invoke-WSL) # ============================================================= set -euo pipefail RED='\033[0;31m'; GREEN='\033[0;32m'; YELLOW='\033[1;33m' CYAN='\033[0;36m'; NC='\033[0m' log() { echo -e "${GREEN}[INFO]${NC} $*"; } warn() { echo -e "${YELLOW}[WARN]${NC} $*"; } error() { echo -e "${RED}[ERROR]${NC} $*" >&2; } step() { echo -e "\n${CYAN}====== $* ======${NC}"; } # ────────────────────────────────────────────────────────────── # 加载 .env(与脚本同目录) # ────────────────────────────────────────────────────────────── SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)" GODOT_MCP_VERSION="godot-mcp-pro-v1.14.1" # ← 升级时只改这里 ENV_FILE="$SCRIPT_DIR/.env" ENV_EXAMPLE="$SCRIPT_DIR/.env.example" if [ ! -f "$ENV_FILE" ]; then if [ -f "$ENV_EXAMPLE" ]; then cp "$ENV_EXAMPLE" "$ENV_FILE" warn "已从 .env.example 创建 .env,请按需编辑后重新运行" exit 0 fi fi if [ -f "$ENV_FILE" ]; then sed -i 's/\r$//' "$ENV_FILE" set -a; source "$ENV_FILE"; set +a fi ANTHROPIC_API_KEY="${ANTHROPIC_API_KEY:-}" ANTHROPIC_BASE_URL="${ANTHROPIC_BASE_URL:-https://api.anthropic.com}" CLAUDE_MODEL="${CLAUDE_MODEL:-claude-opus-4-5}" BRIDGE_PORT="${BRIDGE_PORT:-7890}" # ────────────────────────────────────────────────────────────── # Step 1: v2rayN 代理配置(WSL2 侧) # ────────────────────────────────────────────────────────────── step "1/6 代理配置" # 优先读取 deploy.ps1 写入的 ~/.mcp-proxy.env if [ -f ~/.mcp-proxy.env ]; then source ~/.mcp-proxy.env 2>/dev/null || true if [ -n "${http_proxy:-}" ]; then log "代理已从 ~/.mcp-proxy.env 加载: $http_proxy" fi fi # 若代理未设置但提供了 PROXY_PORT,则动态检测 Windows 主机 IP PROXY_PORT="${PROXY_PORT:-0}" if [ -z "${http_proxy:-}" ] && [ "$PROXY_PORT" -gt 0 ] 2>/dev/null; then WIN_HOST=$(ip route 2>/dev/null | grep default | awk '{print $3}' | head -1) [ -z "$WIN_HOST" ] && WIN_HOST=$(grep nameserver /etc/resolv.conf 2>/dev/null | awk '{print $2}' | head -1) if [ -n "$WIN_HOST" ]; then export http_proxy="http://${WIN_HOST}:${PROXY_PORT}" export https_proxy="$http_proxy" export HTTP_PROXY="$http_proxy" export HTTPS_PROXY="$http_proxy" export no_proxy="localhost,127.0.0.1,::1" log "WSL2 代理已设置: $http_proxy (Windows 主机: $WIN_HOST)" else warn "无法获取 Windows 主机 IP,代理未设置" fi fi # 写入 ~/.mcp-proxy.env 并持久化到 ~/.bashrc if [ -n "${http_proxy:-}" ] && [ -z "$(grep -s 'mcp-proxy.env' ~/.bashrc)" ]; then cat >> ~/.bashrc << 'BASHRCBLOCK' # Unity MCP WSL2 proxy (generated by wsl-setup.sh) [ -f ~/.mcp-proxy.env ] && . ~/.mcp-proxy.env BASHRCBLOCK log "代理配置已写入 ~/.bashrc" fi # 写入 git 代理 if [ -n "${http_proxy:-}" ]; then git config --global http.proxy "$http_proxy" 2>/dev/null || true git config --global https.proxy "$https_proxy" 2>/dev/null || true log "git 代理已配置" fi if [ -z "${http_proxy:-}" ]; then warn "未配置代理,将使用直连网络(如下载失败请通过 PROXY_PORT=10809 重新运行)" fi # ────────────────────────────────────────────────────────────── # Step 2: 系统依赖 # ────────────────────────────────────────────────────────────── step "2/6 系统依赖" export DEBIAN_FRONTEND=noninteractive sudo apt-get update -qq sudo apt-get install -y -qq \ curl wget git build-essential pkg-config libssl-dev \ ca-certificates gnupg lsb-release unzip python3 wslu log "系统依赖安装完成" # ────────────────────────────────────────────────────────────── # Step 3: Node.js LTS # ────────────────────────────────────────────────────────────── step "3/6 Node.js LTS" if command -v node &>/dev/null; then log "Node.js 已安装: $(node --version)" else log "通过 NodeSource 安装 Node.js LTS..." curl -fsSL https://deb.nodesource.com/setup_lts.x | sudo -E bash - > /dev/null 2>&1 sudo apt-get install -y -qq nodejs log "Node.js 安装完成: $(node --version)" fi # 设置 npm 代理 if [ -n "${http_proxy:-}" ] && command -v npm &>/dev/null; then npm config set proxy "$http_proxy" 2>/dev/null || true npm config set https-proxy "$https_proxy" 2>/dev/null || true log "npm 代理已配置" fi # ────────────────────────────────────────────────────────────── # Step 4: Claude Code CLI + GitHub Copilot CLI # ────────────────────────────────────────────────────────────── step "4/6 Claude Code CLI + GitHub Copilot CLI" if command -v claude &>/dev/null; then log "Claude Code 已安装: $(claude --version 2>&1 | head -1)" else log "安装 @anthropic-ai/claude-code..." sudo npm install -g @anthropic-ai/claude-code --quiet log "Claude Code 安装完成: $(claude --version 2>&1 | head -1)" fi GH_PATH="$(command -v gh 2>/dev/null || true)" if [ -n "$GH_PATH" ] && [[ "$GH_PATH" != /mnt/* ]]; then log "GitHub CLI 已安装: $(gh --version 2>/dev/null | head -1)" else log "安装 GitHub CLI..." sudo mkdir -p -m 755 /etc/apt/keyrings if [ ! -f /etc/apt/keyrings/githubcli-archive-keyring.gpg ]; then curl -fsSL --connect-timeout 20 --max-time 30 https://cli.github.com/packages/githubcli-archive-keyring.gpg \ | sudo dd of=/etc/apt/keyrings/githubcli-archive-keyring.gpg status=none sudo chmod go+r /etc/apt/keyrings/githubcli-archive-keyring.gpg fi if [ ! -f /etc/apt/sources.list.d/github-cli.list ]; then echo "deb [arch=$(dpkg --print-architecture) signed-by=/etc/apt/keyrings/githubcli-archive-keyring.gpg] https://cli.github.com/packages stable main" \ | sudo tee /etc/apt/sources.list.d/github-cli.list > /dev/null fi if sudo apt-get update -qq -o Acquire::http::Timeout=20 -o Acquire::https::Timeout=20 2>/dev/null && \ sudo apt-get install -y -qq -o Acquire::http::Timeout=30 gh 2>/dev/null; then log "GitHub CLI 安装完成: $(gh --version 2>/dev/null | head -1)" else warn "GitHub CLI 安装失败(网络超时),跳过;后续可手动安装:sudo apt install gh" fi fi COPILOT_EXT_BIN="$HOME/.local/share/gh/extensions/gh-copilot/gh-copilot" NEED_COPILOT_INSTALL=false if gh extension list 2>/dev/null | grep -q 'github/gh-copilot'; then # 验证是否为原生 Linux 二进制,而非 VS Code 写入的 Windows .bat wrapper if [ -f "$COPILOT_EXT_BIN" ] && grep -qiE '/mnt/[a-z]/|.bat' "$COPILOT_EXT_BIN" 2>/dev/null; then warn "检测到 Windows 版 gh-copilot 扩展(非 Linux 原生),将重新安装..." gh extension remove github/gh-copilot 2>/dev/null || rm -rf "$(dirname "$COPILOT_EXT_BIN")" NEED_COPILOT_INSTALL=true else log "GitHub Copilot CLI 扩展已安装(原生 Linux 版)" fi else NEED_COPILOT_INSTALL=true fi if [ "$NEED_COPILOT_INSTALL" = true ]; then log "安装 GitHub Copilot CLI 扩展..." if gh extension install github/gh-copilot 2>/dev/null; then log "GitHub Copilot CLI 扩展已安装(命令入口:gh copilot)" else warn "GitHub Copilot CLI 扩展安装失败,可稍后手动执行:gh extension install github/gh-copilot" fi fi mkdir -p ~/.claude cat > ~/.claude/settings.json << SETTINGS {"model": "$CLAUDE_MODEL"} SETTINGS log "已写入 ~/.claude/settings.json" PROFILE_BLOCK="" [ -n "$ANTHROPIC_API_KEY" ] && PROFILE_BLOCK+="export ANTHROPIC_API_KEY='$ANTHROPIC_API_KEY'\n" [ -n "$ANTHROPIC_BASE_URL" ] && PROFILE_BLOCK+="export ANTHROPIC_BASE_URL='$ANTHROPIC_BASE_URL'\n" PROFILE_BLOCK+="export CLAUDE_MODEL='$CLAUDE_MODEL'\n" # 从 PATH 过滤掉 VS Code Copilot Chat 写入的 Windows copilot 包装脚本 # 该脚本会调用 .bat 文件,在 Linux 下无法执行,导致 gh copilot 报错 if ! grep -q 'copilotCli' ~/.bashrc 2>/dev/null; then cat >> ~/.bashrc << 'PATHFIX' # 排除 VS Code Copilot Chat Windows wrapper(gh copilot 在 WSL2 中会误调用 .bat 文件) PATH="$(echo "$PATH" | tr ':' '\n' | grep -Fv 'copilotCli' | tr '\n' ':' | sed 's/:$//')" PATHFIX log "已写入 PATH 修复(排除 Windows copilotCli)到 ~/.bashrc" fi # 在当前 shell 也立即生效 PATH="$(echo "$PATH" | tr ':' '\n' | grep -Fv 'copilotCli' | tr '\n' ':' | sed 's/:$//')" for rc in ~/.bashrc ~/.profile; do if ! grep -q "ANTHROPIC_API_KEY" "$rc" 2>/dev/null; then printf "\n# Claude Code CLI\n${PROFILE_BLOCK}" >> "$rc" log "已追加环境变量到 $rc" fi done # ────────────────────────────────────────────────────────────── # Step 5: Unity MCP Server # ────────────────────────────────────────────────────────────── step "5/6 Unity MCP Server (AnkleBreaker-Studio)" MCP_SERVER_DIR="$HOME/.mcp-servers/unity-mcp-server" mkdir -p "$HOME/.mcp-servers" if [ -d "$MCP_SERVER_DIR/.git" ]; then log "unity-mcp-server 已存在,更新至最新..." git -C "$MCP_SERVER_DIR" pull --quiet else log "克隆 unity-mcp-server..." git clone --quiet https://github.com/AnkleBreaker-Studio/unity-mcp-server.git "$MCP_SERVER_DIR" fi log "安装 npm 依赖..." (cd "$MCP_SERVER_DIR" && npm install --prefer-offline --quiet 2>/dev/null || npm install --quiet) # 确定入口文件 if [ -f "$MCP_SERVER_DIR/src/index.js" ]; then SERVER_ENTRY="$MCP_SERVER_DIR/src/index.js" elif [ -f "$MCP_SERVER_DIR/build/index.js" ]; then SERVER_ENTRY="$MCP_SERVER_DIR/build/index.js" else SERVER_ENTRY="$MCP_SERVER_DIR/index.js" fi log "MCP Server 入口:$SERVER_ENTRY" # 写入 Claude Code (WSL2) MCP 配置 MCP_CONFIG_DIR="$HOME/.config/Claude" MCP_CONFIG_FILE="$MCP_CONFIG_DIR/claude_desktop_config.json" mkdir -p "$MCP_CONFIG_DIR" python3 - << PYEOF import json, os cfg_file = "$MCP_CONFIG_FILE" try: with open(cfg_file) as f: cfg = json.load(f) except: cfg = {} cfg.setdefault("mcpServers", {}) cfg["mcpServers"]["unity-mcp"] = { "command": "node", "args": ["$SERVER_ENTRY"], "env": {"UNITY_BRIDGE_PORT": "$BRIDGE_PORT"} } with open(cfg_file, "w") as f: json.dump(cfg, f, indent=2) print("Claude Code (WSL2) MCP config written") PYEOF log "MCP 配置已写入 $MCP_CONFIG_FILE" # ────────────────────────────────────────────────────────────── # Step 5b: Godot MCP Pro(本地包,server/ 目录) # ────────────────────────────────────────────────────────────── step "5b/6 Godot MCP Pro" GODOT_SRC="$SCRIPT_DIR/$GODOT_MCP_VERSION/server" GODOT_DST="$HOME/.mcp-servers/godot-mcp-pro" if [ -d "$GODOT_SRC" ]; then log "安装 Godot MCP Pro: $GODOT_SRC -> $GODOT_DST" mkdir -p "$HOME/.mcp-servers" if command -v rsync >/dev/null 2>&1; then rsync -a --delete --exclude='node_modules' "$GODOT_SRC/" "$GODOT_DST/" else mkdir -p "$GODOT_DST" find "$GODOT_SRC" -mindepth 1 -maxdepth 1 ! -name 'node_modules' \ -exec cp -r {} "$GODOT_DST/" \; fi log "执行 node build/setup.js install(Godot MCP Pro)..." (cd "$GODOT_DST" && node build/setup.js install) GODOT_ENTRY="$GODOT_DST/build/index.js" log "Godot MCP Pro 入口:$GODOT_ENTRY" # 写入 Claude Code (WSL2) MCP 配置(godot-mcp-pro 条目) python3 - << PYEOF import json, os cfg_file = "$MCP_CONFIG_FILE" try: with open(cfg_file) as f: cfg = json.load(f) except: cfg = {} cfg.setdefault("mcpServers", {}) cfg["mcpServers"]["godot-mcp-pro"] = { "command": "node", "args": ["$GODOT_ENTRY"] } with open(cfg_file, "w") as f: json.dump(cfg, f, indent=2) print("Godot MCP Pro config written") PYEOF log "Godot MCP Pro MCP 配置已写入 $MCP_CONFIG_FILE" else warn "未找到 $GODOT_SRC,跳过 Godot MCP Pro 安装" warn "请确认 $GODOT_MCP_VERSION/ 目录与 wsl-setup.sh 位于同一目录" GODOT_ENTRY="" fi # ────────────────────────────────────────────────────────────── # Step 6: Rust 工具链 & Token Killer # ────────────────────────────────────────────────────────────── step "6/6 Rust 工具链 & Token Killer" [ -f "$HOME/.cargo/env" ] && source "$HOME/.cargo/env" if command -v rustc &>/dev/null; then log "Rust 已安装: $(rustc --version)" else log "通过 rustup 安装 Rust stable..." curl --proto '=https' --tlsv1.2 -sSf https://sh.rustup.rs \ | sh -s -- -y --default-toolchain stable --no-modify-path source "$HOME/.cargo/env" log "Rust 安装完成: $(rustc --version)" fi for rc in ~/.bashrc ~/.profile; do grep -q 'cargo/env' "$rc" 2>/dev/null || echo 'source ~/.cargo/env 2>/dev/null || true' >> "$rc" done if command -v rtk &>/dev/null; then log "rtk 已安装: $(rtk --version 2>/dev/null || echo ok)" else log "安装 rtk (Rust Token Killer)..." sudo apt-get install -y -qq pkg-config libssl-dev 2>/dev/null || true if cargo install --git https://github.com/rtk-ai/rtk 2>&1 | tail -3; then log "rtk 安装成功" else warn "rtk 安装失败,请手动运行: cargo install --git https://github.com/rtk-ai/rtk" fi fi # ────────────────────────────────────────────────────────────── # 安装摘要 # ────────────────────────────────────────────────────────────── echo "" echo -e "${CYAN}═══════════════════════════════════════════════════${NC}" echo -e "${GREEN} ✅ WSL2 环境部署完成${NC}" echo -e "${CYAN}═══════════════════════════════════════════════════${NC}" echo -e " Node.js $(node --version 2>/dev/null || echo 未安装)" echo -e " npm $(npm --version 2>/dev/null || echo 未安装)" echo -e " Claude Code $(claude --version 2>/dev/null | head -1 || echo 未安装)" echo -e " GitHub CLI $(gh --version 2>/dev/null | head -1 || echo 未安装)" echo -e " Copilot CLI $(gh extension list 2>/dev/null | grep 'github/gh-copilot' || echo 未安装)" echo -e " Rust $(rustc --version 2>/dev/null || echo 未安装)" echo -e " rtk $(rtk --version 2>/dev/null || echo 未安装)" echo -e " MCP Server $SERVER_ENTRY" echo -e " Godot MCP ${GODOT_ENTRY:-未安装}" if [ -n "${http_proxy:-}" ]; then echo -e " Proxy $http_proxy" fi echo "" echo -e " ${YELLOW}⚠ 后续步骤(手动):${NC}" echo -e " 1. Unity Package Manager 安装插件:" echo -e " ${CYAN}https://github.com/AnkleBreaker-Studio/unity-mcp-plugin.git${NC}" echo -e " 2. 确认 Unity Bridge 在线: http://127.0.0.1:${BRIDGE_PORT}/api/ping" echo -e " 3. 重启 AI 客户端(Claude Desktop / Cursor / Windsurf)" echo -e " 4. Godot 项目:复制 addons/godot_mcp/ 到项目根目录并在 Plugins 中启用" echo -e " 插件源: ${CYAN}$SCRIPT_DIR/$GODOT_MCP_VERSION/addons/godot_mcp/${NC}" echo -e "${CYAN}═══════════════════════════════════════════════════${NC}" echo "" log "请重新加载 shell: source ~/.bashrc"