这篇主要记录我这次博客部署里踩到的一个坑:Hugo 本地构建没有问题,GitHub Actions 里也能正常编译,但是最后推送到站点仓库时报了认证错误。
我最后把问题收敛到了三件事:
- 外部仓库部署和单仓部署不是一回事。
- 公开仓库不等于可以匿名写入。
- token 不能明文写在 workflow 里,应该放到 GitHub Actions Secrets 中。
当前部署结构
我现在的博客不是直接把源码放在 hebohang/hebohang.github.io 里,而是分成了两层:
HebohangWebsiteHugo:存放 Hugo 源文件、主题和工作流。hebohang/hebohang.github.io:作为最终发布仓库。
GitHub Actions 的流程是:
- 在源码仓库里执行
hugo --minify --gc - 得到生成后的
public/ - 再把
public/推送到hebohang/hebohang.github.io的hugo分支
所以这不是“当前仓库自己发布自己”,而是“当前仓库构建后,推送到另一个仓库”。
这次遇到的报错
报错大概是这样:
| |
出错的位置不是 Hugo 构建,而是部署阶段的:
| |
也就是说,页面已经生成出来了,只是最后一步推送失败。
后来在把明文 token 改成 GitHub Actions secret 之后,我又遇到了第二种报错:
| |
这个 403 和前面的 Invalid username or token 不一样,它说明:
- token 已经是有效的
- GitHub 也识别出了这个 token 对应的账号
- 但是这个 token 没有目标仓库的写权限
所以这里失败的不是“身份校验”,而是“授权不足”。
为什么公开仓库也会认证失败
一开始容易误会:hebohang/hebohang.github.io 是公开仓库,为什么还会认证失败?
原因很简单,公开仓库只代表任何人都能读,不代表任何人都能写。
我的 workflow 需要把内容推送到目标仓库,这本质上就是一次写操作,所以必须要有有效的写权限。
如果是把站点直接部署回当前仓库,很多时候可以使用 GITHUB_TOKEN。
但我这里是推送到外部仓库,因此要额外准备能够写入这个目标仓库的凭据,通常就是:
- PAT(Personal Access Token)
- 或者 deploy key
我这次采用的是 PAT。
PAT 是什么
PAT 全称是 Personal Access Token,也就是“个人访问令牌”。
可以把它理解成 GitHub 提供给程序、命令行和 CI 使用的一种访问凭据。
它的作用有点像“专门给自动化流程使用的密码”,但是它比密码更细,可以单独限制权限、单独撤销,也更适合放进 CI 里。
在这个场景里,PAT 的作用就是:
- 让 GitHub Actions 有权限把构建产物推送到
hebohang/hebohang.github.io
PAT 在哪里创建
登录 GitHub 之后,路径是:
- 右上角头像
SettingsDeveloper settingsPersonal access tokens
这里会看到两种 token:
Tokens (classic)Fine-grained tokens
我更推荐 Fine-grained token,因为权限更细,也更安全。
PAT 该给什么权限
如果使用 Fine-grained token,我这里推荐的配置是:
- Token owner:你的 GitHub 账号
- Repository access:只选择
hebohang/hebohang.github.io - Repository permissions:
Contents: Read and write
这样就足够支撑这类部署流程了。
如果使用的是 Tokens (classic),通常会直接勾:
repo
它也能用,只是权限范围更大,不如 fine-grained 精确。
Fine-grained PAT 最容易配错的地方
真正需要写入的是目标仓库 hebohang/hebohang.github.io,而不是当前这个源码仓库 HebohangWebsiteHugo。
所以如果你使用的是 fine-grained PAT,最常见的 403 原因通常是:
- 选错了仓库,只授权了
HebohangWebsiteHugo - 选对了仓库,但没有给
Contents: Read and write
出现下面这种报错时:
| |
优先就检查这两个点。
Secrets 应该怎么配
就算拿到了 PAT,也不要把它直接写进仓库。
正确做法是把它放进 GitHub Actions Secrets:
- 打开源码仓库
HebohangWebsiteHugo - 进入
Settings Secrets and variablesActions- 新建一个 secret
我这里使用的名称是:
| |
然后把刚创建的 PAT 值填进去。
这样 workflow 就可以通过:
| |
来读取这个凭据,而不是把 token 明文提交到 git 历史中。
workflow 的正确写法
我现在用的是 peaceiris/actions-gh-pages@v3,核心配置如下:
| |
这里有几个点要注意:
1. 不要把 token 明文写进 workflow
错误示范:
| |
这会带来几个问题:
- token 暴露在仓库里
- 可能被 GitHub 自动吊销
- 后续维护时也很容易忘记它来自哪里
如果 token 已经明文提交过,最稳妥的处理方式是:
- 删除明文引用
- 重新创建一个新的 token
- 旧 token 直接废弃
2. 只在 push main 时执行 deploy
PR 场景下通常只需要验证构建能不能通过,不应该真的往生产仓库推内容。
因此我这里把 deploy 限制在:
| |
这样 pull request 仍然可以跑 Hugo build,但不会触发外部仓库写入。
3. EXTERNAL_REPOSITORY 要写对
因为我这里是把产物推送到外部仓库,所以这个字段必须明确写成:
| |
如果你自己的站点仓库不同,这里也要改成对应地址。
修复完成后怎么验证
可以按下面这个顺序检查:
本地验证
先确认本地构建没问题:
| |
远端验证
然后推送一次提交,或者在 Actions 中手动重跑 workflow:
- 看
Build是否成功 - 看
Validate deploy secret是否通过 - 看
Deploy是否成功
如果成功,还可以继续检查:
hebohang/hebohang.github.io的hugo分支是否有新提交- 页面内容是否已经更新
常见排查点
如果还是失败,可以优先看下面几项:
1. secret 名称写错
代码里写的是:
| |
那 GitHub 仓库里的 secret 名称也必须一模一样。
2. 把 secret 加错仓库
GH_PAGES_DEPLOY_TOKEN 要加在源码仓库 HebohangWebsiteHugo 的 Actions secrets 中,因为 workflow 是在这个仓库执行的。
3. token 没有目标仓库权限
如果 token 对 hebohang/hebohang.github.io 没有写权限,也会部署失败。
4. fine-grained PAT 授权到了错误的仓库
如果 token 只授权给了 HebohangWebsiteHugo,而没有授权给 hebohang/hebohang.github.io,也会得到 403。
5. token 已经过期或被撤销
重新生成一个新的 PAT 通常是最快的排查手段。
6. 把外部仓库和当前仓库混淆了
当前 workflow 是“源码仓库构建,再推送到站点仓库”。
所以权限检查要看的是目标仓库,不是源码仓库本身。
7. 用 classic PAT 做一次快速排查
如果你一时不确定是 fine-grained PAT 的哪个权限没配对,可以临时创建一个 classic PAT,直接授予:
| |
然后把它写入 GH_PAGES_DEPLOY_TOKEN 再跑一次 workflow。
如果 classic PAT 可以通过,而 fine-grained PAT 不行,那基本就可以确认问题出在 fine-grained PAT 的仓库选择或权限勾选上。
总结
这次问题看起来像是 Hugo 部署失败,实际上根因和 Hugo 本身关系不大,而是 GitHub Actions 的认证配置出了问题。
核心经验就是:
- 公开仓库也不能匿名写入
- 外部仓库部署通常需要单独的 PAT 或 deploy key
- token 不要明文写在 workflow 里
- Secrets 名称、仓库权限和目标仓库地址要完全对齐
如果只是单仓部署,事情会简单一些;但只要涉及“源码仓库”和“发布仓库”分离,认证和权限就必须单独处理清楚。