ChatGPT Web + GitHub + Self-hosted Runner:任意设备开发,统一从 GitHub 部署
我以前做服务器项目,开发和发布基本都绕不开同一台电脑:本地拉代码,Codex 或 IDE 改完,git push,再开终端 SSH 到服务器,git pull、构建、重启、看日志。这个方式没有什么“不专业”,反而很直接;问题是只要项目开始长期维护,电脑、代码工作区、SSH 会话和生产发布就被绑在一起了。
最近我把自己的博客部署链路重新收了一遍,最后保留的生产路径变成了:
浏览器 / 本地开发 / 云端 Coding Agent
↓
GitHub
↓
GitHub Actions
↓
Self-hosted Runner
↓
build / validate
↓
release directory
↓
current symlink
↓
production
这套结构最吸引我的地方,不是“以后再也不用本地电脑”,而是开发入口可以变化,生产出口尽量保持一个。Mac 上写代码、手机上看任务、网页里继续一个云端 Coding 任务,都可以只是入口;真正需要长期稳定的是 GitHub 里的代码状态、审查记录和那条确定性的生产 workflow。
我这次还特意把笔记里的几个说法重新和官方文档、当前仓库代码对了一遍。结果有两个地方必须收紧:普通 ChatGPT 连接 GitHub 并不等于自动拥有写仓库权限;我现在这套部署也不是“任何失败都自动回滚”。把这些边界讲清楚以后,这个架构反而更实用。
GitHub 对 Self-hosted Runner 的当前说明在这里:https://docs.github.com/en/actions/reference/runners/self-hosted-runners
Self-hosted Runner 不是 GitHub 远程 SSH 进你的服务器
我一开始很容易把它脑补成“GitHub 收到 push,然后远程登录 VPS 执行脚本”。实际模型不是这样。
GitHub 官方文档写得很明确:Self-hosted Runner 应用运行在你自己的机器上,由 Runner 主动连接 GitHub,接收 job assignment;主机需要能够向 GitHub 发起 outbound HTTPS 连接,核心通信走 443 端口。
所以一台 VPS 注册成 Runner 以后,更接近长期在线的任务执行器:
GitHub
↑ Runner 主动建立连接
│
Self-hosted Runner on VPS
│
├─ checkout
├─ npm ci
├─ test / build
└─ local deploy
workflow 里真正决定“谁来接这份工作”的只是 runner 标签。公开文章里我把自己的标签简化成:
jobs:
deploy:
runs-on: [self-hosted, linux, prod]
这和旧的 SSH/SCP 发布路径差别很大。以前是 CI 在外部机器构建,再把产物推到 VPS;现在 job 本身就在 VPS 的 Runner workspace 里运行,构建完可以直接调用本机部署脚本,不需要再为“CI 怎么登录服务器”维护一套 SSH key、SCP 路径和远程命令。
对我来说,GitHub 在这里更像控制面和状态中心,VPS 才是执行面。只要 Runner 在线,哪个设备发起了代码修改并不重要,最终生产都经过同一份 workflow。
这也解释了为什么我现在更愿意把 GitHub 当 source of truth。服务器上的工作目录不再是“主代码”,它只是某一次 job 的临时执行环境;真正需要追踪的是 commit、PR、workflow run 和 release。
浏览器可以成为开发入口,但“连接 GitHub”不等于“能写 GitHub”
这里是我觉得最容易被宣传语带偏的一层。
OpenAI 当前的 GitHub 连接文档明确区分了两件事。普通 ChatGPT GitHub app 用来读取、搜索和分析你授权的仓库;帮助中心甚至直接回答了“能否 push 代码、更新或 PR”这个问题:标准 GitHub 连接本身是只读的,要生成、编辑并直接推送代码,需要使用 Codex。
官方说明:https://help.openai.com/en/articles/11145903-connecting-github-to-chatgpt
所以我现在画这套架构时,不会再写成:
ChatGPT Web
↓
直接修改 GitHub
更准确的是:
浏览器 / 手机 / 桌面
↓
具备写权限的 Coding 工作流
例如 Codex Cloud / 受控 Agent
↓
branch / commit / PR
↓
GitHub
Codex Cloud 这层确实让“电脑不在身边也能继续开发”变得更现实。OpenAI 当前帮助中心说明,Codex Cloud 的任务运行在 OpenAI 管理的计算机上,准备好项目环境后,可以在支持的桌面、网页或移动端继续云端任务;任务也可以在你的本地电脑休眠时继续运行。
对应说明:https://help.openai.com/en/articles/11369540-using-codex-with-your-chatgpt-plan
但我不会把它描述成“网页版没有 Token 限制”或者“永远不受额度影响”。产品套餐、工作区权限和使用限制都会变化,真正稳定的优势不是一句“无限”,而是代码状态不再依赖你当前这台 Mac 的终端会话。
这点和我之前写 新版 Codex 为什么越来越像开发环境 时的感受是一致的。模型只是入口之一,真正让工作可持续的是仓库、环境、任务状态和验证链能不能被重新接起来。
生产出口只剩 GitHub 以后,我才开始真正相信“任意设备开发”
如果只做到“网页里能改代码”,我觉得价值还不算大。最麻烦的发布步骤如果最后仍然是“回到 Mac,SSH 上服务器敲几条命令”,那手机和网页只是把写代码提前了,生产依赖仍然绑在本地电脑上。
我现在这套博客 workflow 是更关键的一步:生产只认受信任的 main 更新。
我检查了当前实际运行的 workflow,它的顺序大致是:
main push
↓
checkout
↓
runtime / directory checks
↓
npm ci
↓
syntax checks
↓
build
↓
capture previous release
↓
local atomic deploy
↓
post-deploy SEO verification
↓
submit sitemap
不是设计图而已。2026 年 10 月 7 日我核对了实际的 Deploy Production run #37624628201,对应审计源 revision 为 f36a433;Build、Atomic Deploy、Post-Deploy SEO Verification 和 sitemap submission 都成功结束。因为生产仓库是 private,这里只公开可核对的脱敏标识,不放一个普通读者打不开的私有 GitHub 链接。这个 run 对我最有价值的地方,是它证明生产发布已经真的从“某台本地电脑上的 SSH 操作”迁到了 repository workflow。
部署脚本本身也很朴素。公开示例可以简化成:
/srv/app/
├─ releases/
│ ├─ 20261007-abc1234/
│ └─ 20261007-def5678/
└─ current -> releases/20261007-def5678/
新版本先进入独立 release 目录,再用临时 symlink 原子替换 current。这样 Nginx 或其他 Web Server 始终只看 current,构建工作区不会直接变成线上目录。
这里我顺手纠正了自己笔记里的一个表述。我原来很容易把这套东西概括成“失败就自动回滚”,但真正看 workflow 代码以后,准确说法更窄:
- 构建或前置检查失败时,根本不会进入生产切换;
- 部署后 SEO verification 失败时,workflow 有显式逻辑把
current指回 previous release; - 不能因此推出“任何发生在 symlink 切换之后的故障都会自动回滚”。
这个区别看起来有点抠字眼,实际很重要。运维架构最怕漂亮的框图比代码更强。以后如果我要继续强化这条链路,应该把所有需要自动恢复的 failure class 明确列出来,而不是靠“atomic deploy”四个字产生安全感。
这和我早先写的 Markdown + GitHub Actions 自动发布流程 其实已经不是同一个层次。前者解决“内容怎么自动上线”,现在这套东西解决的是“无论开发从哪里发起,生产到底认哪一条路径”。
Self-hosted Runner 最大的坑,是它真的在你的机器上执行代码
把 Runner 装进 VPS 很爽,因为它直接拿到了本机文件系统、网络和部署能力。风险也恰好来自同一个地方。
GitHub 的安全文档对这一点写得非常重:Self-hosted Runner 不像 GitHub-hosted Runner 那样天然保证每个 job 都来自全新、隔离的临时虚拟机;如果不受信任的 workflow code 在上面执行,机器可以被持久化污染。因此 GitHub 明确建议 Self-hosted Runner 几乎不要用于公共仓库,也提醒即使是 private/internal repo,只要有人能 fork 并提交 PR,也要认真考虑这条攻击路径。
官方安全说明:https://docs.github.com/en/actions/reference/security/secure-use
这让我重新看了一遍自己当前的 workflow。生产部署由受信任的 main push 触发,这个方向没问题;但内容 PR 的验证任务现在也会落到同一台 Self-hosted Runner 上。
在我目前的私人仓库、受信任作者模型里,这个风险是可控的。可一旦未来把仓库公开,或者让更多外部协作者可以提交 PR,我不会继续让“PR 里的任意代码”接触生产 Runner。更合理的拆法会是:
Untrusted / public PR
↓
GitHub-hosted runner
或独立、可销毁的验证 Runner
Protected main
↓
Production self-hosted runner
↓
Deploy
甚至在团队扩大以后,Runner group 也应该只允许指定仓库和指定 workflow 使用生产 Runner。
这也是为什么“AI 不需要拿 VPS SSH key”只是安全改善的一部分。把 SSH key 从 Agent 手里拿走当然很好,但如果 Agent 能修改 workflow,而这个 workflow 可以在生产主机上执行任意 shell,那权限只是换了入口,没有消失。
真正应该保护的是整个执行边界:谁可以修改 workflow、谁可以合并 main、哪些事件能命中生产 Runner、Runner 用户在系统里能写哪些目录、Secrets 有多大权限。
本地 Codex 和 SSH 没有消失,只是从“日常发布”退到了更合适的位置
做到这里以后,我并不觉得本地 Codex + 本地仓库 + SSH 变得落后。恰恰相反,它们处理的是浏览器控制面最不擅长的那部分工作。
我现在会这样分:
| 场景 | 云端 / GitHub 工作流 | 本地 Codex + SSH |
|---|---|---|
| 多设备继续代码任务 | 很合适 | 依赖当前机器 |
| 代码历史与 PR 审查 | 很合适 | 仍以 GitHub 为准 |
| 标准 build / test / deploy | 很合适 | 不应每次手工做 |
查看 docker ps / journalctl | 间接 | 很合适 |
| 数据库、磁盘、网络现场排查 | 间接 | 很合适 |
| 生产故障紧急处理 | 可辅助 | SSH 更直接 |
| 可审计发布 | 天然有 run / commit | 手工 SSH 较弱 |
浏览器或手机最适合当 control plane:决定改什么、审查 diff、看 CI、触发一个已经定义好的流程。本地终端更像 observability/debug plane:你需要知道这一秒服务器上哪个进程卡住、哪个容器重启、磁盘为什么满、Nginx 实际读的是哪份配置。
SSH 也不需要被彻底删掉。我更愿意把它降级成 break-glass 通道:日常发布不依赖它,故障现场需要时仍然可以直接进机器。这样即使临时 SSH 维护,最终修复也应该回到 GitHub 形成 commit/workflow,而不是让服务器长期积累只有一台电脑知道的“现场改动”。
如果已经有一套本地 Markdown + GitHub Actions 的发布流程,可以先参考 我之前的自动发布方案;如果还在权衡静态博客和传统 CMS,那篇 Astro vs WordPress 更接近架构选择这一层。
我现在更看重的不是“以后能不能完全脱离 Mac 开发”,而是四个角色终于能分开:
浏览器 / Codex / 本地 IDE → 开发入口
GitHub → 持久代码与审查状态
Self-hosted Runner → 受信任的自动执行
SSH → 深度诊断与应急
设备可以换,Agent 可以换,甚至生产服务器也可以以后再换。只要代码状态、发布规则和验证逻辑都留在 GitHub,这条链路就不会跟着某一台电脑一起丢。
这才是我觉得 ChatGPT / Codex + GitHub + Self-hosted Runner 最有价值的地方:不是把终端藏起来,而是把“必须在这台电脑上亲手完成”的事情尽量从日常流程里拿掉。