从私有源码仓到公开发布仓:VILab 的桌面应用发布架构实践
这篇文章记录的是 VILab 桌面应用从“私有源码仓直接开发”演进到“私有源码仓 + 公开发布仓 + GitHub Pages 自定义域名”的完整实践。
它不是一篇抽象讨论,而是一套已经落地的发布架构说明,覆盖了:
- 为什么要拆分私有开发仓和公开发布仓
- 如何设计公开站点、下载页、更新日志与文档入口
- 如何让 Tauri 桌面应用切换到公开更新源
- 如何把 GitHub Pages 和自定义域名
vilab.orulink.ai接起来 - 实际落地时会遇到哪些问题,以及如何判断是否真的成功
为什么要做这次改造
项目早期,源码仓库是私有的,这对研发协作没有问题,但对桌面应用的正式分发并不友好。
最主要的矛盾点有四个:
- 私有仓库不适合作为最终用户的自动更新源。
- 用户需要一个公开可访问的下载入口。
- 每次发版最好有公开可查的版本说明和更新日志。
- 文档、下载、更新说明最好统一收口到一个正式域名下。
如果继续让客户端直接追私有仓库 release,会带来一系列现实问题:
- GitHub 私有 release 资产不适合匿名访问
- 客户端内置 updater 不应该依赖用户额外登录 GitHub
- 自动更新链路会被权限和 token 管理复杂化
- 用户无法自然获取下载、文档和 changelog
所以更合理的做法是把职责拆开:
- 私有仓库继续负责开发与构建控制
- 公开仓库专门负责对外分发
这就是这次改造的出发点。
最终采用的架构
这次最终落地的结构是三层:
1. 私有源码仓
私有仓仍然是唯一的开发主仓,负责:
- 源码
- workflow
- 构建脚本
- 版本说明源文件
- 发布控制逻辑
也就是说,所有内容仍然从私有仓生产出来。
2. 公开发布仓
新增公开仓:
Ro-In-AI/VILab-public
它只负责公开分发,不承担主开发职责。主要承载:
- GitHub Releases
- 安装包
- 签名文件
- checksum
latest.json- 静态官网
- 下载页
- 更新日志页
- 公共文档
3. 自定义域名
最终对外使用的域名是:
vilab.orulink.ai
公开站点内容统一从这个域名提供:
/:官网首页/docs/:文档/download/:下载页/changelog/:更新日志
为什么是“公开发布仓”,而不是“公开源码仓”
这是这次设计里非常重要的一个原则。
很多项目在这个阶段会纠结“要不要直接把源码开源”。但这其实是另一个问题。
这里要解决的是“如何把产品正确发布给用户”,而不是“是否公开开发过程”。
所以 VILab 采用的是:
- 私有开发
- 公开分发
这样做的好处很明确:
- 研发流程保持私有和稳定
- 自动更新源可以匿名访问
- 用户能直接看到 release、下载和 changelog
- 文档和产品入口可以统一到正式域名
- 不必为了分发而暴露源码历史
这套模式非常适合桌面应用,尤其适合:
- Tauri 项目
- Electron 项目
- 商业化早期产品
- 仍处于私有迭代阶段的工具型软件
站点结构如何设计
这次没有额外起一个新的官网工程,而是直接基于现有文档体系扩展。
项目原本已经有 VitePress 文档,因此公开站点被设计成两层:
第一层:公开入口页
由自定义脚本生成:
//download//changelog/
这层负责面向用户的公开入口和版本说明。
第二层:文档页
继续使用 VitePress:
/docs/
这样做的最大收益是:
- 不需要维护第二套前端栈
- 本地内嵌文档路径仍然保持稳定
- 公共站点和文档自然整合
- 静态构建链路足够简单
关键实现改造
一、生成完整的公开站点产物
为了让公开仓不只是一个 release 仓,而是真正的“公开入口”,新增了脚本:
scripts/build-public-site.mjs
它负责把以下内容组装成一个完整的静态站:
- 官网首页
- 下载页
- changelog 列表页
- 每个版本的 changelog 详情页
- VitePress 文档构建产物
这个脚本会读取:
.github/releases/*.md
然后自动生成 changelog 页面,同时把 docs/.vitepress/dist 复制到最终产物里的 /docs/ 下。
最终输出目录是:
public-site/dist
这样,公开仓得到的就不再是零散的 release 资产,而是一个完整可部署的公开站。
二、把公开站点同步到公开仓
新增了同步脚本:
scripts/sync-public-repo.mjs
它的职责是:
- 克隆公开仓
- 切到
main - 清理旧站点内容
- 把
public-site/dist整体复制进去 - 自动提交并推送
这样一来,公共站点的同步动作就被标准化成了两步:
npm run public:site:build
npm run public:repo:sync这让“更新官网”和“发布版本”可以共享同一套控制逻辑。
三、把桌面 updater 切到公开仓
原本 updater 指向旧仓库 release,现在已经切到公开仓。
Tauri 配置中的 updater endpoint 改成:
https://github.com/Ro-In-AI/VILab-public/releases/latest/download/latest.json这意味着后续客户端正式更新只认公开发布仓,不再依赖私有仓。
对应修改点在:
src-tauri/tauri.conf.json
同时,latest.json 的生成脚本也改成了支持目标仓参数:
PUBLIC_RELEASE_REPO
对应脚本:
scripts/generate-updater-manifest.mjs
四、让应用内文档入口自动跳到公开域名
应用内原本就有文档入口,但之前只有简单的 fallback URL。
这次把文档地址优先级改成了:
VITE_DOCS_URLVITE_PUBLIC_SITE_URL + /docs/- 默认 fallback
这样发布时只要设置:
VITE_PUBLIC_SITE_URL=https://vilab.orulink.ai应用里的文档入口就会自动跳到:
https://vilab.orulink.ai/docs/对应文件:
src/lib/docs.ts
五、把 workflow 改成跨仓发布
为了让私有仓成为真正的“发布控制面”,workflow 也一起调整了。
新增或调整后的主要 workflow 包括:
.github/workflows/docs.yml.github/workflows/release-windows.yml.github/workflows/release-mac.yml
它们的目标是:
- Windows 版本发到公开仓 release
- macOS 版本发到公开仓 release
- 公共站点内容同步到公开仓
main
这样,私有仓仍然是控制中心,而公开仓只是分发终点。
核心环境变量和 Secrets
这套架构中最关键的三个变量是:
1. PUBLIC_RELEASE_REPO
例如:
Ro-In-AI/VILab-public它告诉脚本:最终应该把内容发布到哪个公开仓。
2. PUBLIC_RELEASE_PAT
这是一个 GitHub token,用来让私有仓 workflow 或本地终端获得写公开仓的权限。
它主要用于:
- 推送公开站点
- 创建和更新公开 release
- 上传安装包、签名文件和
latest.json
3. PUBLIC_SITE_DOMAIN
例如:
vilab.orulink.ai它的作用是:
- 生成
CNAME - 生成公开站点 canonical URL
- 让文档、下载和 changelog 链接统一指向正式域名
域名为什么选择 vilab.orulink.ai
这次没有直接使用根域名 orulink.ai,而是选择了子域名:
vilab.orulink.ai
原因很简单:
- 子域名接 GitHub Pages 更简单
- DNS 配置更清晰
- 不需要根域名的
A/AAAA复杂配置 - 后续主域名还可以留给官网、导航页或其他产品
对于 GitHub Pages 来说,子域名通常是更稳妥的选择。
DNS 是怎么配置的
因为是子域名,所以最终只需要一条 CNAME:
- 类型:
CNAME - 主机记录:
vilab - 记录值:
ro-in-ai.github.io
注意几个容易踩坑的点:
- 不要写成
ro-in-ai.github.io/VILab-public - 不要把
vilab.orulink.ai先跳去orulink.ai - 不要配通配符
*
正确的做法就是让 vilab.orulink.ai 直接 CNAME 到 ro-in-ai.github.io
我是如何验证 DNS 成功的
最直接的方法是用 dig:
dig vilab.orulink.ai CNAME +short当返回:
ro-in-ai.github.io.就说明 DNS 方向已经正确。
在这次实际测试里,确实得到了这个结果,所以 DNS 本身没有问题。
为什么一开始 HTTPS 还是不通
这是这次最容易让人误解的地方。
GitHub Pages 后台显示的是:
DNS check successfulEnforce HTTPS — Unavailable ... because a certificate has not yet been issued
这个状态说明:
- DNS 已正确
- GitHub Pages 已识别域名绑定
- 站点内容已经能提供
- 唯一还没完成的是 TLS 证书签发
也就是说,这时候如果 HTTPS 报错,并不代表配置失败。
实际测试时我得到的现象是:
HTTP 已经正常
curl -I http://vilab.orulink.ai返回 200 OK
HTTPS 证书还未就绪
curl -I https://vilab.orulink.ai会报证书不匹配
进一步查看证书:
openssl s_client -connect vilab.orulink.ai:443 -servername vilab.orulink.ai </dev/null 2>/dev/null | openssl x509 -noout -subject -issuer -ext subjectAltName看到的还是 GitHub 默认的:
CN=*.github.io
这意味着:
- Pages 的内容已经在
- 域名绑定已经在
- 但针对
vilab.orulink.ai的专属证书还没发下来
这一步通常只能等待 GitHub Pages 自动完成。
如何判断 HTTPS 真正生效了
当以下两个信号出现时,就说明 HTTPS 已经真正完成:
1. GitHub Pages 后台
Enforce HTTPS 从灰色不可选变成可勾选
2. 终端校验通过
curl -I https://vilab.orulink.ai不再报证书错误
同时,openssl 查看证书时,subjectAltName 中会出现:
vilab.orulink.ai
这时就可以认为整个域名接入彻底完成。
这次改造的收益
当整个链路打通后,收益是非常直接的。
1. 私有开发与公开分发彻底解耦
开发过程继续保持私有,不影响产品对外发布。
2. 自动更新真正面向最终用户
客户端现在可以直接从公开仓读取 latest.json,不再依赖私有访问权限。
3. 官网、下载、文档和 changelog 收口到一个域名
用户只要记住:
vilab.orulink.ai
就能找到全部公开内容。
4. Release notes 不再只是内部 markdown
每个版本说明不只是私有仓里的记录,而是真正成为了公开 changelog 页面的一部分。
过程中最重要的经验
1. 自动更新源必须从最终用户视角设计
不要只看开发者方便不方便,要看普通用户是否能够匿名访问和顺利更新。
2. 公开发布仓不等于公开源码仓
公开分发和开源开发是两件事,不应该混为一谈。
3. GitHub Pages 接子域名明显比接根域名轻松
vilab.orulink.ai 这种方式比直接用 orulink.ai 更稳更省心。
4. DNS 配对成功不等于 HTTPS 已经完成
很多时候 DNS 是对的,但证书还在签发中,这属于正常中间态。
5. 先让公开仓内容跑通,再接正式域名
只有在公开仓内容和 Pages 部署已经成功时,域名接入才真正有意义。
最终结论
这次实践最后得到的是一套比较完整且可复用的发布架构:
- 私有源码仓继续承担开发职责
- 公开仓
Ro-In-AI/VILab-public负责分发 - GitHub Releases 提供安装包、签名、校验和更新清单
- GitHub Pages 提供官网、文档、下载和 changelog
- 自定义域名
vilab.orulink.ai统一对外 - 桌面应用 updater 指向公开仓
这套方案的意义不只是“把站点搭起来”,而是把整个项目从“能开发”推进到了“能正式交付”。
对于任何一个桌面应用来说,源码只是产品的一部分。
真正的发布链路、更新链路、文档入口和公开分发面,本身也是产品能力的一部分。
而公开发布仓,恰恰就是把这些能力组织起来的关键节点。