关于 exe-dev
通过 SSH、SSH API 和 HTTPS API 管理 exe.dev Linux VM 的技能。内容涵盖连接方式、VM 访问,以及供脚本、Agent 或 CI 作业使用的受限签名令牌。
主要功能
- 01通过三种文档接口管理 VM
- 02使用 SSH 交互式访问 VM
- 03通过 SSH API 或 HTTPS API 自动执行命令
- 04创建受限且有时效的 Bearer 令牌
- 05通过 SSH 端点连接 VM
适用场景
- 01通过 SSH 交互式管理 VM
- 02在脚本或 CI 中自动执行 VM 命令
- 03用受限令牌限制 Agent 权限
MCP 相关
README 提及 MCP OAuth 流程,并建议使用 `ssh .exe.xyz` 建立隧道;未说明 MCP 服务或 MCP 工具。
安装
复制下面的命令,在终端中运行。
npx skills add https:/ / github.com/ kylelundstedt/ dotfiles --skill exe-dev安装后在 Claude Code 中输入/exe-dev确认已加载。
详细介绍
exe.dev
exe.dev 提供带持久化磁盘、即时 HTTPS 和内置身份验证的 Linux VM。
文档
- 文档索引:https://exe.dev/docs.md
- 所有文档合并为一个页面(很长!):https://exe.dev/docs/all.md
- HTTPS API 参考:https://exe.dev/docs/https-api.md
- HTTPS API 介绍(博客):https://blog.exe.dev/apis-for-the-restless
三种接口
exe.dev 官方文档将三种同等的 VM 管理接口列出:SSH、SSH API(程序化 SSH)和 HTTPS API。它们使用完全相同的命令语法——HTTPS API 就是“塞进 POST 请求体里的 SSH API”。官方没有将任何一种标为首选;请根据上下文选择。
| 接口 | 使用场景 |
|---|---|
| SSH | 交互式大厅操作;熟悉的类 Unix 体验 |
| SSH API | 已加载 SSH Agent 的脚本 |
| HTTPS API | 限定范围/有时效的令牌;出站端口 22 被阻止的环境;强化自动化安全性 |
连接速率限制
当超过每个源 IP 的连接速率限制(确切阈值未公开)时,exe.dev 大厅和直接 VM SSH(*.exe.xyz)都会静默丢弃入站 TCP SYN。2026-04-21 的确认仅针对大厅——5 次突发 ssh exe.dev 调用中有 5 次超时。2026-04-23 的复测扩展了这一发现:在 VM 引导期间突发发起新的 ssh <vm>.exe.xyz 调用,在 VM 端点重现了持续数分钟的 22 端口阻断。这些端点共用某种正在生效的 SYN 丢弃防护。
避免触发此限制:
- 为
Host exe.dev *.exe.xyz启用 SSH 多路复用(见下文“SSH 配置”)——一个持久连接即可承载多个命令,并保持在阈值以下。这是交互式和脚本化使用中最重要的配置。 - 对于需要执行大量大厅命令的脚本或 Agent,请使用 HTTPS API。其速率限制按 SSH 密钥计算(有文档说明),而不是静默的按 IP 阻断。
- VM 上启用 Tailscale 后,后续访问 VM 时优先使用 Tailscale SSH(见下文“设置开发 VM”)。Tailnet 流量使用 WireGuard,而非 exe.dev 的边缘网络,因此完全绕过速率限制。
直接访问 VM(仅限 SSH)
ssh <vm>.exe.xyz # shell
scp file.txt <vm>.exe.xyz:~/ # transfer file
每个 VM 都会获得一个带自动 TLS 的 https://<vm>.exe.xyz/。
HTTPS API 和限定范围的令牌
HTTPS API 的显著特点是使用 SSH 签名的持有者令牌,并具有限定范围的权限——适合将有限权限授予 Agent、脚本或 CI 任务,而无需交出完整的 SSH 密钥。
令牌格式:exe0.<base64url-payload>.<base64url-signature>。负载是经过签名的 JSON,包含四个字段:
| 字段 | 用途 |
|---|---|
cmds | 明确的命令允许列表。父级命令不会授权子命令(ssh-key ≠ ssh-key list) |
exp | Unix 过期时间戳。文档“强烈建议始终设置 exp”,尽管默认值为永不过期 |
nbf | 生效时间戳(用于定时令牌) |
ctx | 任意签名 JSON,通过 X-ExeDev-Token-Ctx 传递给 VM;应用级授权数据 |
速率限制按 SSH 密钥计算——独立工作负载请使用不同的密钥。不提供重放保护,因此请使用短时有效的令牌。最大 8 KB。
创建令牌
PERMS='{"cmds":["ls","new","rm","whoami"],"exp":1800000000}'
PAYLOAD=$(printf '%s' "$PERMS" | base64 | tr -d '\n=' | tr '+/' '-_')
SIG=$(printf '%s' "$PERMS" | ssh-keygen -Y sign -f ~/.ssh/exe_dev.pub -n [email protected] 2>/dev/null | sed '1d;$d' | tr -d '\n' | tr '+/' '-_' | tr -d '=')
TOKEN="exe0.$PAYLOAD.$SIG"
ssh-keygen -Y sign 可与 1Password 的 SSH Agent 配合使用——传入公钥文件,由 Agent 负责签名。
使用令牌
curl -s -X POST https://exe.dev/exec -H "Authorization: Bearer $TOKEN" -d "ls"
curl -s -X POST https://exe.dev/exec -H "Authorization: Bearer $TOKEN" -d "new --name myvm --image ubuntu:24.04"
curl -s -X POST https://exe.dev/exec -H "Authorization: Bearer $TOKEN" -d "rm myvm"
响应始终为 JSON。
VM 默认设置
- **镜像:**默认使用
boldsoftware/exeuntu—ssh exe.dev new(不带--image)会创建 exeuntu VM。如需纯 Ubuntu,请使用--image=ubuntu:24.04。exeuntu 是带有 Bold 覆盖层的 Ubuntu 24.04(Shelley/Pi Agent 技术栈位于~/.config/shelley/和~/.pi/,~/.zed_server/已预先暂存,apt 列表包含各种软件包,其中包括 Python 构建依赖项;在纯 Ubuntu 上,这些依赖项曾导致uv tool install snowflake-cli失败)。 - **默认用户:**视镜像而定。
- exeuntu:
exedev(uid 1000,属于sudo和docker组,sudo 无需密码)。$HOME=/home/exedev。采用标准的非 root 开发模式。 - ubuntu:24.04:
root,未安装 sudo。要创建非 root 用户:ssh <vm>.exe.xyz "apt-get update -qq && apt-get install -y -qq sudo && useradd -m -s /bin/bash myuser && echo 'myuser ALL=(ALL) NOPASSWD:ALL' > /etc/sudoers.d/myuser"。
- exeuntu:
- exeuntu 上预先存在的
~/.claude/CLAUDE.md和~/.codex/AGENTS.md是指向~/.config/shelley/AGENTS.md的绝对符号链接。dotfiles 的install.sh会检测到它们,并在暂存agents软件包前备份(文件名为*.pre-dotfiles.<timestamp>)。Bold 的底层文件~/.config/shelley/AGENTS.md会保留。
VM 命名规则
- 名称不能以
-<digits>结尾(例如,test-123会被拒绝,test-abc可用) - 名称的其他位置可以使用连字符
- 名称会成为子域名:
<name>.exe.xyz
SSH 配置
必须固定 exe.dev 密钥(以避免 1Password 的 Agent 提供其他密钥),并为 lobby 和直接 VM 主机启用连接复用(以避免上文所述的速率限制封锁):
Host exe.dev *.exe.xyz
IdentitiesOnly yes
IdentityFile ~/.ssh/exe_dev.pub
ControlMaster auto
ControlPath ~/.ssh/sockets/%r@%h-%p
ControlPersist 600
私钥存储在 1Password 的 Employee 保管库中(“SSH Key - exe.dev”)。磁盘上只有公钥,路径为 ~/.ssh/exe_dev.pub。
install.sh(macOS 分支)会添加以下相关配置段:
Host *.exe.xyz
User exedev
LocalForward 8765 localhost:8765
Match host *.ts.net exec "$HOME/.local/bin/ssh-tailnet-tagged %h tag:dev"
User exedev
IdentitiesOnly yes
IdentityFile ~/.ssh/exe_dev.pub
StrictHostKeyChecking no
UserKnownHostsFile /dev/null
LogLevel ERROR
Host *.ts.net
ForwardAgent yes
*.exe.xyz 配置段始终生效 — 这是 lobby 路径。Match host *.ts.net exec 配置段是动态部分:它会运行 ssh-tailnet-tagged(install.sh 写入 ~/.local/bin 的小型辅助工具),查询 tailscale status --json,并且仅当规范化后的主机名对应的节点带有 tag:dev 时才以 0 退出。因此,新建并完成引导的 exe.dev VM 一加入 tailnet,ssh <new-vm> 就能立即使用 — 无需为每台 VM 添加 ssh_config 条目,也无需在其他每台机器上重新运行 install.sh。Mac 和其他 tailnet 节点(没有 tag:dev)会落到普通的 Host *.ts.net 配置段(仅设置 ForwardAgent yes)— 这就是为什么在所有 *.ts.net 主机上不加区分地设置 User exedev 是错误的(它会尝试以 exedev 身份登录 Mac)。
对匹配的主机会禁用主机密钥检查,因为 WireGuard 已在网络层验证节点身份 — 在 tailnet 上,普通 SSH 主机密钥没有额外作用,而且每次重建 VM 都会轮换。
LocalForward 8765 仅适用于 *.exe.xyz 形式 — 刻意不包含 Tailscale 名称,以免日常 ssh <vm> 连接争抢端口 8765。Zed 的远程服务 SSH 会保持持久连接;如果 Tailscale 模式也启用了 LocalForward,那么之后每次终端执行 ssh <vm> 都会记录 bind: Address already in use,且隧道会失效。
对于 MCP OAuth 流程,请使用 ssh <vm>.exe.xyz — 这样才能建立隧道。日常工作使用 ssh <vm>(Tailscale),不会受到影响。
在脚本和 Agent 中工作
- 用于大厅自动化时,HTTPS API 通常更顺手——你可以限定令牌的
cmds,让 Agent 仅拥有所需权限。 - SSH 多路复用配置在
~/.ssh/config中(由install.sh添加),因此重复调用ssh <vm>.exe.xyz会复用同一个 TCP 连接。不要使用每次调用单独设置的ControlPath标志覆盖它——这会分散套接字池,削弱速率限制缓解效果。 - 首次连接时以非交互方式接受新的主机密钥:
-o StrictHostKeyChecking=accept-new。 - **连接超时:**对 VM SSH 使用
-o ConnectTimeout=30——新 VM 需要几秒钟才能变得可连接。 - **Tailscale 启动后,后续工作都切换到 Tailscale SSH。**参见下文的“SSH 端点”。
SSH 端点——何时使用哪个
安装 dotfiles 后,运行中的 VM 可通过两个 SSH 端点访问:
| 端点 | 使用时机 |
|---|---|
ssh <vm>.exe.xyz (exe.dev) | **仅用于引导。**Tailscale 启动前必须使用(curl 安装和 install.sh 阶段)。如果 VM 上的 Tailscale 出现故障,或使用未加入 tailnet 的 Mac,也可将其用作备用方案。 |
ssh <vm> / ssh <vm>.dojo-sun.ts.net | **引导后的所有工作默认使用此端点。**转发 1Password SSH Agent(克隆私有仓库、推送、签名——VM 上无需令牌),绕过 exe.dev 的逐 IP 速率限制,重建 VM 时不会发生 SSH 主机密钥变更(Tailscale 通过 WireGuard 而非 OpenSSH 主机密钥处理身份验证),并且与 Apple Containers 和 Sprites 使用相同模式,便于跨平台养成习惯。 |
**经验法则:**如果 VM 上的 Tailscale 已启动,优先使用 ssh <vm>。仅在引导期间和紧急情况下使用 ssh <vm>.exe.xyz。
ssh <vm> 如何知道使用 User exedev:install.sh 会在 ~/.ssh/config 中写入一个 Match host *.ts.net exec "..." 块,在连接时运行 ~/.local/bin/ssh-tailnet-tagged。此辅助程序会查询 tailscale status --json,并且仅当对端带有 tag:dev 时才以状态码 0 退出。因此,新 exe.dev VM 加入 tailnet 后即可立即使用——无需逐台配置 VM,也无需在添加 VM 时于其他每台机器上重新运行 install.sh。Mac 和其他 tailnet 对端(没有 tag:dev)则会回退到普通的 Host *.ts.net 块(仅设置 ForwardAgent yes)。
VM 到 VM 的访问
exe.dev VM 提供 /dev/net/tun 和 CAP_NET_ADMIN,因此 install.sh 会以内核模式运行 tailscaled——使用真实的 tailscale0 接口、针对 100.0.0.0/8 的内核路由,以及接入系统解析器的 MagicDNS。VM 之间可以像从 Mac 访问时一样,直接使用 ssh <vm>。
# from inside any exe.dev VM (use the username that matches the destination's image)
ssh exedev@gitlake 'cd ~/dotfiles && git pull && ./install.sh' # exeuntu
ssh root@gitlake 'cd ~/dotfiles && git pull && ./install.sh' # ubuntu:24.04
这要求 Tailscale ACL 允许 tag:dev → tag:dev,同时适用于网络授权和 SSH 规则(管理控制台)。SSH 规则的 users 列表必须包含你登录时使用的目标用户——通常 exeuntu 使用 exedev,ubuntu:24.04 使用 root。
用户空间模式回退方案。 如果无法使用 /dev/net/tun(某些 Apple Containers 配置、Sprite),install.sh 会回退到 tailscaled --tun=userspace-networking。此模式下无法使用普通的 ssh <vm>(没有指向 tailnet IP 的内核路由);应改用 tailscale ssh <vm>,它会通过 tailscaled 的用户空间 TCP 协议栈进行代理。
重命名后的 Tailnet 身份——重启无法修复
VM 的 tailnet 名称是运行 tailscale up 时记录的 --hostname,之后不会从操作系统重新读取。因此,如果操作系统主机名发生变化(例如,重建时以 <name>-next 加入,之后又重命名为 <name>),节点仍会使用旧名称,其他节点无法通过其规范名称访问它——看起来就像“已加入 tailnet,却无法通信”。以下两种操作无法修复此问题(2026-08-02 在 canary 上测得):hostnamectl set-hostname 和 systemctl restart tailscaled——重启会重新通告已保存的同一名称。修复方法是重新注册:运行 sudo tailscale logout,然后重新加入(join-tailnet.sh <vm>,此脚本会幂等地使用请求的主机名,并先清除任何占用该名称的过期节点)。重新注册会创建一个新节点,因此 tailnet IP 也会变化——所有固定使用旧 IP 的地方都必须更新。切勿建议通过“重启 tailscaled”来修复 tailnet 连通性;应重新注册。
设置开发 VM
默认设置脚本(exe-setup.sh)通过 ssh exe.dev defaults write 注册,因此每台新 VM 都会自动安装 Tailscale 和 dotfiles。只需两条命令即可获得可用的代码仓库:
1. 创建 VM 并克隆代码仓库(约 4 秒)
ssh exe.dev new --name=<vm> --tag=iv \
--integration=github-<org>-<repo>
ssh -o ConnectTimeout=30 -o StrictHostKeyChecking=accept-new <vm>.exe.xyz \
"git clone https://github-<org>-<repo>.int.exe.xyz/<org>/<repo>.git ~/<repo>"
--tag=iv授予 VM 访问 IV 范围集成(工作用 GitHub MCP、MotherDuck)的权限--integration=github-<org>-<repo>附加该代码仓库专用的克隆/推送集成- 对于个人 VM,还应在创建后附加
github-mcp-home:ssh exe.dev integrations attach github-mcp-home vm:<vm>
GitHub 集成的命名格式为 github-<org>-<repo>(例如 github-kylelundstedt-gitlake)。注册新代码仓库的方法:
ssh exe.dev integrations add github --name github-<org>-<repo> --repository <org>/<repo>
集成范围
集成的范围经过限制,以尽量减少凭据在不同 VM 之间的暴露:
| 集成 | 范围 | 授予权限 |
|---|---|---|
tailscale-api | auto:all(个人) | Tailscale API——启动时设置脚本所需 |
github-mcp-work | tag:iv | 工作用 GitHub API(议题、PR、代码搜索) |
motherduck-mcp | tag:iv | MotherDuck SQL 查询 |
github-mcp-home | vm:(每台 VM) | 个人 GitHub API——仅限你的 VM |
github-<org>-<repo> | vm:(每台 VM) | 单个代码仓库的 Git 克隆/推送 |
reflection | auto:all | VM 元数据(无害) |
团队成员通过 SSO 加入后,将看不到个人集成。团队集成(--team 标志)仅支持附加 tag:——应使用客户端专属标签(例如 iv、usaa)来限定访问范围。
设置脚本
默认设置脚本(dotfiles 仓库中的 exe-setup.sh)会在首次启动时运行,并执行以下操作:
- 删除主机名相同的过期 Tailscale 节点(防止出现
-2后缀) - 通过
tailscale-apiHTTP 代理集成生成一个一次性临时身份验证密钥 - 启动
tailscaled并完成身份验证(ssh exe.dev new返回后约 6 秒,节点会显示在tailscale status中;约 10–12 秒后即可通过短名称运行ssh <vm>) - Tailscale 启动后,在前台运行
install.sh(约 60 秒)
exe-setup.sh 的用途就是让 Tailscale 在完整安装之前启动。同一套身份验证密钥/代理逻辑也位于 install.sh 的 setup_tailscale 中(在单独调用 install.sh 时作为回退方案),但通过 exe-setup.sh 运行可让 VM 提前约 3 分钟加入 tailnet。
设置默认脚本(一次性操作,已完成;如果更改文件名或位置,请重新运行):
ssh exe.dev "defaults write dev.exe new.setup-script 'curl -fsSL https://raw.githubusercontent.com/kylelundstedt/dotfiles/master/exe-setup.sh | bash'"
与 exe.dev 钩子系统之间的两项约定都必须正确:
- 注册所指向的 URL 必须存在。
raw.githubusercontent.com/.../master/exe-setup.sh必须返回 200。 - 注册必须指向
exe-setup.sh,而不是install.sh。 两者都能运行,但install.sh只有在 apt 和 CLI 工具安装后才会启动 Tailscale(Tailscale 启动前会延迟约 30 秒;实测节点可见需要 34–36 秒)。exe-setup.sh会优先启动 Tailscale(节点约 6 秒后可见)。注册指向哪个文件出现偏差时不会有任何提示——VM 仍会完成引导,只是速度较慢。
同时验证这两项:
ssh exe.dev "defaults read dev.exe new.setup-script" # should contain exe-setup.sh, not install.sh
./test-install.sh hook # verifies URL returns 200
如果注册指向的文件发生偏差,请使用上面的 defaults write 命令重新设置。test-install.sh hook 会检查 URL,但不会检查注册了哪个文件。偏差对用户的影响是:“VM 出现在 tailnet 上需要 30 秒,而不是 6 秒。”
2. 提交签名
要进行提交签名,请使用 Tailscale SSH(ssh <vm>),它会转发 1Password SSH Agent。.zshrc 会在登录时检测转发的 Agent,并自动启用提交签名。
3. MCP 服务
五个 MCP 服务中,有三个通过 exe.dev HTTP 代理集成自动连接——新 VM 无需设置:
| 服务 | 代理集成 | 身份验证 |
|---|---|---|
| motherduck | motherduck-mcp.int.exe.xyz | 静态 Bearer 令牌(自动) |
| github-home | github-mcp-home.int.exe.xyz | 静态 Bearer 令牌(自动) |
| github-work | github-mcp-work.int.exe.xyz | 静态 Bearer 令牌(自动) |
| tigris | — | OAuth(一次性浏览器授权流程) |
| readwise | — | OAuth(一次性浏览器授权流程) |
install.sh 会自动注册全部五个服务。基于代理的三个服务在安装后会立即显示“已连接”;Tigris 和 Readwise 则会显示“需要身份验证”,直到完成 OAuth 流程。
Tigris/Readwise 的 OAuth 流程(每台 VM 执行一次):
ssh <vm>.exe.xyz # use the .exe.xyz form — carries LocalForward 8765
claude # inside the VM, start an interactive session
# /mcp → pick a server marked "Needs authentication" → Authenticate
# claude prints an http://localhost:8765/... URL — open it in the Mac browser
Tailscale 形式(ssh <vm>)不会转发 LocalForward,这是有意为之——否则 Zed 持久运行的远程服务 SSH 每次常规连接时都会争用端口 8765。由于端口 8765 只能绑定一次,请在不同 VM 上依次完成 OAuth 流程。令牌会缓存在每台 VM 的 ~/.claude/ 下,因此每个服务在每台 VM 上只需执行一次。
4. 从 Zed 连接
使用 Tailscale 主机名(由于 ~/.ssh/config 中的规范化配置块,短名称也可用)。用户名和主目录路径取决于镜像:
zed ssh://exedev@<vm>/home/exedev/<repo> # exeuntu (default)
zed ssh://root@<vm>/root/<repo> # ubuntu:24.04
对象存储集成(Tigris / S3)
exe.dev“对象存储”集成属于 s3 类型,可通过 https://<integration>.int.exe.xyz 提供兼容 S3 的路径式代理,由 Tigris 存储桶(t3.storage.dev)提供支持。凭据由边缘注入——虚拟机发送的是占位符凭据,代理会使用真实的 Tigris 密钥重新签名,因此虚拟机上不会留存任何密钥材料。
端点主机名是集成名称,而非存储桶名称。 代理位于 https://<integration>.int.exe.xyz,其中 <integration> 是集成名称——它不一定与存储桶名称相同。请务必查询名称,不要自行假定:ssh exe.dev integrations list 会显示 <name> s3 endpoint=… bucket=…。例如,集成 bucket-gitlake-examples 指向存储桶 gitlake-examples,因此主机名是 bucket-gitlake-examples.int.exe.xyz,但路径中使用的是 gitlake-examples。使用 ssh exe.dev integrations attach <name> vm:<vm> 将集成附加到每台虚拟机。
仅支持路径式寻址。 代理会从 URL 路径 中读取存储桶(.../<bucket>/<key>)。虚拟主机式寻址(将存储桶作为子域名)会失败:<bucket>.<name>.int.exe.xyz 会导致 *.int.exe.xyz TLS 证书失效,而将存储桶放在主机名中并使用路径 / 则会返回 403 "account-level operations are not allowed"。所有客户端都必须使用路径式寻址。
tigris CLI
只有通过 TIGRIS_FORCE_PATH_STYLE 强制使用路径式寻址时才能正常工作。该环境变量是在 3.1.0 之后添加的(已确认存在于 3.6.1 中,在 3.1.0 中不存在)——旧版 tigrisdata/cli 发布版本和过时镜像中附带的 CLI 没有此变量,因此请确保 tigris --version 显示的是当前版本(install.sh 现在会从 tigrisdata/storage 下载)。
export TIGRIS_STORAGE_ENDPOINT=https://<integration>.int.exe.xyz
export TIGRIS_FORCE_PATH_STYLE=1 # REQUIRED — the proxy is path-style
export TIGRIS_STORAGE_ACCESS_KEY_ID=x # placeholders; edge injects the real cred
export TIGRIS_STORAGE_SECRET_ACCESS_KEY=x
tigris ls <bucket>
tigris cp ./file t3://<bucket>/key # up/download/rm all work
tigris CLI 自带的 S3 客户端不会发送 CRC64NVME 流式尾部校验,因此无需额外标志即可上传。
aws CLI / SDK(boto3、aws-sdk-js、rclone)
使用相同的端点、路径式寻址和占位符凭据:
AWS_ACCESS_KEY_ID=x AWS_SECRET_ACCESS_KEY=x \
aws s3 ls s3://<bucket> --endpoint-url https://<integration>.int.exe.xyz
近期版本的 aws-cli v2 会发送默认的 CRC64NVME 校验和尾部,代理会在上传时以 SignatureDoesNotMatch 拒绝该请求。请禁用此功能(也可在 ~/.aws/config 中设置):
export AWS_REQUEST_CHECKSUM_CALCULATION=when_required
export AWS_RESPONSE_CHECKSUM_VALIDATION=when_required
boto3:Config(s3={"addressing_style": "path"})。rclone:force_path_style = true。
预签名
代理提供了一个预签名辅助端点,可返回已签名的 t3.storage.dev URL:
curl -sX POST https://<integration>.int.exe.xyz/_/presign -d '{"key":"path/to/object"}'
说明
- 按设计禁止账户级操作(
ListBuckets、路径/)——该集成的凭据被限定为只能访问一个存储桶。 - 也可以使用普通的未签名
curl "https://<integration>.int.exe.xyz/<bucket>/?list-type=2"(边缘会注入凭据)——可方便地用于探测连通性。