使用GithubAction自动同步obisidian和hexo仓库,避免手动操作。
烦恼
先来说说慕雪现在的笔记和博客是怎么管理的吧,我正在使用两套笔记软件
- 思源笔记:私密性高一些,不是博客的笔记都在这里面。由于思源笔记不是markdown编辑器,不能直接和hexo对接;
- obisdian:专门管理hexo的博客;
然后我的hexo博客和obsidian又有分离,hexo配置仓库是一个单独的git仓库(后文简称为hexo仓库),obsidian博客库也是一个单独的git仓库(后文简称为obisidian仓库)。
我采用的操作特别繁琐,步骤如下:
- 在obsidian里面写好博客之后,手动使用FreeFileSync软件,将
obisidian/blog
目录同步到hexo/source/_posts
目录中(这两个目录完全一样); - 然后再到hexo本地仓库中执行hexo三板斧命令,给新的博客生成abbrlink,push到hexo的github仓库;
- 再用FreeFileSync反向将
hexo/source/_posts
目录同步回obisidian/blog
目录,因为新的博客会多出abbrlink;
是不是听起来都头大了?
曾经的想法
先前我一直在想怎么让这套流程简化,考虑过几个方案都不太满意。我想过直接把obsidian vaults丢到hexo/source/_posts
目录里面,但是考虑到我的obsidian中还有博客模板这种不需要上传到博客里面的内容,此项并不方便(虽然hexo其实可以跳过渲染某些md文件)
现在就想出了自动化的方案,也就是用github action来同步obsidian和hexo的仓库,当obisidian/blog
目录有变动的时候,触发action,自动将这个目录的内容拷贝到hexo/source/_posts
仓库目录中,并push到hexo仓库。
这里就有一个问题,abbrlink是基于hexo插件生成的,如果用这种方式那就没办法给新的博客md文件生成一个固定abbrlink了。不管是怎么让github action执行hexo g
命令,最后都会出现远程仓库md文件中有abbrlink,但本地需要pull才能更新的问题,这会对我后续的博客编写和git操作带来不便(毕竟之前都是无脑push上去的)
之前每次想折腾github action的时候就会发现这个问题(由于没记笔记导致折腾的时候忘记了之前为啥没搞定……),然后又不了了之。
今天突然想起来,既然问题是在abbrlink插件上,那我不用hexo来生成abbrlink不就行了?反正abbrlink本质上和随机数没啥关系,我只要给新的博客手动加上一个和其他博客不冲突的abbrlink不就ok了?
注:hexo的abbrlink插件是通过crc16/crc32算法计算得到文件的abbrlink的,并非随机数生成。但对于abbrlink的作用来看,只要博客上每个文章都有一个独立的abbrlink其实就够了,所以abbrlink说它是随机数也没啥问题。
解决方法明了:用别的方法给新博客生成abbrlink,然后再用github action自动化同步obsidian仓库和hexo仓库。
解决步骤
生成abbrlink的python脚本
其实obsidian中是有一个abbrlink插件的,首先感谢插件作者能提供一个hexo-abbrlink插件的替代品。但是,这个插件不太符合本人的需求,因为它直接针对于obsidian全局,会把我的其他文件以及博客模板文件都加上abbrlink。
折腾了一会后,感觉不如返璞归真,直接写个python脚本,把所有博客文件的abbrlink遍历出来,然后生成30个不冲突的abbrlink写入到一个文件里面,每次写新博客的时候从这个文件里面取一个abbrlink出来用就完事啦!
说干就干,GPT,启动!
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
| import yaml import re import os import random
MD_FILE_PATH = '../../Notes/CODE' """博客md文件路径""" NEW_ABBRLINK_SIZE = 20 """生成几个abbrlink""" NEW_ABBRLINK_MD_FILE = '../../Notes/ABBRLINK归档.md' """生成的abbrlink写入这个md文件里面"""
def extract_front_matter(file_path): """ 提取 Markdown 文件中的 front-matter 内容。 假设 front-matter 是以 '---' 包围的 YAML 格式内容。 """ with open(file_path, 'r', encoding='utf-8') as file: content = file.read() match = re.match(r'---\n(.*?)\n---\n', content, re.DOTALL) if match: front_matter = match.group(1) return yaml.safe_load(front_matter) else: return None
def remove_front_matter(file_path): """ 移除 Markdown 文件中的 front-matter 部分,返回去除 front-matter 后的内容。 """ with open(file_path, 'r', encoding='utf-8') as file: content = file.read() cleaned_content = re.sub(r'---\n(.*?)\n---\n', '', content, flags=re.DOTALL) return cleaned_content
def update_front_matter(file_path, new_front_matter): """ 更新 Markdown 文件中的 front-matter 内容。 """ with open(file_path, 'r+', encoding='utf-8') as file: content = file.read() new_front_matter_str = yaml.dump(new_front_matter, default_flow_style=False) content = re.sub(r'---\n(.*?)\n---\n', f'---\n{new_front_matter_str}\n---\n', content, flags=re.DOTALL) with open(file_path, 'w', encoding='utf-8') as file: file.write(content)
def extract_front_matter_from_dir(directory_path): """ 遍历指定目录及其子目录下的所有 .md 文件,提取它们的 front-matter 内容,并将所有内容添加到列表中。 """ front_matter_list = [] for root, dirs, files in os.walk(directory_path): for filename in files: file_path = os.path.join(root, filename) if filename.endswith('.md'): front_matter = extract_front_matter(file_path) if front_matter: front_matter_list.append(front_matter) return front_matter_list
def generate_unique_10digit_numbers(existing_numbers, n): """ 生成 n 个不在 existing_numbers 列表中的 10 位数字。 :param existing_numbers: 已存在的整数列表 :param n: 需要生成的数字数量 :return: 不重复的 10 位数字列表 """ unique_numbers = set(existing_numbers) generated_numbers = [] if len(unique_numbers) > 9999999999 - 1000000000: return None while len(generated_numbers) < n: num = random.randint(1000000000, 9999999999) if num not in unique_numbers: generated_numbers.append(num) unique_numbers.add(num) return generated_numbers
def write_int_list_to_md(file_path, int_list): """ 将整数列表的成员按行写入一个Markdown文件。 :param file_path: Markdown文件的路径 :param int_list: 要写入文件的整数列表 """ with open(file_path, 'w', encoding='utf-8') as file: for number in int_list: file.write(f"{number}\n")
if __name__ == "__main__": file_path = MD_FILE_PATH front_matter_list = extract_front_matter_from_dir(file_path) if not front_matter_list: print("没有找到 front-matter 或目录为空。") os.abort() abbrlink_list = [] for fm in front_matter_list: if 'abbrlink' not in fm: print(f"ERR! abbrlink not in {fm}") continue link = int(fm['abbrlink']) if link in abbrlink_list: print(f"ERR! {link} in abbrlink list!") continue abbrlink_list.append(link) new_abbrlink = generate_unique_10digit_numbers(abbrlink_list, NEW_ABBRLINK_SIZE) for link in new_abbrlink: print(link) print("Gen abbrlink success") write_int_list_to_md(NEW_ABBRLINK_MD_FILE, new_abbrlink) print("Write abbrlink to", NEW_ABBRLINK_MD_FILE)
|
脚本运行效果如下,会生成新的abbrlink链接数字,然后写入到指定的md文件中。这样在obsidian里面就能看到这个md文件,取用里面的abbrlink了。用完了之后再手动执行一下脚本更新abbrlink就完事啦。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23
| ❯ python3 gen_abbrlink.py 8608489065 7885829874 8484489314 4284761477 1589125738 9151131777 4800824161 7141292217 2714461943 5131440419 2816690027 9574459795 6572894529 2920325088 2724835080 7631222809 1802821635 3120273636 2860205445 3100823185 Gen abbrlink success Write abbrlink to ../../Notes/ABBRLINK归档.md
|
Github Action配置
接下来就是配置Github Action来同步两个仓库了。让GPT写了个大概,发现GPT在瞎说,它给出https的仓库clone链接,并表示用自带的GITHUB_TOKEN
就能克隆私有仓库了,但实际上完全没用。最后还是得用老办法ssh密钥对来实现。
首先使用如下命令生成一个ssh密钥,弹出的提示中填写一个文件名字(不然会覆盖默认目录的ssh密钥对)
1
| ssh-keygen -t rsa -C "github action"
|
然后,搞清楚同步的方向,我的需要是将obsidian仓库中的内容同步到hexo仓库,所以公钥放在hexo仓库,私钥放在obsidian仓库中。
在hexo仓库(被推送的仓库)中,仓库设置Settings->Deploy keys->Add deploy key
添加公钥,命名为HEXO_PUB_KEY
。注意需要勾选允许write写入仓库,不然默认权限只允许pull和clone仓库。
在obsidian仓库中,仓库设置Settings->Secrets and variables->Secrets
添加私钥,命名为HEXO_PRI_KEY
。
最后的Github Action Workflow文件如下,将该文件写入仓库的.github/workflows/sync-code-to-posts.yml
即可。
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
| name: Sync CODE to _posts
on: push: paths: - 'Notes/CODE/**'
jobs: sync: runs-on: ubuntu-latest
steps: - name: Checkout muob repository uses: actions/checkout@v3
- name: Set up Git env: ACTIONS_KEY: ${{ secrets.HEXO_PRI_KEY }} run: | mkdir -p ~/.ssh/ echo "$ACTIONS_KEY" > ~/.ssh/id_rsa chmod 700 ~/.ssh chmod 600 ~/.ssh/id_rsa ssh-keyscan github.com >> ~/.ssh/known_hosts git config --global user.name "musnows" git config --global user.email "ezplayingd@126.com" git config --global core.quotepath false git config --global i18n.commitEncoding utf-8 git config --global i18n.logOutputEncoding utf-8 - name: Checkout HexoBlog repository run: | git clone git@github.com:musnows/Hexo-Blog.git HexoBlog - name: Sync files from CODE to _posts run: | rsync -av --delete Notes/CODE/ HexoBlog/source/_posts/ - name: Commit and push changes to HexoBlog repository run: | cd HexoBlog git add . git commit -m "Sync CODE to _posts at $(TZ='Asia/Shanghai' date '+%Y-%m-%d %H:%M:%S')" git push origin hexo
|
第二步的Git操作中,我们将仓库配置的secrets.HEXO_PRI_KEY
映射成环境变量ACTIONS_KEY
,然后写入执行action的ubuntu环境的~/.ssh/id_rsa
私钥文件中,这样就能操作另外一个仓库了。
第四步的Sync操作使用了rsync命令
1
| rsync -av --delete 源文件夹 目标文件夹
|
解释一下这里的几个命令参数,-a
用于保持文件的原有属性,-v
代表verbose,会输出详细的日志,--delete
用于在目标目录中删除源目录中不存在的文件(同步删除操作)。
测试效果
我在目录中创建了一个测试文件,push到了远端仓库中,触发了action
hexo配置仓库成功被push了,有更新的文件也是在obsidain仓库中被修改的文件,符合预期