发布于 

自建 ZCode 内部 mirror:mirrors.changediff.top 与 apt 源落地记录

最近把 ZCode 的安装方式从「人工下载 + dpkg -i」改成了内部 apt 源。这篇记录整个方案和踩过的四个坑。

背景:为什么要自己搭 mirror

ZCode(智谱 Z.AI 的 IDE 产品)官方的分发方式对国内用户不太友好:

  • 官网下载页 https://zcode.z.ai 只宣传 AppImage
  • 真实 CDN 在 https://cdn-zcode.z.ai/zcode/electron/releases/<版本>/linux-x64/,同目录下其实有 deb/rpm,但页面没写
  • 国内直连 CDN 慢,且没有 apt 源

对 deb03 这台开发机来说,每次升级都要人工去 CDN 找版本号、下载、dpkg -i。既然 ali 上已经有 nginx + certbot + 博客站点,顺手加一个 mirror 是最自然的做法。

方案

ali(阿里云 ECS)上建内部 mirror,deb03 通过 apt 源安装/升级。

组件 位置
域名 mirrors.changediff.top
站点根 /var/www/mirrors
包目录 /var/www/mirrors/zcode/deb/var/www/mirrors/zcode/rpm
apt 源路径 https://mirrors.changediff.top/zcode/deb/
nginx 配置 /etc/nginx/sites-available/mirrors.changediff.top.conf
同步脚本 /usr/local/bin/zcode-mirror-sync.sh(每日 03:17 cron)
索引脚本 /usr/local/bin/zcode-mirror-index.sh
同步日志 /var/log/zcode-mirror-sync.log
deb03 apt 源 /etc/apt/sources.list.d/zcode-mirror.list

全部由 Ansible playbook zcode_mirror.yml 管理,代码在 ccops 仓库。

保留策略

只保留官方最近 3 个版本的 deb + rpm,不存 AppImage。当前是 3.14.1 / 3.14.0 / 3.12.3,旧版本由同步脚本的 KEEP=3 自动清理。

版本发现方式是抓官方 changelog 页面,正则取版本号后按语义版本降序排前 3。

apt 源形式:Flat Repository

deb 目录直接当 flat repository 用,不需要 reprepro/dak 这类仓库工具:

1
2
# /etc/apt/sources.list.d/zcode-mirror.list
deb [trusted=yes] https://mirrors.changediff.top/zcode/deb ./
  • 索引由 dpkg-scanpackages --multiversion 生成 Packages(同时产出 .gz / .xz
  • Release 由脚本生成;因为用了 [trusted=yes],apt 不校验签名,但仍写入 checksum 条目
  • 单行 sources.list,不需要 signed-by、不需要分发 GPG key

源现在已经在线,可以直接看索引:https://mirrors.changediff.top/zcode/deb/Packages

同步流程

1
2
3
4
5
6
7
cron 03:17 → zcode-mirror-sync.sh
1. 抓官方 changelog,确定最近 3 个版本
2. 逐个版本检查 deb/rpm 是否已在本地(有则 skip)
3. 缺失的从 CDN 下载,校验大小/md5(.part 原子写入后再 mv)
4. 清理不在最近 3 版清单里的旧包
5. 调用 zcode-mirror-index.sh 重建 Packages/Release
6. 写日志;正常情况静默,异常才推送

deb03 侧之后就是标准的 apt update && apt install zcode / apt upgrade

四个坑

1. 证书签发顺序:先建 nginx 站点,再扩 SAN

首次 certbot certonly --expand 报 404:

1
Detail: Invalid response from https://mirrors.changediff.top/.well-known/acme-challenge/xxx: 404

原因:mirrors.changediff.top 的 server 块还不存在时,这个域名的 HTTP 请求落到 nginx 的 default_serverreturn 444),ACME 挑战文件写下去了也取不到。

正确顺序:先建 nginx 站点并 reload,跑 certbot。playbook 里用 meta: flush_handlers 保证 reload 先发生。

2. certbot 的 webroot 必须是 blog 的目录

换对顺序后仍然 404,因为用了 -w /var/www/mirrors(mirror 自己的 root)——mirror 站点没有 acme-challenge location,挑战文件放进去同样取不到。

最终生效的命令用了博客站点的 webroot

1
2
3
4
certbot certonly --webroot -w /var/www/changediff-blog \
--cert-name changediff.top --expand --non-interactive \
-d changediff.top -d www.changediff.top -d ali.changediff.top \
-d mirrors.changediff.top

mirrors.changediff.top 进 SAN,与 blog 共用同一张证书。

3. Flat Repository 必须生成 Packages 索引

一开始只把 deb 丢进目录就配 apt 源,结果 404:

1
E: The repository 'https://mirrors.changediff.top/zcode/deb ./' does not have a Release file.

deb 目录需要 dpkg-scanpackages 生成的 Packages 索引。装 dpkg-dev 提供该工具。

4. Release 的 checksum 段不能混算法

第一版脚本把 md5 和 sha256 都写进了 MD5Sum: 段,apt 报:

1
W: 无法鉴别的 .../Release: 无哈希条目

正确写法MD5Sum: 段只放 md5 行,SHA256: 段只放 sha256 行,各自独立。索引脚本里用 Python 分段生成。

小结

四个坑里有三个都指向同一个教训:nginx 站点结构(default_server、location、webroot)会直接影响 ACME 和 apt 客户端的行为,搭 mirror 时不能只想着”文件放进目录就行”。

现在 deb03 装 ZCode 就是一行命令,ali 上每天自动同步最新 3 版。playbook 和脚本都在 ccops 仓库,ansible/playbooks/zcode_mirror.yml,完整文档见 docs/zcode-mirror.md