Skip to content

Commit 12458ac

Browse files
committed
ci(download): 下载源改为可通过 DOWNLOAD_BASE_URL 切换,为 CDN 接入预备
阶段1 预备改造。未配置 Secret 时行为完全不变(回退自建服务器), 使 CDN 开通进度不阻塞代码,出问题清空 Secret 重新部署即可一键回退。 - DownloadCta 读取 NEXT_PUBLIC_DOWNLOAD_BASE,缺省回退 ECS,并去除末尾斜杠 - deploy-website.yml 从 DOWNLOAD_BASE_URL Secret 注入构建期变量 - release.yml 在 Secret 存在时用 -c.publish.0.url 覆盖 electron-updater 的 generic 更新源,使新包 app-update.yml 指向 CDN - 新增技术方案文档:CDN 回源 ECS 优于 CDN+OSS(安装包为固定文件,缓存 命中率近 100%,回源 Host 设为主域即可零改动 nginx),含开通步骤、 成本估算、防刷流量与 latest.yml 禁缓存等风险提示 - 补交 Git LFS 打包失败踩坑记录(索引已引用,否则链接失效) 验证:默认构建内联 ECS 源;注入测试域名后正确替换且无双斜杠。
1 parent 5170f95 commit 12458ac

6 files changed

Lines changed: 266 additions & 6 deletions

File tree

‎.github/workflows/deploy-website.yml‎

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -24,6 +24,10 @@ jobs:
2424
cache-dependency-path: website/package-lock.json
2525

2626
- name: Install dependencies and build
27+
env:
28+
# 下载源基址:配置了 CDN 后填入该 Secret(如 https://dl.entropydecrease.com);
29+
# 未配置时为空字符串,前端自动回退至自建服务器源
30+
NEXT_PUBLIC_DOWNLOAD_BASE: ${{ secrets.DOWNLOAD_BASE_URL }}
2731
run: |
2832
cd website
2933
npm ci

‎.github/workflows/release.yml‎

Lines changed: 18 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -61,7 +61,24 @@ jobs:
6161
- run: cd client && npm ci
6262
# --publish never:发布由下方 release job 统一负责,避免 electron-builder
6363
# 因检测到 tag 而自行尝试发布(github provider 无 GH_TOKEN 会报错)
64-
- run: cd client && npm run electron:build -- --publish never
64+
#
65+
# DOWNLOAD_BASE_URL 已配置时,用 -c.publish.0.url 覆盖 generic 更新源为 CDN,
66+
# 使新包内的 app-update.yml 指向 CDN;未配置时沿用 electron-builder.yml
67+
# 中的自建服务器地址。注:存量客户端的更新源已固化在已安装的
68+
# app-update.yml 中,故 ECS 与 GitHub 两侧资产均需继续发布。
69+
- name: Build Electron app
70+
shell: bash
71+
env:
72+
DOWNLOAD_BASE_URL: ${{ secrets.DOWNLOAD_BASE_URL }}
73+
run: |
74+
cd client
75+
if [ -n "$DOWNLOAD_BASE_URL" ]; then
76+
echo "Update feed -> $DOWNLOAD_BASE_URL"
77+
npm run electron:build -- --publish never -c.publish.0.url="$DOWNLOAD_BASE_URL"
78+
else
79+
echo "Update feed -> 默认(electron-builder.yml 中的自建服务器)"
80+
npm run electron:build -- --publish never
81+
fi
6582
- uses: actions/upload-artifact@v4
6683
with:
6784
name: release-${{ matrix.os }}
Lines changed: 94 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,94 @@
1+
# 知识卡片 · 踩坑记录
2+
3+
## 基本信息
4+
5+
| 字段 | 内容 |
6+
|------|------|
7+
| 标题 | Git LFS 图标未在 CI 拉取致 electron-builder 打包报 `ERR_ELECTRON_BUILDER_CANNOT_EXECUTE` |
8+
| 日期 | 2026-07-30 |
9+
| 类型 | 踩坑记录 |
10+
| 标签 | #CI #GitLFS #electron-builder #GitHubActions #发布 #安装包 |
11+
12+
---
13+
14+
## 症状
15+
16+
GitHub Actions `Release Electron App`(`release.yml`)中 `cd client && npm run electron:build` 每次发版必失败,导致 **GitHub Release 资产为空**、自动更新链断裂、安装包从未同步到服务器。Windows 与 macOS 报同一个错:
17+
18+
```
19+
app-builder.exe process failed ERR_ELECTRON_BUILDER_CANNOT_EXECUTE
20+
Exit code: 1
21+
at ChildProcess.cp.emit (client/node_modules/cross-spawn/lib/enoent.js:34:29)
22+
at WinPackager.signApp (app-builder-lib/src/winPackager.ts:270:27)
23+
at WinPackager.doSignAfterPack (app-builder-lib/src/platformPackager.ts:346:32)
24+
```
25+
26+
关键陷阱:**本地能构建成功(存在历史 `Setup 0.25.0.exe` 产物),只有 CI 失败**;错误信息 `CANNOT_EXECUTE` 极具误导性,看似"二进制无法执行"。
27+
28+
## 环境
29+
30+
| 项目 | 版本/信息 |
31+
|------|----------|
32+
| CI | GitHub Actions(ubuntu 触发、windows-latest/macos-latest 构建) |
33+
| 打包 | electron ^35.7.5 + electron-builder ^25.1.8 |
34+
| 相关文件 | `.github/workflows/release.yml`、`client/electron-builder.yml`、`.gitattributes` |
35+
| 资源托管 | Git LFS(`.gitattributes`: `*.png filter=lfs`) |
36+
37+
## 排查过程
38+
39+
1. 先发现 `release.yml` 从未成功 → GitHub Release(v0.27.0)资产为空,正是 electron-updater 拿不到 `latest.yml` 的根源
40+
2. 看 build job 日志:`fail-fast` 默认开启,**macOS 腿失败连带取消了 Windows 腿** → 先移除无有效 target 的 macOS 腿(产品实为 Windows-only)
41+
3. Windows 单独跑仍失败,报同一 `ERR_ELECTRON_BUILDER_CANNOT_EXECUTE` at `signApp`
42+
4. **误判一次**:依据上游 issue 假设为"Windows Defender 锁住刚生成的 exe 致 rcedit 失败",加了 Defender 排除步骤 → **仍失败**,假设被证伪
43+
5. 抓取**完整堆栈**(而非只看顶部几帧),发现真正的失败函数:
44+
```
45+
app-builder/pkg/icons.DecodeImageAndClose (image-util.go:90) ← 图片解码失败
46+
→ LoadImage → doConvertIcon → ConvertIcon
47+
```
48+
即失败发生在 **PNG→ICO 图标转换**阶段
49+
6. 顺藤查图标文件:`.gitattributes` 有 `*.png filter=lfs`;`git cat-file -s HEAD:client/app-icon.png` = **131 字节**,内容是 LFS 指针文本(`version https://git-lfs...`)而非真图(真图 550KB 在 LFS)
50+
7. 查 `release.yml` 的 `actions/checkout@v4` → **未配置 `lfs: true`** → 定位真因
51+
52+
## 根因
53+
54+
`*.png` 全部由 Git LFS 托管,而 `release.yml` 的 checkout 未开启 LFS:
55+
56+
```yaml
57+
# 错误:CI 拿到的是 131 字节 LFS 指针文本,不是真实 PNG
58+
- uses: actions/checkout@v4
59+
```
60+
61+
electron-builder 在 `signAndEditResources`(写入版本号 + 嵌入图标,**无论是否配置签名证书都会执行**)阶段调用 app-builder 把 `app-icon.png` 转成 `.ico`,对指针文本解码失败,抛出泛化的 `ERR_ELECTRON_BUILDER_CANNOT_EXECUTE`。本地因 LFS 文件已 checkout 为真图,故本地正常、CI 失败。
62+
63+
## 解决方案
64+
65+
给需要 LFS 资源的 workflow 的 checkout 显式启用 LFS:
66+
67+
```yaml
68+
# release.yml(打包需 app-icon.png)
69+
- uses: actions/checkout@v4
70+
with:
71+
lfs: true
72+
73+
# deploy-website.yml 同理(beian.png / sponsor-qr.png 否则为线上坏图)
74+
- uses: actions/checkout@v4
75+
with:
76+
lfs: true
77+
```
78+
79+
验证:release.yml 三 job 全绿;GitHub Release 出现 `Entropydecrease-Setup-0.28.3.exe/.blockmap/latest.yml`;服务器 `https://entropydecrease.com/downloads/latest.json` 返回正确版本,`.exe` 带 `Accept-Ranges: bytes`、`latest.yml` 带 `Cache-Control: no-cache`;官网 `sponsor-qr.png` 由 131 字节指针恢复为 193KB 真图。
80+
81+
## 教训
82+
83+
- **下次如何避免**:仓库启用 Git LFS 后,**所有会读取 LFS 资源的 CI 流程**(打包、静态站点构建、任何用到 `*.png`/大文件的 job)checkout 都必须加 `lfs: true`。新增 workflow 时把它当默认项检查。
84+
- **如何更快定位**:遇到 `ERR_ELECTRON_BUILDER_CANNOT_EXECUTE` / app-builder 相关报错,**先抓完整堆栈**看具体失败的 Go 函数(`icons.*` = 图标、`rcedit` = 资源编辑、`nsis`/`makensis` = 安装器),而不是被泛化的 `CANNOT_EXECUTE` 字面误导。
85+
- **"本地能跑、CI 挂"的经典嫌疑**:环境差异优先排查——LFS 指针未拉取、平台可选依赖缺失、大小写敏感、密钥/环境变量缺失。
86+
- **假设要用证据证伪**:上游 issue 的相似结论(Defender 文件锁)只是候选假设,改完仍失败即应立刻放弃并回到堆栈证据,不要在错误方向叠加修补。
87+
- **误判产物要清理**:被证伪的修复(Defender 排除步骤)连同其误导性注释一并删除,避免留下错误认知。
88+
89+
## 参考
90+
91+
- 修复提交涉及文件:`.github/workflows/release.yml`、`.github/workflows/deploy-website.yml`
92+
- 关联特性:安装包自建服务器托管(`server/nginx/nginx.conf` 的 `/downloads/`、`client/electron-builder.yml` 双 publish)
93+
- app-builder 图标转换源码路径:`app-builder/pkg/icons/icon-converter.go`
94+
- [actions/checkout — lfs 选项](https://github.com/actions/checkout#usage)

‎docs/knowledge/index.md‎

Lines changed: 6 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -6,11 +6,14 @@
66

77
| 日期 | 标题 | 标签 |
88
|------|------|------|
9+
| 2026-07-30 | [Git LFS 图标未在 CI 拉取致 electron-builder 打包报 `ERR_ELECTRON_BUILDER_CANNOT_EXECUTE`](./bugs/2026-07-git-lfs-icon-electron-builder-ci-failure.md) | #CI #GitLFS #electron-builder #GitHubActions #发布 |
910
| 2026-07-30 | [reduced-motion 用 `animation-play-state: paused` 冻结入场动画致内容不可见](./bugs/2026-07-reduced-motion-fade-in-up-invisible.md) | #CSS #a11y #reduced-motion #animation |
1011

1112
## 💡 solutions/ — 技术方案
1213

13-
_(暂无)_
14+
| 日期 | 标题 | 标签 |
15+
|------|------|------|
16+
| 2026-07-31 | [安装包分发加速:CDN 接入方案与下载源可切换设计](./solutions/2026-07-installer-cdn-distribution.md) | #CDN #阿里云 #安装包分发 #下载加速 |
1417

1518
## 🧠 learnings/ — 学习笔记
1619

@@ -20,6 +23,6 @@ _(暂无)_
2023

2124
## 标签速查
2225

23-
- **技术**:#CSS #a11y #React #Vite #Tailwind
26+
- **技术**:#CSS #a11y #React #Vite #Tailwind #CI #GitLFS #electron-builder #GitHubActions
2427
- **类型**:#bug #方案 #学习
25-
- **模块**:#启动仪式 #reduced-motion #animation
28+
- **模块**:#启动仪式 #reduced-motion #animation #发布 #安装包
Lines changed: 135 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,135 @@
1+
# 知识卡片 · 技术方案
2+
3+
## 基本信息
4+
5+
| 字段 | 内容 |
6+
|------|------|
7+
| 标题 | 安装包分发加速:CDN 接入方案与下载源可切换设计 |
8+
| 日期 | 2026-07-31 |
9+
| 类型 | 技术方案 |
10+
| 标签 | #CDN #阿里云 #安装包分发 #下载加速 #electron-updater #成本优化 |
11+
12+
---
13+
14+
## 场景
15+
16+
官网安装包(约 172MB)托管于自建 ECS,实测下载速率过慢,需在可控成本内提速。
17+
18+
**实测基线数据**(源站 ECS,RTT 21ms):
19+
20+
| 并发 | 总吞吐 | 172MB 耗时 |
21+
|------|--------|-----------|
22+
| 1(浏览器默认) | ~140 KB/s | **20.5 分钟** |
23+
| 4 | 500 KB/s | 5.9 分钟 |
24+
| 8 | 764 KB/s | 3.8 分钟 |
25+
26+
**关键结论**:
27+
- 单流被限制在 ~140KB/s,原因非延迟、非丢包、非 `limit_rate`(BBR 启用前后无差异,见踩坑记录);
28+
- 多流可叠加,但**浏览器默认单连接**,故普通用户始终是 20 分钟级体验;
29+
- 服务端调参已触及天花板,**结构性解法只有 CDN**(就近边缘节点 + 充足带宽)。
30+
31+
## 方案描述
32+
33+
### 架构选择:CDN 回源 ECS(推荐)vs CDN + OSS 源站
34+
35+
| 维度 | CDN 回源 ECS | CDN + OSS 源站 |
36+
|------|-------------|---------------|
37+
| 新增组件 | 仅 CDN | CDN + OSS |
38+
| CI 改造 | **无需改动**(沿用现有 scp 同步) | 需新增 OSS 上传步骤 |
39+
| 缓存命中率 | 接近 100%(安装包为固定文件) | 同 |
40+
| ECS 负担 | 仅回源(每版本每节点一次) | 无 |
41+
| 源站可用性 | ECS 故障时无法回源(但已缓存内容仍可服务) | 更高 |
42+
43+
安装包是**内容不变的静态文件**,CDN 缓存命中率接近 100%,回源仅在新版本发布后每节点首次发生。因此 **CDN 回源 ECS 即可解决绝大部分带宽问题**,且改造量最小。OSS 方案可作为后续演进(追求源站高可用时)。
44+
45+
### 下载源可切换设计
46+
47+
无论选哪种架构,客户端与官网需要的都只是「一个可配置的下载源基址」。因此引入 `DOWNLOAD_BASE_URL`(GitHub Secret)作为统一开关:
48+
49+
- **未配置** → 回退自建服务器 `https://entropydecrease.com/downloads`(现状,零风险)
50+
- **已配置** → 官网下载直链与 electron-updater 更新源同时切换至 CDN
51+
52+
好处:CDN 开通进度不阻塞代码合并;出问题只需清空 Secret 重新部署即可**一键回退**。
53+
54+
## 关键代码
55+
56+
**官网下载源(`website/components/DownloadCta.tsx`)**
57+
58+
```ts
59+
const DOWNLOAD_BASE = (
60+
process.env.NEXT_PUBLIC_DOWNLOAD_BASE || "https://entropydecrease.com/downloads"
61+
).replace(/\/+$/, "");
62+
```
63+
64+
**官网构建注入(`.github/workflows/deploy-website.yml`)**
65+
66+
```yaml
67+
- name: Install dependencies and build
68+
env:
69+
NEXT_PUBLIC_DOWNLOAD_BASE: ${{ secrets.DOWNLOAD_BASE_URL }}
70+
```
71+
72+
**客户端更新源覆盖(`.github/workflows/release.yml`)**
73+
74+
```yaml
75+
- name: Build Electron app
76+
shell: bash
77+
env:
78+
DOWNLOAD_BASE_URL: ${{ secrets.DOWNLOAD_BASE_URL }}
79+
run: |
80+
cd client
81+
if [ -n "$DOWNLOAD_BASE_URL" ]; then
82+
npm run electron:build -- --publish never -c.publish.0.url="$DOWNLOAD_BASE_URL"
83+
else
84+
npm run electron:build -- --publish never
85+
fi
86+
```
87+
88+
`-c.publish.0.url` 覆盖 `electron-builder.yml` 中 generic provider 的地址,写入新包的 `app-update.yml`。
89+
90+
## 开通步骤(CDN 回源 ECS)
91+
92+
1. 阿里云 CDN 添加加速域名,如 `dl.entropydecrease.com`;
93+
2. **业务类型**选「大文件下载加速」;
94+
3. **源站**填 ECS 公网 IP,**回源 Host 填 `entropydecrease.com`**——这样命中 nginx 现有 `server_name`,**无需改动 nginx 配置,也无需为子域申请证书**(CDN 侧可免费签发 HTTPS 证书);
95+
4. 缓存规则:
96+
- `.exe` / `.blockmap` → 长缓存(如 30 天,文件名含版本号,天然唯一)
97+
- `.yml` / `.json` → **不缓存**(更新元数据必须实时;源站已设 `Cache-Control: no-cache`)
98+
5. DNS 添加 CNAME 指向 CDN 提供的地址;
99+
6. 在 GitHub 仓库 Secrets 添加 `DOWNLOAD_BASE_URL = https://dl.entropydecrease.com`;
100+
7. 触发官网部署与一次发版,验证生效。
101+
102+
## 优缺点
103+
104+
| 优点 | 缺点 |
105+
|------|------|
106+
| 边缘节点就近,单流吞吐大幅提升(对症浏览器单连接痛点) | 引入按量计费,需设费用预警 |
107+
| 下载流量与业务 API 带宽彻底解耦 | 公开大文件存在被刷流量风险 |
108+
| 按量付费无预付、无最低消费,初期月费可能仅几元 | 缓存策略配错(如缓存了 latest.yml)会导致更新异常 |
109+
| 一个 Secret 即可切换/回退,架构侵入极小 | — |
110+
111+
## 成本参考
112+
113+
按 172MB/次(≈0.168GB)估算,国内 CDN 首档约 0.2–0.24 元/GB(**实际以控制台阶梯价为准**):
114+
115+
| 月下载量 | 流量 | 预估月费 |
116+
|---------|------|---------|
117+
| 50 次 | 8.4 GB | 约 2 元 |
118+
| 200 次 | 34 GB | 约 8 元 |
119+
| 1000 次 | 168 GB | 约 40 元 |
120+
121+
OSS/存储侧可忽略(保留 3 个版本约 0.5GB)。流量稳定后可购买流量包,单价降至约 0.1–0.12 元/GB。
122+
123+
## 注意事项
124+
125+
- **必须先设防再上线**:费用预算提醒 + 资源用量预警 + Referer 防盗链 + 单 IP 频次限制;可选用量封顶。公开大文件按量计费若被恶意刷取,账单会飙升。
126+
- **`latest.yml` 绝不可被 CDN 缓存**:否则客户端长期读到旧版本,表现为「明明发布了新版却检测不到更新」。
127+
- **存量客户端更新源已固化**:已安装版本的 `app-update.yml` 指向旧地址,故切换 CDN 后 **ECS 与 GitHub 两侧资产仍需继续发布**,不可下线。
128+
- **CDN 回源可能触发源站限流**:nginx `/downloads/` 有 `limit_conn dl_conn 8`(按 IP)。若回源出现 503,需为 CDN 回源 IP 段放行或提高该限制。
129+
- 差量更新(blockmap)依赖 HTTP Range,CDN 需确认支持 Range 回源与分片缓存。
130+
131+
## 参考
132+
133+
- 相关文件:`website/components/DownloadCta.tsx`、`.github/workflows/deploy-website.yml`、`.github/workflows/release.yml`、`client/electron-builder.yml`、`server/nginx/nginx.conf`
134+
- 关联踩坑记录:[Git LFS 图标未在 CI 拉取致 electron-builder 打包失败](../bugs/2026-07-git-lfs-icon-electron-builder-ci-failure.md)
135+
- 前置实现:安装包自建服务器托管(nginx `/downloads/`、双 publish、`latest.json`)

‎website/components/DownloadCta.tsx‎

Lines changed: 9 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -6,8 +6,15 @@
66

77
import { useEffect, useState } from "react";
88

9-
/** 自建服务器下载源(release.yml CI 发版时同步安装包与 latest.json) */
10-
const DOWNLOAD_BASE = "https://entropydecrease.com/downloads";
9+
/**
10+
* 下载源基址。优先用构建时注入的 CDN 域名(deploy-website.yml 从
11+
* DOWNLOAD_BASE_URL Secret 传入);未配置时回退自建服务器,
12+
* 使 CDN 开通进度不阻塞代码,且随时可回退。
13+
* 末尾斜杠统一去除,避免拼接出双斜杠。
14+
*/
15+
const DOWNLOAD_BASE = (
16+
process.env.NEXT_PUBLIC_DOWNLOAD_BASE || "https://entropydecrease.com/downloads"
17+
).replace(/\/+$/, "");
1118
/** GitHub Releases 备用源 */
1219
const GITHUB_RELEASES = "https://github.com/Aparencia/Entropydecrease/releases/latest";
1320

0 commit comments

Comments
 (0)