Hexo 维护记录

前言

自前年兴冲冲地折腾好了 Hexo + Buftterfly 后便陷入长达一年半的虚无期。无他,一者发现搭博客「也就那么回事」,二者,此类框架要写好文章才能渲染,且没有第三方博客平台一键发布的便利。[1] 近来被推荐在简历附上个人博客,于是借此机会重新修整,作文以记之。

框架 | 主题

略有过时的 Hexo + Bufferfly,但初见一图流的惊艳让它始终是我的白月光,框架选型时没犹豫地闭眼冲了。插件系统提供了高度自由的定制空间,只是时间过去已久,没能记下魔改博客的每一步。时至今日,倒也没那么重要了。

Twikoo 评论

当初部署时的一个小坑。Vereel 部署时,如果使用 Gitlab 为仓库,则 Twikoo 必须扔在 Personal Projects 下,否则跟你要会员。

之前发现 Twikoo 挂了,检查后发现 i) 当时绑定的域名没在续费;ii) MongoDB 因为太久不活跃,把数据库关了。

按照站长的教程[2]重新盘了一遍。定期发一些评论吧。

SSL

见 OHTTPS 相关文章。

图床

对象存储使用七牛云。上传接口使用 PicUploader,功能齐全。另部署 ImgUrl 作为备用的服务器本地图床。

CDN

Gitlab Pages 墙内加载不稳定,速度时好时坏。终于决定做 CDN 加速。[3] [4] [5]

七牛云 CDN

这里要说明,为什么博客用了两个域名。

主机记录 说明 备注
blog Gitlab Pages 绑定的域名 因为不是在用户同名仓库[6]弄的,必须绑定一个纯净域名方可 CDN,同时一定程度上避免被墙。
www 加速域名 注意从绑定域名回源。

Hexo 资源路径不兼容子路径部署。即,_config.yml 中填入的 url 若带有子路径,则用绑定域名访问是无法正常加载各种 assets 的,反之亦然。所以在没上 CDN 的时候要填 blog.miyamura.top,绑了之后填这个其实也行,但是 RSS 和 sitemap 等就是按源站来,最好改成加速域名。

1
url: https://www.miyamura.top

缓存期限暂时设置与源站同步。这样配置之后得到 www 加速域名,实测速度尚可。

DNS の坑

我们腾讯云太伟大辣!起因是想把根域名重定向到 www,这有很多好处,如避免 SEO 分流等。正常情况下是 Nginx 来做。但由于是托管博客,自然是无法这样操作。几种重定向方式如下。

DNS 重定向 即直接 CNAME 到 www,尝试后 403 Forbidden,是七牛云给拒了。作罢。

HTTP 重定向 这也是最正统的方式,走 Nginx 即为这种方式。对于托管的服务,如果 DNS 托管在 CloudFlare 就好办了,通过配置转发规则可以进行 301 / 302 等重定向。

那么腾讯云是如何支持的呢?答案是不支持,其提供了一个叫显 / 隐性 url[7] 的记录类型,这个东西是类似 HTTP 重定向的,但它只允许你从 HTTP → HTTPS。最后还是用了这种方式,当弄着玩了。下次买新域名及时托管到 CloufFlare,谨记。

前端重定向 字面意思,过于丑陋,不弄。

监控

可用性监控。

首先从 UptimeRobot 添加站点监控,获取 API-KEY,后按照 imsyy/site-status 部署监控页。

自动化

懒是生产力进步的第一要素。

题外话 | StackEdit

前文提及,博客拖更一年的原因之一便是更新麻烦,但实际上这一年一直在写,用的是 Markdown 编辑器 + Github 的方式。编辑器的选择上,因为对 Web 的青眼和跨设备工作的便利性考量,一直用的 StackEdit。

这个开源平台我是十分推荐的,轻量级 / 一键同步 / 现代化的渲染器和语法支持性,附上 Stylus 自己注入一些样式进行美化,真是好用。但是也有令人恼火的地方,即定期乱改部分文章内容,看起来像 merge 错误一样。一直没有定位到成因,所以最后的方案就是凭直觉,预感网络要出问题的时候就直接清空工作区重新拉取。

文章 - 博客解耦

以往的文件组织方式都是把文章和博客框架放在一起,写起来麻烦,有类似 Hexon 之类的配套写作工具,但实际体验也不尽如人意。

那么,把文章和博客分成两个仓库 P 和 B,然后

  1. 每天定时触发 P 的流水线[8],检查 24h 内是否有更新;若执行流水线成功,则通过 Trigger 触发 B 的流水线。
  2. B 的流水线触发后,从 P 获取最新一次的构建产物,然后进行 pages 部署。

下文介绍实操过程。

Overview

仓库 平台 CI
文章 Github (Private) Github Actions
博客框架 Gitlab (Public) Gitlab CI

所有 CI 脚本均使用 ChatGPT (GPT-4o) + CoPilot (Claude 3.7) 编写。

Github Actions | 任务

解耦的好处体现出来了,因为

  1. 可见性。不希望所有文章都展示。
  2. 文章元数据 (front matter) 组织灵活。

Hexo 的文章元数据通过以下方式携带

1
2
3
4
5
---
# front-matter
---

... # 正文

这种方式在一般的 Markdown 渲染器里都会把元数据作为文本渲染出来,看起来较为丑陋。因此,通过 HTML 注释的方式嵌入

1
2
3
4
5
<!--
# front-matter
-->

... # 正文

这样,脚本的任务就很清晰了,首先遍历文章,将嵌入的 HTML 注释替换为 Hexo front-matter,同时,我们自定一个 display 属性,当且仅当为 true 时才允许该文章进入 artifacts/,以上。

Github Actions | 脚本

需要从 Gitlab 仓库配置一个 Pipeline Trigger,加入 Github 仓库的 Secrets。[9] Gitlab 会在 Trigger 的下方给你一个使用样例,内含当前仓库的 Project ID,这个 ID 是可以直接在 API 里使用的。设计上,还应允许手动触发。

关于脚本。定时任务的写法如下

1
2
3
4
on:
schedule:
- cron: '0 21 * * *' # 每天 05:00 (UTC+8) 触发,注意 UTC 时间
workflow_dispatch: # 也允许手动触发

API 调用形如

1
2
3
4
5
6
7
8
9
curl -X POST \
--fail \
-F token="${{ secrets.GITLAB_TRIGGER_TOKEN }}" \
-F ref=master \ # 要在对方仓库哪个分支下工作
-F "variables[GITHUB_REPOSITORY]=${GITHUB_REPOSITORY}" \
-F "variables[GITHUB_WORKFLOW_ID]=${GITHUB_WORKFLOW_ID}" \
-F "variables[GITHUB_RUN_ID]=${GITHUB_RUN_ID}" \
-F "variables[TRIGGER_SOURCE]=github_action" \
https://gitlab.com/api/v4/projects/${GITHUB_PROJECT_ID}/trigger/pipeline

一些小坑。算时间的时候,尽量不要用 schedule - 24 这种方式,因为定时任务触发有延迟,从触发时间开始统计容易漏掉少部分时段。正确的方式是

1
2
3
4
5
6
7
START=$(date -u -d "yesterday 21:00" --iso-8601=seconds)

# [更新] 调整 END 时间为 now,从昨天早上 5 点统计到脚本触发时间
# END=$(date -u -d "today 21:00" --iso-8601=seconds)
END=$(date -u -d "now" --iso-8601=seconds)

git log --since="${{ steps.time.outputs.start }}" --until="${{ steps.time.outputs.end }}" --pretty=format:"%h%n"

为什么是 yesterday 和 today 自己想一下。另注意 git log 输出格式带上 %n,否则 wc -l 检测不到行数,无法进行下一步条件判断。

需要注意的是定时任务只能在默认分支触发,比如将 CI 脚本提交到 master。这样之后仍然需要手动触发一次流水线,然后方可进入 On Schedule 状态。

Gitlab CI | 任务

获取 artifacts → 解压到 source/_posts/ → 执行 pages 的任务,触发部署

Gitlab CI | 脚本

这一侧的流程复杂一些。

  1. 需要有三种触发方式
    1. Trigger 触发
    2. Commit 触发
    3. 手动触发
  2. Trigger 触发时可以直接接收对方携带的 RUN_ID、ARTIFACT_ID 等参数。其他触发方式则需要自行从对方的最新一次成功构建中获取。

由于文章仓库是 Private,需要在 Github 一侧配置 Personal Access Token (PAT),然后作为 Gitlab CI 的流水线变量。

在 Github Actions API 这里耽误了一点时间。参考 [10] 编写

1
2
3
4
5
curl -L \
-H "Accept: application/vnd.github+json" \
-H "Authorization: Bearer <YOUR-TOKEN>" \
-H "X-GitHub-Api-Version: 2022-11-28" \
https://api.github.com/repos/OWNER/REPO/actions/artifacts

主要工作是获取上次成功构建的 RUN_ID 和 ARTIFACT_ID,获取完就万事大吉。(是吗?)

一些小坑。由于 Gitlab 的默认分支是 main,我将其改为 master,但没有将其设为受保护分支。因此,流水线变量默认注入为空,且不报错,看输出 Github API 一直返回 401,真是十分恼火。解决方案是在变量设置种取消勾选「仅在受保护分支生效」。

Trigger 触发被视为一种特殊的分支或 Tag。因此,不要手欠加上 only: master 条件。

联合调试了数次之后终于是解决了所有毛病。至此,一套完整自动化流程实现。

Summary

不要尝试一次性解决所有的问题,因为问题后面还是问题。问题就是矛盾。矛盾是普遍存在的。——《毛泽东选集》

一些遗留问题。

  1. 部分文章在博客发布后内容为空。
  2. 添加 mermaid 支持
  3. 个人主页模板 | imsyy/home

此外,这两个 CI 将近 400 行,即便有 Claude 3.7 加持,仍然调试了十来个小时才彻底贯通。对运维产生了一点阴影。


  1. 实际上部署过 Hexon,云端编辑 + 一键发布是有了。然而作为 Markdown 编辑器还是不够舒服。 ↩︎

  2. Twikoo 评论系统 ↩︎

  3. 使用 CDN 加速你的博客 | 沐雨浥尘 ↩︎

  4. 域名 DNS 服务托管至 Cloudflare 以及 301 重定向的配置 | 半方池水半方田 ↩︎

  5. 为什么把没带 www 域名,通过 301 重定向到带 www 的上面 ? - UCloud 云社区 ↩︎

  6. 同名仓库可以得到一个纯净的博客域名,否则是 <group>.gitlab.io/<project> 形式的链接。 ↩︎

  7. 云解析 DNS 隐/显性 URL 记录_腾讯云 ↩︎

  8. On Schedule,而非每次提交触发。这样做的理由和 StackEdit 有关,因为其每个文件是分开 push 的,每次提交都触发是浪费的。抛开不谈,博客本身也没有必要更新太快,反正写了也没人看,况且 CDN 还跟不上呢。因此,选择每天早 5:00 (UTC 21:00) 定时进行一次 Nightly Build. ↩︎

  9. 在 GitHub Actions 中使用机密 - GitHub 文档 ↩︎

  10. GitHub Actions 构件的 REST API 终结点 - GitHub 文档 ↩︎