mirror of
https://github.com/ksyasuda/SubMiner.git
synced 2026-08-01 19:21:34 -07:00
fix(anime): pin linux-x64 bridge checksum and fetch release by tag
- Add PINNED_BUNDLE_SHA256 entry for linux-x64-bundle.zip so Linux no longer refuses to start with "No pinned checksum" - Fetch the bridge release by its pinned tag (releases/tags/<tag>) instead of listing newest releases, so an upstream publish can't swap in an unverified asset - Update docs and changelog to describe the pin/tag behavior
This commit is contained in:
@@ -4,6 +4,7 @@ import { mkdtemp, mkdir, writeFile } from 'node:fs/promises';
|
||||
import { tmpdir } from 'node:os';
|
||||
import path from 'node:path';
|
||||
import {
|
||||
bundleReleaseUrl,
|
||||
findBundleBinaries,
|
||||
PINNED_BUNDLE_SHA256,
|
||||
PINNED_BUNDLE_TAG,
|
||||
@@ -26,34 +27,71 @@ test('resolveBundleAssetName returns null for unpublished combinations', () => {
|
||||
assert.equal(resolveBundleAssetName('freebsd', 'x64'), null);
|
||||
});
|
||||
|
||||
test('selectBundleAsset skips releases without a matching asset', () => {
|
||||
const releases = [
|
||||
// The iOS runtime release carries no desktop bundle.
|
||||
{ tag_name: 'ios-runtime-v7', assets: [{ name: 'MExtensionServer-ios.jar' }] },
|
||||
const PINNED_RELEASE = {
|
||||
tag_name: PINNED_BUNDLE_TAG,
|
||||
assets: [
|
||||
{
|
||||
tag_name: 'v1.0.6.0',
|
||||
assets: [
|
||||
{
|
||||
name: 'macOS-arm64-bundle.zip',
|
||||
browser_download_url: 'https://example.test/macOS-arm64-bundle.zip',
|
||||
size: 133_058_560,
|
||||
},
|
||||
],
|
||||
name: 'macOS-arm64-bundle.zip',
|
||||
browser_download_url: 'https://example.test/macOS-arm64-bundle.zip',
|
||||
size: 133_058_560,
|
||||
},
|
||||
];
|
||||
],
|
||||
};
|
||||
|
||||
const asset = selectBundleAsset(releases, 'macOS-arm64-bundle.zip');
|
||||
assert.equal(asset?.tagName, 'v1.0.6.0');
|
||||
test('selectBundleAsset reads the by-tag endpoint payload', () => {
|
||||
const asset = selectBundleAsset(PINNED_RELEASE, 'macOS-arm64-bundle.zip');
|
||||
assert.equal(asset?.tagName, PINNED_BUNDLE_TAG);
|
||||
assert.equal(asset?.downloadUrl, 'https://example.test/macOS-arm64-bundle.zip');
|
||||
assert.equal(asset?.sizeBytes, 133_058_560);
|
||||
});
|
||||
|
||||
test('selectBundleAsset skips releases without a matching asset', () => {
|
||||
const releases = [
|
||||
// The iOS runtime release carries no desktop bundle.
|
||||
{ tag_name: 'ios-runtime-v7', assets: [{ name: 'MExtensionServer-ios.jar' }] },
|
||||
PINNED_RELEASE,
|
||||
];
|
||||
|
||||
const asset = selectBundleAsset(releases, 'macOS-arm64-bundle.zip');
|
||||
assert.equal(asset?.tagName, PINNED_BUNDLE_TAG);
|
||||
});
|
||||
|
||||
test('selectBundleAsset ignores releases newer than the pin', () => {
|
||||
const releases = [
|
||||
{
|
||||
tag_name: 'v9.9.9.9',
|
||||
assets: [
|
||||
{
|
||||
name: 'macOS-arm64-bundle.zip',
|
||||
browser_download_url: 'https://example.test/unpinned.zip',
|
||||
size: 1,
|
||||
},
|
||||
],
|
||||
},
|
||||
PINNED_RELEASE,
|
||||
];
|
||||
|
||||
const asset = selectBundleAsset(releases, 'macOS-arm64-bundle.zip');
|
||||
assert.equal(asset?.tagName, PINNED_BUNDLE_TAG);
|
||||
assert.equal(asset?.downloadUrl, 'https://example.test/macOS-arm64-bundle.zip');
|
||||
});
|
||||
|
||||
test('selectBundleAsset returns null when nothing matches', () => {
|
||||
assert.equal(selectBundleAsset([{ tag_name: 'v1', assets: [] }], 'linux-x64-bundle.zip'), null);
|
||||
assert.equal(
|
||||
selectBundleAsset([{ tag_name: PINNED_BUNDLE_TAG, assets: [] }], 'linux-x64-bundle.zip'),
|
||||
null,
|
||||
);
|
||||
assert.equal(selectBundleAsset([], 'linux-x64-bundle.zip'), null);
|
||||
assert.equal(selectBundleAsset({ message: 'rate limited' }, 'linux-x64-bundle.zip'), null);
|
||||
});
|
||||
|
||||
test('bundleReleaseUrl targets the pinned tag', () => {
|
||||
assert.equal(
|
||||
bundleReleaseUrl(),
|
||||
`https://api.github.com/repos/1Selxo/M-Extension-Server/releases/tags/${PINNED_BUNDLE_TAG}`,
|
||||
);
|
||||
});
|
||||
|
||||
test('findBundleBinaries locates the nested jre and jar', async () => {
|
||||
const root = await mkdtemp(path.join(tmpdir(), 'subminer-bundle-'));
|
||||
await mkdir(path.join(root, 'jre', 'jre', 'bin'), { recursive: true });
|
||||
@@ -103,15 +141,19 @@ test('verifyPinnedBundle accepts a matching hash and rejects a mismatch', () =>
|
||||
});
|
||||
|
||||
test('verifyPinnedBundle refuses an asset that has no pin', () => {
|
||||
const result = verifyPinnedBundle('linux-x64-bundle.zip', new Uint8Array([1, 2, 3]));
|
||||
const result = verifyPinnedBundle('windows-x64-bundle.zip', new Uint8Array([1, 2, 3]));
|
||||
assert.equal(result.ok, false);
|
||||
assert.match((result as { reason: string }).reason, /No pinned checksum/);
|
||||
});
|
||||
|
||||
test('the pinned tag and macOS arm64 hash are the verified release', () => {
|
||||
test('the pinned tag and hashes are the verified release', () => {
|
||||
assert.equal(PINNED_BUNDLE_TAG, 'v1.0.6.0');
|
||||
assert.equal(
|
||||
PINNED_BUNDLE_SHA256['macOS-arm64-bundle.zip'],
|
||||
'5f4fb03abfe88bc46ddf5f4d8221156ee2d66b9cbad7c4bc3ade4baf3a4266e6',
|
||||
);
|
||||
assert.equal(
|
||||
PINNED_BUNDLE_SHA256['linux-x64-bundle.zip'],
|
||||
'c2b869d3905b06a308517fec0b44f70ff76f7212230c60710bba39a7025c3a69',
|
||||
);
|
||||
});
|
||||
|
||||
@@ -8,8 +8,15 @@ import path from 'node:path';
|
||||
* required.
|
||||
*/
|
||||
|
||||
export const BUNDLE_RELEASES_URL =
|
||||
'https://api.github.com/repos/1Selxo/M-Extension-Server/releases?page=1&per_page=10';
|
||||
const BUNDLE_REPO_API = 'https://api.github.com/repos/1Selxo/M-Extension-Server';
|
||||
|
||||
/**
|
||||
* Fetch the pinned release by tag rather than listing releases: upstream ships
|
||||
* several a week, so a paged list would scroll the pinned tag off page one.
|
||||
*/
|
||||
export function bundleReleaseUrl(tagName: string = PINNED_BUNDLE_TAG): string {
|
||||
return `${BUNDLE_REPO_API}/releases/tags/${encodeURIComponent(tagName)}`;
|
||||
}
|
||||
|
||||
/**
|
||||
* The bridge release this integration was verified against.
|
||||
@@ -24,6 +31,7 @@ export const PINNED_BUNDLE_TAG = 'v1.0.6.0';
|
||||
/** SHA-256 of each pinned asset, keyed by release asset name. */
|
||||
export const PINNED_BUNDLE_SHA256: Readonly<Record<string, string>> = {
|
||||
'macOS-arm64-bundle.zip': '5f4fb03abfe88bc46ddf5f4d8221156ee2d66b9cbad7c4bc3ade4baf3a4266e6',
|
||||
'linux-x64-bundle.zip': 'c2b869d3905b06a308517fec0b44f70ff76f7212230c60710bba39a7025c3a69',
|
||||
};
|
||||
|
||||
/**
|
||||
@@ -130,12 +138,23 @@ interface GithubRelease {
|
||||
}
|
||||
|
||||
/**
|
||||
* Pick the newest release carrying an asset for this platform. Releases are
|
||||
* returned newest-first, and some (the iOS runtime) carry no desktop bundle.
|
||||
* Pick the asset for this platform from the pinned release. Selecting "newest"
|
||||
* instead would download a release whose checksum we never computed, so every
|
||||
* upstream publish would break the install with a mismatch.
|
||||
*/
|
||||
export function selectBundleAsset(releases: unknown, assetName: string): BundleAsset | null {
|
||||
if (!Array.isArray(releases)) return null;
|
||||
for (const release of releases as GithubRelease[]) {
|
||||
export function selectBundleAsset(
|
||||
releases: unknown,
|
||||
assetName: string,
|
||||
tagName: string = PINNED_BUNDLE_TAG,
|
||||
): BundleAsset | null {
|
||||
// Accepts either a single release (the by-tag endpoint) or a list.
|
||||
const candidates = Array.isArray(releases)
|
||||
? releases
|
||||
: releases && typeof releases === 'object'
|
||||
? [releases]
|
||||
: [];
|
||||
for (const release of candidates as GithubRelease[]) {
|
||||
if (release.tag_name !== tagName) continue;
|
||||
const asset = release.assets?.find((candidate) => candidate.name === assetName);
|
||||
if (asset?.browser_download_url && release.tag_name) {
|
||||
return {
|
||||
|
||||
Reference in New Issue
Block a user