私有源码、公开部署与模板文档:GitHub Pages Artifact 实践
当前这套结构最重要的设计不是“用了几个 Git 平台”,而是把源码所有权、构建过程、 部署产物和公开展示拆成独立层次:
Codeberg 私有仓库 GitHub 公开仓库 公网
博客源码(私有) ──CI──> 静态部署产物 / Pages ──CDN──> 博客网站
GitHub 公开仓库 GitHub Actions 公网
模板源码(公开) ──CI──> Doxygen Pages Artifact ──CDN──> 模板文档这套方案可以做到:
- 博客 Markdown、草稿、私有配置放在 Codeberg 私有仓库;
- 博客最终 HTML 通过 GitHub Pages 公开访问;
- C++ 模板源码保持公开,Doxygen 由模板仓库自己的 CI 自动更新;
- 博客不复制模板源码,也不保存模板生成后的 HTML;
- 博客菜单只保存一个相对路径,由
base_url动态拼接完整 URL。
Stacks 业务层级与截图
下图中的红框标识了 Stacks 2 的工作区、项目、看板和任务位置:

Stacks 页面标题可能显示“项目 - Projekt”。本文和持久化模型统一称它为 Project, 避免与看板中的任务 Task 混淆;Task 只表示最小任务卡片。
这张图应从“一个任务向哪里归属”来理解。一个普通任务从自身向上查找,最多会有三个 上级节点:
Workspace 工作区
└── Project 项目(左侧 Documents / File)
└── Stack 看板
└── Task 任务Workspace 工作区
截图左上角的“工作区 - Space”是 Stacks 的工作区根目录,例如 /System/Volumes/Data/data。
在插件中:
- 一个工作区路径对应
Project.json.library_paths中的一项; - 工作区可以包含多个项目、一个 Inbox、
tasks/和people; - 路径重载扫描的就是工作区及其下级文件。
Project 项目
截图左下方 Documents/File 区域中的“项目 - Projekt”以及右侧页面标题,都是 Stacks 项目入口。
在文件层:
- 项目对应工作区中的
.project文件; - 插件索引保存
projectid和project_path; - 项目可以有多个
backlog任务和多个stacks。
项目中未放入具体看板的任务通常进入 backlog。调试时可以把它理解成一个虚拟的 Backlog 看板,但 Stacks 文件里它位于项目顶层的 backlog 数组。
Stack 看板
截图右侧灰色标题栏“0% 看板 - Stack”对应项目下的一个 Stack。
在文件层:
- Stack 保存在
.project文件的stacks[]数组中; - 每个 Stack 有独立 ID、标题和
tasks数组; - 一个项目可以有多个 Stack;
- 插件
Stack.json中的kind=stack节点用于扁平查询和导航。
Task 任务
截图看板中的“任务 - Task”卡片是 Stack 下的一个任务。
在文件层:
- 每个任务保存为
tasks/<task-id>.task; .project文件只保存任务 ID,不保存任务完整正文;- 任务正文包含标题、描述、开始日期、截止日期、优先级、完成和计时字段;
- 从 Stack 下的任务向上查找,三层上级依次是 Stack → Project → Workspace。
Inbox 收件箱
Inbox 是工作区级特殊入口,不属于普通项目,也不经过 Stack。
在文件层:
- Inbox 任务的
project固定为inbox; - 任务文件仍然位于当前工作区的
tasks/下; - 不写入项目
backlog或stacks[].tasks; - Inbox 任务向上查找只有 Workspace → Inbox 两层。
位置和关系对照
| 术语 | 截图位置 | 文件或字段 | 上级关系 |
|---|---|---|---|
| Workspace 工作区 | 左上角“工作区 - Space” | Project.json.library_paths | 根节点 |
| Project 项目 | 左侧 Documents/File 和页面标题 | <project-id>.project、Project.json.nodes | Workspace |
| Stack 看板 | 右侧灰色看板标题 | .project 的 stacks[]、Stack.json.nodes | Project → Workspace |
| Task 任务 | 看板中的任务卡片 | tasks/<task-id>.task、backlog 或 stacks[].tasks | Stack → Project → Workspace |
| Inbox 收件箱 | 左侧 General → Inbox | 任务文件 project="inbox" | Workspace(特殊两层结构) |
这张图也会作为业务术语参考图,被模板仓库的 Doxygen 业务文档引用。
三个仓库分别负责什么
Codeberg 私有博客源码
Codeberg 仓库保存 Zola 的真源:
00BlogZola/
├── content/ Markdown 文章
├── templates/ 站点级模板覆盖
├── sass/ 样式源码
├── static/ 公开静态资源
├── scripts/ 加密、搜索索引、部署前处理
├── build-site.sh 正式构建入口
└── zola.toml 全站配置私有源码不等于私有网站。Markdown 可以是私有资产,但构建出来的 public/ 会发布为 公开静态站点。
需要注意:公开部署产物会暴露其中所有内容。即使源码仓库私有,只要错误地把密钥、 日志、真实用户数据放进 public/,访问者仍可以下载。因此构建产物的白名单和敏感文件 清理必须由 CI 保证。
GitHub 公开部署仓库
GitHub 仓库承担两类职责:
- 保存或者接收博客构建产物;
- 使用 GitHub Pages 托管静态网站。
采用 GitHub Pages Artifact 时,部署仓库自身不需要保存生成后的 HTML 历史。Actions 可以 checkout Codeberg 源码,构建 public/,然后上传为 Pages artifact。
公开模板仓库
CXX-CMake-template 是独立的公开源码仓库。模板的 Doxygen 由模板仓库自己维护:
CXX-CMake-template/
├── single-exe-classic/
├── single-exe-modules/
├── single-exe-static-modules/
├── ci/build-pages-docs.sh
├── ci/pages-templates.tsv
└── .github/workflows/docs-pages.yml每次模板、文档构建脚本或说明变化时,模板仓库重新生成全部 Doxygen,再一次性发布到:
https://suchaharcan.github.io/CXX-CMake-template/
博客不需要知道 Doxygen 是怎样构建的,只链接这个公开入口。
GitHub Pages Artifact 的原理
传统 Pages 经常把生成文件推送到 gh-pages 分支,再由 Pages 从该分支发布。 GitHub 当前更推荐的方式:
flowchart LR
A[源码仓库] --> B[checkout]
B --> C[安装依赖]
C --> D[zola build / doxygen]
D --> E[upload-pages-artifact]
E --> F[deploy-pages]
F --> G[GitHub Pages CDN]1. GitHub Actions 构建静态目录
Zola 或 Doxygen 的输出本质上都是静态文件:
public/
├── index.html
├── style.css
├── script.js
├── 03Share_blog/
└── ...Pages 不需要数据库或应用服务器,只需要完整、可直接访问的静态文件树。
2. upload-pages-artifact 打包
Action 会把输出目录打成 GitHub Pages 专用 artifact。artifact 中包括:
- HTML、CSS、JavaScript;
- 图片、字体和 Doxygen SVG;
- 根路径下的
.nojekyll; - 所有需要公开下载的静态资源。
.nojekyll 用于避免 GitHub Pages 再经过 Jekyll 处理,尤其适合 Doxygen、Zola 或包含 下划线目录的静态站点。
3. deploy-pages 创建 deployment
部署 Job 使用 GitHub OIDC 身份和临时 token,请求 GitHub Pages 发布刚才上传的 artifact。成功后得到官方 page_url。
部署过程不依赖长期保存的仓库写 token,也不需要把 HTML commit 到分支。
4. Pages CDN 原子切换
GitHub Pages 将 artifact 作为完整站点发布。对外表现为一次原子切换:
- 新版本构建成功,整站切换;
- 构建或上传失败,旧站点继续在线;
- 不需要“先生成 HTML,再逐个推送文件”的中间状态。
这也是为什么模板 Pages CI 每次构建全部模板,而不是只上传发生变化的一个子目录。 artifact 是整站快照,如果只上传一个模板,其他模板就会消失。
最小 Actions 结构
博客和模板仓库可以使用同一套 Pages 模型。核心 YAML 结构如下:
permissions:
contents: read
pages: write
id-token: write
jobs:
build:
runs-on: ubuntu-24.04
steps:
- uses: actions/checkout@v4
- run: ./build-site.sh
- uses: actions/configure-pages@v5
- uses: actions/upload-pages-artifact@v3
with:
path: public
deploy:
needs: build
runs-on: ubuntu-24.04
environment:
name: github-pages
url: ${{ steps.deployment.outputs.page_url }}
steps:
- id: deployment
uses: actions/deploy-pages@v4如果博客源码在 Codeberg,代码检出步骤可以改成公开的 Codeberg 拉取,或者由你在本地/ 中间 CI 构建后把 public/ 传到 GitHub artifact 流程。关键是:GitHub 发布动作只接收 静态目录,不需要博客源码长期存放在 GitHub。
博客源码私有、站点公开的关键边界
私有的是源码访问权限
Codeberg 私有仓库可以阻止别人直接读取:
- Markdown 原稿;
- 草稿;
- 模板和 Sass;
- 构建脚本;
- Git 历史。
公开的是最终 HTML
GitHub Pages 是公开静态托管。发布后,浏览器必须能够下载:
index.html;- CSS、JS、图片;
- 文章正文;
- Doxygen HTML;
- 任何你明确放入 artifact 的文件。
因此不要把这些内容放入 Pages artifact:
.env、token、私钥;- 构建机日志;
- 用户数据库;
- 本地绝对路径索引;
- 未准备公开的草稿;
- 加密密钥。
真正的敏感数据应该留在私有服务端,不能靠“HTML 不好找”来保密。
博客菜单如何动态链接模板文档
{ url = "CXX-CMake-template/", name = "模板文档", icon = "../simple/doxygen", base_relative = true }站点级 templates/partials/sidebar.html 对 base_relative = true 的菜单项使用:
link_url = config.base_url ~ link.url因此结果是:
config.base_url + "CXX-CMake-template/"当前结果是:
https://suchaharcan.github.io/CXX-CMake-template/如果以后把站点迁移到:
base_url = "https://new.example.com/"菜单会自动变成:
https://new.example.com/CXX-CMake-template/博客正文和菜单中都不需要硬编码 GitHub Pages 完整域名。
这里没有修改 themes/ametrine-pre 子模块,而是使用 Zola 的模板覆盖机制,在博客 仓库自己的 templates/partials/sidebar.html 中增加 base_relative 规则。普通站内 链接仍然沿用主题原有的 get_url 行为。
Doxygen 图标
主题自带 Simple Icons 图标集,其中包含:
themes/ametrine-pre/icons/simple/doxygen.svg主题图标组件支持相对图标路径,因此:
icon = "../simple/doxygen"会解析为 Doxygen 官方图标,而不是继续使用报纸图标。
模板仓库的 Pages 汇总流程
模板仓库的 ci/build-pages-docs.sh 做四件事:
- 读取
ci/pages-templates.tsv; - 为每个模板配置独立的 Doxygen 构建目录;
- 把
build/docs/doxygen/html复制到pages-site/<template>/; - 生成根首页和
.nojekyll。
输出结构:
pages-site/
├── .nojekyll
├── index.html
├── single-exe-classic/
├── single-exe-modules/
└── single-exe-static-modules/模板仓库工作流只运行 Doxygen,不运行测试和真实数据写操作:
CXX_ENABLE_DOCS=ON
CXX_DOC_CLANGDOC=OFF
CXX_ENABLE_GTEST=OFF这能让文档发布与业务 CI 解耦。业务测试仍由模板矩阵工作流负责,Pages 工作流只关心 “能否生成一个可公开访问的静态站点”。
除文档外还能链接什么?
GitHub Pages Artifact 不限于 API 文档。只要内容可以表达为静态文件,就能统一发布和 从博客链接。
1. 项目主页和作品集
每个项目可以有独立入口:
/CXX-CMake-template/
/Omni-Stacks/
/AlfredWorkflows/页面可以包含项目截图、功能对比、安装引导、更新日志和演示视频。
2. Release 页面和安装说明
Pages 可以承载:
- 版本说明;
- 安装教程;
- 系统兼容矩阵;
- SHA-256 校验值;
- 链接到 GitHub Releases 的下载地址。
真正的二进制安装包建议放在 GitHub Releases;Pages 可以承担展示和引导,不必把 pages 当大文件中转站。
3. WebAssembly 演示
C++ 项目可以编译为 WASM,并将静态 HTML 作为 artifact 发布:
/demo/parser/
/demo/renderer/
/demo/task-core/博客文章可以直接链接到可交互演示,而不是只放截图。
4. 覆盖率、测试和性能报告
CI 可以生成静态报告:
- HTML coverage;
- Doxygen call/caller graph;
- benchmark 图表;
- CTest 结果摘要;
- clang-tidy 报告;
- 二进制大小对比。
报告同样通过一个 Pages artifact 发布,博客链接到对应目录即可。
5. API Schema 和开发者门户
静态 Pages 可以发布:
- OpenAPI JSON/YAML;
- Swagger UI 静态页面;
- JSON Schema;
- protobuf 文档;
- CLI 参数参考;
- SDK 示例首页。
6. 演示文稿和课程页面
Reveal.js、Marp 导出的 HTML、教程目录、学习路线和实验说明都可以部署到 Pages。
7. RSS、JSON Feed 和机器可读索引
除了给人看的 HTML,还可以发布:
/feed.json
/index.json
/releases.json
/search-index.json博客或其他程序可以消费这些公开静态数据。
8. PWA 和离线资源
Pages 可以托管 manifest.webmanifest、Service Worker、图标和离线缓存资源。需要注意 跨域 Service Worker 作用域限制;独立项目通常应部署在独立路径或域名下。
Artifact 与 Release 的区别
两者都保存构建产物,但用途不同:
| 能力 | Pages Artifact | GitHub Release |
|---|---|---|
| 主要用途 | 部署静态网站 | 发布版本下载 |
| 是否有公网站点 | 是 | 否,需下载 |
| 适合 HTML | 是 | 可以,但不适合浏览 |
| 适合大二进制 | 不建议 | 是 |
| 长期版本保存 | deployment 历史为主 | 明确版本资产 |
| CDN 浏览 | Pages CDN | Release 下载 |
常见组合是:
GitHub Release:保存安装包、校验文件、发布说明
GitHub Pages:展示安装步骤、文档、演示和下载入口
博客:链接到稳定的 Pages 页面常见问题
只部署变更模板可以吗?
不建议直接用 deploy-pages 只上传一个子目录。它会把 artifact 当成整站。
可选方案:
- 每次构建全部模板,整站发布,最稳定;
- 维护一个额外的聚合仓库;
- 改用
gh-pages分支并配合keep_files,但要处理并发覆盖。
当前模板数量有限,采用“全部构建、整站发布”最简单。
私有源码会不会让 Pages 也不可见?
不会。Pages 只关心最终 artifact 是否可公开访问。源码权限和站点发布权限是两层配置。
但如果 Pages 站点被设置为私有访问,就需要符合 GitHub 套餐和组织策略;当前站点使用 公开 Pages。
base_url 改变后模板链接会失效吗?
不会。菜单使用相对路径,主题通过 get_url 拼接当前 base_url。
不要重新写回 @/CXX-CMake-template/。@/ 表示博客内部的 content 页面,而不是外部 GitHub Pages 路径。
能否在博客内直接嵌入 Doxygen?
可以直接超链接。使用 iframe 需要检查 Pages 和博客两侧的 CSP;Doxygen 内部链接很多, 独立打开通常体验更稳定。
文档仓库是否要包含在博客仓库?
不需要。模板源码、Doxygen 构建脚本和 Pages 工作流都保留在模板仓库。博客只保存链接。
最终结构
Codeberg 私有:
博客 Markdown / Zola / Sass / 构建脚本
│
└── CI 构建静态 public/
GitHub 公开:
博客 GitHub Pages Artifact
└── https://suchaharcan.github.io/
GitHub 公开:
CXX-CMake-template 源码
│
└── Doxygen Pages Artifact
└── https://suchaharcan.github.io/CXX-CMake-template/
博客菜单:
url = "CXX-CMake-template/"
+ config.base_url
+ ../simple/doxygen 图标这套方案的核心价值是:
私有源码负责“如何构建”,公开部署负责“如何展示”,模板仓库负责“维护自己的文档生命周期”。
博客、Pages 部署和模板文档可以独立升级,不需要互相复制源码,也不需要每次迁移时重新 写死域名。