原先博客需要使用typora+picgo+typecho,其中typora编写完毕后需要复制到typecho后台去,极其不方便,然后经过高人指点,我对该软件交互使用开发了新高度
obsidian是一个开源的笔记/文档编写软件,软件开放性不错,可以装插件使用。主要优点是基于git 的同步,多端提交的内容可以看到历史记录写了什么,甚至可以回头复制误删的内容。
首先放出搭建思路

看起来很复杂是吧,下面开始列出需要此框架的md大纲,如果懂一些便于跳到下一个环节

注: 以下基于windows平台,但是不代表配置方面差别很大,尤其是git,linux反而更好用

gitee github仓库创建与同步

注:gitee 同步到github或者 通过hook同步到gitee需要github token,建议申请后自行加密备份到网盘或者本地不起眼的位置(可以与github登录密码一致防止忘记),否则你删除或重新创建了后续仓库都要重新配置token,麻烦且得不偿失

gitee官网: https://gitee.com/
github官网: https://github.com/

这里不教怎么创建账号,如果真不知道怎么创建,百度一下是很重要的

创建仓库

github

创建仓库

这里创建的md由于部分文档不便透露,就需要privite(私密)
image.png
image.png
这样基本仓库就建好了,推荐吧Add README 开启,不然你需要手动创建分支

获取token

image.png
image.png
image.png
image.png
建议是这几个权限,当然你要全开也没问题,不用的key一定要删除
image.png

gitee

创建仓库

从github/gitlab导入仓库

如果你github设置好了,gitee可以快捷导入github仓库

image.png
找到导入github仓库,然后导入你github创建的仓库就行
image.png

全新部署

如果你只想搞gitee 不搞后续的话如图所示(和github差不多)
image.png
image.png

仓库互同

首先需要获得github token(看上面github部分)

obsidian、git下载以及配置

下载地址

git下载地址: https://github.com/git-for-windows/git/releases/download/v2.54.0.windows.1/Git-2.54.0-64-bit.exe
obsidian下载地址: https://obsidian.md/download
文件加速下载(百度云盘代理): https://pan.nuoyis.net

安装

先把git安装,然后安装obsidian,安装这里不重点讲解,全程下一步就行

obsidian配置

首先你的obsidian首次进入会是这个界面
image.png

点击新建仓库,起名称不影响远程仓库名,然后指定仓库位置,我的仓库位置在此电脑->文档里,可自定义位置。创建完毕后如图所示
image.png
然后左边可以创建文件夹,也可以创建

github actions 分仓库配置

makedown仓库

流水线

流水线由openclaw编写,测试无问题,可自行二次修改,这里直接粘贴防止后续下载失效,感谢你的理解
放在仓库根目录下的脚本
add-frontmatter.sh

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
#!/usr/bin/env bash
# add-frontmatter.sh
# 为没有 YAML frontmatter 的 Markdown 文件添加头部
#
# 用法: ./add-frontmatter.sh <目录1> [目录2] [目录3] ...
#
# 逻辑:
# 1. 已有 frontmatter → 提取 date/categories/title 复制到新格式
# 2. 没有 frontmatter → 根据 git commit 时间生成 date,文件名生成 title,第一层目录名生成 categories

set -euo pipefail

RED='\033[0;31m'
GREEN='\033[0;32m'
YELLOW='\033[1;33m'
NC='\033[0m'

log_info() { echo -e "${GREEN}[INFO]${NC} $*"; }
log_warn() { echo -e "${YELLOW}[WARN]${NC} $*"; }
log_error() { echo -e "${RED}[ERROR]${NC} $*"; }

# -------------------------------------------------------
# 从 Markdown 文件提取已有 frontmatter 字段
# 返回: title / date / categories (全局变量)
# -------------------------------------------------------
extract_frontmatter() {
local file="$1"
_TITLE=""
_DATE=""
_CATEGORIES=""
_TAGS="[]"

# 取 --- 之间的内容
local in_front=0
local fm_lines=()
while IFS= read -r line; do
if [[ "$line" == "---" ]]; then
((in_front++)) || true
if (( in_front >= 2 )); then
break
fi
continue
fi
if (( in_front == 1 )); then
fm_lines+=("$line")
fi
done < "$file"

for line in "${fm_lines[@]}"; do
# title
if [[ "$line" =~ ^title:\ *\"?(.+?)\"?\ *$ ]]; then
_TITLE="${BASH_REMATCH[1]}"
# 去掉可能残留的引号
_TITLE="${_TITLE%\"}"
_TITLE="${_TITLE#\"}"
fi
# date
if [[ "$line" =~ ^date:\ *(.+)$ ]]; then
_DATE="${BASH_REMATCH[1]}"
_DATE="${_DATE%"${_DATE##*[![:space:]]}"}" # trim trailing
fi
# categories (单行列表形式: categories: [a, b])
if [[ "$line" =~ ^categories:\ *\[(.+)\] ]]; then
_CATEGORIES="${BASH_REMATCH[1]}"
_CATEGORIES="${_CATEGORIES// /}" # 去空格
fi
# categories (多行形式,取第一个 - xxx)
if [[ "$line" =~ ^-\ (.+)$ ]] && [[ -z "$_CATEGORIES" ]]; then
_CATEGORIES="${BASH_REMATCH[1]}"
fi
# tags
if [[ "$line" =~ ^tags:\ *(.+)$ ]]; then
_TAGS="${BASH_REMATCH[1]}"
fi
done
}

# -------------------------------------------------------
# 判断文件是否已有 frontmatter
# -------------------------------------------------------
has_frontmatter() {
local file="$1"
local first_line
first_line=$(head -1 "$file" | tr -d '[:space:]')
[[ "$first_line" == "---" ]]
}

# -------------------------------------------------------
# 从 git 获取文件的最早提交时间 (ISO 格式)
# -------------------------------------------------------
git_commit_date() {
local file="$1"
local dir
dir=$(dirname "$file")

# 找到最近的 .git 目录
local git_dir=""
local check="$dir"
while [[ "$check" != "/" ]]; do
if [[ -d "$check/.git" ]]; then
git_dir="$check"
break
fi
check=$(dirname "$check")
done

if [[ -n "$git_dir" ]]; then
local rel_path
rel_path=$(realpath --relative-to="$git_dir" "$file")
# 获取最早的提交时间
local ts
ts=$(git -C "$git_dir" log --diff-filter=A --follow --format="%aI" -- "$rel_path" 2>/dev/null | tail -1)
if [[ -n "$ts" ]]; then
# 转成 YYYY-MM-DD HH:MM:SS
echo "${ts:0:10} ${ts:11:8}"
return
fi
fi

# fallback: 用文件修改时间
local mtime
mtime=$(stat -c %y "$file" 2>/dev/null || stat -f %Sm "$file" 2>/dev/null)
echo "${mtime:0:19}"
}

# -------------------------------------------------------
# 从文件名生成 title(去掉扩展名,把 - 和 _ 变空格)
# 你可以自定义这个函数来改变 title 生成逻辑
# -------------------------------------------------------
filename_to_title() {
local file="$1"
local basename
basename=$(basename "$file")
# 去掉扩展名,其余原样保留(如 C语言-关键字.md → C语言-关键字)
basename="${basename%.md}"
basename="${basename%.markdown}"
echo "$basename"
}

# -------------------------------------------------------
# 从第一层目录名生成 category
# -------------------------------------------------------
get_category() {
local file="$1"
local base_dir="$2"
local rel_path
rel_path=$(realpath --relative-to="$base_dir" "$file")
local first_dir="${rel_path%%/*}"
# 如果文件直接在 base_dir 下,分类为 default
if [[ "$first_dir" == "$rel_path" ]]; then
echo "default"
else
echo "$first_dir"
fi
}

# -------------------------------------------------------
# 写入 frontmatter 到文件(插入到开头)
# -------------------------------------------------------
write_frontmatter() {
local file="$1"
local title="$2"
local date="$3"
local categories="$4"
local tags="${5:-[]}"

# 构建 frontmatter
local fm="---"
fm+=$'\n'"title: ${title}"
fm+=$'\n'"date: ${date}"
if [[ -n "$categories" ]]; then
fm+=$'\n'"categories: [${categories}]"
fi
fm+=$'\n'"tags: ${tags}"
fm+=$'\n'"---"$'\n'

# 如果已有 frontmatter,先去掉
if has_frontmatter "$file"; then
# 去掉第一个 --- 和第二个 --- 之间的内容(含两行 ---)
local tmp
tmp=$(awk '
BEGIN { found=0; in_fm=0 }
/^---[[:space:]]*$/ {
if (found == 0) { found=1; in_fm=1; next }
else if (in_fm == 1) { in_fm=0; next }
}
in_fm == 0 { print }
' "$file")
echo "$fm$tmp" > "$file"
else
# 直接在前面插入
local content
content=$(cat "$file")
echo "$fm$content" > "$file"
fi
}

# -------------------------------------------------------
# 主逻辑
# -------------------------------------------------------
main() {
if [[ $# -eq 0 ]]; then
echo "用法: $0 <目录1> [目录2] ..."
echo ""
echo "为没有 YAML frontmatter 的 Markdown 文件添加头部。"
echo "已有 frontmatter 的文件会提取字段重新格式化。"
exit 1
fi

local processed=0
local skipped=0
local errors=0

for base_dir in "$@"; do
if [[ ! -d "$base_dir" ]]; then
log_error "目录不存在: $base_dir"
((errors++)) || true
continue
fi

log_info "扫描目录: $base_dir"

# 遍历所有 .md / .markdown 文件(只第一层及子目录,跳过第二层以下由 find 控制)
while IFS= read -r -d '' file; do
# 跳过隐藏文件和 node_modules 等
[[ "$file" == */.* ]] && continue
[[ "$file" == */node_modules/* ]] && continue

log_info "处理: $file"

if has_frontmatter "$file"; then
# 已有 frontmatter → 提取字段
extract_frontmatter "$file"
local title="$_TITLE"
local date="$_DATE"
local categories="$_CATEGORIES"
local tags="$_TAGS"

# 如果提取不到 date,用 git 时间
if [[ -z "$date" ]]; then
date=$(git_commit_date "$file")
log_warn " frontmatter 中无 date,使用 git commit 时间: $date"
fi

# 如果提取不到 title,用文件名
if [[ -z "$title" ]]; then
title=$(filename_to_title "$file")
log_warn " frontmatter 中无 title,使用文件名: $title"
fi

# 如果提取不到 categories,用目录名
if [[ -z "$categories" ]]; then
categories=$(get_category "$file" "$base_dir")
log_warn " frontmatter 中无 categories,使用目录名: $categories"
fi

# 重新写入格式化后的 frontmatter
write_frontmatter "$file" "$title" "$date" "$categories" "$tags"
log_info " ✓ 已更新 frontmatter (title=$title, date=$date, cat=$categories)"

else
# 没有 frontmatter → 生成
local title
title=$(filename_to_title "$file")

local date
date=$(git_commit_date "$file")

local categories
categories=$(get_category "$file" "$base_dir")

write_frontmatter "$file" "$title" "$date" "$categories" "[]"
log_info " ✓ 已添加 frontmatter (title=$title, date=$date, cat=$categories)"
fi

((processed++)) || true

done < <(find "$base_dir" -mindepth 1 -type f \( -name "*.md" -o -name "*.markdown" \) -print0)
# 不限深度,所有 md 都处理
done

echo ""
log_info "完成!处理: $processed | 跳过: $skipped | 错误: $errors"
}

main "$@"

放在.github/workflows内的流水线

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
# sync-to-hexo.yml
# 放在 Repo A (obsidian-sync) 的 .github/workflows/ 下
#
# 功能:
# 1. 当 public/ 目录有变更时触发
# 2. 处理 public/ 下所有 md 文件的 frontmatter
# 3. 推送到 Repo B 的 source/_posts/
#
# 需要的 Secrets (在 makedown 仓库的 Settings → Secrets 配置):
# HEXO_REPO_TOKEN - 一个有 repo 权限的 GitHub PAT (Fine-grained token)
# 用于 push 到 blog-inset

name: Sync to Hexo Blog

on:
push:
branches: [main, master]
paths:
- 'public/**'

# 也支持手动触发
workflow_dispatch:

# 避免同时跑多次
concurrency:
group: sync-hexo
cancel-in-progress: true

jobs:
sync:
runs-on: ubuntu-latest
steps:
# ── 1. 拉取 Repo A(当前仓库)──
- name: Checkout Repo A
uses: actions/checkout@v4
with:
fetch-depth: 0 # 需要完整 git history 来获取文件创建时间

# ── 2. 拉取 Repo B(Hexo 博客仓库)──
- name: Checkout Repo B
uses: actions/checkout@v4
with:
repository: nuoyis/blog-inset
token: ${{ secrets.HEXO_REPO_TOKEN }}
path: hexo-blog
fetch-depth: 1

# ── 3. 检查 public/ 是否存在 ──
- name: Check public directory
id: check
run: |
if [ -d "public" ] && [ -n "$(find public/ -type f -name '*.md' 2>/dev/null)" ]; then
echo "has_content=true" >> "$GITHUB_OUTPUT"
echo "✓ public/ 目录存在且有 md 文件"
else
echo "has_content=false" >> "$GITHUB_OUTPUT"
echo "⚠ public/ 目录不存在或无 md 文件,跳过"
fi

# ── 4. 处理 public/ 下的 md 文件,添加/修复 frontmatter ──
- name: Process frontmatter
if: steps.check.outputs.has_content == 'true'
run: |
chmod +x add-frontmatter.sh
./add-frontmatter.sh public/

# ── 5. 智能增量同步到 Repo B 的 source/_posts/ ──
- name: Sync to Hexo source
if: steps.check.outputs.has_content == 'true'
run: |
TARGET="hexo-blog/source/_posts"
STAGING=$(mktemp -d)

mkdir -p "$TARGET"

# ── 5a. 把 public/ 打平到临时目录 ──
cd public/
find . -mindepth 1 -type f \( -name "*.md" -o -name "*.markdown" \) | while read -r file; do
[[ "$file" == */.* ]] && continue

filename=$(basename "$file")

# 同名文件加分类前缀
if [ -f "$STAGING/$filename" ]; then
dir_prefix=$(echo "$file" | sed 's|^\./||' | cut -d'/' -f1)
if [ "$dir_prefix" != "$filename" ]; then
filename="${dir_prefix}-${filename}"
fi
fi

cp "$file" "$STAGING/$filename"
done
cd ..

echo "📁 临时目录文件:"
ls "$STAGING/"
echo ""
echo "📁 _posts 现有文件:"
ls "$TARGET/" 2>/dev/null || echo "(空)"
echo ""

# ── 5b. 去掉 md 文件的 YAML frontmatter,只留正文 ──
strip_frontmatter() {
awk '
BEGIN { found=0; in_fm=0 }
/^---[[:space:]]*$/ {
if (found == 0) { found=1; in_fm=1; next }
else if (in_fm == 1) { in_fm=0; next }
}
in_fm == 0 { print }
' "$1"
}

# ── 5c. 逐个文件比对,智能同步 ──
added=0
updated=0
skipped=0

for staging_file in "$STAGING"/*.md "$STAGING"/*.markdown; do
[ -f "$staging_file" ] || continue
filename=$(basename "$staging_file")
target_file="$TARGET/$filename"

if [ ! -f "$target_file" ]; then
# _posts 里没有 → 新文章,带头部复制
cp "$staging_file" "$target_file"
echo "➕ 新增: $filename"
((added++)) || true
else
# _posts 里有 → 比较正文是否变化
staging_body=$(strip_frontmatter "$staging_file")
target_body=$(strip_frontmatter "$target_file")

staging_sha=$(echo "$staging_body" | sha256sum | cut -d' ' -f1)
target_sha=$(echo "$target_body" | sha256sum | cut -d' ' -f1)

if [ "$staging_sha" != "$target_sha" ]; then
# 正文有变化 → 替换正文,保留 _posts 原来的头部
original_fm=$(awk '
BEGIN { found=0; in_fm=0 }
/^---[[:space:]]*$/ {
if (found == 0) { found=1; in_fm=1; print; next }
else if (in_fm == 1) { in_fm=0; print; next }
}
in_fm == 1 { print }
' "$target_file")

new_body=$(strip_frontmatter "$staging_file")

# 写入:原头部 + 新正文
{ echo "$original_fm"; echo ""; echo "$new_body"; } > "$target_file"
echo "🔄 更新: $filename (保留原头部)"
((updated++)) || true
else
echo "⏭️ 跳过: $filename (内容无变化)"
((skipped++)) || true
fi
fi
done

rm -rf "$STAGING"

echo ""
echo "✓ 同步完成: 新增 $added | 更新 $updated | 跳过 $skipped"

# ── 6. 提交并推送到 Repo B ──
- name: Push to Repo B
if: steps.check.outputs.has_content == 'true'
run: |
cd hexo-blog

# 配置 git
git config user.name "github-actions[bot]"
git config user.email "github-actions[bot]@users.noreply.github.com"

# 先 add,再检查是否有实际差异(包含未跟踪的新文件)
git add source/_posts/

echo "📋 Git status:"
git status --short
echo ""

if git diff --cached --quiet; then
echo "没有变更,跳过推送"
exit 0
fi

git commit -m "chore: sync articles from obsidian [$(date +%Y-%m-%d)]"
git push origin HEAD

echo "✓ 已推送到 Hexo 仓库"

hexo仓库

依旧去Settings → Secrets 配置 名叫token的机密变量,防止token泄露

Hexo本地部署和安装

安装nodejs

1
下载 https://nodejs.org/dist/v24.15.0/node-v24.15.0-x64.msi

配置加速源

1
2
3
npm config set registry https://registry.npmmirror.com
npm install -g pnpm
pnpm config set registry https://registry.npmmirror.com

安装hexo

1
2
3
4
pnpm install -g hexo-cli
hexo init
pnpm install
pnpm install hexo-deployer-git --save

配置config.yaml,修改以下位置成以下内容(url是你的仓库地址),其他自行修改

1
2
3
4
5
6
7
8
9
# Deployment
# Docs: https://hexo.io/docs/one-command-deployment
deploy:
type: git
repo:
github:
url: https://github.com/nuoyis/nuoyis.github.io.git
branch: master
token: $token

上传到github

流水线

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
name: nuoyis's blog Pages build

on:
push:
branches:
- main

permissions:
contents: write
packages: write

jobs:
build:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- name: Use pnpm
uses: pnpm/action-setup@v6
with:
version: 10
- name: Cache NPM dependencies
uses: actions/cache@v4
with:
path: node_modules
key: ${{ runner.OS }}-npm-cache
restore-keys: |
${{ runner.OS }}-npm-cache
- name: Install Hexo
run: |
rm -rf node_modules && pnpm install
pnpm install -g hexo-cli
- name: Hexo generate
run: |
hexo clean
hexo generate
- name: Hexo deploy
run: |
git config --global user.name "nuoyis"
git config --global user.email "wkkjonlykang@vip.qq.com"
hexo deploy
env:
token: ${{ secrets.token }}

picgo 安装与配置

首先得有云存储,可以互联网搜,后续接着更新时补上

扩展: edgeone pages配置

首先得有腾讯云账号,打开控制台,搜索edgeone,我这里可能是那个兑换码的免费版,然后绑定仓库绑定自定义域名就行,如果没有备案选择全球非大陆即可(但是我还是建议没备案还是别用了,cloudflare pages国内优选国外默认的那种dns设置比这个pages快)