Skip to content

feat: VS Code Remote SSH via SSH ProxyCommand #1

Description

@qiaolei1973

Problem Statement

用户通过 talos up 可以 SSH 连接到 K8s sandbox pod,但连接是临时的——CLI 进程退出后 WebSocket relay 随即销毁。VS Code Remote-SSH 扩展需要一个持久可用的 SSH 连接机制。当前文档建议手动 kubectl port-forward,但这要求用户有集群访问权限,在 ECS/远程 k3s 场景下不可行。

Solution

新增 talosd ssh-proxy 子命令,作为 SSH ProxyCommand 使用。每次 SSH 或 VS Code 发起连接时,自动建立 WebSocket relay 到 sandbox pod,无需后台常驻进程。

用户执行 talos up 创建/唤醒 sandbox 后,SSH config 中会写入 ProxyCommand 指令。之后:

  • 终端 ssh tt-default 直接连入
  • VS Code Remote-SSH 选择 tt-default host 即可连接
  • Sandbox 休眠后重连会自动唤醒

User Stories

  1. As a developer, I want to run talos up and then connect via VS Code Remote-SSH, so that I get full IDE experience in my sandbox
  2. As a developer, I want ssh tt-{project} to work directly from terminal without running talos up again, so that I can quickly reconnect
  3. As a developer, I want the SSH relay to be established on-demand without a background daemon, so that I don't need to manage long-running processes
  4. As a developer, I want VS Code to automatically reconnect when my sandbox wakes from sleep, so that I don't lose my workflow
  5. As a developer, I want meaningful error messages in VS Code output panel when connection fails, so that I can troubleshoot issues
  6. As a developer, I want to open multiple VS Code windows to the same sandbox simultaneously, so that I can work on different files in parallel
  7. As a developer, I want talos up to still drop me into an SSH session immediately, so that my existing workflow is preserved
  8. As a developer, I want no changes required on the server side for this feature, so that deployment is minimal
  9. As a developer, I want the SSH config to use a stable alias (not a random port), so that VS Code can reliably discover and cache the host
  10. As a developer, I want the ProxyCommand binary path to be resolved automatically, so that the SSH config works regardless of how talosd was installed

Implementation Decisions

SSH ProxyCommand 模式

SSH config 格式改为:

Host tt-{project}
  User coder
  StrictHostKeyChecking no
  UserKnownHostsFile /dev/null
  IdentityFile ~/.ssh/id_ed25519
  ProxyCommand {resolvedBinaryPath} ssh-proxy --project {project}

每次 SSH/VS Code 连接时,SSH 客户端自动 spawn talosd ssh-proxy 子进程,通过 stdin/stdout 传输 SSH 协议数据,子进程内部建立 WebSocket relay 到 portal。无需本地 TCP server,无需后台守护进程。

新增 ssh-proxy 子命令

cli/src/commands/ssh-proxy.ts — 核心流程:

  1. 读 config(token, serverUrl),无 token 则 stderr 报错退出
  2. GET /api/sandboxes?project={name} 解析 sandbox
  3. 若 sandbox 不存在 → stderr 提示先 talos up,exit 1
  4. 若 sleeping → POST /api/sandboxes/{id}/wake(已有同步接口,等待 ready 后返回)
  5. 建立 WebSocket 到 /api/sandboxes/{id}/ssh?token=...
  6. process.stdin → WebSocket,WebSocket → process.stdout
  7. stderr 用于状态信息(VS Code 在输出面板显示)
  8. WebSocket 连接前缓冲 stdin 数据(同现有 relay 模式)
  9. 若 WS 被 4004(sleeping)拒绝,自动 retry 一次 wake + reconnect
  10. 处理 SIGTERM/SIGINT 优雅退出

stdin raw mode 注意事项

ProxyCommand 模式下 SSH 客户端作为父进程,stdin 不是 TTY。process.stdin.setRawMode() 可能抛错。需要 try-catch 或判断 process.stdin.isTTY。实际上 ProxyCommand 模式下不需要 setRawMode——SSH 协议数据已经是二进制帧,直接 pipe stdin 即可。

二进制路径解析

resolveTalosBinaryPath() 函数确定 talosd 的绝对路径:

  1. 优先 which talosd(生产环境全局安装)
  2. 回退 process.argv[1] 解析为绝对路径
  3. 开发模式回退 npx tsx {scriptPath}

路径在 talos up 时解析并写入 SSH config,后续 SSH/VS Code 直接使用。

重构 updateSshConfig

去掉 port 参数,改写 ProxyCommand 格式。Host tt-{project} 块的替换逻辑保持不变(正则匹配 + 替换或追加)。

简化 up 命令

  • 删除 establishSshRelay() 调用、let port / cleanup 变量、finally { cleanup() }
  • updateSshConfig(project) 无需 port 参数
  • sshIntoSandbox(project) 改为 spawn("ssh", ["tt-{project}"]) 用 host alias
  • 保留 ensureSshKey()uploadSshKey() 不变

服务端无需修改

  • GET /api/sandboxes?project= 已有
  • POST /api/sandboxes/:id/wake 已有且同步等待 ready
  • WebSocket relay 已支持多连接,sleeping 时返回 4004

保留旧函数

establishSshRelay() 保留在代码中但不再被 up 调用,作为 fallback 或未来用途。

Testing Decisions

测试缝隙(Test Seams)

主要在 CLI 层面测试,使用最高可用缝隙:

  1. SSH config 生成:单元测试 updateSshConfig() — 验证 ProxyCommand 格式正确、已有条目替换、新增条目追加
  2. 二进制路径解析:单元测试 resolveTalosBinaryPath() — mock execSync 验证优先级链
  3. ssh-proxy 命令集成:通过 spawn talosd ssh-proxy 验证 stdin/stdout pipe 行为(mock WebSocket server)
  4. 端到端talos upssh tt-default → 验证连接;VS Code Remote-SSH 连接验证

Good Test Criteria

  • 只测外部行为(SSH config 输出格式、连接成功/失败),不测内部实现
  • SSH config 测试验证:正确的 Host alias、ProxyCommand 包含完整路径、User/IdentityFile 字段正确

Out of Scope

  • SSH multiplexing (ControlMaster) 配置优化 — 当前 SSH 默认行为已足够
  • Workspace image 修改 — 已满足 VS Code Server 的 glibc/shell/curl 要求
  • code-server(浏览器 IDE)支持 — 未来可扩展
  • DevContainer spec 支持 — 未来可扩展
  • Portal / Sandbox Manager 服务端修改
  • Windows 支持(当前仅 macOS/Linux)

Further Notes

DevPod 参考

DevPod (loft-sh) 采用类似方案:SSH 作为主要集成方式,通过 ProxyCommand 按需建立连接。不同之处在于 DevPod 直接管理 Docker 容器,而我们有 K8s sandbox 这一层抽象,需要通过 WebSocket relay 穿透 Portal → Sandbox Manager → Pod。

VS Code Server 安装

首次连接时 VS Code Remote-SSH 会自动在 sandbox 内安装 vscode-server(约 300MB)。Workspace image 已包含 prerequisites(Ubuntu 22.04, glibc 2.35, bash, curl, tar)。安装后的 vscode-server 存储在 /home/coder/.vscode-server,通过 PVC 持久化。

Sleeping Sandbox 恢复时间

Sandbox 从 sleeping 恢复需要约 30-60 秒(pod 重建 + env 注入)。ssh-proxy 会在 stderr 输出 "Sandbox is sleeping, waking..." 进度,VS Code 输出面板可见。

Metadata

Metadata

Assignees

No one assigned

    Labels

    enhancementNew feature or requestready-for-agentPRD ready for autonomous agent implementation

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions