github+hexo博客搭建

搭建博客需求描述

说明:hexo可以快速部署个人博客,但是每次写文章需要使用hexo new post命令去新建文件就比较繁琐,我想搭建一个hexo项目和个人md笔记库分离。笔记就存放在md笔记库,当我git push推送到md笔记仓库会自动将md笔记发布到个人博客,搜寻资料整理了两种方案:

方案一:本地发布

​ 通过脚本将md笔记拷贝到原始hexo项目并本地生成发布推送github的hexo博客仓库

  • 优点:本地能发现脚本错误,方便调试
  • 缺点:需依赖本地配置nodejs,hexo,git等多个环境,不方便跨设备

方案二:远程发布

​ md笔记库与hexo项目库分离,md笔记库推送后自动触发github上配置的action,自动进行打包生成发布到github上的博客仓库

  • 优点:不需要管理原始hexo项目,不需要配置多余的本地环境(需git)

  • 缺点:需调试github的工作流脚本

总结

​ 个人比较倾向于本地专注md仓库管理,初次配置调试好github工作流后后续不太需要进行维护工作。且发布不需要依赖在本地重新配置环境,适合跨设备使用。

远程发布设计

主要管理md文件仓库,内嵌hexo原始项目,当md文件仓库的文件push后,github通过action运行配置的脚本将变动的脚步拷贝到hexo原始项目(自动建立资源文件夹和图片),并安装依赖环境发布hexo到个人博客项目。

项目结构

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
./blog-markdown
├── docker
│   └── 安装
│   └── 教程
│   └── 安装.md
├── generate.sh
├── golang
├── img.png
├── kubernetes
├── .hexo
│   └── blog (hexo原始项目)
│   ├── _config.landscape.yml
│   ├── _config.yml
│   ├── .deploy_git
│   ├── .github
│   ├── .gitignore
│   ├── db.json
│   ├── node_modules
│   ├── package-lock.json
│   ├── package.json
│   ├── public
│   ├── scaffolds
│   ├── source
│   └── themes
├── Linux
├── README.md
├── update.sh
└── 个人随记
└── github+hexo搭建博客.md

搭建流程

初始化hexo项目

安装hexo

1
npm install -g hexo-cli

安装部署插件

1
npm install hexo-deployer-git --save

安装主题(建议主题本地安装插件并预览一下,github上配置的action不配置主题更换变动)

1
npm install hexo-theme-butterfly --save

再_config.yaml配置git信息

1
2
3
4
deploy:
type: 'git'
repo:https://{token}@github.com/MistFjord/mistfjord.github.io
branch: master # GitHub Pages的默认分支

创建markdown笔记仓库

QQ_1749377790834

将hexo项目内嵌到该仓库(可参考上面的项目结构)

配置github的action工作流

QQ_1749378773339

工作流脚本如下:

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
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
name: Auto Sync
on:
push:
branches:
- main
- develop

jobs:
convert:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4

# 安装必要的依赖
- name: Setup Node.js
uses: actions/setup-node@v3
with:
node-version: '18'

- name: Install Hexo CLI
run: npm install -g hexo-cli

- name: Install Hexo Git
run: npm install hexo-deployer-git --save

# 安装路径处理工具
- name: Convert Structure
run: |
#!/bin/bash
# set -xe
set -eo pipefail # 严格错误处理

echo "=========================================================================="
echo "🚀 开始 Markdown 到 Hexo 的转换工作流"
echo "=========================================================================="

# ====== 全局配置 ======
MD_REPO_ROOT=$(pwd)
HEXO_DIR="${MD_REPO_ROOT}/.hexo/blog/source/_posts"
mkdir -p "$HEXO_DIR"
echo "源文件夹: $MD_REPO_ROOT"
echo "目标目录: $HEXO_DIR"

# ====== 辅助函数 ======

# 获取 Markdown 文件名格式化为 Hexo 名称
get_md_files_name() {
local include_dir=$1
local exclude_dir=$2
find "$include_dir" -type f -name "*.md" -not -path "$include_dir/.hexo/**" -not -path "$include_dir/node_modules/**" | while IFS= read -r file; do
# 跳过非目标目录的文件
[[ $file == *"/.hexo/"* ]] && continue
[[ $file == *"/node_modules/"* ]] && continue
[[ $file == *"$exclude_dir"* ]] && continue
local rel_path=${file#"$include_dir/"}
local hexo_name=$(echo "$rel_path" | tr '/' '-' | sed 's/\.md$//')
echo "$hexo_name"
done
}
# 初始化同步环境
init_sync() {
echo "⚙️ 初始化同步环境..."
mkdir -p "$HEXO_DIR"
}
# 清理孤儿文件
clean_orphan_files() {
echo ""
echo "🧹 开始清理孤儿文件..."

# 创建当前有效文件列表
get_md_files_name "$MD_REPO_ROOT" ".hexo" > .current_files.tmp
local clean_dir_list=()
local clean_file_list=()
# 查找所有需要处理的目标文件
find "$HEXO_DIR" -mindepth 1 -maxdepth 1 > tmpfile.txt
while IFS= read -r target_file; do
if [[ -d "$target_file" ]]; then
local hexo_name=$(basename "$target_file")
else
local hexo_name=$(basename "$target_file" .md)
fi
if ! grep -q "^$hexo_name$" .current_files.tmp; then
if [[ -d "$target_file" ]]; then
rm -rf "$target_file"
clean_dir_list+=("${hexo_name}")
echo " 🗑️ 已删除孤儿目录: $hexo_name/"
else
rm -f "$target_file"
clean_file_list+=("${hexo_name}.md")
echo " 🗑️ 已删除孤儿文件: $hexo_name.md"
fi
fi
done < tmpfile.txt
# 清理临时文件
rm -f tmpfile.txt .current_files.tmp
echo "✅ 孤儿文件清理完成"
echo " → 删除目录: ${#clean_dir_list[@]} 个"
for dir in "${clean_dir_list[@]}"; do
echo " - $dir/"
done

echo ""
echo " → 删除文件: ${#clean_file_list[@]} 个"
for file in "${clean_file_list[@]}"; do
echo " - $file"
done
}
# 转换路径为Hexo文件名格式
path_to_hexo_name() {
local rel_path="$1"
echo "$rel_path" | sed 's#/#-#g' | sed 's/\.md$//'
}
# 处理图片资源
process_images() {
local file="$1"
local output_dir="$2"

# 1. 创建对应的资源目录
mkdir -p "${output_dir}"

matches=$(grep -E '!\[.*\]\(([^)]+)\)' "$file") || {
if [ $? -ne 1 ]; then
echo "❌ grep执行失败" >&2
exit 1
fi
}
# 2. 提取并复制图片
if [[ -z "$matches" ]]; then
echo "ℹ️ 文件 $file 中未发现Markdown图片引用"
return 0
fi
echo "$matches" | while IFS= read -r line || [[ -n "$line" ]]; do
# 提取图片路径并进行判空
img_path=$(echo "$line" | sed -E 's/.*\(([^)]+)\).*/\1/')

# 1. 空路径检查 - 核心判空逻辑
if [[ -z "$img_path" ]]; then
echo "⚠️ 跳过空图片路径: $line"
continue # 跳过后续处理
fi
# 2. 处理不同类型图片引用
case "$img_path" in
http*|//*)
echo " 🌐 保持网络图片原样: $img_path"
;;
/*)
echo " ⚠️ 需要手动处理的绝对路径: $img_path" >&2
;;
*)
# 3. 安全路径处理 - 防止路径注入
sanitized_path=$(echo "$img_path" | sed 's/[;&|`$<>]//g')
local_img_path="${sanitized_path}"

# 4. 有效性验证(双路径检查)
if [[ -f "${local_img_path}" ]]; then
echo " 复制项目相对路径图片: $local_img_path"
cp -f "${local_img_path}" "${output_dir}/"
sed -i "s|([[:space:]]*${img_path//\//\\\/}[[:space:]]*)|($(basename "$local_img_path"))|g" "$file"

elif [[ -f "$(dirname "$file")/${local_img_path}" ]]; then
echo " 📁 复制同级目录图片: $(dirname "$file")/$local_img_path"
cp -f "$(dirname "$file")/${local_img_path}" "${output_dir}/"
sed -i "s|([[:space:]]*${img_path//\//\\\/}[[:space:]]*)|($(basename "$local_img_path"))|g" "$file"

else
# 5. 失败处理(带详细日志)
echo "❌ 图片未找到: '$local_img_path'" >&2
printf " ├─ 尝试位置: ${local_img_path}\n └─ 相对位置: $(dirname "$file")/${local_img_path}\n" >&2
fi
;;
esac
done
# 6. 空匹配处理 - 新增的保护机制
if ! grep -qE '!\[.*\]\(([^)]+)\)' "$file"; then
echo "ℹ️ 文件 $file 中未发现Markdown图片引用"
fi
}
# 获取相对路径
get_rel_path() {
local abs_path="$1"
local rel_path="${abs_path#$MD_REPO_ROOT/}"
[ "${rel_path}" == "${abs_path}" ] && rel_path="${abs_path#$MD_REPO_ROOT}"
echo "${rel_path%.md}" # 返回时移除.md后缀
}
# 创建 Hexo 文件
create_hexo_file() {
local src_file="$1" # 源文件路径
local hexo_file="$2" # 目标文件路径
local res_dir="$3" # 资源目录
local rel_path=$(get_rel_path "$src_file") # 获取相对路径
local dir_path=$(dirname "$rel_path") # 获取目录路径

# 创建父目录(必要时)
mkdir -p "$(dirname "$hexo_file")"

# 1. 处理图片资源
process_images "$src_file" "$res_dir"

# 2. 生成 Front Matter
local title=$(basename "$src_file" .md)
# 自动生成Front Matter
echo "---" > "$hexo_file"
echo "title: ${title}" >> "$hexo_file"
echo "date: $(date +"%Y-%m-%d %H:%M:%S")" >> "$hexo_file"

# 从路径提取分类和标签 (技术/前端/Vue.md → 分类:技术, 标签:[前端,Vue])
IFS='/' read -ra parts <<< "$dir_path"
echo "categories: [${parts[0]}]" >> "$hexo_file"

tags=""
for ((i=1; i<${#parts[@]}; i++)); do
tags+=", ${parts[$i]}"
done
echo "tags: [${tags:2}]" >> "$hexo_file"

# 保留原始内容
echo "---" >> "$hexo_file"
cat "$src_file" >> "$hexo_file"
}
# 文件同步主逻辑
sync_files() {
echo ""
echo "🔄 开始同步 Markdown 文件到 Hexo 项目..."

local created=()
local updated=()
local skipped=()

# 查找所有需要处理的MD文件
find "$MD_REPO_ROOT" -type f -name "*.md" -not -path "$MD_REPO_ROOT/.hexo/**" -not -path "$MD_REPO_ROOT/node_modules/**" > tmpfile.txt

while read -r src_file; do
# 跳过非目标目录的文件
[[ $src_file == *"/.hexo/"* ]] && continue
[[ $src_file == *"/node_modules/"* ]] && continue
rel_path=$(get_rel_path "$src_file")
hexo_name=$(path_to_hexo_name "$rel_path")
local hexo_file="${HEXO_DIR}/${hexo_name}.md"
local hexo_res_dir="${HEXO_DIR}/${hexo_name}" # 资源目录

# 判断同步条件
if [ ! -f "${hexo_file}" ]; then
echo " 🆕 新创建: $rel_path"
create_hexo_file "$src_file" "$hexo_file" "$hexo_res_dir"
created+=("${rel_path}")
else
if [ "$src_file" -nt "$hexo_file" ]; then
echo " 🔄 更新文件: $rel_path"
create_hexo_file "$src_file" "$hexo_file" "$hexo_res_dir"
updated+=("${rel_path}")
else
echo " $rel_path (无需更新)"
skipped+=("${rel_path}")
fi
fi
done < tmpfile.txt

rm -f tmpfile.txt
# 详细结果输出
echo ""
echo "🎯 文件同步完成!"
echo " → 新创建: ${#created[@]} 个"
for file in "${created[@]:0:10}"; do
echo " - $file"
done
[[ ${#created[@]} -gt 10 ]] && echo " ...及其他 ${#created[@]-10} 文件"

echo ""
echo " → 已完成更新: ${#updated[@]} 个"
for file in "${updated[@]:0:10}"; do
echo " - $file"
done
[[ ${#updated[@]} -gt 10 ]] && echo " ...及其他 ${#updated[@]-10} 文件"

echo ""
echo " → 已跳过: ${#skipped[@]} 个 (未更改)"
}
# 工作流主函数
main() {
init_sync
clean_orphan_files
sync_files
echo "✅ 所有转换任务已完成!"
echo "=========================================================================="
}

# 执行主工作流
main





# Hexo生成和部署
- name: Build
working-directory: .hexo/blog
run: |
npm install hexo-theme-butterfly --save
# 设置Git用户信息
git config --global user.name "MistFjord"
git config --global user.email "1392792445@qq.com"
# 调试信息
echo "Current directory: $(pwd)"
ls -la

# 安装依赖
npm install -g hexo-cli
npm install

# 执行Hexo命令
echo "=== Hexo Version ==="
hexo version

echo "=== Cleaning ==="
if ! hexo clean; then
echo "::error::Hexo clean failed"
exit 1
fi

echo "=== Generating ==="
if ! hexo generate; then
echo "::error::Hexo generate failed"
exit 1
fi

echo "=== Deploying ==="
if ! hexo deploy; then
echo "::error::Hexo deploy failed"
exit 1
fi

echo "All commands executed successfully"