自动化部署 Org 博客

自动化脚本节省的时间可以用来编写自动化脚本。

publish.yml

完成了上次博客迁移时的 TODO,接入 CI 自动部署。

Codeberg 仓库用的是 Forgejo Actions,语法与 GitHub Actions 相似,还能直接从外部链接拉取 actions,蹭 GitHub 的市场还蛮方便的。只是 runner 要用 Codeberg 自己的 codeberg-small,有时需要等待空闲。

我这次用 Wrangler 直接部署到 Cloudflare Pages,不再需要连接 Git 仓库了,同时也省去一个专门存 HTML 文件的分支。

(由于 CF 只支持连接 GitHub 和 GitLab,我之前甚至是先把仓库镜像到 GitLab,绕了好大一圈。)

name: Publish

on:
  workflow_dispatch:
  push:
    branches:
      - main

jobs:
  publish:
    runs-on: codeberg-small
    steps:
      - uses: https://github.com/actions/checkout@v6

      - uses: https://github.com/purcell/setup-emacs@master
        with:
          version: 30.2

      - name: Install pagefind
        run: |
          curl -sL "https://github.com/Pagefind/pagefind/releases/download/v1.5.2/pagefind_extended-v1.5.2-x86_64-unknown-linux-musl.tar.gz" \
              | tar xz -C /usr/local/bin
          mv /usr/local/bin/pagefind_extended /usr/local/bin/pagefind

      - name: Build
        run: |
          emacs --batch \
                --load setup.el \
                --load publish.el \
                --eval "(require 'ox-publish)" \
                --eval "(require 'ox-html)" \
                --eval "(org-publish-project \"blog-all\" t)"

      - name: Deploy
        uses: https://github.com/cloudflare/wrangler-action@v4
        with:
          apiToken: ${{ secrets.CLOUDFLARE_API_TOKEN }}
          accountId: ${{ secrets.CLOUDFLARE_ACCOUNT_ID }}
          wranglerVersion: "4.98.0"
          command: pages deploy public/ --project-name=blog

Pagefind 的运行已经写进 publish.el:completion-functionorg-publish-project 结束后会自动触发,CI 这里只需要安装好二进制就够了。

publish.el

之前的代码放在 Emacs 用户配置里,挺乱的,现在把它抽成 publish.el 放在博客目录下,本地 Emacs 和 CI 都加载同一份文件。

以前配置的绝对路径在 CI 下肯定不能用,改用 publish.el 自身的路径推导根目录:load-file-name 在文件被 load 时有值,buffer-file-name 在交互式求值时有值。

(defvar blog/root
  (file-name-directory (or load-file-name buffer-file-name)))

(defvar blog/output-dir
  (expand-file-name "public/" blog/root))

参考了 systemcrafters-site 等仓库,发现函数命名惯例是用作者缩写作前缀(如 dw/ 对应 David Wilson)。我统一改成了 blog/

11:(defun blog/slugify (str)
18:(defun blog/post-metadata ()
52:(defun blog/generate-index (index-file title-str keyword-fn &optional reverse)
84:(defun blog/generate-tag-index (&optional _project)
91:(defun blog/generate-category-index (&optional _project)
98:(defun blog/generate-archive-index (&optional _project)
106:(defun blog/generate-homepage (&optional _project)
182:  (defun blog/html-article-meta (info)
214:  (defun blog/html-inner-template (contents info)
230:  (defun blog/html-headline (headline contents info)
248:  (defun blog/html-special-block (special-block contents info)
267:(defun blog/publish-to-html (plist filename pub-dir)
270:(defun blog/run-pagefind (_project)
343:  (defun blog/html-wrap-table (string backend _)
350:  (defun blog/export-remove-zero-width-space (text backend _info)

也许您注意到了,上篇关于卡丘的文章里 log 和 manifest 文件内容有语法高亮,这是专门为了博客写的自定义 Major mode

Emacs 定义 Major mode 非常简单,font-lock 规则写几条正则就够了:

(defvar calabiyau-log-fontlock
  '(("\\([0-9]\\{2\\}-[0-9]\\{2\\} [0-9]\\{2\\}:[0-9]\\{2\\}:[0-9]\\{2\\}\\.[0-9]+\\): \\(Debug\\|Info\\|Warning\\)/(\\([0-9]+\\)):"
     (1 font-lock-keyword-face)
     (2 (cond
         ((string= (match-string 2) "Debug")   'font-lock-comment-face)
         ((string= (match-string 2) "Info")    'font-lock-function-name-face)
         ((string= (match-string 2) "Warning") 'font-lock-warning-face)))
     (3 font-lock-constant-face))))

(define-derived-mode calabiyau-log-mode fundamental-mode "calabiyau-log"
  (setq font-lock-defaults '(calabiyau-log-fontlock)))

(defvar calabiyau-manifest-fontlock
  '(("--[a-z_]+--" (0 font-lock-keyword-face))
    ("\\([^|>\n]+\\)->\\([^|>\n]+\\)|\\([^|]+\\)|\\([^|]+\\)|\\([^|\n]+\\)"
     (1 font-lock-function-name-face) ; old version
     (2 font-lock-function-name-face) ; new version
     (3 font-lock-string-face)        ; file path
     (4 font-lock-constant-face)      ; md5
     (5 font-lock-warning-face))      ; file size
    ("\\([^|\n]+\\)|\\([^|]+\\)|\\([^|\n]+\\)"
     (1 font-lock-string-face)        ; file path
     (2 font-lock-constant-face)      ; md5
     (3 font-lock-warning-face))))    ; file size

(define-derived-mode calabiyau-manifest-mode fundamental-mode "calabiyau-manifest"
  (setq font-lock-defaults '(calabiyau-manifest-fontlock)))

(with-eval-after-load 'org
  (add-to-list 'org-src-lang-modes
               '("calabiyau-log" . calabiyau-log)
               '("calabiyau-manifest" . calabiyau-manifest)))

setup.el

setup.el 只在 CI 中使用,负责安装软件包。它不能并入 publish.el,因为 publish.el 会在本地 Emacs 启动时加载,相关代码不能每次都跑。

;; -*- lexical-binding: t; -*-

(require 'package)

(setq package-archives
      '(("melpa" . "https://melpa.org/packages/")
        ("gnu"   . "https://elpa.gnu.org/packages/")))

(package-initialize)
(package-refresh-contents)

(dolist (pkg '(catppuccin-theme
               htmlize
               haskell-mode
               rust-mode
               racket-mode
               powershell
               yaml-mode
               json-mode
               hare-mode
               fish-mode))
  (unless (package-installed-p pkg)
    (package-install pkg)))

(unless (package-installed-p 'org-publish-rss)
  (package-vc-install '(org-publish-rss
                        :url "https://git.sr.ht/~taingram/org-publish-rss")))

(unless (package-installed-p 'ini-mode)
  (package-vc-install '(ini-mode
                        :url "https://github.com/Lindydancer/ini-mode")))

(setq catppuccin-flavor 'macchiato)
(load-theme 'catppuccin :no-confirm)

接下来

  • 添加更多 Slash pages
  • 404 页面(为什么到现在还没有)
  • 给笔记文档安排一个位置,也许是新站点
  • 更多代码块样式(行号、高亮行)
  • 对话形式的文本块(用虚拟角色对话讲解内容可以带来不一样的体验,比如 OSTEP 的 Dialogue 章节
  • 减少咖啡用量,增加睡眠时间

其实睡眠等同于地球 Online 挂机,挂机 6 小时惩罚 18 小时,你必须要玩这款游戏,永远永远。

Copyright © 2024 13m0n4de · CC BY-NC 4.0