feat: add trusted mirror update source

Version checks and downloads previously depended on GitHub Release. About & Updates now offers GitHub or a validated mirror source, keeps the selector beneath the startup-check switch, and disables it when startup checks are off.

The website resolves platform downloads from a bounded OSS release index with a GitHub fallback. Tagged releases publish and verify immutable OSS assets through OIDC before switching the latest-version index; deployment requires the configured Alibaba Cloud environment variables and role.

Release note: “关于与更新”新增 GitHub 与镜像节点选择,启动检查、手动检查和下载页使用同一可信来源;官网下载也可按系统、架构和安装包类型直接选择。
This commit is contained in:
mesalogo
2026-08-17 17:20:24 +08:00
parent ce15e7021c
commit dc8f86ff3e
32 changed files with 1680 additions and 66 deletions
+8 -6
View File
@@ -39,25 +39,27 @@ node sites/scripts/validate.mjs
node --check sites/app.js
```
校验脚本会检查必需文件、页内链接、本地资源、关键产品文案、主题与响应式规则,以及下载入口是否始终指向官方最新 Release。
校验脚本会检查必需文件、页内链接、本地资源、关键产品文案、主题与响应式
规则,以及下载选择器是否从受信任的正式发布索引加载并保留 GitHub
Release 回退入口。
## 下载入口
官网正文不展示具体版本号,三个系统下载按钮直接指向 GitHub 最新正式
官网正文不写死版本号,页面启动后读取最新正式发布索引。
Windows、macOS 和 Linux 下载卡片分别提供处理器架构与安装包类型选择器,
选择后直接下载经过发布校验的不可变版本对象。发布索引请求失败、
格式无效或返回非受信任的官方下载地址时,按钮继续指向 GitHub 最新正式
Release
```text
https://github.com/mesalogo/goodbuddy/releases/latest
```
新版本发布后 GitHub 会自动更新该地址的目标,官网无需同步修改版本号
或安装资产名称。用户在 Release 页面按系统与架构选择文件并核对
SHA-256 清单。
## 文件
- `index.html`:页面结构与简体中文内容
- `styles.css`:语义令牌、浅深主题、焦点与响应式布局
- `app.js`:主题、移动导航和当前章节
- `assets/goodbuddy-light.png``assets/goodbuddy-dark.png`:由 `npm run icons` 与桌面应用同步生成的官方品牌图标
- `assets/linux-plain.svg`Devicon v2.17.0 提供的黑白 Linux 图标,许可见 `assets/devicon-LICENSE`
- `scripts/validate.mjs`:无依赖静态检查
+132
View File
@@ -12,6 +12,137 @@
const systemTheme = window.matchMedia("(prefers-color-scheme: dark)");
const finePointer = window.matchMedia("(hover: hover) and (pointer: fine)");
const reducedMotion = window.matchMedia("(prefers-reduced-motion: reduce)");
const releaseManifestUrl =
"https://goodbuddy.oss-cn-hangzhou.aliyuncs.com/releases/latest.json";
const releaseFallbackUrl =
"https://github.com/mesalogo/goodbuddy/releases/latest";
const releaseStatus = document.querySelector("[data-release-status]");
const downloadCards = [
...document.querySelectorAll("[data-download-card]"),
];
const platformNames = {
windows: "Windows",
macos: "macOS",
linux: "Linux",
};
const formatNames = {
nsis: "安装版",
portable: "便携版",
dmg: "DMG",
zip: "ZIP",
AppImage: "AppImage",
deb: "DEB",
};
const formatFileSize = (bytes) => {
const megabytes = bytes / (1024 * 1024);
return `${megabytes >= 100 ? megabytes.toFixed(0) : megabytes.toFixed(1)} MB`;
};
const isTrustedReleaseUrl = (value) => {
try {
const url = new URL(value);
return (
url.protocol === "https:" &&
url.hostname === "goodbuddy.oss-cn-hangzhou.aliyuncs.com" &&
url.pathname.startsWith("/releases/")
);
} catch {
return false;
}
};
const configureDownloads = (release) => {
if (
release?.formatVersion !== 1 ||
release?.productName !== "GoodBuddy" ||
typeof release?.version !== "string" ||
!release?.targets
) {
throw new Error("发布索引格式无效");
}
const updateCard = (card) => {
const platform = card.dataset.downloadCard;
const archSelect = card.querySelector("[data-download-arch]");
const formatSelect = card.querySelector("[data-download-format]");
const link = card.closest(".download-card")?.querySelector("[data-release-link]");
const meta = card.closest(".download-card")?.querySelector("[data-download-meta]");
if (
!platform ||
!(archSelect instanceof HTMLSelectElement) ||
!(formatSelect instanceof HTMLSelectElement) ||
!(link instanceof HTMLAnchorElement) ||
!(meta instanceof HTMLElement)
) {
return;
}
const target = release.targets[`${platform}-${archSelect.value}`];
const file = target?.files?.[formatSelect.value];
if (
!file ||
typeof file.name !== "string" ||
!Number.isSafeInteger(file.size) ||
file.size < 1 ||
!isTrustedReleaseUrl(file.url)
) {
link.href = releaseFallbackUrl;
link.textContent =
`前往 GitHub 下载 ${platformNames[platform] ?? platform}`;
meta.textContent = "当前选项暂不可用,请在 GitHub Release 中选择文件。";
return;
}
link.href = file.url;
const platformName = platformNames[platform] ?? platform;
const archName =
platform === "macos" && archSelect.value === "arm64"
? "Apple 芯片"
: archSelect.value === "arm64"
? "ARM64"
: "x64";
const formatName = formatNames[formatSelect.value] ?? formatSelect.value;
link.textContent = `下载 ${platformName} ${archName} ${formatName}`;
meta.textContent =
`GoodBuddy ${release.version} · ${formatFileSize(file.size)} · ` +
`${archSelect.options[archSelect.selectedIndex]?.text ?? archSelect.value}`;
};
for (const card of downloadCards) {
const selects = card.querySelectorAll("select");
for (const select of selects) {
select.addEventListener("change", () => updateCard(card));
}
updateCard(card);
}
if (releaseStatus instanceof HTMLElement) {
releaseStatus.textContent =
`官方下载源已就绪:GoodBuddy ${release.version}` +
"请选择处理器和安装包类型。";
releaseStatus.classList.add("is-ready");
}
};
const loadRelease = async () => {
try {
const response = await fetch(releaseManifestUrl, {
cache: "no-store",
credentials: "omit",
});
if (!response.ok) {
throw new Error(`发布索引请求失败:${response.status}`);
}
configureDownloads(await response.json());
} catch {
if (releaseStatus instanceof HTMLElement) {
releaseStatus.textContent =
"官方下载源暂不可用,下载按钮已切换到 GitHub Release。";
releaseStatus.classList.add("is-fallback");
}
}
};
const getSavedTheme = () => {
try {
@@ -51,6 +182,7 @@
applyTheme(getSavedTheme() ?? (systemTheme.matches ? "dark" : "light"));
setHeaderState();
void loadRelease();
themeToggle?.addEventListener("click", () => {
applyTheme(root.dataset.theme === "dark" ? "light" : "dark", true);
+20
View File
@@ -0,0 +1,20 @@
The MIT License (MIT)
Copyright (c) 2015 konpa
Permission is hereby granted, free of charge, to any person obtaining a copy of
this software and associated documentation files (the "Software"), to deal in
the Software without restriction, including without limitation the rights to
use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of
the Software, and to permit persons to whom the Software is furnished to do so,
subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS
FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR
COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER
IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
+1
View File
@@ -0,0 +1 @@
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 128 128"><path fill-rule="evenodd" clip-rule="evenodd" d="M113.823 104.595c-1.795-1.478-3.629-2.921-5.308-4.525-1.87-1.785-3.045-3.944-2.789-6.678.147-1.573-.216-2.926-2.113-3.452.446-1.154.864-1.928 1.033-2.753.188-.92.178-1.887.204-2.834.264-9.96-3.334-18.691-8.663-26.835-2.454-3.748-5.017-7.429-7.633-11.066-4.092-5.688-5.559-12.078-5.633-18.981a47.564 47.564 0 00-1.081-9.475C80.527 11.956 77.291 7.233 71.422 4.7c-4.497-1.942-9.152-2.327-13.901-1.084-6.901 1.805-11.074 6.934-10.996 14.088.074 6.885.417 13.779.922 20.648.288 3.893-.312 7.252-2.895 10.34-2.484 2.969-4.706 6.172-6.858 9.397-1.229 1.844-2.317 3.853-3.077 5.931-2.07 5.663-3.973 11.373-7.276 16.5-1.224 1.9-1.363 4.026-.494 6.199.225.563.363 1.429.089 1.882-2.354 3.907-5.011 7.345-10.066 8.095-3.976.591-4.172 1.314-4.051 5.413.1 3.337.061 6.705-.28 10.021-.363 3.555.008 4.521 3.442 5.373 7.924 1.968 15.913 3.647 23.492 6.854 3.227 1.365 6.465.891 9.064-1.763 2.713-2.771 6.141-3.855 9.844-3.859 6.285-.005 12.572.298 18.86.369 1.702.02 2.679.653 3.364 2.199.84 1.893 2.26 3.284 4.445 3.526 4.193.462 8.013-.16 11.19-3.359 3.918-3.948 8.436-7.066 13.615-9.227 1.482-.619 2.878-1.592 4.103-2.648 2.231-1.922 2.113-3.146-.135-5zM62.426 24.12c.758-2.601 2.537-4.289 5.243-4.801 2.276-.43 4.203.688 5.639 3.246 1.546 2.758 2.054 5.64.734 8.658-1.083 2.474-1.591 2.707-4.123 1.868-.474-.157-.937-.343-1.777-.652.708-.594 1.154-1.035 1.664-1.382 1.134-.772 1.452-1.858 1.346-3.148-.139-1.694-1.471-3.194-2.837-3.175-1.225.017-2.262 1.167-2.4 2.915-.086 1.089.095 2.199.173 3.589-3.446-1.023-4.711-3.525-3.662-7.118zm-12.75-2.251c1.274-1.928 3.197-2.314 5.101-1.024 2.029 1.376 3.547 5.256 2.763 7.576-.285.844-1.127 1.5-1.716 2.241l-.604-.374c-.23-1.253-.276-2.585-.757-3.733-.304-.728-1.257-1.184-1.919-1.762-.622.739-1.693 1.443-1.757 2.228-.088 1.084.477 2.28.969 3.331.311.661 1.001 1.145 1.713 1.916l-1.922 1.51c-3.018-2.7-3.915-8.82-1.871-11.909zM87.34 86.075c-.203 2.604-.5 2.713-3.118 3.098-1.859.272-2.359.756-2.453 2.964a101.744 101.744 0 00-.012 7.753c.061 1.77-.537 3.158-1.755 4.393-6.764 6.856-14.845 10.105-24.512 8.926-4.17-.509-6.896-3.047-9.097-6.639.98-.363 1.705-.607 2.412-.894 3.122-1.27 3.706-3.955 1.213-6.277-1.884-1.757-3.986-3.283-6.007-4.892-1.954-1.555-3.934-3.078-5.891-4.629-1.668-1.323-2.305-3.028-2.345-5.188-.094-5.182.972-10.03 3.138-14.747 1.932-4.209 3.429-8.617 5.239-12.885.935-2.202 1.906-4.455 3.278-6.388 1.319-1.854 2.134-3.669 1.988-5.94-.084-1.276-.016-2.562-.016-3.843l.707-.352c1.141.985 2.302 1.949 3.423 2.959 4.045 3.646 7.892 3.813 12.319.67 1.888-1.341 3.93-2.47 5.927-3.652.497-.294 1.092-.423 1.934-.738 2.151 5.066 4.262 10.033 6.375 15 1.072 2.524 1.932 5.167 3.264 7.547 2.671 4.775 4.092 9.813 4.07 15.272-.012 2.83.137 5.67-.081 8.482z"/></svg>

After

Width:  |  Height:  |  Size: 2.8 KiB

+66 -9
View File
@@ -229,14 +229,33 @@
<path d="m3 5 8-1v8H3V5Zm10-1.3L21 3v9h-8V3.7ZM3 14h8v8l-8-1v-7Zm10 0h8v9l-8-1v-8Z" />
</svg>
</div>
<div><h3>Windows</h3><p>x64 / arm64 · NSIS / 便携版</p></div>
<div><h3>Windows</h3><p>x64 / arm64 · 安装版 / 便携版</p></div>
<div class="download-options" data-download-card="windows">
<label>
<span>处理器</span>
<select data-download-arch aria-label="Windows 处理器架构">
<option value="x64">x64Intel / AMD</option>
<option value="arm64">ARM64</option>
</select>
</label>
<label>
<span>类型</span>
<select data-download-format aria-label="Windows 安装包类型">
<option value="nsis">安装版(推荐)</option>
<option value="portable">便携版 ZIP</option>
</select>
</label>
</div>
<a
class="button button--download"
href="https://github.com/mesalogo/goodbuddy/releases/latest"
target="_blank"
rel="noreferrer"
data-release-link
>下载 Windows 版<span class="sr-only">(在新窗口打开)</span></a>
>下载 Windows 版<span class="sr-only">(在新窗口打开)</span></a>
<p class="download-meta" data-download-meta aria-live="polite">
正在获取最新版本,暂时可前往 GitHub 下载。
</p>
</article>
<article class="download-card">
<div class="platform-icon">
@@ -244,32 +263,70 @@
<path d="M16.8 12.7c0-2.7 2.2-4 2.3-4.1A5 5 0 0 0 15.2 6c-1.7-.2-3.2 1-4.1 1-.9 0-2.2-1-3.6-1-1.8 0-3.5 1.1-4.5 2.7-2 3.5-.5 8.7 1.4 11.5.9 1.4 2 2.8 3.5 2.7 1.4 0 1.9-.9 3.7-.9 1.7 0 2.2.9 3.7.9s2.5-1.4 3.4-2.7a10 10 0 0 0 1.6-3.3 4.6 4.6 0 0 1-3.5-4.2ZM14.1 4.3A4.7 4.7 0 0 0 15.2 1a4.8 4.8 0 0 0-3.1 1.6A4.4 4.4 0 0 0 11 5.8c1.2.1 2.3-.5 3.1-1.5Z" />
</svg>
</div>
<div><h3>macOS</h3><p>x64 / arm64 · DMG / ZIP</p></div>
<div><h3>macOS</h3><p>Apple 芯片 / Intel · DMG / ZIP</p></div>
<div class="download-options" data-download-card="macos">
<label>
<span>处理器</span>
<select data-download-arch aria-label="macOS 处理器架构">
<option value="arm64">Apple 芯片(推荐)</option>
<option value="x64">Intel</option>
</select>
</label>
<label>
<span>类型</span>
<select data-download-format aria-label="macOS 安装包类型">
<option value="dmg">DMG(推荐)</option>
<option value="zip">ZIP</option>
</select>
</label>
</div>
<a
class="button button--download"
href="https://github.com/mesalogo/goodbuddy/releases/latest"
target="_blank"
rel="noreferrer"
data-release-link
>下载 macOS 版<span class="sr-only">(在新窗口打开)</span></a>
>下载 macOS 版<span class="sr-only">(在新窗口打开)</span></a>
<p class="download-meta" data-download-meta aria-live="polite">
正在获取最新版本,暂时可前往 GitHub 下载。
</p>
</article>
<article class="download-card">
<div class="platform-icon">
<svg viewBox="0 0 24 24" aria-hidden="true">
<path d="M12 3c-3 0-4.7 2.5-4.5 5.4-1.3 1.4-2 3.4-2 5.6 0 3.9 2.9 7 6.5 7s6.5-3.1 6.5-7c0-2.2-.7-4.2-2-5.6C16.7 5.5 15 3 12 3Z" />
<path d="M9.3 10.2h.1M14.6 10.2h.1M9.5 15c1.6 1.2 3.4 1.2 5 0M7 19l-2 2M17 19l2 2" />
</svg>
<img src="./assets/linux-plain.svg" alt="" />
</div>
<div><h3>Linux</h3><p>x64 / arm64 · AppImage / DEB</p></div>
<div class="download-options" data-download-card="linux">
<label>
<span>处理器</span>
<select data-download-arch aria-label="Linux 处理器架构">
<option value="x64">x64Intel / AMD</option>
<option value="arm64">ARM64</option>
</select>
</label>
<label>
<span>类型</span>
<select data-download-format aria-label="Linux 安装包类型">
<option value="AppImage">AppImage(推荐)</option>
<option value="deb">DEB</option>
</select>
</label>
</div>
<a
class="button button--download"
href="https://github.com/mesalogo/goodbuddy/releases/latest"
target="_blank"
rel="noreferrer"
data-release-link
>下载 Linux 版<span class="sr-only">(在新窗口打开)</span></a>
>下载 Linux 版<span class="sr-only">(在新窗口打开)</span></a>
<p class="download-meta" data-download-meta aria-live="polite">
正在获取最新版本,暂时可前往 GitHub 下载。
</p>
</article>
</div>
<div class="download-release-status" data-release-status role="status">
正在连接官方下载源…
</div>
<aside class="domestic-support" aria-labelledby="domestic-support-title">
<div>
+28 -1
View File
@@ -11,6 +11,8 @@ const requiredFiles = [
"app.js",
"assets/goodbuddy-light.png",
"assets/goodbuddy-dark.png",
"assets/linux-plain.svg",
"assets/devicon-LICENSE",
"README.md",
];
@@ -132,6 +134,31 @@ for (const link of releaseLinks) {
report(/target="_blank"/.test(link), `下载入口必须在新窗口打开:${link}`);
report(/rel="[^"]*noreferrer[^"]*"/.test(link), `下载入口缺少 noreferrer${link}`);
}
report(
(html.match(/data-download-card="(?:windows|macos|linux)"/g) ?? []).length === 3,
"下载区必须包含 Windows、macOS 和 Linux 选择器",
);
report(
(html.match(/data-download-arch/g) ?? []).length === 3,
"每个平台必须提供处理器架构选择器",
);
report(
(html.match(/data-download-format/g) ?? []).length === 3,
"每个平台必须提供安装包类型选择器",
);
report(/data-release-status/.test(html), "下载区缺少发布源状态");
report(
appJs.includes(
"https://goodbuddy.oss-cn-hangzhou.aliyuncs.com/releases/latest.json",
),
"官网必须从 GoodBuddy OSS 加载最新发布索引",
);
report(
appJs.includes("https://github.com/mesalogo/goodbuddy/releases/latest"),
"官网必须保留 GitHub Release 回退地址",
);
report(/credentials:\s*"omit"/.test(appJs), "OSS 发布索引请求不得携带凭据");
report(/isTrustedReleaseUrl/.test(appJs), "OSS 下载链接缺少来源校验");
const ids = [...html.matchAll(/\sid="([^"]+)"/g)].map((match) => match[1]);
const duplicateIds = ids.filter((id, index) => ids.indexOf(id) !== index);
@@ -173,7 +200,7 @@ for (const link of externalBlankLinks) {
report(
!/<a\b[^>]*href="[^"]+\.(?:exe|dmg|zip|AppImage|deb)(?:[?#][^"]*)?"/i.test(html),
"具体安装资产链接应由 Release 页面统一提供",
"具体安装资产链接应由 OSS 发布索引动态提供",
);
report(
!/(?:react|vue|angular|bootstrap|tailwind)(?:\.min)?\.(?:js|css)/i.test(html),
+96 -8
View File
@@ -1258,6 +1258,39 @@ p {
font-size: 0.75rem;
}
.download-options {
display: grid;
grid-column: 1 / -1;
grid-template-columns: repeat(2, minmax(0, 1fr));
gap: var(--space-3);
}
.download-options label {
display: grid;
gap: var(--space-2);
color: var(--text-muted);
font-size: var(--font-caption);
font-weight: 680;
}
.download-options select {
width: 100%;
min-height: 40px;
padding: 0 var(--space-3);
border: 1px solid var(--border-control);
border-radius: var(--radius-control);
background: var(--surface-subtle);
color: var(--text-primary);
font: inherit;
font-size: 0.75rem;
}
.download-options select:focus-visible {
border-color: var(--accent);
outline: 2px solid color-mix(in srgb, var(--accent) 28%, transparent);
outline-offset: 2px;
}
.platform-icon {
display: grid;
width: 48px;
@@ -1278,14 +1311,66 @@ p {
stroke-width: 1.3;
}
.download-card:nth-child(3) .platform-icon svg {
fill: none;
.platform-icon img {
width: 28px;
height: 28px;
object-fit: contain;
}
[data-theme="dark"] .platform-icon img {
filter: invert(1);
}
.button--download {
grid-column: 1 / -1;
width: 100%;
width: fit-content;
min-height: 40px;
justify-content: flex-start;
justify-self: start;
padding: var(--space-2) 0;
margin-top: var(--space-2);
border: 0;
background: transparent;
color: var(--accent);
overflow-wrap: anywhere;
text-align: left;
text-decoration: underline;
text-decoration-color: transparent;
text-underline-offset: 4px;
}
.button--download:not(.is-disabled):hover {
color: var(--accent-hover);
text-decoration-color: currentColor;
transform: translateX(2px);
}
.download-meta {
grid-column: 1 / -1;
min-height: 18px;
text-align: left;
}
.download-release-status {
padding: var(--space-3) var(--space-4);
margin-top: var(--space-4);
border: 1px solid var(--border-default);
border-radius: var(--radius-control);
background: var(--surface-subtle);
color: var(--text-secondary);
font-size: 0.75rem;
text-align: center;
}
.download-release-status.is-ready {
border-color: color-mix(in srgb, var(--success) 34%, var(--border-default));
background: var(--success-subtle);
color: var(--success);
}
.download-release-status.is-fallback {
border-color: color-mix(in srgb, var(--warning) 34%, var(--border-default));
color: var(--text-secondary);
}
.domestic-support {
@@ -1601,7 +1686,7 @@ p {
}
.download-card {
grid-template-columns: auto 1fr auto;
grid-template-columns: auto 1fr;
align-items: center;
}
@@ -1610,9 +1695,8 @@ p {
}
.button--download {
grid-column: auto;
width: auto;
margin-top: 0;
grid-column: 1 / -1;
width: fit-content;
}
.assistant-grid {
@@ -1802,9 +1886,13 @@ p {
grid-template-columns: auto 1fr;
}
.download-options {
grid-template-columns: 1fr;
}
.button--download {
grid-column: 1 / -1;
width: 100%;
width: fit-content;
}
.domestic-support {