Cris' Agent Lab
2026-05-12·tech

DevBox VS Code 扩展:从浏览器一键打开云端开发环境

深入分析 DevBox VS Code 扩展的工作原理——URI Handler、Remote-SSH 协议、SSH 配置管理,以及如何基于开源实现构建企业自托管版本。

VS CodeDevBoxSSHExtensionCloud IDE

背景

上篇文章 介绍了如何在企业自有 Kubernetes 集群上部署 DevBox 服务。有了 SSH Gateway 和 DevBox Pod 之后,用户要通过什么方式连接上去写代码?

答案是 VS Code Remote-SSH。VS Code 的远程开发能力允许你在一个本地窗口里编辑远程服务器上的代码,底层通过 SSH 协议通信。但原生的 Remote-SSH 需要用户手动配置 SSH config、管理密钥文件——对开发者来说,每次创建新 DevBox 都要操作一遍,体验很糟糕。

Sealos 开源了一个 VS Code 扩展(labring.devbox-aio)解决这个问题,我们基于它构建了企业自托管版本(enterprise.devbox-enterprise)。本文深入分析这两个扩展的工作原理、架构设计,以及如何实现"浏览器点一下按钮,VS Code 自动打开远程环境"的体验。

VS Code 扩展能做什么?

VS Code 扩展本质上是一个 Node.js 进程,运行在 VS Code 的 Extension Host 中。它能访问 VS Code 提供的全部 API:

  • 文件系统:读写本地文件(如 SSH config、私钥)
  • 命令系统:注册自定义命令,也可以通过 vscode.commands.executeCommand 调用内置命令
  • URI Handler:注册自定义协议处理器,响应外部 URI 调用
  • 窗口管理:打开文件夹、新建窗口、显示通知

DevBox 扩展把这几项能力组合起来,形成了完整的连接流水线。

核心架构

扩展的入口文件 extension.ts 在激活时做三件事:

export async function activate(context: vscode.ExtensionContext) {
  // 1. 注册连接命令(核心逻辑)
  const remoteConnector = new RemoteSSHConnector(context)
 
  // 2. 注册 URI 协议处理器(浏览器一键唤起)
  vscode.window.registerUriHandler({
    handleUri: (uri) => uriHandler.handle(uri),
  })
 
  // 3. 在状态栏添加快速连接按钮
  statusBarItem.command = 'devbox-enterprise.connectWithKey'
  statusBarItem.text = '$(plug) DevBox'
}

整个架构可以概括为三条路径,最终汇入同一段连接逻辑:

浏览器 vscode:// URI ──→ UriHandler ──┐
                                        │
状态栏按钮 ──→ 文件选择器交互 ──────────┼──→ RemoteSSHConnector.connectRemoteSSH()
                                        │
命令面板 (Cmd+Shift+P) ─────────────────┘

三条连接路径

路径一:URI Handler(一键连接)

这是体验最好的一条路径。用户在网页上填好连接参数,点击按钮,浏览器跳转 vscode:// 协议链接,VS Code 被唤起并自动处理:

vscode://enterprise.devbox-enterprise/connect?
  sshDomain=devbox@10.238.134.181
  &sshPort=2222
  &base64PrivateKey=LS0tLS...(PEM base64 编码)
  &sshHostLabel=my-devbox
  &workingDir=/home/devbox/project

UriHandler 收到 URI 后,解析参数并触发连接命令:

export class UriHandler {
  public handle(uri: vscode.Uri): void {
    const queryParams = new URLSearchParams(uri.query)
    const params = {
      sshDomain: queryParams.get('sshDomain') || '',
      sshPort: queryParams.get('sshPort') || '',
      base64PrivateKey: queryParams.get('base64PrivateKey') || '',
      sshHostLabel: queryParams.get('sshHostLabel') || '',
      workingDir: queryParams.get('workingDir') || '',
    }
    // 校验 → 触发命令
    vscode.commands.executeCommand('devbox-enterprise.connect', params)
  }
}

路径二:交互式连接(文件选择器)

用户从状态栏或命令面板触发,通过交互式对话框完成:

  1. 输入 DevBox 名称(用作 SSH Host 别名)
  2. 通过文件选择器选择 SSH 私钥文件
  3. 输入工作目录(可选,有默认值)
private async connectWithPrivateKeyFile() {
  const devboxName = await vscode.window.showInputBox({ ... })
  const keyFileUris = await vscode.window.showOpenDialog({ ... })
  const workingDir = await vscode.window.showInputBox({ ... })
 
  const privateKey = fs.readFileSync(keyFileUris[0].fsPath)
  await this.connectRemoteSSH({
    sshDomain: `${config.defaultUser}@${config.gatewayHost}`,
    sshPort: String(config.gatewayPort),
    base64PrivateKey: privateKey.toString('base64'),
    sshHostLabel: devboxName,
    workingDir: workingDir || config.defaultWorkingDir,
  })
}

路径三:命令面板直接调用

用户通过 Cmd+Shift+P → "DevBox: Connect to DevBox" 也可触发,参数由调用方传入(实际上被 URI Handler 路径使用)。

连接核心逻辑

三条路径最终都调用 connectRemoteSSH(),它是整个扩展的心脏:

private async connectRemoteSSH(args: {
  sshDomain: string;    // 如 devbox@10.238.134.181
  sshPort: string;      // 2222
  base64PrivateKey: string;
  sshHostLabel: string; // 如 my-devbox
  workingDir: string;   // /home/devbox/project
}) {
  // 1. 确保 Remote-SSH 扩展已安装
  await this.ensureRemoteSSHExtInstalled()
 
  // 2. 解析 sshDomain → sshUser + sshHost
  const [sshUser, ...hostParts] = sshDomain.split('@')
  const sshHost = hostParts.join('@')
 
  // 3. 标记远程平台为 Linux
  await modifiedRemoteSSHConfig(sshHostLabel)
 
  // 4. 构建 SSH 配置
  const sshConfig = SSHConfig.parse('')
  sshConfig.append({
    Host: sshHostLabel,
    HostName: sshHost,
    User: sshUser,
    Port: sshPort,
    IdentityFile: `~/.ssh/devbox-enterprise/${sshHostLabel}`,
    IdentitiesOnly: 'yes',
    StrictHostKeyChecking: 'no',
  })
 
  // 5. 预处理 → 确保 ~/.ssh/config Include 了 devbox-enterprise 配置
  this.sshConfigPreProcess()
 
  // 6. 写入 SSH config(去重写入 ~/.ssh/devbox-enterprise/config)
  // 7. 写入私钥文件(~/.ssh/devbox-enterprise/<hostname>,chmod 600)
  // 8. 打开远程窗口
  await vscode.commands.executeCommand('vscode.openFolder',
    vscode.Uri.parse(`vscode-remote://ssh-remote+${sshHostLabel}${workingDir}`),
    { forceNewWindow: true }
  )
}

SSH 配置管理

双文件架构

扩展采用双文件架构来隔离 DevBox 的 SSH 配置和用户自己的 SSH 配置:

~/.ssh/
├── config                          # 用户主配置,Include 引入 DevBox 配置
│   Include ~/.ssh/devbox-enterprise/config
│
├── devbox-enterprise/              # DevBox 专用目录
│   ├── config                      # DevBox SSH Host 条目
│   │   Host my-devbox
│   │     HostName 10.238.134.181
│   │     User devbox
│   │     Port 2222
│   │     IdentityFile ~/.ssh/devbox-enterprise/my-devbox
│   │     IdentitiesOnly yes
│   │     StrictHostKeyChecking no
│   │
│   └── my-devbox                   # 私钥文件(chmod 600)

配置预处理

sshConfigPreProcess() 负责确保环境就绪:

  1. 确保 ~/.ssh/config 存在
  2. 确保 ~/.ssh/devbox-enterprise/config 存在
  3. 在用户主配置中注入 Include ~/.ssh/devbox-enterprise/config(如果还没有)
  4. 清理旧格式的绝对路径 Include
  5. 调用 convertSSHConfigToVersion2() 迁移旧版注释格式

去重写入

每次连接前,扩展会扫描现有的 SSH config,找到同名 Host 块并替换,避免重复条目积累:

for (let i = 0; i < existingLines.length; i++) {
  const line = existingLines[i].trim()
  if (line.startsWith('Host ') && line.substring(5).trim().startsWith(sshHostLabel)) {
    skip = true    // 跳过旧的同名 Host 块
    continue
  }
  // ...
}

跨平台权限处理

私钥文件权限对 SSH 来说是严格的——权限过于开放会导致 SSH 拒绝连接("Permissions are too open")。

export const ensureFileAccessPermission = async (filePath: string) => {
  if (os.platform() === 'win32') {
    // Windows: icacls 限制当前用户
    await execa('icacls', [filePath, '/inheritance:d'])
    await execa('icacls', [filePath, '/remove:g', 'everyone'])
  } else {
    // macOS/Linux: chmod 600
    await execa('chmod', ['600', filePath])
  }
}

Remote-SSH 扩展检测

扩展的核心依赖是 Remote-SSH——它负责建立 SSH 连接和远程文件系统挂载。但不同的编辑器有不同的 Remote-SSH 实现:

| 编辑器 | Remote-SSH 扩展 ID | |--------|-------------------| | VS Code | ms-vscode-remote.remote-ssh | | Cursor | anysphere.remote-ssh | | Kiro | jeanp413.open-remote-ssh | | Windsurf / Trae / CodeBuddy | 内置(无需安装) |

扩展在连接前会检测当前运行的编辑器类型,然后检查对应的 Remote-SSH 是否可用:

private async ensureRemoteSSHExtInstalled(): Promise<boolean> {
  // 内置支持(Windsurf, Trae, CodeBuddy 等)→ 直接返回
  const hasBuiltin = [
    'windsurf', 'trae', 'trae-cn', 'codebuddy', 'codebuddycn', 'antigravity'
  ].includes(vscode.env.uriScheme)
  if (hasBuiltin) return true
 
  // 检查已安装的扩展
  const hasMsExt = vscode.extensions.getExtension('ms-vscode-remote.remote-ssh')
  if (hasMsExt) return true
 
  // 未安装 → 弹窗引导安装
  const action = await vscode.window.showInformationMessage(
    'Remote - SSH extension is required to connect to DevBox. Install it now?',
    { modal: true }, 'Install'
  )
  if (action === 'Install') {
    await vscode.commands.executeCommand('extension.open', msRemoteSSHId)
  }
}

远程窗口的打开方式

VS Code 的远程窗口使用特殊的 URI scheme:vscode-remote://。格式为:

vscode-remote://ssh-remote+<SSH_HOST_ALIAS><REMOTE_PATH>

例如:

vscode-remote://ssh-remote+my-devbox/home/devbox/project

扩展调用内置命令 vscode.openFolder 并强制新窗口:

await vscode.commands.executeCommand(
  'vscode.openFolder',
  vscode.Uri.parse(`vscode-remote://ssh-remote+${sshHostLabel}${workingDir}`),
  { forceNewWindow: true }
)

这背后的流程是:

  1. VS Code 接收 openFolder 命令
  2. 识别出是 ssh-remote 协议
  3. 调用 Remote-SSH 扩展
  4. Remote-SSH 读取 ~/.ssh/devbox-enterprise/config 中的 Host 配置
  5. 使用指定的 IdentityFile(私钥)建立 SSH 连接
  6. 在远程服务器上安装 VS Code Server
  7. 在新窗口中展示远程文件系统

前端 Demo 页面

为了让非技术用户也能使用,我们做了一个独立的 HTML 页面(devbox-demo.html):

<!-- 核心按钮逻辑 -->
<button onclick="openInVSCode()">Open in VS Code</button>
 
<script>
function openInVSCode() {
  const params = new URLSearchParams({
    sshDomain: `devbox@${gatewayHost}`,
    sshPort: gatewayPort,
    base64PrivateKey: btoa(privateKey),   // Base64 编码私钥
    sshHostLabel: devboxName,
    workingDir: workingDir
  })
 
  const uri = `vscode://enterprise.devbox-enterprise/connect?${params}`
 
  // 检测 VS Code 是否被唤起
  let vsOpened = false
  window.addEventListener('blur', () => { vsOpened = true }, { once: true })
  window.location.href = uri
 
  // 2.5 秒后如果浏览器没有失焦,说明 VS Code 没打开
  setTimeout(() => {
    if (!vsOpened) showInstallModal()  // 显示安装引导
  }, 2500)
}
</script>

检测机制: 无法直接从网页检测扩展是否已安装。采用的启发式方法是检测浏览器 blur 事件——如果 vscode:// URI 成功唤起 VS Code,浏览器窗口会失去焦点。2.5 秒后如果焦点还在,显示安装引导弹窗。

企业版 vs 开源版

企业版与 Sealos 开源版(labring.devbox-aio)的核心差异:

| | Sealos 开源版 | 企业版 | |---|---|---| | SSH 配置目录 | ~/.ssh/sealos/devbox_config | ~/.ssh/devbox-enterprise/config | | URI Scheme | vscode://labring.devbox-aio/connect | vscode://enterprise.devbox-enterprise/connect | | Gateway 配置 | 从 Sealos Cloud 动态获取 | 通过 VS Code 设置预配置 | | 密钥管理 | Sealos Cloud 托管 | 用户自行管理(文件或粘贴) | | 多集群支持 | Cloud 内置 | 单个自托管集群 |

两者可以共存安装——它们使用不同的 SSH 配置目录和 URI scheme,互不干扰。

工程要点

extensionKind.UI

{
  "extensionKind": ["ui"]
}

这个配置很关键。VS Code 扩展有三种运行模式:

  • UI:运行在本地 VS Code 窗口进程
  • Workspace:运行在远程窗口进程
  • Web:运行在浏览器环境

DevBox 扩展需要操作本地文件系统(写 SSH config、私钥),必须声明为 UI。如果声明为 Workspace,在远程窗口激活时无法访问本地 ~/.ssh/

activationEvents

{
  "activationEvents": ["onStartupFinished", "onUri"]
}
  • onStartupFinished:确保状态栏按钮在 VS Code 启动后立即可用
  • onUri:在扩展尚未激活时,收到 vscode:// URI 能自动唤起扩展

Webpack 打包

扩展使用 Webpack 将 TypeScript 源码和依赖打包为单个 dist/extension.js

module.exports = {
  target: 'node',
  entry: './src/extension.ts',
  output: { libraryTarget: 'commonjs2' },
  externals: { vscode: 'commonjs vscode' },  // 不打包 vscode API
}

vscode 模块声明为 external——它由 VS Code 运行时提供,不需要打包进去。

安装与使用

前提条件

  • VS Code / Cursor / Windsurf 编辑器
  • Remote-SSH 扩展(VS Code 内建的已包含)
  • 企业 SSH Gateway 网络可达
  • 有效的 SSH 私钥

安装扩展

# 方式一:命令行安装 VSIX
code --install-extension devbox-enterprise-0.1.0.vsix
 
# 方式二:VS Code UI
# Cmd+Shift+P → "Extensions: Install from VSIX..." → 选择 .vsix 文件

使用方式

一键连接(推荐):

  1. 浏览器打开 devbox-demo.html
  2. 填写:DevBox 名称、Gateway IP、端口、私钥、工作目录
  3. 点击 "Open in VS Code"
  4. VS Code 自动打开远程窗口

交互式连接:

  1. VS Code 状态栏点击 "DevBox" 按钮
  2. Cmd+Shift+P → "DevBox: Connect with Private Key File"
  3. 按提示选择名称 → 私钥文件 → 工作目录

常见问题

| 问题 | 原因 | 解决 | |------|------|------| | 点击按钮无反应 | 扩展未安装 | code --install-extension 安装 .vsix | | Permission denied (publickey) | 私钥不匹配或格式损坏 | 检查私钥与 DevBox Secret 一致;确认私钥文件尾部有 \n | | Load key: invalid format | PEM 文件末尾缺少换行符 | .trim() 会去掉换行符,扩展 v0.1.0 已加防御性补 \n | | "Permissions are too open" | 私钥权限不是 600 | chmod 600 ~/.ssh/devbox-enterprise/<name> | | SSH 连接超时 | Gateway 不可达 | 检查 CLB VIP 是否能 ping 通 || "Permission denied" | 私钥不匹配 | 确认私钥与 DevBox 的公钥是一对 |

未来规划

当前版本覆盖了单集群场景的基础连接流程。后续可能扩展的方向:

  • 多集群管理:在配置中添加多个 Gateway 地址,连接时选择目标集群
  • DevBox 生命周期管理:在 VS Code 内创建 / 销毁 DevBox,查看状态
  • Web 面板:通过 VS Code Webview 内嵌管理面板,替代外部 HTML 页面
  • 自动密钥生成:对接企业内部 CA,自动签发短期 SSH 证书

总结

DevBox VS Code 扩展的核心思路并不复杂:生成 SSH 配置 + 写入密钥 + 打开远程窗口。但要做好体验,需要处理很多细节:URI 协议的参数编码、SSH config 的去重写入、跨平台的文件权限、多编辑器的 Remote-SSH 检测、前端页面的安装检测启发式。

通过这套机制,开发者从"拿到一个 DevBox 到开始写代码"的时间从几分钟缩短到了几秒钟——填表、点按钮、自动连接。