新增 claude-dev-stack 全栈一键部署脚本

WSL2 + Claude Code CLI (WSL2 原生) + Unity MCP Server (AnkleBreaker) + Rust Token Killer + v2rayN 代理自动配置

- deploy.ps1:8 步全自动部署,支持幂等重跑
- Invoke-WSL 改用 base64 编码规避 PowerShell→WSL CRLF 问题
- nvm 安装 Node.js,正确排除 Windows interop 路径
- claude mcp add --scope user 全局注册 Unity MCP
- .wslconfig networkingMode=mirrored,代理直连 127.0.0.1
- 摘要显示复用已检测变量,避免二次 WSL 调用
- .env.example 提供配置模板,.env 已加入 .gitignore

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
This commit is contained in:
2026-05-28 09:38:17 +08:00
parent 3239b90771
commit 1a29cab661
6 changed files with 1523 additions and 0 deletions

View File

@@ -0,0 +1,39 @@
# =============================================================
# Claude Dev Stack 配置文件
# 复制为 .env 并按需填写
# =============================================================
# ── Claude / Anthropic API ──────────────────────────────────
# Anthropic 官方 API Key从 https://console.anthropic.com 获取)
ANTHROPIC_API_KEY=
# API Base URL默认使用 Anthropic 官方;若使用中转代理请修改)
# 示例DeepSeek 兼容接口):
# ANTHROPIC_BASE_URL=https://api.deepseek.com/anthropic
ANTHROPIC_BASE_URL=https://api.anthropic.com
# 默认使用的模型
# 可选claude-opus-4-5 | claude-sonnet-4-5 | claude-haiku-4-5
# deepseek-v3-0324配合 DeepSeek 接口使用)
CLAUDE_MODEL=claude-opus-4-5
# ── WSL2 ────────────────────────────────────────────────────
# WSL2 发行版名称wsl --list 查看已安装发行版)
WSL_DISTRO=Ubuntu
# 设为 true 可跳过 WSL2 安装步骤(已安装时使用)
SKIP_WSL_INSTALL=false
# ── Unity MCP ───────────────────────────────────────────────
# unity-mcp-server 自动克隆至 WSL2 ~/.mcp-servers/unity-mcp-server/
# Unity Plugin 需手动通过 Package Manager 安装:
# https://github.com/AnkleBreaker-Studio/unity-mcp-plugin.git
# (无需额外配置)
# ── Rust / Token Killer ──────────────────────────────────────
# (暂无需配置,预留扩展用)
# CARGO_REGISTRY_MIRROR=https://rsproxy.cn/
# ── Docker 镜像加速(可选)──────────────────────────────────
# 若 WSL2 内需要 Docker可配置国内加速镜像逗号分隔
# DOCKER_REGISTRY_MIRRORS=https://docker.m.daocloud.io,https://hub-mirror.c.163.com

2
claude-dev-stack/.gitignore vendored Normal file
View File

@@ -0,0 +1,2 @@
.env
node_modules/

418
claude-dev-stack/README.md Normal file
View File

@@ -0,0 +1,418 @@
# Claude Dev Stack
WSL2 + Claude Code CLI + Unity MCP + Rust Token Killer **全栈一键部署方案**,适用于 Windows 11 开发环境。
支持 **v2rayN 代理自动检测**、**WSL2 代理透传**、**防火墙自动放行**。
## 组件清单
| 组件 | 说明 | 安装位置 |
|------|------|---------|
| **WSL2** | Windows Subsystem for Linux 2 | Windows 功能 |
| **Claude Code CLI** | `@anthropic-ai/claude-code` | WSL2 npm global |
| **Unity MCP Server** | `AnkleBreaker-Studio/unity-mcp-server` — Claude ↔ Unity Editor 双向集成 | WSL2 独立 Node.js 服务 + Unity Plugin |
| **Rust** | rustup stable 工具链 | WSL2 `~/.cargo` |
| **RTK** | Rust Token Killer (`rtk`) — LLM token 统计与上下文优化 | WSL2 cargo bin |
## 目录结构
```
claude-dev-stack/
├── deploy.ps1 # Windows 一键部署PowerShell 5.1+
├── wsl-setup.sh # WSL2 内部安装脚本(可单独运行)
├── .env.example # 配置模板
└── README.md
```
---
## 快速开始
### ⚠️ 重要:首次 WSL2 用户初始化
WSL2 Ubuntu **首次启动**会弹出提示创建用户:
```
Provisioning the new WSL instance Ubuntu
Create a default Unix user account: <输入你的用户名,如 hornet>
New password: <输入密码>
Retype new password: <确认密码>
```
**必须先完成用户创建,再运行 `deploy.ps1`**,否则所有文件会安装到 `root` 用户下,部署后无法正常使用。
> 💡 如果已不小心在 OOBE 前运行了脚本,再次运行 `deploy.ps1` 即可自动用正确用户重新安装。
---
### 场景一:全新 Windows 系统(首次安装 WSL2
> 需要**管理员权限**启用 WSL2 Windows 功能
```powershell
# 1. 以管理员身份运行 PowerShell
cd path\to\claude-dev-stack
# 2. 复制配置文件,填写 ANTHROPIC_API_KEY
cp .env.example .env
notepad .env
# 3. 运行部署脚本(自动检测 v2rayN 代理)
pwsh .\deploy.ps1
# 手动指定代理端口
pwsh .\deploy.ps1 -ProxyPort 10809
# 不使用代理(直连)
pwsh .\deploy.ps1 -ProxyPort -1
```
首次安装 WSL2 特性后可能需要**重启系统**,重启后重新执行脚本。
### 场景二:已有 WSL2跳过 WSL2 安装
```powershell
pwsh .\deploy.ps1 -SkipWSL
```
### 场景三:仅在 WSL2 内安装(无 Windows 脚本)
```bash
# 在 WSL2 Ubuntu 终端中执行
cp .env.example .env
nano .env
# 如需代理(端口由 deploy.ps1 写入 ~/.mcp-proxy.env或手动指定
PROXY_PORT=10809 bash wsl-setup.sh
```
---
## 代理说明v2rayN
### 自动检测顺序
1. Windows 系统代理注册表v2rayN「设为系统代理」时写入
2. v2rayN 配置文件(`%APPDATA%\v2rayN\guiNConfig.json`
3. 探测常用端口:`10809, 10808, 7890, 1080, 8080`
### WSL2 代理透传原理
```
WSL2 Ubuntu
→ [Windows 主机 vEthernet(WSL) IP]:10809
→ v2rayN
→ 互联网
```
**必须**在 v2rayN 中开启:
> 参数设置 → **「允许来自局域网的连接」** ✅
脚本自动完成:
- 写入 WSL2 `~/.mcp-proxy.env`(每次 shell 启动自动生效)
- 配置 WSL2 `git` + `npm` 代理
- `apt-get` 安装阶段同样走代理
### 代理覆盖范围
| 操作 | 是否走代理 |
|------|-----------|
| Windows `git clone` / `npm install` | ✅ |
| WSL2 `apt-get` / `git clone` / `npm install` / `cargo install` | ✅ |
| MCP Server ↔ AI 客户端stdio | ❌ 本地通信,无需代理 |
| MCP Server ↔ Unity Pluginlocalhost | ❌ 本地通信,无需代理 |
---
## 配置说明(`.env`
| 变量 | 默认值 | 说明 |
|------|--------|------|
| `ANTHROPIC_API_KEY` | _(空)_ | Anthropic API Key从 [console.anthropic.com](https://console.anthropic.com) 获取 |
| `ANTHROPIC_BASE_URL` | `https://api.anthropic.com` | API 基础 URL中转代理可改为 DeepSeek 等兼容接口 |
| `CLAUDE_MODEL` | `claude-opus-4-5` | 默认使用的模型 |
| `WSL_DISTRO` | `Ubuntu` | WSL2 发行版名称 |
| `SKIP_WSL_INSTALL` | `false` | `true` 跳过 WSL2 安装步骤 |
---
## Unity MCP 完整安装教程
> **MCP Server 仓库**[AnkleBreaker-Studio/unity-mcp-server](https://github.com/AnkleBreaker-Studio/unity-mcp-server)
> **Unity Plugin 仓库**[AnkleBreaker-Studio/unity-mcp-plugin](https://github.com/AnkleBreaker-Studio/unity-mcp-plugin)
https://github.com/AnkleBreaker-Studio/unity-mcp-plugin.git
> **要求**Unity 2021.3+ + Node.js 18+
### 架构原理
Unity MCP 由**两个独立仓库**组成,各自独立安装:
```
Claude Code CLI
│ MCP 协议 (stdio)
Node.js MCP Server ← 克隆自 unity-mcp-serverWSL2 中运行
(~/.mcp-servers/unity-mcp-server/) ← deploy.ps1 自动完成克隆 + npm install
│ WebSocket / HTTP
Unity Editor Plugin (C#) ← Package Manager 安装 unity-mcp-plugin
Unity 场景 / 对象 / 脚本 / 测试...
```
- **unity-mcp-server**:独立 Node.js MCP 服务,部署脚本自动克隆并安装依赖
- **unity-mcp-plugin**Unity C# 插件,需要手动通过 Package Manager 安装
---
### Step 1运行部署脚本自动完成服务器安装
```powershell
pwsh .\deploy.ps1
```
脚本会自动:
1. 克隆 `unity-mcp-server` 到 WSL2 `~/.mcp-servers/unity-mcp-server/`
2. 执行 `npm install` 安装依赖
3. 将 MCP Server 注册到 `%USERPROFILE%\.claude\claude_desktop_config.json`
---
### Step 2在 Unity Editor 安装 unity-mcp-plugin
1. 打开 Unity Editor
2. 菜单:**Window → Package Manager**
3. 点击左上角 **"+"** 按钮 → **"Add package from git URL..."**
4. 输入以下 URL点击 **Add**
```
https://github.com/AnkleBreaker-Studio/unity-mcp-plugin.git
```
5. 等待包下载安装完成
安装完成后,菜单栏会出现 **Tools → Unity MCP** 选项。
---
### Step 3启动 Unity Editor 端 MCP 服务
每次使用 Claude Code 操作 Unity 之前,需要先在 Unity 中启动服务:
1. 打开 Unity Editor确保项目已加载
2. 菜单:**Tools → Unity MCP → Start Server**(具体菜单项名称以插件实际为准)
3. 状态变为绿色 / Connected 表示服务就绪
---
### Step 4确认 MCP 配置
部署脚本已自动写入 Windows Claude Code 配置。确认内容:
```powershell
cat "$env:USERPROFILE\.claude\claude_desktop_config.json"
```
应包含类似如下内容:
```json
{
"mcpServers": {
"unity-mcp": {
"command": "wsl",
"args": ["-d", "Ubuntu", "--", "node", "/home/用户名/.mcp-servers/unity-mcp-server/src/index.js"]
}
}
}
```
WSL2 内的配置(`~/.config/Claude/claude_desktop_config.json`)直接使用 `node`
```json
{
"mcpServers": {
"unity-mcp": {
"command": "node",
"args": ["/home/用户名/.mcp-servers/unity-mcp-server/src/index.js"]
}
}
}
```
---
### Step 5在 Claude Code 中使用 Unity MCP
```bash
# 启动 Claude CodeWSL2 内)
claude
# 验证 Unity MCP 连接
> /mcp
# 示例:操作 Unity 场景
> 在场景中创建一个名为 Player 的空 GameObject
> 给 Player 添加 Rigidbody 组件,质量设为 5
> 创建一个新场景 Level1保存到 Assets/Scenes/
> 运行所有 EditMode 测试
```
---
### 可用 MCP 工具列表
| 工具 | 功能 |
|------|------|
| `execute_menu_item` | 执行 Unity 菜单项 |
| `select_gameobject` | 选中场景中的 GameObject |
| `update_gameobject` | 更新/创建 GameObject 属性 |
| `update_component` | 更新/添加组件 |
| `add_package` | 通过 Package Manager 安装包 |
| `run_tests` | 运行 Unity Test Runner 测试 |
| `add_asset_to_scene` | 将 Asset 添加到场景 |
| `create_prefab` | 创建 Prefab |
| `create_scene` / `load_scene` / `delete_scene` | 场景管理 |
| `get_gameobject` | 获取 GameObject 详细信息 |
| `get_console_logs` | 读取 Unity Console 日志 |
| `recompile_scripts` | 重新编译脚本 |
| `create_material` / `assign_material` | 材质管理 |
| `move/rotate/scale_gameobject` | Transform 操作 |
| `batch_execute` | 批量执行多个操作 |
---
### 防火墙放行Bridge 端口)
`deploy.ps1` 会自动创建防火墙规则,放行 TCP **78907899**Bridge Port 范围)。
手动放行:
```powershell
New-NetFirewallRule -DisplayName "Unity MCP Bridge Inbound" `
-Direction Inbound -Protocol TCP -LocalPort 7890-7899 -Action Allow -Profile Any
New-NetFirewallRule -DisplayName "Unity MCP Bridge Outbound" `
-Direction Outbound -Protocol TCP -LocalPort 7890-7899 -Action Allow -Profile Any
```
验证 Bridge 是否可用:
```powershell
# Unity Editor 打开后
Invoke-WebRequest http://127.0.0.1:7890/api/ping
# 返回 200 OK 表示 Bridge 正常
```
---
### 故障排查
**问题Unity MCP 连接失败**
```
解决:
1. 确认 Unity Editor 已打开,且 unity-mcp-plugin 已启动服务
2. 测试 Bridge: Invoke-WebRequest http://127.0.0.1:7890/api/ping
3. 确认防火墙 TCP 7890 已放行
4. 确认 MCP Server 进程正常wsl -d Ubuntu -- node ~/.mcp-servers/unity-mcp-server/src/index.js
5. 重新执行 deploy.ps1 重新克隆并注册 MCP Server
```
**问题:更新 unity-mcp-server 后连接失败**
```
解决:
wsl -d Ubuntu -- bash -c "cd ~/.mcp-servers/unity-mcp-server && git pull && npm install"
```
**问题npm install 安装超时(国内网络)**
```bash
# 方案一:通过 v2rayN 代理(推荐)
pwsh .\deploy.ps1 -ProxyPort 10809
# 方案二WSL2 内配置镜像
npm config set registry https://registry.npmmirror.com
```
**问题WSL2 无法连接 v2rayN 代理**
```
解决:
1. v2rayN → 参数设置 → 勾选「允许来自局域网的连接」
2. 确认防火墙未拦截 v2rayN 监听端口
3. WSL2 内测试curl -I https://github.com --proxy http://$(ip route | grep default | awk '{print $3}'):10809
```
**问题Unity 版本兼容性**
```
请参考 unity-mcp-plugin 仓库的 README 了解所需 Unity 最低版本要求
```
---
## 使用方式
### Claude Code CLI
```bash
# 进入 WSL2
wsl -d Ubuntu
# 启动 Claude Code
claude
# 或从 PowerShell 快捷命令(由 deploy.ps1 写入 profile
claude-wsl
```
### RTK (Rust Token Killer)
```bash
# 统计文件 token 数
rtk myfile.txt
# 从管道统计
cat large_context.md | rtk
# 分析代码文件
find . -name "*.cs" | xargs cat | rtk
```
### 故障排查 — rtk 安装失败
```bash
cargo install --git https://github.com/rtk-ai/rtk
```
---
## 环境检测逻辑
脚本对每个组件均先检测是否已安装,**已安装则跳过**,实现幂等执行:
```
WSL2 → wsl --list 检测发行版
Node.js → node --version
Claude Code → claude --version
Rust → rustc --version
rtk → rtk --version
Unity MCP → 检测 ~/.mcp-servers/unity-mcp-server/
```
---
## 故障排查
### WSL2 安装失败
```powershell
Enable-WindowsOptionalFeature -Online -FeatureName Microsoft-Windows-Subsystem-Linux -NoRestart
Enable-WindowsOptionalFeature -Online -FeatureName VirtualMachinePlatform -NoRestart
# 重启后
wsl --update
wsl --install -d Ubuntu
```
### Rust/cargo 安装慢
在 `.env` 或 `~/.bashrc` 中添加 RsProxy 镜像:
```bash
export RUSTUP_DIST_SERVER=https://rsproxy.cn
export RUSTUP_UPDATE_ROOT=https://rsproxy.cn/rustup
```

786
claude-dev-stack/deploy.ps1 Normal file
View File

@@ -0,0 +1,786 @@
#Requires -Version 5.1
<#
.SYNOPSIS
WSL2 + Claude Code CLI + Unity MCP + Rust Token Killer 全栈一键部署脚本
.DESCRIPTION
自动完成以下步骤:
0. 检测 v2rayN 代理端口,配置 Windows & WSL2 两侧代理
1. 启用 WSL2 功能 & 安装 Ubuntu 发行版
2. 检测/安装 Windows 本机 Node.jsAI 客户端 MCP 需要)
3. WSL2 内安装 Node.js LTSClaude Code CLI 需要)
4. 安装 Claude Code CLI (@anthropic-ai/claude-code)
5. 安装 Unity MCP ServerWindows + WSL2 双侧)& 写入各 AI 客户端配置
6. 配置 Windows 防火墙放行 MCP Bridge 端口
7. 安装 Rust 工具链 & Token Killer (rtk)
8. 写入 PowerShell Profile 快捷命令
.PARAMETER ProxyPort
v2rayN HTTP 代理端口0 = 自动检测;-1 = 跳过代理
.PARAMETER BridgePort
Unity MCP Bridge 端口,默认 7890
.PARAMETER InstallDir
Windows 侧 unity-mcp-server 安装目录
.PARAMETER UnityHubPath
Unity Hub 路径(写入 MCP 配置 env
.PARAMETER SkipFirewall
跳过防火墙规则配置
.PARAMETER SkipWSL
跳过 WSL2 安装步骤
.NOTES
首次安装 WSL2 需以管理员身份运行。
已有 WSL2 可普通终端运行,加 -SkipWSL 跳过 WSL2 安装检查。
#>
param(
[int] $ProxyPort = 0,
[int] $BridgePort = 7890,
[string]$InstallDir = "$env:USERPROFILE\unity-mcp-server",
[string]$UnityHubPath = "C:\Program Files\Unity Hub\Unity Hub.exe",
[switch]$SkipFirewall,
[switch]$SkipWSL
)
Set-StrictMode -Version Latest
$ErrorActionPreference = "Stop"
# ──────────────────────────────────────────────────────────────
# 颜色日志辅助
# ──────────────────────────────────────────────────────────────
function Write-Step { param($msg) Write-Host "`n==== $msg ====" -ForegroundColor Cyan }
function Write-OK { param($msg) Write-Host " [OK] $msg" -ForegroundColor Green }
function Write-Info { param($msg) Write-Host " [..] $msg" -ForegroundColor DarkGray }
function Write-Warn { param($msg) Write-Host " [!!] $msg" -ForegroundColor Yellow }
function Write-Fail { param($msg) Write-Host " [ERR] $msg" -ForegroundColor Red }
# ──────────────────────────────────────────────────────────────
# 加载 .env 配置
# ──────────────────────────────────────────────────────────────
$ScriptDir = $PSScriptRoot
$EnvFile = Join-Path $ScriptDir ".env"
$EnvExample = Join-Path $ScriptDir ".env.example"
if (-not (Test-Path $EnvFile)) {
if (Test-Path $EnvExample) {
Copy-Item $EnvExample $EnvFile
Write-Warn "已从 .env.example 创建 .env请按需编辑后重新运行"
Write-Warn " notepad $EnvFile"
exit 0
}
}
$Config = @{}
if (Test-Path $EnvFile) {
Get-Content $EnvFile | Where-Object { $_ -match "^\s*[^#].*=" } | ForEach-Object {
$parts = $_ -split "=", 2
$key = $parts[0].Trim()
$val = $parts[1].Trim().Trim('"').Trim("'")
$Config[$key] = $val
}
Write-Info "已加载配置:$EnvFile"
}
$ANTHROPIC_API_KEY = if ($Config["ANTHROPIC_API_KEY"]) { $Config["ANTHROPIC_API_KEY"] } else { "" }
$ANTHROPIC_BASE_URL = if ($Config["ANTHROPIC_BASE_URL"]) { $Config["ANTHROPIC_BASE_URL"] } else { "https://api.anthropic.com" }
$CLAUDE_MODEL = if ($Config["CLAUDE_MODEL"]) { $Config["CLAUDE_MODEL"] } else { "claude-opus-4-5" }
$WSL_DISTRO = if ($Config["WSL_DISTRO"]) { $Config["WSL_DISTRO"] } else { "Ubuntu" }
$SKIP_WSL_INSTALL = if ($Config["SKIP_WSL_INSTALL"]) { $Config["SKIP_WSL_INSTALL"] } else { "false" }
# ──────────────────────────────────────────────────────────────
# Banner
# ──────────────────────────────────────────────────────────────
Write-Host ""
Write-Host "╔══════════════════════════════════════════════════════════╗" -ForegroundColor Cyan
Write-Host "║ WSL2 + Claude Code CLI + Unity MCP + RTK 全栈部署 ║" -ForegroundColor Cyan
Write-Host "║ AnkleBreaker Unity MCP · v2rayN 代理自动检测 ║" -ForegroundColor Cyan
Write-Host "╚══════════════════════════════════════════════════════════╝" -ForegroundColor Cyan
Write-Host ""
# ══════════════════════════════════════════════════════════════
# Step 0: v2rayN 代理检测与配置
# ══════════════════════════════════════════════════════════════
Write-Step "0/8 v2rayN 代理检测"
# ── 检测代理端口 ──────────────────────────────────────────────
function Get-V2RayNProxyPort {
# 1. Windows 系统代理注册表v2rayN"设为系统代理"时写入)
try {
$ie = Get-ItemProperty `
"HKCU:\Software\Microsoft\Windows\CurrentVersion\Internet Settings" `
-ErrorAction SilentlyContinue
if ($ie.ProxyEnable -eq 1 -and $ie.ProxyServer -match ":(\d+)") {
return [int]$Matches[1]
}
} catch {}
# 2. v2rayN 配置文件
$cfgPaths = @(
"$env:APPDATA\v2rayN\guiNConfig.json",
"$env:LOCALAPPDATA\v2rayN\guiNConfig.json"
)
foreach ($p in $cfgPaths) {
if (Test-Path $p) {
try {
$j = Get-Content $p -Raw | ConvertFrom-Json
if ($j.localPort) { return [int]$j.localPort }
if ($j.httpPort) { return [int]$j.httpPort }
} catch {}
}
}
# 3. 探测常用端口
foreach ($port in @(10809, 10808, 7890, 1080, 8080)) {
try {
$tcp = New-Object System.Net.Sockets.TcpClient
$ar = $tcp.BeginConnect("127.0.0.1", $port, $null, $null)
if ($ar.AsyncWaitHandle.WaitOne(300, $false)) {
$tcp.EndConnect($ar); $tcp.Close(); return $port
}
$tcp.Close()
} catch {}
}
return 0
}
function Set-WindowsProxy {
param([int]$Port)
$url = "http://127.0.0.1:$Port"
git config --global http.proxy $url 2>$null
git config --global https.proxy $url 2>$null
& npm config set proxy $url --location global 2>$null
& npm config set https-proxy $url --location global 2>$null
$env:http_proxy = $url; $env:https_proxy = $url
$env:HTTP_PROXY = $url; $env:HTTPS_PROXY = $url
Write-OK "Windows git/npm/env 代理 -> $url"
}
function Clear-WindowsProxy {
git config --global --unset http.proxy 2>$null
git config --global --unset https.proxy 2>$null
& npm config delete proxy --location global 2>$null
& npm config delete https-proxy --location global 2>$null
"http_proxy","https_proxy","HTTP_PROXY","HTTPS_PROXY" |
ForEach-Object { Remove-Item "Env:\$_" -ErrorAction SilentlyContinue }
}
$resolvedProxyPort = 0
if ($ProxyPort -eq -1) {
Write-Warn "已跳过代理配置(-ProxyPort -1"
} elseif ($ProxyPort -gt 0) {
$resolvedProxyPort = $ProxyPort
Set-WindowsProxy -Port $resolvedProxyPort
Write-OK "使用指定代理端口: $resolvedProxyPort"
} else {
$resolvedProxyPort = Get-V2RayNProxyPort
if ($resolvedProxyPort -gt 0) {
Set-WindowsProxy -Port $resolvedProxyPort
Write-OK "自动检测到 v2rayN 代理端口: $resolvedProxyPort"
} else {
Write-Warn "未检测到活跃代理,将使用直连网络"
Write-Warn "如需代理,请指定: pwsh deploy.ps1 -ProxyPort 10809"
Write-Warn "⚠ 确保 v2rayN 已开启「允许来自局域网的连接」WSL2 需要)"
}
}
# ══════════════════════════════════════════════════════════════
# Step 1: WSL2 安装
# ══════════════════════════════════════════════════════════════
Write-Step "1/8 WSL2 环境检测 & 安装"
$distroInstalled = $false
try {
$dl = wsl --list --quiet 2>&1 | ForEach-Object { $_ -replace "`0","" }
if ($dl -match [regex]::Escape($WSL_DISTRO)) { $distroInstalled = $true }
} catch {}
if ($distroInstalled) {
Write-OK "WSL2 + $WSL_DISTRO 已安装,跳过"
} elseif ($SkipWSL -or $SKIP_WSL_INSTALL -eq "true") {
Write-Warn "跳过 WSL2 安装"
} else {
$isAdmin = ([Security.Principal.WindowsPrincipal][Security.Principal.WindowsIdentity]::GetCurrent()
).IsInRole([Security.Principal.WindowsBuiltInRole]::Administrator)
if (-not $isAdmin) {
Write-Fail "安装 WSL2 需要管理员权限,请以管理员身份运行 PowerShell"
Write-Info "如已安装 WSL2请加 -SkipWSL 参数或在 .env 设置 SKIP_WSL_INSTALL=true"
exit 1
}
$f1 = Get-WindowsOptionalFeature -Online -FeatureName "Microsoft-Windows-Subsystem-Linux" -ErrorAction SilentlyContinue
if ($f1.State -ne "Enabled") {
Enable-WindowsOptionalFeature -Online -FeatureName "Microsoft-Windows-Subsystem-Linux" -NoRestart | Out-Null
}
$f2 = Get-WindowsOptionalFeature -Online -FeatureName "VirtualMachinePlatform" -ErrorAction SilentlyContinue
if ($f2.State -ne "Enabled") {
Enable-WindowsOptionalFeature -Online -FeatureName "VirtualMachinePlatform" -NoRestart | Out-Null
}
wsl --update 2>&1 | Out-Null
wsl --set-default-version 2 2>&1 | Out-Null
wsl --install -d $WSL_DISTRO --no-launch 2>&1
if ($LASTEXITCODE -ne 0) {
Write-Fail "WSL2 安装失败,系统可能需要重启后重新运行"
exit 1
}
Write-OK "WSL2 $WSL_DISTRO 安装完成"
Write-Warn "首次安装可能需要重启,重启后重新运行脚本"
}
# ── WSL2 执行辅助函数 ─────────────────────────────────────────
function Invoke-WSL {
param([string]$Command, [switch]$IgnoreError)
if ($Command -match "`n") {
# 多行命令base64 编码后在 bash 内解码执行,完全规避 PowerShell 管道 CRLF 问题
$CleanCmd = $Command -replace "`r`n","`n" -replace "`r","`n"
$b64 = [Convert]::ToBase64String([System.Text.Encoding]::UTF8.GetBytes($CleanCmd))
$result = wsl -d $WSL_DISTRO -- bash -c "echo '$b64' | base64 -d | bash" 2>&1 |
ForEach-Object { ($_ -replace "`0","").ToString() }
} else {
$result = wsl -d $WSL_DISTRO -- bash -c $Command 2>&1 |
ForEach-Object { ($_ -replace "`0","").ToString() }
}
if ($LASTEXITCODE -ne 0 -and -not $IgnoreError) {
Write-Fail "WSL 命令失败 (exit $LASTEXITCODE)"
Write-Fail ($result -join "`n")
exit 1
}
return ($result -join "`n")
}
# 用 exit code 方式测试连通性,避免输出编码干扰
wsl -d $WSL_DISTRO -- bash -c "exit 0" 2>$null
if ($LASTEXITCODE -ne 0) {
Write-Fail "无法访问 WSL2 发行版 '$WSL_DISTRO'"
exit 1
}
Write-OK "WSL2 ($WSL_DISTRO) 连接正常"
# 获取 WSL2 默认登录用户OOBE 完成后可能是非 root
$wslUser = (wsl -d $WSL_DISTRO -- bash -c "whoami" 2>&1) -replace "`0","" |
Where-Object { $_ -match "^\w+$" } | Select-Object -First 1
if (-not $wslUser) { $wslUser = "root" }
$wslUser = $wslUser.Trim()
Write-OK "WSL2 默认用户: $wslUser"
if ($wslUser -ne "root") {
Write-Info "非 root 用户,系统级命令将通过 sudo 执行"
}
# ── Step 1a: 配置 .wslconfigmirrored 网络模式) ────────────
$wslCfgPath = "$env:USERPROFILE\.wslconfig"
$wslCfgContent = @"
[wsl2]
networkingMode=mirrored
dnsTunneling=true
firewall=true
autoProxy=true
"@
$needRestart = $false
if (Test-Path $wslCfgPath) {
$existing = Get-Content $wslCfgPath -Raw
if ($existing -notmatch "networkingMode=mirrored") {
Set-Content $wslCfgPath $wslCfgContent -Encoding UTF8
$needRestart = $true
Write-OK ".wslconfig 已更新 -> networkingMode=mirrored"
}
} else {
Set-Content $wslCfgPath $wslCfgContent -Encoding UTF8
$needRestart = $true
Write-OK ".wslconfig 已创建 -> networkingMode=mirrored"
}
if ($needRestart) {
Write-Info "重启 WSL2 以应用镜像网络模式..."
wsl --shutdown 2>$null
Start-Sleep 3
}
# ── 配置 WSL2 侧代理(持久化到 ~/.bashrc ───────────────────
if ($resolvedProxyPort -gt 0) {
Write-Info "配置 WSL2 代理(端口 $resolvedProxyPort..."
# mirrored 模式下 127.0.0.1 即 Windows直接用即可
$proxyEnvContent = "# WSL2 mirrored networking: 127.0.0.1 = Windows host`nexport http_proxy=`"http://127.0.0.1:$resolvedProxyPort`"`nexport https_proxy=`"http://127.0.0.1:$resolvedProxyPort`"`nexport HTTP_PROXY=`"http://127.0.0.1:$resolvedProxyPort`"`nexport HTTPS_PROXY=`"http://127.0.0.1:$resolvedProxyPort`"`nexport no_proxy=`"localhost,127.0.0.1,::1`"`n"
$tmpFile = "$env:TEMP\mcp-proxy.env"
$bytes = [System.Text.Encoding]::UTF8.GetBytes($proxyEnvContent)
[System.IO.File]::WriteAllBytes($tmpFile, $bytes)
Copy-Item $tmpFile "\\wsl.localhost\$WSL_DISTRO\tmp\mcp-proxy-deploy.env" -Force 2>$null
Invoke-WSL "cp /tmp/mcp-proxy-deploy.env ~/.mcp-proxy.env && chmod 644 ~/.mcp-proxy.env && echo OK" | Out-Null
# 写入 ~/.bashrc幂等
Invoke-WSL @'
if ! grep -q 'mcp-proxy.env' ~/.bashrc 2>/dev/null; then
printf '\n# Unity MCP WSL2 proxy (generated by deploy.ps1)\n[ -f ~/.mcp-proxy.env ] && . ~/.mcp-proxy.env\n' >> ~/.bashrc
fi
# 立即应用代理并配置 git
. ~/.mcp-proxy.env 2>/dev/null || true
if [ -n "$http_proxy" ]; then
git config --global http.proxy "$http_proxy"
git config --global https.proxy "$https_proxy"
echo "WSL2 git proxy set: $http_proxy"
fi
true
'@ -IgnoreError | Out-Null
Write-OK "WSL2 代理已写入 ~/.mcp-proxy.env + ~/.bashrc"
Write-Warn "⚠ 请确保 v2rayN 已开启「允许来自局域网的连接」!"
}
# ══════════════════════════════════════════════════════════════
# Step 2: Windows 本机 Node.js 检查AI 客户端 MCP 需要)
# ══════════════════════════════════════════════════════════════
Write-Step "2/8 Windows Node.js 检查"
$winNode = Get-Command node -ErrorAction SilentlyContinue
if ($winNode) {
$winNodeVer = & node --version
Write-OK "Windows Node.js 已安装: $winNodeVer"
} else {
Write-Warn "Windows 本机未检测到 Node.js"
Write-Info "尝试通过 winget 安装 Node.js LTS..."
$wingetCmd = Get-Command winget -ErrorAction SilentlyContinue
if ($wingetCmd) {
winget install --id OpenJS.NodeJS.LTS --silent --accept-source-agreements --accept-package-agreements 2>&1 | Out-Null
# 刷新 PATH
$env:Path = [System.Environment]::GetEnvironmentVariable("Path","Machine") + ";" +
[System.Environment]::GetEnvironmentVariable("Path","User")
$winNode = Get-Command node -ErrorAction SilentlyContinue
if ($winNode) {
Write-OK "Node.js 安装成功: $(& node --version)"
} else {
Write-Warn "winget 安装后未找到 node请手动安装 Node.js 18+"
Write-Warn " https://nodejs.org/zh-cn/download"
Write-Warn " 或: winget install OpenJS.NodeJS.LTS"
}
} else {
Write-Warn "未找到 winget请手动安装 Node.js 18+"
Write-Warn " https://nodejs.org/zh-cn/download"
}
}
# 对 Windows npm 设置代理
if ($resolvedProxyPort -gt 0 -and (Get-Command npm -ErrorAction SilentlyContinue)) {
$proxyUrl = "http://127.0.0.1:$resolvedProxyPort"
& npm config set proxy $proxyUrl --location global 2>$null
& npm config set https-proxy $proxyUrl --location global 2>$null
}
# ══════════════════════════════════════════════════════════════
# Step 3: WSL2 系统依赖 & Node.js
# ══════════════════════════════════════════════════════════════
Write-Step "3/8 WSL2 Node.js LTS"
# 检测 WSL2 原生 node排除通过 WSL interop 调用的 Windows node路径含 /mnt/
$nodeVer = Invoke-WSL @'
export NVM_DIR="$HOME/.nvm"
[ -s "$NVM_DIR/nvm.sh" ] && . "$NVM_DIR/nvm.sh"
_n=$(which node 2>/dev/null)
if [ -n "$_n" ] && ! echo "$_n" | grep -q '/mnt/'; then
node --version
else
echo MISSING
fi
true
'@ -IgnoreError
if ($nodeVer -match "v\d+") {
Write-OK "WSL2 Node.js 已安装 (原生): $($nodeVer.Trim())"
} else {
Write-Info "安装 WSL2 Node.js LTS (via nvm)..."
$installNodeCmd = @'
. ~/.mcp-proxy.env 2>/dev/null || true
export DEBIAN_FRONTEND=noninteractive
sudo apt-get install -y -qq curl ca-certificates 2>/dev/null
# 安装 nvm
export NVM_DIR="$HOME/.nvm"
if [ ! -d "$NVM_DIR" ]; then
curl -o- https://raw.githubusercontent.com/nvm-sh/nvm/v0.40.3/install.sh | bash
fi
[ -s "$NVM_DIR/nvm.sh" ] && . "$NVM_DIR/nvm.sh"
# 安装 Node.js LTS
nvm install --lts
nvm use --lts
# 写入 .bashrc幂等
grep -q 'NVM_DIR' ~/.bashrc || cat >> ~/.bashrc << 'NVMEOF'
# nvm (Node Version Manager)
export NVM_DIR="$HOME/.nvm"
[ -s "$NVM_DIR/nvm.sh" ] && \. "$NVM_DIR/nvm.sh"
[ -s "$NVM_DIR/bash_completion" ] && \. "$NVM_DIR/bash_completion"
NVMEOF
node --version
npm --version
true
'@
Invoke-WSL $installNodeCmd
Write-OK "WSL2 Node.js 安装完成"
}
# ══════════════════════════════════════════════════════════════
# Step 4: Claude Code CLI (WSL2)
# ══════════════════════════════════════════════════════════════
Write-Step "4/8 Claude Code CLI"
# 检测 WSL2 原生 claude排除通过 WSL interop 调用的 Windows claude.exe路径含 /mnt/
$claudeVer = Invoke-WSL @'
export NVM_DIR="$HOME/.nvm"
[ -s "$NVM_DIR/nvm.sh" ] && . "$NVM_DIR/nvm.sh"
_c=$(which claude 2>/dev/null)
if [ -n "$_c" ] && ! echo "$_c" | grep -q '/mnt/'; then
claude --version 2>/dev/null
else
echo MISSING
fi
true
'@ -IgnoreError
if ($claudeVer -notmatch "MISSING") {
Write-OK "Claude Code 已安装 (WSL2 原生): $($claudeVer.Trim())"
} else {
Write-Info "安装 @anthropic-ai/claude-code (WSL2 原生)..."
$installClaudeCmd = @'
export NVM_DIR="$HOME/.nvm"
[ -s "$NVM_DIR/nvm.sh" ] && . "$NVM_DIR/nvm.sh"
. ~/.mcp-proxy.env 2>/dev/null || true
npm install -g @anthropic-ai/claude-code --quiet
which claude
claude --version
true
'@
Invoke-WSL $installClaudeCmd
Write-OK "Claude Code 安装完成"
}
# 写入 Claude 配置WSL2 侧)
$writeClaudeSettingsCmd = @"
mkdir -p ~/.claude
cat > ~/.claude/settings.json << 'SETTINGS'
{"model": "$CLAUDE_MODEL"}
SETTINGS
true
"@
Invoke-WSL $writeClaudeSettingsCmd -IgnoreError | Out-Null
# 写入 bash 环境变量WSL2 侧)
$profBlock = ""
if ($ANTHROPIC_API_KEY) { $profBlock += "export ANTHROPIC_API_KEY='$ANTHROPIC_API_KEY'\n" }
if ($ANTHROPIC_BASE_URL) { $profBlock += "export ANTHROPIC_BASE_URL='$ANTHROPIC_BASE_URL'\n" }
$profBlock += "export CLAUDE_MODEL='$CLAUDE_MODEL'\n"
$addEnvCmd = @"
if ! grep -q 'ANTHROPIC_API_KEY' ~/.bashrc 2>/dev/null; then
printf '\n# Claude Code CLI\n$profBlock' >> ~/.bashrc
fi
"@
Invoke-WSL $addEnvCmd -IgnoreError | Out-Null
# Windows 侧 Claude 配置
$claudeDir = "$env:USERPROFILE\.claude"
if (-not (Test-Path $claudeDir)) { New-Item -ItemType Directory -Path $claudeDir | Out-Null }
@{ model = $CLAUDE_MODEL } | ConvertTo-Json | Set-Content "$claudeDir\settings.json" -Encoding UTF8
if ($ANTHROPIC_API_KEY) {
[System.Environment]::SetEnvironmentVariable("ANTHROPIC_API_KEY", $ANTHROPIC_API_KEY, "User")
[System.Environment]::SetEnvironmentVariable("ANTHROPIC_BASE_URL", $ANTHROPIC_BASE_URL, "User")
[System.Environment]::SetEnvironmentVariable("CLAUDE_MODEL", $CLAUDE_MODEL, "User")
}
Write-OK "Claude Code 配置已写入Windows + WSL2"
# ══════════════════════════════════════════════════════════════
# Step 5: Unity MCP Server 安装
# ══════════════════════════════════════════════════════════════
Write-Step "5/8 Unity MCP Server"
Write-Info "MCP Server: https://github.com/AnkleBreaker-Studio/unity-mcp-server"
Write-Info "Unity Plugin: https://github.com/AnkleBreaker-Studio/unity-mcp-plugin"
# ── 5a. Windows 侧安装(供 Claude Desktop / Cursor / Windsurf / VS Code────
Write-Info "安装 Windows 侧 MCP Server -> $InstallDir"
if (Test-Path "$InstallDir\.git") {
Write-Info "已存在,执行 git pull..."
Push-Location $InstallDir
try { git pull --quiet 2>&1 | Out-Null } finally { Pop-Location }
} else {
if (Test-Path $InstallDir) { Remove-Item $InstallDir -Recurse -Force }
git clone --quiet --depth 1 https://github.com/AnkleBreaker-Studio/unity-mcp-server.git $InstallDir
}
$winNodeExists = (Get-Command node -ErrorAction SilentlyContinue) -ne $null
if ($winNodeExists) {
Push-Location $InstallDir
try { & npm install --prefer-offline --quiet 2>&1 | Out-Null } finally { Pop-Location }
Write-OK "Windows MCP Server 就绪: $InstallDir"
} else {
Write-Warn "Windows Node.js 未就绪,跳过 Windows 侧 npm install请安装 Node.js 后重新运行)"
}
# ── 5b. WSL2 侧安装(供 Claude Code CLI─────────────────────
Write-Info "安装 WSL2 侧 MCP Server..."
$wslMcpDir = "~/.mcp-servers/unity-mcp-server"
$wslMcpCmd = @'
export NVM_DIR="$HOME/.nvm"
[ -s "$NVM_DIR/nvm.sh" ] && . "$NVM_DIR/nvm.sh"
. ~/.mcp-proxy.env 2>/dev/null || true
mkdir -p $HOME/.mcp-servers
if [ -d $HOME/.mcp-servers/unity-mcp-server/.git ]; then
git -C $HOME/.mcp-servers/unity-mcp-server pull --quiet 2>/dev/null || true
else
git clone --quiet --depth 1 https://github.com/AnkleBreaker-Studio/unity-mcp-server.git \
$HOME/.mcp-servers/unity-mcp-server 2>/dev/null \
|| git -c http.proxy="" -c https.proxy="" clone --quiet --depth 1 \
https://github.com/AnkleBreaker-Studio/unity-mcp-server.git \
$HOME/.mcp-servers/unity-mcp-server
fi
if [ -d $HOME/.mcp-servers/unity-mcp-server ]; then
cd $HOME/.mcp-servers/unity-mcp-server
# 修复可能由 root 遗留的权限问题
sudo chown -R $(whoami):$(whoami) . 2>/dev/null || true
npm install --prefer-offline --quiet 2>/dev/null || npm install --quiet
fi
echo MCP_WSL_OK
true
'@
Invoke-WSL $wslMcpCmd
$wslHome = (Invoke-WSL "echo `$HOME" -IgnoreError).Trim()
$wslScript = "$wslHome/.mcp-servers/unity-mcp-server/src/index.js"
Write-OK "WSL2 MCP Server 就绪: $wslScript"
# ── 5c. 写入各 AI 客户端 MCP 配置 ───────────────────────────
$winScript = ($InstallDir -replace "\\","/") + "/src/index.js"
$mcpEntry = @{
command = "node"
args = @($winScript)
env = @{
UNITY_HUB_PATH = $UnityHubPath
UNITY_BRIDGE_PORT = "$BridgePort"
}
}
$wslMcpEntry = @{
command = "wsl"
args = @("-d", $WSL_DISTRO, "--", "node", $wslScript)
env = @{
UNITY_BRIDGE_PORT = "$BridgePort"
}
}
function Merge-McpConfig {
param([string]$ConfigFile, [hashtable]$Entry, [string]$Label)
$dir = Split-Path $ConfigFile
if (-not (Test-Path $dir)) { New-Item -ItemType Directory -Path $dir -Force | Out-Null }
# PS5.1 兼容:手动将 PSCustomObject 转为普通 Hashtable
function ConvertTo-Hashtable($obj) {
if ($obj -is [System.Management.Automation.PSCustomObject]) {
$h = @{}
foreach ($p in $obj.PSObject.Properties) { $h[$p.Name] = ConvertTo-Hashtable $p.Value }
return $h
} elseif ($obj -is [System.Collections.IEnumerable] -and $obj -isnot [string]) {
return @($obj | ForEach-Object { ConvertTo-Hashtable $_ })
}
return $obj
}
$cfg = @{ mcpServers = @{} }
if (Test-Path $ConfigFile) {
try {
$raw = Get-Content $ConfigFile -Raw | ConvertFrom-Json
$converted = ConvertTo-Hashtable $raw
if ($converted -is [hashtable]) { $cfg = $converted }
} catch {}
}
if (-not $cfg.ContainsKey("mcpServers")) { $cfg["mcpServers"] = @{} }
$cfg["mcpServers"]["unity-mcp"] = $Entry
$cfg | ConvertTo-Json -Depth 10 | Set-Content $ConfigFile -Encoding UTF8
Write-OK "$Label -> $ConfigFile"
}
# Claude DesktopWindows
Merge-McpConfig "$env:APPDATA\Claude\claude_desktop_config.json" $mcpEntry "Claude Desktop"
# Cursor
Merge-McpConfig "$env:USERPROFILE\.cursor\mcp.json" $mcpEntry "Cursor"
# Windsurf两种路径
$windsurfDir = if (Test-Path "$env:APPDATA\Windsurf") { "$env:APPDATA\Windsurf" } else { "$env:USERPROFILE\.codeium\windsurf" }
Merge-McpConfig "$windsurfDir\mcp_config.json" $mcpEntry "Windsurf"
# VS Code
Merge-McpConfig "$env:APPDATA\Code\User\mcp.json" $mcpEntry "VS Code"
# Claude Code CLIWSL2——用 `claude mcp add --scope user` 写入全局用户级 MCP 配置
$wslMcpCfgCmd = @"
export NVM_DIR="`$HOME/.nvm"
[ -s "`$NVM_DIR/nvm.sh" ] && . "`$NVM_DIR/nvm.sh"
. ~/.mcp-proxy.env 2>/dev/null || true
#
claude mcp remove unity-mcp --scope user 2>/dev/null || true
claude mcp add --scope user unity-mcp node $wslScript -e UNITY_BRIDGE_PORT=$BridgePort
echo "Claude Code MCP configured"
true
"@
Invoke-WSL $wslMcpCfgCmd -IgnoreError | Out-Null
Write-OK "Claude Code CLI (WSL2) MCP -> unity-mcp (node $wslScript)"
# ══════════════════════════════════════════════════════════════
# Step 6: Windows 防火墙放行 MCP Bridge 端口
# ══════════════════════════════════════════════════════════════
Write-Step "6/8 Windows 防火墙规则"
if ($SkipFirewall) {
Write-Warn "已跳过防火墙配置(-SkipFirewall"
} else {
$portRange = "$BridgePort-$($BridgePort + 9)"
$isAdmin = ([Security.Principal.WindowsPrincipal][Security.Principal.WindowsIdentity]::GetCurrent()
).IsInRole([Security.Principal.WindowsBuiltInRole]::Administrator)
if ($isAdmin) {
foreach ($dir in @("Inbound","Outbound")) {
$name = "Unity MCP Bridge $dir"
Get-NetFirewallRule -DisplayName $name -ErrorAction SilentlyContinue |
Remove-NetFirewallRule -ErrorAction SilentlyContinue
New-NetFirewallRule -DisplayName $name -Direction $dir `
-LocalPort $portRange -Protocol TCP -Action Allow -Profile Any | Out-Null
Write-OK "防火墙规则: $name (TCP $portRange)"
}
} else {
Write-Warn "非管理员权限,使用提升权限添加防火墙规则..."
$cmd = @"
`$p = '$portRange'
foreach (`$d in @('Inbound','Outbound')) {
`$n = "Unity MCP Bridge `$d"
Get-NetFirewallRule -DisplayName `$n -ErrorAction SilentlyContinue | Remove-NetFirewallRule -ErrorAction SilentlyContinue
New-NetFirewallRule -DisplayName `$n -Direction `$d -LocalPort `$p -Protocol TCP -Action Allow -Profile Any | Out-Null
Write-Host "OK: `$n"
}
Start-Sleep 1
"@
$enc = [Convert]::ToBase64String([Text.Encoding]::Unicode.GetBytes($cmd))
$proc = Start-Process powershell.exe -ArgumentList "-NoProfile -EncodedCommand $enc" `
-Verb RunAs -Wait -PassThru
if ($proc.ExitCode -eq 0) {
Write-OK "防火墙规则已添加 (TCP $portRange)"
} else {
Write-Warn "防火墙配置可能未成功,请手动放行 TCP $portRange"
}
}
}
# ══════════════════════════════════════════════════════════════
# Step 7: Rust 工具链 & Token Killer (WSL2)
# ══════════════════════════════════════════════════════════════
Write-Step "7/8 Rust & Token Killer"
$rustVer = Invoke-WSL ". ~/.cargo/env 2>/dev/null; rustc --version 2>/dev/null || echo MISSING" -IgnoreError
if ($rustVer -notmatch "MISSING") {
Write-OK "Rust 已安装: $($rustVer.Trim())"
} else {
Write-Info "安装 Rust 工具链..."
$installRustCmd = @"
sudo apt-get install -y -qq build-essential pkg-config libssl-dev 2>/dev/null || true
# mirrored 127.0.0.1rustup env
. ~/.mcp-proxy.env 2>/dev/null || true
curl --proto '=https' --tlsv1.2 -sSf https://sh.rustup.rs -o /tmp/rustup-init.sh
sh /tmp/rustup-init.sh -y --default-toolchain stable --no-modify-path
source ~/.cargo/env
rustc --version
"@
Invoke-WSL $installRustCmd
Invoke-WSL "grep -q 'cargo/env' ~/.bashrc || echo 'source ~/.cargo/env 2>/dev/null || true' >> ~/.bashrc" -IgnoreError | Out-Null
Write-OK "Rust 安装完成"
}
$rtkVer = Invoke-WSL ". ~/.cargo/env 2>/dev/null; rtk --version 2>/dev/null || echo MISSING" -IgnoreError
if ($rtkVer -notmatch "MISSING") {
Write-OK "rtk 已安装: $($rtkVer.Trim())"
} else {
Write-Info "安装 rtk (Rust Token Killer)..."
$installRtkCmd = @"
. ~/.cargo/env 2>/dev/null || true
# mirrored CARGO_NET_GIT_FETCH_WITH_CLI cargo git
# git clone github git proxy https
_saved_http=`$(git config --global http.proxy 2>/dev/null)
_saved_https=`$(git config --global https.proxy 2>/dev/null)
git config --global --unset http.proxy 2>/dev/null || true
git config --global --unset https.proxy 2>/dev/null || true
CARGO_NET_GIT_FETCH_WITH_CLI=true cargo install --git https://github.com/rtk-ai/rtk 2>&1 | tail -5
[ -n "`$_saved_http" ] && git config --global http.proxy "`$_saved_http" || true
[ -n "`$_saved_https" ] && git config --global https.proxy "`$_saved_https" || true
"@
Invoke-WSL $installRtkCmd -IgnoreError
$rtkCheck = Invoke-WSL ". ~/.cargo/env 2>/dev/null; rtk --version 2>/dev/null || echo FAILED" -IgnoreError
if ($rtkCheck -match "FAILED") {
Write-Warn "rtk 安装失败,可手动运行: cargo install --git https://github.com/rtk-ai/rtk"
} else {
Write-OK "rtk 安装成功: $($rtkCheck.Trim())"
}
}
# ══════════════════════════════════════════════════════════════
# Step 8: PowerShell Profile 配置
# ══════════════════════════════════════════════════════════════
Write-Step "8/8 PowerShell Profile"
$pwshProfile = "$env:USERPROFILE\Documents\PowerShell\Microsoft.PowerShell_profile.ps1"
$profileDir = Split-Path $pwshProfile
if (-not (Test-Path $profileDir)) { New-Item -ItemType Directory -Path $profileDir | Out-Null }
$profileBlock = @"
# Claude Dev Stack (generated by deploy.ps1)
`$env:ANTHROPIC_BASE_URL = "$ANTHROPIC_BASE_URL"
$(if ($ANTHROPIC_API_KEY) { "`$env:ANTHROPIC_API_KEY = `"$ANTHROPIC_API_KEY`"" } else { "# ANTHROPIC_API_KEY= (配置 .env 后重新运行)" })
`$env:CLAUDE_MODEL = "$CLAUDE_MODEL"
function claude-wsl { wsl -d $WSL_DISTRO -- bash -ic 'claude' }
function unity-mcp-status { Invoke-RestMethod http://127.0.0.1:$BridgePort/api/ping -ErrorAction SilentlyContinue }
#
"@
$existing = if (Test-Path $pwshProfile) { Get-Content $pwshProfile -Raw } else { "" }
if ($existing -match "Claude Dev Stack") {
$existing = $existing -replace "(?ms)# ── Claude Dev Stack.*?# ─{60}", $profileBlock
Set-Content $pwshProfile $existing -Encoding UTF8
} else {
Add-Content $pwshProfile $profileBlock -Encoding UTF8
}
$policy = Get-ExecutionPolicy -Scope CurrentUser
if ($policy -in @("Restricted","Undefined")) {
Set-ExecutionPolicy -Scope CurrentUser -ExecutionPolicy RemoteSigned -Force
}
Write-OK "PowerShell Profile 已配置: $pwshProfile"
# ══════════════════════════════════════════════════════════════
# 安装总结
# ══════════════════════════════════════════════════════════════
Write-Host ""
Write-Host "╔══════════════════════════════════════════════════════════════╗" -ForegroundColor Green
Write-Host "║ ✅ 全栈部署完成 ║" -ForegroundColor Green
Write-Host "╚══════════════════════════════════════════════════════════════╝" -ForegroundColor Green
$items = @(
@("WSL2 内核", (Invoke-WSL "uname -r 2>/dev/null" -IgnoreError).Trim()),
@("Node.js (Win)", (& node --version 2>$null)),
@("Node.js (WSL)", $(if ($nodeVer -match "v\d+") { $nodeVer.Trim() } else { "" })),
@("Claude Code", $(if ($claudeVer -notmatch "MISSING") { $claudeVer.Trim() } else { "" })),
@("Rust", (Invoke-WSL ". ~/.cargo/env && rustc --version 2>/dev/null" -IgnoreError).Trim()),
@("rtk", (Invoke-WSL ". ~/.cargo/env && rtk --version 2>/dev/null" -IgnoreError).Trim()),
@("MCP Server", $InstallDir),
@("Bridge Port", "TCP $BridgePort (防火墙已放行 $BridgePort-$($BridgePort+9))")
)
foreach ($it in $items) {
Write-Host (" {0,-16} {1}" -f $it[0], $(if ($it[1]) { $it[1] } else { "(未安装)" })) -ForegroundColor White
}
if ($resolvedProxyPort -gt 0) {
Write-Host ""
Write-Host " v2rayN 代理 127.0.0.1:$resolvedProxyPort (WSL2 通过 Windows 主机 IP 访问)" -ForegroundColor DarkGray
}
Write-Host ""
Write-Host " ━━━━ 后续步骤 ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━" -ForegroundColor Cyan
Write-Host " 1) 在 Unity 项目中安装 MCP Plugin每个项目一次" -ForegroundColor White
Write-Host " Window > Package Manager > + > Add from git URL:" -ForegroundColor DarkGray
Write-Host " https://github.com/AnkleBreaker-Studio/unity-mcp-plugin.git" -ForegroundColor Yellow
Write-Host ""
Write-Host " 2) 打开 Unity 后确认 Bridge 在线(浏览器验证):" -ForegroundColor White
Write-Host " http://127.0.0.1:$BridgePort/api/ping" -ForegroundColor Yellow
Write-Host ""
Write-Host " 3) 重启 AI 客户端Claude Desktop / Cursor / Windsurf" -ForegroundColor White
Write-Host " MCP 配置已自动写入各客户端配置文件" -ForegroundColor DarkGray
Write-Host ""
Write-Host " 4) 在 Claude Code (WSL2) 中使用:" -ForegroundColor White
Write-Host " claude-wsl → claude → /mcp" -ForegroundColor DarkGray
Write-Host ""
if (-not $ANTHROPIC_API_KEY) {
Write-Warn " ⚠ 未设置 ANTHROPIC_API_KEY请编辑 .env 后重新运行"
}
Write-Host "╚══════════════════════════════════════════════════════════════╝" -ForegroundColor Green

6
claude-dev-stack/package-lock.json generated Normal file
View File

@@ -0,0 +1,6 @@
{
"name": "claude-dev-stack",
"lockfileVersion": 3,
"requires": true,
"packages": {}
}

View File

@@ -0,0 +1,272 @@
#!/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)"
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
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
# ──────────────────────────────────────────────────────────────
step "4/6 Claude Code 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
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"
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 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 " Rust $(rustc --version 2>/dev/null || echo 未安装)"
echo -e " rtk $(rtk --version 2>/dev/null || echo 未安装)"
echo -e " MCP Server $SERVER_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 "${CYAN}═══════════════════════════════════════════════════${NC}"
echo ""
log "请重新加载 shell: source ~/.bashrc"