惯性聚合 高效追踪和阅读你感兴趣的博客、新闻、科技资讯
阅读原文 在惯性聚合中打开

推荐订阅源

Threat Intelligence Blog | Flashpoint
Threat Intelligence Blog | Flashpoint
爱范儿
爱范儿
H
Help Net Security
Last Week in AI
Last Week in AI
The Cloudflare Blog
博客园 - 三生石上(FineUI控件)
小众软件
小众软件
IT之家
IT之家
aimingoo的专栏
aimingoo的专栏
大猫的无限游戏
大猫的无限游戏
Jina AI
Jina AI
Google DeepMind News
Google DeepMind News
B
Blog
C
Check Point Blog
T
Tailwind CSS Blog
云风的 BLOG
云风的 BLOG
D
Docker
Recent Announcements
Recent Announcements
Vercel News
Vercel News
博客园 - 聂微东
阮一峰的网络日志
阮一峰的网络日志
MyScale Blog
MyScale Blog
The GitHub Blog
The GitHub Blog
Stack Overflow Blog
Stack Overflow Blog
雷峰网
雷峰网
人人都是产品经理
人人都是产品经理
月光博客
月光博客
F
Fortinet All Blogs
Blog — PlanetScale
Blog — PlanetScale
B
Blog RSS Feed
The Register - Security
The Register - Security
V
Visual Studio Blog
F
Full Disclosure
Hugging Face - Blog
Hugging Face - Blog
T
Threat Research - Cisco Blogs
Latest news
Latest news
PCI Perspectives
PCI Perspectives
Cisco Talos Blog
Cisco Talos Blog
博客园 - Franky
D
DataBreaches.Net
A
Arctic Wolf
OSCHINA 社区最新新闻
OSCHINA 社区最新新闻
奇客Solidot–传递最新科技情报
奇客Solidot–传递最新科技情报
G
Google Developers Blog
P
Palo Alto Networks Blog
Engineering at Meta
Engineering at Meta
Microsoft Azure Blog
Microsoft Azure Blog
T
Tenable Blog
L
LINUX DO - 热门话题
Spread Privacy
Spread Privacy

暗无天日

读:AI Agent 安全日志——从可见性与隐私的两难说起 - 暗无天日 读:AI Agent 生产化——一份从原型到上线的速查清单 - 暗无天日 AI写作的语言指纹——如何让文字不那么像机器 - 暗无天日 读:50 条 Claude Code 技巧——一个工程经理的六个月使用心得 读:AI 辅助开发为什么让 E2E 测试更有价值 - 暗无天日 读:在Emacs中使用Claude Code(Spacemacs适配版) - 暗无天日 Claude Code 背后的工程哲学——读 Agent Harness Engineering 读:Agent Harness Engineering——AI 智能体不只是模型,还有套件 - 暗无天日 browser-harness:让 AI 直接接管你的浏览器 - 暗无天日 读:Security-First CI/CD —— DevSecOps 自动化实践指南 TIL: 数字小键盘的小数点陷阱与行内算术求值 - 暗无天日 读:Immutability 不是万能药,它是一种权衡 - 暗无天日 Conducty:给 Claude Code 加上项目记忆和并行执行能力 - 暗无天日 读 — GitHub Trending 里的 Claude Code 技能包 读 — Prompt Caching 省钱指南 TIL: Emacs 中那些跟鼠标配合的冷门快捷键 - 暗无天日 读:Anvil——把 Emacs 变成 AI 的工具服务器 读:Emacs 代码折叠终极指南 - 暗无天日 读:Clojure 搭车客指南 - 暗无天日 git推送失败后恢复仓库损坏的完整记录 - 暗无天日 多智能体系统的两个有效模式——以及对 Claude Code 用户的启示 - 暗无天日 用 Org Babel 写 Literate 博文:扩展执行 + 定制导出 proced:Emacs 内置的进程查看器 - 暗无天日 从 proced 定制中学到的 Elisp 模式 读:让 Emacs proced 在 macOS 上显示 CPU 和内存 异步编程的函数着色税 - 暗无天日 链式调用的代价:JavaScript 和 Clojure 的共同教训 - 暗无天日 hyperfine:命令行基准测试工具 - 暗无天日 管道中的变量去哪了?——子 shell 作用域陷阱 - 暗无天日 开源包装器的信任陷阱:四个危险信号 - 暗无天日 程序员愿意为 AI 写文档,却不愿为同事写 - 暗无天日 mktemp: Shell 脚本中临时文件的安全陷阱与最佳实践 - 暗无天日 WSL9x —— 在 Windows 9x 里跑 Linux 内核 6.19 用 ox.el 做你想做的事 —— org-export 高级编程指南 读:Hot-wiring the Lisp Machine —— 用纯 Elisp 构建零依赖的 Org 静态站点生成器 Elisp 性能优化的六个实战教训 - 暗无天日 fcitx5 下 Emacs 无法切换输入法的排查 - 暗无天日 ERT 测试交互命令的三种方式 - 暗无天日 SEM Assistant: 当 Elisp 守护进程遇上 LLM 用 dmsg 给 Elisp 加上结构化调试日志 用 org-habit 追踪非每日习惯 - 暗无天日 Clojure X-Men:当编程语言特性变成超能力 - 暗无天日 TIL: 用 diff-hl 在 fringe 中显示 git 变更 读:llm-test —— 用 LLM agent 驱动 Emacs 测试 TIL: AI 时代的橡皮鸭调试 - 暗无天日 fcitx 启动后键盘输入卡顿的排查 - 暗无天日 TIL: 早期网页的图片热区导航 - 暗无天日 读 Seeing the Whole System 用 Emacs 自动生成每周链接推荐 - 暗无天日 读:ASCII control characters in my terminal 读 What to learn - 暗无天日 Lisp 的括号之痛——一个愚人节玩笑揭开的老伤疤 - 暗无天日 一本书该"线性读"还是"并行读" - 暗无天日 读 How to Monetize a Blog:一篇伪装成变现指南的讽刺文 Python Mock 第三方依赖的四种策略 - 暗无天日 Emacs Lisp 热重载实用指南 - 暗无天日 Prot 的 Emacs 配置哲学 - 暗无天日 TIL: 从直播对谈中学到的三个 Emacs 技巧 - 暗无天日 TIL: 自动使用项目虚拟环境的 Python - 暗无天日 TIL: 让 Help buffer 自动获得焦点 一条命令让本地开发用上 HTTPS —— slim 工具介绍 用 fsck 检查和修复 Linux 文件系统 排查Linux进程"卡死"实战:从strace到gdb全流程 - 暗无天日 PostgreSQL 索引:从基础到你可能不知道的高级用法 - 暗无天日 用 .pdbrc 自定义 Python 调试器 ANSI 转义码的标准化现状 - 暗无天日 终端程序的潜规则 - 暗无天日 PARA Org-mode 测试配置 - 暗无天日 AI越强越辣鸡?控制论说这是必然的 - 暗无天日 AI 越强越需要你盯着——反馈循环实操指南 - 暗无天日 你的AI代理正在偷你的密钥——四种你没想到的泄露通道 - 暗无天日 LLM 在 DevOps 中的三种角色 - 暗无天日 写作风格的反建议 - 暗无天日 反驳本质复杂性——Dan Luu 论为什么《没有银弹》错了 - 暗无天日 文件充满了危险——Dan Luu 谈文件系统的可靠性陷阱 - 暗无天日 AI 时代的 PARA 方法:用 Org-mode 和 AI 打造个人知识管理系统 Linux 数据去重学习笔记 - 暗无天日 创建跨平台 ZIP 文件的隐藏陷阱:Extra Field - 暗无天日 X11 Forwarding 排障指南 - 暗无天日 IP欺骗端口扫描:当别人冒充你去扫描别人 - 暗无天日 Linux 输入栈全景解析:从硬件按键到屏幕响应 - 暗无天日 Unix 系统中那些被埋没的配置开关——以 FontConfig 为例 - 暗无天日 在Linux上限制儿童使用电脑 - 暗无天日 GIF不仅仅是一种图片格式——用GIF流做些奇怪的事 - 暗无天日 Leiningen 学习笔记:Clojure 项目构建与管理从入门到实战配置 - 暗无天日 Google SRE Book 读书笔记 - 暗无天日 yes 管道 head 发生了什么 - 暗无天日 为什么 nohup 在 crontab 中不起作用 Bash中的Indirection与Nameref - 暗无天日 Linux PAM 简介 - 暗无天日 从Linux ISO文件启动计算机 - 暗无天日 用 Bash 打造一个Screen Locker blocking I/O 的作用 - 暗无天日 mobileog 手机端同步提示Error:2 No such file 的解决方法 回收 WSL2 VHDX 文件占用空间 使用 org-mode columnview 生成任务列表 - 暗无天日 Emacs 作为 MPD 客户端 - 暗无天日 移动文件路径却不破坏org file link的方法 - 暗无天日 如何合理的导出help link 成HTML - 暗无天日 笑话理解之Biology - 暗无天日
用GitHub Actions自动构建EGO博客 - 暗无天日
2026-04-10 · via 暗无天日

1. 背景

我的博客 暗无天日 使用 EGO(Emacs + Git + Org-mode)作为静态站点生成器。源文件是 .org 格式,存放在 source 分支;生成的 HTML 存放在 master 分支,通过 GitHub Pages 托管。

一直以来,我都是在本机执行 auto_publish.el 来发布博客。但既然用了 GitHub,为什么不把构建过程也自动化呢?于是我开始折腾用 GitHub Actions 来自动构建博客。

2. 最终效果

每次 pushsource 分支时,GitHub Actions 会自动:

  1. 用 EGO 将 .org 文件转换为 HTML(增量构建)
  2. 将生成的 HTML 推送到 master 分支
  3. GitHub Pages 自动部署

顺带还会在 Habitica 上完成"写博客"任务打个卡(游戏化激励)。

3. 实现过程

3.1. 第一步:改造 auto_publish.el

原来 auto_publish.el 里的路径都是硬编码的:

(setq load-path (cons "~/EGO/" load-path))

这在本地没问题,但在 CI 环境里路径完全不同。所以我把所有硬编码路径改为从环境变量读取,保留默认值确保本地兼容:

(setq load-path (cons (or (getenv "EGO_DIR") "~/EGO/") load-path))

ego-project-config-alist 中的 :repository-directory:store-dir 也做了类似处理:

`(("blog" :repository-directory ,(or (getenv "REPO_DIR") "~/source")
   ...
   :store-dir ,(or (getenv "STORE_DIR") "~/web")))

最后,用 condition-case 包裹 ego-do-publication 调用,确保 EGO 出错时不会静默失败:

(condition-case err
    (ego-do-publication "blog" nil nil nil)
  (error
   (message "EGO ERROR: %s" (error-message-string err))
   (message "EGO ERROR backtrace:")
   (backtrace)))

3.2. 第二步:编写 GitHub Actions Workflow

最终的 workflow 文件 .github/workflows/github-action.yml

name: 提交 habitica 任务 + 构建博客
on:
  push:
    branches: [source]

permissions:
  contents: write

jobs:
  Finish-Habitica-Tasks:
    runs-on: ubuntu-latest
    steps:
      - name: Check out habash code
        uses: actions/checkout@v4
        with:
          fetch-depth: 0
          repository: nasfarley88/habash
      - name: 确保 habash 可执行
        run: chmod +x ./habash
      - name: 提交 habitica 任务
        run: |
          set -x
          ./habash up "写博客"
        env:
          HABITICA_TOKEN: ${{ secrets.HABITICA_TOKEN }}
          HABITICA_UUID: ${{ secrets.HABITICA_UUID }}

  Build-Blog:
    runs-on: ubuntu-latest
    env:
      CI: true
      EGO_DIR: /tmp/EGO
      REPO_DIR: ${{ github.workspace }}
      STORE_DIR: /tmp/web
      REPO: https://github.com/lujun9972/${{ github.event.repository.name }}
    steps:
      - name: Checkout 博客源文件
        uses: actions/checkout@v4
        with:
          fetch-depth: 0
          ref: source

      - name: Checkout EGO
        run: git clone https://github.com/lujun9972/EGO.git "$EGO_DIR"

      - name: Checkout master 分支到 store-dir
        run: |
          git clone --branch master --single-branch \
            https://x-access-token:${{ secrets.GITHUB_TOKEN }}@github.com/${{ github.repository }}.git \
            "$STORE_DIR"
          cd "$STORE_DIR"
          git config user.name "github-actions[bot]"
          git config user.email "github-actions[bot]@users.noreply.github.com"

      - name: 安装 Emacs 和系统依赖
        run: sudo apt-get update && sudo apt-get install -y emacs-nox

      - name: 执行 auto_publish.el 构建博客
        run: |
          git config core.quotePath false
          emacs --batch -l auto_publish.el

      - name: 推送 HTML 到 master 分支
        run: |
          cd "$STORE_DIR"
          git config user.name "github-actions[bot]"
          git config user.email "github-actions[bot]@users.noreply.github.com"
          git add -A
          git diff --staged --quiet || git commit -m "auto publish: $(date -u +%Y-%m-%dT%H:%M:%SZ)"
          git push origin master

两个 Job 并行运行,互不影响。关键设计决策:

  • **EGO_DIR 和 STORE_DIR 指向 /tmp**:不在 github.workspace 内,避免污染 source 仓库的 git 状态(详见坑3)
  • **~git config core.quotePath false~**:解决 EGO 无法识别中文路径的问题(详见坑4)
  • **增量构建**:~ego-do-publication~ 第二个参数为 ~nil~,只重建变更文件

3.3. 第三步:踩过的坑

3.3.1. 坑1:EGO 缺少依赖

auto_publish.el 在本地运行时,~ht~ 和 dash 两个包早就安装过了。但 CI 是全新环境,~package-install~ 列表里没有它们,导致 EGO 加载失败:

Cannot open load file: No such file or directory, ht

解决:在 package-install 列表中加上 ht 和 ~dash~。

3.3.2. 坑2:EGO 内部 git commit 缺少身份

EGO 构建完后会用 vc-git-commit 提交 store-dir 的变更,但 store-dir 里没配 git user:

Failed (status 128): git –no-pager commit -m Update published html files…

解决:clone master 分支后立即配置 ~git config user.name/email~。

3.3.3. 坑3:EGO 的 stash/pop 在 CI 中失败

这是最有意思的一个坑。

EGO 在发布博客前,会通过 ego-git-repo-up2date-p 检查 repo 是否干净:

(defun ego-git-repo-up2date-p (repo-dir)
  (let* ((default-directory (file-name-as-directory repo-dir))
         (state (vc-git-state "")))
    (equal state 'up-to-date)))

如果检测到 state 不是 up-to-date~,EGO 会先 ~git stash 保护变更,构建完成后再 git stash pop 恢复。但在 CI 中 stash pop 总是失败:

Failed (status 1): git –no-pager stash pop -q 0 .

我一开始以为是 CI 环境 git 版本的问题,试了 git clean -fd~、~git checkout -- . 确保构建前 repo 干净,都没用。试了全量构建模式 (ego-do-publication "blog" t nil "CI auto publish") 绕过 stash,虽然能工作但丢失了增量构建的优势。

后来仔细看 CI 日志,发现了真正的原因:

repo home/runner/work/lujun9972.github.com/lujun9972.github.com state is edited

repo 是 edited 而不是 ~up-to-date~! 明明是从空白环境 checkout 出来的,怎么会有未提交的变更?

答案是:我最初把 EGO 和 master 分支都 checkout 到了 github.workspace 的子目录下:

EGO_DIR: ${{ github.workspace }}/EGO      STORE_DIR: ${{ github.workspace }}/web     

这样 github.workspace~(即 source 分支的 git repo)下面多了 ~EGO/web/ 两个未跟踪目录,~vc-git-state~ 自然返回 ~edited~。

解决:把 EGO 和 master 分支 checkout 到 /tmp 下,与 source repo 完全隔离:

EGO_DIR: /tmp/EGO      STORE_DIR: /tmp/web    

改完后日志变为:

repo home/runner…/lujun9972.github.com/ state is up-to-date

EGO 不再触发 stash,增量构建也正常工作了。

3.3.4. 坑4:中文路径导致 git ls-tree 无法识别 .org 文件

这个坑藏得最深,折腾了我很久。

EGO 的 ego-git-get-all-files 函数用 git ls-tree -r --name-only HEAD 获取所有 org 文件,然后用 string-suffix-p ".org" 过滤:

(defun ego-git-get-all-files (repo-dir &optional branch)
  (let ((output (ego--shell-command
                 repo-dir
                 (concat "env LC_ALL=C git ls-tree -r --name-only "
                         (or branch "HEAD"))
                 t)))
    (delq nil (mapcar #'(lambda (line)
                          (when (string-suffix-p ".org" line t)
                            (expand-file-name line repo-dir)))
                      (split-string output "\n")))))

看起来没问题对吧?但 git ls-tree 默认会对非 ASCII 路径进行八进制转义:

"Emacs\344\271\213\346\200\222/Connection Local Variable.org"

注意路径被双引号包裹,中文变成了 \344\271\213\346\200\222~。这样一来,每行的实际结尾是 ~.org"~(带引号),而不是 ~.org~,导致 ~string-suffix-p ".org" 匹配失败。

结果就是只有纯 ASCII 路径的 about.orgREADME.org 能被匹配到,整个博客 663 个 org 文件中只有这 2 个被 EGO 识别。首页也只显示了 about 一个分类。

解决:在运行 EGO 前设置 ~git config core.quotePath false~,让 git 输出原始 UTF-8 路径:

- name: 执行 auto_publish.el 构建博客
  run: |
    git config core.quotePath false
    emacs --batch -l auto_publish.el

改完后 EGO 正确识别了所有 662 个 org 文件,首页也恢复了完整的 10 个分类。

3.3.5. 坑5:force-all 模式的静默失败

在调试坑4 的过程中,我尝试用 ~force-all=t~(全量构建)来绕过问题:

(ego-do-publication "blog" t nil nil)  

但 EGO 静默失败了——没有任何错误输出,构建日志在 "current-branch is already source" 之后就断了。

查看 EGO 源码发现,~force-all=t~ 时 base-git-commit 会被设为 ~nil~:

(base-git-commit (unless force-all                        (or (ego--get-first-commit-before-publish ...)
                        "HEAD~1")))

base-git-commitnil 时,EGO 的 changed-files 会变成 ~(:update 全部文件 :delete 全部文件)~,即同时删除并重建所有文件。这个逻辑矛盾导致了静默失败。

解决:使用增量构建模式 ~force-all=nil~,让 EGO 自动根据 master 分支的上次发布时间计算变更:

(ego-do-publication "blog" nil nil nil)  

4. 总结

整个折腾过程有个关键收获:

  1. **checkout 路径隔离**:GitHub Actions 中多个仓库的 checkout 不要互相嵌套,否则会污染父仓库的 git 状态
  2. **中文路径注意 ~core.quotePath~**:git 默认转义非 ASCII 路径,在需要解析路径的场景下记得关闭

虽然中间踩了不少坑,但最终效果还是很满意的——以后 push 博客源文件就能自动构建发布,再也不用手动跑 auto_publish.el 了。