数字花园持续集成与部署

此方案为配置与Obsidian Vault分离的方案

Quartz4

安装

在这里我们通过克隆官方仓库开始:

git clone https://github.com/jackyzha0/quartz.git blog

然后进入blog文件夹安装相关依赖并创建我们自己的数字花园:

npm i
npx quartz create

接下来我们选择Symlink,链接content到我们的ownCloud同步笔记目录即可。

如果不需要本地预览,也可以不创建。

配置

由于配置和Obsidian Vault是分离的,所以我们首先将content目录忽略,配置.gitignore,如下:

.DS_Store
.gitignore
node_modules
public
prof
tsconfig.tsbuildinfo
.obsidian
.quartz-cache
private/
.replit
replit.nix
content

然后改quartz.config.ts,参考如下:

import { QuartzConfig } from "./quartz/cfg"
import * as Plugin from "./quartz/plugins"
 
/**
 * Quartz 4 Configuration
 *
 * See https://quartz.jzhao.xyz/configuration for more information.
 */
const config: QuartzConfig = {
  configuration: {
    pageTitle: "Evalexp's Digital Garden",
    pageTitleSuffix: "",
    enableSPA: true,
    enablePopovers: true,
    analytics: {
      provider: "plausible",
    },
    locale: "zh-CN",
    baseUrl: "blog.evalexp.top",
    ignorePatterns: ["private", "Template 模板", ".obsidian", ".trash", "Daily Note 日记"],
    defaultDateType: "modified",
    theme: {
      fontOrigin: "googleFonts",
      cdnCaching: false,
      typography: {
        header: "Schibsted Grotesk",
        body: "Source Sans Pro",
        code: "IBM Plex Mono",
      },
      colors: {
        lightMode: {
          light: "#faf8f8",
          lightgray: "#e5e5e5",
          gray: "#b8b8b8",
          darkgray: "#4e4e4e",
          dark: "#2b2b2b",
          secondary: "#284b63",
          tertiary: "#84a59d",
          highlight: "rgba(143, 159, 169, 0.15)",
          textHighlight: "#fff23688",
        },
        darkMode: {
          light: "#161618",
          lightgray: "#393639",
          gray: "#646464",
          darkgray: "#d4d4d4",
          dark: "#ebebec",
          secondary: "#7b97aa",
          tertiary: "#84a59d",
          highlight: "rgba(143, 159, 169, 0.15)",
          textHighlight: "#b3aa0288",
        },
      },
    },
  },
  plugins: {
    transformers: [
      Plugin.FrontMatter(),
      Plugin.CreatedModifiedDate({
        priority: ["frontmatter", "git", "filesystem"],
      }),
      Plugin.SyntaxHighlighting({
        theme: {
          light: "github-light",
          dark: "github-dark",
        },
        keepBackground: false,
      }),
      Plugin.ObsidianFlavoredMarkdown({ enableInHtmlEmbed: false }),
      Plugin.GitHubFlavoredMarkdown(),
      Plugin.TableOfContents(),
      Plugin.CrawlLinks({ markdownLinkResolution: "relative" }),
      Plugin.Description(),
      Plugin.Latex({ renderEngine: "katex" }),
    ],
    filters: [Plugin.ExplicitPublish()],
    emitters: [
      Plugin.AliasRedirects(),
      Plugin.ComponentResources(),
      Plugin.ContentPage(),
      Plugin.FolderPage(),
      Plugin.TagPage(),
      Plugin.ContentIndex({
        enableSiteMap: true,
        enableRSS: true,
      }),
      Plugin.Assets(),
      Plugin.Static(),
      Plugin.Favicon(),
      Plugin.NotFoundPage(),
      // Comment out CustomOgImages to speed up build time
      Plugin.CustomOgImages(),
    ],
  },
}
 
export default config

基本上只需要改这两个配置即可;然后启用在线预览查看一下是否满意。

本土化

我们会发现在使用这个配置预览时,如果我们的网络环境不错,能够访问到Google等服务时,加载是不会有问题的;可是在大陆,应该大部分人的网络都无法访问到Google;因此我们需要做一点本土化的操作。

加载出错的核心问题在于Google字体库和KaTeX的加载问题,因此我们需要稍微改一下插件代码:

1000

贴出一下改动,首先将配置中的cdn重新打开,然后替换quartz/util/theme.ts的Google Fonts字体为国内源:

https://fonts.font.im/

再将jsdelivr的地址改为Cloudflare CDNJS的地址,替换quartz/plugins/transformers/latex.ts

https://cdnjs.cloudflare.com/ajax/libs/KaTeX/0.16.9/katex.min.css
https://cdnjs.cloudflare.com/ajax/libs/KaTeX/0.16.9/contrib/copy-tex.min.js

注意这个不能直接换域名,url需整体替换;这样完成后在国内应该访问就没有问题了。

容器化

为了更方便的将笔记发布,我们通过将构建的静态数字花园构建为容器镜像,这样可以直接使用watchtower监控容器镜像状态从而实现无人值守的自动更新。

在blog目录下创建builder文件夹,首先我们配置Nginx,创建nginx.conf文件:

server {
    listen 80;
    server_name localhost;
 
    location / {
        root /usr/share/nginx/html/;
        index index.html;
        try_files $uri $uri.html $uri/ /404.html;
    }
}

在这里为了保证页面效果和本地一致,我们需要通过使用tri_files来匹配可能的路径,如果路径不存在则返回404.html。

然后在builder下创建Dockerfile:

FROM nginx
COPY public /usr/share/nginx/html
COPY builder/nginx.conf /etc/nginx/conf.d/default.conf

这个就很简单了,不赘述。

Actions 自动构建

如果你的代码仓库选择为Github,则需要对应创建.github/workflows文件夹。

在blog目录下创建.gitea/workflows文件夹,然后我们在其中创建build.yaml;填写自动构建流程:

name: Auto Build Blog on Push
 
on:
  push:
    tags:
      - "*-build"
 
jobs:
  build:
    runs-on: ubuntu-latest
 
    steps:
      - uses: actions/checkout@v5
        with:
          fetch-depth: 0
 
      - name: Login Registry
        uses: docker/login-action@v3
        with:
          registry: ccr.ccs.tencentyun.com
          username: ${{ secrets.TENCENT_USER }}
          password: ${{ secrets.TENCENT_PASSWORD }}
 
      - name: Get Tag
        id: meta
        uses: docker/metadata-action@v5
        with: 
          images: ccr.ccs.tencentyun.com/evalexp-gz/blog
 
      - name: Setup rclone
        run: curl https://rclone.org/install.sh | bash
 
      - name: Configure rclone
        run: |
          rclone config create ocis webdav 'url=${{ secrets.OCIS_URL }}' 'vendor=other' 'user=${{ secrets.OCIS_USER }}' 'pass=${{ secrets.OCIS_PASSWORD }}'
 
      - name: Cache content
        uses: actions/cache@v4
        with:
          path: content
          key: webdav-${{ steps.meta.outputs.version }}
          restore-keys: |
            webdav-
 
      - name: Sync content from OCIS
        run: rclone sync ocis:/Notes/Notes ./content
 
      - name: Setup Node
        uses: actions/setup-node@v4
        with:
          node-version: 22
 
      - name: Cache dependencies
        uses: actions/cache@v4
        with:
          path: ~/.npm
          key: ${{ runner.os }}-node-${{ hashFiles('**/package-lock.json') }}
          restore-keys: |
            ${{ runner.os }}-node-
      
      - run: npm ci
      
      - name: Build Blog
        run: npx quartz build -d content -v
 
      - name: Set up docker buildx
        uses: docker/setup-buildx-action@v3
 
      - name: Build and push
        uses: docker/build-push-action@v6
        with:
          context: .
          file: ./builder/Dockerfile
          push: true
          tags: ${{ steps.meta.outputs.tags }}
          labels: ${{ steps.meta.outputs.labels }}

上面的自动流程仅当推送标签为*-build时才会自动触发,其流程如下:

  1. git checkout
  2. 登陆容器仓库
  3. 获取元信息
  4. 安装rclone
  5. 配置rclone的webdav信息
  6. 为webdav同步的内容启用缓存
  7. 同步webdav中的笔记
  8. 安装Nodejs 22
  9. 为npm 依赖启用缓存
  10. 安装相关依赖
  11. quartz4 静态编译
  12. 安装docker buildx
  13. 构建容器镜像并推送远程仓库

之所以使用tag是因为在配置和Vault分离的情况下,即便不变更此仓库内容,也能自由的推送tag,从而引发自动构建

请注意上面引用了许多的secrets,这些需要在仓库的Actions配置中进行添加:

  • TENCENT_USER
  • TENCENT_PASSWORD
  • OCIS_URL
  • OCIS_USER
  • OCIS_PASSWORD

数字花园更新

如果说某一天写了一篇文章,需要发布,此时我们仅需在当前的blog仓库下打一个tag,例如2025.09.07-v1-build,将其推送到Git仓库,就会自动触发流程并推送最新镜像到远程仓库。

注意,如果需要自动部署,你需要在远程服务器上使用腾讯云的该镜像部署,并且使用watchtower持续监控镜像更新操作。