跳转至

部署 Zensical

obsidian 笔记,通过 Zensical 私有化部署

ZensicalMaterial for MkDocs 创建者开发的现代静态站点生成器,解决了很多 MKDocs 的痛点。使用对比后最明显的感觉就是更快的生成速度、全新的搜索引擎。

项目网址 Beyond欣 's Notes

项目源码 site-notes.beyondxin.top

1 使用 Zensical 创建笔记

pip install zensical
set PYTHONUTF8=1
python -m zensical new .

1.1 测试 && 打包

python -m zensical serve
python -m zensical build --clean

1.2 文章顺序

静态站点文章顺序需要手动配置,格式如下。

[project]
nav = [
  {"Home" = "index.md"},
  {"About" = [
     "about/index.md",
     "about/vision.md",
     "about/team.md"
  ]}
]

1.3 自定义样式

追加 css 或者 js

# zensical.toml
[project]
extra_css = ["css/extra.css"]
.md-search {
    transform: translateX(160px);
}

.md-header__option[data-md-component="palette"] {
    transform: translateX(160px);
}

1.4 自定义主题

custom_dir 目录中的任何文件都会覆盖 Zensical 提供的同名模板和局部模板。

# zensical.toml
[project.theme]
custom_dir = "overrides"

修改模板后必须清空缓存后 zensical serve 才能看到效果。 清空缓存可以用 zensical build --clean,或者手动删除 ./.cache/ 目录。

1.5 自定义模板

可以某些文章使用单独的格式,template 目录是相对于 custom_dir 的。

# zensical.toml
[project.theme]
custom_dir = "overrides"
---
template: "my_template.html"
---

# Page title

...

2 部署

2.1 Github Pages

  • Github Pages zensical new . 生成的空白项目默认带 Github Actions 配置,会自动部署到 Github Pages

2.2 服务器

方式一:服务器定时更新

#!/bin/sh
cd /home/www/MyNote
git reset --hard HEAD
git pull
set PYTHONUTF8=1
/www/server/pyporject_evn/versions/3.12.11/bin/python -m zensical build --clean

方式二:Github Actions 推送到服务器

导出当前环境。

如需升级 zensical 版本需要重新导出环境。

pip freeze > requirements.txt

workflows

jobs:
  notes:
    runs-on: ubuntu-latest
    steps:
      - name: Set up SSH for private submodules
        run: |
          mkdir -p ~/.ssh
          echo "${{ secrets.GH_SSH_KEY }}" > ~/.ssh/id_rsa
          chmod 600 ~/.ssh/id_rsa
          ssh-keyscan github.com >> ~/.ssh/known_hosts
          git config --global url."git@github.com:".insteadOf "https://github.com/"
      - uses: actions/checkout@v4
        with:
          submodules: recursive
      - name: Update submodules to latest remote commit
        run: |
          git submodule foreach git fetch origin
          git submodule foreach git reset --hard origin/HEAD
      - name: Prepare notes content
        run: |
          mkdir -p site-notes.beyondxin.top/docs
          cp -r MyNote/.space site-notes.beyondxin.top/docs/
          cp -r MyNote/Qt site-notes.beyondxin.top/docs/
          cp -r MyNote/VTK site-notes.beyondxin.top/docs/
          cp -r MyNote/C++ site-notes.beyondxin.top/docs/
          cp -r MyNote/开源库 site-notes.beyondxin.top/docs/
          cp -r MyNote/编程 site-notes.beyondxin.top/docs/
          cp -r MyNote/运维 site-notes.beyondxin.top/docs/
          cp -r MyNote/系统 site-notes.beyondxin.top/docs/
          cp -r MyNote/我的项目 site-notes.beyondxin.top/docs/
      - name: Set up Python
        uses: actions/setup-python@v5
        with:
          python-version: "3.13"
      - name: Build notes.beyondxin.top
        run: |
          cd site-notes.beyondxin.top
          python -m pip install -r requirements.txt
          python script/rebuild_nav.py
          python -m zensical build --clean
      - name: Upload notes.beyondxin.top
        uses: appleboy/scp-action@master
        with:
          host: ${{ secrets.SSH_HOST }}
          username: ${{ secrets.SSH_USER }}
          key: ${{ secrets.SSH_KEY }}
          port: ${{ secrets.SSH_PORT }}
          source: "site-notes.beyondxin.top/site/*"
          target: "/home/www/html/notes.beyondxin.top"
          strip_components: 2
          overwrite: true

3 辅助脚本

toml 完整配置

3.1 手动排序

解析 obsidian 中 MAKE.md 插件排序

import os
import sqlite3
import re

db_name = '.space/context.mdb'
base_directory = './docs/'
ignore_directory = ["模板", "Blog", "读书笔记"]
toml_path = './zensical.toml'


def get_context_bt_db(path):
    db_path = path + db_name
    if not os.path.exists(db_path):
        return []
    conn = sqlite3.connect(db_path)
    cursor = conn.cursor()
    cursor.execute("SELECT File FROM files")
    file_list = [row[0] for row in cursor.fetchall()]
    conn.close()
    return file_list


def get_page_tree(relative_path=""):
    page_tree = []
    order_tree = get_context_bt_db(base_directory + relative_path)
    for key in order_tree:
        if key in ignore_directory:
            break
        if key.endswith('.md'):
            page_tree.append(key)
        else:
            subtree = get_page_tree(key+"/")
            if subtree:
                page_tree.append({key.split('/')[-1]: subtree})
    return page_tree


def format_nav_tree(tree, indent=1):
    """将导航树格式化为 TOML 字符串"""
    lines = []
    indent_str = "    " * indent

    for item in tree:
        if isinstance(item, str):
            lines.append(f'{indent_str}"{item}",')
        elif isinstance(item, dict):
            for key, value in item.items():
                lines.append(f'{indent_str}{{ "{key}" = [')
                lines.extend(format_nav_tree(value, indent + 1))
                lines.append(f'{indent_str}] }},')

    return lines


# 生成导航树
nav_tree = get_page_tree()


# 格式化为 TOML 字符串
nav_lines = ["nav = ["]
nav_lines.extend(format_nav_tree(nav_tree))
nav_lines.append("]")
new_nav_content = "\n".join(nav_lines)

# 读取原文件
with open(toml_path, 'r', encoding='utf-8') as f:
    content = f.read()

# 使用正则表达式替换 nav 部分
# 匹配从 nav = [ 到对应的 ] 结束
pattern = r'nav = \[.*?\n\]'
new_content = re.sub(pattern, new_nav_content, content, flags=re.DOTALL)

# 写回文件
with open(toml_path, 'w', encoding='utf-8') as f:
    f.write(new_content)

print(f"导航已更新到 {toml_path}")

3.2 自动提交

@echo off
python script/rebuild_nav.py
git add .
for /f "tokens=1-4 delims=/ " %%a in ('date /t') do (set mydate=%%a-%%b-%%c %%d)
for /f "tokens=1-2 delims=: " %%a in ('time /t') do (set mytime=%%a:%%b)
git commit -m "%mydate% %mytime%"
git push
pause