news 2026/9/29 9:57:22

读Open Terminal源码:FastAPI+PTY如何把一台电脑变成REST API

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
读Open Terminal源码:FastAPI+PTY如何把一台电脑变成REST API

读Open Terminal源码:FastAPI+PTY如何把一台电脑变成REST API

【免费下载链接】open-terminalA computer you can curl ⚡项目地址: https://gitcode.com/gh_mirrors/ope/open-terminal

Open Terminal 是一个轻量级的自托管终端 API:它用 FastAPI 做 HTTP 服务层,用 PTY(伪终端)做命令执行层,把一台 Linux/macOS 电脑变成可以通过 REST API 调用的"远程计算机"——运行命令、读写文件、搜索代码、甚至开一个交互式 Shell,全部通过curl即可完成 ⚡。本文带你从源码角度看懂这两块核心技术如何拼在一起。

一句话架构:HTTP 接口 + 子进程 + 文件操作

Open Terminal 的核心代码只有一个包:open_terminal/,总共约 5000 行 Python。整个系统可以拆成三层:

层负责什么关键文件
API 层接收 HTTP 请求、鉴权、路由open_terminal/main.py
执行层启动子进程、接管 PTY 输出open_terminal/utils/runner.py
启动层CLI 参数解析、配置加载open_terminal/cli.py

FastAPI 应用实例在 main.py 第 209 行 创建,所有接口共用一个 Bearer Token 鉴权依赖verify_api_key,未带正确 API Key 的请求统一返回 401。

第一步:用 FastAPI 把"命令执行"变成 POST 请求

最有代表性的接口是POST /execute。你发一个 JSON:{"command": "ls -la"},它做了四件事:

  1. 创建 Runner:调用 create_runner 工厂函数,根据平台选择 PTY / WinPTY / 管道三种后端之一;
  2. 登记进程:生成时间戳+随机数的进程 ID,把进程放进内存字典_processes,同时启动一个后台任务把输出实时写入 JSONL 日志文件;
  3. 可选等待:支持wait参数,在超时前持续收集输出(见 main.py 第 1726 行);
  4. 返回结果:输出按"条目"存储,后续用GET /execute/{id}/status?offset=N增量轮询——这个"offset 增量读取"设计让 AI 客户端可以反复拉取新输出而不重复消费。

除了执行命令,FastAPI 层还暴露了一整套文件工具接口:/files/list、/files/read、/files/write、/files/replace(带行号范围限制的精确查找替换)、/files/grep(正则搜索,优先调用系统里的rg,否则回退到纯 Python 实现,见 main.py 第 1183 行)、/files/matches(按文件名和内容综合排序的搜索)。读文件接口还有一个巧思:遇到 PDF、Office 文档时自动抽取文本返回,遇到图片则直接返回二进制——因为它的目标用户是 LLM,而不是浏览器 👀。

文件操作统一走UserFS这个封装类(open_terminal/utils/fs.py),单用户模式下用标准库直接 I/O,多用户模式下自动改走sudo -u,让每个用户只能碰自己的家目录。

核心魔法:PTY 让命令"以为"自己运行在真终端里

这是本文的重点。普通subprocess只能拿到"管道输出",而很多程序(vim、pip进度条、带颜色的 CLI)会检测到"不是终端"就改变行为甚至拒绝运行。解决方案就是PTY(伪终端):内核提供一对文件描述符,slave 端交给子进程当 stdin/stdout/stderr 用,master 端由服务端读取——子进程以为自己连着真实终端,一切 ANSI 转义序列照常工作。

看 runner.py 中的 PtyRunner,关键就三步:

  1. pty.openpty()打开伪终端对;
  2. 用ioctl(TIOCSWINSZ)设置 24×80 的默认窗口尺寸;
  3. subprocess.Popen启动 shell,三个标准流全部指向 slave fd,并设置start_new_session=True让子进程拥有独立进程组——这样"杀命令"时可以对整个进程组发SIGTERM,连sleep 999 &这种后台任务一起收走,不留孤儿进程。

读取输出时(read_output 方法),代码把阻塞的os.read(master_fd)丢进线程池执行,每读到一块数据就追加时间戳写成 JSON 行,实现"异步友好的实时日志流"。

跨平台与降级策略

create_runner 工厂 体现了优雅的降级链:

  • Linux / macOS→PtyRunner(原生 PTY)
  • Windows→WinPtyRunner(通过 pywinpty 的 ConPTY)
  • 都没有→PipeRunner(普通管道,功能可用但失去终端特性)

交互式终端接口POST /api/terminals同理:main.py 第 2008 行 先探测 PTY 是否可用,不可用再尝试 WinPTY,都失败则返回 503 并提示安装依赖。每个会话还受MAX_TERMINAL_SESSIONS上限保护,PTY 设备耗尽时返回 503 而不是崩溃。

WebSocket 层:把键盘按键和屏幕输出双向打通

对于需要真正"交互式 Shell"的场景(比如 AI Agent 想在python解释器里连续敲代码),REST 轮询太慢了。Open Terminal 在 main.py 第 2283 行 提供了一个 WebSocket 端点/api/terminals/{session_id},协议设计得很克制:

  • 首帧鉴权:连接后必须先在 10 秒内发送{"type": "auth", "token": "<key>"},失败即断开;
  • 二进制帧 = 数据:客户端发按键、服务端回 PTY 输出,全程走 bytes,零序列化开销;
  • 文本帧 = 控制信令:发送{"type": "resize", "cols": 120, "rows": 40}就能让服务端调用ioctl调整终端窗口,vim的布局会实时跟着变。

服务端用一个_pty_reader任务把 PTY 输出持续转发到 WebSocket,并维护一个 64KB 的环形输出缓冲(第 2397 行)供 REST 接口随时回读最近内容。断连时清理逻辑会先SIGTERM、等 3 秒、再SIGKILL整个进程组,确保资源不泄漏(见 _cleanup_session)。

其余亮点:这些细节让 AI 用得更顺手

  • 进程自动清理:结束 5 分钟后的进程从内存移除,日志文件超过保留期自动删除(_cleanup_expired);
  • 会话级工作目录:用X-Session-Id请求头追踪每个会话的 cwd,替代了不安全的进程级os.chdir,并发会话互不串扰;
  • 转义序列容错:POST /execute/{id}/input会把字面量\n、\x03(Ctrl-C)转成真实控制字符——因为 LLM 经常"字面地"输出这些转义符(第 1828 行);
  • 端口检测 + 反向代理:GET /ports列出本机监听端口,/proxy/{port}/...直接代理请求,AI 启动一个 Web 服务后无需暴露端口就能访问;
  • LLM 系统提示:GET /system返回一段描述当前操作系统、Shell、Python 版本的提示词模板,帮助 LLM 快速"了解"这台机器。

如何安装与启动 Open Terminal

两种部署方式任选其一:

# Docker(推荐,自带完整开发工具链的沙箱) docker run -d --name open-terminal -p 8000:8000 \ -v open-terminal:/home/user \ -e OPEN_TERMINAL_API_KEY=your-secret-key \ ghcr.io/open-webui/open-terminal
# 裸机运行(标准 Python 包,pip 即可装) pip install open-terminal open-terminal run --host 0.0.0.0 --port 8000 --api-key your-secret-key

启动入口是 cli.py 中的 run 命令:用 Click 解析参数、加载 TOML 配置(优先级为 CLI 参数 > 环境变量 > 用户配置 > 系统配置 > 默认值,见 config.py),最后交给 uvicorn 跑起 FastAPI。不设置 API Key 时会自动生成一个并打印在启动日志里。服务起来后访问http://localhost:8000/docs即可看到自动生成的交互式 API 文档——这就是 FastAPI 白送的福利。

⚠️ 裸机模式下命令以你当前用户的权限直接执行,想要隔离环境请用 Docker。

总结

Open Terminal 用不到 5000 行代码演示了一套非常干净的"把系统能力 API 化"的工程范式:

  1. FastAPI 负责"接口长什么样"——声明式路由、自动 OpenAPI 文档、依赖注入式鉴权;
  2. PTY 负责"命令怎么跑"——伪终端保证任意 CLI 程序行为一致,独立进程组保证可彻底清理;
  3. 异步 I/O 负责"并发怎么扛"——阻塞的系统调用一律丢进线程池,WebSocket 与 REST 双通道满足不同场景。

如果你想给自己的 AI Agent 配一台随叫随到的"云端小电脑",这套源码值得逐行读一遍:它证明了把一台电脑变成 REST API,核心代码其实并不多。

【免费下载链接】open-terminalA computer you can curl ⚡项目地址: https://gitcode.com/gh_mirrors/ope/open-terminal

创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考

版权声明: 本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若内容造成侵权/违法违规/事实不符,请联系邮箱:809451989@qq.com进行投诉反馈,一经查实,立即删除!
网站建设 2026/9/29 9:56:49

UI 自动化踩坑记:一次被百度安全验证拦下的测试

报错在进行《实验案例1&#xff1a;百度搜索软件测试》时发现代码跑完assert "软件测试" in driver.title 测试报错断言期望&#xff1a;页面标题里包含“软件测试”。 实际结果&#xff1a;标题并没有包含软件测试即没有成功跳转调试反复确认代码编写没有错误在AI的…

作者头像 李华
网站建设 2026/9/29 9:53:48

Jlink嵌入式调试实战:SWD接线、Keil配置与烧录失败排查

做嵌入式开发的朋友&#xff0c;桌面上一定躺过那抹蓝色——Jlink。市面上叫它“烧录仿真工具”&#xff0c;听起来好像只是把程序烧进芯片、再用仿真器跑一下&#xff0c;但在STM32、NXP、GD32这些主流MCU项目里&#xff0c;它几乎包揽了从下载固件到在线调试的整条链路。我第…

作者头像 李华
网站建设 2026/9/29 9:53:12

OpenClaw 配 TaoToken:国产龙虾三剑客的 config.toml 骨架与验证动作

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

作者头像 李华