前言
博客迁移到 GitHub Actions 后,源码、构建结果和线上目录有了清晰边界。本篇不讨论按钮操作,重点解释一条可靠发布链路为什么要有锁、暂存目录、验证、备份和受保护文件。
一、先分清三个位置
GitHub main 分支 |
线上目录不是源码仓库。直接修改服务器里的 HTML,下次部署会被构建结果覆盖;把 public/ 提交到 Git,也会同时维护源码和产物两份内容。
因此 .gitignore 忽略:
node_modules/ |
二、什么操作会触发发布
工作流监听 main 分支推送,也允许手工触发:
on: |
并发配置为:
concurrency: |
同一时间只允许一条生产发布链路运行。cancel-in-progress: false 表示新提交不会在旧版本复制到一半时强行终止它,而是排队等待,避免线上目录处于半更新状态。
三、为什么使用 npm ci 而不是 npm install
- uses: actions/setup-node@v4 |
npm ci 严格按照 package-lock.json 安装。如果 package.json 和锁文件不一致会直接失败,不会悄悄选一个新版本。
对静态站来说,可重复构建很重要:同一个提交今天和下周构建,应该得到兼容的依赖组合。
四、构建为什么分三步
- name: Build site |
1. clean
删除上次生成的 public/,防止已经从源码删除的旧文件残留。
2. build
执行 hexo generate,把 Markdown、模板和数据转换为静态站。
3. verify
构建成功只代表 Hexo 没有抛出致命错误,不代表页面一定正确。验证脚本还会检查:
index.html和404.html是否存在;- 所有站内
href、src是否指向真实文件; - 首页是否泄漏相册 JSON;
- 统计页数据和 ECharts 是否存在;
- ECharts 是否错误地加载到所有页面;
- 非文章页面头图是否按预期移除;
- 页脚、归档链接等关键定制是否仍然存在。
验证失败时,流程在上传前停止,错误版本不会到达服务器。
五、为什么先上传到 staging
Actions 没有直接把文件同步到网站根目录,而是传到:
/home/blogdeploy/gyyra-blog-staging/ |
上传命令使用:
rsync -az --delete \ |
-a保留目录结构和常规属性;-z传输时压缩;--delete删除暂存区中已不属于新版本的文件。
如果网络中途断开,受影响的只是暂存区,线上网站仍保持上一个完整版本。
六、服务器激活脚本做了什么
上传完成后,Actions 通过 SSH 执行一个受控脚本:
sudo /usr/local/sbin/deploy-gyyra-blog |
脚本开头启用严格模式:
set -Eeuo pipefail |
含义是命令失败、未定义变量或管道中间失败时立即退出,避免错误被忽略后继续覆盖线上目录。
七、部署锁怎样防止两个版本互相覆盖
exec 9>/var/lock/deploy-gyyra-blog.lock |
脚本占用一个文件锁。另一个发布同时到达时无法获得锁,会直接失败,而不是两份 rsync 交叉写入同一个目录。
GitHub Actions 的并发控制属于云端第一道保护,服务器文件锁是第二道保护。
八、为什么激活前还要检查文件
test -f "$staging/index.html" |
这是最小烟雾检查:暂存区必须像一个完整网站,目标目录也必须已经存在。
它不能替代完整验证,但可以阻止路径写错、上传空目录等明显事故。
九、备份怎样产生
激活新版本前,脚本创建带时间戳的备份目录:
stamp=$(date +%Y%m%d-%H%M%S) |
如果新版本上线后发现视觉问题,可以从最近备份恢复静态站,不必等待重新构建。
十、为什么 .well-known 和 .user.ini 不能删除
网站目录里有些文件不由 Hexo 生成:
.well-known/可能用于 HTTPS 证书验证;.user.ini是宝塔面板生成的目录保护文件。
生产同步使用 --delete 时,如果不排除它们,rsync 会认为这些文件“不在新版本中”并删除。
正确激活命令:
rsync -a --delete \ |
--chown=www:www 让 Nginx 使用的账号保持正确所有权。
十一、SSH 密钥为什么放在 Secrets
工作流通过以下 Secrets 获取连接信息:
SERVER_SSH_KEY |
私钥不能写进仓库。known_hosts 也不能简单关闭验证,否则攻击者可能伪装服务器接收部署文件。
工作流给私钥和 known_hosts 设置 600 权限,SSH 才会接受这些文件。
十二、代码更新了但线上没变化,先查哪一层
可以按四层顺序排查:
1. GitHub:提交是否已经进入 main |
不要一上来反复修改代码。先确定变化在哪一层停止,才能避免把部署问题误当成程序问题。
十三、静态资源缓存怎样处理
HTML 通常更新较快,但 CSS 和 JS 可能被浏览器或反向代理缓存很久。修改资源后可以更新查询版本号:
<script src="/js/home-banner-carousel.js?v=20260715-2"></script> |
浏览器把不同查询字符串视为不同缓存键,会重新请求文件。
调试时在开发者工具 Network 面板确认:
- 请求地址是不是新版本号;
- 状态是
200还是来自 memory/disk cache; - Response 内容是不是新代码;
- 页面 HTML 是否仍引用旧地址。
十四、为什么直接打开图片可能显示 404
线上资源启用了防盗链时,服务器可能检查 Referer。直接把图片地址粘贴进新标签页时没有来自博客首页的来源信息,于是返回 404;在博客页面中加载却是正常的。
验证这类资源时应:
- 从实际博客页面打开开发者工具观察请求;
- 或在请求中携带
Referer: https://blog.gyyra.cn/; - 同时检查 Nginx 日志,区分“文件不存在”和“规则拒绝”。
这也是为什么线上排查不能只看一个直接 URL 的状态码。
十五、日常发布流程
修改源码 |
十六、维护入口
- 自动化步骤:
.github/workflows/deploy.yml - 服务器激活脚本:
ops/deploy-gyyra-blog - 构建验证:
tools/verify-build.mjs - 依赖版本:
package.json与package-lock.json - 发布说明:
README.md
结语
可靠部署的重点不是“自动上传”,而是上传前验证、上传时隔离、激活前备份、激活时加锁,并明确哪些服务器文件必须被保护。






