mirror of
https://github.com/saphid/frame-control.git
synced 2026-10-06 08:00:32 +02:00
The bundled Python only trusts roots already in the Windows certificate store, which Windows fills lazily, so on a new install Steam store search, F-Droid downloads and the compat DB failed with CERTIFICATE_VERIFY_FAILED. fetch-deps.js now also bundles curl's pinned copy of Mozilla's CA list, and the server adds it to the default HTTPS context on top of the system certificates (before any urlopen, since urllib keeps the context it first builds). Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
125 lines
5.7 KiB
JavaScript
125 lines
5.7 KiB
JavaScript
// Downloads what the app bundles so users install nothing else: a standalone
|
|
// Python (python-build-standalone), adb (Android platform-tools) and a CA
|
|
// bundle. Each goes in build/deps/<os>-<arch>/{python,tools}, which package.json
|
|
// copies into the app's resources. Everything is pinned by version and SHA-256.
|
|
// node build/fetch-deps.js mac arm64 | win x64 | linux x64 arm64
|
|
const crypto = require("crypto");
|
|
const fs = require("fs");
|
|
const https = require("https");
|
|
const path = require("path");
|
|
const { execFileSync } = require("child_process");
|
|
|
|
const PY = "3.12.14+20260924";
|
|
const PY_URL = (triple) => "https://github.com/astral-sh/python-build-standalone/releases/download/"
|
|
+ `${PY.split("+")[1]}/cpython-${PY}-${triple}-install_only_stripped.tar.gz`;
|
|
const PYTHON = {
|
|
"mac-arm64": ["aarch64-apple-darwin", "c2edb321cd32ec2b170df208db0446dccc4398db602ca27cf2079098fb1f7d9d"],
|
|
"win-x64": ["x86_64-pc-windows-msvc", "c5bf8edfe858c1df9891be498b5bbc8761d383df5b9790658b088fea4870433a"],
|
|
"linux-x64": ["x86_64-unknown-linux-gnu", "269b2c99e4db15b242bf01832f4fea1e8f1a664f273cff519393f296e9820b41"],
|
|
"linux-arm64": ["aarch64-unknown-linux-gnu", "c8499b61252c433280f134df954464d19811527b31cb920c35fc6967c1222e35"],
|
|
};
|
|
|
|
// Google publishes no arm64 Linux platform-tools; there the app uses the system adb.
|
|
const PT = "37.0.1";
|
|
const PT_URL = (os) => `https://dl.google.com/android/repository/platform-tools_r${PT}-${os}.zip`;
|
|
const TOOLS = {
|
|
mac: ["darwin", "ee39ad5967e95c2a07f04dbcbde96b1a0c916ba376096db5d2f498b7727a5d1d", ["adb"]],
|
|
win: ["win", "45f4d63113e895ebde0c90f194099a4676b6ac653bd28d54314a9e022bbc1a99",
|
|
["adb.exe", "AdbWinApi.dll", "AdbWinUsbApi.dll", "libwinpthread-1.dll"]],
|
|
linux: ["linux", "d230f13842f60f782a8645f9c813f8f845bf36089ea7289f28c48f17979313f1", ["adb"]],
|
|
};
|
|
|
|
// Mozilla's CA list, as curl publishes it: Python on Windows only trusts roots
|
|
// already in the Windows store (see frame_host.trust_bundled_cas).
|
|
const CA = "2026-09-25";
|
|
const CA_SHA256 = "a41b5d356aea97a529fe27e0f7316d2f9d946d75927476cf9cf1b90637d00505";
|
|
|
|
// Parts of Python the server never imports (GUI, tests, packaging, headers).
|
|
const PRUNE = [
|
|
"include", "share", "Scripts", "libs", "tcl", "lib/pkgconfig", "lib/itcl4", "lib/tcl8", "lib/tcl8.6",
|
|
"lib/tk8.6", "lib/thread2.8", "bin/idle3", "bin/idle3.12", "bin/pip", "bin/pip3", "bin/pip3.12",
|
|
"bin/pydoc3", "bin/pydoc3.12", "bin/2to3", "bin/2to3-3.12", "bin/python3-config", "bin/python3.12-config",
|
|
...["test", "idlelib", "tkinter", "turtledemo", "ensurepip", "lib2to3", "site-packages/pip"]
|
|
.flatMap((d) => [`lib/python3.12/${d}`, `Lib/${d}`]),
|
|
];
|
|
|
|
function get(url) {
|
|
return new Promise((resolve, reject) => {
|
|
https.get(url, (res) => {
|
|
if (res.statusCode >= 300 && res.statusCode < 400 && res.headers.location) {
|
|
res.resume();
|
|
return resolve(get(res.headers.location));
|
|
}
|
|
if (res.statusCode !== 200) return reject(new Error(`${url}: HTTP ${res.statusCode}`));
|
|
const chunks = [];
|
|
res.on("data", (c) => chunks.push(c));
|
|
res.on("end", () => resolve(Buffer.concat(chunks)));
|
|
}).on("error", reject);
|
|
});
|
|
}
|
|
|
|
async function download(url, sha256, file) {
|
|
const data = await get(url);
|
|
const sum = crypto.createHash("sha256").update(data).digest("hex");
|
|
if (sum !== sha256) throw new Error(`checksum mismatch for ${url}: ${sum}`);
|
|
fs.writeFileSync(file, data);
|
|
}
|
|
|
|
// Windows' own bsdtar: Git's GNU tar, often first on PATH, reads C:\ as a remote host.
|
|
const TAR = process.platform === "win32" ? path.join(process.env.SystemRoot, "System32", "tar.exe") : "tar";
|
|
|
|
function extract(file, dir) {
|
|
fs.mkdirSync(dir, { recursive: true });
|
|
// bsdtar (macOS, Windows 10+) reads zip files; GNU tar doesn't, so fall back to unzip.
|
|
try { execFileSync(TAR, ["-xf", file, "-C", dir]); }
|
|
catch (e) {
|
|
if (!file.endsWith(".zip")) throw e;
|
|
execFileSync("unzip", ["-q", "-o", file, "-d", dir]);
|
|
}
|
|
fs.rmSync(file);
|
|
}
|
|
|
|
async function fetch(os, arch) {
|
|
const key = `${os}-${arch}`;
|
|
if (!PYTHON[key]) throw new Error(`no bundle for ${key}`);
|
|
const out = path.join(__dirname, "deps", key);
|
|
const stamp = path.join(out, ".version");
|
|
const version = `python ${PY}, platform-tools ${PT}, CA ${CA}`;
|
|
if (fs.existsSync(stamp) && fs.readFileSync(stamp, "utf8") === version) {
|
|
console.log(`${key}: already fetched (${version})`);
|
|
return;
|
|
}
|
|
fs.rmSync(out, { recursive: true, force: true });
|
|
fs.mkdirSync(out, { recursive: true });
|
|
|
|
const [triple, pySha] = PYTHON[key];
|
|
const tgz = path.join(out, "python.tar.gz");
|
|
await download(PY_URL(triple), pySha, tgz);
|
|
extract(tgz, out); // unpacks to python/
|
|
for (const p of PRUNE) fs.rmSync(path.join(out, "python", p), { recursive: true, force: true });
|
|
|
|
const tools = path.join(out, "tools");
|
|
fs.mkdirSync(tools);
|
|
if (!(os === "linux" && arch === "arm64")) {
|
|
const [name, ptSha, keep] = TOOLS[os];
|
|
const zip = path.join(out, "pt.zip");
|
|
const tmp = path.join(out, "pt");
|
|
await download(PT_URL(name), ptSha, zip);
|
|
extract(zip, tmp);
|
|
for (const f of [...keep, "NOTICE.txt", "source.properties"]) {
|
|
fs.copyFileSync(path.join(tmp, "platform-tools", f), path.join(tools, f));
|
|
}
|
|
if (os !== "win") fs.chmodSync(path.join(tools, "adb"), 0o755);
|
|
fs.rmSync(tmp, { recursive: true, force: true });
|
|
}
|
|
await download(`https://curl.se/ca/cacert-${CA}.pem`, CA_SHA256, path.join(tools, "cacert.pem"));
|
|
fs.writeFileSync(stamp, version);
|
|
console.log(`${key}: ${version} -> ${out}`);
|
|
}
|
|
|
|
(async () => {
|
|
const [os, ...archs] = process.argv.slice(2);
|
|
if (!os || !archs.length) throw new Error("usage: node build/fetch-deps.js <mac|win|linux> <arch>...");
|
|
for (const arch of archs) await fetch(os, arch);
|
|
})().catch((e) => { console.error(e.message); process.exit(1); });
|