摘要生成中...
AI 摘要
Hunyuan-lite

站内文章3年前,我将博客框架从 WordPress 切换到了 Hexo,并通过服务器进行托管,机制如下图:

preview

期间,我有尝试过将目前的发布方案交给 GitHub Action 去处理。但是当时我的博客发布的工作流特别复杂,这检查那检查,这生成那生成…通过对博客 站内文章关系图谱 方案改造后,我的博客工作流就变得简洁了很多。所以今天我想尝试能不能抽出一点时间一劳永逸解决这个问题。(结果今天的单休就泡汤了)

本文是 站内文章这篇文章 的后续补充

本文是对服务器托管静态文件的情况下,对本地发布步骤的改造。当然,如果你的公共目录就放在 GitHub,也许会简单多。不过那是另外一套架构的做法,估计也大差不差,这里就不再讨论。

GitHub 上有博客项目和主题的私有仓库

在开始之前,我们确保我们的博客项目的源代码已交给 GitHub 托管,后续操作将基于这个仓库进行 GitHub Actions 的构建:当仓库 main 分支变动时,执行 GitHub Action,将 public 目录发送到服务器。如果我们的博客配置中包含有敏感密钥,或者我们不想将文章 Markdown 内容直接公开,那么建议仓库保持私有仓库,除非我们已经处理好这些问题。

我们的主题也应该是交给 GitHub 管理的(主题开发者的仓库,或自己的私仓)。主题和博客项目的关系应当形成一种 Git Submodule 的关系。在博客项目中,首先删掉 theme 文件夹下我们使用的主题(注意备份,确保已经保存到远仓)。然后通过以下命令重新导入到博客项目中:

1
2
# 把主题仓库作为子模块添加 
git submodule add https://github.com/xxx/hexo-theme-xxx themes/xxx

根目录生成的 .gitmodules 记得妥善管理。子模块文件夹本身就是独立仓库,Git 会自动忽略。

SSH 的公钥和私钥

注意到,我们之前部署的主要流程是将本地电脑生成好的 public 文件夹发送的服务器中的仓库,中间是通过 SSH 链接的。现在我们想要 GitHub Actions 做这件事,那么我们必须生成另一对 SSH 公钥和私钥:

  • 公钥放在服务器的 /home/git/.ssh/authorized_keys 中;
  • 私钥放在 Actions secrets and variables 中。
1
2
# 生成专用密钥(4096位高强度加密,-f指定固定文件名deploy_key)
ssh-keygen -t rsa -b 4096 -f deploy_key

image.png

其他环境变量的配置

如果你在本地发布工作流中使用到了一些脚本,这些脚本依赖了一些本地计算机的环境变量,如 JavaScript 的类似这样的代码 process.env.TENCENT_API_SECRETID,那么也放进 Secrets 中。

GitHub 的 PAT 我也放进了 Secrets,这是为了保证子模块拉取的顺利进行。

另外,我们还需要补充 Git 的一些基础参数,这部分可以放在 VariablesVariablesSecrets 区别在于变量的值是否明文。

image.png

新增 Workflows

在博客根目录下新增相应文件夹和工作流文件:

image.png

内容参考如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
name: Hexo Deploy  

on:
push:
branches:
- main
workflow_dispatch:

jobs:
build-and-deploy:
runs-on: ubuntu-latest

steps:
- name: Checkout code
uses: actions/checkout@v4
with:
token: ${{ secrets.MY_GITHUB_TOKEN }}
submodules: true
fetch-depth: 0

- name: Setup Node.js
uses: actions/setup-node@v4
with:
node-version: '20'

- name: Cache node modules
id: cache-node-modules
uses: actions/cache@v4
with:
path: node_modules
key: ${{ runner.os }}-node-${{ hashFiles('**/package-lock.json') }}
restore-keys: |
${{ runner.os }}-node-

- name: Install dependencies
if: steps.cache-node-modules.outputs.cache-hit != 'true'
run: npm install

- name: Setup SSH Key
run: |
mkdir -p ~/.ssh
echo "${{ secrets.SSH_PRIVATE_KEY }}" > ~/.ssh/id_rsa
chmod 600 ~/.ssh/id_rsa
# 扫描服务器指纹,防止部署时被拦截
ssh-keyscan -t rsa 106.15.38.218 >> ~/.ssh/known_hosts

- name: Publish
env:
# 核心:将 GitHub Secrets 映射为环境变量,供 JS 和 Python 脚本读取
TENCENT_API_SECRETID: ${{ secrets.TENCENT_API_SECRETID }}
TENCENT_API_SECRETKEY: ${{ secrets.TENCENT_API_SECRETKEY }}

# Git 部署所需的明文变量
GIT_NAME: ${{ vars.GIT_USER_NAME }}
GIT_EMAIL: ${{ vars.GIT_USER_EMAIL }}
run: |
git config --global user.name "$GIT_NAME"
git config --global user.email "$GIT_EMAIL"
npm run publish

重新审视之前的工作流

基本步骤已经弄得差不多了,现在开始检查。检查 package.json 中,npm run publishpostpublishprepublish 工作流是否都正确。检查无误后,我们就可以进行 push 测试了。

push 的过程中,观察 Actions 中的日志,逐步排查问题。不断试错,不断解决即可。

问题解决

采用 GitHub Actions 方法发布时文章的更新时间错误

如果使用 GitHub Actions 进行发布,所有文章的更新时间可能会全部设定为当前时间。为了解决这个问题,我们需要为我们的文章提供正确的更新时间。

直接想到的办法是为所有文章的 Front-Matter 增加 updated 字段,但是这么做未免太麻烦。我们可以使用插件/脚本解决。

首先,关掉 Hexo 默认的「更新时间」的逻辑。在 _config.yaml 中,修改以下设置:

1
updated_option: "empty"

然后,在项目根目录的 scripts 文件夹下新增以下脚本:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
const { exec } = require('child_process');  
const { promisify } = require('util');
const path = require('path');

const execAsync = promisify(exec);

hexo.extend.filter.register('before_post_render', async function (post) {
// 1. 如果 front-matter 已有 updated,或配置已提供时间,直接跳过
// 配合 updated_option: empty,这里能完美过滤出需要 Git 时间的文章
if (post.updated) {
return post;
}

try {
// 2. 使用 path.resolve 确保在 CI 环境下的路径绝对可靠
const fullPath = path.resolve(hexo.source_dir, post.source);

// 3. 执行 Git 命令
// 使用 %at (Unix timestamp) 理由:它是纯数字,解析速度最快,且完全无视时区干扰
const { stdout } = await execAsync(
`git log -1 --format=%at -- "${fullPath}"`
);

const timestamp = stdout.trim();

if (timestamp) {
// 4. 转换为 JavaScript Date 对象
// Unix 时间戳是秒,JS Date 需要毫秒
const gitDate = new Date(parseInt(timestamp, 10) * 1000);

if (!isNaN(gitDate.getTime())) {
post.updated = gitDate;
}
}
} catch (err) {
// 只有在真正报错(如该文件未跟踪)时才记录调试信息
hexo.log.debug(`[GitTime] Could not get git log for ${post.source}. Reason: ${err.message}`);
}

return post;
});

这个脚本借助 Hexo 过滤器,在文章渲染前自动处理时间:

  • 如果文章已手动设置 updated 时间,则直接跳过处理;
  • 通过 Git 命令获取文件最后一次提交的时间,自动赋值给文章 updated 字段。
Obsidian 插件推荐:Linter

使用 Obsidian 管理的同学还可以使用 Linter,顺手管理 YAML 字段。

本文参考