背景
之前写过《使用GitHub Actions自动部署Hexo博客》,把「写完文章 → 推送 → 自动上线」这条流水线搭了起来。我的部署是典型的双仓库结构:源码在一个私有仓库,构建产物推到公开的 <用户名>.github.io Pages 仓库。
这套结构有个绕不开的坑:CI 要往另一个仓库推送,而 GitHub Actions 自带的 GITHUB_TOKEN 只能操作当前仓库。跨仓库推送必须额外带凭据,我一开始用的是 personal_token(Personal Access Token),结果某次推送后 CI 直接报 The process '/usr/bin/git' failed with exit code 128——说白了,就是跨仓库推送的凭据一直没真正可用。
问题不是”要不要凭据”,而是”用什么凭据”。折腾一圈后我换成了 SSH Deploy Key,这篇文章讲讲为什么换、怎么配、有什么风险。
为什么不用 Token,而用 Deploy Key
先说结论:跨仓库推送的凭据无非两种,Personal Access Token 或者 SSH Deploy Key,我强烈建议后者。
| 维度 | Personal Access Token | SSH Deploy Key |
|---|---|---|
| 权限范围 | 账号级,repo scope 等于所有仓库 |
只对指定仓库有效,且只授予 Contents 写权限 |
| 泄露影响 | 攻击者可读写你所有可见仓库 | 只能改写这一个 Pages 仓库 |
| 吊销方式 | 在 Developer settings 里逐个管理 | 在目标仓库 Deploy keys 里删掉即失效 |
| 是否经过账号体系 | 是,token 关联账号 | 否,纯密钥对,不依赖账号登录态 |
| 配置复杂度 | 需要登录网页创建、配 scope | 本地生成密钥对,粘贴两次 |
Token 最大的问题是权限面过大:一个 repo scope 的 token 相当于把账号下所有仓库的读写权都交给了 CI。一旦 secret 泄露,攻击者拿到的不只是博客,而是你 GitHub 账号能看到的全部代码。Deploy Key 是一把”专钥专用”的钥匙,只对 Pages 仓库有写权限,就算泄露,损失也被锁死在一个仓库里。
另一个现实原因是它真的能跑通。Deploy Key 走 SSH 协议,不依赖 token 的 scope、过期时间、SSO 授权这些环节,配好就是好的,不存在”看着配置没问题却推不上去”的玄学。
前置要求
- 一个 Hexo 项目,已配置好
.github/workflows/main.yml(还不会配的可以先看上一篇) - 一个公开的
<用户名>.github.ioPages 仓库 - 本地能执行
ssh-keygen(macOS / Linux 自带)
配置步骤
第一步:生成密钥对
1 | ssh-keygen -t ed25519 -C "github-actions-deploy" -f ~/.ssh/blog_deploy_key -N "" |
生成两个文件:私钥 blog_deploy_key 和公钥 blog_deploy_key.pub。私钥自留,公钥给 GitHub。
第二步:把公钥挂到 Pages 仓库
打开 <用户名>.github.io → Settings → Deploy keys → Add deploy key:
- Title 随意,比如
github-actions-deploy - Key 粘贴公钥内容
- 务必勾选 Allow write access,否则只能读不能推
第三步:把私钥存成 Actions secret
打开源码仓库 → Settings → Secrets and variables → Actions → New repository secret:
- Name:
DEPLOY_KEY - Secret:私钥完整内容(含
-----BEGIN OPENSSH PRIVATE KEY-----头尾),cat ~/.ssh/blog_deploy_key复制即可
secret 存进去之后 GitHub 就看不到明文了,所以本地私钥别删太早,等确认部署没问题再说。
第四步:改 workflow
.github/workflows/main.yml 里把 personal_token 换成 deploy_key:
1 | - name: Deploy to GitHub Pages |
第五步:验证
先确认密钥被 GitHub 认可:
1 | ssh -T -i ~/.ssh/blog_deploy_key git@github.com |
返回 Hi <用户名>/<用户名>.github.io! You've successfully authenticated 就说明公钥挂载成功。接着随便推送一次(或者重跑失败的 run),去 Pages 仓库提交页看有没有 deploy: 开头的提交——有就说明 CI 走通了。
第六步:收尾
- 确认稳定后,删掉旧的
DEPLOY_TOKENsecret - 本地私钥副本不需要了就删;想留底就放
~/.ssh/并chmod 600
可能的风险
Deploy Key 不是银弹,几个坑提前说:
- 私钥泄露 = 博客仓库被改写。拿到私钥的人能随意往 Pages 仓库推内容,等于能控制你博客展示的东西(包括挂恶意脚本)。所以私钥绝不提交 Git、不贴聊天记录,本地副本用完即删。
- 怀疑泄露就立即吊销。在 Pages 仓库的 Deploy keys 里删掉公钥,重新生成密钥对、更新 secret,几分钟搞定,比吊销 token 简单。
- secret 不能回显。私钥丢了又没留本地副本,只能重新生成一套,没有”导出”选项。
- 一把 Deploy Key 只绑一个仓库,不能复用。以后有别的 Pages 仓库就再生成一把,这是刻意为之的权限最小化。
- 每次 push 都会触发部署。workflow 监听
hexo分支,空提交也会触发,调试 CI 时别制造无意义的提交。
总结
一句话:自动部署跨仓库推送,用最小权限的专钥,别用账号级的 token。SSH Deploy Key 只对目标仓库有写权限、泄露可以单点吊销、不依赖账号体系,是这个场景下比 PAT 更可控的选择。配置成本也就”生成一次密钥、粘贴两次、改一行 workflow”,换来的是稳定可用的自动部署和更小的爆炸半径。