Files
server-deploy/wsl-dev-stack/wsl-setup.sh
Joywayer dd3eb24d0f refactor: 拆分 claude-dev-stack 为 windows-dev-stack 和 wsl-dev-stack
将原 claude-dev-stack 目录拆分为独立的 Windows 和 WSL 部署栈,便于分别维护和使用。

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
2026-05-29 01:11:20 +08:00

389 lines
17 KiB
Bash
Raw Permalink Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
#!/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 wrappergh 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 installGodot 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"