0%

告别 Token:用 SSH Deploy Key 自动部署 Hexo 博客

背景

之前写过《使用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.io Pages 仓库
  • 本地能执行 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
2
3
4
5
6
7
- name: Deploy to GitHub Pages
uses: peaceiris/actions-gh-pages@v4
with:
deploy_key: ${{ secrets.DEPLOY_KEY }}
external_repository: <用户名>/<用户名>.github.io
publish_branch: master
publish_dir: ./public

第五步:验证

先确认密钥被 GitHub 认可:

1
ssh -T -i ~/.ssh/blog_deploy_key git@github.com

返回 Hi <用户名>/<用户名>.github.io! You've successfully authenticated 就说明公钥挂载成功。接着随便推送一次(或者重跑失败的 run),去 Pages 仓库提交页看有没有 deploy: 开头的提交——有就说明 CI 走通了。

第六步:收尾

  • 确认稳定后,删掉旧的 DEPLOY_TOKEN secret
  • 本地私钥副本不需要了就删;想留底就放 ~/.ssh/chmod 600

可能的风险

Deploy Key 不是银弹,几个坑提前说:

  1. 私钥泄露 = 博客仓库被改写。拿到私钥的人能随意往 Pages 仓库推内容,等于能控制你博客展示的东西(包括挂恶意脚本)。所以私钥绝不提交 Git、不贴聊天记录,本地副本用完即删。
  2. 怀疑泄露就立即吊销。在 Pages 仓库的 Deploy keys 里删掉公钥,重新生成密钥对、更新 secret,几分钟搞定,比吊销 token 简单。
  3. secret 不能回显。私钥丢了又没留本地副本,只能重新生成一套,没有”导出”选项。
  4. 一把 Deploy Key 只绑一个仓库,不能复用。以后有别的 Pages 仓库就再生成一把,这是刻意为之的权限最小化。
  5. 每次 push 都会触发部署。workflow 监听 hexo 分支,空提交也会触发,调试 CI 时别制造无意义的提交。

总结

一句话:自动部署跨仓库推送,用最小权限的专钥,别用账号级的 token。SSH Deploy Key 只对目标仓库有写权限、泄露可以单点吊销、不依赖账号体系,是这个场景下比 PAT 更可控的选择。配置成本也就”生成一次密钥、粘贴两次、改一行 workflow”,换来的是稳定可用的自动部署和更小的爆炸半径。